这是我参与「第四届青训营 」笔记创作活动的的第10天
JS中的作用域
ES6块级作用域:
每个{}大括号内就算一个块级作用域 ❎
每个函数内,或者用 let / const 定义的变量 / 常量 存在一个块级作用域 ✅
作用域链
内部函数访问外部函数的变量,采取的是链式查找
根据作用域链从函数内往外找,来决定 变量/方法 取哪一个值(哪怕是undefined也算找到)
function fun() {
window.a = 10
console.log(a) // 打印undefined
var a = 20 // 注意这里有变量提升,打印undefined只不过是因为“作用域链”
}
fun()
function fun() {
a = 10 // 如果是不加var,是先执行window.a = 10呢?还是变量提升var a呢?
console.log(a) // 答案是先变量提升var a,那么a = 10 就不能解释成window.a = 10 了
var a = 20
}
fun() // 所以打印10
连续声明变量
连续声明变量时,没有用 var / let / const 声明的,相当于在全局声明(window.xxx)
function test(){
var a = 1, b = 1;
return b;
}
function test1(){
var a1 = b1 = 1; // 相当于先 window.b1 = 1 然后 var a1 = b1
}
test();
// console.log(a); // undefined a是局部的
// console.log(b); // undefined b是局部的
test1();
// console.log(a1);// undefined a1是局部的
console.log(b1); // 1 说明b1是全局的,相当于 window.b1 = 1
var 的变量声明提升
JS引擎运行.js分为2步:预解析、代码执行
- 预解析:js引擎会把.js 里面的所有 var 和 function 提到当前作用域的最前面▲
var name = 'a';
(function () {
if (typeof name == 'undefined') {
var name = 'b'
console.log('111' + name)
} else {
console.log('222' + name)
}
})()
// 结果是 111b
因为if else 虽然有{},但它不是函数,所以没有块级作用域,它处在 立即执行函数的块级作用域
所以 立即执行函数的作用域 只要发现里面有 var 哪怕判断条件不对,都会先把 var 提升到当前作用域上面声明
上面的例子相当于
var name = 'a';
(function () {
var name // 变量声明提升
if (typeof name == 'undefined') {
name = 'b'
console.log('111' + name)
} else {
console.log('222' + name)
}
})()
- 代码执行:按照书写顺序从上往下执行
function fun() {
a = 10
}
fun()
console.log(a)
// 10
----------------------------------------------------------------------------
即使函数声明提升了,但是代码执行顺序从上往下,都还没到执行函数内部的时候呢
console.log(a)
function fun() {
a = 10
}
fun()
// 报错,a未定义
预解析的执行顺序
预解析分为:变量预解析(变量提升)和 函数预解析(函数提升)
-
变量(包括函数表达式)提升:只提升变量声明,不提升变量赋值
-
函数提升:只提升函数(function)声明,不调用函数
-
js代码执行顺序:传入的参数 > 函数提升 > 变量提升(只声明不赋值) > window. (你连声明都没声明就直接赋值,我才挂到 window. 上)
传参赋值 会优先于 变量提升 执行▲ 变量提升中,函数声明 会优先于 变量声明 执行▲
▲ 函数声明和赋值相当于是一起执行的!!!
function fun() {
console.log(a)
var a
function a() {
return 666
}
}
fun()
// 打印 ƒ a() {return 666}
所以上面相当于
function fun() {
function a() {
return 666
}
var a
console.log(a)
}
fun()
----------------------------------------------------------------------------
▲ 传参赋值 先于函数变量提升执行;又因为函数声明和赋值相当于是一起执行的,所以被函数覆盖
function fun(a) {
function a() {
return 666
}
console.log(a)
}
fun(100)
// 打印 ƒ a() {return 666}
----------------------------------------------------------------------------
▲ 函数提升 > 变量提升
function fun() {
var a = 10
function a() {}
console.log(a) // 结果是 10
}
fun()
上面例子相当于
function fun() {
function a() {}
var a // 注意,此时a 是 ƒ a() {},并不是undefined
a = 10 // 后面才赋值成了10
console.log(a)
}
fun()
----------------------------------------------------------------------------
▲ 变量提升 > window.
function fun() {
a = 10
console.log(a) // 打印 10
var a = 20
}
fun()
上面例子相当于
function fun() {
var a // 优先于 window.
a = 10
console.log(a) // 打印 10
a = 20
}
fun()
----------------------------------------------------------------------------
▲ 综合起来:传入的参数 > 函数提升 > 变量提升(只声明不赋值) > window.
function fun(a) {
console.log(a)
var a = 10
function a() {
return 666
}
}
fun(100)
// 打印 ƒ a() {return 666}
所以上面相当于
function fun(a) {
var a = 100
function a() {
return 666
}
var a
console.log(a)
a = 10
}
fun(100)
参数 > 变量
function fun(a) {
var a
console.log(a)
a = 10
}
fun(100)
// 打印 100
----------------------------------------------------------------------------
函数 > 变量
function fun() {
function a() {
return 666
}
var a
console.log(a)
a = 10
}
fun()
// 打印 ƒ a() {return 666}
- 声明变量并不是调到最前面,而是紧跟在同级的,原本就有的声明之后
var a = 18; // 拆分成 var a; 和 a = 18;
fun();
function fun() {
var b = 9;
console.log(a);
console.log(b);
var a = '123';
}
// 相当于以下代码
var a;
function fun() { // 函数并不是调到最前面,而是紧跟在原本就有的声明之后
var b;
var a; // a并不是调到最前面,而是紧跟在原本就有的声明之后
b = 9;
console.log(a);
console.log(b);
a = '123';
}
a = 18 ;
fun();
// 最后打印 undefined 和 9
例题:
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9; //相当于a有声明,局部变量;b和c没声明,是全局变量
console.log(a);
console.log(b);
console.log(c);
}
//输出5个9,最后报错
暂存死区
与通过 var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。
看过很多文章后用大佬的话做下总结
-
let有无变量提升取决于你如何定义变量提升。 若[变量提升」是指变量可在声明语句之前被调用,则let没有变量提升;若[变量提升」是指变量在声明语句之前就被执行上下文记住,则let有变量提升。【let 声明的变量,虽然不能在声明前知道它的值,但是能让上下文知道存在这个变量】
-
JS代码是即时编译与执行的,一个函数作用域会拥有一个执行上下文,执行上下文是一块存储空间。执行上下文内又有名为「变量环境」和「词法环境」2个环境。
-
由var和function声明的变量,在代码编译完成后,执行之前,其变量名和值就被存储在变量环境中了,所以在代码执行阶段的任何时刻,都可以调用它们,自然也能在声明语句之前调用了。
-
由const和let声明的变量,在代码编译完成后、执行之前,其变量名被存储在**「词法环境」中,代码执行过程中会从依据【词法环境→变量环境→闭包/上一个作用域】的顺序来查找变量**,而词法环境所存储的值被要求只有在声明语句之后才能调用。所以会存在暂时性死区,但变量又确确实实被执行上下文提前记住了,所以可以把暂时性死区理解为「变量暂时不能使用的阶段」。所以得出结论①
var a = 456;
(function() {
console.log(a)
let a=123
})()
如果let没有变量提升的话输出的应该是456
但是如果你执行代码的话会报错 Cannot access 'a' before initialization 初始化前无法访问“a”
想不到吧!从这里其实可以看出let存不存在变量提升有争议了,值在变量显式赋值之前不能对变量进行读写,否则就会报错,这也就是所谓的let和const的暂时性死区。