一、一切皆為對象
JavaScript是一個(gè)面向?qū)ο螅ㄔ蛯ο螅┱Z言,除了一些基礎(chǔ)類型,一切皆為對象,所有的對象都是函數(shù)(Function也是對象)創(chuàng)建的。
面向?qū)ο蟮睦^承等一些特性,像極了人類的傳承和繁衍。我們先解釋下一切皆為對象:都是爹養(yǎng)娘生的。對于JS原型鏈的鼻祖Object.prototype就像是人類的祖先一樣,是其他物種進(jìn)化而來的,或者是女媧捏出來的,就不是Function構(gòu)造出來的。
1.1 原型對象
原型對象是面向?qū)ο笾械囊环N。區(qū)別于面向?qū)ο?,JavaScript中的原型對象是無類型(class)的,雖然ES6中加入了class這一特性,但是這個(gè)class就只是一個(gè)語法糖,并不是一個(gè)面向?qū)ο箢惖膶?shí)現(xiàn)。JavaScript通過原型鏈這一特性來實(shí)現(xiàn)對象的繼承。
/**
* 通過構(gòu)造函數(shù)創(chuàng)建對象
*/
function Obj() {}; // Obj.propotype 的構(gòu)造函數(shù)
console.log(Obj.propotype.constructor); // function Obj
var obj = new Obj(); // Obj.propotype 的實(shí)例對象
console.log(obj.__proto__); // Obj.propotype
以上代碼實(shí)現(xiàn)了用Obj.propotype原型對象的構(gòu)造函數(shù)Obj創(chuàng)建了實(shí)例對象obj的一個(gè)示例,他們的關(guān)系如下圖:

