亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

15分鐘深入了解JS繼承分類、原理與用法

發布時間:2020-10-22 23:35:37 來源:腳本之家 閱讀:101 作者:沐風的心 欄目:web開發

本文全面講述了JS繼承分類、原理與用法。分享給大家供大家參考,具體如下:

許多 OO 語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。由于 ECMAScript 中的函數沒有簽名,所以在 JS 中無法實現接口繼承。ECMAScript 只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。所以,下面所要說的原型鏈繼承借用構造函數繼承組合繼承原型式繼承寄生式繼承寄生組合式繼承都屬于實現繼承。

最后的最后,我會解釋 ES6 中的 extend 語法利用的是寄生組合式繼承。

1. 原型鏈繼承

ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。實現原型鏈繼承有一種基本模式,其代碼大致如下:

function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
};
function SubType(){
  this.subproperty = false;
}
SubType.prototype = new SuperType();    // 敲黑板!這是重點:繼承了 SuperType
SubType.prototype.getSubValue = function (){
  return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());    // true

原型鏈繼承的一個本質是重寫原型對象,代之以一個新類型的實例;給原型添加方法的代碼一定要放在替換原型的語句之后;在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。

實例屬性在實例化后,會掛載在實例對象下面,因此稱之為實例屬性。上面的代碼中 SubType.prototype = new SuperType(); ,執行完這條語句后,原 SuperType 的實例屬性 property 就掛載在了 SubType.prototype 對象下面。這其實是個隱患,具體原因后面會講到。

每次去查找屬性或方法的時候,在找不到屬性或方法的情況下,搜索過程總是要一環一環的前行到原型鏈末端才會停下來。

所有引用類型默認都繼承了 Object,而這個繼承也是通過原型鏈實現的。由此可知,所有函數的默認原型都是 object 的實例,因此函數的默認原型都會包含一個內部指針,指向 Object.prototype 。

缺點:

  1. 最主要的問題來自包含引用類型值的原型。在通過原型來實現繼承時,原型實際上會變成另一個類型的實例。于是,原先的實例屬性也就順理成章地變成了現在的原型屬性了。
  2. 在創建子類型的實例時,不能向超類型的構造函數傳遞參數。

* 題外話:確定原型與實例的關系的兩種方式

  1. 第一種方式是使用 instanceOf 操作符,只要用這個操作符來測試實例的原型鏈中是否出現過某構造函數。如果有,則就會返回 true ;如果無,則就會返回 false 。以下為示例代碼:
    alert(instance instanceof Object);   //true
    alert(instance instanceof SuperType);  //true
    alert(instance instanceof SubType);   //true
    
    
  1. 第二種方式是使用 isPrototypeOf() 方法。同樣,只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生出來的實例的原型。以下為示例代碼:
alert(Object.prototype.isPrototypeOf(instance));    //true
alert(SuperType.prototype.isPrototypeOf(instance));   //true
alert(SubType.prototype.isPrototypeOf(instance));    //true

2. 借用構造函數繼承

借用構造函數繼承,也叫偽造對象或經典繼承。其基本思想相當簡單,即在子類型構造函數的內部調用超類型構造函數。其繼承代碼大致如下:

function SuperType(){
  this.colors = [ "red", "blue", "green"];
}
function SubType(){
  SuperType.call(this);    // 敲黑板!注意了這里繼承了 SuperType
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    // "red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);    // "red,blue,green"

通過使用 call() 方法(或 apply() 方法也可以),我們實際上是在(未來將要)新創建的子類的實例環境下調用父類構造函數。

為了確保超類構造函數不會重寫子類型的屬性,可以在調用超類型構造函數后,再添加應該在子類型中定義的屬性。

優點:可以在子類型構造函數中向超類型構造函數傳遞參數。

缺點:

  1. 方法都在構造函數中定義,每次實例化,都是新創建一個方法對象,因此函數根本做不到復用;
  2. 使用這種模式定義自定義類型,超類型的原型中定義的方法,對子類型而言是不可見。

3. 組合繼承

組合繼承(combination inheritance),有時候也叫做偽經典繼承,其背后的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。其繼承代碼大致如下:

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};
function SubType(name, age){
  SuperType.call(this, name);   // 繼承屬性
  this.age = age;         // 先繼承,后定義新的自定義屬性
}
SubType.prototype = new SuperType();    // 繼承方法
Object.defineProperty( SubType.prototype, "constructor", {   // 先繼承,后定義新的自定義屬性
  enumerable: false,   // 申明該數據屬性——constructor不可枚舉
  value: SubType
});
SubType.prototype.sayAge = function(){   // 先繼承,后定義新的自定義方法
  alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);    // "red, blue, green, black"
