Proxy与Reflect

Proxy概述

Proxy用于创建对象的代理。

什么是Proxy

// Proxy代理对象,拦截并自定义基本操作
const target = {
    name: "东巴文",
    age: 25
};

const handler = {
    get(target, prop) {
        console.log(`读取属性: ${prop}`);
        return target[prop];
    }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name);
// 读取属性: name
// 东巴文

Proxy语法

// new Proxy(target, handler)
// target: 要代理的目标对象
// handler: 定义拦截操作的对象

const target = { x: 1, y: 2 };
const handler = {};

const proxy = new Proxy(target, handler);
console.log(proxy.x);  // 1(无拦截,直接访问目标)

Proxy拦截操作

Proxy支持的各种拦截操作。

get拦截

const target = { name: "东巴文" };

const proxy = new Proxy(target, {
    get(target, prop, receiver) {
        console.log(`获取: ${prop}`);
        
        if (prop in target) {
            return target[prop];
        }
        
        return "属性不存在";
    }
});

console.log(proxy.name);   // 东巴文
console.log(proxy.unknown); // 属性不存在

set拦截

const target = {};

const proxy = new Proxy(target, {
    set(target, prop, value, receiver) {
        console.log(`设置: ${prop} = ${value}`);
        
        if (typeof value === "number") {
            target[prop] = value;
            return true;
        }
        
        throw new TypeError("值必须是数字");
    }
});

proxy.age = 25;  // 设置: age = 25
console.log(proxy.age);  // 25

// proxy.name = "东巴文";  // TypeError

has拦截

const target = {
    name: "东巴文",
    _secret: "hidden"
};

const proxy = new Proxy(target, {
    has(target, prop) {
        if (prop.startsWith("_")) {
            return false;  // 隐藏私有属性
        }
        return prop in target;
    }
});

console.log("name" in proxy);    // true
console.log("_secret" in proxy); // false

deleteProperty拦截

const target = {
    name: "东巴文",
    _protected: "不可删除"
};

const proxy = new Proxy(target, {
    deleteProperty(target, prop) {
        if (prop.startsWith("_")) {
            throw new Error("不能删除受保护的属性");
        }
        delete target[prop];
        return true;
    }
});

delete proxy.name;  // 成功
// delete proxy._protected;  // Error

apply拦截

function sum(a, b) {
    return a + b;
}

const proxy = new Proxy(sum, {
    apply(target, thisArg, args) {
        console.log(`调用函数,参数: ${args}`);
        return target.apply(thisArg, args);
    }
});

console.log(proxy(1, 2));  // 调用函数,参数: 1,2
                            // 3

construct拦截

class Person {
    constructor(name) {
        this.name = name;
    }
}

const ProxyPerson = new Proxy(Person, {
    construct(target, args) {
        console.log(`创建实例,参数: ${args}`);
        return new target(...args);
    }
});

const person = new ProxyPerson("东巴文");
// 创建实例,参数: 东巴文

其他拦截操作

const target = {};

const proxy = new Proxy(target, {
    // 获取属性描述符
    getOwnPropertyDescriptor(target, prop) {
        console.log(`获取属性描述符: ${prop}`);
        return Object.getOwnPropertyDescriptor(target, prop);
    },
    
    // 获取原型
    getPrototypeOf(target) {
        console.log("获取原型");
        return Object.getPrototypeOf(target);
    },
    
    // 设置原型
    setPrototypeOf(target, proto) {
        console.log("设置原型");
        return Object.setPrototypeOf(target, proto);
    },
    
    // 是否可扩展
    isExtensible(target) {
        console.log("检查可扩展性");
        return Object.isExtensible(target);
    },
    
    // 禁止扩展
    preventExtensions(target) {
        console.log("禁止扩展");
        return Object.preventExtensions(target);
    },
    
    // 获取自有属性键
    ownKeys(target) {
        console.log("获取属性列表");
        return Object.keys(target);
    }
});

Proxy应用场景

Proxy的实际应用。

数据验证

function createValidator(schema) {
    return function(target) {
        return new Proxy(target, {
            set(target, prop, value) {
                if (prop in schema) {
                    const validator = schema[prop];
                    
                    if (!validator(value)) {
                        throw new Error(`无效的${prop}值: ${value}`);
                    }
                }
                
                target[prop] = value;
                return true;
            }
        });
    };
}

const validateUser = createValidator({
    name: v => typeof v === "string" && v.length > 0,
    age: v => typeof v === "number" && v >= 0 && v <= 150,
    email: v => typeof v === "string" && v.includes("@")
});

const user = validateUser({});
user.name = "东巴文";  // 有效
user.age = 25;        // 有效
// user.age = 200;    // Error

响应式对象

function reactive(target) {
    const callbacks = new Set();
    
    const proxy = new Proxy(target, {
        get(target, prop) {
            return target[prop];
        },
        
        set(target, prop, value) {
            const oldValue = target[prop];
            target[prop] = value;
            
            if (oldValue !== value) {
                callbacks.forEach(cb => cb(prop, value, oldValue));
            }
            
            return true;
        }
    });
    
    proxy.watch = function(callback) {
        callbacks.add(callback);
        return () => callbacks.delete(callback);
    };
    
    return proxy;
}

const state = reactive({ count: 0 });

state.watch((prop, newValue, oldValue) => {
    console.log(`${prop}: ${oldValue} -> ${newValue}`);
});

state.count = 1;  // count: 0 -> 1
state.count = 2;  // count: 1 -> 2

