JS:先給自己定一個(gè)小目標(biāo),例如先new一個(gè)對(duì)象出來

最近在研究iOS第三方框架JSPath的時(shí)候,原本以為該熱修復(fù)功能的實(shí)現(xiàn)是基于react-native的方式,但是根據(jù)很多博客和對(duì)該第三方源碼的分析發(fā)現(xiàn),JSPath就像這個(gè)名字一樣,其實(shí)是基于庫(kù)<JavaScriptCore.framework>與JS代碼進(jìn)行動(dòng)態(tài)調(diào)用,由于OC語言的動(dòng)態(tài)特性,在程序運(yùn)行時(shí),可以通過解析JS代碼動(dòng)態(tài)添加類、對(duì)象、執(zhí)行方法等。于是就翻看了一下JS,就搜到了這么一句話:一切皆對(duì)象...對(duì)象原型...繼承,眾說紛紜,好吧我承認(rèn),懵逼了,那么到底什么是原型,網(wǎng)上說的prototype 和 __proto__有什么聯(lián)系和不同,接下來就會(huì)談?wù)勅缦聨c(diǎn):

1. 創(chuàng)建一個(gè)對(duì)象

2. 函數(shù)與函數(shù)對(duì)象

3. 什么是prototype

4. 什么是__proto__

5. 結(jié)論

1. 創(chuàng)建一個(gè)對(duì)象

js對(duì)象有普通對(duì)象和函數(shù)對(duì)象,普通對(duì)象類似json或者說其他語言的字典,先來欣賞下:

var people = {
  name: "Tom",
  age: 18,
  init: function(name , age){
    this.name = name;
    this.age = age;
  }
}
people // ①

以上代碼創(chuàng)建了一個(gè)people對(duì)象,① 處在chrome打印為:

19-04-54.jpg

調(diào)用一下init方法吧,重新設(shè)置一下name和age屬性:

people.init("Jerry" , 23) ;
people // ②

② 處打印如下所示:

1

以上的例子就是js創(chuàng)建對(duì)象最簡(jiǎn)單的方法,但是有一個(gè)弊端,了解其它面向?qū)ο蟪绦蛘Z言(例如Java和OC)的猿應(yīng)該知道,通常我們獲得一個(gè)對(duì)象都是由一個(gè)模板實(shí)例化來的,我們把這個(gè)模板叫做類,由類可以得到任意多的對(duì)象(這就有點(diǎn)像設(shè)計(jì)圖和產(chǎn)品一樣),但是普通對(duì)象的創(chuàng)建并不能滿足這個(gè)條件,那么來看看函數(shù)對(duì)象。
其實(shí)一開始我是比較好奇,為啥js要基于函數(shù)創(chuàng)建對(duì)象,并通過原型搞繼承呢?這個(gè)原因要追溯到j(luò)avascript的誕生史,“與其說我愛Javascript,不如說我恨它。它是C語言和Self語言一夜情的產(chǎn)物”,感興趣的讀者可以讀一下這篇文章:<a >Javascript誕生記</a>。
那么首先來看一個(gè)簡(jiǎn)單的js函數(shù):

function sum(a , b) {
  return a+b;
}
sum(10,100); //110

現(xiàn)在有了函數(shù),馬上new一個(gè)對(duì)象出來:

var sumObject = new sum(10 , 100);
sumObject // 此處打印的是sumObject對(duì)象

如下圖所示:


19-47-29.jpg

我滴天哪,這是什么,不應(yīng)該是110么,確實(shí)不是110,現(xiàn)在的sumObject就是一個(gè)對(duì)象,也就是說通過new 一個(gè)函數(shù),就可以得到函數(shù)對(duì)象了,說到對(duì)象,那得有屬性和方法啊,這個(gè)可以有,重新定義一下sum函數(shù),讓他看起來更類一點(diǎn):

function Sum(a , b) {
  this.a = a;
  this.b = b;
  this.execute = function(){
    return this.a + this.b;
  }
}
var s = new Sum(10,110);
s;
s.execute();

對(duì)象s的打印結(jié)果為:

19-53-32.jpg

s.execute()執(zhí)行的結(jié)果:


19-53-56.jpg

面包有了,牛奶也有了,這樣我們可以通過這個(gè)Sum函數(shù)new出來很多的對(duì)象,噢耶!那么函數(shù)與函數(shù)對(duì)象有什么區(qū)別呢?繼續(xù)分析。

