이 기사는 ES6의 DefineProperty 및 프록시에 대한 자세한 소개(코드 예제)를 제공합니다. 이는 특정 참고 가치가 있으므로 도움이 될 수 있습니다.
우리 모두 "데이터 바인딩"이라는 단어를 어느 정도 들어본 적이 있을 것입니다. "데이터 바인딩"의 핵심은 데이터의 변경 사항을 모니터링하는 것이지만 이러한 개체의 경우 var obj = {value: 1} code입니다. >, obj가 변경되었는지 어떻게 알 수 있나요? <code>var obj = {value: 1}
,我们该怎么知道 obj 发生了改变呢?
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
之所以讲到 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 }]
既然可以监控数据的改变,那我可以这样设想,即当数据改变的时候,自动进行渲染工作。举个例子:
HTML 中有个 span 标签和 button 标签
e729387c80c75e8349ab06d70943ab81154bdf357c58b8a65c66d7c19c8e4d114 be83eb51f340b921e483a6b46a36fe5f点击加 165281c5ac262bf6d81768915a4a77ac0
当点击按钮的时候,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 的值,因为如果你在 set 中直接 obj.value = newValue
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; })()예: 🎜
var proxy = new Proxy(target, handler);🎜 속성과 값을 직접 추가할 수 있지만 이 방법을 사용하면 더 많은 변경이 가능합니다. 다양한 구성. 🎜🎜함수의 세 번째 매개변수 설명자로 표현되는 속성 설명자는 데이터 설명자 및 액세스 설명자의 두 가지 형식을 갖습니다. 🎜🎜둘 다 다음 두 가지 키 값을 가집니다: 🎜🎜구성 가능🎜🎜속성의 구성 가능이 true인 경우에만 속성 설명자가 삭제될 수 있습니다. 기본값은 거짓입니다.
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🎜 또한 다음을 수행할 수 있습니다. 🎜
// 使用 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🎜 그러나 다음은 수행할 수 없습니다. 🎜
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"🎜 또한 모든 속성 설명자는 선택 사항이지만 그렇지 않은 경우 설명자 필드는 필수입니다. 🎜
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"🎜Setters and Getters🎜🎜 우리가 DefineProperty에 대해 이야기하는 이유는 액세스 설명자에서 get 및 set을 사용해야 하기 때문입니다. 이 두 가지 방법을 getter 및 setter라고도 합니다. getter 및 setter에 의해 정의된 속성을 "접근자 속성"이라고 합니다. 🎜🎜프로그램이 접근자 속성 값을 쿼리하면 JavaScript는 getter 메서드를 호출합니다. 이 메소드의 반환 값은 속성 액세스 표현식의 값입니다. 프로그램이 접근자 속성의 값을 설정하면 JavaScript는 setter 메서드를 호출하여 할당 표현식 오른쪽의 값을 매개변수로 setter에 전달합니다. 어떤 의미에서 이 메서드는 속성 값을 "설정"하는 역할을 합니다. setter 메서드의 반환 값은 무시할 수 있습니다. 🎜🎜예: 🎜
(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 });🎜이것이 우리가 데이터 변경 사항을 모니터링하려는 방법이 아닌가요? 다시 캡슐화해 보겠습니다. 🎜rrreee🎜watch API🎜🎜데이터 변경 사항을 모니터링할 수 있으므로 데이터가 변경되면 자동으로 렌더링 작업이 수행될 것이라고 상상할 수 있습니다. 예: 🎜🎜HTML에는 스팬 태그와 버튼 태그가 있습니다🎜rrreee🎜버튼을 클릭하면 스팬 태그의 값이 1씩 증가합니다. 🎜🎜전통적인 접근 방식은 다음과 같습니다. 🎜rrreee🎜defineProperty를 사용하는 경우: 🎜rrreee🎜코드가 늘어나는 것 같지만, SPAN 태그의 값을 변경해야 하는 경우 obj.value의 값을 직접 수정할 수 있습니다. 🎜🎜그러나 현재 작성 방식으로는 여전히 obj.value의 값을 저장하기 위해 별도의 변수를 선언해야 합니다. 왜냐하면 set에 직접
obj.value = newValue
를 입력하면 결과가 떨어지기 때문입니다. 무한 루프에 빠진다. 또한, 여러 속성 값의 변경 사항을 하나씩 모니터링해야 할 수도 있으므로 간단히 watch 함수를 작성합니다. 효과는 다음과 같습니다. 🎜rrreee🎜 다음 감시 함수를 작성해 보겠습니다. 🎜rrreee🎜 이제 객체의 속성 값 변경을 모니터링하고 속성 값 변경에 따라 콜백 함수를 추가할 수 있습니다~🎜🎜proxy🎜🎜. DefineProperty를 사용하면 속성 읽기(가져오기) 및 설정(설정) 동작만 재정의할 수 있습니다. ES6에서는 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 来弥补。
我们使用 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 및 프록시에 대한 자세한 소개(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!