摘要:創建構造函數后,其原型對象默認只會取得屬性至于其他的方法都是從繼承來的。上圖展示了構造函數的原型對象和現有的兩個實例之間的關系。所有原生的引用類型都在其構造函數的原型上定義了方法。
第6章我一共寫了3篇總結,下面是相關鏈接:
讀《javaScript高級程序設計-第6章》之理解對象
讀《javaScript高級程序設計-第6章》之繼承
所謂的工廠模式就是,把創建具體對象的過程抽象成了一個函數,每次調用這個函數都會返回一個相似的對象。
function createPerson(name, age, job){ var o = new Object(); ??? o.name = name; ??? o.age = age; ??? o.job = job; ??? o.sayName = function(){ alert(this.name); ??? };??? ??? return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor"); person1.sayName();?? //"Nicholas" person2.sayName();?? //"Greg"
工廠模式雖然解決了創建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
構造函數模式js里經常如此寫var obj=new Object();var arr=new Array();,Object和Array就是構造函數,使用new操作符可以創建相應類型的對象,使用instanceof可以驗證對象的類型,例如:
alert(arr instance Array); ? ? ?//true
構造函數模式就是,自定義像Array和Object等這樣的構造函數,并使用new操作符調用它來創建自定義類型對象的方法。
例如:
function Person(name, age, job){ ??? this.name = name; ??? this.age = age; ??? this.job = job; ??? this.sayName = function(){ ??????? alert(this.name); ??? };??? } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.sayName();?? //"Nicholas" person2.sayName();?? //“Greg”
new操作符
使用new操作符調用,Person就是一個構造函數
要創建Person的新實例,必須使用new操作符。以這種方式調用構造函數實際上會經歷一下4個步驟:
? ? (1)創建一個新對象
? ??(2)將構造函數的作用域賦給新對象,即把構造函數的this指向這個新對象
?? ?(3)執行構造函數中的代碼(為這個新對象添加屬性)
?? ?(4)返回新對象
如果不使用new,Person就是一個普通的函數,可以正常調用。例如:
//作為普通函數在全局作用域下調用 Person("Greg", 27, "Doctor");? //adds to window window.sayName();?? //“Greg" //作為普通函數在另一個對象中調用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName();??? //"Kristen"
檢測類型
alert(person1 instanceof Object);? //true
alert(person1 instanceof Person);//true
綜上,創建自定義的構造函數,意味著將來可以將它的實例標識為一種特定的類型(類似于Array類型,Number類型);而這正是構造函數模式勝過工廠模式的地方。但是構造函數模式也存在缺點。
構造函數模式的問題
使用構造函數的主要問題就是,每個方法都要在每個實例上重新創建一遍(實例化一次Function對象),浪費內存。例如,person1和person2都有一個sayName()的方法,但創建person1和person2時候,定義sayName這個方法時都實例化了一個函數對象,因此person1.sayName和person2.sayName是不相等的,而事實上它們又是做的同樣的事情。或者也可以這么說,person1和person2的sayName()方法做同樣的事情,但卻在創建對象時被實例化了兩次,也就占用了兩倍內存。
雖然可以解決,但并不完美,例如:
function Person(name, age, job){ ??? this.name = name; ??? this.age = age; ??? this.job = job; ??? this.sayName = sayName; } function sayName(){ ??? alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor”); alert(person1.sayName == person2.sayName);? //true?
但是如果共享方法有很多,就需要定義很多個全局函數,那么我們的自定義的引用類型就絲毫沒有封裝性可言了。好在,這些問題可以通過使用原型模式解決。
原型模式 (1)理解原型對象無論什么時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性就是該函數的原型對象。每個函數都有一個原型對象,所有原型對象都會自動獲得constructor屬性,constructor指向該函數(擁有該prototype屬性的函數)。
例如,Person.prototype.constructor指向Person。
創建構造函數后,其原型對象默認只會取得constructor屬性;至于其他的方法都是從Object繼承來的(__proto__)。當調用構造函數創建一個新實例后,該實例內部將包含一個指針(__proto__),指向構造函數的原型對象。(ECMA-262第5版中管這個指針叫[[Prototype]],但在腳本中沒有標準的方式訪問它。在chrome,safari和firefox中都支持一個屬性__proto__,但在其他實現中__proto__對腳本是不可見的)。所以和實例有直接關系的是構造函數的原型對象,而不是構造函數。
上圖展示了Person構造函數、Person的原型對象和Person現有的兩個實例之間的關系。
原型屬性即構造函數的原型對象的屬性;實例屬性即在實例對象上直接添加的屬性。
例如:person1.name=“Jone”。
通過點運算符可以訪問到實例的實例屬性和原型屬性。實例訪問屬性時,腳本會先搜索實例屬性,如果找到了,則停止搜索返回實例屬性的值;如果沒找到就繼續搜索原型屬性。所以如果實例屬性和原型屬性同名,那么原型屬性就會被屏蔽掉,無法訪問到。
需要注意的是:實例無法修改他的原型屬性的值,也無法修改原型對象(即不能修改、刪除和增加一個原型屬性)
(注意:實例不能修改的是原型屬性的值,但是如果原型屬性指向一個引用類型,原型屬性的值是存儲這個引用類型的地址,即不能修改原型屬性指向另一個對象,但卻能修改原型屬性指向的對象里的屬性。下面原型對象的問題里還會再講到)。
如果person1.name=“Jone”這樣寫,腳本只會在實例屬性里創建或修改一個name=“Jone”的屬性,delete?person1.name 只會刪除person1的實例屬性name(就算實例沒有name的實例屬性,也不會刪除實例的原型屬性)。
isPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1)); ? ?//true
如果person1的[[prototype]] ?(即__proto__)指向調用isPrototypeOf的對象即Person.prototype就會返回true。
即判斷Person.prototype是否是person1的[[prototype]]
Object.getPrototypeOf()
alert(Object.getPrototypeOf(person1)==Person.prototype); ? //true
返回person1這個對象的原型[[prototype]]
hasOwnProperty()
person1.hasOwnProperty(“name”); ? ? 如果person1.name是來自于person1的實例屬性,返回true;如果來自于person1的原型屬性,則返回false。
(4)原型與in操作符有兩種方式使用in操作符:
多帶帶使用in:alert(“name” in person1); ? //true
在通過person1能夠訪問給定屬性是返回true,無論屬性是實例屬性還是原型屬性。
在for-in循環中使用:返回的是所有能夠通過對象訪問的、可枚舉的屬性,其中包括實例屬性也包括原型屬性。
Object.keys()
接受一個對象作為參數,返回一個包含對象的所有可枚舉屬性的字符串數組。
如果對象是一個實例,則只返回實例的實例屬性而不包含原型屬性
Object.getOwnPropertyNames()
?var keys = Object.getOwnPropertyNames(Person.prototype); ?alert(keys);?? //"constructor,name,age,job,sayName”
得到對象的所有實例屬性,無論它是否可枚舉
??
所謂的更簡單的原型寫法就是用字面量的形式來定義構造函數的原型對象,如下:
function Person(){ } Person.prototype = { ??? name : "Nicholas", ??? age : 29, ??? job: "Software Engineer", ??? sayName : function () { ??????? alert(this.name); ??? } }; var friend = new Person(); alert(friend instanceof Object);? //true alert(friend instanceof Person);? //true alert(friend.constructor == Person);? //false alert(friend.constructor == Object);? //true
這樣定義完了之后,Person.prototype這個對象就被重寫了,導致它的constructor這個屬性的指向變成了Object,而不是Person
(解釋:Person.prototype是Object的一個實例,所以它有一個原型屬性constructor指向Object。Person被創建時,它的原型對象Person.prototype自動獲得了一個constructor的屬性,指向Person,這個屬性是對象的實例的實例屬性,所以會屏蔽掉對象的原型屬性,所以說Person.prototype.constructor是指向Person的。但是用字面量重寫了Person.prototype后,Person.prototype仍是Object的一個實例,所以它有一個原型屬性constructor指向Object,但它沒有了指向Person的實例屬性constructor,所以在訪問Person.prototype.constructor時,就是訪問了Person.prototype對象的原型屬性,指向了Object)。
但我們可以再把它定義進這個對象字面量里手動指向Person,即給Person.prototype這個對象的實例加一個實例屬性constructor,指向Person。如下:
function Person(){ } Person.prototype = { ??? constructor: Person, ??? name : "Nicholas", ??? age : 29, ??? job: "Software Engineer", ??? sayName : function () { ??????? alert(this.name); ??? } };
我們知道如此定義對象,對象的屬性的[[enumerable]]特性默認是true。而默認情況下,原聲的原型對象的constructor屬性是不可枚舉的,因此如果你使用兼容ES5的javaScript引擎,可以使用Object.defineProperty()來設置constructor屬性。如下:
//重設構造函數,只適用于ES5兼容的瀏覽器 Object.difineProperty(Person.prototype,”constructor”,{ ? ? enumerable:false, ? ? value:Person });(6)原型的動態性
簡單點來說,就是實例的[[prototype]]是指向構造函數的原型對象,而不是構造函數。只要你明白這一點,原型的動態性就好理解了。
第一種情況:Person.prototype可以在任意地方增加修改或刪除屬性,實例可以實時的訪問最新的原型屬性。因為每次實例訪問屬性,都是一次搜索的過程,搜索原型屬性時是到實例的[[prototype]]指向的對象里查找。實例的[[prototype]]是一個指針,Person.prototype也是一個指針,指向的是同一個地址,也就是說修改和查找都在同一個地方,那么查找到的值自然就是最新實時的了。
function Person(){ } var friend = new Person(); Person.prototype.sayHi = function(){ ??? alert("hi"); }; friend.sayHi();?? //"hi"
第二種情況:在實例被創建之后,Person.prototype被重寫了
function Person(){ } var friend = new Person(); ??????? Person.prototype = { ??? constructor: Person, ??? name : "Nicholas", ??? age : 29, ??? job : "Software Engineer", ??? sayName : function () { ??????? alert(this.name); ??? } }; friend.sayName();?? //error
這種情況是因為:實例一旦被創建,實例的[[prototype]]存儲的地址就確定了,指向的對象地址就確定了,如果你改變這個地址里的對象,實例都可以訪問的到。但是如果在實例被創建之后,重寫Person.prototype,就相當于是把Person.prototype指向了一個新的對象,而實例的[[prototype]]還是指向原來的對象,所以實例訪問的原型屬性還是要在原來的對象里查找,原來的對象里并沒有sayName這個方法,因此會報錯。
(7)原生對象的原型我們用原型模式創建自定義類型,讓自定義類型和原生類型一樣使用。其實所有的原生的對象(Object、Array、String,等等)也是采用的原型模式創建的。所有原生的引用類型都在其構造函數的原型上定義了方法。
例如,在Array.prototype中可以找到sort()方法,而在String.prototype中可以找到substring()方法。
通過原生對象的原型,不僅可以取得所有默認方法的引用,也可以定義新的方法。可以像修改自定義對象的原型一樣修改原生對象的原型,因此可以隨時添加方法。但是不建議如此做(在支持該方法的實現中運行代碼時會導致命名沖突,或者意外重寫了原生方法)。
**首先,原型模式省略了為構造函數傳遞參數,初始化實例的環節,使得所有實例默認時都是一樣的。
其次,原型模式的共享本性使得所有的實例都能共享它的屬性。**
如果屬性值是函數或者是基本值時,實例不能修改原型屬性的值,只會為該實例增加一個同名屬性,然后屏蔽掉同名原型屬性,這樣其它的實例都不會受到影響,使用的仍然是原型屬性原來的值。
如果屬性值是引用類型,實例雖不能修改原型屬性的值(這個值就是指向的對象的地址),即實例不能讓這個原型屬性重新指向另一個對象,但是卻可以修改指向的對象的屬性,這就會導致其它實例再訪問這個對象時,對象已被修改了。
例如:
function Person(){ } Person.prototype = { ??? constructor: Person, ??? name : "Nicholas", ??? age : 29, ??? job : "Software Engineer", ??? friends : ["Shelby", "Court"], ??? sayName : function () { ??????? alert(this.name); ??? } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends);??? //"Shelby,Court,Van" alert(person2.friends);??? //"Shelby,Court,Van" alert(person1.friends === person2.friends);? //true
這樣就違反了我們希望實例擁有屬于自己的全部屬性的初衷
組合使用構造函數模式和原型模式綜合前面所說的,我們發現構造函數模式優點在于能向構造函數傳遞,定義屬于實例自己的實例屬性。原型模式優點在于共享著對方法的引用,原型屬性是所有實例所共享的。
所以創建自定義類型的最常見方式,就是組合使用構造函數模式與原型模式
例如:
function Person(name, age, job){ ??? this.name = name; ??? this.age = age; ??? this.job = job; ??? this.friends = ["Shelby", "Court"]; } Person.prototype = { ??? constructor: Person, ??? sayName : function () { ??????? alert(this.name); ??? } }; var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends);??? //"Shelby,Court,Van" alert(person2.friends);??? //"Shelby,Court" alert(person1.friends === person2.friends);? //false alert(person1.sayName === person2.sayName);? //true動態原型模式
這一小節,私以為了解了解就好,只要你理解了上面所說的構造函數模式和原型模式的原理,那么原型屬性的定義你可以隨心所欲,只要符合你的預期就好。你高興就好,代碼高興就好。
寄生構造函數模式與工廠模式的區別是使用new 調用。不使用new調用,它就是工廠模式。
這一小節,私以為了解了解就好。
與工廠模式的區別是對象定義的方法不使用this,構造函數傳進來的參數不向外直接暴露。
這一小節,私以為了解了解就好。
好了,封裝類的幾種方式已經介紹完了。我的觀點是理解了對象和構造函數模式以及原型模式,就可以隨機應變了。不需要記住什么什么各種模式的,無非就是使用對象的場景不同。要理解對象和構造函數以及原型對象,靈活變換,無招勝有招才好。
這是我讀《javaScript高級程序設計》這本書的第6章面向對象的程序設計,做的筆記,在本篇之前還有一篇理解對象的筆記,后面還有一篇繼承的筆記。發現問題的小伙伴歡迎指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89311.html
摘要:此時的原型對象包括一個指向另一個原型的指針,相應的,另一個原型中的指向另一個構造函數。這種關系層層遞進,就通過一個原型對象鏈接另一個構造函數的原型對象的方式實現了繼承。 讀這篇之前,最好是已讀過我前面的關于對象的理解和封裝類的筆記。第6章我一共寫了3篇總結,下面是相關鏈接:讀《javaScript高級程序設計-第6章》之理解對象讀《javaScript高級程序設計-第6章》之封裝類 一...
摘要:把對象定義為無序屬性的集合,其屬性可以包含基本值對象或函數。接受兩個參數屬性所在的對象和要讀取其特性的屬性名返回的時其特性的對象例如讀高級程序設計這本書的第章面向對象的程序設計,我做了篇筆記。這是第一篇,后面還有兩篇,分別是封裝類和繼承。 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或函數。所以,我們可以理解對象就是名值對的集合,名就是對象的每個屬性的名字,...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環境的關閉是頁面關閉或者瀏覽器關閉,而局部環境的關閉是指函數結束。數值范圍最大和最小的范圍是超出范圍的數字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內容好像...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環境的關閉是頁面關閉或者瀏覽器關閉,而局部環境的關閉是指函數結束。數值范圍最大和最小的范圍是超出范圍的數字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內容好像...
摘要:題外話最近在看高級程序設計這本書,面對著多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。全局環境的關閉是頁面關閉或者瀏覽器關閉,而局部環境的關閉是指函數結束。數值范圍最大和最小的范圍是超出范圍的數字如何表示是一個特殊的值。 題外話 最近在看《JavaScript高級程序設計》這本書,面對著700多頁的厚書籍,心里有點壓力,所以我決定梳理一下。。探究一下到底怎么讀這本書。本書的內容好像...
閱讀 2950·2021-11-23 09:51
閱讀 3776·2021-11-22 15:29
閱讀 3226·2021-10-08 10:05
閱讀 1552·2021-09-22 15:20
閱讀 952·2019-08-30 15:56
閱讀 1069·2019-08-30 15:54
閱讀 733·2019-08-26 11:54
閱讀 2636·2019-08-26 11:32