摘要:創(chuàng)建子類實(shí)例,可以向父類構(gòu)造函數(shù)傳參數(shù)。修復(fù)如下其實(shí)方式組合繼承優(yōu)化核心通過這種方式,砍掉父類的實(shí)例屬性,這樣在調(diào)用父類的構(gòu)造函數(shù)的時(shí)候,就不會(huì)初始化兩次實(shí)例,避免組合繼承的缺點(diǎn)。優(yōu)點(diǎn)只調(diào)用一次父類構(gòu)造函數(shù)。
2018.06.03
第一部分:導(dǎo)入 1、構(gòu)造函數(shù)的屬性funcion A(name) { this.name = name; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // 實(shí)例引用屬性 (該屬性,強(qiáng)調(diào)私用,不共享) this.say = function() { // 實(shí)例引用屬性 (該屬性,強(qiáng)調(diào)復(fù)用,需要共享) console.log("hello") } } 注意:數(shù)組和方法都屬于‘實(shí)例引用屬性’,但是數(shù)組強(qiáng)調(diào)私有、不共享的。方法需要復(fù)用、共享。 注意:在構(gòu)造函數(shù)中,一般很少有數(shù)組形式的引用屬性,大部分情況都是:基本屬性 + 方法。2、原型對象的作用
原型對象的用途是為每個(gè)實(shí)例對象存儲(chǔ)共享的方法和屬性,它僅僅是一個(gè)普通對象而已。并且所有的實(shí)例是共享同一個(gè)原型對象,因此有別于實(shí)例方法或?qū)傩裕蛯ο髢H有一份。而實(shí)例有很多份,且實(shí)例屬性和方法是獨(dú)立的。在構(gòu)造函數(shù)中:為了屬性(實(shí)例基本屬性)的私有性、以及方法(實(shí)例引用屬性)的復(fù)用、共享。我們提倡:
將屬性封裝在構(gòu)造函數(shù)中
將方法定義在原型對象上
funcion A(name) { this.name = name; // (該屬性,強(qiáng)調(diào)私有,不共享) } A.prototype.say = function() { // 定義在原型對象上的方法 (強(qiáng)調(diào)復(fù)用,需要共享) console.log("hello") } // 不推薦的寫法:[原因](https://blog.csdn.net/kkkkkxiaofei/article/details/46474303) A.prototype = { say: function() { console.log("hello") } }第二部分:js 繼承---各種方式的優(yōu)缺點(diǎn) 方式1、原型鏈繼承
核心:將父類實(shí)例作為子類原型
優(yōu)點(diǎn):方法復(fù)用
由于方法定義在父類的原型上,復(fù)用了父類構(gòu)造函數(shù)的方法。比如say方法。
缺點(diǎn):
創(chuàng)建子類實(shí)例的時(shí)候,不能傳參數(shù)。
子類實(shí)例共享了父類構(gòu)造函數(shù)的引用屬性,比如arr屬性。
function Parent() { this.name = "父親"; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // (該屬性,強(qiáng)調(diào)私有) } Parent.prototype.say = function() { // -- 將需要復(fù)用、共享的方法定義在父類原型上 console.log("hello") } function Child(like) { this.like = like; } Child.prototype = new Parent() // 核心 let boy1 = new Child() let boy2 = new Child() // 優(yōu)點(diǎn):共享了父類構(gòu)造函數(shù)的say方法 console.log(boy1.say(), boy2.say(), boy1.say === boy2.say); // hello , hello , true // 缺點(diǎn)1:不能傳參數(shù) // 缺點(diǎn)2: console.log(boy1.name, boy2.name, boy1.name===boy2.name); // 父親,父親,true boy1.arr.push(2); // 修改了boy1的arr屬性,boy2的arr屬性,也會(huì)變化,因?yàn)閮蓚€(gè)實(shí)例的原型上(Child.prototype)有了父類構(gòu)造函數(shù)的實(shí)例屬性arr;所以只要修改了boy1.arr,boy2.arr的屬性也會(huì)變化。 ---- 原型上的arr屬性是共享的。 console.log(boy2.arr); // [1,2] 注意:修改boy1的name屬性,是不會(huì)影響到boy2.name。因?yàn)閚ame是基本屬性,不是引用屬性。方式2、借用構(gòu)造函數(shù)
核心:借用父類的構(gòu)造函數(shù)來增強(qiáng)子類實(shí)例,等于是復(fù)制父類的實(shí)例屬性給子類。
優(yōu)點(diǎn):實(shí)例之間獨(dú)立。
創(chuàng)建子類實(shí)例,可以向父類構(gòu)造函數(shù)傳參數(shù)。
子類實(shí)例不共享父類構(gòu)造函數(shù)的引用屬性。如arr屬性
缺點(diǎn):
父類的方法不能復(fù)用
由于方法在父構(gòu)造函數(shù)中定義,導(dǎo)致方法不能復(fù)用(因?yàn)槊看蝿?chuàng)建子類實(shí)例都要?jiǎng)?chuàng)建一遍方法)。比如say方法。(方法應(yīng)該要復(fù)用、共享)
子類實(shí)例,繼承不了父類原型上的屬性。(因?yàn)闆]有用到原型)
function Parent(name) { this.name = name; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // (該屬性,強(qiáng)調(diào)私有) this.say = function() { // 實(shí)例引用屬性 (該屬性,強(qiáng)調(diào)復(fù)用,需要共享) console.log("hello") } } function Child(name,like) { Parent.call(this,name); // 核心 this.like = like; } let boy1 = new Child("小紅","apple"); let boy2 = new Child("小明", "orange "); // 優(yōu)點(diǎn)1:可傳參 console.log(boy1.name, boy2.name); // 小紅, 小明 // 優(yōu)點(diǎn)2:不共享父類構(gòu)造函數(shù)的引用屬性 boy1.arr.push(2); console.log(boy1.arr,boy2.arr);// [1,2] [1] // 缺點(diǎn)1:方法不能復(fù)用 console.log(boy1.say === boy2.say) // false (說明,boy1和boy2 的say方法是獨(dú)立,不是共享的) // 缺點(diǎn)2:不能繼承父類原型上的方法 Parent.prototype.walk = function () { // 在父類的原型對象上定義一個(gè)walk方法。 console.log("我會(huì)走路") } boy1.walk; // undefined (說明實(shí)例,不能獲得父類原型上的方法)方式3、組合繼承
核心:通過調(diào)用父類構(gòu)造函數(shù),繼承父類的屬性并保留傳參的優(yōu)點(diǎn);然后通過將父類實(shí)例作為子類原型,實(shí)現(xiàn)函數(shù)復(fù)用。
優(yōu)點(diǎn):
保留構(gòu)造函數(shù)的優(yōu)點(diǎn):創(chuàng)建子類實(shí)例,可以向父類構(gòu)造函數(shù)傳參數(shù)。
保留原型鏈的優(yōu)點(diǎn):父類的實(shí)例方法定義在父類的原型對象上,可以實(shí)現(xiàn)方法復(fù)用。
不共享父類的引用屬性。比如arr屬性
缺點(diǎn):
由于調(diào)用了2次父類的構(gòu)造方法,會(huì)存在一份多余的父類實(shí)例屬性,具體原因見文末。
注意:"組合繼承"這種方式,要記得修復(fù)Child.prototype.constructor指向
第一次Parent.call(this);從父類拷貝一份父類實(shí)例屬性,作為子類的實(shí)例屬性,第二次Child.prototype = new Parent();創(chuàng)建父類實(shí)例作為子類原型,此時(shí)這個(gè)父類實(shí)例就又有了一份實(shí)例屬性,但這份會(huì)被第一次拷貝來的實(shí)例屬性屏蔽掉,所以多余。
為啥是兩次?如果還是,不清楚,可以看文末,我會(huì)詳細(xì)講解!
function Parent(name) { this.name = name; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // (該屬性,強(qiáng)調(diào)私有) } Parent.prototype.say = function() { // --- 將需要復(fù)用、共享的方法定義在父類原型上 console.log("hello") } function Child(name,like) { Parent.call(this,name,like) // 核心 第二次 this.like = like; } Child.prototype = new Parent() // 核心 第一次 let boy1 = new Child("小紅","apple") let boy2 = new Child("小明","orange") // 優(yōu)點(diǎn)1:可以傳參數(shù) console.log(boy1.name,boy1.like); // 小紅,apple // 優(yōu)點(diǎn)2:可復(fù)用父類原型上的方法 console.log(boy1.say === boy2.say) // true // 優(yōu)點(diǎn)3:不共享父類的引用屬性,如arr屬性 boy1.arr.push(2) console.log(boy1.arr,boy2.arr); // [1,2] [1] 可以看出沒有共享arr屬性。 注意:為啥要修復(fù)構(gòu)造函數(shù)的指向? console.log(boy1.constructor); // Parent 你會(huì)發(fā)現(xiàn)實(shí)例的構(gòu)造函數(shù)居然是Parent。 而實(shí)際上,我們希望子類實(shí)例的構(gòu)造函數(shù)是Child,所以要記得修復(fù)構(gòu)造函數(shù)指向。修復(fù)如下 Child.prototype.constructor = Child;
其實(shí)Child.prototype = new Parent()方式4、組合繼承優(yōu)化1console.log(Child.prototype.__proto__ === Parten.prototype); // true
核心:
通過這種方式,砍掉父類的實(shí)例屬性,這樣在調(diào)用父類的構(gòu)造函數(shù)的時(shí)候,就不會(huì)初始化兩次實(shí)例,避免組合繼承的缺點(diǎn)。
優(yōu)點(diǎn):
只調(diào)用一次父類構(gòu)造函數(shù)。
保留構(gòu)造函數(shù)的優(yōu)點(diǎn):創(chuàng)建子類實(shí)例,可以向父類構(gòu)造函數(shù)傳參數(shù)。
保留原型鏈的優(yōu)點(diǎn):父類的實(shí)例方法定義在父類的原型對象上,可以實(shí)現(xiàn)方法復(fù)用。
缺點(diǎn):
修正構(gòu)造函數(shù)的指向之后,父類實(shí)例的構(gòu)造函數(shù)指向,同時(shí)也發(fā)生變化(這是我們不希望的)
注意:"組合繼承優(yōu)化1"這種方式,要記得修復(fù)Child.prototype.constructor指向
原因是:不能判斷子類實(shí)例的直接構(gòu)造函數(shù),到底是子類構(gòu)造函數(shù)還是父類構(gòu)造函數(shù)。
function Parent(name) { this.name = name; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // (該屬性,強(qiáng)調(diào)私有) } Parent.prototype.say = function() { // --- 將需要復(fù)用、共享的方法定義在父類原型上 console.log("hello") } function Child(name,like) { Parent.call(this,name,like) // 核心 this.like = like; } Child.prototype = Parent.prototype // 核心 子類原型和父類原型,實(shí)質(zhì)上是同一個(gè) let boy1 = new Child("小紅","apple") let boy2 = new Child("小明","orange") let p1 = new Parent("小爸爸") // 優(yōu)點(diǎn)1:可以傳參數(shù) console.log(boy1.name,boy1.like); // 小紅,apple // 優(yōu)點(diǎn)2: console.log(boy1.say === boy2.say) // true // 缺點(diǎn)1:當(dāng)修復(fù)子類構(gòu)造函數(shù)的指向后,父類實(shí)例的構(gòu)造函數(shù)指向也會(huì)跟著變了。 具體原因:因?yàn)槭峭ㄟ^原型來實(shí)現(xiàn)繼承的,Child.prototype的上面是沒有constructor屬性的,就會(huì)往上找,這樣就找到了Parent.prototype上面的constructor屬性;當(dāng)你修改了子類實(shí)例的construtor屬性,所有的constructor的指向都會(huì)發(fā)生變化。 沒修復(fù)之前:console.log(boy1.constructor); // Parent 修復(fù)代碼:Child.prototype.constructor = Child 修復(fù)之后:console.log(boy1.constructor); // Child console.log(p1.constructor);// Child 這里就是存在的問題(我們希望是Parent)方式5、組合繼承優(yōu)化2 又稱 寄生組合繼承 --- 完美方式
核心:
優(yōu)點(diǎn):完美i
缺點(diǎn):---
function Parent(name) { this.name = name; // 實(shí)例基本屬性 (該屬性,強(qiáng)調(diào)私有,不共享) this.arr = [1]; // (該屬性,強(qiáng)調(diào)私有) } Parent.prototype.say = function() { // --- 將需要復(fù)用、共享的方法定義在父類原型上 console.log("hello") } function Child(name,like) { Parent.call(this,name,like) // 核心 this.like = like; } Child.prototype = Object.create(Parent.prototype) // 核心 通過創(chuàng)建中間對象,子類原型和父類原型,就會(huì)隔離開。不是同一個(gè)啦,有效避免了方式4的缺點(diǎn)。 Child.prototype.constructor = Child let boy1 = new Child("小紅","apple") let boy2 = new Child("小明","orange") let p1 = new Parent("小爸爸") 注意:這種方法也要修復(fù)構(gòu)造函數(shù)的 修復(fù)代碼:Child.prototype.constructor = Child 修復(fù)之后:console.log(boy1.constructor); // Child console.log(p1.constructor);// Parent 完美第三部分:其他 + 相關(guān)問題解答 1、Object.create() 或 Object.create(object, [,propertiesObject])
Object.create() 的第二參數(shù),是可選的。
- Object.create() 的內(nèi)部原理: // 其中,o 是新創(chuàng)建對象的原型(對象) function object(o) { function F() {} F.prototype = o return new F() } 注意:之前,Object.create()沒有出現(xiàn)之前,就是采用的這種方式。 參見《js高級程序設(shè)計(jì)》P170
Object.create() 做了哪幾件事情?
創(chuàng)建空對象{}
指定空對象{}的原型為Object.create()的參數(shù)。
new 與 Object.create() 的區(qū)別?
以下是我的個(gè)人見解,(如有不對,還請指正):new 產(chǎn)生的實(shí)例,優(yōu)先獲取構(gòu)造函數(shù)上的屬性;構(gòu)造函數(shù)上沒有對應(yīng)的屬性,才會(huì)去原型上查找;如果構(gòu)造函數(shù)中以及原型中都沒有對應(yīng)的屬性,就會(huì)報(bào)錯(cuò)。
Object.create() 產(chǎn)生的對象,只會(huì)在原型上進(jìn)行查找屬性,原型上沒有對應(yīng)的屬性,就會(huì)報(bào)錯(cuò)。
let Base1 = function() { this.a = 1 } let o1 = new Base1() let o2 = Object.create(Base1.prototype) console.log(o1.a); // 1 console.log(o2.a); // undefined let Base2 = function() {} Base2.prototype.a = "aa" let o3 = new Base2() let o4 = Object.create(Base2.prototype) console.log(o3.a); // aa console.log(o4.a); // aa let Base3 = function() { this.a = 1 } Base3.prototype.a = "aa" let o5 = new Base3() let o6 = Object.create(Base3.prototype) console.log(o5.a); // 1 console.log(o6.a); // aa2、new 的過程
funciton Func(name) { this.name = name } let p = new Func("小紅")
new 的過程,做了啥?做了四件事。
創(chuàng)建一個(gè)空對象obj:let obj = new Object()
設(shè)置原型鏈
obj.__proto__ = Func.prototype 就是:將新對象的__proto__ 指向構(gòu)造函數(shù)的prototype
將構(gòu)造函數(shù)Func的this指向obj,并執(zhí)行構(gòu)造函數(shù)Func
let result = Func.call(obj) 就是:使用call或apply,將構(gòu)造函數(shù)的this綁定到新對象,并執(zhí)行構(gòu)造函數(shù)
判斷構(gòu)造函數(shù)Func的返回值類型
如果是引用類型,就返回這個(gè)引用類型的對象。如果是值類型或沒有return,則返回空對象obj。 if (typeof(result) === "object"){ func=result; } else{ func=obj; // 默認(rèn)返回 } 注意:js中的構(gòu)造函數(shù),是不需要有返回值的,所以默認(rèn)返回的是新創(chuàng)建的空對象obj3、為啥‘組合繼承’這種方式,會(huì)執(zhí)行兩次父類構(gòu)造函數(shù)??
第一次:Child.prototype = new Parent()
‘new 的過程’的第三步,其實(shí)就是執(zhí)行了父類構(gòu)造函數(shù)。
第二次:Parent.call(this,name,like)
call的作用是改變函數(shù)執(zhí)行時(shí)的上下文。比如:A.call(B)。其實(shí),最終執(zhí)行的還是A函數(shù),只不過是用B來調(diào)用而已。所以,你就懂了Parent.call(this,name,like) ,也就是執(zhí)行了父類構(gòu)造函數(shù)。第四部分:參考鏈接
new操作符具體干了什么呢?
new的過程
你不知道的javascript之Object.create 和new區(qū)別
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/108027.html
摘要:組合模式繼承結(jié)合了構(gòu)造函數(shù)繼承時(shí)可以為每個(gè)屬性重新初始化,構(gòu)造一個(gè)副本的優(yōu)點(diǎn),以及原型鏈繼承時(shí)一次定義處處共享的優(yōu)點(diǎn)。但令我百思不得其解的是,從上面給出的例子來看,組合繼承并沒有調(diào)用兩次超類型構(gòu)造函數(shù)。 最近在閱讀《js權(quán)威指南》的繼承這一章,對于組合模式和寄生組合模式的區(qū)別有點(diǎn)混淆,在多次重讀以及嘗試之后,得到一些心得。 組合模式繼承 結(jié)合了構(gòu)造函數(shù)繼承時(shí)可以為每個(gè)屬性重新初始化,構(gòu)...
摘要:創(chuàng)建自定義的構(gòu)造函數(shù)之后,其原型對象只會(huì)取得屬性,其他方法都是從繼承來的。優(yōu)缺點(diǎn)寄生式繼承在主要考慮對象而不是創(chuàng)建自定義類型和構(gòu)造函數(shù)時(shí),是十分有用的。 原文鏈接:https://kongchenglc.coding.me... 1.原型鏈 ??js的繼承機(jī)制不同于傳統(tǒng)的面向?qū)ο笳Z言,采用原型鏈實(shí)現(xiàn)繼承,基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。理解原型鏈必須先理...
摘要:下面來看一個(gè)例子繼承屬性繼承方法在這個(gè)例子中構(gòu)造函數(shù)定義了兩個(gè)屬性和。組合繼承最大的問題就是無論什么情況下都會(huì)調(diào)用兩次超類型構(gòu)造函數(shù)一次是在創(chuàng)建子類型原型的時(shí)候另一次是在子類型構(gòu)造函數(shù)內(nèi)部。 組合繼承 組合繼承(combination inheritance),有時(shí)候也叫做偽經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長的一種繼承模式。其背后的思路是使用原型鏈...
摘要:寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部已某種方式來增強(qiáng)對象,最后再像真的是它做了所有工作一樣返回對象。 這篇本來應(yīng)該是作為寫JS 面向?qū)ο蟮那白?,只是作為《javascript高級程序設(shè)計(jì)》繼承一章的筆記 原型鏈 code 實(shí)現(xiàn) function SuperType() { this.colors = [red,blu...
摘要:因?yàn)檫@造成了繼承鏈的紊亂,因?yàn)榈膶?shí)例是由構(gòu)造函數(shù)創(chuàng)建的,現(xiàn)在其屬性卻指向了為了避免這一現(xiàn)象,就必須在替換對象之后,為新的對象加上屬性,使其指向原來的構(gòu)造函數(shù)。這個(gè)函數(shù)接收兩個(gè)參數(shù)子類型構(gòu)造函數(shù)和超類型構(gòu)造函數(shù)。 最近一直在研究js面向?qū)ο螅玩溊^承是一個(gè)難點(diǎn),下面是我對繼承的理解以下文章借鑒自CSDN季詩筱的博客 原型鏈繼承的基本概念: ES中描述了原型鏈的概念,并將原型鏈作為實(shí)現(xiàn)...
閱讀 2035·2021-11-22 19:20
閱讀 2675·2021-11-22 13:54
閱讀 2041·2021-09-04 16:40
閱讀 1861·2021-08-13 11:54
閱讀 2730·2019-08-30 15:55
閱讀 3493·2019-08-29 13:51
閱讀 553·2019-08-29 11:09
閱讀 3034·2019-08-26 14:06