一 原型
1. 基本概念
JavaScript是一种基于原型继承的语言,不包含传统的类继承模型,没有“类”和“实例”的概念,全靠原型链实现继承。
2. new 运算符
传统类继承,通过new命令创建实例,Brendan Eich也把new命令引入了JavaScript,通过new 构造函数来创建对象。
function Dog(name) {
this.name = name;
this.eat = function () { ...}
}
var daMao = new Dog("大毛");
var xiaoMao = new Dog("小毛");
上面创建了两个对象,但是有个问题就是,daMao和xiaoMao各有个eat function的副本,这有点浪费资源,怎么办呢。
3. prototype属性的引入
Brendan Eich为构造函数设置了一个prototype属性,这个属性指向一个对象(原型对象),所有实例对象共享该对象的方法和属性。给出如下代码
function Dog(name) {
this.name = name;
}
Dog.prototype = {
eat: function() {
//eat something
}
}
这就是原型继承的实现方式了,然后通过
var dogA = new Dog("大狗");
就可以创建新的dogA对象。
new运算符做了哪些工作呢,
(1) 创建一个空的对象,对象的__proto__属性指向Dog.prototype;
(2) 初始化对象,函数Dog被传入参数并调用,this指向该对象。
(3) 返回对象。
4. 原型链
大多数JavaScript实现用一个__proto__属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。—JavaScript秘密花园
Vjeux在Javascript – How Prototypal Inheritance really works中给出一段代码用于解释JS引擎如何查找属性:
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop))
return obj[prop]
else if (obj.__proto__ !== null)
return getProperty(obj.__proto__, prop)
else
return undefined;
}
上述dogA的原型链如下:
dogA
Dog.prototype
{eat: function() {}}
Object.prototype
{toString: ... , hasOwnProperty ...}
dogA具有原型链上对象的所有方法。
总结:
所有的实例对象共享同一个prototype对象,实例对象就好像"继承"了prototype对象一样。下面给出一张图(出自 ECMAScript规范)来解释原型继承的工作原理。
二 hasOwnProperty 和 for in
通过上述知道查找对象属性时会向上遍历原型链,如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。有时只需要判断对象是否具有自定义属性而不是原型链上的属性,这时候就必须使用“继承”自 Object.prototype 的 hasOwnProperty 方法。
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
注意: hasOwnProperty是可以被覆盖的,
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // always returns false
当hasOwnProperty被覆盖时,可以通过如下方式来调用外部的hasOwnProperty函数
({}).hasOwnProperty.call(foo, 'bar'); // true
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。可以通过Object.prototype原型上的hasOwnProperty函数来过滤不希望出现的属性。
for (var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
不仅是过滤不希望出现的属性,当JS对象被扩展时,使用for in遍历属性时可能会出错。Prototype类库就扩展了内置对象,因此当页面包含这个类库时,不使用hasOwnProperty过滤的for in循环可能会出问题。
Reference
[1] Javascript继承机制的设计思想.