最近在学习javascript,javascript是原型继承,原型继承和普通的类、对象这种继承模型相比更容易让人迷惑。这个文章记录一下我的javascript原型继承的解惑过程。
为了方便叙述,我是用a–>b来表示a继承了b(下面也称作原型链)。
在理解原型继承时,一定要强化一个观点:在javascript里,一切皆是对象。
javascript中的原型继承
function F1(){}
function F2(){}
function F3(){}
F2.prototype=Object.create(F1.prototype);
F3.prototype=Object.create(F2.prototype);
var f3 = new F3();
javascript里的原型继承是这个样子的,在上述的代码里可以有继承关系:
f3–>F3.prototype–>F2.prototype–>F1.prototype–>…
上述链上的节点都是对象,当需要访问f3的某个属性时,就从左向右在这条链上寻找,知道找到或者节点为空。
这里要注意一点节点上的是对象,而并不是F1、F2、F3的方法(即使方法也是对象)。
正常的原型继承
上述javascript里的原型继承是畸形的,下面会给出一个正常的原型继承,首先说明几点:
1.万物皆对象,javascript里没有类的概念,所以继承是对象继承对象。
2.原型继承链,其实就是一个链表的结构,有个引用指向它继承的对象就可以了。javascript里对象有一个属性_proto_就是这个作用。需要注意的是,这个对象并不是标准规定的,但多数情况下都有可以访问到,所以不要在开发时使用_proto_。
var v1 = {}
var v2 = {}
var v3 = {}
v3.__proto__=v2;
v2.__proto__=v1;
在这个例子中的继承关系是:
v3–>v2–>v1–>…
所以一个对象,它的继承关系,就是_proto_的引用链。
new 构造方法
首先,构造方法就是一个普通的方法。只是一个方法前面是new时,它就成了F构造方法,作为构造方法被调用和作为普通方法被调用有些差别。
new F(arguments…),调用构造方法,会做下面三个事情:
- 生成一个空的对象,让后让该对象的proto属性指向F.prototype。
- 让该对象作为上下文(this)调用F方法。
- 返回该对象。
下面例子模拟一个new操作符:
function New(f) {
var n = { '__proto__': f.prototype };
return function() {
f.apply(n, arguments);
return n;
};
}
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
print: function() { console.log(this.x, this.y); }
};
var p1 = new Point(10, 20);
p1.print(); // 10 20
console.log(p1 instanceof Point); // true
var p2 = New(Point)(10, 20);
p2.print(); // 10 20
console.log(p2 instanceof Point); // true
Object.create()
The Object.create() method creates a new object with the specified prototype object and properties.
在第一个例子中使用了这个方法,该方法的作用是返回一个对象,返回的对象继承自第一个参数。
下面这个例子模仿Object.create()的功能:
Object.create = function (parent) {
return { '__proto__': parent };
};
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = Object.create(Point);
p.x = 10;
p.y = 20;
p.print(); // 10 20
Object.getPrototypeOf()
The Object.getPrototypeOf() method returns the prototype (i.e. the value of the internal [[Prototype]] property) of the specified object.
可以理解为返回_proto_
instanceof
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
// 定义构造函数
function C(){}
function D(){}
var o = new C();
// true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof C;
// false,因为 D.prototype不在o的原型链上
o instanceof D;
o instanceof Object; // true,因为Object.prototype.isPrototypeOf(o)返回true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype指向了一个空对象,这个空对象不在o的原型链上.
D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true
理解一下为什么最后一行是true。o3 instanceof C;就是判断o3的原型链中是否包含C.prototype。经过上面的代码后o3的原型链是这样的。
o3–>D.prototype(=new C())–>C.prototype
总结
- 1.继承关系根据__proto__链上的对象查找。在new完对象以后,可能会改变链中对象的引用关系,判断时要弄清楚当前运行链中的引用关系。
- 2.调用构造方法时,把__proto__的值赋成方法的prototype属性。
- 3.原型链上都是对象,在javascript里多数会是Function.prototype,要搞明白对象和方法的关系。
参考
Javascript – How Prototypal Inheritance really works
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof
Javascript继承机制的设计思想