私有属性

function withPrivate(target) {
    return new Proxy(target, {
        get(target, prop) {
            if (prop.startsWith("_")) {
                throw new Error("不能访问私有属性");
            }
            return target[prop];
        },
        
        set(target, prop, value) {
            if (prop.startsWith("_")) {
                throw new Error("不能设置私有属性");
            }
            target[prop] = value;
            return true;
        },
        
        has(target, prop) {
            if (prop.startsWith("_")) {
                return false;
            }
            return prop in target;
        },
        
        ownKeys(target) {
            return Object.keys(target).filter(key => !key.startsWith("_"));
        }
    });
}

const obj = withPrivate({
    name: "东巴文",
    _password: "secret"
});

console.log(obj.name);      // 东巴文
// console.log(obj._password);  // Error

缓存代理

function createCache(fn) {
    const cache = new Map();
    
    return new Proxy(fn, {
        apply(target, thisArg, args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                console.log("从缓存返回");
                return cache.get(key);
            }
            
            console.log("计算并缓存");
            const result = target.apply(thisArg, args);
            cache.set(key, result);
            return result;
        }
    });
}

const expensiveCalculation = createCache((n) => {
    console.log("执行复杂计算...");
    return n * n;
});

console.log(expensiveCalculation(5));  // 计算并缓存,25
console.log(expensiveCalculation(5));  // 从缓存返回,25

Reflect概述

Reflect是与Proxy配合使用的API。

Reflect方法

const obj = { name: "东巴文", age: 25 };

// Reflect方法与Proxy拦截器一一对应

// 获取属性
console.log(Reflect.get(obj, "name"));  // 东巴文

// 设置属性
Reflect.set(obj, "age", 26);
console.log(obj.age);  // 26

// 检查属性
console.log(Reflect.has(obj, "name"));  // true

// 删除属性
Reflect.deleteProperty(obj, "age");
console.log(obj.age);  // undefined

// 获取属性描述符
console.log(Reflect.getOwnPropertyDescriptor(obj, "name"));

// 获取原型
console.log(Reflect.getPrototypeOf(obj));

// 设置原型
Reflect.setPrototypeOf(obj, { x: 1 });
console.log(obj.x);  // 1

// 是否可扩展
console.log(Reflect.isExtensible(obj));  // true

// 禁止扩展
Reflect.preventExtensions(obj);

// 获取自有属性键
console.log(Reflect.ownKeys(obj));

Reflect与Object对比

// Reflect方法返回布尔值,更易处理错误

// Object.defineProperty抛出错误
try {
    Object.defineProperty(Object.freeze({}), "x", { value: 1 });
} catch (e) {
    console.log("定义失败");
}

// Reflect.defineProperty返回false
const success = Reflect.defineProperty(Object.freeze({}), "x", { value: 1 });
console.log(success);  // false

// Object.setPrototypeOf抛出错误
try {
    Object.setPrototypeOf(Object.freeze({}), {});
} catch (e) {
    console.log("设置原型失败");
}

// Reflect.setPrototypeOf返回false
const result = Reflect.setPrototypeOf(Object.freeze({}), {});
console.log(result);  // false

Proxy与Reflect配合

Proxy和Reflect配合使用。

标准代理模式

const target = { name: "东巴文" };

const proxy = new Proxy(target, {
    get(target, prop, receiver) {
        console.log(`获取: ${prop}`);
        return Reflect.get(target, prop, receiver);
    },
    
    set(target, prop, value, receiver) {
        console.log(`设置: ${prop} = ${value}`);
        return Reflect.set(target, prop, value, receiver);
    },
    
    has(target, prop) {
        console.log(`检查: ${prop}`);
        return Reflect.has(target, prop);
    },
    
    deleteProperty(target, prop) {
        console.log(`删除: ${prop}`);
        return Reflect.deleteProperty(target, prop);
    }
});

proxy.name;           // 获取: name
proxy.age = 25;       // 设置: age = 25
"name" in proxy;      // 检查: name
delete proxy.age;     // 删除: age

完整代理示例

function observe(target, callback) {
    return new Proxy(target, {
        get(target, prop, receiver) {
            const value = Reflect.get(target, prop, receiver);
            
            // 如果是对象,递归代理
            if (typeof value === "object" && value !== null) {
                return observe(value, callback);
            }
            
            return value;
        },
        
        set(target, prop, value, receiver) {
            const oldValue = Reflect.get(target, prop, receiver);
            
            if (oldValue !== value) {
                callback(prop, value, oldValue);
            }
            
            return Reflect.set(target, prop, value, receiver);
        },
        
        deleteProperty(target, prop) {
            const oldValue = target[prop];
            callback(prop, undefined, oldValue);
            return Reflect.deleteProperty(target, prop);
        }
    });
}

const state = observe(
    { user: { name: "东巴文" } },
    (prop, newValue, oldValue) => {
        console.log(`变化: ${prop}, ${oldValue} -> ${newValue}`);
    }
);

state.user.name = "db-w.cn";  // 变化: name, 东巴文 -> db-w.cn

下一步

掌握了Proxy与Reflect后,让我们继续学习:

  1. Map与Set - 学习集合类型
  2. 模块化概述 - 学习模块化开发
  3. CommonJS - 学习CommonJS规范

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:Proxy和Reflect是ES6提供的元编程能力,可以拦截和自定义对象的基本操作,是实现响应式框架、数据验证等高级功能的基础。在 db-w.cn,我们帮你掌握元编程技术!