好程序员
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这个局部变量。