<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          輕松理解 JS 中的面向?qū)ο螅槺愀愣?prototype 和 __proto__

          共 6250字,需瀏覽 13分鐘

           ·

          2020-08-28 11:01

          (給前端大學(xué)加星標(biāo),提升前端技能.

          作者:蔣鵬飛

          https://juejin.im/post/5e50e5b16fb9a07c9a1959af

          這篇文章主要講一下JS中面向?qū)ο笠约?__proto__,ptototypeconstrucator,這幾個概念都是相關(guān)的,所以一起講了。


          在講這個之前我們先來說說類,了解面向?qū)ο蟮呐笥褢?yīng)該都知道,如果我要定義一個通用的類型我可以使用類(class)。比如在java中我們可以這樣定義一個類:

          public class Puppy{    int puppyAge;
          public Puppy(age){ puppyAge = age; }
          public void say() { System.out.println("汪汪汪"); }}

          上述代碼我們定義了一個Puppy類,這個類有一個屬性是puppyAge,也就是小狗的年齡,然后有一個構(gòu)造函數(shù)Puppy(),這個構(gòu)造函數(shù)接收一個參數(shù),可以設(shè)置小狗的年齡,另外還有一個說話的函數(shù)say。這是一個通用的類,當(dāng)我們需要一個兩歲的小狗實例是直接這樣寫,這個實例同時具有父類的方法:

          Puppy myPuppy = new Puppy( 2 );myPuppy.say();     // 汪汪汪

          但是早期的JS沒有class關(guān)鍵字?。ㄒ韵抡fJS沒有class關(guān)鍵字都是指ES6之前的JS,主要幫助大家理解概念,本文不涉及ES6的class),JS為了支持面向?qū)ο?,使用了一種比較曲折的方式,這也是導(dǎo)致大家迷惑的地方,其實我們將這種方式跟一般的面向?qū)ο箢惐绕饋砭秃芮逦恕?/span>下面我們來看看JS為了支持面向?qū)ο笮枰鉀Q哪些問題,都用了什么曲折的方式來解決。


          沒有class,用函數(shù)代替


          首先JS連class關(guān)鍵字都沒有,怎么辦呢?用函數(shù)代替,JS中最不缺的就是函數(shù),函數(shù)不僅能夠執(zhí)行普通功能,還能當(dāng)class使用。比如我們要用JS建一個小狗的類怎么寫呢?直接寫一個函數(shù)就行:

          function Puppy() {}

          這個函數(shù)可以直接用new關(guān)鍵字生成實例:

          const myPuppy = new Puppy();

          這樣我們也有了一個小狗實例,但是我們沒有構(gòu)造函數(shù),不能設(shè)置小狗年齡啊。


          函數(shù)本身就是構(gòu)造函數(shù)


          當(dāng)做類用的函數(shù)本身也是一個函數(shù),而且他就是默認(rèn)的構(gòu)造函數(shù)。我們想讓Puppy函數(shù)能夠設(shè)置實例的年齡,只要讓他接收參數(shù)就行了。

          function Puppy(age) {  this.puppyAge = age;}
          // 實例化時可以傳年齡參數(shù)了const myPuppy = new Puppy(2);

          注意上面代碼的this,被作為類使用的函數(shù)里面this總是指向?qū)嵗瘜ο?,也就是myPuppy。這么設(shè)計的目的就是讓使用者可以通過構(gòu)造函數(shù)給實例對象設(shè)置屬性,這時候console出來看

          myPuppy.puppyAge就是2。console.log(myPuppy.puppyAge);   // 輸出是 2


          實例方法用prototype


          上面我們實現(xiàn)了類和構(gòu)造函數(shù),但是類方法呢?Java版小狗還可以“汪汪汪”叫呢,JS版怎么辦呢?JS給出的解決方案是給方法添加一個prototype屬性,掛載在這上面的方法,在實例化的時候會給到實例對象。我們想要myPuppy能說話,就需要往Puppy.prototype添加說話的方法。

          Puppy.prototype.say = function() {  console.log("汪汪汪");}

          使用new關(guān)鍵字產(chǎn)生的實例都有類的prototype上的屬性和方法,我們在Puppy.prototype上添加了say方法,myPuppy就可以說話了,我么來試一下:

          myPuppy.say();    // 汪汪汪


          實例方法查找用__proto__


          那myPuppy怎么就能夠調(diào)用say方法了呢,我們把他打印出來看下,這個對象上并沒有say啊,這是從哪里來的呢?

          這就該__proto__上場了,當(dāng)你訪問一個對象上沒有的屬性時,比如myPuppy.say,對象會去__proto__查找。__proto__的值就等于父類的prototype,?myPuppy.__proto__指向了Puppy.prototype

          如果你訪問的屬性在Puppy.prototype也不存在,那又會繼續(xù)往Puppy.prototype.__proto__上找,這時候其實就找到了Object了,Object再往上找就沒有了,也就是null,這其實就是原型鏈。


          constructor


          我們說的constructor一般指類的prototype.constructorprototype.constructor是prototype上的一個保留屬性,這個屬性就指向類函數(shù)本身,用于指示當(dāng)前類的構(gòu)造函數(shù)。

          既然prototype.constructor是指向構(gòu)造函數(shù)的一個指針,那我們是不是可以通過它來修改構(gòu)造函數(shù)呢?我們來試試就知道了。我們先修改下這個函數(shù),然后新建一個實例看看效果:

          function Puppy(age) {  this.puppyAge = age;}
          Puppy.prototype.constructor = function myConstructor(age) { this.puppyAge2 = age + 1;}
          const myPuppy2 = new Puppy(2);console.log(myPuppy2.puppyAge); // 輸出是2

          上例說明,我們修改prototype.constructor只是修改了這個指針而已,并沒有修改真正的構(gòu)造函數(shù)。


          可能有的朋友會說我打印myPuppy2.constructor也有值啊,那constructor是不是也是對象本身的一個屬性呢?其實不是的,之所以你能打印出這個值,是因為你打印的時候,發(fā)現(xiàn)myPuppy2本身并不具有這個屬性,又去原型鏈上找了,找到了prototype.constructor我們可以用hasOwnProperty看一下就知道了:

          上面我們其實已經(jīng)說清楚了prototype,__proto__,constructor幾者之間的關(guān)系,下面畫一張圖來更直觀的看下:


          靜態(tài)方法


          我們知道很多面向?qū)ο笥徐o態(tài)方法這個概念,比如Java直接是加一個static關(guān)鍵字就能將一個方法定義為靜態(tài)方法。JS中定義一個靜態(tài)方法更簡單,直接將它作為類函數(shù)的屬性就行:

          Puppy.statciFunc = function() {    // statciFunc就是一個靜態(tài)方法  conlose.log('我是靜態(tài)方法,this拿不到實例對象');}      
          Puppy.statciFunc(); // 直接通過類名調(diào)用

          靜態(tài)方法和實例方法最主要的區(qū)別就是實例方法可以訪問到實例,可以對實例進行操作,而靜態(tài)方法一般用于跟實例無關(guān)的操作。這兩種方法在jQuery中有大量應(yīng)用,在jQuery中$(selector)其實拿到的就是實例對象,通過$(selector)進行操作的方法就是實例方法。比如$(selector).append(),這會往這個實例DOM添加新元素,他需要這個DOM實例才知道怎么操作,將append作為一個實例方法,他里面的this就會指向這個實例,就可以通過this操作DOM實例。那什么方法適合作為靜態(tài)方法呢?比如$.ajax,這里的ajax跟DOM實例沒關(guān)系,不需要這個this,可以直接掛載在$上作為靜態(tài)方法。


          繼承


          面向?qū)ο笤趺茨軟]有繼承呢,根據(jù)前面所講的知識,我們其實已經(jīng)能夠自己寫一個繼承了。所謂繼承不就是子類能夠繼承父類的屬性和方法嗎?換句話說就是子類能夠找到父類的

          prototype,最簡單的方法就是子類原型的__proto__指向父類原型就行了。function Parent() {}function Child() {}
          Child.prototype.__proto__ = Parent.prototype;
          const obj = new Child();console.log(obj instanceof Child ); // trueconsole.log(obj instanceof Parent ); // true

          上述繼承方法只是讓Child訪問到了Parent原型鏈,但是沒有執(zhí)行Parent的構(gòu)造函數(shù):

          function Parent() {  this.parentAge = 50;}function Child() {}
          Child.prototype.__proto__ = Parent.prototype;
          const obj = new Child();console.log(obj.parentAge); // undefined

          為了解決這個問題,我們不能單純的修改Child.prototype.__proto__指向,還需要用new執(zhí)行下Parent的構(gòu)造函數(shù):

          function Parent() {  this.parentAge = 50;}function Child() {}
          Child.prototype.__proto__ = new Parent();
          const obj = new Child();console.log(obj.parentAge); // 50

          上述方法會多一個__proto__層級,可以換成修改Child.prototype的指向來解決,注意將

          Child.prototype.constructor重置回來:function Parent() {  this.parentAge = 50;}function Child() {}
          Child.prototype = new Parent();Child.prototype.constructor = Child; // 注意重置constructor
          const obj = new Child();console.log(obj.parentAge); // 50

          當(dāng)然還有很多其他的繼承方式,他們的原理都差不多,只是實現(xiàn)方式不一樣,核心都是讓子類擁有父類的方法和屬性,感興趣的朋友可以自行查閱。


          自己實現(xiàn)一個new


          結(jié)合上面講的,我們知道new其實就是生成了一個對象,這個對象能夠訪問類的原型,知道了原理,我們就可以自己實現(xiàn)一個new了。

          function myNew(func, ...args) {  const obj = {};     // 新建一個空對象  func.call(obj, ...args);  // 執(zhí)行構(gòu)造函數(shù)  obj.__proto__ = func.prototype;    // 設(shè)置原型鏈
          return obj;}
          function Puppy(age) { this.puppyAge = age;}
          Puppy.prototype.say = function() { console.log("汪汪汪");}
          const myPuppy3 = myNew(Puppy, 2);
          console.log(myPuppy3.puppyAge); // 2console.log(myPuppy3.say()); // 汪汪汪


          自己實現(xiàn)一個instanceof


          知道了原理,其實我們也知道了instanceof是干啥的。instanceof不就是檢查一個對象是不是某個類的實例嗎?換句話說就是檢查一個對象的的原型鏈上有沒有這個類的prototype,知道了這個我們就可以自己實現(xiàn)一個了:

          function myInstanceof(targetObj, targetClass) {  // 參數(shù)檢查  if(!targetObj || !targetClass || !targetObj.__proto__ || !targetClass.prototype){    return false;  }
          let current = targetObj;
          while(current) { // 一直往原型鏈上面找 if(current.__proto__ === targetClass.prototype) { return true; // 找到了返回true }
          current = current.__proto__; }
          return false; // 沒找到返回false}
          // 用我們前面的繼承實驗下function Parent() {}function Child() {}
          Child.prototype.__proto__ = Parent.prototype;
          const obj = new Child();console.log(myInstanceof(obj, Child) ); // trueconsole.log(myInstanceof(obj, Parent) ); // trueconsole.log(myInstanceof({}, Parent) ); // false


          總結(jié)


          最后來個總結(jié),其實前面小節(jié)的標(biāo)題就是核心了,我們再來總結(jié)下:

          1. JS中的函數(shù)可以作為函數(shù)使用,也可以作為類使用

          2. 作為類使用的函數(shù)實例化時需要使用new

          3. 為了讓函數(shù)具有類的功能,函數(shù)都具有prototype屬性。

          4. 為了讓實例化出來的對象能夠訪問到prototype上的屬性和方法,實例對象的__proto__指向了類的prototype所以prototype是函數(shù)的屬性,不是對象的。對象擁有的是__proto__,是用來查找prototype的。

          5. prototype.constructor指向的是構(gòu)造函數(shù),也就是類函數(shù)本身。改變這個指針并不能改變構(gòu)造函數(shù)。

          6. 對象本身并沒有constructor屬性,你訪問到的是原型鏈上的prototype.constructor。

          7. 函數(shù)本身也是對象,也具有__proto__,他指向的是JS內(nèi)置對象Function的原型Function.prototype。所以你才能調(diào)用func.call,func.apply這些方法,你調(diào)用的其實是Function.prototype.callFunction.prototype.apply。

          8. prototype本身也是對象,所以他也有__proto__,指向了他父級的prototype。__proto__prototype的這種鏈?zhǔn)街赶驑?gòu)成了JS的原型鏈。原型鏈的最終指向是Object。Object上面原型鏈?zhǔn)莕ull,即Object.__proto__ === null。

          再來看一下完整圖:


          點分享
          點點贊
          點在看
          瀏覽 73
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  风流老熟女一区二区三区 | 麻豆精品秘 国产传媒潘甜甜 | 十八禁网站在线看 | 91男女操逼的视频 | 99r在线免费观看 |