摘要:繼承方法原型鏈繼承父類型子類型子類型的原型為父類型的一個實例對象這種繼承方式把設置直接設置為對象子類就可以通過原型鏈訪問父級所有的屬性和方法了。但是缺點很明顯,在創建子類的時候會調用調用兩次父類的構造函數。
起因
最近在使用node-jsonwebtoken中發現了下面這個代碼,感覺挺好看,于是就打算探索一些相關代碼:
代碼地址,點擊這里
var JsonWebTokenError = function (message, error) { Error.call(this, message); if(Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } this.name = "JsonWebTokenError"; this.message = message; if (error) this.inner = error; }; JsonWebTokenError.prototype = Object.create(Error.prototype); JsonWebTokenError.prototype.constructor = JsonWebTokenError; module.exports = JsonWebTokenError;
等會再來分析這個段代碼.
找到MDN中關于繼承部分(繼承在原型鏈中是高級部分教程)如下:
在JavaScript中繼承都是通過原型鏈來實現的。下面就來談談在JS 中繼承
繼承是面向對象的軟件的當中一個概念。在面向對象還有兩個特征分別是多態、分裝。繼承是可以讓自雷擁有父類的屬性和方法或者重新定義、追加屬性和方法等。
wikipedia)繼承方法 原型鏈繼承
//父類型 function Parent(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () { } } Parent.prototype.setAge = function () { } //子類型 function Child(price) { this.price = price this.setScore = function () { } } Child.prototype = new Parent() // 子類型的原型為父類型的一個實例對象 var s1 = new Child(15000) var s2 = new Child(14000) console.log(s1,s2)
這種繼承方式把 Child.prototype 設置直接設置為 Parent 對象, 子類就可以通過原型鏈訪問父級所有的屬性和方法了。后續若要增加新的屬性,必須在 Child.prototype = new Parent() 后面,否則則會被覆蓋.
在上面示例中需要說明的事,在使用父類屬性的時候會遵守JS的數據類型變化規則, 原始值(Primitive values) 不可突變(not mutation), 對象則會進行突變的。這個JS 存儲類型決定。原始值每次返回一定是一個新的值,而對象則代表是內存中一個區域地址,地址不變,具體在代碼中表現是即使內部數據不同,但是他們依舊相等。這個則設計JS中深淺拷貝問題,后續再涉及。使用子類構造函數來繼承
function Parent(name, age) { this.name = name, this.age = age, this.setName = function () {} } Parent.prototype.setAge = function () {} function Child(name, age, price) { Parent(this, name, age) // 相當于: this.Parent(name, age) /*this.name = name this.age = age*/ this.price = price } var s1 = new Child("Tom", 20, 15000)
這種方式首先在初始化 Child 之前,會對 Child 進行一個初始化,我們這里涉及這個關于 new 知識點, 在 new 之前會將 this 初始化為 Child.prototype 這個對象,這里使用 Function.prototype.call 來調用Parent,其實就是類似如下代碼:
const ChildPrototype = { Parent: function(name, age) { //... } }
這里就獲得了Parent中使用 this 初始化的屬性和方法, 這里不能夠獲取Parent原型鏈的數據。使用這種方式有如下優劣:
優點
解決了原型鏈繼承,子類訪問父類引用屬性的問題
子類初始化時候可以向父類傳參
實現多繼承(call多個父類對象)
缺點
子類實例使用instanceof 并不等于父類實例
不能繼承父類原型鏈上的方法
每次初始化過程中都需要進行父類函數初始化,性能不佳
這里使用 Function.prototype.call 方式可以看這里組合繼承(原型鏈+子類構造函數) - 1
function Parent(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Parent.prototype.setAge = function () { console.log("111") } function Child(name, age, price) { Parent.call(this, name, age) this.price = price this.setScore = function () { } } Child.prototype = new Parent() Child.prototype.contructor = Child Child.prototype.sayHello = function () { } var s1 = new Child("Tom", 20, 15000) console.log(s1)
這里的話融合了原型鏈繼承和構造函數的的優點。但是缺點很明顯,在創建子類的時候會調用調用兩次父類的構造函數。
在上面中 Child.prototype.contructor = Child 這里需要進行通過原型鏈繼承修復。這里主要修復之前幾個繼承問題
父類引用共享
子類可以獲取父類所有屬性和方法
組合繼承 - 2function Parent(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Parent.prototype.setAge = function () { console.log("111") } function Child(name, age, price) { Parent.call(this, name, age) this.price = price this.setScore = function () { } } Child.prototype = Parent.prototype Child.prototype.sayHello = function () { } var s1 = new Child("Tom", 20, 15000) console.log(s1)
這里通過子類的 prototype 重新賦值給 Parent.prototype, 就可以繼承到父類的所有原型鏈上的屬性,例如 setAge, 在子類函數申明中,通過 Parent.call 繼承父類本身的屬性和方法。這種方式就可以解決兩次調用構造函數問題, 但是這里也有一個問題就是,子類構造函數Child.prototype.constructor 被父類構造函數覆蓋,Parent.prototype.construtor 和 Child.prototype.constructor 指向都是 Parent.prototype.constructor。
組合繼承 - 3function Parent(name, age) { this.name = name, this.age = age, this.setAge = function () { } } Parent.prototype.setAge = function () { console.log("111") } function Child(name, age, price) { Parent.call(this, name, age) this.price = price this.setScore = function () { } } Child.prototype = Object.create(Parent.prototype) Child.prototype.construct = Child Child.prototype.sayHello = function () { } var s1 = new Child("Tom", 20, 15000) console.log(s1)
這里和上面一種區別在于通過創建使用 Object.create() 來把 Parent.prototype 設置為 Child.prototype.__proto__ 上, 也就說現在結構會變成如下圖:
通過這種方式的繼承,目前來看是最完美的一種方式,也解決之前很多問題。
ES6 的 extends 關鍵字class Parent { //調用類的構造方法 constructor(name, age) { this.name = name this.age = age } //定義一般的方法 showName() { console.log("調用父類的方法") console.log(this.name, this.age); } } let p1 = new Parent("kobe", 39) console.log(p1) //定義一個子類 class Child extends Parent { constructor(name, age, salary) { super(name, age)//通過super調用父類的構造方法 this.salary = salary } showName() {//在子類自身定義方法 console.log("調用子類的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Child("wade", 38, 1000000000) console.log(s1) s1.showName()
這種方式是目前es6的方式,缺點就是兼容性,不是所有瀏覽器都完美兼容es6.不過有點也很明顯和其他語言保持了一致的繼承語法,簡單易懂。
實際JS 都是通過原型鏈繼承,所以這里最終會編譯成如下代碼:
這是Babel編譯的結果,可以看到還是通過原型鏈來實現的。
"use strict"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Test = function Test() { _classCallCheck(this, Test); }; var Test1 = /*#__PURE__*/ function (_Test) { _inherits(Test1, _Test); function Test1() { _classCallCheck(this, Test1); return _possibleConstructorReturn(this, _getPrototypeOf(Test1).apply(this, arguments)); } return Test1; }(Test);
看下面圖示:
這里代碼和組合繼承3是類似的哦
更多查看:
https://www.quora.com/What-is...
https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104322.html
摘要:本套課程包含兩大部分,第一部分是基礎部分,也是重要部分,參考官方文檔結構,針對內容之間的關聯性和前后順序進行合理調整。 showImg(https://segmentfault.com/img/bVbpBA0?w=1460&h=400); 講師簡介: iview 核心開發者,iview-admin 作者,百萬級虛擬渲染表格組件 vue-bigdata-table 作者。目前就職于知名互...
摘要:關于中面向對象的理解面向對象編程它是一種編程思想我們的編程或者學習其實是按照類實例來完成的學習類的繼承封裝多態封裝把實現一個功能的代碼封裝到一個函數中一個類中以后再想實現這個功能,只需要執行這個函數方法即可,不需要再重復的編寫代碼。 關于js中面向對象的理解 面向對象編程(oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學習其...
摘要:關于中面向對象的理解面向對象編程它是一種編程思想我們的編程或者學習其實是按照類實例來完成的學習類的繼承封裝多態封裝把實現一個功能的代碼封裝到一個函數中一個類中以后再想實現這個功能,只需要執行這個函數方法即可,不需要再重復的編寫代碼。 關于js中面向對象的理解 面向對象編程(oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學習其...
摘要:預解釋變量和函數的預解釋只發生在當前的作用于中中內存的分類棧內存用來提供一個代碼指定的環境作用域全局作用域和局部作用域堆內存用來存儲引用類型的值對象存儲的是鍵值對,函數儲存的是字符串函數執行的時候形成一個私有作用域有形參給形參賦值,形參也 預解釋 變量和函數的預解釋只發生在當前的作用于中 js中內存的分類 棧內存:用來提供一個js代碼指定的環境 —>作用域(全局作用域和局部作用域...
閱讀 1449·2021-11-22 13:54
閱讀 4365·2021-09-22 15:56
閱讀 1822·2021-09-03 10:30
閱讀 1321·2021-09-03 10:30
閱讀 2089·2019-08-30 15:55
閱讀 1857·2019-08-30 14:13
閱讀 2063·2019-08-29 15:19
閱讀 2365·2019-08-28 18:13