js之你真的理解了作用域、作用域链、变量对象和闭包吗?

相信学前端的兄弟,都看过JS高程这本书。这本书中讲到了作用域、作用域链、变量对象和闭包的种种含义。刚开始看的时候觉得说的很有道理,直到出现了如下的情况:

代码1:

function test(){ // 代码1
	var b = 1;
	return function (){ // 这里对函数的命名可以是除了b以外的名称,我们假设是一个匿名函数
		console.log(b);  // b = 1;
	}
}
test()();

看到这里,大家都会认为这不就是一个闭包嘛。这有什么好说的呢,那么下面这个情况呢?
代码2:

function test(){ // 代码2
	var b = 1;
	return function b(){
		console.log(b); // b = Function: b
	}
}
test()();

大家也许会认为这个我也能解释,这个跟:
代码3:

function b(){ // 代码3
	console.log(b); // b = Function: b
}
b();

有什么区别嘛?
没错,这里有区别,你可能会说,代码3中的b是在全局环境下声明的,而上面代码2中的b它是在test当中声明的。所以代码3在执行b的时候,向它的父作用域去查找,也就是全局环境下,查找到b是一个函数。
这里似乎说通了,但是代码2当中的b真的是在test当中声明了嘛?按照书中作用域链的说法,我们应该能在test当中打印出b,并且它是一个函数。说到这里,有的小伙伴一定会在var b = 1;之前打印一下b,但实际的结果是undefined。也就是说test函数中并没有b=Function: b
这里就要说一下return了,函数的创建阶段是没有管这个return的,当在执行阶段中才真正去解释这个return。通过Chrome打断点我们看到:
在这里插入图片描述
当代码2中的test函数执行到return这里的时候,会在Local中声明一个Return value的属性,它的属性值就是函数b。那么,它是在这里声明了函数b嘛?我们接着往下看,你可能还会去用书上的那套理论解释代码2中最后为什么b打印出来是一个函数,那么接下来,我们看一个恐怖的东西:
在这里插入图片描述
这里的Local为什么有个b的属性?而且属性值是Function b,这是为什么?调用栈里面也是b函数进栈,我并没有在b当中声明b这个属性,它是从哪里冒出来的呢?是不是感觉书上说的那一套理论已经无法解释这里的现象了。我们都知道这个b函数是在test函数中声明的,但是在test里面我们也打印不出来,在b里面却出现了,无限怀疑人生。。。。。。。

那么接下来就是我的理解了~~~

作用域:是在代码编译阶段制定的规则
作用域链:是在代码执行阶段对作用域规则的具体实现。

这两句话乍一看没什么问题,细思极恐啊!!!
现在我们也不要管什么作用域链,闭包,还有什么执行上下文生命周期这一类的说法了。我们换个角度,站在V8引擎上去看这段代码。
我们知道V8引擎是由C++实现的,那么V8在编译这段代码的时候,规定了,b应该是哪个一个值,如果让编写V8的人自己解释为什么b就是这个值而不是其他的值,他肯定会说,我就是这么规定的,所以为了让大家更好的理解应该是哪个一个值,就引入了变量提升,作用域链之类的说法。因此,当遇到这种问题时,我们就找到了规律,这里就是闭包,那里就是变量提升等等。下面我们再来看看代码1和代码2:

function test() 1. 第一步遇到了函数声明test,编译器会在这里打上标记这里是函数声明
{   			2. 第二步遇到了大括号,表示函数体,编译器打上标记下面是函数体
	
	var b = 1;  3. 第三步又遇到了变量声明var b,好,编译器打上标记这里是var声明 

	return function b()4. 第四步遇到了return,表示test函数要返回了,编译器就要查看这里返回的啥,这里
							又遇到函数声明了,所以打上标记b函数声明了,
	{ 					5. 第五步遇到了大括号,表示函数体
		console.log(b);	6. 第六步遇到了打印b,编译器就会打上标记这里的b,就是前面函数声明的b
	}					7. 第七步遇到了大括号,没有return,b函数体结束了
}						8. 第八步遇到了大括号,test函数体结束了
test()(); 				9. 第九步遇到了小括号,表示有函数在这里要调用。

以上就是编译阶段所干的事情,下面就是执行代码了。
(这里的test()()是为了简写,也可以拆开来,即执行完test函数,再执行test返回的函数(在这里返回的是一个函数),但是不方便看这一类的问题)。

  • 首先,test会在全局环境中声明test = Function:test
  • 然后test函数执行,创建上下文执行环境,开始执行创建阶段的操作,变量b会被声明在这个地方。
  • 再然后就是return,为return value赋值对b函数的引用。
  • 接下来,函数b声明了:b = Function:b
  • 此时test函数全部执行完成,退出栈。
  • 然后,b函数入栈。
  • 请注意代码是从return value那一行执行过来的,因此b进栈的时候,发现已经有一个b = Function:b,所以在ChromeLocal那里会出现b = Function:b
  • b的函数体中只有一个打印语句,所以执行该代码,并且在编译阶段已经打上标记了,这里的b就是在test函数return的时候声明的。

至此,我对这个现象的理解已经解释完了。相信这里面也存在较多说明上的漏洞,毕竟我没看过V8的引擎源码也不知道他的工作流程,仅仅是对这个现象的理解。欢迎大家评论交流,共同学习进步。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值