这几天因为对于JavaScript中的作用域链和原型链有点混淆,当访问一个不带有this修饰的变量时,我想知道它的搜索顺序,因为作用域链的链结点也是一个变量对象,那么当在这个变量对象中查找变量时会不会沿着它的原型链查找呢?这样就有两种可能:
先查找作用域链前端的变量对象,然后再查找它的原型,然后再查找作用域链中下一个变量对象,然后再查找它的原型;
一直查找作用域链中的变量对象,直到window对象,再查找它的原型。
然而在使用with语句做实验时,发现了下面的现象,因此本篇文章是我对下面的事实作出的猜测和理解,望指正!
考察下列代码:
Object.prototype.s=10;
(function(){
var s=25;
var obj={};
obj.__proto__={};
obj.__proto__.__proto__={s:15};
with(obj){
console.log(s);//输出15
}
}());
上述代码中,在Object.prototype中定义了一个属性s=10,在匿名函数中定义一个变量s=25,其中又有一个对象obj,在obj的二级原型链中定义一个属性s=15,然后,使用with将这个对象obj挂在作用域链顶端,输出s,但是它输出了15.
对此我做出的猜测是:在搜查变量中实际上是一个二维的过程而不是一维的,它构造出的二维链是这样子的:
所以上述过程中,先从obj(即作用域链顶端的变量对象开始搜查),因为obj本身没有s,则沿着obj的原型链搜查,在二级原型链中找到了s=15,从而停止搜查,返回s=15的值,故而输出15.
按照这个思路,发现所有对象的原型链都最终指向Obje.prototype,所以为了验证这个猜想,做下述实验,即删除s=15这个语句
Object.prototype.s=10; //保留Object.prototype中的s
(function(){
var s=25;
var obj={};
obj.__proto__={};
obj.__proto__.__proto__={};//删除了s:15
with(obj){
console.log(s);//输出10
}
}());
上述代码输出了10,这就是说,沿着第一个作用域链结点的原型链找,最终在Objec.prototype中找到了s,从而停止查找,那么函数中s=25没有遍历到
下面做个实验,证明确实是二维查找。
使用嵌套的with语句,在作用域链顶端挂两个对象,其中第二个对象是一个函数对象,而s正是在Function.prototype中,如果上面的猜想可行,那么应该能正确输出s,而事实上确实如此
Function.prototype.s=5;//给Funct.prototype添加s
var obj={};
var func=function(){};
(function(){
var s=25; //s=25
with(func){ //嵌套
with(obj){
console.log(s); //输出5
}
}
}());
上述输出的正是5,其过程如下所示:
最后要特别说明的是,函数对象本身和函数的上下文不是同一个东西,比如上图中,我并不知道匿名函数上下文的__proto__指向哪里,但是我认为它不会指向Function.prototype,因为如果Function.prototype在匿名函数上下文的继承链中,那么下面代码应该能正常输出:
Function.prototype.s=5;//给Funct.prototype添加s
var obj={};
(function(){
with(obj){
console.log(s); //输出????
}
}());
也就是说,因为obj本身没有s,一直找到Object.prototype中也没有s,转而从作用域链的下一节点也即匿名函数上下文寻找,同时匿名函数中也没有s,如果Function.prototype在匿名函数上下文的继承链中,那么应该能找到s=5,但是很遗憾,上面输出的是
Uncaught ReferenceError: s is not defined。
若改成下面这样则能正常输出15,因为s本身就在匿名函数的上下文中
Function.prototype.s=5;//给Funct.prototype添加s
var obj={};
(function(s=15){ //参数
with(obj){
console.log(s); //输出15
}
}());
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。