ホームページ > 記事 > ウェブフロントエンド > ES6 でのdefineProperty と proxy の詳細な紹介 (コード例)
この記事では、ES6 の defineProperty と proxy について詳しく説明します (コード例)。必要な方は参考にしていただければ幸いです。
私たちは皆、多かれ少なかれ「データ バインディング」という言葉を聞いたことがあるでしょう。「データ バインディング」の鍵はデータの変更を監視することですが、そのようなオブジェクトの場合は次のようになります。 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
プロパティと値を直接追加することもできますが、この方法を使用すると、より多くの構成を実行できます。
関数の 3 番目のパラメーター記述子によって表される属性記述子には、データ記述子とアクセス記述子の 2 つの形式があります。
どちらも次の 2 つのキー値を持ちます。:
configurable
属性の構成可能値が When である場合に限り、次の 2 つのキー値を持ちます。 true の場合、プロパティ記述子は変更または削除できます。デフォルトは false です。
enumerable
プロパティの列挙可能性が true の場合に限り、プロパティはオブジェクトの列挙プロパティに表示されます。デフォルトは false です。
データ記述子には、次のオプションのキー値もあります:
value
これに対応する値属性 。任意の有効な JavaScript 値 (数値、オブジェクト、関数など) を指定できます。デフォルトは未定義です。
writable
プロパティの writable が true である場合に限り、プロパティは代入演算子によって変更できます。デフォルトは false です。
アクセス記述子には、次のオプションのキー値もあります:
get
属性に指定される 1 つgetter メソッドは、getter が存在しない場合は未定義です。このメソッドの戻り値が属性値として使用されます。デフォルトは未定義です。
set
プロパティにセッターを提供するメソッド。セッターがない場合は未定義になります。このメソッドは一意のパラメータを受け入れ、パラメータの新しい値をプロパティに割り当てます。デフォルトは未定義です。
注目に値する:
属性記述子は、データ記述子またはアクセス記述子の 2 つの形式のいずれかでなければなりません。両方同時に。 これは、次のことができることを意味します:
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; } });
さらに、すべての属性記述子はオプションですが、記述子はフィールドは必須です。設定を何も実行しない場合は、次のようにすることができます。
var obj = Object.defineProperty({}, "num", {}); console.log(obj.num); // undefined
Setters と Getters
defineProperty について説明する理由は、次のとおりです。アクセス記述子で get および set を使用します。これら 2 つのメソッドはゲッターおよびセッターとも呼ばれます。ゲッターとセッターによって定義されたプロパティは「アクセサー プロパティ」と呼ばれます。
プログラムがアクセサー プロパティの値をクエリするとき、JavaScript はゲッター メソッドを呼び出します。このメソッドの戻り値は、属性アクセス式の値です。プログラムがアクセサー プロパティの値を設定すると、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('button').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('container').innerHTML = newValue; } }); document.getElementById('button').addEventListener("click", function() { obj.value += 1; });
コードは増加するように見えますが、span タグの値を直接変更する必要がある場合は、 obj.value の値を変更するだけです。
ただし、現在の書き方では、obj.value の値を格納する別の変数を宣言する必要があります。これは、セット内で obj.value = newValue
を直接実行する場合があるためです。 、無限ループに陥ってしまいます。さらに、多くの属性値の変更を監視する必要がある場合があります。それらを 1 つずつ記述するのは面倒なので、単純に監視関数を記述します。使用の効果は次のとおりです:
var obj = { value: 1 } watch(obj, "num", function(newvalue){ document.getElementById('container').innerHTML = newvalue; }) document.getElementById('button').addEventListener("click", function(){ obj.value += 1 });
この監視関数を作成しましょう:
(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('设置 get 操作') return obj[prop]; }, set: function(obj, prop, value) { console.log('设置 set 操作') 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] === '_') { return false; } return key in target; } }; var target = { _prop: 'foo', prop: 'foo' }; var proxy = new Proxy(target, handler); console.log('_prop' in proxy); // false
又比如说 apply 方法拦截函数的调用、call 和 apply 操作。
apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,不过这里我们简单演示一下:
var target = function () { return 'I am the target'; }; var handler = { apply: function () { return 'I am the proxy'; } }; var p = new Proxy(target, handler); p(); // "I am the proxy"
又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。
let target = { _bar: 'foo', _prop: 'bar', prop: 'baz' }; let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== '_'); } }; 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 == 'value') document.getElementById('container').innerHTML = newvalue; }) document.getElementById('button').addEventListener("click", function() { newObj.value += 1 });
我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。
以上がES6 でのdefineProperty と proxy の詳細な紹介 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。