JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。 注意:只有声明本身被提升,而任何赋值或者其他执行逻辑都被留在原处。 函数声明会被提升,但函数表达式不会
foo() //Function foo
function foo(){
console.log('Function foo')
}
foo2() //TypeError: foo2 is not a function
var foo2 = function() {
console.log('Function foo2')
}
复制代码
函数声明和变量声明都会被提升,但(可以拥有多个“重复”声明的代码中出现)是,函数会优先被提升。 考虑这段代码:
foo2() //2
var foo2 = function() {
console.log(1)
}
function foo2(){
console.log(2)
}
foo2()//1
复制代码
注意var foo2是一个重复的(因此被无视),即使它出现在function foo2()...声明之前。因为函数声明是在普通变量之前被提升的。但后续的声明会覆盖前一个。
var声明及变量提升(Hoisting)机制 在函数作用域或全局作用域中,通过关键字var声明的变量,无论实际上在哪声明的,都会被当成在当前作用域顶部声明的变量。
function getValue(flag){
//var value,变量value的声明被提升至函数顶部,而初始化操作停留在原处执行
if(flag){
var value = "true";
}else{
return null
}
//此处可以访问变量value,值为undefined
}
复制代码
块级声明
块级作用域 (也被称为词法作用域)存在于函数内部、块中({}之间的区域)
- let声明的用法与var相同,用let代替var来声明可以把变量的作用域限制在当前代码块中。let声明不会被提升,假设作用域中已经存在某个标识符,在使用let关键字声明则会抛出错误。
var a = 1;
let a = 2;//Uncaught SyntaxError
var b = 3;
if(true){
let b = 4;//不会抛出错误
}
复制代码
-
const声明一个常量,其值一旦被设定后不可更改,因此通过const声明的常量必须进行初始化。
-
对于复合类型的变量,const声明不允许修改绑定,但允许修改值。变量名不指向数据,而是指向数据所在的地址,const命令只是保证变量名指向的地址不变,并不能保证该地址的数据不变,急可以修改该对象的属性值。
const b = 'b';
b = 'c';//Uncaught SyntaxError:
const obj = {};
obj.a = 'a';
console.log(obj);//{a:'a'}
复制代码
临时死区(Temporal Dead Zone) 与var不同,let和const声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,即使相对安全的typeof操作符也会触发引用错误
if(true){
console.log(typeof a)//Uncaught ReferenceError
let a = 1;
}
复制代码
由于console.log(typeof a)语句会抛出错误,因此用let定义并初始化变量a的语句不会被执行,此时a还位于所谓的临时死区(TDZ)中;将let换成const会有相同的效果。 JavaScript引擎在运行代码是发现变量声明时,要么将它们提升至作用域顶部(var声明),要么将声明放到TDZ中(let和const声明)。访问TDZ中的变量会触发运行是的错误,只有执行过变量声明语句后,变量才会从TDZ中移出,然后方可正常访问。
循环中的块作用域绑定
for(var i = 0;i<3;i++){
console.log(i)//0,1,2
}
console.log(i)//3
//由于var声明提升,变量i在循环结束后仍可以访问。如果换用let声明,循环结束后访问i则会抛出一个错误
复制代码
循环中的函数:
var data = [];
for(var i = 0;i<10;i++){
data[i] = function(){
console.log(i)
return i
}
}
data[1]();//10
复制代码
**循环里的每次迭代同时共享着变量i,循环内部创建的函数保留了对相同变量的引用,循环结束时变量i的值为10,所以每次调用console.log(i)时就会输出是在10。
为解决这个问题,通常会使用立即调用函数表达式(IIFE),以强制生成计数器变量的副本
var data = [];
for(var i = 0;i<10;i++){
data[i] = function(){
console.log(i);
return i
}()
}
data[5];//5
复制代码
循环中的let声明 let声明模仿上述示例中IIFE所做的一切来简化循环过程,每次迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化(即删除IIFE之后仍可得到预期的效果)
var arr = [];
for(let i = 0;i<10;i++) {
data[i] = function(){
return i
}
}
data[1]();//1
复制代码
全局作用域绑定
let和const与var的另一个区别是它们在全局作用域中的行为,当var被用于全局作用域时,会创建一个新的全局变量作为全局对象(浏览器中的window对象)的属性,这意味着var可能会无意覆盖一个已经存在的全局属性
//浏览器中
var RegExp = "hello";
console.log(window.RegExp);//hello
复制代码
即使全局对象RegExp定义在window上,也不能幸免于var声明覆盖。如果使用let或const,会在全局作用一下创建一个新的绑定,但该绑定不会添加为全局对象的属性,即不会覆盖全局变量,只能遮蔽它。
let RegExp = "hello";
console.log(RegExp)//"hello"
console.log(window.RegExp === RegExp);//false,不会破坏全局作用域
复制代码
相关文章:ES6深入学习(二)关于函数(juejin.im/post/5cb7e9…)