<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>

          ES5 繼承

          共 6890字,需瀏覽 14分鐘

           ·

          2020-10-16 00:05

          作者:assassin cike

          來源:SegmentFault 思否社區(qū)




          首先要明白兩點:


          一、非方法屬性每個子類實例需要獨立
          二、方法屬性每個子類實例需要共享


          為什么?


          如果非方法屬性為引用類型,且非方法屬性共享,在一個實例中改變,其他實例中就會做出改變,這樣每個實例就會相互影響,而方法屬性一般是不需要進(jìn)行改變的,只是對方法調(diào)用。


          • 方法跟屬性分別可以定義在構(gòu)造函數(shù)內(nèi)部跟prototype上。

          • 繼承的目的是子類繼承父類的方法跟屬性。

          • 代碼主要來自于紅寶書4




          基于原型鏈的繼承


          每個函數(shù)都有個prototype屬性,每個對象都有__proto__屬性(在chrome中表現(xiàn)如此,prototype也是如此) 如圖,屬性的查找會從當(dāng)前層級依次向原型鏈上查找,直到查找到原型鏈的頂端null,具體可參考:

          js?proto(https://www.jianshu.com/p/cd26f07df9ba)




          既然屬性的查找是按照原型鏈向上查找的,且繼承就是繼承父類的屬性跟方法,那么就可以利用這個特性,進(jìn)行繼承。


          function?SuperType()?{
          ??this.property?=?true;
          }

          SuperType.prototype.getSuperValue?=?function?()?{
          ??return?this.property;
          };
          function?SubType()?{
          ??this.subproperty?=?false;
          }
          //?繼承SuperType
          SubType.prototype?=?new?SuperType();
          SubType.prototype.getSubValue?=?function?()?{
          ??return?this.subproperty;
          };
          let?instance?=?new?SubType();
          console.log(instance.getSuperValue());?//?true?可以正確調(diào)用父類的方法,拿到父類的屬性


          原型雖然實現(xiàn)了繼承,但是還是有缺點的


          劣勢:


          1. 子類或者父類的屬性為引用類型時,改變一個實例的引用類型屬性,其他實例的該引用類型屬性也會發(fā)生改變,這樣其實例就會相互污染了。


          function?SuperType()?{
          ??this.colors?=?["red",?"blue",?"green"];
          }

          function?SubType()?{}?//?繼承SuperType
          SubType.prototype?=?new?SuperType();

          let?instance1?=?new?SubType();
          instance1.colors.push("black");
          console.log(instance1.colors);
          //?"red,blue,green,black";

          let?instance2?=?new?SubType();
          console.log(instance2.colors);
          //?"red,blue,green,black";


          為什么非方法屬性不寫在prototype上?


          因為prototype上的屬性的共享的,在一個實例上改了該屬性,其他實例的該屬性也會被改掉。


          為什么方法不寫在構(gòu)造函數(shù)內(nèi)部?


          • 方法寫在子類內(nèi)部:每次實例化構(gòu)造函數(shù),方法都是新的;方法只是用來調(diào)用,不需要修改,所以實例共享就行了。

          • 方法寫在父類內(nèi)部:不同的子類繼承父類都需要實例化父類;方法只是用來調(diào)用,不需要做修改,所以實例共享就行了,包括子類實例。如果子類需要修改父類方法,直接在子類中定義相同方法名,進(jìn)行覆蓋就行了。


          2. 子類在實例化時不能給父類的構(gòu)造函數(shù)傳參,因為父類的實例化是在前面,而不是構(gòu)造函數(shù)調(diào)用的時候。




          盜用構(gòu)造函數(shù)


          為了解決父類中屬性為引用類型導(dǎo)致子類實例化后,引用屬性共享的問題,跟父類構(gòu)造函數(shù)無法傳參的問題。引入了“盜用構(gòu)造函數(shù)“方式實現(xiàn)繼承。思路是在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)。


          • 不同實例的引用屬性不會相互影響

          function?SuperType()?{
          ??this.colors?=?["red",?"blue",?"green"];
          }
          function?SubType()?{
          ??//?繼承SuperType
          ??SuperType.call(this);
          }

          let?instance1?=?new?SubType();
          instance1.colors.push("black");
          console.log(instance1.colors);
          //?"red,blue,green,black";

          let?instance2?=?new?SubType();
          console.log(instance2.colors);
          //?"red,blue,green";

          instance1 instance2兩個實例就不會相互影響。

          • 可以為父類構(gòu)造函數(shù)傳參

          function?SuperType(name)?{
          ??this.name?=?name;
          }
          function?SubType(name)?{
          ??//?繼承SuperType并傳參
          ??SuperType.call(this,?name);
          ??//?實例屬性
          ??this.age?=?29;
          }
          let?instance?=?new?SubType("geek");
          console.log(instance.name);?//?"geek";
          console.log(instance.age);?//?29

          動態(tài)傳遞參數(shù)到父類構(gòu)造函數(shù)

          劣勢:

          • 定義在父類prototype上的方法,子類無法繼承

          function?SuperType(name)?{
          ??this.name?=?name;
          }
          SuperType.prototype.say?=?function?()?{
          ??console.info("hello");
          };

          function?SubType(name)?{
          ??//?繼承SuperType并傳參
          ??SuperType.call(this,?name);
          ??//?實例屬性
          ??this.age?=?29;
          }

          let?instance?=?new?SubType("geek");
          console.log(instance.name);?//?"geek";
          console.log(instance.age);?//?29
          instance.say()?//?獲取不到該函數(shù)

          通過 new 實例化后,實例才能拿到prototype上的方法,a.__proto__===Animal.prototype,所以instance.say()不存在

          • 定義在父類構(gòu)造函數(shù)中方法無法共享

          每次實例化子類,都會調(diào)用父類構(gòu)造函數(shù),其內(nèi)部定義的方法都是新的,占用了不必要的內(nèi)存,沒有實現(xiàn)方法的共享。



          組合繼承


          組合繼承兼顧原型鏈繼承跟盜用構(gòu)造函數(shù)的優(yōu)點,這樣既可以把方法定義在原型上以實現(xiàn)重用,又可以看讓每個實力都有自己的屬性。

          function?SuperType(name)?{
          ??this.name?=?name;
          ??this.colors?=?["red",?"blue",?"green"];
          }
          SuperType.prototype.sayName?=?function?()?{
          ??console.log(this.name);
          };
          function?SubType(name,?age)?{
          ??//?繼承屬性,綁定上下文為SubType的實例
          ??SuperType.call(this,?name);
          ??this.age?=?age;
          }
          //?繼承方法
          SubType.prototype?=?new?SuperType();
          SubType.prototype.sayAge?=?function?()?{
          ??console.log(this.age);
          };
          let?instance1?=?new?SubType("Nicholas",?29);
          instance1.colors.push("black");
          console.log(instance1.colors);
          //?"red,blue,green,black"
          instance1.sayName();?//?"Nicholas";
          instance1.sayAge();?//?29

          let?instance2?=?new?SubType("Greg",?27);
          console.log(instance2.colors);
          //?"red,blue,green";
          instance2.sayName();?//?"Greg";
          instance2.sayAge();?//?27

          • 可以傳遞參數(shù)到父類構(gòu)造函數(shù)

          • 兩個實例中的引用類型不會相互影響

          • 實例可以調(diào)用父類的方法,且實現(xiàn)方法的共享

          • 組合繼承也保留了 instanceof 操作符和isPrototypeOf() 方法識別合成對象的能力。






          劣勢:

          SuperType會被調(diào)用兩次,SubType實例跟原型鏈上都有name跟colors屬性。



          原型式繼承


          不定義構(gòu)造函數(shù)通過原型實現(xiàn)對象之前的繼承。

          function?object(o)?{
          ??function?F()?{}
          ??F.prototype?=?o;
          ??return?new?F();
          }

          返回新對象,讓其原型指向O

          let?person?=?{
          ??name:?"Nicholas",
          ??friends:?["Shelby",?"Court",?"Van"],
          };
          let?anotherPerson?=?object(person);
          anotherPerson.name?=?"Greg";
          anotherPerson.friends.push("Rob");

          let?yetAnotherPerson?=?object(person);
          yetAnotherPerson.name?=?"Linda";
          yetAnotherPerson.friends.push("Barbie");

          console.log(person.friends);
          //?"Shelby,Court,Van,Rob,Barbie";

          父對象中的引用屬性會在子對象中共享,導(dǎo)致相互污染。

          ES5引入了Object.create()規(guī)范了原型式繼承。

          let?person?=?{
          ??name:?"Nicholas",
          ??friends:?["Shelby",?"Court",?"Van"],
          };
          let?anotherPerson?=?Object.create(person);
          anotherPerson.name?=?"Greg";
          anotherPerson.friends.push("Rob");

          let?yetAnotherPerson?=?Object.create(person);
          yetAnotherPerson.name?=?"Linda";
          yetAnotherPerson.friends.push("Barbie");

          console.log(person.friends);
          //?"Shelby,Court,Van,Rob,Barbi

          使用Object.create后anotherPerson.__proto__===person成立,所以anotherPerson可以拿到person的屬性,但是同樣存在父對象屬性共享的問題,改了父對象的屬性,其他的子對象都跟著改變。

          劣勢:

          父對象的引用類型會在實例中共享,這樣就會相互污染。



          寄生式繼承


          寄生式繼承跟原型式繼承很類似,用工廠函數(shù)在對返回的新對象包一層,給新對象賦值一些屬性

          工廠函數(shù)的定義:

          function?createAnother(original)?{
          ??let?clone?=?object(original);
          ??//?通過調(diào)用函數(shù)創(chuàng)建一個新對象;

          ??clone.sayHi?=?function?()?{
          ????//?以某種方式增強這個對象;
          ????console.log("hi");
          ??};
          ??return?clone;?//?返回這個對象
          }

          使用:

          let?person?=?{
          ??name:?"Nicholas",
          ??friends:?["Shelby",?"Court",?"Van"],
          };
          let?anotherPerson?=?createAnother(person);
          anotherPerson.sayHi();?//?"hi"

          定義在新對象上的sayHi方法,每次調(diào)用新對象都是新的,無法實現(xiàn)共享。

          劣勢:

          • 父對象的引用類型會在實例中共享,這樣就會相互污染。

          • 方法無法實現(xiàn)共享





          寄生式組合繼承


          上面提到,組合繼承的缺點就是父類構(gòu)造函數(shù)會被調(diào)用兩次,一次是在子類的構(gòu)造函數(shù)中,另一次在創(chuàng)建子類原型時調(diào)用。繼承就是要繼承父類的屬性跟方法,組合繼承實現(xiàn)了這個目標(biāo),但是怎么避免重復(fù)調(diào)用父類構(gòu)造函數(shù)。

          先看下組合繼承:

          function?SuperType(name)?{
          ??this.name?=?name;
          ??this.colors?=?["red",?"blue",?"green"];
          }

          SuperType.prototype.sayName?=?function?()?{
          ??console.log(this.name);
          };
          function?SubType(name,?age)?{
          ??SuperType.call(this,?name);?//?第二次調(diào)用,將父類的屬性綁定到子類的實例中
          ??SuperType();
          ??this.age?=?age;
          }

          SubType.prototype?=?new?SuperType();
          //?第一次調(diào)用SuperType();

          SubType.prototype.constructor?=?SubType;
          SubType.prototype.sayAge?=?function?()?{
          ??console.log(this.age);
          };




          由圖可見s的原型鏈上依然有name跟colors屬性。這也是不需要的。怎么解決這兩個問題?

          父類的屬性是需要的,父類的原型上的方法是需要的,重復(fù)的父類屬性不需要,由上圖可見重復(fù)的父類屬性是由于實例化父類給子類原型造成的,我們不去實例化父類,而是將父類的原型傳遞給子類的原型就行了,結(jié)合原型式繼承特點可以做到

          function?SuperType(name)?{
          ??this.name?=?name;
          ??this.colors?=?["red",?"blue",?"green"];
          }

          SuperType.prototype.sayName?=?function?()?{
          ??console.log(this.name);
          };

          function?SubType(name,?age)?{
          ??SuperType.call(this,?name);?//?將父類的屬性綁定到SubType實例中
          ??this.age?=?age;
          }

          SubType.prototype?=?Object.create(SuperType.prototype);
          //?將子類的prototype關(guān)聯(lián)到父類的prototype上

          SubType.prototype.sayAge?=?function?()?{
          ??console.log(this.age);
          };

          使用Object.create解決了父類構(gòu)造函數(shù)調(diào)用兩次,父類屬性重復(fù)的問題,但是子類constructor并沒有出現(xiàn)在原型鏈中



          下面做出改造:

          SubType.prototype?=?Object.create(SuperType.prototype,?{
          ??constructor:?{
          ????value:?SubType,?//?修正?constructor?指向
          ????writable:?true,
          ????configurable:?true,
          ??},
          });


          SuperType的constructor出現(xiàn)了,其實constructor并沒什么用,只是個約定罷了,參考賀老的解釋JavaScript 中對象的 constructor 屬性的作用是什么?(https://www.zhihu.com/question/19951896/answer/13457869)

          instanceof操作符和 isPrototypeOf() 方法正常有效。寄生式組合繼承可以
          算是引用類型繼承的最佳模式



          點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。

          -?END -

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  色站综合 | 国产足交免费看 | 黄色片视频日本 | 人人超人人超碰国产 | 亚洲无码蜜桃视频 |