摘要:這樣肯定不行,給添加方法或影響到這種方式有一個(gè)缺點(diǎn),在一個(gè)實(shí)例時(shí)會(huì)調(diào)用兩次構(gòu)造函數(shù)一次是,另一次是,浪費(fèi)效率,且如果構(gòu)造函數(shù)有副作用,重復(fù)調(diào)用可能造成不良后果。
寫在前面
此文只涉及基于原型的繼承,ES6之后基于Class的繼承請(qǐng)參考相關(guān)文獻(xiàn)。
知識(shí)儲(chǔ)備構(gòu)造函數(shù)的兩種調(diào)用方式(結(jié)果完全不同)
通過(guò)關(guān)鍵字new調(diào)用:
function Person(name) { this.name = name; this.age = 18; } var o = new Person("hx"); console.log(o.name, o.age); // hx 18 console.log(window.name, window.age); // "" undefined
直接調(diào)用:
function Person(name) { this.name = name; this.age = 18; } var o = Person("hx"); console.log(o); // undefined console.log(window.name, window.age); // hx 18
由此可見:
構(gòu)造函數(shù)與普通函數(shù)無(wú)異,可直接調(diào)用,無(wú)返回值,this指向Window;
通過(guò)new調(diào)用的話,返回值為一個(gè)對(duì)象,且this指向該對(duì)象
new到底做了什么?
new關(guān)鍵字會(huì)進(jìn)行如下操作:
創(chuàng)建一個(gè)空對(duì)象;
鏈接該對(duì)象到另一個(gè)對(duì)象(即:設(shè)置該對(duì)象的構(gòu)造函數(shù));
將第一步創(chuàng)建的空對(duì)象作為this的上下文(this指向該空對(duì)象);
執(zhí)行構(gòu)造函數(shù)(為對(duì)象添加屬性),并返回該對(duì)象
function Person(name) { this.name = name; this.age = 18; } var o = new Person("hx");
上述代碼對(duì)應(yīng)的四步操作是:
var obj = {};
obj.__proto__ = Person.prototype;
Person.call(obj,"hx");
return obj;
JavaScript實(shí)現(xiàn)繼承的幾種方式1.原型鏈繼承
function Parent(name) { this.name = name; this.age = 18; this.arr = ["hello","world"] } Parent.prototype.sayAge = function() { console.log(this.age) } function Child(gender) { this.gender = gender; } Child.prototype = new Parent(); var child1 = new Child("male"); child1.arr.push("js") console.log(child1.name); // undefined console.log(child1.age); // 18 console.log(child1.arr); // ["hello","world","js"] console.log(child1.gender); // male child1.sayAge(); // 18 var child2 = new Child("female"); console.log(child2.name); // undefined console.log(child2.age); // 18 console.log(child2.arr); // ["hello","world","js"] console.log(child2.gender); // female child2.sayAge(); // 18
優(yōu)點(diǎn):
Parent原型對(duì)象上的方法可以被Child繼承
缺點(diǎn):
Parent的引用類型屬性會(huì)被所有Child實(shí)例共享,互相干擾
Child無(wú)法向Parent傳參
2.構(gòu)造函數(shù)繼承(經(jīng)典繼承)
function Parent(name) { this.name = name; this.age = 18; this.arr = ["hello","world"]; this.sayName = function() { console.log(this.name) } } Parent.prototype.sayAge = function() { console.log(this.age) } function Child(name,gender) { Parent.call(this,name); // this由Window指向待創(chuàng)建對(duì)象 this.gender = gender; } var child1 = new Child("lala","male"); child1.arr.push("js"); console.log(child1.name); // lala console.log(child1.age); // 18 console.log(child1.arr); // ["hello","world","js"] console.log(child1.gender); // male child1.sayName(); // 18 child1.sayAge(); // Uncaught TypeError: child1.sayAge is not a function var child2 = new Child("fafa","female"); console.log(child2.name); // fafa console.log(child2.age); // 18 console.log(child2.arr); // ["hello","world"] console.log(child2.gender); // female child2.sayName(); // 18 child2.sayAge(); // Uncaught TypeError: child1.sayAge is not a function
優(yōu)點(diǎn):
避免了引用類型屬性被所有Child實(shí)例共享
Child可以向Parent傳參
缺點(diǎn):
Parent原型對(duì)象上的方法無(wú)法被Child繼承
每次創(chuàng)建Child實(shí)例都會(huì)創(chuàng)建sayName方法,造成內(nèi)存資源的浪費(fèi)
3.組合繼承
function Parent(name,age) { this.name = name; this.age = age; this.arr = ["hello","world"] } Parent.prototype.sayName = function() { console.log(this.name) } function Child(name,age,gender) { Parent.call(this,name,age); this.gender = gender } Child.prototype = Object.create(Parent.prototype); Child.prototype.constuctor = Child; Child.prototype.sayAge = function() { console.log(this.age) } var child1 = new Child("lala",18,"male"); child1.arr.push("js"); child1.name; // "lala" child1.age; // 18 child1.arr; // ["hello","world","js"] child1.gender; // "male" child1.sayName(); // lala child1.sayAge(); // 18 var child2 = new Child("fafa",28,"female"); child1.name; // "fafa" child1.age; // 28 child1.arr; // ["hello","world"] child1.gender; // "female" child1.sayName(); // fafa child1.sayAge(); // 28
補(bǔ)充 1組合繼承是JavaScript繼承的最佳實(shí)踐
屬性使用構(gòu)造函數(shù)繼承 - 避免了Parent引用屬性被多個(gè)Child實(shí)例影響,同時(shí)支持傳參
方法使用原型鏈繼承 - 支持Child繼承Parent原型對(duì)象方法,避免了多實(shí)例中方法的重復(fù)拷貝
對(duì)于組合繼承代碼中的Child.prototype = Object.create(Parent.prototype),還有兩種類型的方法:
Child.prototype = Parent.prototype或者Child.prototype = new Parent()。
Child.prototype = Parent.prototype:這樣肯定不行,給Child.prototype添加方法或影響到Parent;
Child.prototype = new Parent():這種方式有一個(gè)缺點(diǎn),在new一個(gè)Child實(shí)例時(shí)會(huì)調(diào)用兩次Parent構(gòu)造函數(shù)(一次是new Parent(),另一次是Parent.call(this,name)),浪費(fèi)效率,且如果Parent構(gòu)造函數(shù)有副作用,重復(fù)調(diào)用可能造成不良后果。
對(duì)于第二種情況,除了使用Object.create(Parent.prototype)這種方法外,還可以借助一個(gè)橋接函數(shù)實(shí)現(xiàn)。實(shí)際上,不管哪種方法,其實(shí)現(xiàn)思路都是調(diào)整原型鏈:
由:
new Child() ----> Child.prototype ----> Object.prototype ----> null
調(diào)整為:
new Child() ----> Child.prototype ----> Parent.prototype ----> Object.prototype ----> null
function Parent(name) { this.name = name } Parent.prototype.sayName = function() { console.log(this.name) } function Child(name,age) { Parent.call(this,name); this.age = age; } function F() { } F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constuctor = Child; Child.prototype.sayAge = function() { console.log(this.age) }
可見,通過(guò)一個(gè)橋接函數(shù)F,實(shí)現(xiàn)了只調(diào)用了一次 Parent 構(gòu)造函數(shù),并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的、多余的屬性
// 封裝一下上述方法 function object(o) { function F() {} F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); child.prototype = prototype; prototype.constructor = child; } // 當(dāng)我們使用的時(shí)候: prototype(Child, Parent);補(bǔ)充 2
什么是最優(yōu)的繼承方式?
其實(shí)不管是改良的組合繼承(使用 Object.create 也好,還是使用 Object.setPrototypeOf 也好),還是所謂的寄生組合繼承(使用橋接函數(shù)F),都不是回答該問(wèn)題的關(guān)鍵。
最優(yōu)的繼承方式體現(xiàn)的是一種設(shè)計(jì)理念:
不分靜態(tài)屬性還是動(dòng)態(tài)屬性,其維度的劃分標(biāo)準(zhǔn)是:是否可共享
對(duì)于每個(gè)子類都有,但子類實(shí)例相互獨(dú)立的屬性(非共享):應(yīng)該++放到父類的構(gòu)造方法上++,然后通過(guò)子類調(diào)用父類構(gòu)造方法來(lái)實(shí)現(xiàn)初始化;
對(duì)于每個(gè)子類都有,且子類實(shí)例可以共享的屬性(不管是靜態(tài)屬性還是動(dòng)態(tài)屬性):應(yīng)該++放到父類的原型對(duì)象上++,通過(guò)原型鏈獲得;
對(duì)于每個(gè)子類獨(dú)有,且子類實(shí)例相互獨(dú)立的屬性(非共享):應(yīng)該++放到子類的構(gòu)造方法上++實(shí)現(xiàn);
對(duì)于每個(gè)子類獨(dú)有,但子類實(shí)例可以共享的屬性:應(yīng)該++放到子類的原型對(duì)象上++,通過(guò)原型鏈獲得;
從文字上不容易理解,看代碼:
function Man(name,age) { // 每個(gè)子類都有,但相互獨(dú)立(非共享) this.name = name; this.age = age; } Man.prototype.say = function() { // 每個(gè)子類都有,且共享的動(dòng)態(tài)屬性(共享) console.log(`I am ${this.name} and ${this.age} years old.`) } // 每個(gè)子類都有,且共享的靜態(tài)屬性(共享) Man.prototype.isMan = true; function Swimmer(name,age,weight) { Man.call(this,name,age); // Swimmer子類獨(dú)有,且各實(shí)例獨(dú)立(非共享) this.weight = weight; } function BasketBaller(name,age,height) { Man.call(this,name,age); // BasketBaller子類獨(dú)有,且各實(shí)例獨(dú)立(非共享) this.height = height; } // 使用ES6直接設(shè)置原型關(guān)系的方法來(lái)構(gòu)建原型鏈 Object.setPrototypeOf(Swimmer.prototype, Man.prototype) // 等同于 Swimmer.prototype = Object.create(Man.prototype); Swimmer.prototype.constructor = Swimmer; Object.setPrototypeOf(BasketBaller.prototype, Man.prototype) // 等同于 BasketBaller.prototype = Object.create(Man.prototype); BasketBaller.prototype.constructor = BasketBaller; // 繼續(xù)擴(kuò)展子類原型對(duì)象 Swimmer.prototype.getWeight = function() { // Swimmer子類獨(dú)有,但共享的動(dòng)態(tài)屬性(共享) console.log(this.weight); } // Swimmer子類獨(dú)有,但共享的靜態(tài)屬性(共享) Swimmer.prototype.isSwimmer = true; var swimmer1 = new Swimmer("swimmer1",11,100); var swimmer2 = new Swimmer("swimmer2",21,200); swimmer1; // Swimmer?{name: "swimmer1", age: 11, weight: 100} swimmer1.isMan; // ture swimmer1.say(); // I am swimmer1 and 11 years old. swimmer1.isSwimmer; // ture swimmer1.getWeight(); // 100 swimmer2; // Swimmer?{name: "swimmer2", age: 21, weight: 200} swimmer2.isMan; // ture swimmer2.say(); // I am swimmer2 and 21 years old. swimmer2.isSwimmer; // ture swimmer2.getWeight(); // 200 // BasketBaller同理(略)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/106315.html
摘要:首先,需要來(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ù)人沒有想要深刻理解這個(gè)機(jī)制的內(nèi)涵,以及越來(lái)越多的開發(fā)者缺乏計(jì)算機(jī)編程相關(guān)的基礎(chǔ)知識(shí)。對(duì)于這樣的開發(fā)者來(lái)說(shuō) J...
摘要:我們有了構(gòu)造函數(shù)之后,第二步開始使用它構(gòu)造一個(gè)函數(shù)。來(lái)個(gè)例子這種方式很簡(jiǎn)單也很直接,你在構(gòu)造函數(shù)的原型上定義方法,那么用該構(gòu)造函數(shù)實(shí)例化出來(lái)的對(duì)象都可以通過(guò)原型繼承鏈訪問(wèn)到定義在構(gòu)造函數(shù)原型上的方法。 來(lái)源: 個(gè)人博客 白話解釋 Javascript 原型繼承(prototype inheritance) 什么是繼承? 學(xué)過(guò)面向?qū)ο蟮耐瑢W(xué)們是否還記得,老師整天掛在嘴邊的面向?qū)ο笕筇?..
摘要:這正是我們想要的太棒了毫不意外的,這種繼承的方式被稱為構(gòu)造函數(shù)繼承,在中是一種關(guān)鍵的實(shí)現(xiàn)的繼承方法,相信你已經(jīng)很好的掌握了。 你應(yīng)該知道,JavaScript是一門基于原型鏈的語(yǔ)言,而我們今天的主題 -- 繼承就和原型鏈這一概念息息相關(guān)。甚至可以說(shuō),所謂的原型鏈就是一條繼承鏈。有些困惑了嗎?接著看下去吧。 一、構(gòu)造函數(shù),原型屬性與實(shí)例對(duì)象 要搞清楚如何在JavaScript中實(shí)現(xiàn)繼承,...
摘要:使用構(gòu)造函數(shù)的原型繼承相比使用原型的原型繼承更加復(fù)雜,我們先看看使用原型的原型繼承上面的代碼很容易理解。相反的,使用構(gòu)造函數(shù)的原型繼承像下面這樣當(dāng)然,構(gòu)造函數(shù)的方式更簡(jiǎn)單。 五天之前我寫了一個(gè)關(guān)于ES6標(biāo)準(zhǔn)中Class的文章。在里面我介紹了如何用現(xiàn)有的Javascript來(lái)模擬類并且介紹了ES6中類的用法,其實(shí)它只是一個(gè)語(yǔ)法糖。感謝Om Shakar以及Javascript Room中...
摘要:繼承簡(jiǎn)介在的中的面向?qū)ο缶幊?,繼承是給構(gòu)造函數(shù)之間建立關(guān)系非常重要的方式,根據(jù)原型鏈的特點(diǎn),其實(shí)繼承就是更改原本默認(rèn)的原型鏈,形成新的原型鏈的過(guò)程。 showImg(https://segmentfault.com/img/remote/1460000018998684); 閱讀原文 前言 JavaScript 原本不是純粹的 OOP 語(yǔ)言,因?yàn)樵?ES5 規(guī)范中沒有類的概念,在 ...
摘要:的繼承方式屬于原型式繼承,非常靈活。當(dāng)使用關(guān)鍵字執(zhí)行類的構(gòu)造函數(shù)時(shí),系統(tǒng)首先創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象會(huì)繼承自構(gòu)造函數(shù)的原型對(duì)象新對(duì)象的原型就是構(gòu)造函數(shù)的屬性。也就是說(shuō),構(gòu)造函數(shù)用來(lái)對(duì)生成的新對(duì)象進(jìn)行一些處理,使這個(gè)新對(duì)象具有某些特定的屬性。 繼承這個(gè)東西在Javascript中尤其復(fù)雜,我掌握得也不好,找工作面試的時(shí)候在這個(gè)問(wèn)題上栽過(guò)跟頭。Javascript的繼承方式屬于原型式繼承,...
閱讀 3192·2021-11-24 09:39
閱讀 1058·2021-09-07 10:20
閱讀 2520·2021-08-23 09:45
閱讀 2394·2021-08-05 10:00
閱讀 657·2019-08-29 16:36
閱讀 904·2019-08-29 11:12
閱讀 2889·2019-08-26 11:34
閱讀 1891·2019-08-26 10:56