一、概述
我們應該都知道 vue
會通過 object.defineProperty
對數據進行劫持,來實現視圖響應數據的變化,然而有些時候我們的組件就是純粹的數據展示,不會有任何改變,我們就不需要 vue
來劫持我們的數據,在大量數據展示的情況下,這能夠很明顯的減少組件初始化的時間,那如何禁止 vue
劫持我們的數據呢?可以通過 object.freeze
方法來凍結一個對象,一旦被凍結的對象就再也不能被修改了。
Object.freeze()
方法可以凍結一個對象。一個被凍結的對象再也不能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象后該對象的原型也不能被修改。freeze()
返回和傳入的參數相同的對象。
比方我們需要渲染一個非常大的數組對象,例如用戶列表,對象列表,文章列表等等。
vue 會將 data 對象中的所有的屬性加入到 vue 的響應式系統中,當這些屬性的值發生改變時,視圖將會產生響應,若對象的體積比較大,會消耗很多瀏覽器解析時間。所以我們可以通過減少數據的響應式轉換來提供前端的性能。
另外需要說明的是:這里只是凍結了 users
的值,引用不會被凍結,所以當我們需要更新數據的時候,我們可以重新給
users
賦值,即更改其引用,那么視圖就會更新。
export default { data: () => ({ users: {} }), async created() { const users = await axios.get("/api/users"); // this.users = users; this.users = Object.freeze(users);
} };
二、object.freeze 定義
在 Vue 的文檔中介紹數據綁定和響應時,特意標注了對於經過 Object.freeze() 方法的對象無法進行更新響應。數據與方法。
Object.freeze() 方法用於凍結對象,禁止對於該對象的屬性進行修改(由於數組本質也是對象
,因此該方法可以對數組使用)。在 Mozilla MDN 中是如下介紹的:
可以凍結一個對象。一個被凍結的對象再也不能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象后該對象的原型也不能被修改
該方法的返回值是其參數本身。需要注意的是以下兩點:
1、Object.freeze() 和 const 變量聲明不同,也不承擔 const 的功能。
const和Object.freeze()完全不同
- const的行為像 let。它們唯一的區別是, const定義了一個無法重新分配的變量。 通過 const聲明的變量是具有塊級作用域的,而不是像 var聲明的變量具有函數作用域。
- Object.freeze()接受一個對象作為參數,並返回一個相同的不可變的對象。這就意味着我們不能添加,刪除或更改對象的任何屬性。
- const和Object.freeze()並不同,const是防止變量重新分配,而Object.freeze()是使對象具有不可變性。
// 深凍結函數.
function deepFreeze(obj) { // 取回定義在obj上的屬性名
var propNames = Object.getOwnPropertyNames(obj); // 在凍結自身之前凍結屬性
propNames.forEach(function(name) { var prop = obj[name]; // 如果prop是個對象,凍結它
if (typeof prop == 'object' && prop !== null) deepFreeze(prop); }); // 凍結自身(no-op if already frozen)
return Object.freeze(obj); }
其實就是個簡單的遞歸方法。但是涉及到一個很重要,但是在寫業務邏輯的時候很少用的知識點 Object.getOwnPropertyNames(obj)
。我們都知道在 JS 的 Object 中存在原型鏈屬性,通過這個方法可以獲取所有的非原型鏈屬性。
三、利用Object.freeze()
提升性能原理
當你把一個普通的 JavaScript 對象傳給 Vue 實例的 data
選項,Vue 將遍歷此對象所有的屬性,並使用 Object.defineProperty 把這些屬性全部轉為 getter/setter,這些 getter/setter 對用戶來說是不可見的,但是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。
但 Vue 在遇到像 Object.freeze()
這樣被設置為不可配置之后的對象屬性時,不會為對象加上 setter getter 等數據劫持的方法。參考 Vue 源碼
1、性能提升效果對比
在基於 Vue 的一個big table benchmark里,可以看到在渲染一個一個 1000 x 10 的表格的時候,開啟Object.freeze()
前后重新渲染的對比。
開啟優化之前


在這個例子里,使用了 Object.freeze()
比不使用快了 4 倍
2、為什么 Object.freeze()
的性能會更好
Object.freeze()
的CPU開銷

使用 Object.freeze()
的CPU開銷
對比可以看出,使用了 Object.freeze()
之后,減少了 observer 的開銷。
3、Object.freeze()
應用場景
由於 Object.freeze()
會把對象凍結,所以比較適合展示類的場景,如果你的數據屬性需要改變,可以重新替換成一個新的 Object.freeze()
的對象。
四、Javascript對象解凍
修改 props 生成的對象是不能修改props的, 但實踐中遇到需要修改props的情況。如果直接修改, js代碼將報錯, 原因是props對象被凍結了, 可以用Object.isFrozen()來檢測, 其結果是true,說明該對象的屬性是只讀的。
那么, 有方法將props對象解凍,從而進行修改嗎?
事實上, 在javascript中, 對象凍結后, 沒有辦法再解凍, 只能通過克隆一個具有相同屬性的新對象, 通過修改新對象的屬性來達到目的。
// 可以這樣
ES6: Object.assign({}, frozenObject); lodash: _.assign({}, frozenObject);
來看實際代碼
function modifyProps(component) { let condictioin = this.props.condictioin, newComponent = Object.assign({}, component), newProps = Object.assign({}, component.props) if (condictioin) { if (condictioin.add) newProps.add = true
if (condictioin.del) newProps.del = true } newComponent.props = newProps return newComponent }
鎖定對象的方法
- Object.preventExtensions()
no new properties or methods can be added to the project 對象不可擴展, 即不可以新增屬性或方法, 但可以修改/刪除
- Object.seal()
same as prevent extension, plus prevents existing properties and methods from being deleted 在上面的基礎上,對象屬性不可刪除, 但可以修改
- Object.freeze()
same as seal, plus prevent existing properties and methods from being modified 在上面的基礎上,對象所有屬性只讀, 不可修改
以上三個方法分別可用Object.isExtensible(), Object.isSealed(), Object.isFrozen()來檢測
五、總結
Object.freeze()是ES5新增的特性,可以凍結一個對象,防止對象被修改。
vue 1.0.18+對其提供了支持,對於data或vuex里使用freeze凍結了的對象,vue不會做getter和setter的轉換。如果你有一個巨大的數組或Object,並且確信數據不會修改,使用Object.freeze()可以讓性能大幅提升。
在我的實際開發中,這種提升大約有5~10倍,倍數隨着數據量遞增。並且,Object.freeze()凍結的是值,而不是引用,所以你仍然可以將變量的引用替換掉來達到更新視圖的目的。
vue的文檔沒有寫上這個特性,但這是個非常實用的做法,對於純展示的大數據,都可以使用Object.freeze提升性能。