解釋一下Obj.propotype示意圖:
function Obj(); // 構(gòu)造函數(shù)
Obj.propotype; // 原型對象
var obj = new Obj(); // 實(shí)例對象
1.2 原型對象與面向?qū)ο蟮膮^(qū)別
一些后端開發(fā)的同學(xué)對于面向?qū)ο蟮牡谝挥∠罂赡芫褪荗bject。像Java這樣的語言中類都是繼承Object的,恰好JavaScript也有Object,大家可能先入為主的認(rèn)為JS中的Object就是Java中的Object,這種想法是錯(cuò)誤的。
非常典型的一個(gè)例子(反例)就是許多人在問:Function和Object到底是什么關(guān)系?為什么Object是Function構(gòu)造出來的?為什么Function能夠構(gòu)造出它自己?Object大兄弟,你的家庭關(guān)系有點(diǎn)亂哦。
Object.__proto__ === Function.prototype; // true
Function.__proto__ === Function.prototype; // true
說好的實(shí)例的__proto__(隱式原型)等于對象的prototype(原型)?。徽f好的一切都是對象,函數(shù)也是對象?。粸槭裁碠bject反而是Function構(gòu)造出來的?這不是逗我玩呢?
引起這樣的誤解是因?yàn)槠綍r(shí)我們都以構(gòu)造函數(shù)的名稱來簡稱一個(gè)原型對象,其實(shí)Function和Object都只是構(gòu)造函數(shù),JavaScript的所有對象都是繼承Object.prototype的,并不是Object!不是Object!不是Object!Function和Object都是function聲明的函數(shù),所以它們是繼承Function.prototype的,是Function構(gòu)造出來的。這里就不舉人類起源的例子了,有點(diǎn)不可描述,留在第三節(jié)說。
不知道上面的反例是把你們掰直了還是掰得更彎了。。。。
舉一反三這種能力對于學(xué)習(xí)是很重要的,但是非常忌諱先入為主的思想,虛心使人進(jìn)步。
二、繼承
繼承的意思不難理解,就是子承父業(yè)或者是子承祖業(yè)。在ES6之前,JavaScript沒有明確規(guī)定繼承的概念,ES6中才使用了extentds來規(guī)范繼承的方式。
2.1 new 的實(shí)現(xiàn)原理
看了很多同行實(shí)現(xiàn)js繼承的方案,很多都是基于new和Object.create。其實(shí)在構(gòu)造函數(shù)沒有返回值的情況下,new和Ojbect.create(Obj.prototype)實(shí)現(xiàn)的效果是差不多的,都能夠繼承Obj.prototype,大家可以跑一下下面的代碼對比效果:
function Obj() {
console.log('I am Obj constructor...');
this.name = 'Obj';
}
// 這里的參數(shù)絕對不是Obj,如過是Obj,則是繼承了Function
var obj2 = Object.create(Obj.prototype);
var obj3 = new Obj();
我們可以看到,對比于Object.create,new不僅繼承了Obj.prototype,還調(diào)用了構(gòu)造函數(shù)Obj。由此我們可以試著推理下new的實(shí)現(xiàn):
function fnew(constructor) {
var newObj = Object.create(constructor.prototype); // 繼承Obj.prototype
constructor.call(newObj); // 調(diào)用父對象的構(gòu)造函數(shù)
return newObj;
}
var obj4 = fnew(Obj);
我們通過fnew得到的obj4和new實(shí)現(xiàn)的obj3是一樣的!
2.2 extends 的實(shí)現(xiàn)原理
按照我們對于原型鏈的理解,new出來的實(shí)例對象是不是也是js的繼承呢?我理解的繼承是祖?zhèn)鞲?,父傳子,子傳孫。但是new出來的實(shí)例對象因?yàn)槿鄙贅?gòu)造函數(shù)而失去了再繼承和實(shí)例化的能力,就是說子不能傳孫了,這不是我想要的繼承!我要的繼承是像Function這樣繼承Object后還具備‘生育能力’的,像extends這樣的。
結(jié)合我們?nèi)祟惖男袨閬硭伎枷拢肷⒆拥糜械湍锇 ,F(xiàn)在new出來的實(shí)例對象再嫁給一個(gè)構(gòu)造函數(shù)就可以愉快的生孩子了,讓我們改造下fnew:
function fextends(protoConstructor, constructor) {
var newObj = Object.create(protoConstructor.prototype); // 繼承Obj.prototype
protoConstructor.call(newObj); // 調(diào)用父對象的構(gòu)造函數(shù)
constructor.prototype = newObj;
constructor.prototype.constructor = constructor;
return constructor;
}
var ObjChild = fextends(Obj, function() {});
這樣ObjChild就實(shí)現(xiàn)了Function extend Object的效果啦。那我們以此分析下ES6的class和extends其實(shí)就是類似于fextends(Object, function() {})的語法糖,class A extends B 就是類似于 var A = fextends(B, function A(){})的效果咯。
2.3 instansof原理和實(shí)現(xiàn)
MDN對instansof的解釋是:instanceof運(yùn)算符用于測試構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在對象的原型鏈中的任何位置。比如Function instanceof Object就是判斷Object.prototype是否出現(xiàn)在Function的原型鏈( __proto__ )中。

// instansof
function finstansof(a, b) {
if(a.__proto__) {
if(a.__proto__ === b.prototype) {
return true;
} else {
return finstansof(a.__proto__, b);
}
}
return false;
}
看完instansof示意圖和代碼,相信大家都掌握了instansof這一特性的原理。下次再有人跟你說A instansof B的意思是判斷A是否繼承B的這樣不嚴(yán)謹(jǐn)?shù)闹囌?,你告訴我,我給他寄一盒的刀片。
下面再給大家舉一個(gè)反例:世界上是先有Object還是先有Function?
Function instanceof Object // true
Object instanceof Function // true
毋庸置疑,F(xiàn)unction(Function.prototype的構(gòu)造函數(shù))和Object(Object.prototype的構(gòu)造函數(shù))都是函數(shù),那就是繼承Function.prototype的;結(jié)論就是先有Object.prototype的。