摘要:高程第六章繼承理解與實踐昨日細細的讀了一遍高程現在寫篇文章來鞏固下認知吧讀首先是從中讀到了什么我自己也在讀書的時候用筆記下了各個部分的點現在等于閱讀筆記回憶下書本理解基礎第五版中規定了兩種屬性數據屬性訪問器屬性數據屬性包含一個數據值的位
JavaScript高程第六章:繼承-理解與實踐
昨日細細的讀了一遍JavaScript高程,現在寫篇文章來鞏固下認知吧.
首先是從中讀到了什么,我自己也在讀書的時候用筆記下了各個部分的點,現在等于閱讀筆記回憶下書本.
理解基礎ECMA-262(第五版)
ECMA中規定了兩種屬性:數據屬性 and 訪問器屬性
包含一個數據值的位置(讀取和寫入)
4個描述行為的特性
[[Configurable]] 默認值為true,描述了可否delete,可否修改其特性(變更為訪問器屬性)
[[Enumerable]] 默認值為true,描述了能否通過for-in循環返回屬性.
[[Writable]] 默認為true,能否修改屬性的值
[[Value]] 默認為undefined,就是屬性的值
相關函數 Object.defineProperty(屬性所在對象,屬性名,描述符對象(可多個,{}))
注!修改configurable為false,則對后續調用該方法有限制,變得只能修改Writable和Value特性.
不包含屬性值,包含一對getter和setter函數(非必需),同樣有4個特性,相同功能不多加解釋.
[[Configurable]] 默認值為true
[[Enumerable]] 默認值為true
[[Get]] default:undefined getter函數
[[Set]] default:undefined setter函數
注!訪問器屬性不能直接定義,必須使用Object.defineProperty()定義,在嚴格模式中,嘗試寫入只指定了getter函數的屬性會拋出錯誤,嘗試讀取只指定了setter函數的屬性同理.
非嚴格模式中,則會忽略/返回undefined
相關函數和兼容Object.defineProperty(屬性所在對象,屬性名,描述符對象(可多個,{}))
支持:IE9+(IE8部分實現),Firefox4+,Safari5+,Opera 12+和Chrome
不兼容解決方案:__defineGetter__(屬性名,函數),__defineSetter__(屬性名,函數)
但是無法解決對[[Configurable]]和[[Enumerable]]的修改
Object.defineProperties(對象,{屬性1:{描述符},屬性2:{}...})
支持:IE9+(IE8部分實現),Firefox4+,Safari5+,Opera 12+和Chrome
Object.getOwnPropertyDescriptor(對象,屬性名)
返回:對象(訪問器/格式)
可以對JS中任何對象,包括BOM,DOM使用.
工廠模式
構造函數模式
原型模式 - 引申出原型對象的理解
組合模式 解決原型模式問題
動態原型模式
寄生構造函數模式
穩妥構造函數模式
工廠模式缺點:未解決識別問題(怎么知道一個對象的類型)
示例:function makePerson(name,age,job){ var o =new Object(); o.name = name; o.age = age; o.job = job; o.arr = ["a","b"]; o.sayName = function(){ alert(this.name); } return o; } var a = makePerson("jack",18,"programmer"); var b = makePerson("james",20,"designer"); a.arr.push("c"); console.log("a:"+a.arr); //a:a,b,c console.log("b:"+b.arr); //b:a,b console.log(a instanceof makePerson);//false console.log(b instanceof makePerson);//false console.log(a.prototype); //undefined console.log(b.prototype); //undefined console.log(a.prototype); //undefined console.log(b.prototype); //undefined構造函數模式
應該值得注意的是構造函數我們是大寫字母開頭,這是約定俗成的.創建一個Person示例我們會有如下步驟.
創建一個新對象
將構造函數作用域賦給新對象(this指向)
執行構造函數中的代碼(為新對象添加屬性)
返回新對象
而instanceof操作符和constructor屬性都能讓我們分辨出這是一種特定的類型,這也是構造函數模式勝過工廠模式的地方.
如果直接作為普通函數調用,則會將屬性賦值給window對象(Global)
問題:函數不復用問題,實例中的方法不是同一個Function的實例,鑒定方法.
console.log(a.sayName == b.sayName)
解決:放到全局定義,構造函數中設置即可
導致新問題:毫無封裝性,而為了解決這些問題,我們可以使用后續的原型模式來解決.
注!所有對象都繼承自Object,所以a,b使用instanceof操作符判斷是否為Object的實例是true.
示例:function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.arr = ["a","b"]; this.sayName = function(){ alert(this.name); }; } var a = new Person("jack",18,"programmer"); var b = new Person("james",20,"designer"); a.arr.push("c"); console.log("a:"+a.arr); //a:a,b,c console.log("b:"+b.arr); //b:a,b console.log(a instanceof Person);//true console.log(b instanceof Person);//true console.log(a.prototype); //undefined console.log(b.prototype); //undefined console.log(a.constructor); //[Function: Person] console.log(b.constructor); //[Function: Person]原型模式
每一個function都有一個prototype(原型)屬性,為一個指針,指向一個對象(用途:包含可以由特定類型的所有實例共享的屬性和方法).
通過prototype設置的屬性和方法都是共享的,接下來讓我們理解一下原型對象.
理解原型對象在任何時候,我們創建一個新函數都意味著我們會根據一個特定規則創建prototype屬性,該屬性指向函數的原型對象.
在默認情況下,所有原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針.
調用構造函數創建一個實例后,實例內部包含一個指針[[Prototype]] (Firefox,Safari,Chrome訪問使用__proto__),
對于判斷可以使用Person.prototype.isPrototypeOf(a)函數.Person的prototype為a的prototype
Object.getPrototypeOf可以訪問[[Prototype]]的值.
值得注意的是,我們可以通過對象實例來訪問保存在原型的值,但是我們不能通過對象實例重寫原型的值(對象.屬性 = 值,這樣是添加屬性到實例,覆蓋屏蔽了原型的值而已,并沒有重寫,但是對于引用類型不同,即使設置對象.屬性=null也是不會恢復其指向,只是在實例中寫入屬性.對象為null而已,要想恢復,可以使用delete操作符)
原型與in操作符方式一:for-in循環中使用
方式二:多帶帶使用,會在能訪問(不管通過對象還是原型)給定屬性時返回true(所有能通過對象訪問,可枚舉的屬性)
所有開發人員定義的屬性都是可枚舉的(IE8以及更早例外,其中屏蔽的不可枚舉屬性的實例屬性不會出現在for-in循環中)
相關函數:
a.hasOwnProperty(屬性名),可以確定屬性是否存在于實例中,是則返回true
var keys = Object.keys(Person.prototype)
變量中保存一個數組,Object.keys返回的是一個包含所有可枚舉屬性的字符串數組.
Object.getWenPropertyNames()可以獲取所有實例屬性(無論是否可枚舉)
Person.prototype = { name : "Nicholas", age: 29, job: "software engineer", sayName:fuinction(){ alert("this.name"); } }
在上面代碼中,我們相當于完全重寫了prototype對象,同時其constructor不再指向Person(指向Object構造函數),盡管instanceof操作符能返回正確結果,但是constructor已經無法確定對象類型了.當然我們可以自己在新建對象時候設置constructor: Person,但是這樣做會導致它變為可枚舉屬性(原生不可枚舉,解決方法:Object.defineProperty()).
原型的動態性使用上述原型語法,會切斷構造函數與最初原型的聯系.
如var friend = new Person()出現在完全重寫之前,則我們無法通過friend訪問重寫的原型.
function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name: "Jack", age: 29, job: "programmer", sayName:function(){ console.log(this.name); } } console.log(friend.age); // undefined friend.sayName(); //報錯
friend中的[[Prototype]]指向的仍然是原來的空無一物的Prototype,而不是我們后來重寫的原型對象.
原生對象的原型原生引用類型(Object,Array,String等)都采用原型模式創建
注!不推薦修改原生對象的原型,可能導致命名沖突/重寫原生方法.
共享引用類型值的屬性,如Array,修改則會共享
示例:function Person(){ } Person.prototype.name = "Jack"; Person.prototype.age = 18; Person.prototype.job = "Software Engineer"; Person.prototype.arr = ["a","b"];//引用類型 Person.prototype.sayName = function(){ console.log(this.name); } var a = new Person(); a.sayName(); //Jack var b = new Person(); b.name = "James";//創建值,屏蔽了原型的值 console.log(b.age);//18 console.log(b);//Person { name: "James" } b.sayName();//James console.log(a.sayName == b.sayName);//true a.arr.push("c");//修改引用類型 console.log("a:"+a.arr); //a:a,b,c console.log("b:"+b.arr); //b:a,b,c console.log(a instanceof Person);//true console.log(b instanceof Person);//true console.log(a.prototype); //undefined console.log(b.prototype); //undefined console.log(a.constructor); //[Function: Person] console.log(b.constructor); //[Function: Person]組合使用構造函數模式和原型模式
解決原型模式的問題-共享引用類型值的屬性
其中特點在于,實例屬性在構造函數中定義,共享的constructor與方法在原型中定義,如下.
目前來說最廣泛,認同度最高的一種方式來創建自定義類型.
示例:function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["a", "b"]; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name); } } var a = new Person("jack",18,"programmer"); var b = new Person("james",20,"designer"); a.friends.push("c"); console.log(a.friends);//a,b,c console.log(b.friends);//a,b console.log(a.friends === b.friends); //false console.log(a.sayName === b.sayName); //true動態原型模式
在構造函數中,if檢查初始化后應存在的任何屬性或方法.從而對構造函數和原型方法進行封裝.
示例:function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = ["a","b"]; //注意不要使用對象字面量重寫原型 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var a = new Person("jack",18,"programmer"); var b = new Person("james",20,"designer"); a.friends.push("c"); console.log(a.friends);//a,b,c console.log(b.friends);//a,b console.log(a.friends === b.friends);//false console.log(a.sayName === b.sayName);//true console.log(a instanceof Person);//true console.log(b instanceof Person);//true寄生構造函數模式(不推薦
相當于工廠模式,通常用于在特殊情況下為對象創建構造函數,如我們要創建一個具有額外方法的特殊數組,又不能直接修改Array構造函數,就可以使用該模式.
注!返回對象和構造函數外部創建對象沒有不同,所以無法確定對象類型.不推薦使用
function SpecialArray(){ var values = new Array(); //添加值 values.push.apply(values,arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; return values; }穩妥構造函數模式(不推薦
穩妥對象:沒有公共屬性,方法都不引用this的對象
和寄生構造函數模式的相似點:
創建對象實例不引用this
不使用new操作符調用構造函數
instanceof無效
注意,穩妥對象中,除了定義的方法之外沒有其他方法訪問某值.
注!和寄生構造函數模式一樣,不推薦使用
function Person(name,age,job){ var o = new Object(); o.sayName = function(){ alert(name); }; return 0; } var friend =Person("Jack",18,"Software Enginner"); friend.sayName();繼承
在ECMAScript中支持的是實現繼承,并且其實現繼承主要依靠原型鏈實現,所以明白原型鏈就很重要了.
原型鏈
借用構造函數
組合繼承
原型式繼承
寄生式繼承
寄生組合式繼承
原型鏈基本思想:利用原型鏈讓一個引用類型繼承另一個引用類型的屬性和方法.
注!和我們之前提到的一樣,所有函數的默認原型都是Object的實例.內部指針->Object.prototype
instanceof操作符,可以測試實例與原型鏈中的構造函數.
isPrototypeOf()方法 ,與instanceof操作符返回效果相同.
子類重寫超類/父類中某個方法,或者添加父類/超類不存在的某個方法時,要放在替換原型語句后.
注!不要使用對象字面量創建原型方法,這會重寫原型鏈
引用類型問題
創建子類型實例時不能(或者說沒辦法在不影響所有對象實例的情況下)向超類型的構造函數傳遞參數.
根據上述問題,實踐中很少多帶帶使用原型鏈.
示例: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; } // 謹慎定義方法 //SubType.prototype.getSuperValue=function(){ // return false; //}該方法會屏蔽原來的方法,即通過SuperType的實例調用getSuperValue時依然調用原來的方法,而通過SubType的實例調用時,會執行這個重新定義的方法.必須在SubType.prototype = new SuperType();之后,再定義getSubValue和該方法. var a = new SubType(); console.log(a.getSubValue());//false console.log(a.getSuperValue());//true //原型與實例的關系 console.log(a instanceof Object);//true console.log(a instanceof SuperType);//true console.log(a instanceof SubType);//true借用構造函數
偽造對象/經典繼承.
目的:解決引用類型問題->借用構造函數(constructor stealing)
基本思想:子類型構造函數內部調用超類/父類構造函數
缺點:無法避免構造函數模式存在的問題(函數無法復用)
所以該方式很少多帶帶使用.
function SuperType(name){ this.name = name; this.arr = ["a","b","c"]; } function SubType(){ SuperType.call(this,"jack");//傳遞參數 this.age = 18;//實例屬性 } var a = new SubType(); a.arr.push("d"); var b = new SubType(); console.log(a.arr);//a,b,c,d console.log(b.arr);//a,b,c組合繼承
combination inheritance
也稱偽經典繼承,將原型鏈和借用構造函數技術結合一起的繼承模式.
基本思想:使用原型鏈實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承.constructor重指向
相當于:屬性繼承(借用構造函數),函數外定義方法,constructor重新指向
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了優點,成為了JS中最常用的繼承模式,而且instanceof和isPrototypeOf()都能夠識別
示例:function SuperType(name){ this.name = name; this.arr = ["a","b"]; } SuperType.prototype.sayName =function(){ console.log(this.name); }; function SubType(name,age){ SuperType.call(this,name); this.age = age; } //inherit SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); }; var a = new SubType("Jack",18); a.arr.push("c"); console.log(a.arr);//a,b,c a.sayName();//Jack a.sayAge();//18 var b = new SubType("James",20); console.log(b.arr);//a,b b.sayName();//James b.sayAge();//20原型式繼承
Prototypal inheritance
將傳入的對象作為函數內定義的構造函數的原型(要求必須有一個對象可以作為另一個對象的基礎),在ECMAScript5中新增Object.create()方法規范了原型式繼承,它接收兩個參數,一個用作新對象原型的對象和(可選)一個為新對象定義額外屬性的對象.
單個參數情況下Object.create()和Object()行為相同
兼容性:IE9+,Firefox4+,Safari5+,Opera12+,Chrome
缺點:和原型模式一樣,引用類型共享.
function object(o){ function F(){}; F.prototype = o; return new F(); } var person = { name: "Jack", arr: ["a","b"] }; var a = object(person); var b = object(person); a.name = "James"; a.arr.push("c"); b.name = "Ansem"; b.arr.push("d"); console.log(person.arr);//a,b,c,d console.log(a.arr);//a,b,c,d console.log(b.arr);//a,b,c,d //Object.create var person2 = { name: "Jack", arr: ["a","b"] }; var c = Object.create(person2,{ name:{ value: "James" } }); var d = Object.create(person2,{ name:{ value: "Ansem" } }); c.arr.push("c"); d.arr.push("d"); console.log(c.name);//James console.log(d.name);//Ansem console.log(person.arr);//a,b,c,d console.log(c.arr);//a,b,c,d console.log(d.arr);//a,b,c,d寄生式繼承
parasitic inherit
思路與寄生構造函數和工廠模式類似,創建新對象,增強對象,返回對象.
缺點:函數復用不了,對于引用類型為共享.
function createAnother(original){ var clone = object(original); clone.sayHi = function(){ console.log("HI"); }; return clone; }寄生組合式繼承(重點)
組合繼承的問題:無論什么情況都會兩次調用超類型構造函數
第一次:SubType.prototype = new SuperType()時
第二次:new SuperType()內->SuperType.call(this,name);
這造成的結果是,第一次時:SuperType的實例(SubType的原型)初始化屬性.第二次時:新對象上又新創建了相同的屬性,于是這兩個屬性就屏蔽了原型中兩個同名屬性.
解決方法就是寄生組合式繼承.通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方式.
基本思路:不必為了指定子類型的原型而調用超類/父類的構造函數,我們需要的知識超類/父類原型的一個副本.在這點上使用寄生式繼承來繼承超類/父類的原型,再將結果指定給子類的原型.
高效率體現在避免了創建多余不必要的屬性,原型鏈還能保持不變.instanceof和isPrototypeOf()都能正常使用.
可以說寄生組合式繼承是引用類型最理想的繼承范式,這也被YUI庫所采用.
示例://基本模式 function inheritPrototype(subType,superType){ var prototype = Object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name){ this.name = name; this.arr = ["a","b"]; } SuperType.prototype.sayName =function(){ console.log(this.name); }; function SubType(name,age){ SuperType.call(this,name); this.age = age; }; inheritPrototype(SubType,SuperType);//避免了多次執行,提高了效率 SubType.prototype.sayAge = function(){ console.log(this.age); }; var c = new SubType("Jack",18); var d = new SubType("Ansem",25); c.arr.push("c"); d.arr.push("d"); console.log(c.name);//Jack console.log(d.name);//Ansem console.log(c.arr);//a,b,c console.log(d.arr);//a,b,d
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80455.html
摘要:高程讀書筆記第六章理解對象創建自定義對象的方式有創建一個實例,然后為它添加屬性和方法。創建了自定義的構造函數之后,其原型對象默認只會取得屬性至于其他方法都是從繼承而來的。 JS高程讀書筆記--第六章 理解對象 創建自定義對象的方式有創建一個Object實例,然后為它添加屬性和方法。還可用創建對象字面量的方式 屬性類型 ECMAScript在定義只有內部采用的特性時,描述了屬性的各種特征...
摘要:創建一個新對象將構造函數的作用域賦給新對象因此就指向了這個新對象執行構造函數中的代碼為這個新對象添加屬性返回新對象。 本章內容 理解對象屬性 理解并創建對象 理解繼承 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數 理解對象 創建對象 創建自定義對象的最簡單方式就是創建一個Object的實例,再為它添加屬性和方法。 var person = new...
摘要:對于采用這種模式的對象,還可以使用操作符確定它的類型寄生構造函數模式通常,在前述的幾種模式都不適用的情況下,可以使用寄生構造函數模式。這個模式可以在特殊的情況下用來為對象創建構造函數。 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數。嚴格來講,這就相當于說對象是一組沒有特定順序的值。 1 理解對象 創建對象: var person = new Obje...
摘要:繼承的是超類型中構造函數中的屬性,如上繼承了屬性,但沒有繼承原型中的方法。上述造成的結果是子類型實例中有兩組超類型的構造函數中定義的屬性,一組在子類型的實例中,一組在子類型實例的原型中。 ECMAScript只支持實現繼承,主要依靠原型鏈來實現。與實現繼承對應的是接口繼承,由于script中函數沒有簽名,所以無法實現接口繼承。 一、原型鏈 基本思想:利用原型讓一個引用類型繼承另一個引用...
摘要:把原型修改為另外一個對象就等于切斷了構造函數與最初原型之間的聯系。組合使用構造函數模式動態原型模式通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。 理解對象 屬性類型 數據屬性 數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有 4 個描述其行為的特性。 [[Configurable]] :表示能否通過 delete 刪除屬性從而重新定義屬性,能否修...
閱讀 1410·2021-11-24 09:39
閱讀 3691·2021-11-24 09:39
閱讀 1863·2021-11-16 11:54
閱讀 1468·2021-09-30 09:47
閱讀 1717·2021-09-26 10:16
閱讀 2349·2021-09-22 15:33
閱讀 1457·2021-09-14 18:01
閱讀 2443·2021-09-07 09:59