摘要:不必在構造函數中定義對象實例的信息。其次,按照一切事物皆對象的這餓極本的面向對象的法則來說,類本身并不是一個對象,然而原型方式的構造函數和原型本身也是個對象。第二個問題就是在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
前言
對象(Object)應該算是js中最為重要的部分,也是js中非常難懂晦澀的一部分。更是面試以及框架設計中各出沒。寫這篇文章,主要參考與JavaScript紅寶書(JavaScript高級程序設計 第六章章節)以及各大博主博客。
原文地址:https://github.com/Nealyang/YOU-SHOULD-KNOW-JS
畢竟是面向對象編程,我們在討論如何面向對象之前先討論討論對象具有哪些屬性和特性。
屬性類型簡單的說,對象擁有四個屬性:
Configurable :是否可以通過delete刪除,能否修改屬性的特性。直白點:是否可配置
Enumerable :枚舉性,表示是否可以通過for-in循環返回
Writable :可寫性:是否可以修改屬性的值
Value :包含屬性的值,也就是對應的可讀性。
以上四個對象的屬性的屬性類型默認值分別為:true,true,true,undefined。
如果要修改屬性默認的特性,必須通過Object.defineProperty()方法。大致如下:
var animal = {}; Object.defineProperty(animal,"name",{ writable:false, value: "dog"; }); console.log(animal.name);//dog animal.name = "cat"; console.log(animal.name);//dog
從上面的實例大家也能看出,在調用Object.defineProperty()方法后,如果不指定 configurable、enumerable、writable 特性的值時,默認為FALSE。
訪問器屬性訪問器屬性不包含數據值,但是包含getter和setter函數。在讀取訪問器屬性時,會調用getter函數,這個函數負責返回有效值。在寫入訪問器屬性時,回到用setter函數并傳入新值。
Configurable:表示是否可以通過delete刪除。默認為TRUE
Enumerable:同上面介紹的Enumerable一樣,默認為true
Get:讀取數據時候調用的方法。默認為undefined
Set:在寫入屬性值得時候默認調用的方法。默認為undefined
這里不做過多解釋,直接看例子吧(來自js紅寶書)
var book = { _year:2012, edition:1 }; Object.defineProperty(book, "year",{ get:function(){ return this._year }, set:function(value){ if(value>2012){ this._year = value, this.edition++ } } }); book.year = 2013; console.log(book.edition);//2
其實對于多個屬性的定義,我們可以使用Object.defineProperties方法。然后對于讀取屬性的特性我們可以使用Object.getOwnPropertyDescriptor()方法。大家自行查看哈。
創建對象創建對象,我們不是直接可以通過Object的構造函數或者對象字面量的方法來實現對象的創建嘛?當然,這些方法是可以的,但是有一個明顯的缺點:使用同一個接口創建很多對象,產生大量重復的代碼。所以這里,我們使用如下的一些騷操作
工廠模式一種很基礎的設計模式,簡而言之就是用函數來封裝以特定接口創建對象的細節。
function createAnimal(name,type){ var o = new Object(); o.name = name; o.type = type; o.sayName = function(){ alert(this.name) } return o; } var cat = createAnimal("小貓","cat"); var dog = createAnimal("小?","dog");
優點:可以無數次的調用這個函數,來創建相似對象。
缺點:不能解決對象識別的問題。也就是說,我不知道你是誰家的b孩子
ECMAScript中的構造函數可以用來創建特定類型的對象。在運行時會自動出現在執行環境中(這句話后面講解this的時候還是會說到)。
function Animal(name,type){ this.name = name; this.type = type; this.say = function(){ alert(this.name); } } var cat = new Animal("小貓","cat"); var dog = new Animal("小?","dog");
注意上面我們沒有顯示的return過一個對象出來,為什么?因為this(后面會講this的)。
關于構造函數慣例首字母大寫就不啰嗦了。強調構造函數一定要使用關鍵字new來調用。為什么使用new呢?因為你使用了new,他會
創建一個新的對象
將構造函數的作用域賦值給新對象(this執行新的對象)
執行構造函數的代碼
返回新的對象
那么解決了工廠模式的詬病了么?當然~
在實例對象中,都有一個constructor屬性。
cat.constructor == Animal //true dog.constructor == Animal //true cat instanceof Animal //true dog instanceof Animal //true
構造函數模式的優點如上所說,但是缺點還是有的,比如說
cat.sayName == dog.sayName //false
也就是說,他創建了兩個功能一樣的函數,這樣是很沒有必要的,當然,我們可以把sayName放到構造函數外面,然后通過this.sayName=sayName來操作,但是這樣的話,又會導致全局變量的污染。腫么辦???
原型模式我們在創建每一個函數的時候都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象。而這個對象的用途就是包含由特定類型的所有實例共享的屬性和方法。
function Animal() {} Animal.prototype.name = "毛毛"; Animal.prototype.type = "dog"; Animal.prototype.sayName = function() { alert(this.name); } var cat = new Animal(); var dog = new Animal(); alert(cat.sayName == dog.sayName)//true
原型模式的好處就是可以讓所有的對象實例共享他的屬性和方法。不必在構造函數中定義對象實例的信息。
function Person() {} Person.prototype.name = "Nealyang"; Person.prototype.age = 24; Person.prototype.sayName = function(){ alert(this.name); } var neal = new Person(); console.log(neal.name)//"Nealyang" -> 來自原型 neal.name = "Neal"; console.log(neal.name)// Neal -> 來自實例 delete neal.name; console.log(neal.name)//"Nealyang" -> 來自原型
上面的例子說明兩點
原型中的對象屬性可以被實例所覆蓋重寫
通過delete可以刪除實例中的屬性,但是刪除不了對象上的
我們可以通過hasOwnProperty()方法來確定一個屬性是在原型上還是在實例上。person1.hasOwnProperty("name"),如果name為實例屬性,則返回true。
我們也可以通過 "name" in person1 來確定,person1上是否有name這個屬性。
上面大家可能已將發現,這種原型模式的寫法非常的繁瑣,有了大量的XXX.prototype. 這里有一種簡寫的形式。
參照具體說明參照阮神的博客 面向對象第二篇
function Person(){} Person.prototype = { constructor:Person, name:"Neal", age:24, job:"Software Engineer", sayName:function(){ alert(this.name); } }
上面代碼特意添加了一個constructor屬性,因為每創建一個函數,就會自動創建他的prototype對象,這個對象會自動獲取contractor屬性。而我們這中寫法,本質上重寫了默認的prototype對象,因此,constructor屬性也就變成新的對象的constructor屬性了(指向Object構造函數),所以這里的簡寫方式,一定要加上constructor。
下面我們再談一談原型模式的優缺點。
優點,正如上面我們說到的,可以省略為構造函數傳遞出實話參數這個環節,并且很多實例可以共享屬性和方法。正是因為原型中所有的屬性是被所有的實例所共享的,這個特性在方法中非常實用,但是對于包含引用類型的屬性來說問題就比較突出了。
function Person(){}; Person.prototype = { constructor:Person, name:"neal", friends:["xiaohong","xiaoming"], sayName:function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("xiaohua"); alert(person1.friends);//"xiaohong","xiaoming","xiaohua" alert(person2.friends);//"xiaohong","xiaoming","xiaohua" alert(person1.friends == person2.friends)//true
由于friends數組存在于Person.prototype上,并不是person1上面,所以當我們修改的時候,其實修改的是所有實例所共享的那個值。
組合使用構造函數和原型模式這是創建自定義類型最常見的一種方式。就是組合使用構造函數和原型模式.構造函數模式用于定義實力屬性,原型模式用于定義方法和共享的屬性。
function Person(name,age){ this.name = name, this.age = age } Person.prototype = { constructor:Person, sayName:function(){ alert(this.name); } } var person1 = new Person("Neal",24); var person2 = new Person("Yang",23); ...
上面的例子中,實例所有的屬性都是在構造函數中定義,而實例所有共享的屬性和方法都是在原型中定義。這種構造函數和原型模式混合的模式,是目前ECMAScript中使用最為廣泛的一種方法。
當然,有些人會覺得獨立的構造函數和原型非常的難受,所以也有推出所謂的動態原型構造模式的這么一說。
function Person(name,age){ this.name = name, this.age = age, if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ console.log(this.name) } } } ...
注意上面的代碼,之后在sayName不存在的時候,才會在原型上給他添加相應的方法。因為對原型的修改,能夠立即在所有的實例中得到反應。所以這中做法確實也是非常的完美。
關于javaScript高程中說到的別的寄生構造函數模式和穩妥構造函數模式大家可以自行查看哈~這里就不做過多介紹了。
繼承說到面向對象,當然得說到繼承。說到繼承當然得說到原型。說到原型,這里我們摘自網上一篇博客里的段落
為了說明javascript是一門面向對象的語言,首先有必要從面相對象的概念入手1、一切事物皆對象。2、對象具有封裝和繼承特性。3、對象與對象之間使用消息通信,各自存在信息隱秘 。
javascript語言是通過一種叫做原型(prototype) 的方式來實現面向對象編程的。當然,還有比如java就是基于類來實現面向對象編程的。
基于類的面向對象和基于原型的面向對象方式比價對于基于類的面向對象的方式中,對象依靠class類來產生。而在基于原型的面向對象方式中,對象則是依靠構造器(constructor)利用原型(prototype)構造出來的。舉個客觀世界的例子來說,例如工廠造一輛汽車一方面,工人必須參照一張工程圖紙,設計規定這輛車如何制造,這里的工程圖紙就好比語言中的類class。而車就是按照這個類制造出來的。另一方面,工人和機器相當于contractor,利用各種零部件(prototype)將汽車造出來。
當然,對于上面的例子兩種思維各種說法。當然,筆者更加傾向于基于原型的面向對象編程,畢竟我是前端出生(咳咳,真相了),正當理由如下:
首先,客觀世界中的對象的產生都是其他實物對象構造的世界,而抽象的圖紙是不能產生出汽車的。也就是說,類,是一個抽象概念的而非實體,而對象的產生是一個實體的產生。其次,按照一切事物皆對象的這餓極本的面向對象的法則來說,類本身并不是一個對象,然而原型方式的構造函數和原型本身也是個對象。再次,在類的面向對象語言中,對象的狀態又對象的實例所持有,對象的行為方法則由申明該對象的類所持有,并且只有對象的構造和方法能夠被繼承。而在原型的面向對象語言中,對象的行為、狀態都屬于對象本身,并且能夠一起被繼承。
原型鏈ECMAScript描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
實現原型鏈有一種基本模式:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType (){ this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue());
在上面的代碼中,我們沒有使用SubType默認提供的原型,而是給它換了一個新的原型,這個新原型就是SuperType的實例。于是,新原型不僅具有所謂一個SuperType的實例所擁有的全部屬性和方法,而且其內部還有一個指針,指向SuperType的原型。最終結果是這樣的:instance指向subtype的原型,subtype的原型又指向SuperType的原型。
通過實現原型鏈,本質上是擴展了原型搜索機制。
雖然如上,我們已經實現了javascript中的繼承。但是依舊存在一些問題:最主要的問題來自包含引用類型的原型。第二個問題就是在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。這兩個問題上面也都有說到,這里就不做過多介紹,直接看解決辦法!
借用構造函數在解決原型中包含引用類型的數據時,我們可以在子類型構造函數內部調用超類型的構造函數。直接看代碼:
function SuperType(name){ this.colors = ["red","yellow"]; this.name = name; } function SubType(name){ //繼承了Super SuperType.call(this,name) } var instance1 = new SubType("Neal"); alert(instance1.name) instance1.colors.push("black"); alert(instance1.colors);//"red","yellow","black" var instance2 = new SubType("yang"); alert(instance2.colors);//"red","yellow"
畢竟函數只不過是在特定環境中執行代碼的對象,因此可以通過call活著apply方法在新創建的對象上執行構造函數。而且如上代碼也解決了子類構造函數中向超類構造函數傳遞參數的問題
但是,這樣問題就來了,類似我們之前討論創建的對象那種構造函數的問題:如果都是使用構造函數,那么,也就避免不了方法都在構造函數中定義,然后就會產生大量重復的代碼了。
組合繼承因為考慮到上述的缺點,所以這里又使用了組合繼承的方式,歷史總是驚人的相似。直接看代碼:
function SuperType(name){ this.name = name; this.colors = ["red","yellow"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ //繼承屬性 SuperType.call(this,name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nealyang",24); instance1.colors.push("white"); instance1.sayName();//Nealyang instance1.sayAge();// 24 var instance2 = new SubType("Neal",21); alert(instance2.colors);//"red","yellow" instance2.sayName();//Neal instance2.sayAge();//21
在上面的例子中,SuperType構造函數定義了兩個屬性,name和colors,SuperType的原型中定義了一個方法sayName,subtype的構造函數中調用SuperType構造函數并且傳入name,然后將SuperType的實例賦值給subtype的原型。然后又在新的原型中定義了sayAge的方法。這樣一來,就可以讓兩個不同的SubType實例既分別擁有自己的屬性,包括colors,又可以使用相同的方法了。
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了他們的優點。成為javascript中最為常見的繼承模式。而且instanceof和isPrototypeOf方法也能用于識別組合模式創建的對象。
別的繼承模式繼承模式是有很多,上面只是說到我們經常使用到的繼承模式。包括還有原型式繼承、寄生式繼承、寄生組合式繼承等,其實,只要理解了原型、原型鏈、構造函數等對象的基本概念,理解起來這中模式都是非常容易的。后續如果有時間,再給大家總結吧~
交流掃碼關注我的個人微信公眾號,分享更多原創文章。點擊交流學習加我微信、qq群。一起學習,一起進步
歡迎兄弟們加入:
Node.js技術交流群:209530601
React技術棧:398240621
前端技術雜談:604953717 (新建)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89308.html
摘要:面向對象面向對象編程的全稱是,簡稱,面向對象編程是用抽象方式創建基于現實世界模型的一種編程模式。面向對象編程的三個主要特征是封裝繼承多態。 面向對象 面向對象編程的全稱是Object Oriented Programming,簡稱OOP,面向對象編程是用抽象方式創建基于現實世界模型的一種編程模式。面向對象編程可以看做是使用一系列對象相互協作的軟件設計,面向對象程序設計的目的是在編程中促...
摘要:中的和是一門很靈活的語言,尤其是。即然是面向對象的編程語言,那也是不可或缺的。在中,永遠指向的是他的調用者。定義是存在于實例化后對象的一個屬性,并且指向原對象的屬性。我們在擴展的時候,同時父類也會有對應的方法,這很顯然是一個很嚴重的問題。 javascript中的this和new javascript是一門很靈活的語言,尤其是function。他即可以以面向過程的方式來用,比如: f...
摘要:會造成內存浪費的問題構造函數繼承聲明父類聲明子類生成實例組合式繼承組合式繼承是汲取了兩者的優點,既避免了內存浪費,又使得每個實例化的子類互不影響。 寫在前面 既然是淺談,就不會從原理上深度分析,只是幫助我們更好地理解... 面向對象與面向過程 面向對象和面向過程是兩種不同的編程思想,剛開始接觸編程的時候,我們大都是從面向過程起步的,畢竟像我一樣,大家接觸的第一門計算機語言大概率都是C語...
摘要:工廠模式優點集中實例化,可以傳參等缺點分不清屬于哪個對象我們先來談談優點,看例子集中實例化返回實例化對象返回返回不難看出,工廠模式比上面的例子減少了很多代碼。 ECMAscript開發的兩種模式:1.過程化 2.OOP(面向對象) 面向對象的語言有一個標志,那就是類的概念,而通過類可以創建任意多個具有相同屬性的方法的對象。但是ECMAscript中沒有類的概念! 又談作用域 首先...
摘要:構造函數通過原型繼承了構造函數和原型,這就形成了一個鏈條,通俗的講就是原型鏈繼承。而且方法只能冒充構造函數里面的屬性和方法而無法冒充原型對象里面的屬性和方法還有最大的問題就是重復使用。 前言: 寫到這里,差不多就把OOP完結了,寫了幾篇OOP的文章,但是只是略懂皮毛,可能深入的OOP還有很多,但是我感覺寫到這里也算是差不多完結了。 繼承 繼承是面向對象比較核心的概念,其他語言可能實現...
閱讀 1129·2021-10-27 14:13
閱讀 2636·2021-10-09 09:54
閱讀 897·2021-09-30 09:46
閱讀 2424·2021-07-30 15:30
閱讀 2166·2019-08-30 15:55
閱讀 3409·2019-08-30 15:54
閱讀 2847·2019-08-29 14:14
閱讀 2771·2019-08-29 13:12