2. 函數(shù)與函數(shù)對(duì)象

首先來研究一下,為什么加了一個(gè)new,就能有對(duì)象呢?真理解不了咱們這些猿啊。
還是上面的Sum函數(shù)吧,我們執(zhí)行一下這個(gè)代碼:

var sum = Sum(); // undefined

是的返回的是undefined,Sum()這個(gè)函數(shù)并沒有返回值,所以sum的值為undefined并沒有毛病,如果我們使用sum函數(shù)呢:

var sumOrigin = sum(10,100); // 110

是不是又繞回來了,這個(gè)結(jié)果我們之前得到過的,此時(shí)sumOrigin其實(shí)就是函數(shù)的返回值,也就是說,我們call了一個(gè)函數(shù)。
我們首先能想到的是,通過在函數(shù)前加了一個(gè)new就得到了一個(gè)對(duì)象,js引擎要根據(jù)這個(gè)new返回給我們一個(gè)對(duì)象。猜的沒有錯(cuò),js引擎在遇到new一個(gè)函數(shù)的時(shí)候,會(huì)執(zhí)行兩步隱式的操作:

this = Object.create(Sum.prototype); // 1
.
.
.
.
return this; // 2

js引擎通過Sum的原型隱式創(chuàng)建了this對(duì)象,之后又將這個(gè)this對(duì)象返回給調(diào)用處。我們?cè)囈幌拢宻um(a,b)函數(shù)返回sum對(duì)象:

function sum(a,b){
  that = Object.create(sum.prototype);
  return that; //此處返回一個(gè)對(duì)象,不能返回a+b,否則得不到that對(duì)象
}

結(jié)果:


20-11-58.jpg

sum函數(shù)內(nèi)我們用that來接受對(duì)象,并返回給調(diào)用處。(為啥不用this,我在瀏覽器中進(jìn)行實(shí)例的編寫和調(diào)試,通過上述方式調(diào)用的話this代表的是window)。下面問題來了,prototype是什么?

3. 什么是prototype

上面說到,js引擎通過一個(gè)函數(shù)的prototype來構(gòu)建函數(shù)對(duì)象,那么就打印一下這個(gè)prototype吧:

Sum.prototype;

得到結(jié)果:


20-20-24.jpg

可以發(fā)現(xiàn)prototype也是一個(gè)對(duì)象,該對(duì)象內(nèi)有兩個(gè)引用類型的屬性變量:constructor和__proto__;這個(gè)constructor是構(gòu)建Sum對(duì)象的構(gòu)造函數(shù)么?試試就知道了。

Sum.prototype = null;
Sum.prototype ; //此處為null
var snut = new Sum(10,100);
snut;

結(jié)果如下:


21-07-52.jpg

再來執(zhí)行一下execute函數(shù):

snut.execute(); // 110

函數(shù)執(zhí)行結(jié)果為110;也就是說即使將Sum的prototype賦值null,同樣可以得到對(duì)象,那我要它有何用,看一下下面的例子:

Sum.prototype = {
  title : "Sum",
  age: 18,
}
var snut = new Sum(10,100);
snut;

如下圖所示:


21-13-53.jpg

可以看到當(dāng)我們?yōu)镾um的prototype指定了一個(gè)對(duì)象的時(shí)候,由Sum產(chǎn)生的對(duì)象snut中的__proto__屬性變成了prototype所指的對(duì)象,我們其實(shí)是將普通對(duì)象賦值給了Sum的prototype,那換一個(gè)函數(shù)對(duì)象吧:

function Minus(a , b){
  this.a = a;
  this.b = b;
  this.execute = function() {
    return a - b
  }
}
Sum.prototype = new Minus(10 , 110);
var snut = new Sum(100 , 10);
snut;

如果沒猜錯(cuò)的話,snut中的__proto__應(yīng)該指向Minus的對(duì)象(是對(duì)象啊,就是代碼里new Minus(10,110) 這個(gè)匿名對(duì)象),打印如下:


21-20-50.jpg

果然沒猜錯(cuò),這個(gè)__proto__就是賦值給Sum.prototype的Minus對(duì)象,試一試調(diào)用__proto__中的execute()方法:

snut.__proto__.execute() // 輸出 -100

輸出結(jié)果為-100。
好了分析完prototype了再來看看Object.create()這個(gè)方法吧,上面sum函數(shù)的例子,通過Object.create(sum.prototype)返回了一個(gè)that,再試試下面的代碼:

