<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中原型、原型鏈(一)

          共 7550字,需瀏覽 16分鐘

           ·

          2021-04-30 19:24

          Javascript繼承機(jī)制的設(shè)計(jì)思想

          ***下面這部分說(shuō)的都是ES6之前的***

          首先我們先了解繼承從何而來(lái),借助阮一峰大神的文章一起來(lái)看看。

          首先我們得知道,JavaScript最開(kāi)始是Brendan Eich為網(wǎng)景瀏覽器開(kāi)發(fā)。因?yàn)樽铋_(kāi)始,Brendan Eich設(shè)計(jì)javascript的目的就只是為了解決網(wǎng)景公司Navigator瀏覽器和用戶交互的問(wèn)題,當(dāng)時(shí)C++、java這些已經(jīng)算是流行起來(lái)了,覺(jué)得沒(méi)有必要設(shè)計(jì)很復(fù)雜,只要能夠完成簡(jiǎn)單的一些操作就可以啦。

          為什么javascript使用new命令用來(lái)從原型對(duì)象生成一個(gè)實(shí)例對(duì)象呢,因?yàn)楫?dāng)時(shí)C++、java這些都是用的這種寫(xiě)法,也算是為了統(tǒng)一吧。但是,Javascript沒(méi)有"類",怎么來(lái)表示原型對(duì)象呢?想到C++和Java使用new命令時(shí),都會(huì)調(diào)用"類"的構(gòu)造函數(shù)(constructor)。就做了一個(gè)簡(jiǎn)化的設(shè)計(jì),在Javascript語(yǔ)言中,new命令后面跟的不是類,而是構(gòu)造函數(shù)。


          舉例


          舉例來(lái)說(shuō),現(xiàn)在有一個(gè)叫做Animal的構(gòu)造函數(shù),表示動(dòng)物對(duì)象的原型。

          function Animal(name) { ?this.name = name;}

          對(duì)這個(gè)構(gòu)造函數(shù)使用new,就會(huì)生成一個(gè)兔子對(duì)象的實(shí)例。

          let rabbit = new Animal('兔子');
          console.log("rabbit",rabbit); //兔子

          注意構(gòu)造函數(shù)中的this關(guān)鍵字,它就代表了新創(chuàng)建的實(shí)例對(duì)象。

          new運(yùn)算符的缺點(diǎn)

          SPRING

          用構(gòu)造函數(shù)生成實(shí)例對(duì)象,有一個(gè)缺點(diǎn),那就是無(wú)法共享屬性和方法。

          比如,在Animal對(duì)象的構(gòu)造函數(shù)中,設(shè)置一個(gè)實(shí)例對(duì)象的共有屬性type。

          function Animal(name) { ?this.name = name; ?this.type = '狗';}

          然后,生成兩個(gè)實(shí)例對(duì)象:

          let rabbit = new Animal('兔子');
          let dog = new Animal('二狗子');

          這兩個(gè)對(duì)象的species屬性是獨(dú)立的,修改其中一個(gè),不會(huì)影響到另一個(gè)。

          rabbit.type = '兔';
          console.log("rabbit",rabbit.type); //兔
          console.log("dog",dog.type); //狗,不受rabbit的影響


          雖然一方面可能會(huì)覺(jué)得自己的屬性只歸自己用挺好,但是這也不僅無(wú)法做到數(shù)據(jù)共享,也是極大的資源浪費(fèi)。

          prototype屬性的引入

          SPRING

          考慮到數(shù)據(jù)共享這一點(diǎn),Brendan Eich決定為構(gòu)造函數(shù)設(shè)置一個(gè)prototype屬性。

          這個(gè)屬性包含一個(gè)對(duì)象(以下簡(jiǎn)稱"prototype對(duì)象"),所有實(shí)例對(duì)象需要共享的屬性和方法,都放在這個(gè)對(duì)象里面;那些不需要共享的屬性和方法,就放在構(gòu)造函數(shù)里面。

          實(shí)例對(duì)象一旦創(chuàng)建,將自動(dòng)引用prototype對(duì)象的屬性和方法。也就是說(shuō),實(shí)例對(duì)象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。


          還是以Animal構(gòu)造函數(shù)為例,現(xiàn)在用prototype屬性進(jìn)行改寫(xiě):


          function Animal(name) { ?this.name = name;}
          Animal.prototype = { type: '兔子' };let rabbitA = new Animal('大兔子');let rabbitB = new Animal('小兔子');
          console.log("rabbitA",rabbitA.type);//兔子console.log("rabbitB",rabbitB.type);//兔子

          現(xiàn)在,type屬性放在prototype對(duì)象里,是兩個(gè)實(shí)例對(duì)象共享的。只要修改了prototype對(duì)象,就會(huì)同時(shí)影響到兩個(gè)實(shí)例對(duì)象。

          Animal.prototype.type = '狗子';
          console.log("rabbitA",rabbitA.type);//狗子
          console.log("rabbitB",rabbitB.type);//狗子

          由于所有的實(shí)例對(duì)象共享同一個(gè)prototype對(duì)象,那么從外界看起來(lái),prototype對(duì)象就好像是實(shí)例對(duì)象的原型,而實(shí)例對(duì)象則好像"繼承"了prototype對(duì)象一樣。

          這就是Javascript繼承機(jī)制的設(shè)計(jì)思想。

          對(duì)象

          創(chuàng)建對(duì)象

          SPRING

          雖然 Object 構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢杂脕?lái)創(chuàng)建單個(gè)對(duì)象,但這些方式有個(gè)明顯的缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。為解決這個(gè)問(wèn)題,人們開(kāi)始使用工廠模式的一種變體。

          工廠模式

          function createPerson(name, age, job){  ? let o = new Object();  ? o.name = name;  ? o.age = age;  ? o.job = job;  ? o.sayName = function(){  ? ? alert(this.name);  ? };  ?return o; } let person1 = createPerson("Nicholas", 29, "Software Engineer"); let person2 = createPerson("Greg", 27, "Doctor");

          函數(shù) createPerson()能夠根據(jù)接受的參數(shù)來(lái)構(gòu)建一個(gè)包含所有必要信息的 Person 對(duì)象。可以無(wú)數(shù)次地調(diào)用這個(gè)函數(shù),而每次它都會(huì)返回一個(gè)包含三個(gè)屬性一個(gè)方法的對(duì)象。工廠模式雖然解決了創(chuàng)建 多個(gè)相似對(duì)象的問(wèn)題,但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)。

          構(gòu)造函數(shù)模式

          function Person(name, age, job){  ?this.name = name;  ?this.age = age;  ?this.job = job;  ?this.sayName = function(){  ? ?alert(this.name);  ?}; } let person1 = new Person("Nicholas", 29, "Software Engineer"); let person2 = new Person("Greg", 27, "Doctor");


          在這個(gè)例子中,Person()函數(shù)取代了 createPerson()函數(shù)。我們注意到,Person()中的代碼 除了與 createPerson()中相同的部分外,還存在以下不同之處:?

          1、沒(méi)有顯式地創(chuàng)建對(duì)象;

          2、直接將屬性和方法賦給了 this 對(duì)象;?

          3、沒(méi)有 return 語(yǔ)句。

          要?jiǎng)?chuàng)建 Person 的新實(shí)例,必須使用 new 操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下 4 個(gè)步驟:

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

          2、 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象);?

          3、執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性);

          4、返回新對(duì)象。

          在前面例子的最后,person1 和 person2 分別保存著 Person 的一個(gè)不同的實(shí)例。這兩個(gè)對(duì)象都 有一個(gè) constructor(構(gòu)造函數(shù))屬性,該屬性指向 Person,如下所示。

          alert(person1.constructor==Person); //true ?
          alert(person2.constructor==Person); //true

          對(duì)象的 constructor 屬性最初是用來(lái)標(biāo)識(shí)對(duì)象類型的。但是,提到檢測(cè)對(duì)象類型,還是 instanceof 操作符要更可靠一些。

          alert(person1 instanceof Object); //true ?
          alert(person1 instanceof Person); //true ?
          alert(person2 instanceof Object); //true ?
          alert(person2 instanceof Person); //true ?

          創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型;而這正是構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方。在這個(gè)例子中,person1 和 person2 之所以同時(shí)是 Object 的實(shí)例,是因?yàn)樗袑?duì)象均繼承自 Object。

          構(gòu)造函數(shù)的問(wèn)題

          構(gòu)造函數(shù)模式雖然好用,但也并非沒(méi)有缺點(diǎn)。使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè) 實(shí)例上重新創(chuàng)建一遍。在前面的例子中,person1 和 person2 都有一個(gè)名為 sayName()的方法,但那 兩個(gè)方法不是同一個(gè) Function 的實(shí)例。不要忘了——ECMAScript 中的函數(shù)是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。從邏輯角度講,此時(shí)的構(gòu)造函數(shù)也可以這樣定義。

          function Person(name, age, job){  this.name = name;  this.age = age;  this.job = job;  this.sayName = new Function("alert(this.name)"); // 與聲明函數(shù)在邏輯上是等價(jià)的}

          從這個(gè)角度上來(lái)看構(gòu)造函數(shù),更容易明白每個(gè) Person 實(shí)例都包含一個(gè)不同的 Function 實(shí)例(以 顯示 name 屬性)的本質(zhì)。說(shuō)明白些,以這種方式創(chuàng)建函數(shù),會(huì)導(dǎo)致不同的作用域鏈和標(biāo)識(shí)符解析,但創(chuàng)建 Function 新實(shí)例的機(jī)制仍然是相同的。因此,不同實(shí)例上的同名函數(shù)是不相等的

          alert(person1.sayName == person2.sayName); //false

          然而,創(chuàng)建兩個(gè)完成同樣任務(wù)的 Function 實(shí)例的確沒(méi)有必要;況且有 this 對(duì)象在,根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定對(duì)象上面。因此,大可像下面這樣,通過(guò)把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來(lái)解決這個(gè)問(wèn)題。

          function Person(name, age, job){  this.name = name;  this.age = age;  this.job = job;  this.sayName = sayName; } function sayName(){  alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");

          在這個(gè)例子中,我們把 sayName()函數(shù)的定義轉(zhuǎn)移到了構(gòu)造函數(shù)外部。而在構(gòu)造函數(shù)內(nèi)部,我們將sayName 屬性設(shè)置成等于全局的 sayName 函數(shù)。這樣一來(lái),由于 sayName 包含的是一個(gè)指向函數(shù) 的指針,因此 person1 和 person2 對(duì)象就共享了在全局作用域中定義的同一個(gè) sayName()函數(shù)。這 樣做確實(shí)解決了兩個(gè)函數(shù)做同一件事的問(wèn)題,可是新問(wèn)題又來(lái)了:在全局作用域中定義的函數(shù)實(shí)際上只 能被某個(gè)對(duì)象調(diào)用,這讓全局作用域有點(diǎn)名不副實(shí)。而更讓人無(wú)法接受的是:如果對(duì)象需要定義很多方 法,那么就要定義很多個(gè)全局函數(shù),于是我們這個(gè)自定義的引用類型就絲毫沒(méi)有封裝性可言了。好在, 這些問(wèn)題可以通過(guò)使用原型模式來(lái)解決。

          原型模式

          SPRING

          無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè) prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè) constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向 prototype 屬性所在函數(shù)的指針。

          322152a453e361f5fa3d51afb1b31dc1.webp


          展示了 Person 構(gòu)造函數(shù)、Person 的原型屬性以及 Person 現(xiàn)有的兩個(gè)實(shí)例之間的關(guān)系。在此,Person.prototype 指向了原型對(duì)象,而 Person.prototype.constructor 又指回了 Person。原型對(duì)象中除了包含 constructor 屬性之外,還包括后來(lái)添加的其他屬性。Person 的每個(gè)實(shí)例——person1 和 person2 都包含一個(gè)內(nèi)部屬性,該屬性僅僅指向了 Person.prototype;換句話說(shuō),它們與構(gòu)造函數(shù)沒(méi)有直接的關(guān)系。

          雖然可以通過(guò)對(duì)象實(shí)例訪問(wèn)保存在原型中的值,但卻不能通過(guò)對(duì)象實(shí)例重寫(xiě)原型中的值。如果我們?cè)趯?shí)例中添加了一個(gè)屬性,而該屬性與實(shí)例原型中的一個(gè)屬性同名,那我們就在實(shí)例中創(chuàng)建該屬性,該屬性將會(huì)屏蔽原型中的那個(gè)屬性。來(lái)看下面的例子。

          function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){  alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg"——來(lái)自實(shí)例alert(person2.name); //"Nicholas"——來(lái)自原型

          當(dāng)為對(duì)象實(shí)例添加一個(gè)屬性時(shí),這個(gè)屬性就會(huì)屏蔽原型對(duì)象中保存的同名屬性;換句話說(shuō),添加這個(gè)屬性只會(huì)阻止我們?cè)L問(wèn)原型中的那個(gè)屬性,但不會(huì)修改那個(gè)屬性。即使將這個(gè)屬性設(shè)置為 null,也只會(huì)在實(shí)例中設(shè)置這個(gè)屬性,而不會(huì)恢復(fù)其指向原型的連接。不過(guò),使用 delete 操作符則可以完全刪除實(shí)例屬性,從而讓我們能夠重新訪問(wèn)原型中的屬性,如下所示。

          function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){  alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg"——來(lái)自實(shí)例alert(person2.name); //"Nicholas"——來(lái)自原型delete person1.name; alert(person1.name); //"Nicholas"——來(lái)自原型

          在這個(gè)修改后的例子中,我們使用 delete 操作符刪除了 person1.name,之前它保存的"Greg"值屏蔽了同名的原型屬性。把它刪除以后,就恢復(fù)了對(duì)原型中 name 屬性的連接。因此,接下來(lái)再調(diào)用person1.name 時(shí),返回的就是原型中 name 屬性的值了。

          使用 hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是存在于實(shí)例中,還是存在于原型中。這個(gè)方法(不要忘了它是從 Object 繼承來(lái)的)只在給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回 true。來(lái)看下面這個(gè)例子。

          function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg"——來(lái)自實(shí)例alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas"——來(lái)自原型alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas"——來(lái)自原型alert(person1.hasOwnProperty("name")); //false

          通過(guò)使用 hasOwnProperty()方法,什么時(shí)候訪問(wèn)的是實(shí)例屬性,什么時(shí)候訪問(wèn)的是原型屬性就一清二楚了。調(diào)用 person1.hasOwnProperty( "name")時(shí),只有當(dāng) person1 重寫(xiě) name 屬性后才會(huì)返回 true,因?yàn)橹挥羞@時(shí)候 name 才是一個(gè)實(shí)例屬性,而非原型屬性。

          圖展示了上面例子在不同情況下的實(shí)現(xiàn)與原型的關(guān)系(為了簡(jiǎn)單起見(jiàn),圖中省略了與 Person 構(gòu)造函數(shù)的關(guān)系)。

          2e86d913edc3725630f91511ca7e9164.webp


          總結(jié)一下

          1、通過(guò)new之后的實(shí)例無(wú)法數(shù)據(jù)共享

          2、通過(guò)prototype實(shí)現(xiàn)數(shù)據(jù)共享

          3、因?yàn)閖s的大多數(shù)數(shù)據(jù)類型都是對(duì)象(函數(shù)、數(shù)組、正則、時(shí)間。。。),所以如何優(yōu)雅的創(chuàng)建對(duì)象成為了關(guān)鍵問(wèn)題。

          4、工廠模式的缺陷是無(wú)法解決識(shí)別問(wèn)題

          5、

          ????5.1、構(gòu)造函數(shù)中有很多的方法那么就會(huì)開(kāi)辟很多的空間,浪費(fèi)內(nèi)存資源;

          ????5.2、如果在全局情況下聲明函數(shù),雖然解決了內(nèi)存資源浪費(fèi)的問(wèn)題,但是又會(huì)出現(xiàn)全局變量污染的問(wèn)題;?

          ????5.3、如果重新聲明一個(gè)對(duì)象專門存放這些方法,但是新的問(wèn)題時(shí),如果有很多個(gè)構(gòu)造函數(shù),就要聲明很多個(gè)這樣的對(duì)象。

          6、

          ????6.1、原型模式中通過(guò)實(shí)例添加屬性會(huì)屏蔽原型中的那個(gè)屬性,如果去prototype獲取,則為nuhll,如果想取消屏蔽,則使用delete刪除即可;

          ????6.2、判斷是實(shí)例還是原型對(duì)象上的屬性,通過(guò)hasOwnProperty去識(shí)別,如果是實(shí)例則為true。

          避免一次消化太多。這一篇先到這里,接下來(lái)會(huì)講到原型對(duì)象、繼承、原型鏈;

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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精品国久久久久久无码一区二区三区 | 二区三区导航 | 91美女禁 | 免费又黄又爽又色的视频 | 国产棈品久久久久久久久久九秃 |