instance1.sayName();      // "Nicholas"
instance1.sayAge();       // 29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);    // "red, blue, green"
instance2.sayName();      // "Greg";
instance2.sayAge();       // 27

優點:

  1. 融合了原型鏈繼承和借用構造函數繼承的優點,避免了他們的缺陷;
  2. instanceOf()isPrototypeOf() 也能夠用于識別基于組合繼承創建的對象。

缺點:

在實現繼承的時候,無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。子類型的原型最終會包含超類型對象的全部實例屬性,但我們不得不在定義子類型構造函數時重寫這些屬性,因為子類型的原型中最好不要有引用類型值。但這在實際中,就造成了內存的浪費。

4. 原型式繼承

原型式繼承所秉承的思想是:在不必創建自定義類型的情況下,借助原型鏈,基于已有的對象創建新對象。這其中會用到 Object.create() 方法,讓我們先來看看該方法的原理代碼吧:

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

從本質上講,object() 對傳入其中的對象執行了一次淺復制。

ECMAScript 5 想通過 Object.create() 方法規范化原型式繼承。這個方法接受兩個參數:一參是被用來作為新對象原型的一個對象;二參為可選,一個為新對象定義額外屬性的對象,這個參數的格式與 Object.defineProperties() 的二參格式相同。以下為原型式繼承的示例代碼:

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
  name: {
    value: "Greg"
  }
});
anotherPerson.friends.push("Rob");
alert(anotherPerson.name);   //"Greg"
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

缺點:所有實例始終都會共享源對象中的引用類型屬性值。

5. 寄生式繼承

寄生式(parasitic)繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像真的是它做了所有工作一樣返回對象。下面來看看,寄生式繼承的示例代碼:

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
function createAnother(original){
  var clone = object(original);  // 通過調用函數創建一個新對象
  clone.sayHi = function(){    // 以某種方式來增強這個對象
    alert("hi");
  };
  return clone;          // 返回這個對象
}

該繼承方式其實就是將原型式繼承放入函數內,并在其內部增強對象,再返回而已。就相當于原型式繼承寄生于函數中,故而得名寄生式繼承。

前面示范繼承模式時使用的 object() 函數不是必需的;任何能夠返回新對象的函數都適用于此模式。

缺點:不能做到函數復用,效率低下。

6. 寄生組合式繼承(推薦)

寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型。以下為寄生組合式繼承的實例代碼:

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
function inheritPrototype(subType, superType){
  var prototype = object(superType.prototype);    //創建對象
  prototype.constructor = subType;          //增強對象
  subType.prototype = prototype;           //指定對象
}
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};
function SubType(name, age){
  SuperType.call(this, name);     // 繼承屬性
  this.age = age;
}
inheritPrototype(SubType, SuperType);    // 繼承原型方法
SubType.prototype.sayAge = function(){
  alert(this.age);
};

優點:

  1. 只調用一次超類型構造函數;
  2. 避免了在子類原型上創建不必要的、多余的屬性,節省內存空間;
  3. 原型鏈還能正常保持不變,也就意味著能正常使用 instanceOf 和 isPrototypeOf() 進行對象識別。

寄生組合式繼承是最理想的繼承方式。

7. ES6 中的 extend 繼承

來看看 ES6 中 extend 如何實現繼承的示例代碼:這一塊的內容解釋,我閱讀的是這篇文章,欲知原文,請戳這里~

class Child extends Parent{
  name ='qinliang';
  sex = "male";
  static hobby = "pingpong";   //static variable
  constructor(location){
    super(location);
  }
  sayHello (name){
    super.sayHello(name);    //super調用父類方法
  }
}

我們再來看看 babel 編譯過后的代碼中的 _inherit() 方法:

