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