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

          JavaScript 的原型鏈

          共 4005字,需瀏覽 9分鐘

           ·

          2021-07-27 20:16

          前言

          我們都知道,js在ES6以前是沒有類的概念的,如果想用面向?qū)ο蟮乃枷雭黹_發(fā)它就要去了解他的繼承實(shí)現(xiàn)方式。
          JavaScript是一門基于原型繼承的語言,這在語言設(shè)計(jì)之初就已經(jīng)被確定了的,即使在后來的ES6中加入了類的概念,加入了extends關(guān)鍵字,但仍舊無法改變他作為原型繼承的本質(zhì),ES6的繼承可以看作是ES5繼承方式的語法糖,將之前的寫好的繼承方式合法化,規(guī)范化。

          什么是原型

          我一般喜歡把原型比作模型,或者說模具更加貼切。比如說一個(gè)水杯,他是圓的還是方的就是由這個(gè)模具決定的,如果這個(gè)模具改變了樣子,那么生產(chǎn)出來的水杯也會(huì)變了樣子。
          這樣來描述,你可能會(huì)覺得,叫什么原型,叫構(gòu)造器不是更貼切。沒錯(cuò),在這個(gè)例子中,模具就是水杯的構(gòu)造器(constructor)。
          那么原型和說的這個(gè)有什么關(guān)系呢。假設(shè)我們的這個(gè)水杯是有額外功能的,加熱和制冷。那么問題就來了,構(gòu)造器只能決定水杯的樣子,是沒辦法給他提供加熱和制冷的功能的。這個(gè)時(shí)候就需要原型(prototype)了。
          一般情況,構(gòu)造器決定樣子,名稱等固定的屬性。原型決定的是功能,可以進(jìn)行操作的方法。構(gòu)造器(constructor)和原型(prototype)共同決定了一個(gè)物體的存在形式。
          構(gòu)造器(constructor)和原型(prototype)的關(guān)系怎么來描述呢
          原型對(duì)象的 constructor 是 構(gòu)造器。
          構(gòu)造器的 prototype 是原型對(duì)象。


          在js中有一句話叫萬物皆對(duì)象,每個(gè)對(duì)象都有原型。我們創(chuàng)建函數(shù),如果采用new的方式調(diào)用,當(dāng)然這種調(diào)用方式有個(gè)名字叫實(shí)例化。

          // 創(chuàng)建一個(gè)函數(shù)function B(name) {    this.name = name;};// 實(shí)例化var bb = new B('實(shí)例化的b');console.log(bb.name); // 實(shí)例化的b;

          如上面的代碼,bb是通過B實(shí)例化之后得到的對(duì)象。在這里B就是一個(gè)構(gòu)造器,他所擁有的名字(this.name)屬性會(huì)帶給bb;這也符合之前杯子的例子,杯子的屬性會(huì)從構(gòu)造器中獲得。

          假如我們想要做出來的bb具有一定的功能,那么就需要在原型上下功夫了。根據(jù)上面構(gòu)造器和原型的關(guān)系。我們可以這樣做。

          // 創(chuàng)建一個(gè)函數(shù)
          function B(name) { this.name = name;};
          // 在原型上添加一個(gè)方法
          B.prototype.tan = function() { alert('彈出框');}
          // 實(shí)例化
          var bb = new B('實(shí)例化的b');
          console.log(bb.name); // 實(shí)例化的b;
          bb.tan(); // alert('彈出框');

          在上面的代碼中,我們?cè)贐的原型上添加了一個(gè)tan的方法,在實(shí)例化出來的bb也具備了這個(gè)方法。這里我們就簡(jiǎn)單實(shí)現(xiàn)了一個(gè)類。

          用下面一張圖,說明一下。實(shí)例對(duì)象(bb), 原型(prototype), 構(gòu)造函數(shù)(constructor)的關(guān)系。


          B是我們構(gòu)造的一個(gè)類,這里稱為構(gòu)造函數(shù)。他用prototype指向了自己的原型。而他的原型也通過constructor指向了它。

          B.prototype.constructor === B;  // true;

          bb和B沒有直接的關(guān)聯(lián),雖然B是bb的構(gòu)造函數(shù),這里用虛線表示。bb有一個(gè)__ proto__屬性,指向了B的prototype

          bb.__ proto__ === B.prototype; // true;bb.__ proto__.constructor = B; // true;

          總之

          1,每創(chuàng)建一個(gè)函數(shù)B,就會(huì)為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象;

          2,原型對(duì)象會(huì)默認(rèn)去取得constructor屬性,指向構(gòu)造函數(shù)。

          3,當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例bb后,該實(shí)例的內(nèi)部將包含一個(gè)指針__ proto__,指向構(gòu)造函數(shù)的原型對(duì)象。

          默認(rèn)原型

          我們知道,所有引用對(duì)象都默認(rèn)繼承了Object,所有函數(shù)的默認(rèn)原型都是Object的實(shí)例。
          之前說過構(gòu)造函數(shù)和原型之間具備對(duì)應(yīng)關(guān)系,如下:


          既然函數(shù)的默認(rèn)原型都是Object的實(shí)例,B的原型對(duì)象也應(yīng)該是Object的實(shí)例子,也就是說。B的原型的__ proto__應(yīng)該指向Objct的原型。


          Object的原型對(duì)象的原型是最底部了,所以不存在原型,指向NULL;

          console.log(Object.prototype.__ proto__); // null;


          Function對(duì)象

          我們知道,函數(shù)也是對(duì)象,任何函數(shù)都可以看作是由構(gòu)造函數(shù)Function實(shí)例化的對(duì)象,所以Function與其原型對(duì)象之間也存在如下關(guān)系


          如果將Foo函數(shù)看作實(shí)例對(duì)象的話,其構(gòu)造函數(shù)就是Function(),原型對(duì)象自然就是Function的原型對(duì)象;同樣Object函數(shù)看作實(shí)例對(duì)象的話,其構(gòu)造函數(shù)就是Function(),原型對(duì)象自然也是Function的原型對(duì)象。


          如果Function的原型對(duì)象看作實(shí)例對(duì)象的話,如前所述所有對(duì)象都可看作是Object的實(shí)例化對(duì)象,所以Function的原型對(duì)象的__ proto __指向Object的原型對(duì)象。


          到這里prototype,__ proto __, constructor三者之間的關(guān)系我們就說完了。

          實(shí)現(xiàn)繼承

          function Animal() {    this.type = '動(dòng)物';}Animate.prototype.eat = function() {  console.log('吃食物');}

          上面定義了一個(gè)動(dòng)物類,作為父類。

          function Cat(name) {    this.name = name || ‘小貓’;}

          定義了一個(gè)貓作為子類,這里我們要繼承動(dòng)物類的eat方法和type屬性

          function Cat(name){  Animal.call(this);  this.name = name || '小貓';}

          在實(shí)例化Cat時(shí)通過call執(zhí)行了Animal類, 這樣Animal中的this就被修改為當(dāng)前Cat的this。所有的屬性也會(huì)加在Cat上。

          (function(){  // 創(chuàng)建一個(gè)沒有實(shí)例方法的類  var Super = function(){};  Super.prototype = Animal.prototype;  //將實(shí)例作為子類的原型  Cat.prototype = new Super();})();

          通過寄生方式,砍掉父類的實(shí)例屬性,這樣,在調(diào)用兩次父類的構(gòu)造的時(shí)候,就不會(huì)初始化實(shí)例方法/屬性。而父類的方法仍舊可以賦值給子類。

          Cat.prototype = new Super(); 可以實(shí)現(xiàn)方法的繼承,是因?yàn)椋鶕?jù)前面的知識(shí)我們知道 new Cat()的__ proto __是指向 Cat的原型的。

          (new Cat()).__ proto __ === Cat.prototype; // true

          new Cat()所有的方法都是從原型上取到的。

          我們通過 Cat.prototype = new Super(); 公式變成了。

          (new Cat()).__ proto __ = Cat.prototype = new Super();

          所以現(xiàn)在(new Cat()).__ proto __ 指向了 Super的prototype。也就是new Cat的方法是繼承自Super.prototype。

          Super.prototype又在前一句等于Animal.prototype。所以實(shí)現(xiàn)了Cat繼承Animal。

          這里我們就實(shí)現(xiàn)了js屬性和方法的繼承。不過還在最后一個(gè)小問題。

          我們知道 prototype 和 constructor 是相互指向的。

          Cat.prototype.constructor 應(yīng)該等于 Cat;

          但是隨著我們的修改了Cat.prototype = Super.prototype;

          現(xiàn)在Cat.prototype.constructor是等于Super的。

          所以我們還應(yīng)該糾正這個(gè)問題,一句話搞定。

          Cat.prototype.constructor = Cat; // 需要修復(fù)下構(gòu)造函數(shù)

          以上就是js的原型繼承,完整代碼如下。

          // 創(chuàng)建一個(gè)父類function Animal() {    this.type = '動(dòng)物';}// 給父類添加一個(gè)方法Animate.prototype.eat = function() {  console.log('吃食物');}
          // 創(chuàng)建一個(gè)子類function Cat(name){ // 繼承Animal的屬性 Animal.call(this); this.name = name || '小貓';}
          // 繼承 Animal 的方法
          (function(){ // 創(chuàng)建一個(gè)沒有實(shí)例方法的類 var Super = function(){}; Super.prototype = Animal.prototype; //將實(shí)例作為子類的原型 Cat.prototype = new Super();})();// 修正構(gòu)造函數(shù)Cat.prototype.constructor = Cat;

          好啦,js的繼承原理和prototype,proto, constructor之間的關(guān)系我們就說完了,ES6底層的實(shí)現(xiàn)方式原理基本相同。


          學(xué)習(xí)更多技能

          請(qǐng)點(diǎn)擊下方公眾號(hào)

          瀏覽 49
          點(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>
                  男人的天堂天天在线视频 | 人人艹人人摸 | 午夜无码人妻AV大片 | 久久精品成人电影 | 青娱乐 欧美在线视频 |