摘要:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承??偨Y(jié)實(shí)現(xiàn)繼承有種方式原型鏈繼承借用構(gòu)造函數(shù)繼承組合繼承原型式繼承寄生式繼承寄生組合式繼承寄生組合式繼承是大家公認(rèn)的最好的實(shí)現(xiàn)引用類型繼承的方法。
簡(jiǎn)介
本文不準(zhǔn)備深入細(xì)節(jié),主要是對(duì)《JavaScript高級(jí)程序設(shè)計(jì)中》介紹的JS如何實(shí)現(xiàn)繼承做一個(gè)總結(jié),畢竟好記性不如爛筆頭。文末會(huì)附帶一張神圖,搞清楚這張圖,原型鏈也就沒(méi)有什么問(wèn)題了。
ES5實(shí)現(xiàn)繼承的六種方式 1. 原型鏈基本思想:
利用原型鏈讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; // 子類 SubType function SubType () { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subProperty; }; // 實(shí)例 var instance = new SubType(); console.log(instance); console.log(instance.getSuperValue()); // true console.log(instance instanceof SubType); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof Object); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(Object.prototype.isPrototypeOf(instance)); // true
缺點(diǎn):
1. 來(lái)自原型對(duì)象的引用屬性是所有實(shí)例共享的。
2. 創(chuàng)建子類實(shí)例時(shí),無(wú)法向父類構(gòu)造函數(shù)傳參。
舉例如下:
// 1. 來(lái)自原型對(duì)象的引用屬性是所有實(shí)例共享的 // 父類 function SuperType () { this.colors = ["red", "blue", "green"]; } // 子類 function SubType () { } SubType.prototype = new SuperType(); // 實(shí)例 var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType(); console.log(instance2.colors); // ["red", "blue", "green", "black"] // 因?yàn)樾薷腸olors是修改的SubType.prototype.colors,所以所有的實(shí)例都會(huì)更新
// 2. 創(chuàng)建子類實(shí)例時(shí),無(wú)法向父類構(gòu)造函數(shù)傳參 // 調(diào)用父類是在 SubType.prototype = new SuperType() // 新建子類實(shí)例調(diào)用 new SubType() // 所以無(wú)法再new SubType() 的時(shí)候給父類 SuperType() 傳參2. 借用構(gòu)造函數(shù)
基本思想:
在子類構(gòu)造函數(shù)的內(nèi)部通過(guò)call()以及apply()調(diào)用父類構(gòu)造函數(shù)。
// 父類 SuperType function SuperType (name) { this.name = name; this.colors = ["red", "blue", "green"]; this.getName = function () { return this.name; } } // 子類 function SubType (name) { // 繼承了SuperType,同時(shí)還傳遞了參數(shù) SuperType.call(this, name); // 實(shí)例屬性 this.age = 20; } // 實(shí)例 var instance1 = new SubType("Tom"); instance1.colors.push("black"); console.log(instance1.name); // "Tom" console.log(instance1.getName()); // "Tom" console.log(instance1.age); // 20 console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter"); console.log(instance2.name); // "Peter" console.log(instance2.getName()); // "Peter" console.log(instance2.age); // 20 console.log(instance2.colors); // ["red", "blue", "green"]
可以看到,借用構(gòu)造函數(shù)實(shí)現(xiàn)繼承,解決了原型鏈繼承的兩個(gè)問(wèn)題,既可以在新建子類實(shí)例的時(shí)候給父類構(gòu)造函數(shù)傳遞參數(shù),也不會(huì)造成子類實(shí)例共享父類引用變量。
但是你注意到了嗎,這里我們把父類方法也寫在了SuperType()構(gòu)造函數(shù)里面,可以像前面一樣寫在SuperType.prototype上嗎?
答案是不可以,必須寫在SuperType()構(gòu)造函數(shù)里面。因?yàn)檫@里是通過(guò)調(diào)用SuperType.call(this)來(lái)實(shí)現(xiàn)繼承的,并沒(méi)有通過(guò)new生成一個(gè)父類實(shí)例,所以如果寫在prototype上,子類是無(wú)法拿到的。
缺點(diǎn):
1. 如果方法都在構(gòu)造函數(shù)中定義,那么就無(wú)法復(fù)用函數(shù)。每次構(gòu)建實(shí)例時(shí)都會(huì)在實(shí)例中保留方法函數(shù),造成了內(nèi)存的浪費(fèi),同時(shí)也無(wú)法實(shí)現(xiàn)同步更新,因?yàn)槊總€(gè)實(shí)例都是多帶帶的方法函數(shù)。如果方法寫在prototype上,就只會(huì)有一份,更新時(shí)候會(huì)做到同步更新。
3. 組合繼承基本思想:
將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。
使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。
// 父類 function SuperType (name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); } // 子類 function SubType (name, age) { // 繼承父類實(shí)例屬性 SuperType.call(this, name); // 子類實(shí)例屬性 this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); }; // 實(shí)例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] instance1.sayName(); // "Tom" instance1.sayAge(); // 20 var instance2 = new SubType("Peter", 30); console.log(instance2.colors); // ["red", "blue", "green"] instance2.sayName(); // "Peter" instance2.sayAge(); // 30
缺點(diǎn):
1. 調(diào)用了兩次父類構(gòu)造函數(shù),一次通過(guò)SuperType.call(this)調(diào)用,一次通過(guò)new SuperType()調(diào)用。
4. 原型式繼承基本思想:
不使用嚴(yán)格意義上的構(gòu)造函數(shù),借助原型可以基于已有的對(duì)象創(chuàng)建新的對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。
// 在object函數(shù)內(nèi)部,先創(chuàng)建了一個(gè)臨時(shí)的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回這個(gè)臨時(shí)類型的一個(gè)新實(shí)例。 // 從本質(zhì)上講,object()對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制。 function object (o) { function F() {} F.prototype = o; return new F(); } var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(yetAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
ECMAScript5中新增了一個(gè)方法Object.create(prototype, descripter)接收兩個(gè)參數(shù):
prototype(必選),用作新對(duì)象的原型對(duì)象
descripter(可選),為新對(duì)象定義額外屬性的對(duì)象
在傳入一個(gè)參數(shù)的情況下,Object.create()與前面寫的object()方法的行為相同。
var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person, { name: { value: "Linda", enumerable: true } }); yetAnotherPerson.friends.push("Barbie"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(yetAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
缺點(diǎn):
1. 和原型鏈繼承一樣,所有子類實(shí)例共享父類的引用類型。
5. 寄生式繼承基本原理:
寄生式繼承是與原型式繼承緊密相關(guān)的一種思路,創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)內(nèi)部以某種形式來(lái)做增強(qiáng)對(duì)象,最后返回對(duì)象。
function object (o) { function F() {} F.prototype = o; return new F(); } function createAnother (o) { var clone = object(o); clone.sayHi = function () { console.log("Hi"); } return clone; } var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi" anotherPerson.friends.push("Rob"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob"] var yerAnotherPerson = createAnother(person); console.log(yerAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob"]
缺點(diǎn):
1. 和原型鏈?zhǔn)嚼^承一樣,所有子類實(shí)例共享父類引用類型。
2. 和借用構(gòu)造函數(shù)繼承一樣,每次創(chuàng)建對(duì)象都會(huì)創(chuàng)建一次方法。
6. 寄生組合式繼承基本思想:
將寄生式繼承和組合繼承相結(jié)合,解決了組合式繼承中會(huì)調(diào)用兩次父類構(gòu)造函數(shù)的缺點(diǎn)。
組合繼承是JavaScript最常用的繼承模式,它最大的問(wèn)題就是無(wú)論在什么情況下,都會(huì)調(diào)用兩次父類構(gòu)造函數(shù):一次是在創(chuàng)建子類原型的時(shí)候,另一次是在子類構(gòu)造函數(shù)內(nèi)部。
// 組合繼承 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); //第二次調(diào)用 SuperType() this.age = age; } SubType.prototype = new SuperType(); //第一次調(diào)用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { alert(this.age); };
組合繼承在第一次調(diào)用SuperType構(gòu)造函數(shù)時(shí),SubType.prototype會(huì)得到兩個(gè)屬性:name和colors;它們都是 SuperType 的實(shí)例屬性,只不過(guò)現(xiàn)在位于 SubType的原型中。當(dāng)調(diào)用SubType構(gòu)造函數(shù)時(shí),又會(huì)調(diào)用一次SuperType構(gòu)造函數(shù),這一次又在新對(duì)象上創(chuàng)建了實(shí)例屬性name和colors。于是,這兩個(gè)屬性就屏蔽了原型中的兩個(gè)同名屬性。
所謂寄生組合式繼承,即通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法。
其背后的基本思路是:不必為了指定子類型的原型而調(diào)用父類的構(gòu)造函數(shù),我們需要的無(wú)非就是父類原型的一個(gè)副本而已。本質(zhì)上,就是使用寄生式繼承來(lái)繼承父類的prototype,然后再將結(jié)果指定給子類的prototype。
寄生組合式繼承的基本模型如下:
function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 創(chuàng)建對(duì)象 prototype.constructor = SubType; // 增強(qiáng)對(duì)象 SubType.prototype = prototype; // 指定對(duì)象 }
實(shí)現(xiàn)一個(gè)完整的寄生組合式繼承:
function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 創(chuàng)建對(duì)象 prototype.constructor = SubType; // 增強(qiáng)對(duì)象 SubType.prototype = prototype; // 指定對(duì)象 } // 父類 function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; // 子類 function SubType(name, age) { // 繼承父類實(shí)例屬性 SuperType.call(this, name); // 子類實(shí)例屬性 this.age = age; } // 繼承父類方法 inheritPrototype(SubType, SuperType); // 子類方法 SubType.prototype.sayAge = function () { console.log(this.age); }; // 實(shí)例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter", 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
寄生組合式繼承的高效率體現(xiàn)在它只調(diào)用了一次SuperType構(gòu)造函數(shù),并且因此避免了再SubType.prototype上面創(chuàng)建不必要的、多余的屬性。與此同時(shí),原型鏈還能保持不變。因此,還能夠正常使用instanceof和isPrototypeOf()。
開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承方式。
ES6實(shí)現(xiàn)繼承// 父類 class SuperType { constructor(name) { this.name = name; this.colors = ["red", "blue", "green"]; } sayName() { console.log(this.name); }; } // 子類 class SubType extends SuperType { constructor(name, age) { // 繼承父類實(shí)例屬性和prototype上的方法 super(name); // 子類實(shí)例屬性 this.age = age; } // 子類方法 sayAge() { console.log(this.age); } } // 實(shí)例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter", 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
用ES6的語(yǔ)法來(lái)實(shí)現(xiàn)繼承非常的簡(jiǎn)單,下面是把這段代碼放到Babel里轉(zhuǎn)碼的結(jié)果圖片:
可以看到,底層其實(shí)也是用寄生組合式繼承來(lái)實(shí)現(xiàn)的。
總結(jié)ES5實(shí)現(xiàn)繼承有6種方式:
原型鏈繼承
借用構(gòu)造函數(shù)繼承
組合繼承
原型式繼承
寄生式繼承
寄生組合式繼承
寄生組合式繼承是大家公認(rèn)的最好的實(shí)現(xiàn)引用類型繼承的方法。
ES6新增class和extends語(yǔ)法,用來(lái)定義類和實(shí)現(xiàn)繼承,底層也是采用了寄生組合式繼承。
附圖:
歡迎關(guān)注我的公眾號(hào)文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97151.html
摘要:中的繼承并不是明確規(guī)定的,而是通過(guò)模仿實(shí)現(xiàn)的。繼承中的繼承又稱模擬類繼承。將函數(shù)抽離到全局對(duì)象中,函數(shù)內(nèi)部直接通過(guò)作用域鏈查找函數(shù)。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質(zhì)區(qū)別是屬性查找方式的不同。 這一節(jié)梳理對(duì)象的繼承。 我們主要使用繼承來(lái)實(shí)現(xiàn)代碼的抽象和代碼的復(fù)用,在應(yīng)用層實(shí)現(xiàn)功能的封裝。 javascript 的對(duì)象繼承方式真的是百花齊放,屬性繼承、原型繼承、...
摘要:和構(gòu)造函數(shù)前面提到,是個(gè)內(nèi)置隱藏屬性,雖然在可以通過(guò)訪問(wèn),但是其設(shè)計(jì)本意是不可被讀取和修改的,那么我們?nèi)绾卫迷玩渷?lái)建立繼承關(guān)系提供了關(guān)鍵字。到這兒,思路就清晰了,怎么讓對(duì)象和對(duì)象的相連實(shí)現(xiàn)繼承只需把的構(gòu)造函數(shù)的連接到就行了。 什么是繼承? 大多數(shù)人使用繼承不外乎是為了獲得這兩點(diǎn)好處,代碼的抽象和代碼的復(fù)用。代碼的抽象就不用說(shuō)了,交通工具和汽車這類的例子數(shù)不勝數(shù),在傳統(tǒng)的OO語(yǔ)言中(...
摘要:首先,需要來(lái)理清一些基礎(chǔ)的計(jì)算機(jī)編程概念編程哲學(xué)與設(shè)計(jì)模式計(jì)算機(jī)編程理念源自于對(duì)現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過(guò)程式和函數(shù)式編程。 JavaScript 中的原型機(jī)制一直以來(lái)都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因?yàn)榻^大多數(shù)人沒(méi)有想要深刻理解這個(gè)機(jī)制的內(nèi)涵,以及越來(lái)越多的開發(fā)者缺乏計(jì)算機(jī)編程相關(guān)的基礎(chǔ)知識(shí)。對(duì)于這樣的開發(fā)者來(lái)說(shuō) J...
摘要:前言作為中最重要的內(nèi)容之一,繼承問(wèn)題一直是我們關(guān)注的重點(diǎn)。如果一個(gè)類別繼承自另一個(gè)類別,就把這個(gè)稱為的子類,而把稱為的父類別也可以稱是的超類。 前言 作為 JavaScript 中最重要的內(nèi)容之一,繼承問(wèn)題一直是我們關(guān)注的重點(diǎn)。那么你是否清晰地知道它的原理以及各種實(shí)現(xiàn)方式呢 閱讀這篇文章,你將知道: 什么是繼承 實(shí)現(xiàn)繼承有哪幾種方式 它們各有什么特點(diǎn) 這里默認(rèn)你已經(jīng)清楚的知道構(gòu)造函...
摘要:繼承前言作為一門輕量級(jí)的腳本語(yǔ)言在和的橫空出世之后將其推向的新的高度雖然中出現(xiàn)的新的生成對(duì)象的類語(yǔ)法格式但依然為的語(yǔ)法糖而我們依然有必要從的原生實(shí)現(xiàn)入手來(lái)了解它的繼承實(shí)現(xiàn)方式給出了更加簡(jiǎn)潔的固定的類聲明方式有興趣的可以查看阮一峰的入門下面給 javascript繼承 前言 javascript作為一門輕量級(jí)的腳本語(yǔ)言在ES6和node.js的橫空出世之后將其推向的新的高度,雖然 ES6...
閱讀 3021·2023-04-26 02:14
閱讀 3839·2019-08-30 15:55
閱讀 1926·2019-08-29 16:42
閱讀 2833·2019-08-26 11:55
閱讀 2927·2019-08-23 13:38
閱讀 557·2019-08-23 12:10
閱讀 1371·2019-08-23 11:44
閱讀 2976·2019-08-23 11:43