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