打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
javascipt作用域深入理解
                                                      
我们知道,JavaScript中with语句和catch语句会延长作用域链,例如下面这样:
with({x:20}){
console.log(x);  //20
}
console.log(x)//抛出错误
很明显,结果是控制台打印出20,然后抛出错误,因为在with语句中作用域链被延长了(称为压栈),添加了一个新的作用域,它就是with语句圆括号中的对象。退出with语句后,作用域链会被缩短到原来的长度(称为出栈)。那么,要是这样呢:
with({x:20}){
var x=30;
}
console.log(x);//问题1
很多人猜结果是抛出错误,但其实是undefined。为什么?下面先引出几个概念:
1.执行上下文
2.作用域链(scope)
3.变量对象
4.活跃对象 
执行上下文:执行上下文是可执行代码的环境,每一个函数调用都会进入一个新的执行上下文,执行上下文可想象成一个对象,它拥有两个重要的属性,一个是作用域链(scope),另一个是this(关键字this)。当函数抛出未捕获错误的时候,错误会沿着执行上下文抛出,每抛出一个执行上下文,执行上下文链顶端的对象会被弹出栈,直到错误被捕获或者程序退出 运行。

作用域链:首先解释什么是作用域,Javascript 中的作用域本质上是一个对象,它保存着执行上下文上声明的变量。当引用一个变量的时候,就是在作用域上查找这个变量的值。顾名思义,作用域链就是一系列有先后顺序串联在一起的作用域。全局执行上下文中的作用域链只有一个作用域,在浏览器端那就是我们熟知的window。当查找 一个变量的时候,会先查找作用域链最前面的元素,如果找不到,会递归查找次前面的元素,直到找到。当作用域链中的元素被遍历完后如果还是找不到对应的变量,会抛出错误。

变量对象:变量对象用于保存在执行上下文中声明的变量,执行上下文中声明的变量的名字会作为变量对象的属性,声明的值会作为变量对象中相应属性的值。在全局执行上下文中,变量对象就是全局对象,执行上下文中的this对象也是全局对象(在浏览器端时window)

活跃对象:当调用一个函数的时候,执行会被分为两个阶段。
第一阶段是进入执行上下文阶段,这个时候会发生几件事:
1.创建一个新的执行上下文,并把它拼在执行上下文链的最前端
2.创建一个活跃对象,这个活跃对象有一个属性,它就是arguments,值就是传入函数的实参值组成的类数组。
3.为当前执行上下文分配一个作用域链(scope)
4.创建变量对象,此时并没有真正创建一个新变量对象,而是把第二步中创建的活跃对象当做了变量对象
5.当前执行上下文中的变量声明初始化,变量名作为变量对象的属性名 ,初始值为undefined,但有一个例外,那就是函数声明是的形参名也会作为变量对象的属性名,且值初始化为传进来的实参值,对于没有传进来的形参,值为undefined。还有一个例外就是函数声明,其初始值设为声明的函数对象。
6.为关键字this分配一个值

第二阶段是执行代码阶段,这一阶段可能会重置变量的初始值

当声明一个函数时,会为这个函数分配一个内部属性[[scope]](不同于上面的scope),每一个执行上下文都会有一个对于的scope(作用域链),函数内部的[[scope]]引用的就是函数声明所在执行上下文所对应的scope。当调用一个函数时,会新建一个执行上下文,并且为它分配一个scope,这个新分配的scope有两部分组成,一个是函数内部属性[[scope]]所引用的作用域链,加上一个函数执行时所产生的活跃对象,活跃对象被拼接在新生产的scope链的最前面。现在,上面的'问题1'就解释的通了:
with({x:20}){
var x=30;
}
console.log(x);//undefined
在with语句中,作用域链被延伸,在原来作用域链的基础上新增一个对象,就是with语句圆括号中的那个。我们前面说过,在进入执行上下文的时候,会有一个变量声明初始化,而当进入执行上下文的时候,作用域链还没被延伸呢(注意了即使进入with语句中还一样是原来的执行上下文,执行上下文的改变只在调用一个函数的时候),当前执行上下文的作用域链的最强面是变量对象,with语句中的var x=30;声明会在变量对象上新增一个属性'x’,值为undefined。在执行阶段,进入with语句的时候,作用域链被延伸,对x的赋值其实是对{x:20}中的x赋值而不是变量对象中的x进行赋值,所以结果就是undefined。

闭包:退出函数后,执行上下文会被销毁,其对于的scope和this也不复存在。那么,为什么子函数还可以引用父函数中的变量呢?相信很多人多会有这个疑问,现在来揭秘。前面说过,声明一个函数的时候,它有一个内部属性[[scope]],指向当前执行上下文中的scope链。而scope链中的元素其实是对象。当父元素执行上下文销毁后,其scope链中的对象并不一定会被销毁,因为JavaScript中的垃圾回收机制采用的是引用计数算法,当声明子函数的时候,子函数内部的[[scope]]属性其实已经引用了父函数执行上下文中的scope,而父函数执行上下文中的scope包含着父函数的变量对象,所以当父函数对应的scope销毁后,父函数的变量对象还会被子函数引用着(在子函数的[[scope]]中),即使子函数还没有被调用。这也解释了为什么但一个父函数调用多次,会产生多个互不相同的子函数,并且不同的子函数不共用父函数中的变量。
function parent(){
var x=1;
function child(){
x++;
console.log(x)
}
return child;
}
var child1=parent();
var child2=parent();
child1();//2 
child1();//3
child2();//2















本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
深入理解JavaScript作用域和作用域链
作用域链(Scope Chain)
理解 JavaScript 作用域
JavaScript作用域链其二:函数的生命周期
深入理解JavaScript系列(14):作用域链(Scope Chain)
高性能Javascript:高效的数据访问
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服