摘要:如果按照字面意思來理解,那么就是通過調用構造函數而創建的那個對象實例的原型對象。構造函數在不返回值的情況下,默認會返回新對象實例。
面向對象
對象:是無序屬性的集合,其屬性可以包含基本值、對象或者函數。new運算符
創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。
當代碼 new Foo(...) 執行時,會發生以下事情:
一個繼承自Foo.prototype的新對象被創建
使用指定的參數調用構造函數 Foo ,并將 this 綁定到新創建的對象。
屬性類型ECMAScript 中有兩種屬性:數據屬性和訪問器屬性。
數據屬性
數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有 4 個描述其行為的特性。
Configurable:表示能否通過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值為 true。
Enumerable:表示能否通過 for-in 循環返回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值為 true。
Writable:表示能否修改屬性的值
Value:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值為 undefined。
要修改屬性默認的特性,必須使用 ECMAScript 5 的 Object.defineProperty()方法。這個方法 接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象。
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"
把 configurable 設置為 false,表示不能從對象中刪除屬性。如果對這個屬性調用 delete,則 在非嚴格模式下什么也不會發生,而在嚴格模式下會導致錯誤。而且,一旦把屬性定義為不可配置的, 就不能再把它變回可配置了。此時,再調用 Object.defineProperty()方法修改除 writable 之外 的特性,都會導致錯誤:
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas" }); //拋出錯誤 Object.defineProperty(person, "name", { configurable: true, value: "Nicholas" });
訪問器屬性
訪問器屬性不包含數據值;它們包含一對兒 getter 和 setter 函數(不過,這兩個函數都不是必需的)。 在讀取訪問器屬性時,會調用 getter 函數,這個函數負責返回有效的值;在寫入訪問器屬性時,會調用 setter 函數并傳入新值,這個函數負責決定如何處理數據。訪問器屬性有如下 4 個特性。
Configurable:表示能否通過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特 性,或者能否把屬性修改為數據屬性。對于直接在對象上定義的屬性,這個特性的默認值為 true。
Enumerable:表示能否通過 for-in 循環返回屬性。對于直接在對象上定義的屬性,這 5 個特性的默認值為 true
Get:在讀取屬性時調用的函數。默認值為 undefined
Set:在寫入屬性時調用的函數。默認值為 undefined
訪問器屬性不能直接定義,必須使用 Object.defineProperty()來定義
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition); //2
不一定非要同時指定 getter 和 setter。只指定 getter 意味著屬性是不能寫,嘗試寫入屬性會被忽略。 在嚴格模式下,嘗試寫入只指定了 getter 函數的屬性會拋出錯誤。類似地,只指定 setter 函數的屬性也不能讀,否則在非嚴格模式下會返回 undefined,而在嚴格模式下會拋出錯誤。
創建對象 工廠模式用函數來封裝以特定接口創建對象的細節
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");
函數 createPerson()能夠根據接受的參數來構建一個包含所有必要信息的 Person 對象。可以無數次地調用這個函數,而每次它都會返回一個包含三個屬性一個方法的對象。
工廠模式雖然解決了創建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。隨著 JavaScript 的發展,又一個新模式出現了。
構造函數模式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");
好處:
沒有顯式地創建對象;
直接將屬性和方法賦給了 this 對象;
沒有 return 語句。
person1 和 person2 分別保存著 Person 的一個不同的實例。這兩個對象都有一個 constructor(構造函數)屬性,該屬性指向 Person,如下所示
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true
對象的 constructor 屬性最初是用來標識對象類型的。但是,提到檢測對象類型,還是 instan- ceof 操作符要更可靠一些。我們在這個例子中創建的所有對象既是 Object 的實例,同時也是 Person 的實例,這一點通過 instanceof 操作符可以得到驗證。
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
將構造函數當作函數
構造函數與其他函數的唯一區別,就在于調用它們的方式不同。不過,構造函數畢竟也是函數,不 存在定義構造函數的特殊語法。任何函數,只要通過 new 操作符來調用,那它就可以作為構造函數;而 任何函數,如果不通過 new 操作符來調用,那它跟普通函數也不會有什么兩樣。
// 當作構造函數使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" // 作為普通函數調用 Person("Greg", 27, "Doctor"); // 添加到window window.sayName(); //"Greg" // 在另一個對象的作用域中調用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
這個例子中的前兩行代碼展示了構造函數的典型用法,即使用 new 操作符來創建一個新對象。
接下來的兩行代碼展示了不使用new 操作符調用 Person()會出現什么結果:屬性和方法都被添加給 window 對象了。當在全局作用域中調用一個函數時,this對象總是指向Global 對象(在瀏覽器中就是window對象)。因此,在調用完函數之后,可以通過 window 對象來調用 sayName()方法,并且還返回了"Greg"。
最后,也可以使用call()(或者apply())在某個特殊對象的作用域中調用Person()函數。這里是在對象o的作用域中調用的,因此調用后o就擁有了所有屬性和sayName()方法。
構造函數的問題
使用構造函數的主要問題,就是每個方法都要在每個 實例上重新創建一遍。在前面的例子中,person1 和 person2 都有一個名為 sayName()的方法,但那 兩個方法不是同一個 Function 的實例。
以這種方式創建函數,會導致不同的作用域鏈和標識符解析,但創建 Function 新實例的機制仍然是相同的。因此,不同實例上的同名函數是不相等的,以下代碼可以證明這一點
alert(person1.sayName == person2.sayName); //false原型模式
我們創建的每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象, 而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。如果按照字面意思來理解,那 么 prototype 就是通過調用構造函數而創建的那個對象實例的原型對象。使用原型對象的好處是可以 讓所有對象實例共享它所包含的屬性和方法。換句話說,不必在構造函數中定義對象實例的信息,而是 可以將這些信息直接添加到原型對象中,如下面的例子所示。
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
理解原型對象
無論什么時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個 prototype屬性,這個屬性指向函數的原型對象。在默認情況下,所有原型對象都會自動獲得一個 constructor (構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針。就拿前面的例子來說, Person.prototype.constructor 指向 Person。而通過這個構造函數,我們還可繼續為原型對象添加其他屬性和方法。
創建了自定義的構造函數之后,其原型對象默認只會取得 constructor 屬性;至于其他方法,則都是從 Object 繼承而來的。當調用構造函數創建一個新實例后,該實例的內部將包含一個指針(內部 屬性),指向構造函數的原型對象。ECMA-262 第 5 版中管這個指針叫[[Prototype]]。雖然在腳本中 沒有標準的方式訪問[[Prototype]],但 Firefox、Safari 和 Chrome 在每個對象上都支持一個屬性 __proto__;而在其他實現中,這個屬性對腳本則是完全不可見的。不過,要明確的真正重要的一點就 是,這個連接存在于實例與構造函數的原型對象之間,而不是存在于實例與構造函數之間。
使用 hasOwnProperty()方法可以檢測一個屬性是存在于實例中,還是存在于原型中。這個方法(不 要忘了它是從 Object 繼承來的)只在給定屬性存在于對象實例中時,才會返回 true。
有兩種方式使用 in 操作符:多帶帶使用和在 for-in 循環中使用。在多帶帶使用時,in 操作符會在通 過對象能夠訪問給定屬性時返回 true,無論該屬性存在于實例中還是原型中。
由于 in 操作符只要通過對象能夠訪問到屬性就返回 true,hasOwnProperty()只在屬性存在于 實例中時才返回 true,因此只要 in 操作符返回 true 而 hasOwnProperty()返回 false,就可以確 定屬性是原型中的屬性。
原型對象的問題
所有實例在默認情況下都將取得相同的屬性值
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
Person.prototype對象有一個名為friends的屬性,該屬性包含一個字符串數組。然后, 創建了 Person 的兩個實例。接著,修改了 person1.friends 引用的數組,向數組中添加了一個字符 串。由于 friends 數組存在于 Person.prototype 而非 person1 中,所以剛剛提到的修改也會通過 person2.friends(與 person1.friends 指向同一個數組)反映出來。
組合使用構造函數模式和原型模式構造函數模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度地節省了內存。另外,這種混成模式還支持向構造函數傳遞參數;可謂是集兩種模式之長
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,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true動態原型模式
通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。
function Person(name, age, job){ //屬性 this.name = name; this.age = age; this.job = job; //方法 if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();寄生構造函數模式
這種模式的基本思想是創建一個函數,該函數的作用僅僅是封裝創建對象的代碼,然后再返回新創建的對象
function Person(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 friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
在這個例子中,Person 函數創建了一個新對象,并以相應的屬性和方法初始化該對象,然后又返 回了這個對象。除了使用 new 操作符并把使用的包裝函數叫做構造函數之外,這個模式跟工廠模式其實 是一模一樣的。構造函數在不返回值的情況下,默認會返回新對象實例。而通過在構造函數的末尾添加一個 return 語句,可以重寫調用構造函數時返回的值。
這個模式可以在特殊的情況下用來為對象創建構造函數。假設我們想創建一個具有額外方法的特殊數組。由于不能直接修改 Array 構造函數,因此可以使用這個模式。
function SpecialArray(){ //創建數組 var values = new Array(); values.push.apply(values, arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; //返回數組 return values; } var colors = new SpecialArray("red", "blue", "green"); alert(colors.toPipedString()); //"red|blue|green"
需要說明:首先,返回的對象與構造函數或者與構造函數的原型屬性之間沒有關系;也就是說,構造函數返回的對象與在構造函數外部創建的對象沒有什么不同。為此,不能依賴 instanceof 操作符來確定對象類型。由于存在上述問題,我們建議在可以使用其他模式的情況下,不要使用這種模式。
穩妥構造函數模式所謂穩妥對象,指的是沒有公共屬性,而且其方法也不引用 this 的對象。穩妥對象最適合在 一些安全的環境中(這些環境中會禁止使用 this 和 new),或者在防止數據被其他應用程序(如 Mashup 程序)改動時使用。穩妥構造函數遵循與寄生構造函數類似的模式,但有兩點不同:一是新創建對象的 實例方法不引用 this;二是不使用 new 操作符調用構造函數。按照穩妥構造函數的要求,可以將前面 的 Person 構造函數重寫如下。
function Person(name, age, job){ //創建要返回的對象 var o = new Object(); //可以在這里定義私有變量和函數 //添加方法 o.sayName = function(){ alert(name); }; //返回對象 return o; }
在以這種模式創建的對象中,除了使用 sayName()方法之外,沒有其他辦法訪問 name 的值。 可以像下面使用穩妥的 Person 構造函數。
即使有其他代碼會給這個對象添加方法或數據成員,但也不可能有別的辦法訪問傳 入到構造函數中的原始數據。穩妥構造函數模式提供的這種安全性,使得它非常適合在某些安全執行環 境——例如,ADsafe(www.adsafe.org)和 Caja(http://code.google.com/p/goog... )提供的環境—— 下使用。
繼承 原型鏈構造函數、原型和實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //繼承了 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue()); //true
問題:包含引用類型值的原型屬性會被所有實例共享;在創建子類型的實例時,不能向超類型的構造函數中傳遞參數
借用構造函數在子類型構造函數的內部調用超類型構造函數;函數只不過是在特定環境中執行代碼的對象, 因此通過使用 apply()和 call()方法也可以在(將來)新創建的對象上執行構造函數,如下所示
function SuperType() { this.colors = ["red", "blue", "green"] } function SubType(){ //繼承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
代碼中“借調”了超類型的構造函數。通過使用 call()方法(或 apply()方法 也可以),我們實際上是在(未來將要)新創建的 SubType 實例的環境下調用了 SuperType 構造函數。 這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的所有對象初始化代碼。結果, SubType 的每個實例就都會具有自己的 colors 屬性的副本了。
優勢:相對于原型鏈而言,借用構造函數有一個很大的優勢,即可以在子類型構造函數中向超類型構造函數傳遞參數。
問題:如果僅僅是借用構造函數,那么無法避免構造函數模式存在的問題——方法都在構造函數中定義,因此函數復用就無從談起了。而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結 果所有類型都只能使用構造函數模式。考慮到這些問題,借用構造函數的技術也是很少多帶帶使用的。
組合繼承思路:使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } 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("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27原型式繼承
借助原型可以基于已有的對象創建新對象,同時還不必因此創建自定義類型。
function object(o){ function F(){} F.prototype = o return new F()
在object()函數內部,先創建了一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回了這個臨時類型的一個新實例。從本質上講,object()對傳入其中的對象執行了一次淺復制。e.g.
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); // 這相當于創建了 person 對象的兩個副本。 alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ECMAScript 5 通過新增 Object.create()方法規范化了原型式繼承。這個方法接收兩個參數:一 個用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象。在傳入一個參數的情況下, Object.create()與 object()方法的行為相同。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"寄生式繼承
寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像真地是它做了所有工作一樣返回對象。以下代碼示范了寄生式繼承模式
function createAnother(original){ var clone = object(original) //通過調用函數創建一個新對象 clone.sayHi = function() { //以某種方式來增強這個對象 alert("hi") } return clone; //返回這個對象 }寄生組合式繼承
組合繼承最大的 問題就是無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是 在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子 類型構造函數時重寫這些屬性。
寄生組合式繼承的基本模式如下所示。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //創建超類型原型的一個副本 prototype.constructor = subType; //為創建的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性 subType.prototype = prototype; //將新創建的對象(即副本)賦值給子類型的原型 }
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ alert(this.age); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94626.html
摘要:更形象的我們還可以將面向對象理解為一種宗教信仰。這就導致面向對象教的程序員們在寫時就很難受。所以為了滿足信仰面向對象教的需求通過構造函數的形式模擬了偽類。這個套路的核心就是類那么里沒有類所以其實是通過構造函數來模擬的偽類。 JS面向對象之一 【概述】 在學習JS的面向對象之前,我們應該先自問這樣幾個問題: 面向對象是什么意思? 學習面向對象的核心是什么? 為什么要學習面向對象?(它的...
摘要:是完全的面向對象語言,它們通過類的形式組織函數和變量,使之不能脫離對象存在。而在基于原型的面向對象方式中,對象則是依靠構造器利用原型構造出來的。 JavaScript 函數式腳本語言特性以及其看似隨意的編寫風格,導致長期以來人們對這一門語言的誤解,即認為 JavaScript 不是一門面向對象的語言,或者只是部分具備一些面向對象的特征。本文將回歸面向對象本意,從對語言感悟的角度闡述為什...
摘要:面向過程函數式編程面向對象編程第二個并不是大家理解的那樣,我們先說舉個現實例子就明白了。多說一句函數是編程是非常強大也是我最喜歡的,以后再說,我們先說面向對象編程。 概述 當大家已經把js的語言基礎理解了,然后能夠寫出一些簡單的例子了,這個時候基本上達到了一年工作經驗的水平,而自己能夠獨立的寫一些小功能,完成一些小效果,或者臨摹修改一些比較復雜的插件的時候差不多就是兩年工作經驗的水平,...
摘要:對象重新認識面向對象面向對象從設計模式上看,對象是計算機抽象現實世界的一種方式。除了字面式聲明方式之外,允許通過構造器創建對象。每個構造器實際上是一個函數對象該函數對象含有一個屬性用于實現基于原型的繼承和共享屬性。 title: JS對象(1)重新認識面向對象 date: 2016-10-05 tags: JavaScript 0x00 面向對象 從設計模式上看,對象是...
摘要:自己的理解的第一個參數就是的值如果沒用默認是那個調用函數的當前的對象在全局作用域中就是被隱藏的所以不寫且在全局作用于調用函數的時候就是可以使用或者自己指定的指向 JS面向對象一:MVC的面向對象封裝 MDNjavascript面向對象 面向對象(Object-Oriented) showImg(https://segmentfault.com/img/remote/1460000016...
閱讀 2980·2021-11-16 11:45
閱讀 5124·2021-09-22 10:57
閱讀 1763·2021-09-08 09:36
閱讀 1585·2021-09-02 15:40
閱讀 2508·2021-07-26 23:38
閱讀 1184·2019-08-30 15:55
閱讀 923·2019-08-30 15:54
閱讀 1213·2019-08-29 14:06