摘要:王國維在人間詞話里談到了治學經驗,他說古今之成大事業大學問者,必經過三種之境界。其中談到中凍結一個對象幾種由淺入深的實踐。王國維已先自表明,吾人可以無勞糾葛。總結本文先后介紹了關于凍結一個對象的三種進階方法。
王國維在《人間詞話》里談到了治學經驗,他說:“古今之成大事業、大學問者,必經過三種之境界。”
巧合的是,最近受 git chat / git book 邀請,做了一個分享。
其中談到JS中凍結一個對象幾種由淺入深的實踐。想想也暗合國學大師所謂的三重境界。
這篇文章由淺入深討論JS中對象的一些鎖定特性。但都是一些基礎語法的實現,相信即便是前端小白也可以大體領會。不過需要讀者預先了解JS中對象的特性,尤其是對象自身屬性的描述符:configurable、writable...
另外,如果您對JS中對象操作、不可變數據、函數式編程感興趣,同樣推薦我的其他一些相關文章:
如何優雅安全地在深層數據結構中取值
從JS對象開始,談一談“不可變數據”和函數式編程
解析Twitter前端架構 學習復雜場景數據設計
等等。
昨夜西風凋碧樹 獨上高樓 望盡天涯路第一種境界:“昨夜西風凋碧樹,獨上高樓,望盡天涯路。”
該詞句出自晏殊的《蝶戀花》,原意是說,“我”上高樓眺望所見的更為蕭颯的秋景,西風黃葉,山闊水長,案書何達?
王國維此句中解成:做學問成大事業者,首先要有執著的追求,登高望遠,瞰察路徑,明確目標與方向,了解事物的概貌。
我們就從最基本的場景說起,究竟為什么要凍結一個對象?
場景一:
我們造了一個輪子,對外暴露一個對象,開放出來給第三方使用。同時需要保證這個對外暴露的對象完全安全,不能被業務代碼所改寫覆蓋或下鉤子(hook)函數。
場景二:
如果你看過Vue 2.* 版本源碼,你會發現凍結一個對象的操作頻繁出現。
我們先來看凍結對象的第一層實現 —— 擴展特性鎖:
他包含了兩個基本方法:
Object.isExtensible
Object.preventExtensions
如果一個對象可以添加新的屬性,則這個對象是可擴展的。擴展特性鎖就是讓這個對象變的不可擴展,也就是不能再有新的屬性。
Object.isExtensibleMDN上內容概述:
概述 Object.isExtensible() 方法判斷一個對象是否是可擴展的(是否可以在它上面添加新的屬性)。 語法 Object.isExtensible(obj) 參數 obj 需要檢測的對象
例如,我們正常使用對象字面量聲明的對象都是可擴展的:
var person1 = {}; person1.name = "Lucas"; console.log(person1); // {name: "Lucas"}
同時:
Object.isExtensible(person1) === true; // true
你可能要問了,那么使用Object.create方法聲明的對象,并對該對象屬性進行配置是什么情況呢?
我們知道,用上面對象字面量聲明的對象相當于:
var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : true, //不可配置 enumerable : true , //可枚舉 writable : true //可寫 } });
即便嘗試將configurable設置為false:
var person1 = Object.create({},{ "name":{ value : "Lucas", configurable : false, //不可配置 enumerable : true, //可枚舉 writable : true //可寫 } });
仍然得到:
Object.isExtensible(person1) === true; // trueObject.preventExtensions
當然,我們還是有方法可以使得一個對象變的不可擴展。
MDN上內容概述:
概述 Object.preventExtensions() 方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。 語法 Object.preventExtensions(obj) 參數 obj 將要變得不可擴展的對象
幾個注意點包括但不限于:
不可擴展的對象的屬性通常仍然可以被刪除。
嘗試給一個不可擴展對象添加新屬性的操作將會失敗,不過可能是靜默失敗,也可能會拋出 TypeError 異常(嚴格模式下)。
Object.preventExtensions 只能阻止一個對象不能再添加新的自身屬性,仍然可以為該對象的原型添加屬性。
比如:
var person1 = { name: "Lucas" } Object.preventExtensions(person1); person1.age = 18; // 非嚴格模式下,這里不會有報錯,屬于靜默失敗 person1.age // undefined // 擴展新屬性失敗了
仍然可以向原型鏈添加屬性:
person1.__proto__.age = 18; person1.age // 18 // 可以從原型鏈上取到
同樣也可以復寫一些屬性:
person1.name = "Eros"; person1.name // "Eros"
也可以刪除已有屬性:
person1.name; // "Eros", delete person1.name; person1.name; // undefined
通過以上方法,我們實現了對一個對象屬性擴展的凍結。但是同樣也認識到,這并不是全面的保護:例如可以隨意改動去覆蓋已有屬性,在對象原型鏈上增加屬性也還是難以屏蔽。
衣帶漸寬終不悔 為伊消得人憔悴第二種境界:“衣帶漸寬終不悔,為伊消得人憔悴。”
這引用的是北宋柳永《蝶戀花》最后兩句詞,原詞是表現作者對愛的艱辛和愛的無悔。若把“伊”字理解為詞人所追求的理想和畢生從事的事業,亦無不可。王國維則別有用心,以此兩句來比喻成大事業、大學問者,不是輕而易舉,隨便可得的,必須堅定不移,經過一番辛勤勞動,廢寢忘食,孜孜以求,直至人瘦帶寬也不后悔。
下面介紹一個更深一層的做法:密封特性。
密封對象是指那些不能添加新的屬性,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性(enumerable)、可配置性(configurable)、可寫性(writable),但可能可以修改已有屬性的值的對象。
他同樣包含了兩個基本方法:
Object.isSealed
Object.seal
Object.isSealedMDN上內容概述:
概述 Object.isSealed() 方法判斷一個對象是否是密封的(sealed)。 語法 Object.isSealed(obj) 參數 obj 將要檢測的對象
正常對象字面量聲明的對象是不被密封的:
var person1 = { name: "Lucas" } Object.isSealed(person1); // false
當將這個對象禁止擴展時,它也不會變成密封的:
var person1 = { name: "Lucas" } Object.preventExtensions(person1); Object.isSealed(person1); // false
但是在此基礎上,使用Object.defineProperty方法,把屬性變得不可配置(configurable),則這個對象也就成了密封對象:
var person1 = { name: "Lucas" } Object.defineProperty(person1, "name", {configurable : false}); Object.isSealed(person1); // true
此時,我們有:
Object.getOwnPropertyDescriptor(person1, "name"); // 得到: Object { value: "Lucas", writable: true, enumerable: true, configurable: false }
根據這個getOwnPropertyDescriptor,我們可以更加深入的理解密封特性:被密封的對象,就是在不可擴展基礎上講屬性描述符configurable設置為false; 同時,被密封的對象,仍然有機會改變屬性的值。只不過對于此對象本身而言,不可以再擴展新的屬性,不可以更改已有屬性的配置信息。
Object.seal相對應我們也有一個方法將一個對象密封。
MDN上內容概述:
概述 Object.seal() 方法可以讓一個對象密封,并返回被密封后的對象。 語法 Object.seal(obj) 參數 obj 將要被密封的對象
比如:
var person1 = { name: "Lucas" } Object.getOwnPropertyDescriptor(person1, "name"); // 得到: Object { value: "Lucas", writable: true, enumerable: true, configurable: true }
將此對象密封后:
Object.seal(person1); Object.getOwnPropertyDescriptor(person1, "name"); // 得到: Object { value: "Lucas", writable: true, enumerable: true, configurable: false }
也就是說:
person1.age = 18; person1.age; // undefined // 擴展新屬性失敗 // 同時調用defineProperty失敗 Object.defineProperty(person1,"name",{get : function(){return "g";}}); // 拋出異常
任何除更改屬性值以外的操作,非嚴格模式下都會靜默失敗,如上并如下:
delete person1.name; person1.name; // "Lucas"
而更改屬性值可以成功:
person1.name = "Eros"; person1.name; // "Eros"
怎么理解這樣的現象呢?牢記,被密封的對象擁有如下的屬性描述符:
Object { value: "Lucas", writable: true, enumerable: true, configurable: false }
而刪除屬性屬于configurable,更改屬性才屬于writable;
一點延伸借助于此,我們其實已經可以完成凍結對象的第三重境界:達到即密封又不可修改原屬性值。因為可以這樣做:
var person1 = {name: "Lucas"}; Object.defineProperty(person1, "name", {configurable: false, writable: false}); Object.preventExtensions(o);
總結下就是設置:
configurable: false + writable: false + preventExtensions
或者因為
configurable: false+ preventExtensions = seal
所以也可以設置:
眾里尋他千百度,驀然回首,那人卻在,燈火闌珊處seal + writable: false
第三種境界:“眾里尋他千百度,驀然回首,那人卻在,燈火闌珊處。”
這是引用南宋辛棄疾《青玉案》詞中的最后四句。梁啟超稱此詞“自憐幽獨,傷心人別有懷抱”。這是借詞喻事,與文學賞析已無交涉。王國維已先自表明,“吾人可以無勞糾葛”。他以此詞最后的四句為“境界”之第三,即最終最高境界。
這雖不是辛棄疾的原意,但也可以引出悠悠的遠意:做學問、成大事業者,要達到第三境界,必須有專注的精神。反復追尋、研究,下足功夫,自然會豁然貫通,有所發現,有所發明,就能夠從必然王國進入自由王國。
上邊那種凍結對象的方法,其實也有原生實現,可謂:“眾里尋他千百度,驀然回首,那人卻在,燈火闌珊處”
我們這里所說的一個對象的凍結(frozen)是指它不可擴展,所有屬性都是不可配置的(non-configurable),且所有數據屬性(data properties)都是不可寫的(non-writable)。
或者說,凍結對象是指那些不能添加新的屬性,不能修改已有屬性的值,不能刪除已有屬性,以及不能修改已有屬性的可枚舉性、可配置性、可寫性的對象。也就是說,這個對象永遠是不可變的。
同樣,包含了兩個基本方法:
Object.isFrozen
Object.freeze
Object.isFrozenMDN上內容概述:
概述 Object.isFrozen() 方法判斷一個對象是否被凍結(frozen)。 語法 Object.isFrozen(obj) 參數 obj 被檢測的對象Object.freeze 方法
MDN上內容概述:
概述 Object.freeze() 方法可以凍結一個對象。 語法 Object.freeze(obj) 參數 obj 將要被凍結的對象
可以先理解為,這是最高一層的凍結對象:
var person1 = { name: "Lucas" } Object.freeze(person1);
此時,我們有:
Object.getOwnPropertyDescriptor(person1, "name") Object { value: "Lucas", writable: false, enumerable: true, configurable: false } // 對凍結對象的任何操作都會失敗 person1.name = "Eros"; // 改寫屬性值,非嚴格模式下靜默失敗; person1.age = 18; // 擴展屬性值,非嚴格模式下靜默失敗; Object.defineProperty(person1,"name",{value: "Eros"}); // 使用defineProperty會直接報錯
改寫屬性值,擴展新屬性,調用defineProperty,全部都會失敗。
但是,這種層面的凍結,只是淺凍結。如果對象里面還嵌套有對象,那么這個內部對象絲毫不受影響。
var person1 = { name: "Lucas", family: { brother: "Eros" } } Object.freeze(person1); person1.family.brother = "Tim"; person1.family.brother // "Tim"終極實現
那么,如果我們想深層次凍結一個對象呢?思路和深拷貝暗合,使用遞歸:
Object.prototype.deepFreeze = Object.prototype.deepFreeze || function (o){ var prop, propKey; Object.freeze(o); // 首先凍結第一層對象 for (propKey in o){ prop = o[propKey]; if(!o.hasOwnProperty(propKey) || !(typeof prop === "object") || Object.isFrozen(prop)){ continue; } deepFreeze(prop); // 遞歸 } }
這樣子,我們再回過頭來看:
var person1 = { name: "Lucas", family: { brother: "Eros" } } Object.deepFreeze(person1); person1.family.brother = "Tim"; person1.family.brother // "Eros"
已經達到了深層次對象屬性的凍結。
總結本文先后介紹了關于凍結一個對象的三種進階方法。他們層層遞進,卻又相互關聯。關系如圖:
文章部分概念粘取了MDN語法介紹和Tomson的文章。
在《文學小言》一文中,王國維把上述三境界說成“三種之階級”。并說:“未有不閱第一第二階級而能遽躋第三階級者,文學亦然。此有文學上之天才者,所以又需莫大之修養也。”
與大家共勉。
Happy coding!
PS: 作者Github倉庫,歡迎通過代碼各種形式交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83378.html
摘要:王國維在人間詞話里談到了治學經驗,他說古今之成大事業大學問者,必經過三種之境界。其中談到中凍結一個對象幾種由淺入深的實踐。王國維已先自表明,吾人可以無勞糾葛。總結本文先后介紹了關于凍結一個對象的三種進階方法。 王國維在《人間詞話》里談到了治學經驗,他說:古今之成大事業、大學問者,必經過三種之境界。 巧合的是,最近受 git chat / git book 邀請,做了一個分享。其中談到J...
摘要:程序員英語提升指南深入理解內部機制即將到來,現在該考慮新的打包方案了嘛凍結對象的人間詞話完美實現究竟有幾層境界關于響應式的另一種思考 程序員英語提升指南 深入理解 Node Stream 內部機制 ES6 modules 即將到來,現在該考慮新的打包方案了嘛? JS 凍結對象的《人間詞話》 完美實現究竟有幾層境界? 關于響應式的另一種思考 Best websites a program...
摘要:從能力上分,一個是搬運工,一個是設計者能寫代碼是愚公移山為什么說能寫代碼是愚公移山呢我們中國大部分程序員都應該處于一個初級程序員的水平,怎么講只有少數的程序員處于中高級水平。 導語:你知道普通程序員和優秀程序員之間的差距嗎?其實答案很簡單,那就是「愚公移山」和「女媧補天」之間的區別。 之所以提這個話題,跟前兩天在微信群里的討論有關,年后本該是跳槽、找工作的高峰月份,各公司面試邀約應該很...
摘要:地址為什么使用遷移學習根據聯合創始人斯坦福副教授吳恩達介紹,遷移學習將會成為機器學習商業成就的下一驅動力。遷移學習是一種機器學習技術,允許在特定的數據集上再利用已訓練的卷積神經網絡,并將其調整或遷移到其他數據集。 GitHub 地址:https://github.com/miguelgfierro/sciblog_support/blob/master/A_Gentle_Introducti...
閱讀 2879·2021-11-24 09:39
閱讀 3130·2021-11-19 10:00
閱讀 1535·2021-10-27 14:17
閱讀 1811·2021-10-14 09:43
閱讀 961·2021-09-03 10:30
閱讀 3413·2019-08-30 15:54
閱讀 2728·2019-08-30 13:05
閱讀 2006·2019-08-30 11:02