摘要:原型鏈和構造函數是一種面向對象的語言,并且可以進行原型繼承。來了極大的支持了工程化,它的標準讓瀏覽器內部實現類和類的繼承構造函數構造函數調用父類構造函數現在瀏覽器對其支持程度還不高。
原型鏈
原型鏈比作用域鏈要好理解的多。
JavaScript中的每個對象,都有一個內置的_proto_屬性。這個屬性是編程不可見的(雖然ES6標準中開放了這個屬性,然而瀏覽器對這個屬性的可見性的支持不同),它實際上是對另一個對象或者null的引用。
當一個對象需要引用一個屬性時,JavaScript引擎首先會從這個對象自身的屬性表中尋找這個屬性標識,如果找到則進行相應讀寫操作,若沒有在自身的屬性表中找到,則在_proto_屬性引用的對象的屬性表中查找,如此往復,直到找到這個屬性或者_proto_屬性指向null為止。
這個_proto_的引用鏈,被稱作原型鏈。
注意,此處有一個性能優化的問題:往原型鏈越深處搜索,耗費的時間越多。
原型鏈和構造函數JavaScript是一種面向對象的語言,并且可以進行原型繼承。
JavaScript中的函數有一個屬性prototype,這個prototype屬性是一個對象,它的一個屬性constructor引用該函數自身。即:
func.prototype.constructor === func; // ==> true
這個屬性有什么用呢?我們知道一個,一個函數使用new操作符調用時,會作為構造函數返回一個新的對象。這個對象的_proto_屬性引用其構造函數的prototype屬性。
因此這個就不難理解了:
var obj = new Func(); obj.constructor == Func; // ==> true
還有這個:
obj instanceof Func; // ==> true
也是通過查找原型鏈上的constructor屬性實現的。
構造函數生成的不同實例的_proto_屬性是對同一個prototype對象的引用。所以修改prototype對象會影響所有的實例。
“子類”繼承實現的幾種方式之所以子類要加引號,是因為這里說“類”的概念是不嚴謹的。JavaScript是一門面向對象的語言,但是它跟Java等語言不同,在ES6標準出爐之前,它是沒有類的定義的。
但是熟悉Java等語言的程序員,也希望使用JavaScript時,跟使用Java相似,通過類生成實例,通過子類復用代碼。那么在ES6之前,怎么做到像如下代碼一樣使用類似"類"的方式呢?
var parent = new Parent("Sam"); var child = new Children("Samson"); parent.say(); // ==> "Hello, Sam!" child.say(); // ==> "Hello, Samson! hoo~~" child instanceof Parent; // ==> true
我們看到,這里我們把構造函數當做類來用。
以下我們討論一下實現的幾種方式:
最簡單的方式結合原型鏈的概念,我們很容易就能寫出這樣的代碼:
function Parent(name){ this.name = name; } Parent.prototype.say = function(){ console.log("Hello, " + this.name + "!"); } function Children(name){ this.name = name; } Children.prototype = new Parent(); Children.prototype.say = function(){ console.log("Hello, " + this.name + "! hoo~~"); }
這個方式缺點很明顯:作為子類的構造函數需要依賴一個父類的對象。這個對象中的屬性name根本毫無用處。
第一次改進// ... Children.prototype = Parent.prototype; // ...
這樣就不會產生無用的父類屬性了。
然而,這樣的話子類和父類的原型就引用了同一個對象,修改子類的prototype也會影響父類的原型。
這時候我們發現:
parent.say(); // ==> "Hello,Sam! hoo~~"
這第一次改進還不如不改。
第二次改進——臨時構造函數/Object.createfunction F(){ // empty } F.prototype = Parent.prototype; Children.prototype = new F(); // ... parent.say(); // ==> "Hello, Sam!" child.say(); // ==> "Hello, Samson! hoo~~"
這樣一來,修改子類的原型只是修改了F的一個實例的屬性,并沒有改變Parent.prototype,從而解決了上面的問題。
在ES5的時代,我們還可以直接這樣:
Children.prototype = Object.create(Parent.prototype);
這里的思路是一樣的,都是讓子類的prototype不直接引用父類prototype。目前的現代瀏覽器幾乎已經添加了對這個方法的支持。(但我們下面會仍以臨時構造函數為基礎)
但是細細思考,這個方案仍有需要優化的地方。例如:如何讓父類的構造函數邏輯直接運用到子類中,而不是再重新寫一遍一樣的?這個例子中只有一個name屬性的初始化,那么假設有很多屬性且邏輯一樣的話,豈不是沒有做到代碼重用?
第三次改進——構造函數方法借用使用apply,實現“方法重用”的思想。
function Children(name){ Parent.apply(this, arguments); // do other initial things }“圣杯”模式
現在完整的代碼如下:
function Parent(name){ this.name = name; } Parent.prototype.say = function(){ console.log("Hello, " + this.name + "!"); } function Children(name){ Parent.apply(this, arguments); // do other initial things } function F(){ // empty } F.prototype = Parent.prototype; Child.prototype = new F(); Children.prototype.say = function(){ console.log("Hello, " + this.name + "! hoo~~"); }
這就是所謂“圣杯”模式,聽著很高大上吧?
以上就是ES3的時代,我們用來實現原型繼承的一個近似最佳實踐。
“圣杯”模式的問題“圣杯”模式依然存在一個問題:雖然父類和子類實例的繼承的prototype對象不是同一個實例,但是這兩個prototype對象上面的屬性引用了同樣的對象。
假設我們有:
Parent.prototype.a = { x: 1}; // ...
那么即使是“圣杯”模式下,依然會有這樣的問題:
parent.x // ==> 1 child.x // ==> 1 child.x = 2; parent.x // ==>2
問題在于,JavaScript的拷貝不是 深拷貝(deepclone)
要解決這個問題,我們可以利用屬性遞歸遍歷,自己實現一個深拷貝的方法。這個方法在這里我就不寫了。
ES6來了ES6極大的支持了工程化,它的標準讓瀏覽器內部實現類和類的繼承:
class Parent { constructor(name) { //構造函數 this.name = name; } say() { console.log("Hello, " + this.name + "!"); } } class Children extends Parent { constructor(name) { //構造函數 super(name); //調用父類構造函數 // ... } say() { console.log("Hello, " + this.name + "! hoo~~"); } }
現在瀏覽器對其支持程度還不高。但是這種寫法的確省力不少。讓我們對未來拭目以待。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90857.html
摘要:原文地址詳解的類博主博客地址的個人博客從當初的一個彈窗語言,一步步發展成為現在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向對象編程中,類是對象的模板,定義了同一組對象又稱實例共有的屬性和方法。這個等同于的屬性現已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個人博客 ??Javascript從當初的一個彈窗語言,一...
摘要:原文地址詳解的類博主博客地址的個人博客從當初的一個彈窗語言,一步步發展成為現在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向對象編程中,類是對象的模板,定義了同一組對象又稱實例共有的屬性和方法。這個等同于的屬性現已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個人博客 ??Javascript從當初的一個彈窗語言,一...
摘要:接下來我們來聊一下的原型鏈繼承和類。組合繼承為了復用方法,我們使用組合繼承的方式,即利用構造函數繼承屬性,利用原型鏈繼承方法,融合它們的優點,避免缺陷,成為中最常用的繼承。 JavaScript是一門面向對象的設計語言,在JS里除了null和undefined,其余一切皆為對象。其中Array/Function/Date/RegExp是Object對象的特殊實例實現,Boolean/N...
摘要:原型鏈理解第一件事你不用管其他語言,一句話,你只要記住里面的對象包含一個原型,原型是啥,就是另外一個對象。原型就相當于你家的車棚子,而你的那個自行車就是對象。萬事萬物皆對象有啥用一句話,擴展原型方法,給大家一到面試題,數組去重自己體會下。 概述 通過上節課的學習,大家已經會用一種json的方式定義對象了,其實這個就是傳說中的單體模式,當然這個大家不用記,關于設計模式暫時不用了解。但是總...
閱讀 917·2021-10-27 14:14
閱讀 1741·2021-10-11 10:59
閱讀 1315·2019-08-30 13:13
閱讀 3152·2019-08-29 15:17
閱讀 2750·2019-08-29 13:48
閱讀 488·2019-08-26 13:36
閱讀 2082·2019-08-26 13:25
閱讀 857·2019-08-26 12:24