目錄
definePropety
Setters 和Getters
watch API
proxy
watch API 优化
首頁 web前端 js教程 ES6中defineProperty與proxy的詳細介紹(程式碼範例)

ES6中defineProperty與proxy的詳細介紹(程式碼範例)

Nov 15, 2018 pm 04:59 PM
handler javascript 互動設計 函數 前端

本篇文章帶給大家的內容是關於ES6中defineProperty與proxy的詳細介紹(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

我們或多或少都聽過「資料綁定」這個詞,「資料綁定」的關鍵在於監聽資料的變化,可是對於這樣一個物件:var obj = {value: 1},我們該怎麼知道obj 發生了改變呢?

definePropety

ES5 提供了 Object.defineProperty 方法,該方法可以在一個物件上定義一個新屬性,或修改一個物件的現有屬性,並傳回這個物件。

語法

Object.defineProperty(obj, prop, descriptor)
登入後複製

參數

obj: 要在其上定义属性的对象。
prop:  要定义或修改的属性的名称。
descriptor: 将被定义或修改的属性的描述符。
登入後複製

舉例:

var obj = {};
Object.defineProperty(obj, "num", {
    value : 1,
    writable : true,
    enumerable : true,
    configurable : true
});
//  对象 obj 拥有属性 num,值为 1
登入後複製

雖然我們可以直接加入屬性和值,但是使用這種方式,我們可以進行更多的配置。

函數的第三個參數 descriptor 所表示的屬性描述符有兩種形式:資料描述子和存取描述子

兩者皆有下列兩種鍵值

configurable

當且僅當該屬性的 configurable 為true 時,此屬性描述子才能夠被改變,也能夠被刪除。預設為 false。

enumerable

當且僅當此屬性的 enumerable 為 true 時,此屬性才能夠出現在物件的列舉屬性中。預設為 false。

資料描述子同時具有以下可選鍵值

#value

此屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。預設為 undefined。

writable

當且僅當該屬性的 writable 為 true 時,此屬性才能被賦值運算子改變。預設為 false。

訪問描述子同時具有以下可選鍵值

#get

一個給屬性提供getter 的方法,如果沒有 getter 則為 undefined。此方法傳回值被用作屬性值。預設為 undefined。

set

一個提供屬性 setter 的方法,如果沒有 setter 則為 undefined。此方法將接受唯一參數,並將該參數的新值指派給該屬性。預設為 undefined。

值得注意的是:

屬性描述符必須是資料描述符或存取描述符兩種形式之一,不能同時是兩者。 這意味著你可以:

Object.defineProperty({}, "num", {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});
登入後複製

也可以:

var value = 1;
Object.defineProperty({}, "num", {
    get : function(){
      return value;
    },
    set : function(newValue){
      value = newValue;
    },
    enumerable : true,
    configurable : true
});
登入後複製

但是不可以:

// 报错
Object.defineProperty({}, "num", {
    value: 1,
    get: function() {
        return 1;
    }
});
登入後複製

此外,所有的屬性描述子都是非必須的,但是descriptor 這個欄位是必須的,如果不進行任何配置,你可以這樣:

var obj = Object.defineProperty({}, "num", {});
console.log(obj.num); // undefined
登入後複製

Setters 和Getters

之所以講到defineProperty,是因為我們要使用存取描述符中的get 和set,這兩個方法又被稱為getter 和setter。由 getter 和 setter 定義的屬性稱做」存取器屬性「。

當程式查詢存取器屬性的值時,JavaScript 呼叫 getter方法。這個方法的回傳值就是屬性存取表達式的值。當程式設定一個存取器屬性的值時,JavaScript 呼叫 setter 方法,將賦值表達式右邊的值當作參數傳入 setter。從某種意義上講,這個方法負責「設定」屬性值。可以忽略 setter 方法的回傳值。

舉個例子:

var obj = {}, value = null;
Object.defineProperty(obj, "num", {
    get: function(){
        console.log('执行了 get 操作')
        return value;
    },
    set: function(newValue) {
        console.log('执行了 set 操作')
        value = newValue;
    }
})

obj.value = 1 // 执行了 set 操作

console.log(obj.value); // 执行了 get 操作 // 1
登入後複製

這不就是我們要的監控資料改變的方法嗎?我們再來封裝一下:

function Archiver() {
    var value = null;
    // archive n. 档案
    var archive = [];

    Object.defineProperty(this, 'num', {
        get: function() {
            console.log('执行了 get 操作')
            return value;
        },
        set: function(value) {
            console.log('执行了 set 操作')
            value = value;
            archive.push({ val: value });
        }
    });

    this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.num; // 执行了 get 操作
arc.num = 11; // 执行了 set 操作
arc.num = 13; // 执行了 set 操作
console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]
登入後複製

watch API

既然可以監控資料的改變,那我可以這樣設想,也就是當資料改變的時候,自動進行渲染工作。舉個例子:

HTML 中有一個 span 標籤和 button 標籤

<span id="container">1</span>
<button id="button">点击加 1</button>
登入後複製

當點擊按鈕的時候,span 標籤裡的值加 1。

傳統的做法是:

document.getElementById(&#39;button&#39;).addEventListener("click", function(){
    var container = document.getElementById("container");
    container.innerHTML = Number(container.innerHTML) + 1;
});
登入後複製

