整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Javascript實(shí)現(xiàn)繼承的幾種方式

          Javascript實(shí)現(xiàn)繼承的幾種方式

          天來(lái)講解javascript中實(shí)現(xiàn)繼承的幾種方式,有興趣的小伙伴們可以來(lái)看看,若有錯(cuò)誤之處,歡迎指出!

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

          每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。在前面的例子中,person1和person2都有一個(gè)名為sayName()的方法,但那兩個(gè)方法不是同一個(gè)Function的實(shí)例。

          二、原型模式

          無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性。在默認(rèn)情況下,所有prototype屬性都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。Person.prototype.constructor指向Person。創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型屬性默認(rèn)只會(huì)取得constructor屬性;至于其他方法,則都是從object繼承而來(lái)的。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型屬性。在很多實(shí)現(xiàn)中,這個(gè)內(nèi)部屬性的名字是_proto_。不過(guò),要明確的真正重要一點(diǎn),就是這個(gè)鏈接存在于實(shí)例與構(gòu)造函數(shù)的原型屬性之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。

          三、原型鏈

          原型鏈:ECMAScript中描述了原型鏈的概念,并將原型鏈作為實(shí)現(xiàn)繼承的主要方法。其基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。簡(jiǎn)單回顧一下構(gòu)造函數(shù)、原型、實(shí)例的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針。那么,假如我們讓原型對(duì)象等于另一個(gè)類型的實(shí)例,結(jié)果會(huì)怎么樣呢?顯然,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。假如另一個(gè)原型又是另一個(gè)類型的實(shí)例,那么上述關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。這就是所謂原型鏈的基本概念。

          四、組合繼承

          組合繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。其背后的思路是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。這樣,既通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能夠保證每個(gè)實(shí)例都有它自己的屬性。

          于有基于類的語(yǔ)言經(jīng)驗(yàn) (如 Java 或 C++) 的開發(fā)人員來(lái)說(shuō),JavaScript 有點(diǎn)令人困惑,因?yàn)樗莿?dòng)態(tài)的,并且本身不提供一個(gè)class實(shí)現(xiàn)。(在 ES2015/ES6 中引入了class關(guān)鍵字,但只是語(yǔ)法糖,JavaScript 仍然是基于原型的)。

          JavaScript 只有一種繼承結(jié)構(gòu):對(duì)象。每個(gè)實(shí)例對(duì)象(object )都有一個(gè)私有屬性(稱之為[[prototype]])指向它的原型對(duì)象(prototype)。該原型對(duì)象也有一個(gè)自己的原型對(duì)象 ,層層向上直到一個(gè)對(duì)象的原型對(duì)象為 null。根據(jù)定義,null 沒(méi)有原型,并作為這個(gè)原型鏈中的最后一個(gè)環(huán)節(jié)。

          幾乎所有 JavaScript 中的對(duì)象都是位于原型鏈頂端的Object的實(shí)例。

          遵循ECMAScript標(biāo)準(zhǔn),someObject.[[Prototype]] 符號(hào)是用于指向 someObject的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過(guò)Object.getPrototypeOf()和Object.setPrototypeOf()訪問(wèn)器來(lái)訪問(wèn)。這個(gè)等同于 JavaScript 的非標(biāo)準(zhǔn)但許多瀏覽器實(shí)現(xiàn)的屬性 proto。

          但它不應(yīng)該與構(gòu)造函數(shù) func 的 prototype 屬性相混淆。被構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象的 [[prototype]] 指向 func 的 prototype 屬性。Object.prototype 屬性表示Object的原型對(duì)象。

          // 讓我們從一個(gè)自身?yè)碛袑傩詀和b的函數(shù)里創(chuàng)建一個(gè)對(duì)象o:
          let f=function () {
           this.a=1;
           this.b=2;
          }
          /* 你要這么寫也沒(méi)區(qū)別
          function f(){
           this.a=1;
           this.b=2;
          }
          */
          let o=new f(); // {a: 1, b: 2}
          // 在f函數(shù)的原型上定義屬性
          f.prototype.b=3;
          f.prototype.c=4;
          //不要在f函數(shù)的原型上直接定義 f.prototype={b:3,c:4};這樣會(huì)直接打破原型鏈
          // o.[[Prototype]] 有屬性 b 和 c (其實(shí)就是o.__proto__或者o.constructor.prototype)
          // o.[[Prototype]].[[Prototype]] 是 Object.prototye.
          // 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
          // 這就是原型鏈的末尾,即 null,
          // 根據(jù)定義,null 沒(méi)有[[Prototype]].
          // 綜上,整個(gè)原型鏈如下: 
          // {a:1, b:2} ---> {b:3, c:4} ---> Object.prototye---> null
          console.log(o.a); // 1
          // a是o的自身屬性嗎?是的,該屬性的值為1
          console.log(o.b); // 2
          // b是o的自身屬性嗎?是的,該屬性的值為2
          // 原型上也有一個(gè)'b'屬性,但是它不會(huì)被訪問(wèn)到.這種情況稱為"屬性遮蔽 (property shadowing)"
          console.log(o.c); // 4
          // c是o的自身屬性嗎?不是,那看看原型上有沒(méi)有
          // c是o.[[Prototype]]的屬性嗎?是的,該屬性的值為4
          console.log(o.d); // undefined
          // d是o的自身屬性嗎?不是,那看看原型上有沒(méi)有
          // d是o.[[Prototype]]的屬性嗎?不是,那看看它的原型上有沒(méi)有
          // o.[[Prototype]].[[Prototype]] 為 null,停止搜索
          // 沒(méi)有d屬性,返回undefined
          

          當(dāng)繼承的函數(shù)被調(diào)用時(shí),this 指向的是當(dāng)前繼承的對(duì)象,而不是繼承的函數(shù)所在的原型對(duì)象

          var o={
           a: 2,
           m: function(){
          return this.a + 1;
           }
          };
          console.log(o.m()); // 3
          // 當(dāng)調(diào)用 o.m 時(shí),'this'指向了o.
          var p=Object.create(o);
          // p是一個(gè)繼承自 o 的對(duì)象
          p.a=4; // 創(chuàng)建 p 的自身屬性 a
          console.log(p.m()); // 5
          // 調(diào)用 p.m 時(shí), 'this'指向 p. 
          // 又因?yàn)?p 繼承 o 的 m 函數(shù)
          // 此時(shí)的'this.a' 即 p.a,即 p 的自身屬性 'a' 
          

          使用不同的方法來(lái)創(chuàng)建對(duì)象和生成原型鏈

          var o={a: 1};
          // o 這個(gè)對(duì)象繼承了Object.prototype上面的所有屬性
          // o 自身沒(méi)有名為 hasOwnProperty 的屬性
          // hasOwnProperty 是 Object.prototype 的屬性
          // 因此 o 繼承了 Object.prototype 的 hasOwnProperty
          // Object.prototype 的原型為 null
          // 原型鏈如下:
          // o ---> Object.prototype ---> null
          var a=["yo", "whadup", "?"];
          // 數(shù)組都繼承于 Array.prototype 
          // (Array.prototype 中包含 indexOf, forEach等方法)
          // 原型鏈如下:
          // a ---> Array.prototype ---> Object.prototype ---> null
          function f(){
           return 2;
          }
          // 函數(shù)都繼承于Function.prototype
          // (Function.prototype 中包含 call, bind等方法)
          // 原型鏈如下:
          // f ---> Function.prototype ---> Object.prototype ---> null
          

          在 JavaScript 中,構(gòu)造器其實(shí)就是一個(gè)普通的函數(shù)。當(dāng)使用 new 操作符 來(lái)作用這個(gè)函數(shù)時(shí),它就可以被稱為構(gòu)造方法(構(gòu)造函數(shù))。

          function Graph() {
           this.vertices=[];
           this.edges=[];
          }
          Graph.prototype={
           addVertex: function(v){
           this.vertices.push(v);
           }
          };
          var g=new Graph();
          // g是生成的對(duì)象,他的自身屬性有'vertices'和'edges'.
          // 在g被實(shí)例化時(shí),g.[[Prototype]]指向了Graph.prototype.
          

          ECMAScript 5 中引入了一個(gè)新方法:Object.create()??梢哉{(diào)用這個(gè)方法來(lái)創(chuàng)建一個(gè)新對(duì)象。新對(duì)象的原型就是調(diào)用 create 方法時(shí)傳入的第一個(gè)參數(shù):

          var a={a: 1}; 
          // a ---> Object.prototype ---> null
          var b=Object.create(a);
          // b ---> a ---> Object.prototype ---> null
          console.log(b.a); // 1 (繼承而來(lái))
          var c=Object.create(b);
          // c ---> b ---> a ---> Object.prototype ---> null
          var d=Object.create(null);
          // d ---> null
          console.log(d.hasOwnProperty); // undefined, 因?yàn)閐沒(méi)有繼承Object.prototype
          

          ECMAScript6 引入了一套新的關(guān)鍵字用來(lái)實(shí)現(xiàn) class。使用基于類語(yǔ)言的開發(fā)人員會(huì)對(duì)這些結(jié)構(gòu)感到熟悉,但它們是不同的。JavaScript 仍然基于原型。這些新的關(guān)鍵字包括 class, constructor,static,extends 和 super。

          上面的章節(jié)中我們看到了JavaScript的對(duì)象模型是基于原型實(shí)現(xiàn)的,特點(diǎn)是簡(jiǎn)單,缺點(diǎn)是理解起來(lái)比傳統(tǒng)的類-實(shí)例模型要困難,最大的缺點(diǎn)是繼承的實(shí)現(xiàn)需要編寫大量代碼,并且需要正確實(shí)現(xiàn)原型鏈。

          有沒(méi)有更簡(jiǎn)單的寫法?有!

          新的關(guān)鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡(jiǎn)單。

          我們先回顧用函數(shù)實(shí)現(xiàn)Student的方法:

          function Student(name) {
           this.name=name;
          }
          Student.prototype.hello=function () {
           alert('Hello, ' + this.name + '!');
          }
          

          如果用新的class關(guān)鍵字來(lái)編寫Student,可以這樣寫:

          class Student {
           constructor(name) {
           this.name=name;
           }
           hello() {
           alert('Hello, ' + this.name + '!');
           }
          }
          

          比較一下就可以發(fā)現(xiàn),class的定義包含了構(gòu)造函數(shù)constructor和定義在原型對(duì)象上的函數(shù)hello()(注意沒(méi)有function關(guān)鍵字),這樣就避免了Student.prototype.hello=function () {...}這樣分散的代碼。

          最后,創(chuàng)建一個(gè)Student對(duì)象代碼和前面章節(jié)完全一樣:

          var xiaoming=new Student('小明');
          xiaoming.hello();
          

          class繼承

          用class定義對(duì)象的另一個(gè)巨大的好處是繼承更方便了。想一想我們從Student派生一個(gè)PrimaryStudent需要編寫的代碼量。現(xiàn)在,原型繼承的中間對(duì)象,原型對(duì)象的構(gòu)造函數(shù)等等都不需要考慮了,直接通過(guò)extends來(lái)實(shí)現(xiàn):

          class PrimaryStudent extends Student {
           constructor(name, grade) {
           super(name); // 記得用super調(diào)用父類的構(gòu)造方法!
           this.grade=grade;
           }
           myGrade() {
           alert('I am at grade ' + this.grade);
           }
          }
          

          注意PrimaryStudent的定義也是class關(guān)鍵字實(shí)現(xiàn)的,而extends則表示原型鏈對(duì)象來(lái)自Student。子類的構(gòu)造函數(shù)可能會(huì)與父類不太相同,例如,PrimaryStudent需要name和grade兩個(gè)參數(shù),并且需要通過(guò)super(name)來(lái)調(diào)用父類的構(gòu)造函數(shù),否則父類的name屬性無(wú)法正常初始化。

          PrimaryStudent已經(jīng)自動(dòng)獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。

          ES6引入的class和原有的JavaScript原型繼承有什么區(qū)別呢?實(shí)際上它們沒(méi)有任何區(qū)別,class的作用就是讓JavaScript引擎去實(shí)現(xiàn)原來(lái)需要我們自己編寫的原型鏈代碼。簡(jiǎn)而言之,用class的好處就是極大地簡(jiǎn)化了原型鏈代碼。

          你一定會(huì)問(wèn),class這么好用,能不能現(xiàn)在就用上?

          現(xiàn)在用還早了點(diǎn),因?yàn)椴皇撬械闹髁鳛g覽器都支持ES6的class。如果一定要現(xiàn)在就用上,就需要一個(gè)工具把class代碼轉(zhuǎn)換為傳統(tǒng)的prototype代碼,可以試試Babel這個(gè)工具。

          需要瀏覽器支持ES6的class,如果遇到SyntaxError,則說(shuō)明瀏覽器不支持class語(yǔ)法,請(qǐng)換一個(gè)最新的瀏覽器試試。


          主站蜘蛛池模板: 亚洲成av人片一区二区三区| 精品无码成人片一区二区| 成人H动漫精品一区二区| 久久精品视频一区二区三区| 国产视频一区二区在线播放| 亚洲色精品vr一区二区三区| 国内偷窥一区二区三区视频| 毛片一区二区三区| 国产在线一区二区三区av| 一区二区国产在线播放| 日韩精品人妻一区二区中文八零| 国产精品女同一区二区久久| 亚洲日韩精品无码一区二区三区| 人妻无码久久一区二区三区免费| 亚洲国产一区在线观看| 国产AV天堂无码一区二区三区| 狠狠爱无码一区二区三区| 日韩精品中文字幕无码一区| 中文字幕一区二区人妻性色| 人妻夜夜爽天天爽爽一区| 日韩亚洲AV无码一区二区不卡| 日韩人妻无码一区二区三区久久| 果冻传媒一区二区天美传媒| 亚洲宅男精品一区在线观看| 久久精品无码一区二区日韩AV | 国产SUV精品一区二区四| 亚洲国产综合无码一区二区二三区 | 久久中文字幕无码一区二区| 国产精品视频一区二区三区不卡| 亚洲不卡av不卡一区二区| 国产一区在线视频观看| 无码视频一区二区三区| 一区二区免费视频| 久久精品一区二区三区日韩| 97精品国产福利一区二区三区| 国产A∨国片精品一区二区| 精品国产一区二区麻豆| 蜜桃AV抽搐高潮一区二区| 在线电影一区二区三区| 国产suv精品一区二区6| 在线视频国产一区|