function muliple(x , y) {
  this.x = x;
  this.y = y;
  this.execute = function(){
    return x * y;
  }
}
var mul = Object.create(muliple.prototype);
mul;

打印結(jié)果如下


23-00-40.jpg

我們發(fā)現(xiàn)muliple的屬性都沒有了,但是mul對(duì)象中的__proto__卻有一個(gè)構(gòu)造函數(shù):muliple(x, y),那么可以理解為,通過Object.create(muliple.prototype)可以創(chuàng)建一個(gè)對(duì)象,該對(duì)象的原型就是muliple.prototype(Object.create()方法的第一個(gè)參數(shù))。清晰了么?還是糊涂了?可以這么理解,通過Object.create()方法js回返給你一個(gè)沒有屬性的對(duì)象,只有一個(gè)原型指向方法的第一個(gè)參數(shù)(指定的對(duì)象)。我們可以將那個(gè)小寫的sum改進(jìn)一下啦:

function sum(a,b){
  that = Object.create(sum.prototype);
  that.a = a ;
  that.b = b ;
  that.execute = function(){
    return that.a + that.b;
  }
  return that;
}
var s = sum(10 , 8);
s ;

執(zhí)行結(jié)果如下:


23-12-24.jpg

這樣就返回了一個(gè)完整的sum對(duì)象。

4. 什么是__proto__

有了上面對(duì)prototype的分析,相信應(yīng)該有答案了吧,__proto__這個(gè)玩意就是一個(gè)對(duì)象啊,這個(gè)對(duì)象是由函數(shù)對(duì)象的prototype來的:

Sum.prototype = new Minus(10,5);
var sm = new Sum(10,5);
sm.__proto__ === Sum.prototype; // true

嚴(yán)格相等,沒錯(cuò)就是一個(gè)東西,原型只不過也是一個(gè)對(duì)象,prototype,是在對(duì)象創(chuàng)建時(shí)傳遞給Object.create()函數(shù)的,而__proto__,是對(duì)象的屬性,只是該屬性有點(diǎn)特別,會(huì)構(gòu)成原型鏈,先來一睹原型鏈的風(fēng)采:


23-23-49.jpg

Sum->Minus->Object 這就是sm對(duì)象的原型鏈,身為對(duì)象的它原本可以平平凡凡的做一個(gè)安靜的對(duì)象,可偏偏不安定。當(dāng)我們?cè)噲D調(diào)用sm中的屬性時(shí),如果該屬性能在Sum這層找到,就返回并結(jié)束查找,否則會(huì)去Minus中去查找,還找不到就去Object這層去找,如果還找不到就undefined吧,因?yàn)镺bject的__proto__屬性為null,是不是有點(diǎn)類似于Java的繼承機(jī)制,子類找不到的東西就去父類找,只是在JS中我覺得用繼承這個(gè)詞并不是那么貼切。網(wǎng)上在講解這個(gè)原型的時(shí)候很多帖子畫了很多圖,繞來繞去的就被繞進(jìn)去了,你只需要把原型理解為一個(gè)比較特殊的對(duì)象,這個(gè)對(duì)象是被穿在釬子上的,一個(gè)原型挨著另一個(gè),組成一條鏈,原型并不是非得指向自身或者某一個(gè)對(duì)象,而是任意的對(duì)象,甚至可以將其設(shè)置成null,所以有些人說js的對(duì)象都繼承自O(shè)bject也不貼切。

5. 結(jié)論

prototype和__proto__本身就是對(duì)象,只不過是特殊的對(duì)象,創(chuàng)建后的對(duì)象有了__proto__屬性,可以用來實(shí)現(xiàn)繼承,通常原型鏈的頂層為Object。prototype中的constructor屬性并不是用來創(chuàng)建對(duì)象的構(gòu)造函數(shù)(我看好多博客都說是構(gòu)造了該對(duì)象的函數(shù)雖然通常情況下該函數(shù)與對(duì)象的構(gòu)造函數(shù)相同),這個(gè)constructor只是一個(gè)函數(shù)(注意是函數(shù)),通過constructor可以用一個(gè)對(duì)象構(gòu)建出其父類的對(duì)象,例如var mi = new sm.__proto__.__proto__.constructor()就構(gòu)建出了Minus的對(duì)象。----一切皆對(duì)象!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容