This article brings you a detailed introduction (code example) about defineProperty and proxy in ES6. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
We have all heard the word "data binding" more or less. The key to "data binding" is to monitor changes in data, but for such an object: var obj = {value: 1}
, how do we know that obj has changed?
ES5 provides the Object.defineProperty method, which can define a new property on an object, or modify an existing property of an object, and return the object.
Syntax
Object.defineProperty(obj, prop, descriptor)
Parameters
obj: 要在其上定义属性的对象。 prop: 要定义或修改的属性的名称。 descriptor: 将被定义或修改的属性的描述符。
For example:
var obj = {}; Object.defineProperty(obj, "num", { value : 1, writable : true, enumerable : true, configurable : true }); // 对象 obj 拥有属性 num,值为 1
Although we can add it directly Properties and values, but this way we can do more configuration.
The attribute descriptor represented by the third parameter descriptor of the function has two forms: data descriptor and access descriptor.
Both have the following two key values:
configurable
If and only if the attribute's configurable is When true, the property descriptor can be changed or deleted. Default is false.
enumerable
If and only if the enumerable of the property is true, the property can appear in the enumeration property of the object. Default is false.
The data descriptor also has the following optional key values:
value
The value corresponding to this attribute . Can be any valid JavaScript value (number, object, function, etc.). Default is undefined.
writable
The property can be changed by the assignment operator if and only if the property's writable is true. Default is false.
The access descriptor also has the following optional key values:
get
One provided for the attribute Getter method, if there is no getter, it is undefined. The return value of this method is used as the attribute value. Default is undefined.
set
A method that provides a setter for a property. If there is no setter, it will be undefined. This method will accept a unique parameter and assign the parameter's new value to the property. Default is undefined.
It is worth noting:
The attribute descriptor must be one of two forms: data descriptor or access descriptor, not both at the same time . This means that you can:
Object.defineProperty({}, "num", { value: 1, writable: true, enumerable: true, configurable: true });
You can also:
var value = 1; Object.defineProperty({}, "num", { get : function(){ return value; }, set : function(newValue){ value = newValue; }, enumerable : true, configurable : true });
But you cannot:
// 报错 Object.defineProperty({}, "num", { value: 1, get: function() { return 1; } });
In addition, all attribute descriptors are optional, But the descriptor field is required. If you don’t configure anything, you can do this:
var obj = Object.defineProperty({}, "num", {}); console.log(obj.num); // undefined
The reason why we talk about defineProperty is because we want to use the access descriptor get and set, these two methods are also called getter and setter. Properties defined by getters and setters are called "accessor properties".
When a program queries the value of an accessor property, JavaScript calls the getter method. The return value of this method is the value of the attribute access expression. When a program sets the value of an accessor property, JavaScript calls the setter method, passing the value on the right side of the assignment expression as a parameter to the setter. In a sense, this method is responsible for "setting" the property value. The return value of the setter method can be ignored.
For example:
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
Isn’t this the method we want to monitor data changes? Let’s encapsulate it again:
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 }]
Since we can monitor data changes, I can imagine that when the data changes, the rendering work will be automatically performed. For example:
There is a span tag and a button tag in HTML
<span id="container">1</span> <button id="button">点击加 1</button>
When the button is clicked, the value in the span tag is increased by 1.
The traditional approach is:
document.getElementById('button').addEventListener("click", function(){ var container = document.getElementById("container"); container.innerHTML = Number(container.innerHTML) + 1; });
If defineProperty is used:
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; });
The code seems to increase, but when we need to change the value in the span tag, directly Just modify the value of obj.value.
However, with the current way of writing, we still need to declare a separate variable to store the value of obj.value, because if you directly obj.value = newValue
in the set, you will fall into an infinite loop. middle. In addition, we may need to monitor changes in many attribute values. It would be tiring to write them one by one, so we simply write a watch function. The effect of use is as follows:
var obj = { value: 1 } watch(obj, "num", function(newvalue){ document.getElementById('container').innerHTML = newvalue; }) document.getElementById('button').addEventListener("click", function(){ obj.value += 1 });
Let’s write this watch function:
(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; })()
Now we can monitor changes in object attribute values, and add callback functions based on changes in attribute values, great Great~
Using defineProperty can only redefine the read (get) and set (set) behaviors of properties. In ES6, Proxy is provided, and more behaviors can be redefined. , such as in, delete, function call and more behaviors.
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 来弥补。
我们使用 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 的实例才可以触发拦截。
The above is the detailed content of Detailed introduction to defineProperty and proxy in ES6 (code example). For more information, please follow other related articles on the PHP Chinese website!