摘要:同理,原型鏈也是實現(xiàn)繼承的主要方式的只是語法糖。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層以此類推。利用構(gòu)造函數(shù)小明張三張三小明缺點每次實例化都需要復(fù)制一遍函數(shù)到實例里面。寄生構(gòu)造函數(shù)模式只有被類出來的才能用。
引言
最近又攀登了一下JS三座大山中的第二座。登山過程很酸爽,一路發(fā)現(xiàn)了許多之前沒曾注意到的美景。本著獨樂樂不如眾樂樂的原則,這里和大家分享一下。
JS的面試對象有些人認(rèn)為 JavaScript 不是真正的面向?qū)ο蟮恼Z言,比如它沒有像許多面向?qū)ο蟮恼Z言一樣有用于創(chuàng)建class類的聲明(在 ES2015/ES6 中引入了 class 關(guān)鍵字,但那只是語法糖,JavaScript 仍然是基于原型的)。JavaScript 用一種稱為構(gòu)建函數(shù)的特殊函數(shù)來定義對象和它們的特征。原型、原型鏈不像“經(jīng)典”的面向?qū)ο蟮恼Z言,從構(gòu)建函數(shù)創(chuàng)建的新實例的特征并非全盤復(fù)制,而是通過一個叫做原形鏈的參考鏈鏈接過去的。同理,原型鏈也是實現(xiàn)繼承的主要方式(ES6的extends只是語法糖)。
一直在猶豫,到底是先講創(chuàng)建對象的方法還是先講原型。為了后面保證講創(chuàng)建對象方法的連貫性,這里還是先講講原型吧,
這里為了權(quán)威,直接就摘抄MDN的定義了
JavaScript 常被描述為一種基于原型的語言 (prototype-based language)——每個對象擁有一個原型對象,對象以其原型為模板、從原型繼承方法和屬性。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層、以此類推。這種關(guān)系常被稱為原型鏈 (prototype chain),它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。準(zhǔn)確地說,這些屬性和方法定義在Object的構(gòu)造器函數(shù)(constructor functions)之上的prototype屬性上,而非對象實例本身。
這個__proto__屬性有什么用呢?在傳統(tǒng)的 OOP 中,首先定義“類”,此后創(chuàng)建對象實例時,類中定義的所有屬性和方法都被復(fù)制到實例中。在 JavaScript 中并不如此復(fù)制,而是在對象實例和它的構(gòu)造器之間建立一個鏈接(它是__proto__屬性,是從構(gòu)造函數(shù)的prototype屬性派生的),之后通過上溯原型鏈,在構(gòu)造器中找到這些屬性和方法。
簡單的說,就是實例對象能通過自己的__proto__屬性去訪問“類”原型(prototype)上的方法和屬性,類如果也是個實例,就會不斷往上層類的原型去訪問,直到找到
補充:
1.“類”的原型有一個屬性叫做constructor指向“類”
2.__proto__已被棄用,提倡使用Object.getPrototypeOf(obj)
舉例:
var arr = [1,2,3] //arr是一個實例對象(數(shù)組類Array的實例) arr.__proto__ === Array.prototype //true 實例上都有一個__proto__屬性,指向“類”的原型 Array.prototype.__proto__ === Object.prototype //true “類”的原型也是一個Object實例,那么就一定有一個__proto__屬性,指向“類”object的原型
這里補充一個知識點:
瀏覽器在在Array.prototype上內(nèi)置了pop方法,在Object.prototype上內(nèi)置了toString方法
上圖是我畫的一個原型鏈圖
[1,2,3].pop() //3 [1,2,3].toString() //"1,2,3" [1,2,3].constructor.name //"Array" [1,2,3].hehe() //[1,2,3].hehe is not a function
當(dāng)我們調(diào)用pop()的時候,在實例[1,2,3]上面沒有找到該方法,則沿著原型鏈搜索"類"Array的原型,找到了pop方法并執(zhí)行,同理調(diào)用toString方法的時候,在"類"Array沒有找到則會繼續(xù)沿原型鏈向上搜索"類"Object的原型,找到toString并執(zhí)行。
當(dāng)執(zhí)行hehe方法的時候,由于“類”O(jiān)bject的原型上并沒有找到,搜索“類”O(jiān)bject的__proto__,由于執(zhí)行null,停止搜索,報錯。
注意,[1,2,3].constructor.name顯示‘Array’不是說明實例上有constructor屬性,而是正是因為實例上沒有,所以搜索到類的原型上了,找到了constructor
類,創(chuàng)建對象的方法怎么創(chuàng)建對象,或者說怎么模擬類。這里我就不學(xué)高程一樣,給大家介紹7種方法了,只講我覺得必須掌握的。畢竟都es6 es7了,很多方法基本都用不到,有興趣自己看高程。
利用構(gòu)造函數(shù)const Person = function (name) { this.name = name this.sayHi = function () { alert(this.name) } } const xm = new Person("小明") const zs = new Person("張三") zs.sayHi() //"張三" xm.sayHi() //"小明"
缺點: 每次實例化都需要復(fù)制一遍函數(shù)到實例里面。但是不管是哪個實例,實際上sayHi都是相同的方法,沒必要每次實例化的時候都復(fù)制一遍,增加額外開銷。
組合使用原型和構(gòu)造函數(shù)//共有方法掛到原型上 const Person = function () { this.name = name } Person.prototype.sayHi = function () { alert(this.name) } const xm = new Person("小明") const zs = new Person("張三") zs.sayHi() //"張三" xm.sayHi() //"小明"
缺點:基本沒啥缺點了,創(chuàng)建自定義類最常見的方法,動態(tài)原型模式也只是在這種混合模式下加了層封裝,寫到了一個函數(shù)里面,好看一點,對提高性能并沒有卵用。
es6的類es6的‘類’class其實就是語法糖
class Person { constructor(name) { this.name = name } say() { alert(this.name) } } const xm = new Person("小明") const zs = new Person("張三") zs.sayHi() //"張三" xm.sayHi() //"小明"
在es2015-loose模式下用bable看一下編譯
"use strict"; var Person = /*#__PURE__*/ function () { function Person(name) { this.name = name; } var _proto = Person.prototype; _proto.say = function say() { alert(this.name); }; return Person; }();
分析:嚴(yán)格模式,高級單例模式封裝了一個類,實質(zhì)就是組合使用原型和構(gòu)造函數(shù)
寄生構(gòu)造函數(shù)模式比如現(xiàn)在需要創(chuàng)建一些特殊的數(shù)組,這些數(shù)組有sayContent方法,可以打印出自己的內(nèi)容。怎么創(chuàng)建出這個特殊的數(shù)組類,又不影響Array類
function specialArray() { var arr = new Array(...arguments) arr.sayContent = function () { console.log([...this.values()]) } return arr } var arr = new specialArray("x","y","z")
這個和在數(shù)組的原型鏈上增加方法有啥區(qū)別?原型鏈上增加方法,所有數(shù)組都可以用。寄生構(gòu)造函數(shù)模式只有被specialArray類new出來的才能用。
JS世界里的關(guān)系圖知識點:
Object.getPrototypeOf(Function) === Function.prototype // Function是Function的實例,沒毛病
Object.getPrototypeOf(Object.prototype)
任何方法上都有prototype屬性以及__proto__屬性
任何對象上都有__proto__屬性
Function.__proto__.__proto__===Object.prototype
Object.getPrototypeOf(Object)===Function.prototype
最高級應(yīng)該就是Function.prototype了,因為5
判斷類型的方法之前在JS核心知識點梳理——數(shù)據(jù)篇里面說了一下判斷判斷類型的四種方法,這里借著原型再來分析一下
1. typeof:只能判斷基礎(chǔ)類型中的非Null,不能判斷引用數(shù)據(jù)類型(因為全部為object)它是操作符
2. instanceof:用于測試構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在對象的原型鏈中的任何位置 風(fēng)險的話有兩個
//判斷不唯一 [1,2,3] instanceof Array //true [1,2,3] instanceof Object //true //原型鏈可以被改寫 const a = [1,2,3] a.__proto__ = null a instanceof Array //false
仿寫一個instanceof,并且掛在Object.prototype上,讓所有對象都能用
//仿寫一個instance方法 Object.prototype.instanceof = function (obj) { let curproto = this.__proto__ while (!Object.is(curproto , null)){ if(curproto === obj.prototype){ return true } curproto = curproto.__proto__ } return false } [1,2,3].instanceof(Array) //true [1,2,3].instanceof(Object) //true [1,2,3].instanceof(Number) //false [1,2,3].instanceof(Function) //false 1..instanceof(Function) //false (1).instanceof(Number) //true3. constructor:
constructor 這玩意已經(jīng)介紹過了,“類”的原型執(zhí)行constructor指向“類”
風(fēng)險的話也是來自原型的改寫
[1,2,3].constructor.name //"Array" // 注意下面兩種寫法區(qū)別 Person.protorype.xxx = function //為原型添加方法,默認(rèn)constructor還是在原型里 Person.protorype = { //原型都被覆蓋了,沒有constructor了,所要要手動添加,要不然constructor判斷失效 xxx:function constructor:Person }4.Object.prototype.toString.call(xxx)
試了下,好像這個方法也不是很準(zhǔn)
null 可以用object.is(xxx,null)代替
Array 可以用Array.isArray(xxx)代替
Object.prototype.toString.call([1,2,3]) //"[object Array]" Object.prototype.toString.call(function(){}) //"[object Function]" Object.prototype.toString.call(1) //"[object Number]" Object.prototype.toString.call(null) //"[object Null]" Object.prototype.toString.call({}) //"[object Object]" Object.prototype.toString.call(undefined) //"[object Undefined]" Object.prototype.toString.call(true) // 特別注意 特別注意 特別注意"[object Object]" Object.prototype.toString.call("string") // 特別注意 特別注意 特別注意 "[object Undefined]"in操作符
對于for in 和in 都是沿著原型鏈查找屬性是否存在,可以利用hasOwnProperty進(jìn)行相關(guān)過濾
// "in" operation test class Person { constructor (name) { this.name = name } sayHi() { console.log("Hi") } } var p1 = new Person("小明") "name" in p1 //true "sayHi" in p1 //true for (var i in p1) { if (p1.hasOwnProperty(i)) { console.log("ownProperty:" + i) } else { console.log("prototypeProperty: " + i) } } //"ownProperty: name" // prototypeProperty: sayHi總結(jié)
參照各種資料,結(jié)合自己的理解,在盡量不涉及到繼承的情況下,詳細(xì)介紹了原型及其衍生應(yīng)用。由于本人技術(shù)有限,如果有說得不對的地方,希望在評論區(qū)留言。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/109739.html
摘要:引言上篇文章介紹原型,這篇文章接著講繼承,嘔心瀝血之作,大哥們點個贊呀明確一點并不是真正的面向?qū)ο笳Z言,沒有真正的類,所以我們也沒有類繼承實現(xiàn)繼承有且僅有兩種方式,和原型鏈在介紹繼承前我們先介紹下其他概念函數(shù)的三種角色一個函數(shù),有三種角色。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 上篇文章介紹原型,...
摘要:而且在超類型的原型中定義的方法,對子類型而言也是不可見的,結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式。在主要考慮對象而不是自定義類型和構(gòu)造函數(shù)的情況下,這個模式也不錯。 寫在前面 注:這個系列是本人對js知識的一些梳理,其中不少內(nèi)容來自書籍:Javascript高級程序設(shè)計第三版和JavaScript權(quán)威指南第六版,感謝它們的作者和譯者。有發(fā)現(xiàn)什么問題的,歡迎留言指出。 1.原型鏈 將原型鏈作...
摘要:核心知識點梳理數(shù)據(jù)篇看了一些資料,結(jié)合高程和對核心知識點進(jìn)行了梳理。所以,一共有種聲明變量的方法。凡是在聲明之前就使用這些變量,就會報錯。還是那句話,建議大家掌握核心知識點,細(xì)枝末節(jié)的東西就隨意啦。 JS核心知識點梳理——數(shù)據(jù)篇 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 看了一些資料,結(jié)合ES6、高程和MD...
摘要:引言滿滿的干貨,面試必系列,參考大量資料,并集合自己的理解以及相關(guān)的面試題,對核心知識點中的作用域閉包上下文進(jìn)行了梳理。本篇重點介紹閉包和。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 滿滿的干貨,面試必bei系列,參考大量資料,并集...
摘要:英文原文中本來是,而翻譯成第一類公民其實就是一種比喻。所以,通過上述的結(jié)果,我們發(fā)現(xiàn)在中不管我們是用構(gòu)造函數(shù)創(chuàng)建的對象還是用本身提供的數(shù)據(jù)類型創(chuàng)建的對象都源自于。使用可以解除函數(shù)體內(nèi)代碼和函數(shù)名的耦合狀態(tài)。 作為一個Jser,不光要會用js,還要明白它的運行原理,不然就會一直停留在表面。 函數(shù)在JavaScript中被稱作第一等公民,這個第一等公民是什么鬼?看看知乎上是怎么回答的。就像...
閱讀 2909·2021-11-19 09:40
閱讀 3757·2021-11-15 18:10
閱讀 3373·2021-11-11 16:55
閱讀 1324·2021-09-28 09:36
閱讀 1723·2021-09-22 15:52
閱讀 3425·2019-08-30 14:06
閱讀 1220·2019-08-29 13:29
閱讀 2363·2019-08-26 17:04