摘要:繼承方法原型鏈繼承父類型子類型子類型的原型為父類型的一個(gè)實(shí)例對(duì)象這種繼承方式把設(shè)置直接設(shè)置為對(duì)象子類就可以通過(guò)原型鏈訪問(wèn)父級(jí)所有的屬性和方法了。但是缺點(diǎn)很明顯,在創(chuàng)建子類的時(shí)候會(huì)調(diào)用調(diào)用兩次父類的構(gòu)造函數(shù)。
起因
最近在使用node-jsonwebtoken中發(fā)現(xiàn)了下面這個(gè)代碼,感覺(jué)挺好看,于是就打算探索一些相關(guān)代碼:
代碼地址,點(diǎn)擊這里
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;
等會(huì)再來(lái)分析這個(gè)段代碼.
找到MDN中關(guān)于繼承部分(繼承在原型鏈中是高級(jí)部分教程)如下:
在JavaScript中繼承都是通過(guò)原型鏈來(lái)實(shí)現(xiàn)的。下面就來(lái)談?wù)勗贘S 中繼承
繼承是面向?qū)ο蟮能浖漠?dāng)中一個(gè)概念。在面向?qū)ο筮€有兩個(gè)特征分別是多態(tài)、分裝。繼承是可以讓自雷擁有父類的屬性和方法或者重新定義、追加屬性和方法等。
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() // 子類型的原型為父類型的一個(gè)實(shí)例對(duì)象 var s1 = new Child(15000) var s2 = new Child(14000) console.log(s1,s2)
這種繼承方式把 Child.prototype 設(shè)置直接設(shè)置為 Parent 對(duì)象, 子類就可以通過(guò)原型鏈訪問(wèn)父級(jí)所有的屬性和方法了。后續(xù)若要增加新的屬性,必須在 Child.prototype = new Parent() 后面,否則則會(huì)被覆蓋.
在上面示例中需要說(shuō)明的事,在使用父類屬性的時(shí)候會(huì)遵守JS的數(shù)據(jù)類型變化規(guī)則, 原始值(Primitive values) 不可突變(not mutation), 對(duì)象則會(huì)進(jìn)行突變的。這個(gè)JS 存儲(chǔ)類型決定。原始值每次返回一定是一個(gè)新的值,而對(duì)象則代表是內(nèi)存中一個(gè)區(qū)域地址,地址不變,具體在代碼中表現(xiàn)是即使內(nèi)部數(shù)據(jù)不同,但是他們依舊相等。這個(gè)則設(shè)計(jì)JS中深淺拷貝問(wèn)題,后續(xù)再涉及。使用子類構(gòu)造函數(shù)來(lái)繼承
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) // 相當(dāng)于: this.Parent(name, age) /*this.name = name this.age = age*/ this.price = price } var s1 = new Child("Tom", 20, 15000)
這種方式首先在初始化 Child 之前,會(huì)對(duì) Child 進(jìn)行一個(gè)初始化,我們這里涉及這個(gè)關(guān)于 new 知識(shí)點(diǎn), 在 new 之前會(huì)將 this 初始化為 Child.prototype 這個(gè)對(duì)象,這里使用 Function.prototype.call 來(lái)調(diào)用Parent,其實(shí)就是類似如下代碼:
const ChildPrototype = { Parent: function(name, age) { //... } }
這里就獲得了Parent中使用 this 初始化的屬性和方法, 這里不能夠獲取Parent原型鏈的數(shù)據(jù)。使用這種方式有如下優(yōu)劣:
優(yōu)點(diǎn)
解決了原型鏈繼承,子類訪問(wèn)父類引用屬性的問(wèn)題
子類初始化時(shí)候可以向父類傳參
實(shí)現(xiàn)多繼承(call多個(gè)父類對(duì)象)
缺點(diǎn)
子類實(shí)例使用instanceof 并不等于父類實(shí)例
不能繼承父類原型鏈上的方法
每次初始化過(guò)程中都需要進(jìn)行父類函數(shù)初始化,性能不佳
這里使用 Function.prototype.call 方式可以看這里組合繼承(原型鏈+子類構(gòu)造函數(shù)) - 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)
這里的話融合了原型鏈繼承和構(gòu)造函數(shù)的的優(yōu)點(diǎn)。但是缺點(diǎn)很明顯,在創(chuàng)建子類的時(shí)候會(huì)調(diào)用調(diào)用兩次父類的構(gòu)造函數(shù)。
在上面中 Child.prototype.contructor = Child 這里需要進(jìn)行通過(guò)原型鏈繼承修復(fù)。這里主要修復(fù)之前幾個(gè)繼承問(wèn)題
父類引用共享
子類可以獲取父類所有屬性和方法
組合繼承 - 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)
這里通過(guò)子類的 prototype 重新賦值給 Parent.prototype, 就可以繼承到父類的所有原型鏈上的屬性,例如 setAge, 在子類函數(shù)申明中,通過(guò) Parent.call 繼承父類本身的屬性和方法。這種方式就可以解決兩次調(diào)用構(gòu)造函數(shù)問(wèn)題, 但是這里也有一個(gè)問(wèn)題就是,子類構(gòu)造函數(shù)Child.prototype.constructor 被父類構(gòu)造函數(shù)覆蓋,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)
這里和上面一種區(qū)別在于通過(guò)創(chuàng)建使用 Object.create() 來(lái)把 Parent.prototype 設(shè)置為 Child.prototype.__proto__ 上, 也就說(shuō)現(xiàn)在結(jié)構(gòu)會(huì)變成如下圖:
通過(guò)這種方式的繼承,目前來(lái)看是最完美的一種方式,也解決之前很多問(wèn)題。
ES6 的 extends 關(guān)鍵字class Parent { //調(diào)用類的構(gòu)造方法 constructor(name, age) { this.name = name this.age = age } //定義一般的方法 showName() { console.log("調(diào)用父類的方法") console.log(this.name, this.age); } } let p1 = new Parent("kobe", 39) console.log(p1) //定義一個(gè)子類 class Child extends Parent { constructor(name, age, salary) { super(name, age)//通過(guò)super調(diào)用父類的構(gòu)造方法 this.salary = salary } showName() {//在子類自身定義方法 console.log("調(diào)用子類的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Child("wade", 38, 1000000000) console.log(s1) s1.showName()
這種方式是目前es6的方式,缺點(diǎn)就是兼容性,不是所有瀏覽器都完美兼容es6.不過(guò)有點(diǎn)也很明顯和其他語(yǔ)言保持了一致的繼承語(yǔ)法,簡(jiǎn)單易懂。
實(shí)際JS 都是通過(guò)原型鏈繼承,所以這里最終會(huì)編譯成如下代碼:
這是Babel編譯的結(jié)果,可以看到還是通過(guò)原型鏈來(lái)實(shí)現(xiàn)的。
"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...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104322.html
摘要:本套課程包含兩大部分,第一部分是基礎(chǔ)部分,也是重要部分,參考官方文檔結(jié)構(gòu),針對(duì)內(nèi)容之間的關(guān)聯(lián)性和前后順序進(jìn)行合理調(diào)整。 showImg(https://segmentfault.com/img/bVbpBA0?w=1460&h=400); 講師簡(jiǎn)介: iview 核心開(kāi)發(fā)者,iview-admin 作者,百萬(wàn)級(jí)虛擬渲染表格組件 vue-bigdata-table 作者。目前就職于知名互...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實(shí)是按照類實(shí)例來(lái)完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實(shí)現(xiàn)一個(gè)功能的代碼封裝到一個(gè)函數(shù)中一個(gè)類中以后再想實(shí)現(xiàn)這個(gè)功能,只需要執(zhí)行這個(gè)函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實(shí)是按照類實(shí)例來(lái)完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實(shí)現(xiàn)一個(gè)功能的代碼封裝到一個(gè)函數(shù)中一個(gè)類中以后再想實(shí)現(xiàn)這個(gè)功能,只需要執(zhí)行這個(gè)函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
摘要:預(yù)解釋變量和函數(shù)的預(yù)解釋只發(fā)生在當(dāng)前的作用于中中內(nèi)存的分類棧內(nèi)存用來(lái)提供一個(gè)代碼指定的環(huán)境作用域全局作用域和局部作用域堆內(nèi)存用來(lái)存儲(chǔ)引用類型的值對(duì)象存儲(chǔ)的是鍵值對(duì),函數(shù)儲(chǔ)存的是字符串函數(shù)執(zhí)行的時(shí)候形成一個(gè)私有作用域有形參給形參賦值,形參也 預(yù)解釋 變量和函數(shù)的預(yù)解釋只發(fā)生在當(dāng)前的作用于中 js中內(nèi)存的分類 棧內(nèi)存:用來(lái)提供一個(gè)js代碼指定的環(huán)境 —>作用域(全局作用域和局部作用域...
閱讀 1457·2021-11-22 13:54
閱讀 4376·2021-09-22 15:56
閱讀 1828·2021-09-03 10:30
閱讀 1326·2021-09-03 10:30
閱讀 2093·2019-08-30 15:55
閱讀 1859·2019-08-30 14:13
閱讀 2066·2019-08-29 15:19
閱讀 2374·2019-08-28 18:13