// Object.defineProperty的第一個缺陷,無法監聽數組變化 如這種數組改變方式 list[0] = xx; 對象的話是 var obj = {a: 1} obj.b = 2; Vue.set() / this.$set() /* 以下八種方法Vue是可以檢測到數組變化的進行了數組方法重載 push() pop() // 刪除並返回數組的最后一個元素 shift() // 把數組的第一個元素從其中刪除,並返回第一個元素的值 unshift() // 向數組的開頭添加一個或更多元素,並返回新的長度 splice() sort() reverse() */
// 這是將要被劫持的對象
const obj = { name: 'bob', age: 25 }; function test(age) { if (age === 25) { console.log('這是我的年齡'); } else if (age > 25) { console.log('油膩大叔'); } else { console.log('風花雪月的日子一去不復返了'); } } // 遍歷對象,對其屬性值進行劫持
Object.keys(obj).forEach(function(key) { // 語法 Object.defineProperty(obj, prop, descriptor) obj 要定義屬性的對象。prop 要定義或修改的屬性的名稱或 Symbol, descriptor要定義或修改的屬性描述符。
/*描述符默認值匯總 擁有布爾值的鍵 configurable、enumerable 和 writable 的默認值都是 false。 屬性值和函數的鍵 value、get 和 set 字段的默認值為 undefined */ Object.defineProperty(obj, key, { enumerable: true, // 當且僅當該屬性的 enumerable 鍵值為 true 時,該屬性才會出現在對象的枚舉屬性中。默認為 false
configurable: true, // 當且僅當該屬性的 configurable 鍵值為 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除,默認false。
get: function() { console.log('get'); }, set: function(newVal) { // 當屬性值發生變化時我們可以進行額外操作
console.log(`新值${newVal}`); // document.getElementById('input').value = newVal; // 例如雙向綁定原理 輸入框的 e.target.value;
test(newVal) } }) }) obj.age = 26; console.log(obj.age, 'obj.age') /* 輸出結果 新值26 油膩大叔 get undefined "obj.age" */
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div>es6</div>
<div id="app">
<input type="text" id="input" />
<div>輸入框的值: <span id="title"></span></div>
<button type="button" name="button" id="btn">添加到list</button>
<ul id="list"></ul>
</div>
<script type="text/javascript">
/* Object.defineProperty() 的問題主要有三個 不能監聽數組的變化 必須遍歷對象的每個屬性 必須深層遍歷嵌套的對象 */
// Proxy語法糖 優勢 解決了vue2.0 Object.defineProperty 沒解決的幾個問題 Proxy 的第二個參數可以有 13 種攔截方法
// target:[目標值], key:[目標的key值], value:[要改變的值], receiver:[改版前的原始值]
/* 針對對象:針對整個對象,而不是對象的某個屬性 支持數組:不需要對數組的方法進行重載,省去了眾多 hack 嵌套支持(本質也是不支持嵌套的): get 里面遞歸調用 Proxy 並返回 */
// 劣勢:Proxy 的兼容性不如 Object.defineProperty() 不能使用 polyfill 來處理兼容性
// var proxy = new Proxy(target, handler);
const obj = {}; const inp = document.getElementById("input"); const title = document.getElementById("title"); // 監聽對象
// Reflect.get():獲取對象身上某個屬性的值,類似於 target[name]。
// Reflect.set():將值分配給屬性的函數,返回一個Boolean,如果更新成功,則返回true。
const newObj = new Proxy(obj, { get: function(target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); // Reflect.get 和 Reflect.set 可以理解為類繼承里的 super,即調用原來的方法
}, set: function(target, key, value, receiver) { console.log(target, key, value, receiver, '對象劫持'); if (key === "text") { inp.value = value; title.innerHTML = value; } return Reflect.set(target, key, value, receiver); } }); inp.addEventListener("keyup", function(e) { newObj.text = e.target.value; }); // 渲染list列表
const render = { // 初始化
init: function(arr) { // document.createDocumentFragment 創建一個新的空白的文檔片段因為文檔片段存在於內存中,並不在DOM樹中,所以將子元素插入到文檔片段時不會引起頁面回流(對元素位置和幾何上的計算)。
// 因此,使用文檔片段通常會帶來更好的性能
const fragment = document.createDocumentFragment(); for (let i = 0; i < arr.length; i++) { const li = document.createElement("li"); li.textContent = arr[i]; fragment.appendChild(li); } list.appendChild(fragment); }, addItem: function(val) { const li = document.createElement("li"); li.textContent = val; list.appendChild(li); } }; // 監聽數組
const arr = []; const newArr = new Proxy(arr, { get: function(target, key, receiver) { return Reflect.get(target, key, receiver); }, set: function(target, key, value, receiver) { console.log(target, key, value, receiver,'數組的劫持響應'); if (key !== "length") { render.addItem(value); } return Reflect.set(target, key, value, receiver); } }); // 初始化
window.onload = function() { render.init(arr); }; btn.addEventListener("click", function() { newArr.push(parseInt(newObj.text)); }); </script>
</body>
</html>