好程序员 web前端带 你了解 JS的作用域链, 我们都知道 js 是一个基于对象的语言,系统内置各种对象。
而 window 作为一个天然存在的全局对象,它承担了所有全局资源的存储。
我们使用的任何全局变量,都是 window 下的。也就是说,在 js 中,实际上没有任何对象、方法是可以独立的,它们必须依赖于某个对象才可以被访问或执行。
就像 alert() ,它的完整写法是 window.alert()
parseInt(), 完整写法是 window.parseInt()
所有放在 window 对象下的资源,访问时可以默认省略 window
但有一种情况非常特殊,例如函数中的局部变量:
function Person(){
var name = “abc” ;
}
当我们试图访问这个 name 属性时
console.log(newPerson().name);
结果是 undefined
我们只能在函数内部访问:
function Person(){
var name = “abc”;
console .log(name);
}
这种属性,在构造函数中,也被称为私有属性,我们必须提供一种对外公开的方法才可以被外界访问。例如:
function Person(){
var name = “abc” ;
this .getName = function (){
return name;
}
this .setName= function (_name){
name = _name;
}
}
这是一个典型的作用域问题 , 似乎我们每个人都知道。
但这似乎也违反了我们的一个常识:那就是在 js 中,所有资源都必须依赖对象才能存在,不可独立使用。比如说:
function aaa(){
function bbb(){ }
bbb();
}
这段代码看上去并没有错,但是请问 bbb 这个函数为什么可以独立存在呢?如果我们把它换成这样:
function aaa(){
function bbb(){ }
window .bbb();
}
结果是运行错误!
那如果换成这样呢?
function aaa(){
function bbb(){ }
this .bbb();
}
结果还是运行错误!
那么我们不禁要发问了, bbb 这个函数到底是属于哪个对象的?
当我们在调用一个函数的时候,浏览器会为这个函数的执行开辟一块内存区域用来存储这个方法在执行的临时数据。而对象作为 js 的基本存储单位,因此,临时数据实际上都被保存到了一个对象当中,这个对象,就是我们平时所说的 执行上下文环境
当我们调用 aaa 函数时,例如 window.aaa()
浏览器会为这一次函数的执行,创建一个执行上下文环境对象,我们暂时给它起个名字,叫做 contextAAA 吧,当然它是临时的,因为函数执行完它也就消失了。
那我们的代码实际上会变成这样:
function aaa(){
function bbb(){ }
contextAAA .bbb();
}
尽管 contextAAA 对象是看不见的,但它确实存在。
而当我们执行 bbb 函数时,浏览器会再次为这一次函数调用创建一个临时的执行上下文环境,我们暂且叫它 contextBBB
那么 contextAAA 和 contextBBB以及window 之间会形成链条关系,
举个例子来说明吧
var num = 888;
function aaa(){
var num = 100;
function bbb(){
var num= 200;
console .log(num);
}
bbb();
}
aaa();
那么 contextAAA 如下:
contextAAA = {
num : 100,
bbb : function (){ … },
parentContext: window // 父级上下文对象
}
那么 contextBBB 如下:
contextBBB = {
num : 200,
parentContext: contextAAA // 父级上下文对象
}
因此我们发现,在父级上下文对象中,我们没有办法访问到子级上下对象,这是一个单向链表,这就是全局不能访问局部的原因。
而 bbb 函数中打印出的 num 应该是多少呢?这取决在上下文对象中的查找顺序,顺序大概是这样的:
首先在当前上下文对象 contextBBB 中,找一下有没有 num 变量,找到就直接打印。以我们目前的代码看,结果应该是 200
我们把代码改造一下:
var num= 888;
function aaa(){
var num = 100;
function bbb(){
console .log(num);
}
bbb();
}
aaa();
由于这次在 contextBBB 对象中找不到 num 变量了,因此它会从父级上下文对象中查找,也就是 contextAAA 里面的 num ,因此打印的结果是 100;
我们再把代码改造一下
var num= 888;
function aaa(){
function bbb(){
console .log(num);
}
bbb();
}
aaa();
由于这次连 contextAAA 对象里也找不到了,会再次向它的父级上下文对象,也就是 window 查找,因此打印结果是 888
contextAAA 和 contextBBB 的父子关系,在你写代码的一刻就决定了,这就是作用域。
function aaa(){
var
num = 10;
function bbb(){ console .log(num); }
}
而代码执行时,产生的上下文对象,是链表的关系。这就是我们所说的作用域链,它的原理跟原型链是一样的。
理解了这一点,也能弄明白闭包的原理。
function aaa(){
var num = 10;
return function bbb(){ console .log(num); }
}
aaa( )( )
尽管 bbb函数通过return在全局范围被执行了,但作用域的链表关系并没有发生改变,因此,bbb函数依然可以访问num这个局部变量。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。