一本书里面内容较多, 因此分成了多篇 Post, 可以从此处看到相关文章:
Metaprogramming 意指使用 JS 原生的一些方法实现一些高级的步骤
使用相同的值重新定义 symbol 将会得到两个完全不同的示例 Symbol('a') !== Symbol('a')
Symbol 创建的示例有自己的域 (Local or Global)
对于 Local Symbol, 只有得到 Symbol 的引用才能使用这个 Symbol
Object.getOwnPropertySymbols
来查看已经定义的 Symbol 但是依然无法访问和修改const clone = {……obj};
obj[symFoo] === clone[symFoo]; // true
对于 Global Symbol 可以在任何位置引用到
/* Local */
const symFoo = Symbol('foo');
/* Global */
const symFoo = Symbol.for('foo'); /* 注意使用的方法不同 */
Symbol.keyFor(symFoo);// 'foo'
就因为 Symbol 有这些神奇的功能, 所以可以考虑用来定义一个不可见的方法.
const _count = Symbol("count");
class Counter {
constructor(count) {
Object.defineProperty(this, _count, { enumerable: false, writable: true });
this[_count] = count;
}
inc(by = 1) {
return (this[_count] += by);
}
dec(by = 1) {
return (this[_count] -= by);
}
}
// Outside this class, there's no way to access the internal count property:
const counter = new Counter(1);
counter._count; // undefined
counter.count; // undefined
counter[Symbol('count')]; // undefined
counter[Symbol.for("count")]; // undefined
另外, 这边需要注意一下通过一些特定的 api (比如
Reflect.ownKeys
andObject.getOwnPropertySymbols
) 还是可以找到这个方法.
可以定义一个 Symbol, 使得在使用的时候进行判断其是否存在
export const toJson = (obj) => {
/* 如果 obj 含有一个特定签名的方法则调用此方法, 否则调用默认方法 */
return isFunction(obj[Symbol.for("toJson")])
? obj[Symbol.for("toJson")]()
: JSON.stringify(obj);
};
const obj = {
a: 1,
b: 2,
};
const proxy = new Proxy(obj, {
get: function (target, property) {
return 3;
},
});
obj.a // 1
proxy.a // 3
proxy.abcdef // 3 全部被代理并改写成 3
proxy // {a:1, b:2} 但是这个地方很神奇的, 依然显示 1 和 2
const traceLogHandler = {
get(target, key) {
/* target = credentials */
console.log( `${(new Date())} [TRACE] Calling: ${key}` );
return target[key];
},
};
const credentials = {
username: "@luijar",
password: "Som3thingR@ndom",
login: () => {
console.log("Logging in……");
},
};
const credentials$Proxy = new Proxy(credentials, traceLogHandler);
credentials$Proxy.login();
const passwordObfuscatorHandler = {
get(target, key) {
/* 将密码替换成无意义的字符 */
if (key === "password" || key === "pwd") {
return "X".repeat(10);
}
return target[key];
},
has(target, key) {
/* 将这个属性从 Proxy 中移除 */
if (key === "password" || key === "pwd") {
return false;
}
return true;
},
};
credentials$Proxy.password; // 'XXXXXXXXXX'
/* has 方法移除了这个 key */
'password' in credentials$Proxy;
如果希望有多个 Proxy 嵌套那么就可以考虑使用 compose 方法:
const credentials$Proxy = new Proxy(
new Proxy(credentials, passwordObfuscatorHandler),
traceLogHandler
);
转换成
const weave = curry((handler, target) => new Proxy(target, handler));
const tracer = weave(traceLogHandler); // 使用方法: tracer(target)
const obfuscator = weave(passwordObfuscatorHandler); // 使用方法: obfuscator(target)
const credentials$Proxy = compose(tracer, obfuscator)(credentials);
// 或者可以使用更方便,更简洁的 Pipeline Operator:
const credentials$Proxy = credentials |> obfuscator |> tracer;
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
const handler = (……props) => ({
/* 在这个 proxy 里面设定 set 方法 */
set(target, key) {
/* 如果我 set 了一个特定的 field */
if (props.includes(key)) {
debugger
/* 调用默认的 set 方法 */
Reflect.set(……arguments);
/* 使用被代理的 obj 的内置方法, target.data 当做参数传递进去 */
const value = Reflect.apply(target.test, target, [target.data]);
/* 给 target 的 value 设置计算后的值 */
Reflect.set(target, "value", value);
return true;
}
},
});
const block = {
test: (v) => v + 1,
};
/* 可以看到这个地方首先创建了一个 proxy */
const smartBlock = new Proxy(block, handler("data"));
smartBlock.data = 3; // 如此一来在每一次设置 data 参数的时候就会自动更新 value
smartBlock.value; // 4
smartBlock.data = 4;
smartBlock.value; // 5
const handler = (……props) => ({
set(target, key) {
if (props.includes(key)) {
Reflect.set(……arguments);
const value = Reflect.apply(target.test, target, [target.data]);
Reflect.set(target, "value", value);
return true;
}
},
});
const block = {
test: (v) => v + 1,
};
/* 使用的时候有一些很小的区别, 这里不使用 new 进行构造 */
const smartBlock = Proxy.revocable(block, handler("data"));
smartBlock.data = 3; // 如此一来在每一次设置 data 参数的时候就会自动更新 value
smartBlock.value; // 4
smartBlock.revoke()
smartBlock.data = 4;
smartBlock.value; // 因为 Proxy 被 revoked, 这里不等于 5 了
class Counter {
constructor(count) {
this[_count] = count;
}
inc(by = 0) {
return (this[_count] += by);
}
dec(by = 0) {
return (this[_count] -= by);
}
}
const decorator = {
actions: {
before: () => {},
after: () => {},
},
methods: [],
};
/* 这个地方是 decrator 一个使用的例子. */
const validation = {
actions: { before: checkLimit, after: identity },
methods: ["inc", "dec"],
};
/* 首先这一设计一个简单的 validate 方法 */
const { isFinite, isInteger } = Number;
const checkLimit = (value = 1) =>
isFinite(value) && isInteger(value) && value >= 0
? value
: throw new RangeError("Expected a positive number");
/* Proxy 的 get 方法无法直接获得到 proxied method 的实际调用参数, 因此这个地方设计了一个 Higher-order function */
const decorate = (decorator, obj) =>
new Proxy(obj, {
get(target, key) {
if (!decorator.methods.includes(key)) {
return Reflect.get(……arguments);
}
const methodRef = target[key];
return (……capturedArgs) => {
/* 在这里进行 before 调用 */
const newArgs = decorator.actions?.before.call(target, ……capturedArgs);
const result = methodRef.call(target, ……[newArgs]);
/* 在这里进行 after 调用 */
return decorator.actions?.after.call(target, result);
};
},
});
/* 使用 decorate 这个 higher order function */
const counter$Proxy = decorate(validation, new Counter(3));
counter$Proxy.inc(); // 4
counter$Proxy.inc(3); // 7
counter$Proxy.inc(-3); // RangeError, 直接终止 inc 方法的执行
上面这一段 decorator 会产生以下这样的调用逻辑:
Proxy 虽然非常的有用,但是需要注意的是, 因为其调用逻辑很复杂, 可能造成其他开发者在这地方调试很久很久, 所以这类 metaProgramming 使用的时候要慎重.