君子生非异也,善假于物也。
——《荀子·劝学》
JavaScript 中关于类的继承是一个原型继承的方式,总结起来其精髓就在于“借”这个字。
JavaScript 中,对于一个对象,它所能访问的属性,不一定全是它自己的属性,也有可能是它原型的属性,甚至是原型的原型的属性……
__proto__
是一个属性,除了null
与 undefined
没有以外,其他的都具有这个属性。用于指向对象的原型,是一个引用(指针)。
"123".__proto__ === String.prototype; // true
(1).__proto__ === Number.prototype; // true
(function A(x){
return x;}).__proto__ === Function.prototype; // true
对象的构造器(constructor) 是一个函数。
函数的原型是一个原型对象(prototype)。
从一个函数(类)能构造出很多对象,但一个函数只有对应的一个原型。从某种意义上来说,这个原型对象也是函数的实例之一,不过这是一个特殊的实例。
假设有一个函数 A
function A(){
};
对于其原型(prototype)对象obj
来说,构造器就是A
var obj = A.prototype;
obj.constructor === A; // true
但对于一般的f实例化对象x
:
var x = new A();
x.hasOwnProperty('constructor'); // false
x.constructor === A; // true
x
是不具有constructor
属性的,但x.constructor
依然能访问,并且指向的也是A
。这是因为x.constructor
沿着原型链进行了回溯搜索,实际上x.constructor
就是x.__proto__.constructor
,而x.__proto__
就是A.prototype
。
function A(){
this.key = 1;};
A.prototype.val = 1;
var x = new A();
对于这样一个基本的构造。我们可以发现,x
可访问的属性如下
x.__defineGetter__
x.__defineSetter__
x.__lookupGetter__
x.__lookupSetter__
x.__proto__
x.constructor
x.hasOwnProperty
x.isPrototypeOf
x.propertyIsEnumerable
x.toLocaleString
x.toString
x.valueOf
x.val
x.key
实际上真正属于 x
自己的属性有什么呢?
只有x.key
是真正属于x
的,这两个都是在new A()
的调用过程中产生的。
其他的,就连x.val
都是属于A.prototype
而不属于x
。
x.__proto__ === A.prototype; // true
x.__proto__.__proto__ === Object.prototype; // true
x.__proto__.__proto__.__proto__ === null; // true
x.hasOwnProperty('__proto__'); // false
x.__proto__.hasOwnProperty('__proto__'); // false
x.__proto__.__proto__.hasOwnProperty('__proto__'); // true
实际上我们可以看到,x
能访问的属性,除了__proto__, key
这样自身的属性之外,还有A.prototype, Object.prototype
的。
总结:那么现在我们可以知道在构造过程究竟发生了什么:
function myNew(f) {
// create new object
var obj = {};
// link to prototype chain
obj.__proto__ = f.prototype;
// apply constructor to the object
f.apply(obj, Array.prototype.slice.call(arguments, 1));
// return the object
return obj;
}
这个过程也可以手动地操作一下。
function f() {
this.key = 1; };
f.prototype.val = 2;
var x = {};
x instanceof f; // false
x.val; // undefined
x.key; // undefined
x.__proto__ = f.prototype;
x instanceof f; // true
x.val; // 2
x.key; // undefined
f.call(x); // same as f.apply(x, []);
x instanceof f; // true
x.val; // 2
x.key; // 1
类的继承其实很简单:
function A(){
};
function B(){
};
// B inherits A
B.prototype.__proto__ = A.prototype;
这样就完成了类的继承,如果理解了最终的实例对象的寻找属性的方式,那么这种做法是显而易见的。