日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
輕松理解JS中的面向?qū)ο?,順便搞懂prototype和__proto__

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

創(chuàng)新互聯(lián)建站咨詢電話:13518219792,為您提供成都網(wǎng)站建設(shè)網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),創(chuàng)新互聯(lián)建站網(wǎng)頁(yè)制作領(lǐng)域十載,包括成都工商代辦等多個(gè)領(lǐng)域擁有多年的網(wǎng)站制作經(jīng)驗(yàn),選擇創(chuàng)新互聯(lián)建站,為企業(yè)錦上添花。

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

 
 
 
 
  1. public class Puppy{ 
  2.     int puppyAge; 
  3.     public Puppy(age){ 
  4.       puppyAge = age; 
  5.     } 
  6.     public void say() { 
  7.       System.out.println("汪汪汪");  
  8.     } 
  9. }

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

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

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

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

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

 
 
 
 
  1. function Puppy() {}

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

 
 
 
 
  1. const myPuppy = new Puppy();

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

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

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

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

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

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

實(shí)例方法用prototype

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

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

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

 
 
 
 
  1. myPuppy.say();    // 汪汪汪

實(shí)例方法查找用__proto__

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

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

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

constructor

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

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

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

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

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

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

靜態(tài)方法

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

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

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

繼承

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

prototype,最簡(jiǎn)單的方法就是子類原型的__proto__指向父類原型就行了。function Parent() {}

 
 
 
 
  1. function Child() {} 
  2. Child.prototype.__proto__ = Parent.prototype; 
  3. const obj = new Child(); 
  4. console.log(obj instanceof Child );   // true 
  5. console.log(obj instanceof Parent );   // true

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

 
 
 
 
  1. function Parent() { 
  2.   this.parentAge = 50; 
  3. function Child() {} 
  4. Child.prototype.__proto__ = Parent.prototype; 
  5. const obj = new Child(); 
  6. console.log(obj.parentAge);    // undefined

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

 
 
 
 
  1. function Parent() { 
  2.   this.parentAge = 50; 
  3. function Child() {} 
  4. Child.prototype.__proto__ = new Parent(); 
  5. const obj = new Child(); 
  6. console.log(obj.parentAge);    // 50

上述方法會(huì)多一個(gè)__proto__層級(jí),可以換成修改Child.prototype的指向來解決,注意將

 
 
 
 
  1. Child.prototype.constructor重置回來:function Parent() { 
  2.   this.parentAge = 50; 
  3. function Child() {} 
  4. Child.prototype = new Parent(); 
  5. ChildChild.prototype.constructor = Child;      // 注意重置constructor 
  6. const obj = new Child(); 
  7. console.log(obj.parentAge);    // 50

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

自己實(shí)現(xiàn)一個(gè)new

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

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

自己實(shí)現(xiàn)一個(gè)instanceof

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

 
 
 
 
  1. function myInstanceof(targetObj, targetClass) { 
  2.   // 參數(shù)檢查 
  3.   if(!targetObj || !targetClass || !targetObj.__proto__ || !targetClass.prototype){ 
  4.     return false; 
  5.   } 
  6.   let current = targetObj; 
  7.   while(current) {   // 一直往原型鏈上面找 
  8.     if(current.__proto__ === targetClass.prototype) { 
  9.       return true;    // 找到了返回true 
  10.     } 
  11.     currentcurrent = current.__proto__; 
  12.   } 
  13.   return false;     // 沒找到返回false 
  14. // 用我們前面的繼承實(shí)驗(yàn)下 
  15. function Parent() {} 
  16. function Child() {} 
  17. Child.prototype.__proto__ = Parent.prototype; 
  18. const obj = new Child(); 
  19. console.log(myInstanceof(obj, Child) );   // true 
  20. console.log(myInstanceof(obj, Parent) );   // true
  21. console.log(myInstanceof({}, Parent) );   // false

總結(jié)

最后來個(gè)總結(jié),其實(shí)前面小節(jié)的標(biāo)題就是核心了,我們?cè)賮砜偨Y(jié)下:

  1.  JS中的函數(shù)可以作為函數(shù)使用,也可以作為類使用
  2.  作為類使用的函數(shù)實(shí)例化時(shí)需要使用new
  3.  為了讓函數(shù)具有類的功能,函數(shù)都具有prototype屬性。
  4.  為了讓實(shí)例化出來的對(duì)象能夠訪問到prototype上的屬性和方法,實(shí)例對(duì)象的__proto__指向了類的prototype。所以prototype是函數(shù)的屬性,不是對(duì)象的。對(duì)象擁有的是__proto__,是用來查找prototype的。

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

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

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

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

再來看一下完整圖:


當(dāng)前題目:輕松理解JS中的面向?qū)ο螅槺愀愣畃rototype和__proto__
標(biāo)題鏈接:http://m.5511xx.com/article/cocogpp.html