Object.defineProperty
语法
-
Object.defineProperty() 是 JavaScript 中用于定义或修改对象的属性的方法,可以控制属性的特性 (如可枚举性、可配置性、可写性等);
-
Object.defineProperty(obj, prop, descriptor) 方法的语法如下:
- obj:要在其上定义属性的对象;
- prop:要定义或修改的属性的名称;
- descriptor:属性的描述符对象,包含属性的特性设置;
属性 描述 configurable 属性是否可配置 (delete 等操作),默认为 false enumerable 属性是否可枚举,默认为 false writable 属性是否可写,默认为 false value 属性的值,默认为 undefined get 获取属性值的函数 set 设置属性值的函数
示例
-
使用案例:
JavaScriptJavaScript// 对 obj 监听,并操作 obj,会出现死循环,循环引用 var obj = {}; Object.defineProperty(obj, "age", { get: function() { return obj.age; }, set: function(newValue) { obj.age = newValue; }, enumerable: true, // 控制属性是否可以枚举,默认值是 false configurable: true, // 控制属性是否可以被删除,默认值是 false // writable:true, // 控制属性是否可以被修改,默认值是 false }); console.log(obj.age); obj.age = 20; // newValue: 20 console.log(obj.age);
// 对 age 监听,并操作 obj,解决前面死循环的问题 var obj = {}; var age = undefined; Object.defineProperty(obj, "age", { get: function () { return age; }, set: function (newValue) { age = newValue; }, enumerable: true, // 控制属性是否可以枚举,默认值是 false configurable: true, // 控制属性是否可以被删除,默认值是 false // writable:true, // 控制属性是否可以被修改,默认值是 false }); console.log(obj.age); // undefined obj.age = 20; console.log(obj.age); // 20
-
实现一个简易的 vue2 响应式:
// Vue2 在初始化时会对数据进行劫持,如果劫持的属性还是对象的话需要递归劫持 function observer(data) { if (typeof data !== 'object' || data == null) return; for (const key in data) { defineReactive(data, key, data[key]); } } function defineReactive(obj, key, value) { observer(value); Object.defineProperty(obj, key, { get() { return value; }, set(newValue) { if (newValue === value) return; observer(newValue); value = newValue; } }) } // 测试 var data = { name: 'ES', lession: 'ES6 Wiki', obj: { a: 1 } } observer(data);
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义 (如属性查找即读取、赋值、枚举、函数调用等);
Proxy 用于修改某些操作的默认行为,等同于在语言层面作出修改,所以属于一种 ‘元编程 meta programming’,即对编程语言进行编程;
- 可以理解成,在目标对象之前架设一层 ‘拦截’,外界对该对象的访问,都必须先通过这层拦截 (想象成明星身边的经纪人,想要接触明星,首先需要通过经纪人)
- 因此,提供了一种机制,可以对外界的访问进行过滤和改写;
语法
-
Proxy() 只能通过 new 关键字来调用;
-
new Proxy(target, handler) 方法的语法如下:
- target:Proxy 会对目标 (target) 对象进行包装,它可以是任何类型的对象,包括原生的数组、函数甚至是另一个代理对象;
- handler:一个对象,其属性是定义了在对代理执行操作时的行为的函数;
handler ⽅法 默认调⽤ 功能 get Reflect.get() 获取对象身上某个属性的值 set Reflect.set() 在对象上设置属性 has Reflect.has() 判断一个对象是否存在某个属性 deleteProperty Reflect.deleteProperty() 删除对象上的属性 getProperty Reflect.getPrototypeOf() 获取指定对象原型的函数 setProperty Reflect.setPrototypeOf() 设置或改变对象原型的函数 isExtensible Reflect.isExtensible() 判断一个对象是否可扩展 (即是否能够添加新的属性) preventExtensions Reflect.preventExtensions() 阻止新属性添加到对象 getOwnPropertyDescriptor Reflect.getOwnPropertyDescriptor() 获取给定属性的属性描述符 defineProperty Reflect.defineProperty() 定义或修改一个对象的属性 ownKeys Reflect.ownKeys() 返回由目标对象自身的属性键组成的数组 apply Reflect.apply() 对一个函数进行调用操作,同时可以传入一个数组作为调用参数 construct Reflect.construct() 对构造函数进行 new 操作,实现创建类的实例 preventExtensions Reflect.preventExtensions() 阻止新属性添加到对象
示例
-
使用案例:
JavaScriptJavaScriptJavaScriptJavaScriptJavaScriptJavaScriptJavaScriptJavaScriptvar p = new Proxy( {}, { get: function (target, prop, receiver) { console.log("called: " + prop); return 10; }, }, ); console.log(p.a); // "called: a"; ouptut 10
var p = new Proxy( {}, { set: function (target, prop, value, receiver) { target[prop] = value; console.log("property set: " + prop + " = " + value); return true; }, }, ); console.log("a" in p); // false p.a = 10; // "property set: a = 10" console.log("a" in p); // true console.log(p.a); // 10
var p = new Proxy( {}, { has: function (target, prop) { console.log("called: " + prop); return true; }, }, ); console.log("a" in p); // "called: a"; outputs true
var p = new Proxy( {}, { deleteProperty: function (target, prop) { console.log("called: " + prop); return true; }, }, ); delete p.a; // "called: a"
var p = new Proxy( function () { }, { apply: function (target, thisArg, argumentsList) { console.log("called: " + argumentsList.join(", ")); return argumentsList[0] + argumentsList[1] + argumentsList[2]; }, } ); console.log(p(1, 2, 3)); // "called: 1, 2, 3"; outputs 6
var p = new Proxy( {}, { construct: function (target, argumentsList, newTarget) { return {}; }, }, ); new p(); // TypeError is thrown, "p" is not a constructor
var p = new Proxy( {}, { isExtensible: function (target) { console.log("called"); return true; // 也可以 return 1; 等表示为 true 的值 }, }, ); console.log(Object.isExtensible(p)); // "called"; outputs true
var p = new Proxy( {}, { ownKeys: function (target) { console.log("called"); return ["a", "b", "c"]; }, }, ); console.log(Object.getOwnPropertyNames(p)); // "called"; outputs [ 'a', 'b', 'c' ]
-
Proxy 实现响应式
// Vue3 中摘出来的 reactive 函数的实现,可以直观地看到没有对 target 进行递归循环去创建观察对象 function reactive(target) { return createReactiveObject(target) } function createReactiveObject(target) { // 判断如果不是一个对象的话返回 if (!isObject(target)) return target; // target观察前的原对象; proxy观察后的对象:observed observed = new Proxy(target, { get(target, key, receiver) { const res = target[key]; console.log('获取值:', res); // todo: 收集依赖... return isObject(res) ? reactive(res) : res; }, set(target, key, value, receiver) { // 当对 obj 下的 a 属性设置值时,执行 get 函数,这是为什么呢? // - 这就是 Proxy 的优点,在对 obj 下属性设置值时,首先需要调用 set 方法获取 target 下 obj 的值,然后判断 obj 又是一个对象再去调用 reactive 函数进行观察; // - 这样就不需要递归地去对嵌套数据进行观察了,而是在获取值的时候,判断获取的值是不是一个对象,这样极大地节约了资源; target[key] = value; } }) return observed; } // 测试 var target = { name: 'ES', lession: 'ES6 Wiki', obj: { a: 1 } } var p = reactive(target); console.log(p.name); // 获取值: ES p.obj.a = 10; // 获取值: {a : 1} console.log(p.obj.a); // 获取值: {a : 10}
区别与比较
-
Proxy 可以监视读写以外的操作;
-
Proxy 可以很方便的监视数组操作
const list = []; const listProxy = new Proxy(target, { get(target, key, receiver) { // return target[key] return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { // target[key] = value // 使用该种方式严格模式下会报错 return Reflect.set(target, key, value, receiver) }, deleteProperty(target, key) { // delete target[key] // 使用该种方式严格模式下会报错 return Reflect.deleteProperty(target, key) } }) listProxy.push(100);
-
Proxy 不需要侵入对象
// Object.defineProperty 方式需要递归迭代对象 // Proxy 方式更为合理 const person = { name: 'tom', age: 20 } const personProxy = new Proxy(person, { get(target, property) { console.log('get', property) // return target[property] return Reflect.get(target, property); }, set(target, property, value) { console.log('set', property, value) // target[property] = value // 使用该种方式严格模式下会报错 Reflect.set(target, property, value); } }) personProxy.name = 'jack' console.log(personProxy.name)
应用案例
观察者模式
<!-- Object.defineProperty 方式实现 -->
<div id="container"></div>
<script>
// 创建一个观察者
function observer(target) {
const div = document.getElementById("container");
const ob = {};
const props = Object.keys(target);
for (const prop of props) {
Object.defineProperty(ob, prop, {
get() {
return target[prop];
},
set(val) {
target[prop] = val;
render();
},
enumerable: true,
configurable: true,
})
}
render();
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html += `
<p>
<span>${prop}:</span>
<span>${target[prop]}</span>
</p>`;
}
div.innerHTML = html;
}
return ob;
}
const target = {
a: 1,
b: 2
}
const obj = observer(target);
// 缺点 1:如果 target.c = 3,这里并不会对 c 这个属性做监听
// 缺点 2:对象层次较深的情况下,需要递归处理
// 缺点 3:声明了 target、ob 两个对象,浪费空间,下面用 proxy 改写
</script>
<!-- proxy 方式实现 -->
<div id="container"></div>
<script>
//创建一个观察者
function observer(target) {
const div = document.getElementById("container");
const proxy = new Proxy(target, {
get(target, prop) {
return Reflect.get(target, prop);
},
set(target, prop, value) {
Reflect.set(target, prop, value);
render();
},
})
render();
function render() {
let html = "";
for (const prop of Object.keys(target)) {
html += `
<p>
<span>${prop}:</span>
<span>${target[prop]}</span>
</p>`;
}
div.innerHTML = html;
}
return proxy;
}
const target = {
a: 1,
b: 2
}
const obj = observer(target);
</script>
偷懒的构造函数
// 代理 class,不再需要在 construct 里面赋值操作了(减少重复代码)
function ConstructorProxy(Class, ...propNames) {
return new Proxy(
Class,
{
construct(target, argumentsList) {
const obj = Reflect.construct(target, argumentsList)
propNames.forEach((name, i) => {
obj[name] = argumentsList[i];
})
return obj;
}
}
);
}
class User { }
const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age")
const obj = new UserProxy("张", "三", 18);
console.log(obj)
class Monster { }
const MonsterProxy = ConstructorProxy(Monster, "attack", "defence", "hp", "rate", "name")
const m = new MonsterProxy(10, 20, 100, 30, "怪物");
console.log(m);
验证函数参数
function validatorFunction(func, ...types) {
return new Proxy(
func,
{
apply(target, thisArgument, argumentsList) {
types.forEach((t, i) => {
const arg = argumentsList[i]
if (typeof arg !== t) {
throw new TypeError(`第${i + 1}个参数${argumentsList[i]}不满足类型${t}`);
}
})
return Reflect.apply(target, thisArgument, argumentsList);
}
}
);
}
function sum(a, b) {
return a + b;
}
const sumProxy = validatorFunction(sum, "number", "number");
console.log(sumProxy(1, 2));
计算机网络🛜 jwt
上一篇