下面题输出结果是?
let a = {
n : 1
}
let b = a
a.x = a = {
n: 2
}
console.log(a.x) // undefined
console.log(b) // { n: 1, x: { n: 2 } }
执行过程分析:
a = {n: 1}
,先在堆内存中创建一个地址(比如:AAAFFF00
),内容是 n: 1
,然后在全局执行上下文EC(G)
中的全局变量环境VO(G)
,创建全局变量a
,指向堆内存地址:AAAFFF00
b = a
,同理,在全局变量环境VO(G)
创建一个全局变量b
,指向堆内存地址:AAAFFF00
a.x = a = { n: 2 }
,这个的优先级,其实等同于a.x = {n: 1}
,a = {n: 1}
a.x = {n: 1}
,在堆内存中再创建一个地址(比如:AAAFFF11
),内容是n: 1
,在a
的内存地址AAFF00
中创建一个x
变量,指向AAAFFF11
,此时,b = a = {n:1, x: {n: 1}}
a = {n: 1}
,将a
的指向由AAAFFF00
改为AAAFFF11
所以最后结果为:
b = {n:1, x: {n: 1}}
a = {n: 1}
变量提升:当前上下文执行之前,会把var/function
声明或者定义提升,带var
的只声明,带function
的声明+定义
但如果遇到{}
块级作用域时,新老版本浏览器表现不一样(兼容ES3、兼容ES6)
{}
,还是一如既往的function
声明+定义,而且也不会存在块级作用域{}是除对象中的{}
)undefined{}
中的function
,在全局下只声明不再定义;undefined{}
中出现function/let/const
,会创建一个块级作用域下面拿一道题来说明:
var a = 0
if(true) {
a = 1
function a() {}
a = 21
console.log(a) // 21
}
console.log(a) // 1
如果是在IE10以下
的执行过程:
如果是在新版本浏览器,也就是向前兼容ES3,向后兼容ES6的执行过程:
var a,function a
都在全局执行上下文EC(G)
中的全局变量环境VO(G)
中创建一个全局变量a
a = 0
,全局变量环境VO(G)
中a = 0
{}
块级作用域,生成一个块级执行上下文EC(block)
,会生成一个私有变量对象AO(block)
function a
,所以块级中发生变量提升,声明+定义,在heap
堆内存中生成一个函数,在AO(block)
创建一个变量a
指向函数堆内存, 这之后在这块级中,遇到的a
都是私有的 image.png
a = 1
,AO(block)
中a = 1
image.png
function a() {}
,因为这个函数,为了向前兼容ES3
,所以在全局内提升过一回;为了向后兼容ES6
,又在块级作用域中提升一回,所以 浏览器为了兼容,真遇到块级中的函数时,它会做一件事:遇到这代码前,会把代码之前所有对a的操作,映射给全局一份,但是后面的则不会再处理了,它会认为这之后的都是自己私有的了 ,即之前a = 1
会映射到全局VO(G) 中 a =1
image.png
a = 21
,已经是私有的了,所以只影响块级AO(block) 中 a = 21
image.png
console.log(a) => AO(block) a => 21
console.log(a) => VO(G) a => 1
为了加深这个变态的规则,我们多做几道题:
{
function foo() {}
foo = 1
}
console.log(foo);
{}
中的function foo
在变量提升,全局中只声明,不定义EC(G):
AO(G) function foo
{}
块级中有function
,所以会产生一个块级作用域EC(Block)
,foo
在这个块级作用域里,变量提升:声明+定义EC(block):
AO(block): funciont foo() {}
AO变量对象
=>VO激活对象
),因为foo
在全局和私有都声明过,为了兼容ES3
和ES6
,在执行到function foo() {}
里,这之前的操作映射到全局,也就是AO(block): funciont foo() {}
声明+定义的过程EC(G):
VO(G) function foo() {}
EC(block):
VO(G) foo => 1
下面我要开始偷懒了,哈哈哈,请结合上下文理解下面注释步骤
// 第1步,EC(G), AO(G): function foo
// 第2步,EC(block), AO(block): foo => function foo() {}
// 第3步,开始执行,EC(G), VO(G): foo => function foo() {}
// 第4步,foo = 1, EC(block), VO(block): foo => 1
// 第5步,EC(G), VO(G): foo => 1
{
function foo() {}
foo = 1
function foo() {} // 1
}
console.log(foo); // 1
// 第1步,EC(G), AO(G): function foo
// 第2步,EC(block), AO(block): foo => function foo() {}
// 第3步,开始执行function foo() {},EC(G), VO(G): foo => function foo() {}
// 第4步,foo = 1, EC(block), VO(block): foo => 1
// 第5步,function foo() {} => EC(G), VO(G): foo => 1
// 第6步,foo = 2,EC(block), VO(block): foo => 2
{
function foo() {}
foo = 1
function foo() {}
foo = 2
console.log(foo); // 2
}
console.log(foo); // 1
以下函数输出结果是?
var x = 1
function func(x, y = function func2() { x = 2 }) {
x = 3;
y()
console.log(x) // 2
}
func(5)
console.log(x) // 1
下面使用图分析下过程:
x = 1
,在EC(G)
中创建一个VO(G)
,创建值1,再创建一个对象x
,x指向值1 image.png
VC(G)
,形参x、y
,及其函数体字符串,并在全局变量中创建一个func
,指向这个堆内存 image.png
func(5)
,执行一个函数,会为其创建一个私有的执行上下文EC(func)
,在其中会创建一个私有变量环境AO(func)
EC(func)
初始其作用域链:它自身作用域EC(func)
和其上级作用域EC(G)
(全局作用域)x = 5
,y
没传参,所以使用其默认值函数func2
,碰到函数,为其申请堆内存AAAFFF111
,同样,分析其作用域和形参,并将函数体保存到内存中,并将y指向AAAFFF111
image.png
func5
函数体x = 3
, 在自己作用域EC(func)
中查找x,发现有私有x
,所以将x
指向3y()
,在自己作用域EC(func)
中查找y
,发现有y
指向func2
,执行函数func2
,并其又单独创建一个执行上下文EC(func2)
,为其分析作用域链,自身所在作用域EC(func2)
和其上级作用域EC(func)
,因为其没有形参赋值,所以也没创建相关的私有变量 image.png
func2
的函数体,x = 2
,所以在其自身作用域EC(func2)
中找不到该变量x
,会向其上级作用域EC(func)
中查找,发现有x
,所以将EC(func)
中x
指向2
image.png
func2
执行结束,继续执行EC(func)
中的console.log(x)
,输出其自身的x
:2func
执行结束,继续执行EC(G)
中的console.log(x)
,输出全局VO(G)
中的x
:1 image.png
下面题目输出是?
var x = 1
function func(x, y = function func2() { x = 2 }) {
// 这里,x多一个var声明
var x = 3;
y()
console.log(x)
}
func(5)
console.log(x)
这里就要讲到浏览器运行es6的机制了:
{}
产生的块级作用域这种是我们平常所认识的,ES6
中存在块级作用域,即只要{}
(除了对象中的{}
)出现let/const/function
var/let/const/function
都算)undefined那么这个函数就会产生 2个上下文 :* 一个是函数本身执行产生的私有上下文,比如上面`func`函数执行时,会生成`EC(FUNC)`
* 一个是函数体大括号包起来的块级上下文`EC(BLOCK)`
下面,我们依然画图来分析:
EC(G)
,它在VO(G)
声明两个变量:x
和func
,x
指向值1,func
指向函数堆内存AAAFFF000
image.png
func(5)
,这时,因为func
中形参y
设有默认值,且函数体中声明了变量var x = 3
,按照上面的规则,这里会生成两个执行上下文EC(FUNC)
和EC(BLOCK)
EC(FUNC)
,它的作用域是EC(G)
,作用域链是它自身上下文EC(FUNC)
和上级作用域上下文EC(G)
,它的形参是x => 5
,y => 函数func2堆内存AAAFFF111
image.png
4.对EC(BLOCK)
的代码块分析,它的作用域是EC(FUNC)
,作用域链是它自身上下文EC(BLOCK)
和上级执行上下文EC(FUNC)
,在块级中,`var
x = 3它声明了
x,所以
x是块级作用域中的私有变量,当执行
x = 3时,将块级中的
x => 3`
image.png
y()
,发现块级中没有y
变量,去它的作用域链上级EC(FUNC)
找,找到了y
,执行y()
,生成func2
执行上下文EC(FUNC2)
,它的作用域是EC(FUNC)
,所以其作用域链是[自身执行上下文EC(FUNC2),作用域EC(FUNC)]
,没有形参和变量声明,所以没有自身私有变量;执行里面函数体x = 2
,在EC(FUNC2)
中找不到x
,所以去它作用域上级找EC(FUNC)
,EC(FUNC)
中有x
,所以将EC(FUNC)
中x => 2
image.png
EC(BLOCK)
继续执行console.log(x)
,打印的是 EC(BLOCK)
中私有的x
,即3
func(5)
执行完毕,继续执行到EC(G)
中console.log(x)
,打印的是VO(G)
中的x
,即1
image.png
所以,答案是3和1
下面,为了证明我没有胡说八道='=,我们在浏览器上断点调试下最后一题
VO(G)
的x
,所以我在Watch
中添加了window.x
变量,方便我们观察VO(G)
中(也就是浏览器的Global
)x
的值,可以看到,还没调试之前,全局中的x
是undefind
// 第4题:变态带形参的堆栈考核
debugger;
var x = 1
function func(x, y = function func2() { x = 2 }) {
var x = 3;
y()
console.log(x)
}
func(5)
console.log(x)
image.png
x=1
时,func(5)
执行前,可以看到全局VO(G)
的x = 1
image.png
func(5)
,可以看到生了Scope
中,也就是EC(FUNC)
的作用域中,生成两个执行上下文BLOCK
和LOCAL
,对应我们上文说的EC(BLOCK)
和EC(FUNC)
,因为私有变量中有x
,然后块级中也有声明x
,所以会将私有的x
映射到块级中的x
image.png
var x = 3
,发现块级中私有变量x
变为3
image.png
y()
,即生成func2的执行上下文EC(FUNC2)
,因为没有私有变量,所以其Local
为空 image.png
y()
中x = 2
后,看到EC(FUNC)
中的x => 2
image.png
EC(BLOCK)
中console.log(x)
,输出的是Block
中的x
image.png
EC(G)
中console.log(x)
,输出的是Global
中的x
image.png
由此,验证了上面的分析结论。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有