对象和原型链
前言
平时在工作中用原型和this极少,但在看别人库时因为不熟悉这块内容,看得比较痛苦,所以还是要系统的学习下javascirpt原生的内容。
概述
javascript原生只有对象,没有类这个概念,new运算符,es6的class只是对面向对象模式的模拟。javascript内部是通过原型对象和原型链的机制来实现对象属性和方法的复用,当在当前对象找不到对应的属性时,会在它的原型链更高级找,直到原型链的顶层Object.prototype。
所以在我们设计程序时,"OLOO"(objects-linked-to-other-objects)是比面向对象(class-orientation)更自然的方式。
函数与对象
javascrpt原始类型中没有函数类型,函数是属于复杂原始类型对象的子类型。我们用instanceof来证明。
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
const Foo = function(){}
const foo = new Foo // {}
foo instanceof Foo // true
Foo instanceof Object // true
Foo.prototype // {constructor: Foo()}
Foo.prototype === Object.getPrototypeOf(foo) // true
可以看到foo的原型链上游存在构造函数(本质就是普通函数)Foo的原型属性。Foo函数有原型属性prototype,还有call、apply等属性方法,表明函数是对象的子类型。
那为什么Foo instanceof Object也为true呢?我们可以用对象的__proto__属性(等价于Object.getPrototypeOf方法)来探索,这两种方式都可以获取到对象原型链的上一级对象原型对象。
Foo.__proto__ // ƒ () { [native code] }
Foo.__proto__ === Function.prototype // true 函数的原型对象
Foo.__proto__.call // ƒ call() { [native code] } call等方法都在这里
Foo.prototype.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Foo.__proto__.__proto__ === Object.prototype // true 对象的原型对象
所以我们明白了虽然Foo是函数,但因为函数也是对象的子类型,所以也可以用instanceof来判断Object.prototype这个顶层原型对象是否在它的原型链上。
那么在javascript里函数和对象原型链中的关系到底是什么呢?可以看一张图

可以看出Foo函数由内置函数Function创建,Function构造函数创建普通函数,Object构造函数创建普通对象。Function的prototype和__proto__和Object的__proto__都指向Function.prototype原型对象,即ƒ () { [native code] },里面包含call等方法,所以我们所有的函数都可以调用。
而Function.prototype原型链上游是Object.prototype,即{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …},所以我们所有对象都可以调用toString等内置方法。
更多的讨论可以参考这个issus的内容。
面向对象和原型链继承
这两种设计模式我实际没多少经验,引用You-Dont-Know-JS的说法,原型链继承更加易于理解些,看下面的例子:
面向对象:
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
它的继承结构如图:

原型继承:
var Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
var Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
它的继承结构如图:

明显后者更简单些。原文内容