function _inherits(subClass, superClass) {
  //SuperClass必須是一個函數,同時非null
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
  subClass.prototype = Object.create(   // 寄生組合式繼承
    superClass && superClass.prototype,   //原型上的方法、屬性全部被繼承過來了
    {
      constructor: {   // 并且定義了新屬性,這里是重寫了constructor屬性
        value: subClass,
        enumerable: false,   // 并實現了該屬性的不可枚舉
        writable: true,
        configurable: true
      }
    }
  );
  if (superClass)   // 實現類中靜態變量的繼承
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

從這里我們就可以很明顯的看出 ES6 中的 extend 語法,在內部實現繼承時,使用的是寄生組合式繼承。

下面我們來看看編譯過后,除了 _inherit() 方法外的其他編譯結果代碼:

"use strict";
var _createClass = function () {    // 利用原型模式創建自定義類型
  function defineProperties(target, props) {   // 對屬性進行數據特性設置
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor)
        descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  return function (Constructor, protoProps, staticProps) {
    // 設置Constructor的原型屬性到prototype中
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    // 設置Constructor的static類型屬性
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();
var _get = function get(object, property, receiver) {  // 調用子類的方法之前會先調用父類的方法
  // 默認從Function.prototype中獲取方法
  if (object === null) object = Function.prototype;
  // 獲取父類原型鏈中的指定方法
  var desc = Object.getOwnPropertyDescriptor(object, property);
  if (desc === undefined) {
    var parent = Object.getPrototypeOf(object);   // 繼續往上獲取父類原型
    if (parent === null) {
      return undefined;
    } else {    // 繼續獲取父類原型中指定的方法
      return get(parent, property, receiver);
    }
  } else if ("value" in desc) {
    return desc.value;   // 返回獲取到的值
  } else {
    var getter = desc.get;   // 獲取原型的getter方法
    if (getter === undefined) {
      return undefined;
    }
    return getter.call(receiver);    // 接著調用getter方法,并傳入this對象
  }
};
function _classCallCheck(instance, Constructor) {    // 保證了我們的實例對象是特定的類型
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
// 在子類的構造函數中調用父類的構造函數
function _possibleConstructorReturn(self, call) {    // 一參為子類的this,二參為父類的構造函數
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
var Child = function (_Parent) {
  _inherits(Child, _Parent);
  function Child(location) {   // static variable
    _classCallCheck(this, Child);    // 檢測this指向問題
    // 調用父類的構造函數,并傳入子類調用時候的參數,生成父類的this或者子類自己的this
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location));
    _this.name = 'qinliang';
    _this.sex = "male";
    return _this;
  }
  _createClass(Child, [{   //更新Child類型的原型
    key: "sayHello",
    value: function sayHello(name) {
      // super調用父類方法,將調用子類的super.sayHello時候傳入的參數傳到父類中
      _get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype), "sayHello", this).call(this, name);
    }
  }]);
  return Child;
}(Parent);
Child.hobby = "pingpong";

從我的注釋中就可以看出 _possibleConstructorReturn() 函數,其實就是寄生組合式繼承中唯一一次調用超類型構造函數,從而對子類型構造函數進行實例化環境的初始化。從這點,我們可以更加確定的 ES6 中的 extend 使用的是寄生組合式繼承。

更多關于JavaScript相關內容還可查看本站專題:《javascript面向對象入門教程》、《JavaScript錯誤與調試技巧總結》、《JavaScript數據結構與算法技巧總結》、《JavaScript遍歷算法與技巧總結》及《JavaScript數學運算用法總結》

希望本文所述對大家JavaScript程序設計有所幫助。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

平湖市| 乐都县| 兴化市| 双城市| 扎鲁特旗| 龙江县| 神池县| 武强县| 宽甸| 珠海市| 漳浦县| 碌曲县| 金溪县| 博客| 原阳县| 镇坪县| 绩溪县| 苍梧县| 秦皇岛市| 青神县| 白河县| 呼图壁县| 金山区| 开封县| 加查县| 黄陵县| 海原县| 乌兰县| 白水县| 新营市| 赤水市| 濮阳市| 云和县| 花莲市| 衡阳县| 四平市| 新疆| 梅州市| 景德镇市| 崇礼县| 凤山县|