之前已经总结过:
- 全局执行上下文:全局对象GO-->变量提升-->代码执行
- 函数的执行会形成函数私有上下文:-->变量对象AO-->初始化作用域链-->初始化this-->初始化arguments-->形参赋值-->变量提升-->代码执行
此外
除“函数和对象”的大括号外[例如:判断体/循环体/代码块...],如果大括号中出现了let/const/function/class等关键词声明变量,则当前大括号会产生一个块级私有上下文;
它的上级上下文是所处的环境(也就是创建这个块级作用域的上下文);
var不产,也不受块级上下文的影响
代码块:
{
}
判断体:
if(1) {
}
循环体:
for(let i = 0; i<6; i++) {
}
通过一段代码来详解一下:
console.log(a)
console.log(b)
var a = 12
var b = 13
if (1 == 1) {
console.log(a)
console.log(b)
var a = 100
let b = 200
console.log(a)
console.log(b)
}
console.log(a)
console.log(b)
1、全局代码执行,会有一个全局执行环境栈EC(G)
2、变量提升:
代码第三行和第八行 都是var 声明的变量,要变量提升
但是规定不允许重复声明,所以只有一个全局变量a
存储在GO(图中为了省事,GO和VO(G)写一起了)中
GO:
a
3、全局代码执行:
执行到第一行:a只声明,没有定义,所以输出undefined
4、执行到第二行:正常情况下会报错代码不往下执行,这里先忽略报错,看一下往下执行会是什么样子
5、代码执行到第三行:a被赋值为12
6、代码执行到第四行:通过let关键字,声明一个变量b,值为13
7、此时GO中存的是:
a :12
VO(G)中存的是:
b :13
8、代码执行到五行遇到判断体了;
除“函数和对象”的大括号外[例如:判断体/循环体/代码块...],如果大括号中出现了let/const/function/class等关键词声明变量,则当前大括号会产生一个块级私有上下文;
它的上级上下文是所处的环境(也就是创建这个块级作用域的上下文);
var不产,也不受块级上下文的影响
所以此时会产生一个块级私有上下文,暂取名为EC(B)
块级私有上下文中的代码正式执行之前,会做以下操作:
@1、初始化作用域链:
(块级私有上下文不初始化this,不初始化arguments,不会形参赋值)
@2、变量提升:特殊:var不受块级上下文影响,所以这里的变量提升只有function会处理,所以这个代码块中不存在变量提升
@3、代码执行:
{
console.log(a) // 不受块级私有上下文影响,找的是上上下文的a,上级上下文中(也就是全局)a=12,所以输出值是12
console.log(b) // 正常代码这里是会报错的,代码不会往下执行了,忽略,假设代码可以往下执行
var a = 100 // 全局代码执行的时候,var a已经声明过了,所以这里不会再重复声明,全局a重新赋值为100。此时GO中寸的是:a : 100
let b = 200 // let关键字声明变量b,属于私有变量,存在AO(B)中,值是200
console.log(a) // 块级上下文没有变量a,网上找,a=100,输出100
console.log(b) // 私有变量,输出200
}
9、块级私有上下文执行完毕,执行最后两个:
console.log(a):全局a已经在块级私有上下文中被改为100,所以输出100
console.log(b):全局b值是 13,所以输出13
再看一个例子,这个和上边的还有所不同
console.log(foo)
if(1==1) {
console.log(foo)
function foo() {}
foo = 1
console.log(foo)
}
console.log(foo)
这段代码在老版本和新版本浏览器中执行结果是不同的
老版本浏览:不支持块级上下文,不支持ES6语法
1、全局代码执行之前,变量提升
@1、创建一个堆内存0x000,用来存放函数代码字符串等
@2、function foo --> 0x000
2、代码执行
console.log(foo) :输出函数foo
if(1==1) {
console.log(foo):输出函数foo
function foo() {}
foo = 1 :foo重新赋值为1
console.log(foo) :输出1
}
console.log(foo) :输出1
新版本浏览:支持块级上下文,支持ES6语法
1、全局代码执行
2、执行之前会进行变量提升
3、遇到判断体(不管条件是否成立)/循环体/代码块中如果有function,只会声明,不定义。
所以此时 全局变量对象中会有个变量 foo,但是没有值
4、代码执行:所以第行代码
console.log(foo) // undefined
5、除“函数和对象”的大括号外[例如:判断体/循环体/代码块...],如果大括号中出现了let/const/function/class等关键词声明变量,则当前大括号会产生一个块级私有上下文;
它的上级上下文是所处的环境(也就是创建这个块级作用域的上下文);
var不产,也不受块级上下文的影响
6、代码执行到if(1===1)条件成立,判断体中又有function,所以会形成块级私有上下文:自定义为EC(Block)
7、
if(1==1) {
console.log(foo)
function foo() {}
foo = 1
console.log(foo)
}
8、块级上下文中的代码执行执行还要做一些操作:
初始化作用域链:
变量提升:
function foo-->创建堆内存0x001
(foo是私有变量)
9、代码执行:
console.log(foo) // 函数foo
function foo() {} // 变量提升阶段已经执行过,不再执行这一行代码,但是这里会有个特殊处理:@1、会把这行代码之前的所有操作同步给全局一份,此时全局的foo指向的就是堆内存0x001。@2、断开和全局的联系,也就是这行代码以后对foo的操作和全局没关系了
foo = 1 // 私有变量foo 重新赋值为1
console.log(foo) // 1
10、最后一行:
console.log(foo):函数foo
如果对于以上有疑惑,可以再加几行代码来测验
console.log(foo)
if(1==1) {
console.log(foo)
foo = 100
function foo() {} // 这行代码之前 foo显示指向一个函数,又被被赋值为100,这个值也会同步到全局,所以最后一行输出全局的foo是100
foo = 1
foo = 200
console.log(foo)
}
console.log(foo)
练习题
f = function() {return true}
g = function() {return false};
(function(){
if(g() && [] == ![]) {
f = function () {return false}
function g() {return true}
}
})()
解析
/**
* EC(G)
* VO(G)/GO
* f --> 0x001 [[scope]]:EC(G)
* g --> 0x002 [[scope]]:EC(G)
* 变量提升:--
*/
f = function() {return true}
g = function() {return false};
// 自执行函数执行,形成块级私有上下文EC(AN)
(function(){
/**
* EC(AN)
* 作用域链:EC(AN),EC(G)
* 形参赋值:--
* 变量提升:遇到判断体,不管条件是否成立,都会变量提升。
* function只声明不定义
* function g
* 执行判断条件:if(g() && [] == ![])
* 执行g()的时候报错:g is not a function,因为g只声明了没定义
*/
if(g() && [] == ![]) {
f = function () {return false}
function g() {return true}
}
})()
注意:如果第二行代码不加分号会报错:3.js:65 Uncaught TypeError: (intermediate value)(...) is not a function
**一般来说,“(”、“[”、“/”、“+”、“-”,都会在上一行代码不加分号的情况下,与上一行代码相接,“++”、“--”在上下两行都不加分号的情况下,与下一行代码相接,如遇上了return才会在改行末尾加上分号。:https://blog.csdn.net/KamyoChae/article/details/81198460
**
·````
f = function() {return true}
g = function() {return false};
(function(){
if(g() && [] == ![]) {
f = function () {return false}
function g() {return true}
}
})()