一、導讀
本篇文章將說清楚javaScript的原型帖鸦、原型鏈機制芝薇,說的不對評論區(qū)砸板磚~
如果你看了很多篇博客仍然搞不清楚prototype 、_ _ proto _ _作儿、new洛二、constructor的關(guān)系,請往下看攻锰!
如果你剛從java換坑到j(luò)avaScript晾嘶,請往下看!
如果你還在滿口“實例”口注、“構(gòu)造”去理解/閱讀javaScript代碼的話变擒,請往下看!
總之寝志,請往下看娇斑!
二策添、[[prototype]] 屬性才真正的叫做原型
JS中所有的對象被創(chuàng)建時會被賦予一個特殊的隱式屬性——[[prototype]] ,它是另一個對象的引用毫缆,并且?guī)缀跛械膶ο蟮?[[prototype]] 屬性都是非空的值唯竹。一個對象的 [[prototype]] 屬性值就被稱為這個對象的原型。一個對象成為另一個對象的原型苦丁,就稱這兩個對象被一條原型鏈關(guān)聯(lián)起來浸颓。如果某個對象A,它的 [[prototype]] 屬性值是對象B的引用的話旺拉,就稱B成為了A的原型产上。
JS函數(shù)的prototype 屬性、_ _ proto _ _屬性等其他的和“proto”字眼沾邊兒的蛾狗,我們統(tǒng)統(tǒng)不稱為原型晋涣。所以下文任何地方出現(xiàn)的“原型”都只表示 [[prototype]] 屬性。
值得一提沉桌,上面說所有對象都會有 [[prototype]] 屬性谢鹊。嚴謹點說是除了null和undefined外所有JS對象都有,包括引用類型和數(shù)字留凭、字符串這些基本類型佃扼。聲明let a = 'hello'
那a也是有原型的,默認是String.prototype
2.1蔼夜、用JS標準api -- getPrototypeOf兼耀、setPrototypeOf訪問原型
開頭講到,如果某個對象A挎扰,它的 [[prototype]] 屬性值是對象B的引用翠订,就稱B成為了A的原型,A的這個 [[prototype]] 屬性是隱式的(JS里應(yīng)該稱為非公有屬性)遵倦,也就是說不能通過.點操作符去訪問它尽超,它是JS底層機制。為了靈活性梧躺,JS標準依然提供了兩個api去訪問它(setter和getter)-- getPrototypeOf似谁、setPrototypeOf,Object.getPrototypeOf(A) 返回對象A的原型
掠哥,Object.setPrototypeOf(A,B) 把B設(shè)置成A的原型
從這兩個api可以看出巩踏,A的原型不是生來就唯一確定的,可以中途修改续搀,所以對象和它原型的關(guān)系完全不是“類和實例的關(guān)系”塞琼,如果用類和實例的思維先入為主的理解話,很多現(xiàn)象解釋不通禁舷。比如彪杉,我定義了一個Bird()函數(shù)用來創(chuàng)建一只鳥毅往,語句let myBird = new Bird()
執(zhí)行后,如果認為myBird是Bird的實例好像一點都不違和派近,好像Bird就是myBird的構(gòu)造函數(shù)攀唯,原型就約等于類。但事實是myBird這只鳥的原型是可以改的渴丸,let frog = {}; Object.setPrototypeOf(myBird,frog)
侯嘀,frog(青蛙)成了這只鳥的原型,這個時候你還能說原型是類嗎?顯然是解釋不通的谱轨。這種“死胡同”的現(xiàn)象有很多戒幔,造成混淆的一個因素是new
操作符,它和java里面的new可不是一回事土童,后面會解釋溪食。所以理解JS原型鏈的第一步是摒棄“類”、“實例”娜扇、“構(gòu)造”這些思想。
我認為“貸款人--擔保人”是比較合適的例子去形容原型關(guān)系:A去銀行辦貸款栅组,銀行要求A填寫一個擔保人B雀瓢,如果A沒錢還款,銀行就找擔保人還款玉掸。B就可以稱為A的原型刃麸。這里A可以指定父母、朋友司浪,甚至是一家公司(和A不是一個數(shù)據(jù)類型)作為自己的擔保人泊业,A也可以隨時找銀行申請重新指定擔保人。這里“銀行找A還款無果再找B還款”的動作就類似于訪問A的屬性啊易,不存在的話則沿著原型鏈往上找吁伺,找到則返回,一直找到原型鏈盡頭(和面向?qū)ο笾性L問子類屬性找不到后訪問父類租谈、祖父類的現(xiàn)象很像)篮奄。這個動作在JS被稱為委托(本篇不準備展開講委托)
2.2、JS對象的 _ _ proto _ _屬性
_ _ proto _ _屬性想必大家非常熟悉割去,非JS標準窟却,是各大瀏覽器為了方便程序員訪問對象的原型而提供的顯式屬性,可以這樣方便的訪問對象A的原型:A. _ _ proto _ _呻逆,當然 _ _ proto _ _屬性也是可以修改的夸赫,A. _ _proto _ _ = B
是有效的。這個屬性也許會成為你理解原型鏈的絆腳石咖城,到此為止你完全可以認為 _ _ proto _ _ 完全等效于 [[prototype]] 屬性茬腿,從目前來看他倆意義是一樣的呼奢,都是表示原型。但是下文將不再出現(xiàn) _ _ proto _ _ 滓彰,僅僅用[[prototype]] 表示原型控妻,一是希望大家將原型當做是JS對象間產(chǎn)生聯(lián)系的機制,而不是一個屬性揭绑,二是盡管各家瀏覽器都“不約而同”的使用了 _ _ proto _ _ 顯式的表示原型弓候,但是最終JS標準組也沒將它納入標準中,肯定有個中原因的他匪。
(不約而同實際都是瀏覽器廠商向市場的妥協(xié)菇存,帶頭大哥chrome用了 _ _ proto _ _ ,程序員寫的網(wǎng)頁在chrome上跑得風生水起邦蜜,在你xxx瀏覽器就報錯依鸥,用戶只會說xxx瀏覽器真垃圾,哪會說你chrome不按標準來呢悼沈,所以xxx瀏覽器只能跟上大哥腳步)贱迟。
2.3、JS函數(shù)的 prototype 屬性
先說結(jié)論:JS函數(shù)的prototype屬性命名是極為失敗的絮供,隨便換個名稱都能讓JS原型鏈更容易理解衣吠,它不表示函數(shù)的原型。
無疑壤靶,JS函數(shù)的prototype 是原型鏈非常重要的一環(huán)缚俏。它是JS專門為函數(shù)賦予的一個屬性,并且是顯式屬性贮乳,它默認也是指向另一個對象忧换。前面講過,每個對象都有 [[prototype]] 屬性(原型)向拆,并且只有 [[prototype]] 屬性才表示原型亚茬。函數(shù)也是對象,所以它既有原型又有prototype屬性浓恳,所以千萬別被prototype這個名字給騙了才写,它不表示函數(shù)的原型,包括函數(shù)在內(nèi)的所有對象的原型有且只用[[prototype]] 屬性表示奖蔓。
許多博客解釋原型鏈喜歡用這個例子:
let Foo = function(){}
let myFoo = new Foo()
雖然赞草,new是關(guān)鍵,但我不打算管new操作吆鹤,希望大家先看看let Foo = function(){}
的時候發(fā)生了什么厨疙,然后引出我的觀點。分析下面的代碼:
let Foo = function(){}
console.log(Foo.prototype) //{constructor: ?}疑务,是一個帶有constructor屬性的對象
Object.getPrototypeOf(Foo) === Foo.prototype //false
Object.getPrototypeOf(Foo) === Function.prototype //true
從上面的代碼可以看出沾凄,我聲明了一個name為Foo的函數(shù)梗醇,F(xiàn)oo便自動獲得prototype屬性,并且指向了一個包含constructor屬性的全新對象(constructor屬性后面再討論)撒蟀。再看一下叙谨,Object.getPrototypeOf(Foo) === Function.prototype 返回true
說明JS系統(tǒng)指定Function.prototype這個對象成為了Foo的原型,F(xiàn)unction是JS內(nèi)置的函數(shù)保屯,常常被當做所謂的“構(gòu)造函數(shù)”使用(new Function()
)手负,也就是說Foo與系統(tǒng)中某些已經(jīng)存在的對象產(chǎn)生了聯(lián)系,一種稱為原型的聯(lián)系姑尺。我聲明了個函數(shù)竟终,系統(tǒng)就指派Function.prototype作為它的原型,顯然是根據(jù)數(shù)據(jù)類型去指派的切蟋,如果聲明一個字符串统捶,那么原型將被指派為String.prototype,JS數(shù)據(jù)類型就那么幾個柄粹,他們的原型也是能枚舉出來的喘鸟,我稱它為固有原型,下一章就做這個事驻右。
再回頭說說constructor屬性迷守,constructor屬性也是JS命名的一大敗筆,它妄想用“構(gòu)造函數(shù)/構(gòu)造器”的語義讓程序員以為它是構(gòu)造函數(shù)旺入,但它卻根本沒有“構(gòu)造”的含義在里面,是constructor這個名字誤導了我們凯力。前面說到茵瘾,let myBird = new Bird()
讓程序員認為Bird是一個類的原因是new關(guān)鍵字,那么到目前為止我們又遇到一個因素了咐鹤,這個constructor變本加厲的誤導程序員拗秘。constructor屬性所指向的函數(shù)對象是和原型一樣,是可以更改的祈惶。
分析下面代碼:
let Bird= function(){}
let Frog= function(){}
let myBird = new Bird()
myBird.constructor === Bird //true
myBird.constructor = Frog //代碼生效雕旨,構(gòu)造函數(shù)重新指向到Frog
myBird.constructor === Bird //false
如果你把constructor 當做構(gòu)造函數(shù)去看待,那表示我很輕易的將小鳥的構(gòu)造函數(shù)改成了青蛙捧请,以后小鳥就是由青蛙構(gòu)造而來的(WTF)含義上根本說不通凡涩。另外,你會發(fā)現(xiàn)constructor 到目前為止幾乎沒有其他用武之地疹蛉,它純粹是湊數(shù)的屬性活箕,所以在你徹底理解原型鏈之前,請放下這個屬性可款,不要糾結(jié)育韩。
2.5克蚂、固有原型鏈
首先我認為:每一個對象都不是孤立的,每一個對象誕生之初就已經(jīng)身在一條原型鏈之中筋讨,只是根據(jù)自己的類型不同埃叭,身處的位置就不一樣,因此程序員每一次的聲明悉罕、賦值都意義重大赤屋,實際是解鏈再建鏈的過程。如果你按字面量給對象賦值時蛮粮,那么就表示讓系統(tǒng)給你分配原型益缎,這個系統(tǒng)分配的原型就可以稱為固有原型,如果你想自己指定原型就要用Object.create或者new操作符自行指定然想。先說一下讓系統(tǒng)分配原型的情況:
2.5.1莺奔、系統(tǒng)默認分配原型的情況
1、Number類型—— let obj0 = 1024
2变泄、String類型—— let obj1 = 'hello'
3令哟、Boolean類型—— let obj2 = true
4、Symbol類型—— let obj3 = Symbol('id')
5妨蛹、Function類型—— let obj4 = function ()
6屏富、Object類型—— let obj5 = {name:'張三'}
7、Array類型—— let obj6 = [1,2,3]
以上7種方式創(chuàng)建的對象我們在控制臺打印出來蛙卤,它們的原型是:
let obj0 = 1024
let obj1 = 'hello'
let obj2 = true
let obj3 = Symbol('id')
let obj4 = function (){}
let obj5 = {name:'張三'}
let obj6 = [1,2,3]
Object.getPrototypeOf(obj0) === Number.prototype //true
Object.getPrototypeOf(obj1) === String.prototype //true
Object.getPrototypeOf(obj2) === Boolean.prototype //true
Object.getPrototypeOf(obj3) === Symbol.prototype //true
Object.getPrototypeOf(obj4) === Function.prototype //true
Object.getPrototypeOf(obj5) === Object.prototype //true
Object.getPrototypeOf(obj6) === Array.prototype //true
拿let obj0 = 1024舉例狠半,可以看到,聲明obj0 并直接賦初值1024時颤难,JS系統(tǒng)就默認指定了obj0的原型為Number.prototype(嚴格上應(yīng)該說成obj0的原型和Number的prototype屬性指向了同一個對象神年,因為總不能拿內(nèi)存地址來解釋,只能暫時把這個對象就稱為Number.prototype)行嗤。前面說到已日,JS只有函數(shù)才有prototype屬性,所以Number是個函數(shù)栅屏,并且Number.prototype是個object
typeof Number === 'function' //true
typeof Number.prototype === 'object' //true
那么Number和Number.prototype也都有各自的原型飘千。這里直接給出結(jié)果:
Number.prototype的原型是Object.prototype栈雳,Number的原型是Function.prototype护奈,F(xiàn)unction.prototype的原型也是Object.prototype,再往上游Object.prototype的原型就是null了哥纫,意味著鏈到頭兒了逆济。其他類型的對象的原型和Number類型對象如出一轍,用一張總圖表示:
1、 Number.prototype > Object.prototype > null奖慌,這樣的一條原型鏈便是固有原型鏈抛虫,每一個變量對象的誕生,都伴隨著一條鏈的誕生简僧。任何一個對象一定處在一條原型鏈的其中一環(huán)建椰。
2、前面講到岛马,可以用setPrototypeOf這個api隨意設(shè)置對象的原型棉姐,固有的原型鏈也可以改,但是99.99%的情況下啦逆,你不會去改它的伞矩。MDN上說改變一個對象的原型,開銷比較大夏志,更合適的做法是用Object.create創(chuàng)造一個新的原型鏈乃坤。
3、前面講到Number沟蔑、String湿诊、Boolean、Symbol瘦材、Function厅须、Object、Array這些都是函數(shù)食棕,所以它們各自的原型都是Function.prototype朗和。
4、大部分情況下簿晓,typeof為object的對象眶拉,它的原型是Object.prototype,只是數(shù)組有些區(qū)別抢蚀,JS的怪異行為(缺陷)導致typeof [1,2,3] 等于object,而數(shù)組對象的原型是Array.prototype镰禾,而不是Object.prototype皿曲。
5、null是所有原型鏈的盡頭吴侦。null和undefined本身語義就表示“空的”屋休、“沒定義”,因此沒有原型备韧。嘗試用getPrototypeOf(null)劫樟、getPrototypeOf(undefined)會報語法錯。
6、雖說setPrototypeOf可以隨意設(shè)置叠艳,但是類型必須是object類型的對象奶陈、函數(shù)或者null)。嘗試設(shè)置setPrototypeOf({}附较,1024)會報語法錯吃粒。
7、前面講到拒课,嘗試訪問對象A的屬性徐勃,不存在的話則沿著原型鏈往上找,找到則返回早像,一直找到原型鏈盡頭僻肖。所以系統(tǒng)內(nèi)置的這幾個固有原型,上面承載了很多通用api卢鹦。比如:
number類型對象的原型Number.prototype臀脏,就被JS內(nèi)置了toFixed、toPrecision等api法挨,
let obj0 = 1024
obj0.toFixed(2) //1024.00谁榜,精確到小數(shù)點后兩位
就是obj0對象去自己原型上借來的toFixed函數(shù)使用。
2.6凡纳、自行指定原型
可以通過Object.create和new操作符窃植,在賦值時自行指定原型。
1荐糜、new操作符方式
再來看看前面的示例代碼:
let Foo = function(){}
let myFoo = new Foo()
我們知道了Foo是函數(shù)類型的對象巷怜,所以它有Foo.prototype屬性,并且Foo的原型是Function.prototype暴氏。new操作符會執(zhí)行Foo函數(shù)延塑,并且創(chuàng)造一個object類型的空對象,并返回答渔,同時把這個空對象的原型指定為Foo.prototype关带。所以myFoo是一個空的對象,它的原型是Foo.prototype沼撕。
1宋雏、new操作符會自動把返回的對象的原型指定為Foo的prototype。
let Foo = function(){}
let myFoo = new Foo()
Object.getPrototypeOf(myFoo) === Foo.prototype //true
2务豺、myFoo是空對象這一點很重要磨总,常常被忽略,(除非Foo執(zhí)行完有確實返回了對象)笼沥。myFoo之所以能訪問到Foo上的屬性蚪燕,完全是通過原型機制實現(xiàn)的娶牌,這和傳統(tǒng)面向?qū)ο蟮?code>實例化概念是完全不一樣的,傳統(tǒng)面向?qū)ο箢惗x的屬性馆纳,在類實例化時诗良,實例就實實在在的擁有了這個屬性。
3厕诡、無論Foo執(zhí)行完返回的類型是什么累榜,經(jīng)過new后都會變?yōu)閛bject類型。
let Foo = function(){
return 1024
}
let myFoo = new Foo()
typeof myFoo //object
4灵嫌、既然new會把myFoo的原型指定為Foo的prototype壹罚,而函數(shù)才有prototype屬性贷盲,所以new只能作用函數(shù)對象祟偷,作用于其他類型的對象會報語法錯。
let Foo = {name:'張三'}
let myFoo = new Foo() //報Foo is not a constructor
所以到這里就能看清new的面目了盏缤,和構(gòu)造實例一點兒關(guān)系都沒有绪穆。再看看和new作用差不多的Object.create辨泳。
2、Object.create方式
let Foo = function(){}
let myFoo = Object.create(Foo)
1玖院、Object.create(Foo)執(zhí)行的結(jié)果是創(chuàng)造一個空的對象菠红,并返回,同時將返回的對象的原型指定為Foo本身
let Foo = function(){}
let myFoo = Object.create(Foo)
Object.getPrototypeOf(myFoo) === Foo //true
2难菌、Object.create(Foo)和new操作符不同试溯,不必要求Foo是函數(shù)。但是因為Foo要作為別人的原型郊酒,所以Foo就要滿足原型的類型限制(object對象遇绞、函數(shù)或者null),否則會報語法錯燎窘。
3摹闽、如果Foo是函數(shù),new操作符會執(zhí)行一次Foo褐健,而Object.create不會執(zhí)行Foo付鹿,因為它的本質(zhì)只是要取Foo本身作為它的原型而已,不需要Foo執(zhí)行蚜迅。因為Foo不會執(zhí)行舵匾,所以Foo函數(shù)返回任何都不會影響到myFoo,所以無論Foo是函數(shù)還是普通對象慢叨,Object.create(Foo)返回的永遠是空對象纽匙。
let Foo = { age:24 }
let myFoo = Object.create(Foo)
myFoo.age //24
myFoo.hasOwnProperty('age') //false
let Foo = function(){
console.log('Foo執(zhí)行')
return { age:24 }
}
let myFoo = Object.create(Foo) //Foo沒執(zhí)行
myFoo.age //undefined
myFoo.hasOwnProperty('age') //false
這點非常重要务蝠,myFoo能訪問Foo的屬性完全是因為Foo是myFoo的原型拍谐。一步小心就會造成下面這種情況:
let Foo = { age:24 }
let myFoo = Object.create(Foo)
let myFoo1 = Object.create(Foo)
myFoo.age = 25
myFoo1.age //25
myFoo通過=賦值改變了原型鏈上的age屬性值,導致污染全局。
3轩拨、instanceof操作符
解釋了new操作符和構(gòu)造實例沒關(guān)系践瓷,還有一個和實例有關(guān)系的操作符——instanceof,它和實例也一點關(guān)系都沒有亡蓉。myFoo instanceof Foo 回答的不是myFoo是否是Foo的一個實例晕翠,而是回答Foo.prototype是否是myFoo原型鏈上的一環(huán),看兩個例子:
let Foo = function(){ }
let myFoo = new Foo()
Object.getPrototypeOf(myFoo) === Foo.prototype //true
myFoo instanceof Foo //true,顯然Foo.prototype就是myFoo的原型(第一環(huán))砍濒。所以返回true淋肾。
接著這個例子,我們創(chuàng)造一個新的原型鏈:
let a0 = {age:24}
let a1 = Object.create(a0)
let a2 = Objece.create(a1)
經(jīng)過前文Object.create的作用和固有原型鏈的介紹爸邢,我們知道樊卓,JS為我們創(chuàng)建了這么一條原型鏈
a2 > a1 > a0 > Object.prototype > null
這時執(zhí)行:
Object.setPrototypeOf(myFoo,a2) //把myFoo的原型指定為a2
那么myFoo的原型鏈就是:
myFoo > a2 > a1 > a0 > Object.prototype > null
再執(zhí)行:
Foo.prototype = a0 //Foo.prototype屬性重新賦值
myFoo instanceof Foo //true
Foo.prototype重新指向了a0,a0位于myFoo的原型鏈上杠河,所以myFoo instanceof Foo返回true碌尔。同理把Foo.prototype指向a2、a1券敌、a0甚至Object.prototype唾戚,myFoo instanceof Foo都會返回true。所以instanceof 雖然有“實例”的語義待诅,卻和實例沒有關(guān)系叹坦,本質(zhì)還是原型鏈機制。
ES6有提出一個isPrototypeOf的api咱士,它的作用和instanceof很像立由,但也有點誤導人,F(xiàn)oo.isPropertyOf(myFoo)語義上好像是回答Foo是myFoo的原型嗎序厉?但是實際是回答Foo是myFoo原型鏈上的一環(huán)嗎锐膜?所以上面a1.isPrototypeOf(myFoo)
和 a2.isPrototypeOf(myFoo)
都會返回true。
總結(jié):
1弛房、JS的原型鏈機制和類-實例機制完全不同道盏,有相似之處,但不要用傳統(tǒng)面向?qū)ο蟮乃枷肴ダ斫釰S代碼文捶,遇到10個場景可能有9個都能解釋得通荷逞,但總有那么1個你解釋不了,比如前文講到的“鳥由青蛙構(gòu)造而來”一樣粹排。
2种远、JS只有[[prototype]]這隱式屬性才叫做原型,prototype是函數(shù)的一個屬性顽耳,專屬于函數(shù)坠敷,這個屬性和原型大有關(guān)系妙同。反之,有prototype屬性的對象一定是函數(shù)膝迎。
3粥帚、JS兩個對象之間通過原型鏈產(chǎn)生聯(lián)系,原型鏈上的對象之間不會發(fā)生復制限次,盡管你是我的原型芒涡,但你還是你,我還是我卖漫,我不會復制你身上的屬性到我身上费尽,我只是想辦法引用你。
4羊始、每個對象誕生之時就已經(jīng)處于一條原型鏈中依啰,如果不指定就由JS自動分配原型,JS根據(jù)賦值時數(shù)據(jù)類型來分配到具體的鏈上店枣,稱為固有原型鏈速警。原型鏈上可以定義通用的api。一旦發(fā)生賦值操作鸯两,如果賦值前后的類型不一致闷旧,就會發(fā)生斷鏈和接新鏈的操作,這種操作是有開銷的钧唐,所以盡量保持變量類型一致忙灼。
5、拋棄了“構(gòu)造”钝侠、“實例”该园,忘掉了_ _ proto _ _、constructor才能有助于理解原型帅韧。
6里初、new操作符和構(gòu)造實例一點兒關(guān)系都沒有,它的功能是創(chuàng)造新的原型鏈忽舟,和它功能很相似的是Object.create双妨,但是兩者有很多區(qū)別。
7叮阅、instanceof 也和實例沒有關(guān)系刁品,myFoo instanceof Foo 回答的不是myFoo是否是Foo的一個實例,而是回答Foo.prototype是否是myFoo原型鏈上的一環(huán)浩姥。
8挑随、JS標準小組努力把自己往傳統(tǒng)面向?qū)ο笊峡浚‥S6出的class勒叠,但是太倉促了兜挨,有時候某些機制簡直匪夷所思竞阐。
舉個匪夷所思的例子,看下面代碼:
let obj = {age:24}
Object.defineProperty(obj,'age',{
writable:false,
configurable:false,
enumerable:true,
})
obj.age = 25 //writable為false暑劝,只讀,所以賦值不生效
let newObj = Object.create(obj)
newObj.hasOwnProperty('age') //false颗搂。前文分析過担猛,obj1是空對象,沒有age屬性
newObj.age = 26
newObj.hasOwnProperty('age') //false,為什么還是false丢氢?
Object.defineProperty是用來設(shè)置對象屬性的描述符傅联,將obj的age屬性的writable設(shè)置為false(只讀),所以用=賦值25不生效疚察,這個沒問題蒸走,但是用Object.create創(chuàng)建newObj后發(fā)現(xiàn)newObj .age = 26 竟然也沒有生效!貌嫡!newObj依然沒有age這個屬性比驻。這個很奇怪,newObj竟然被原型鏈對象內(nèi)的同名屬性影響到了岛抄,貌似“繼承”了writable:false别惦,但事實卻不是繼承,只是JS在刻意的模仿類屬性的繼承夫椭,這么模仿的結(jié)果就是讓程序員匪夷所思掸掸,不知道改怎么辦!蹭秋!
更令人費解的是扰付,newObj用=賦值沒生效,但用Object.defineProperty賦值又可以仁讨。接上面的代碼:
Object.defineProperty(newObj,'age',{
value:27,
writable:false,
configurable:false,
enumerable:true,
})
newObj.hasOwnProperty('age') //true
newObj.age //27
用Object.defineProperty賦值27是生效的羽莺,并且把writable改成true也是生效的。
還有一點是newObj對象age屬性的configurable不管原型上設(shè)置的是什么洞豁,都不會產(chǎn)生影響(不繼承禽翼,符合預期),有的會有繼承的現(xiàn)象發(fā)生族跛,有的又不會闰挡,我太難了~~。
JS發(fā)展十多年來礁哄,不少設(shè)計缺陷令人抓狂长酗,要修復bug,兼容是不可能了桐绒,沒有別的招夺脾,只能新推出嚴格模式之拨,或者靠TpyeScript力挽狂瀾。