如果使用了defineProperty:

var obj = {
    value: 1
}

// 储存 obj.value 的值
var value = 1;

Object.defineProperty(obj, "value", {
    get: function() {
        return value;
    },
    set: function(newValue) {
        value = newValue;
        document.getElementById(&#39;container&#39;).innerHTML = newValue;
    }
});

document.getElementById(&#39;button&#39;).addEventListener("click", function() {
    obj.value += 1;
});
登入後複製

程式碼看似增多了,但是當我們需要改變span 標籤裡的值的時候,直接修改obj.value 的值就可以了。

然而,現在的寫法,我們還需要單獨宣告一個變數儲存obj.value 的值,因為如果你在set 中直接 obj.value = newValue 就會陷入無限的循環中。另外,我們可能需要監控很多屬性值的改變,要是一個一個寫,也很累吶,所以我們簡單寫個 watch 函數。使用效果如下:

var obj = {
    value: 1
}

watch(obj, "num", function(newvalue){
    document.getElementById(&#39;container&#39;).innerHTML = newvalue;
})

document.getElementById(&#39;button&#39;).addEventListener("click", function(){
    obj.value += 1
});
登入後複製

我們來寫下這個watch 函數:

(function(){
    var root = this;
    function watch(obj, name, func){
        var value = obj[name];

        Object.defineProperty(obj, name, {
            get: function() {
                return value;
            },
            set: function(newValue) {
                value = newValue;
                func(value)
            }
        });

        if (value) obj[name] = value
    }

    this.watch = watch;
})()
登入後複製

現在我們已經可以監控物件屬性值的改變,並且可以根據屬性值的改變,添加回呼函數,棒棒噠~

proxy

使用defineProperty 只能重定義屬性的讀取(get)和設定(set)行為,到了ES6,提供了Proxy,可以重定義更多的行為,例如in、delete、函數呼叫等更多行為。

Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:

var proxy = new Proxy(target, handler);
登入後複製

proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

