JavaScript的原型继承是老生常谈。由于原型即prototype本身也是对象,所以“原型”继承可认为是一种特殊的“对象式”继承。”对象式“继承是笔者基于自己的理解,所提出的一个名词。本文就着重阐述这两种继承方式的异同之处。
原型继承
JavaScript里的原型即prototype是函数的特有属性,原型继承事先得有函数。
// 定义函数Foo
function Foo(name) {
this.name = name;
}
// 定义Foo的原型
Foo.prototype.say = function() {
console.log(this.name, 'say');
}
函数及其原型定义好了,就可以使用原型继承了。
var foo = new Foo('foo');
foo.say() //foo say
foo instanceof Foo; //true。 foo 是Foo的一个实例
foo.__proto__ === Foo.prototype // true
上面用的是new操作符,它实际是通过Object.create工作,其过程如下。所以new其实是Object.create的便利操作方式。
var foo = Object.create(Foo.prototype);
foo.name = 'foo'
foo.say();
foo instanceof Foo; //true。 foo 是Foo的一个实例
foo.__proto__ === Foo.prototype // true
请打起精神,Object.create(…)接受一个函数原型即object实例,以此实例为“模型”,创建新的object实例。新object实例的__proto__指向前function的prototype,自此新object实例也就拥有了前function prototype的字段和方法。
既然Foo.prototype本身是object实例,那么我们是否可以给Object.create(…)传入一个普通的object实例呢?答案是可以的。这就是本文所要表述的“对象式”继承。
“对象式“继承
开门见山地用code来说明:
// 创建对象实例Foo
var Foo = {
name: 'foo',
say: function() {
console.log(this.name, 'say');
}
}
// 以Foo为“模型”,创建新的对象实例foo
var foo = Object.create(Foo);
foo.__proto__ == Foo;// true
// 所以foo也会拥有Foo的字段和方法,这点与原型继承类似。
foo.say(); // foo say.
// 但是foo不是Foo的实例。
foo instanceof Foo; // TypeError: Right-hand side of 'instanceof' is not callable
如果Foo是个函数,结果基本是相同的,除了instanceof Foo 会等于false。
function Foo() {
}
Foo.say = function() {
console.log(Foo.name, 'say');
}
// 以Foo为“模型”,创建新的对象实例foo
var foo = Object.create(Foo);
foo.__proto__ == Foo;// true
// 所以foo也会拥有Foo的字段和方法,这点与原型继承类似。
foo.say(); // Foo say.
// 但是foo不是Foo的实例。
foo instanceof Foo; // false
“对象式”继承的一个应用
举个不太恰当的例子,如果我们需要对Math.abs做些“修正”,对于在-1和1之间的数值保持原值。
var MMath = Object.create(Math);
MMath.abs = function(val) {
if (val >= -1 && val <=1) {
return val;
}
return MMath.__proto__.abs(val);
}
MMath.abs(0.5) // 0.5
MMath.abs(-0.5) // -0.5
MMath.abs(-2) // 2
揭秘Object.create魔术箱
不论原型继承还是“对象式”继承,其核心技术是Object.create(…)实现了对象实例的__proto__链。我们做个简单的实现:
Object.myCreate = function(obj) {
if (obj instanceof Object || obj === null) {
var newObj = {};
Object.setPrototypeOf(newObj, obj)
return newObj;
} else {
throw 'error happens. Should input a object';
}
}
function Fooo() {}
Fooo.say = function() {
console.log(Fooo.name, 'say');
}
var myFooo = Object.myCreate(Fooo);
myFooo.say(); // Fooo say
自此我们了解了Object.create(…)的黑魔法,也有助于我们理解Object.create({})和Oject.create(null)的区别,就是前者的__proto__是个object实例,拥有toString等方法。后者的__proto__是null,它不具有任何方法。在特别注重效率的情景,后者具有优势。
总结
本文提出“对象式”继承的概念,并与原型继承对比,阐述其区别和联系,希望有助于深化理解。虽然ES6越来越广泛应用了,了解函数原型等这些ES3/ES5的概念应该是有助于JS的深入学习。