摘要:原型鏈構(gòu)造函數(shù)原型實(shí)例的關(guān)系每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,實(shí)例有一個指向原型對象的指針構(gòu)造函數(shù)原型對象構(gòu)造函數(shù)構(gòu)造函數(shù)操作符實(shí)例對象構(gòu)造函數(shù)實(shí)例對象原型對象如果試
原型鏈
構(gòu)造函數(shù)/原型/實(shí)例 的關(guān)系
每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,實(shí)例有一個指向原型對象的指針
構(gòu)造函數(shù) --(prototype)--> 原型對象 --(constructor)--> 構(gòu)造函數(shù)
構(gòu)造函數(shù) --(new 操作符)--> 實(shí)例對象 --(constructor)--> 構(gòu)造函數(shù)
實(shí)例對象 --(__proto__)--> 原型對象
如果試圖使用某個對象(實(shí)例)的某個屬性或方法,會首先在對象內(nèi)部尋找該屬性,如果找不到去該對象的原型(instance.__proto__)里面去找,如果還找不到就繼續(xù)沿著 __proto__ 這個鏈條往上找,直到找到 Object.prototype 為止
javascript 里面一切皆對象,所以都可以從這個鏈條去出發(fā)
JavaScript 的繼承不同于傳統(tǒng)面向?qū)ο笫强款悓?shí)現(xiàn)繼承,而是通過原型鏈實(shí)現(xiàn)繼承
ES5 繼承拷貝式繼承(通過深拷貝實(shí)現(xiàn)繼承)
原型式繼承
缺點(diǎn):只能繼承原型方法
借用構(gòu)造函數(shù)繼承
缺點(diǎn):只能繼承實(shí)例屬性
組合式繼承
缺點(diǎn):無論在什么情況下,都會調(diào)用兩次構(gòu)造函數(shù)(創(chuàng)建父類實(shí)例的時候,在子類構(gòu)造函數(shù)內(nèi)不調(diào)用父類構(gòu)造函數(shù)時)
組合寄生式繼承 (比較完美的繼承,但不能繼承父類靜態(tài)方法、靜態(tài)屬性)
function Parent() {} function Child() { // 繼承父類實(shí)例屬性 Parent.call(this) // 如果父類有參數(shù)寫在 this 后面 } // 繼承父類原型方法 Child.prototype = Object.create(Parent.prototype) // 修正子類原型的 constructor 指向 Child.prototype.constructor = Child
Object.create(proto, [propertiesObject]) MDN
創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的的對象的 __proto__ Object.create(null) // 創(chuàng)建一個沒有原型的空對象 第二個參數(shù)可添加屬性描述符 js高級程序設(shè)計(jì)用一下代碼代替的這個方法 function createObject(P) { var F = function() {} F.prototype = P.prototype return new F() }
為什么要修正子類原型的 constructor 指向? 阮一峰
簡單總結(jié)一下: 任何一個 prototype 對象都有一個 constructor 屬性,指向它的構(gòu)造函數(shù) 更重要的是,每一個實(shí)例也有一個 constructor 屬性,默認(rèn)調(diào)用 prototype 的 constructor 屬性 如果沒有修正的那行代碼,結(jié)果如下 var c = new C() c.constructor === Child // false Child.prototype.constructor === Child // false c.constructor === Parent // true Child.prototype.constructor === Parent // true 這顯然會導(dǎo)致繼承鏈的混亂(c 明明是用構(gòu)造函數(shù)Child生成的),因此我們必須手動糾正ES6 繼承
ES6 的繼承本質(zhì)上還是借助原型鏈實(shí)現(xiàn)繼承
// 借助 class extends 關(guān)鍵字 class Parent { static sayAge() { return "18" } constructor(name) { this.name = "name" } } class Child extends Parent { constructor(name, age) { /** * 如果寫 constructor 必須調(diào)用 super 方法,這是因?yàn)樽宇愖约旱?this 對象,必須先通過父類構(gòu)造函數(shù)完成塑造 * 不寫 super 就得不到 this對象,new 的時候就會報(bào)錯 * Uncaught ReferenceError: Must call super constructor in derived class before accessing "this" or returning from derived constructor * * ES5 實(shí)質(zhì)上先創(chuàng)造子類的實(shí)例對象 this,然后再將父類的方法添加到 this 上面 (Parent.call(this)) * ES6 實(shí)質(zhì)上先將父類實(shí)例對象的屬性和方法,加到 this 上面(必須先調(diào)用 super 方法),然后用子類構(gòu)造函數(shù)修改 this */ super(name, age) this.age = age } } // 注意點(diǎn):es6 的繼承可以繼承父類的靜態(tài)方法和靜態(tài)屬性,而ES5的繼承不行 // 經(jīng)過 extends 之后,Child.__proto__ ==== Parent // true // Parent.__proto__ 返回 f() { [native code] } // Parent.__proto__.__proto__ === Object.prototypeBabel 轉(zhuǎn)碼后的 ES6 繼承代碼
// 為方便觀看,對代碼做了一些美化和省略處理 "use strict"; // 檢查子類是否調(diào)用了 super 方法 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) { // ... // 同 es5 繼承的那一段 subClass.prototype = Object.create(superClass.prototype, { constructor: { value: subClass, // 修正 constructor 指向 writable: true, configurable: true } }); // 實(shí)現(xiàn)靜態(tài)屬性和方法的繼承 原理為:Child.__proto__ = Parent // 即子類(子類現(xiàn)在相當(dāng)于實(shí)例)的在往上的 prototype = Parent,即子類可以使用父類的靜態(tài)屬性和方法 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); } // ... 省略類的創(chuàng)建及檢測等方法 var Parent = /*#__PURE__*/ (function() { _createClass(Parent, null, [ { key: "sayAge", value: function sayAge() { return "18"; } } ]); function Parent(name) { _classCallCheck(this, Parent); this.name = "name"; } return Parent; })(); var Child = /*#__PURE__*/ (function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); // 下面一行代碼即 調(diào)用 super 的效果,如果不調(diào)用 super 子類將沒有 this _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name, age)); /*** * 如果注釋源碼中的 super 將編譯為如下代碼 * _this.age = age; * return _possibleConstructorReturn(_this); * 因?yàn)闆]有調(diào)用 super 子類還沒有 this,所以下一行直接報(bào)錯 * * 如果源碼中不寫 _this.age = age * 將直接進(jìn)入 _assertThisInitialized 方法,然后報(bào)錯,沒有調(diào)用 super 方法 */ _this.age = age; return _this; } return Child; })(Parent); var c = new Child(); // 如果不用 babel轉(zhuǎn)碼,直接在瀏覽器里運(yùn)行,不寫 super,結(jié)果如下和 babel 轉(zhuǎn)義后報(bào)錯信息不一樣 // 原生報(bào)錯信息: Uncaught ReferenceError: Must call super constructor in derived class before accessing "this" or returning from derived constructor
原生構(gòu)造函數(shù)不可通過 extends 實(shí)現(xiàn)繼承 ES6阮一峰
因?yàn)樽宇惸貌坏?原生父類內(nèi)部的對象,即是通過 call 也不行
new 運(yùn)算符上面說了繼承,那產(chǎn)生實(shí)例的操作符 new 是什么原理?
var obj = {} obj.__proto__ = Child.prototype F.call(obj) // 1. 創(chuàng)建一個空對象 // 2. 將這個空對象的 __proto__ 屬性 指向了構(gòu)造函數(shù)的 prototype 屬性 上 ==> 繼承原型屬性方法 // 3. 將構(gòu)造函數(shù)的 this 指針替換成了 obj(實(shí)例),再調(diào)用 構(gòu)造函數(shù) ===> 繼承實(shí)例屬性方法與原型鏈有關(guān)的幾個方法
hasOwnProperty : 該方法只會查找對象本身是否有某屬性,不會去原型鏈上尋找
A.isPropertyOf(instanceA) : 判斷 A 是不是 instanceA 的原型對象
instanceof : 判斷對象是不是某個構(gòu)造函數(shù)的實(shí)例
__proto__ 只是瀏覽器廠商的私有實(shí)現(xiàn),規(guī)范并不支持,規(guī)范支持Object.getPrototypeOf 和 Object.setPrototypeOf
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/105935.html
摘要:今天閑來無事,看見幾行小字。又說所有對象,繼承終是。強(qiáng)行押韻一波這首詩的意思就是說的我今天沒有什么事情,然后無意中又在網(wǎng)上看到了任何對象都是從對象繼承而來的這句話。一時興起,便去驗(yàn)證這句話。 今天閑來無事,看見幾行小字。又說所有對象,繼承終是Obj。—— 強(qiáng)行押韻一波 這首詩的意思就是說的我今天沒有什么事情,然后無意中又在網(wǎng)上看到了任何對象都是從Object對象繼承而來的這句話。一時興...
摘要:在繼承的構(gòu)造函數(shù)中,我們必須如上面的例子那么調(diào)用一次方法,它表示構(gòu)造函數(shù)的繼承,與中利用繼承構(gòu)造函數(shù)是一樣的功能。 showImg(https://segmentfault.com/img/remote/1460000009078532); 在實(shí)際開發(fā)中,ES6已經(jīng)非常普及了。掌握ES6的知識變成了一種必須。盡管我們在使用時仍然需要經(jīng)過babel編譯。 ES6徹底改變了前端的編碼風(fēng)格,...
摘要:聲明會提升,但是不會被初始化賦值,所以優(yōu)先初始化賦值,則會進(jìn)入暫時性死區(qū),類似,變量內(nèi)部啟動嚴(yán)格模式的所有方法包括靜態(tài)方法和示例方法都沒有原型對象,所以也沒有,不能使用來調(diào)用必須使用來調(diào)用內(nèi)部無法重寫類名 class聲明會提升,但是不會被初始化賦值,所以優(yōu)先初始化賦值,則會進(jìn)入暫時性死區(qū),類似let,const變量 const bar = new Bar(); // ok funct...
摘要:組合式繼承是最常用的繼承模式,但組合繼承使用過程中會被調(diào)用兩次一次是創(chuàng)建子類型的時候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部。 首先需要了解原型鏈機(jī)制: 原型鏈作為實(shí)現(xiàn)繼承的主要方法,其基本思想就是利用原型讓一個引用類型繼承另 一個引用類型的屬性和方法. 構(gòu)造函數(shù)、原型、實(shí)例之間的關(guān)系: 每個構(gòu)造函數(shù)都有一個原型對象(prototype),原型對象都包含一個指向構(gòu)造函數(shù)的指針(constr...
摘要:對象擴(kuò)展簡潔表示法屬性表達(dá)式值用中括號包起來,就是個表達(dá)式跟的功能是一樣的數(shù)組也是引用類型,值雖然都是空,但指向不同的內(nèi)存地址實(shí)現(xiàn)對象的拷貝淺拷貝只拷貝對象自身的屬性,如果對象有繼承,繼承的屬性不會被拷貝只拷貝可枚舉屬性,不可枚舉屬性不會被 對象擴(kuò)展 簡潔表示法 { let o = 1,k = 2; let es5 = { o: o, k...
閱讀 3038·2023-04-25 19:08
閱讀 1496·2021-11-16 11:45
閱讀 2137·2021-10-13 09:40
閱讀 4310·2021-09-30 09:47
閱讀 2493·2019-08-30 15:44
閱讀 2447·2019-08-30 13:03
閱讀 1448·2019-08-30 12:56
閱讀 1950·2019-08-26 14:04