先看几道例题,以下代码会输出什么?
ini
console.log(text);
var text = '学习前端';
输出:undefined
arduino
console.log(text);
let text = '学习前端'
输出:ReferenceError:Cannot access 'text' before initialization。
arduino
console.log(text);
if(true){
var text = '学习前端'
}
输出:undefined
arduino
console.log(text);
if(false){
var text = '学习前端'
}
输出:undefined
arduino
console.log(text);
if(true){
let text = '学习前端'
}
输出:ReferenceError: text is not defined
arduino
console.log(text);
function init(){
var text = '学习前端'
}
输出:ReferenceError: text is not defined
相信彦祖们都轻松拿捏,为什么var会有这些不寻常的表现呢?为什么要引入let const呢?
变量提升
使用var声明的变量会存在变量提升。什么是变量提升呢?是js代码编译的过程中,js引擎把声明部分提升到代码开头这个行为。并且会给变量设置为一个默认值undefined、
ini
console.log(text);
var text = '学习前端';
模拟这段代码被编译后的结果:
ini
vat text = undefined;
console.log(text);
text = '学习前端';
变量提升到代码开头,这个开头是哪里的开头呢? 是全局的开头? 还是函数的开头?还是一个语句块的开头?
这就需要先去了解作用域。
作用域
简单的理解:作用域就是变量与函数的可访问范围。
再ES6之前,作用域只有两种:全局作用域 和函数作用域。
这两个作用域也就控制了变量提升的位置。
arduino
console.log(text); // 报错 text is not defined
function init(){
var text = '学习前端'
}
在ES6之前,由于没有块级作用域的原因,总会有一些奇怪的现象。
arduino
console.log(text); //undefined
if(false){
var text = '学习前端'
}
可以看到这段代码也是被提升了。 和if条件没有关系, 可能有些初学者会认为条件没满足,是不会创建text,这个观点是错误的。
还有在循环块中, 循环结束之后还是可以访问到变量。
css
for(var i = 0; i < 4;i++){
}
console.log(i); //4
为了解决上述的问题, ES6引入了let const 关键字。这样js也拥有了块级作用域。
scss
//全局作用域
var text = '学习前端';
//if块
if(true){};
//for循环块
for(let i = 0; i<100; i++){
}
//块
{}
let和const
使用let和const声明的变量只能在块级作用域中访问。
特点:
- 不会被变量提升
arduino
console.log(text); //ReferenceError: text is not defined
if(true){
let text = '学习前端'
}
- 不能重复声明
- 临时性死区:在声明前访问变量会报错。
arduino
if (true) {
console.log(text); //ReferenceError: Cannot access 'text' before initialization
let text = "学习前端";
}
不同点:let声明之后可以修改, const声明的值,不能被修改。
截止到现在,开篇的几个例题都是可以解释了, 但还可以再深入了解一下。
JS如何支持块级作用域的呢?
JS是如何保持了var的变量提升, 又保持了块级作用域呢?
在执行一段代码之前,第一步会进行编译创建执行上下文,使用var声明的变量会放在变量环境中,使用let const声明的变量,会放在词法环境中。
在访问一个变量的时候,会先访问词法环境 ,再访问变量环境。
词法环境内部维护了一个栈结构,解析到一个作用域块,就会把这个块压入栈中,当这个作用域执行完毕之后,就会将这块弹出, 具体查找方式沿着词法环境的栈顶向下查询,找不到再去变量环境中查找。
javascript
// 全局词法环境
let globalVar = '全局变量';
function func() {
// func 的词法环境
let localVar = '局部变量';
let a = 1;
// func 的 变量环境
var c = 2;
{
//当前块的词法环境
let a = 3;
console.log(a); //3 ,从当前词法环境中找
console.log(c); //2 从当前词法环境中没找到,去变量环境中找到了
}
console.log(a);//1 从词法作用域中找
console.log(localVar); // 从当前 func 的词法环境访问
console.log(globalVar); // 从全局词法环境访问
}
func(); // 3 ,2 ,1 ,局部变量,全局变量
上述代码中,func函数中 解析到块结构后,将这个块压入到栈中,访问a变量时,从栈顶开始查找,这个块执行结束后,就会被弹出; 17行代码再去访问a变量时,从栈顶开始查找,现在的词法环境栈中只有func的块,所以输出了1。
整理下话术:
面试官:说一说var let const的区别
首先这三个都是JS中用于声明变量的。他们的区别是
- 在作用域方面,var是函数作用域,在函数作用域中使用var声明变量之后, 整个函数中都可以访问到,在函数外无法访问,而let和const是块级作用域,由大括号括起来的块中,只能被该块访问。
- 使用var声明的变量,存在变量提升,表现为在声明语句之前可以访问该变量,但是值为undefined。let和const不存在变量提升,在声明之前存在暂时性死区,访问会报错。
- 重复声明方面,var可以重复声明同一个变量,后者会覆盖前者。let和const 不允许重复声明相同变量。
- 值修改方面,var和let可以对声明后的值进行重新赋值,而const不允许。
- 在创建执行上下文时,var被放在了变量环境中,而let和const声明的变量放在了词法环境中,在访问一个变量时,先访问词法环境再访问变量环境。
leetcode
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
分析:题目中的关键字,有序,只有一个目标值,使用二分法。
ini
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left = 0;
let right = nums.length - 1;
while(left <=right){
const mind = Math.floor((left + right) /2)
if(target > nums[mind]){
left = mind + 1
}else if(target < nums[mind]){
right = mind - 1
}else{
return mind
}
}
return -1
};