var proxy = new Proxy({}, {
    get: function(obj, prop) {
        console.log(&#39;设置 get 操作&#39;)
        return obj[prop];
    },
    set: function(obj, prop, value) {
        console.log(&#39;设置 set 操作&#39;)
        obj[prop] = value;
    }
});

proxy.time = 35; // 设置 set 操作

console.log(proxy.time); // 设置 get 操作 // 35
登入後複製

除了 get 和 set 之外,proxy 可以拦截多达 13 种操作,比如 has(target, propKey),可以拦截 propKey in proxy 的操作,返回一个布尔值。

// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
  has (target, key) {
    if (key[0] === &#39;_&#39;) {
      return false;
    }
    return key in target;
  }
};
var target = { _prop: &#39;foo&#39;, prop: &#39;foo&#39; };
var proxy = new Proxy(target, handler);
console.log(&#39;_prop&#39; in proxy); // false
登入後複製

又比如说 apply 方法拦截函数的调用、call 和 apply 操作。

apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,不过这里我们简单演示一下:

var target = function () { return &#39;I am the target&#39;; };
var handler = {
  apply: function () {
    return &#39;I am the proxy&#39;;
  }
};

var p = new Proxy(target, handler);

p();
// "I am the proxy"
登入後複製

又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:

  • Object.getOwnPropertyNames()

  • Object.getOwnPropertySymbols()

  • Object.keys()

下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。

let target = {
  _bar: &#39;foo&#39;,
  _prop: &#39;bar&#39;,
  prop: &#39;baz&#39;
};

let handler = {
  ownKeys (target) {
    return Reflect.ownKeys(target).filter(key => key[0] !== &#39;_&#39;);
  }
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
  console.log(target[key]);
}
// "baz"
登入後複製

更多的拦截行为可以查看阮一峰老师的 《ECMAScript 6 入门》

值得注意的是,proxy 的最大问题在于浏览器支持度不够,而且很多效果无法使用 poilyfill 来弥补。

watch API 优化

我们使用 proxy 再来写一下 watch 函数。使用效果如下:

(function() {
    var root = this;

    function watch(target, func) {

        var proxy = new Proxy(target, {
            get: function(target, prop) {
                return target[prop];
            },
            set: function(target, prop, value) {
                target[prop] = value;
                func(prop, value);
            }
        });

        if(target[name]) proxy[name] = value;
        return proxy;
    }

    this.watch = watch;
})()

var obj = {
    value: 1
}

var newObj = watch(obj, function(key, newvalue) {
    if (key == &#39;value&#39;) document.getElementById(&#39;container&#39;).innerHTML = newvalue;
})

document.getElementById(&#39;button&#39;).addEventListener("click", function() {
    newObj.value += 1
});
登入後複製

我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。

以上是ES6中defineProperty與proxy的詳細介紹(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

golang函數動態建立新函數的技巧 golang函數動態建立新函數的技巧 Apr 25, 2024 pm 02:39 PM

Go語言提供了兩種動態函數創建技術:closures和反射。 closures允許存取閉包作用域內的變量,而反射可使用FuncOf函數建立新函數。這些技術在自訂HTTP路由器、實現高度可自訂的系統和建置可插拔的元件方面非常有用。

C++ 函數命名中參數順序的考慮 C++ 函數命名中參數順序的考慮 Apr 24, 2024 pm 04:21 PM

在C++函數命名中,考慮參數順序至關重要,可提高可讀性、減少錯誤並促進重構。常見的參數順序約定包括:動作-物件、物件-動作、語意意義和遵循標準函式庫。最佳順序取決於函數目的、參數類型、潛在混淆和語言慣例。

excel函數公式大全 excel函數公式大全 May 07, 2024 pm 12:04 PM

1. SUM函數,用於對一列或一組單元格中的數字進行求和,例如:=SUM(A1:J10)。 2、AVERAGE函數,用於計算一列或一組儲存格中的數字的平均值,例如:=AVERAGE(A1:A10)。 3.COUNT函數,用於計算一列或一組單元格中的數字或文字的數量,例如:=COUNT(A1:A10)4、IF函數,用於根據指定的條件進行邏輯判斷,並返回相應的結果。

如何在Java中寫出高效和可維護的函數? 如何在Java中寫出高效和可維護的函數? Apr 24, 2024 am 11:33 AM

編寫高效且可維護的Java函數的關鍵在於:保持簡潔。使用有意義的命名。處理特殊情況。使用適當的可見性。

C++ 函式預設參數與可變參數的優缺點比較 C++ 函式預設參數與可變參數的優缺點比較 Apr 21, 2024 am 10:21 AM

C++函數中預設參數的優點包括簡化呼叫、增強可讀性、避免錯誤。缺點是限制靈活性、命名限制。可變參數的優點包括無限彈性、動態綁定。缺點包括複雜性更高、隱式型別轉換、除錯困難。

C++ 函式回傳參考型別有什麼好處? C++ 函式回傳參考型別有什麼好處? Apr 20, 2024 pm 09:12 PM

C++中的函數傳回參考類型的好處包括:效能提升:引用傳遞避免了物件複製,從而節省了記憶體和時間。直接修改:呼叫方可以直接修改傳回的參考對象,而無需重新賦值。程式碼簡潔:引用傳遞簡化了程式碼,無需額外的賦值操作。

自訂 PHP 函數和預定義函數之間有什麼區別? 自訂 PHP 函數和預定義函數之間有什麼區別? Apr 22, 2024 pm 02:21 PM

自訂PHP函數與預定義函數的差異在於:作用域:自訂函數僅限於其定義範圍,而預定義函數可在整個腳本中存取。定義方式:自訂函數使用function關鍵字定義,而預先定義函數則由PHP核心定義。參數傳遞:自訂函數接收參數,而預先定義函數可能不需要參數。擴充性:自訂函數可以根據需要創建,而預定義函數是內建的且無法修改。

C++ 函式異常進階:客製化錯誤處理 C++ 函式異常進階:客製化錯誤處理 May 01, 2024 pm 06:39 PM

C++中的異常處理可透過自訂異常類別增強,提供特定錯誤訊息、上下文資訊以及根據錯誤類型執行自訂操作。定義繼承自std::exception的異常類,提供特定的錯誤訊息。使用throw關鍵字拋出自訂異常。在try-catch區塊中使用dynamic_cast將捕獲到的異常轉換為自訂異常類型。在實戰案例中,open_file函數會拋出FileNotFoundException異常,捕捉並處理該異常可提供更具體的錯誤訊息。

See all articles