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