浅谈javascript的作用域链与原型链

myplist 2014-08-11 12:03:01

从一个问题开始:

代码如下


var name = "Robben";

console.info(name);
console.info(this.name);


在全局环境下,这段代码执行的结果是一样的,但它们含义是否一样呢?
首先,我们来看看console.info(name):在执行这一行代码的时候,需要访问name的值,会查找了一次函数的执行环境,然后在控制台打印name的值。
然后,我们来看看console.info(this.name):首先要在执行环境中查找this变量绑定的对象,然后在对象上搜索name的值。

所以我的结论是:最终打印的结果虽然一样,但背后所代表的含义却是有本质区别的。结果一样得"归功"于这样的一个既存事实:在全局环境下,this指向的是window对象,而在全局环境下定义的任何属性或函数都将作为window对象的属性或函数。

如果我们把以上代码放在一个局部环境(即函数里)会如何呢?代码如下:

function Demo(){
this.name = "Robben in object";
var name = "Robben in local";

console.info(name);
console.info(this.name);
}
object = new Demo();


在函数里定义的局部变量并不会作为object对象的属性存在。如果定义该引用类型时没有使用到闭包,var name = "Robben in local"在返回对象时已经销毁了, 而this.name会随返回的对象一直存在着,直到对象被销毁。

所以我们得注意了:和全局中声明的name不同的是,局部声明的name并没有成为某个对象的属性。this.name和name就这样走上了各自的旅程。

思维跳一下,接以上代码,我们比较下以下两行代码为什么会产生不同:

object = new Demo();
Demo();


要解答这个问题我们又得细细琢磨下了:new Demo()奇特就奇特在这个new字,我觉得可以如下理解new在function Demo内的作用机制:

Object obj = new Object();
this = obj;

而有关Demo(),我就不重复说this.Demo()和Demo()的区别了,虽然在全局环境中它们执行的结果是一样的。我觉得可以理解Demo();这行代码为如下:

this = window;


闭包给我们带来了什么呢?还是先上代码:

function Demo(){
this.name = "Robben in object";
var name = "Robben in local";
this.sayHello1 = function() {
console.info(this.name);
}
this.sayHello2 = function() {
console.info(name);
}
}
object = new Demo()
object.sayHello1();
object.sayHello2();

由于闭包的存在,让我们在全局环境中可以访问到var name = "Robben in local"声明的name的值。这说明了一个什么问题呢?明显是说该name的值在对象返回后没有被销毁,依然存在内存中。那我们使用闭包有什么好处呢?我觉得要回答这个问题,我们可以想想在全局环境中定义太多的变量有什么坏处,个从观点认为有二:一是没有充分利用被分配的不宽裕的内存,二是很容易产生命名冲突。闭包的这种使用方式其实只是解决了一个问题,减少了命名冲突的可能性。
谈谈作用域链与原型链的时候到了

javascript的面向对象和java相比,是一个很特殊的面向对象。不过个人觉得,javascript的面向对象大的感觉上和python还是有些类似的。如果要谈谈这三种语言面向对象的异同,那估计有得谈了。不过我确实有写一篇博文谈的意愿,主要是想和大家交流下我的理解是否有偏差。

回到作用域链与原型链的话题,先上个代码:

name = "Robben";
function Demo() {
var name = "Robben in local";
this.name = "Robben in object";
this.sayHello1 = function() {
console.info(name);
}
this.sayHello2 = function() {
console.info(this.name);
};
this.sayHello3 = function() {
return (function(){console.info(this.name);})();
}
}
Demo.name = "Robben in classlike object"; //真不巧,函数有个默认的只读name属性,属性值为函数名

object = new Demo();
object.sayHello1();
object.sayHello2();
object.sayHello3();


在javascript中函数也是对象,在Python中类也是对象。javascript和Python都属于动态语言,它们能动态地给对象添加属性。以我写java的经验,我觉得Demo.name怎么也得类似于java类里面的类变量吧。可惜。。我只猜对了一半。。。对一半的意思是,我猜中了python却猜错了javascript.如果我们注释this.name = "Robben in object",我们会发现,我现还是无法访问Demo.name的值。在python中如果我们给类动态添加了属性,是可以访问得到的。只从读的角度来说,python的这种写法类似于java里面定义类变量。

如果你能猜中代码在控制台打印出来的消息,然后知道为什么无法通过object.name访问到Demo.name的值,那我觉得我所说的浅谈作用域链与原型链的任务也就完成了。
要理解第一点我给出的心法的:直接通过属性访问值是搜索作用域链,通过对象.属性访问值是先从作用域链搜索对象,再从原型链搜索对象的属性。匿名函数执行的作用域中的this默认是指向全局作用域的,即window。

要理解第二点我给出的心法是:实例只和构造函数的原型关联起来了,没有和构造函数关联起来。this.属性是为创建的对象添加属性,与构造函数这个对象无关。构造函数这个对象本身不在原型链上,所以你无法通过this.name访问到它的name属性。不过这还是有破解方法的,看看以下代码的Method1和Method2部分:

name = "Robben";
function Demo() {
var name = "Robben in local";
this.name = "Robben in object";
this.sayHello1 = function() {
console.info(name);
};
this.sayHello2 = function() {
console.info(this.name);
};
this.sayHello3 = function(){
return (function(){console.info(this.name);})();
};
//理解匿名函数的this指向的是window
var test = function() {
console.info(name);
console.info(this.name);
};
test();
}
// Method1
//Demo.prototype = Demo;
//Demo.prototype.constructor = Demo;
//Demo.name = "Robben in classlike object";

object = new Demo();
object.sayHello1();
object.sayHello2();
object.sayHello3();

// Method2
//object.sayHello2.call(Demo);


好了,浅谈到此搁笔。现在讲求"小而美",文章不宜过长~
...全文
172 2 打赏 收藏 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复

87,984

社区成员

发帖
与我相关
我的任务
社区描述
Web 开发 JavaScript
社区管理员
  • JavaScript
  • 无·法
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