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