js中的变量提升
ES6中为何要提出用let和const来定义变量?使用var会有什么弊端呢?其中最关键的就是:使用var关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内部,则视为在全局作用域的顶部)。这就是所谓的变量提升。
栗子如下:
function getValue(condition){ if(condition){ var value = 'blue'; return value; }else{ return null; } }
实际上当condition为false或者true时候value都被创建。在else的语句中value的值也是可以被访问的,是undefined。
ES6中引入块级声明:目的就是防止这种变量提升的现象。让声明的变量在指定的块级的作用域外无法被访问。创建形式如下:
1,在一个函数内部
2,由一对花括号包裹的代码块内部
function getValue(condition){ if(condition){ let value = 'blue'; return value; }else{ return null; } }
使用let声明变量,该声明没有被提升到函数定义的顶部。因此value值if代码块之外无法访问,在condition为false时候,该变量永远不会被声明并初始化。
使用let定义变量要注意禁止变量重复定义。
常量声明:
const进行变量的声明设置完成后就不允许再被更改,所以,所有的const变量都需要在声明时候进行初始化,不初始化会报错。
let和const声明的对比:
相同点:1,都是块级声明,不存在变量提升,2,如果变量在同一作用域中(无论是全局还是函数作用域)之前声明过,再用let或者const进行重复定义会报错。
使用const声明对象:
const person = { name:'miya' } person.name = 'jone' person Object {name: "jone"}
Object {name: "jone"}
person = {
name:'chris'
}
VM183:1 Uncaught TypeError: Assignment to constant variable.(…)
如果常量是一个对象,对象所包含的值是可以被修改的。但是尝试修改person对象本身就会报错。const阻止的是对于变量绑定的修改,而不阻止对成员值得修改。
还有个相同点就是:暂时性死区
使用let或者const声明变量时候,在未达到声明位置之前都是无法访问的,试图访问会导致引用错误。
js引擎在处理var时将声明提升到函数或者全局作用域的顶部,而在处理let或者cosnt时,则会将声明放入暂时性死区。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除,才可以安全使用。
暂时性死区是块级绑定的一个独特表现,而另一个独特表现就是在循环内使用。
循环中的块级绑定:
for(var i = 0;i<10;i++){ setTimeout(function(){ console.log(i) },0) } console.log(`i==>${i}`)
分析下这个代码的输出:肯定是 i==>10 然后输出10个10,因为使用var声明导致了变量提升,循环结束后i仍然可以被访问,但是如果使用let进行定义i,则在for循环的外部访问i是会报错的。
循环内的函数:
var的特点使得循环变量在循环作用于之外仍然可以被访问,于是在循环内部创建函数会不会出什么问题?思考入下:
var funcs = []; for(var i = 0;i<10;i++){ funcs.push(function(){console.log(i)}); } funcs.forEach(function(func){ func(); }) //10 times 10
func()方法里面的i在循环的每次迭代中都被共享,所以在循环内部创建的那些函数都拥有对同一个变量的引用。循环结束后,变量i的值会是10。
为了修正这个问题,开发者在循环内部使用立即调用函数表达式,在每次迭代中强制创建变量的一个新副本。例子如下:
var func = []; for(var i = 0;i<10;i++){ func.push((function(value){ return function(){ console.log(value); } }(i))) } func.forEach(function(func){ func(); })
这种写法在循环内部使用了立即执行函数,变量i传递给立即执行函数,从而创建作为副本的value变量,每个value都存储每次迭代时变量i的值,迭代中的函数使用这些副本值,所以得到0-9的期望结果。
循环内部let的使用:
let可以简化像上面立即执行函数那样的作用,在每次迭代中,都会创建一个新的同名变量并对其进行初始化。
而用const进行循环遍历会报错,因为第二次执行循环语句试图更改常量的值。因此在循环中只能使用const来声明一个确定不会被更改的变量。
使用for/in for/of 进行遍历时候。看下面的栗子==>
var funcs = [], object = { a: true, b: true, c: true }; // 不会导致错误 for (const key in object) { funcs.push(function() { console.log(key); }); } funcs.forEach(function(func) { func(); // 依次输出 "a"、 "b"、 "c" });
这段代码没有报错。而且正常的输出结果,原因是:循环为每次迭代创建一个新的变量绑定,而不是像for循环那样去修改已绑定的变量的值。
全局块级绑定:
使用var进行全局变量声明时,该变量成为全局对象的一个属性(浏览器中是window),所以有可能无意中会覆盖一个已有的全局属性。
像酱紫:
var RegExp = 'hello'; console.log(window.RegExp)
可以看出window上面的RegExp对象被覆盖,而如果在全局使用let或者const,虽然在全局作用域会创建新的绑定,但是不会有任何的属性被添加到全局对象上面。
最佳实践===>
默认情况下应当使用let来代替var。let的行为方式是var本应有的方式,因此直接用let替代var更符合逻辑。而需要受到保护的变量再使用const。
而更加流行的方式可以酱紫:默认情况下使用const,仅当明确变量值需要被更改的情况下才使用let,因为大部分变量在初始化后都应当保持不变。
总结:
let和const将块级作用域引入js中,都不会进行变量提升,不能在变量声明位置之前访问,因为块级绑定存在暂时性死区,试图在声明之前访问就会导致错误。
let和const在循环中的差异,let和const都能在每次迭代时创建一个新的变量绑定。
块级绑定当前的最佳实践是:默认情况使用const,而仅在变量值需要给更改的时候才使用let,有助于防止某些类型的错误。