如果我在构造函数中使用 Proxy:get,为什么 mocha chai 不应该证明 `return this` 的身份?
Why does mocha chai should not prove identity for `return this`, if I use Proxy:get in the constructor?
我想写一个 class,它处理未定义的属性。我还希望 return this
能够链接方法来创建领域特定语言 (DSL)。
I return 来自构造函数的代理,用于处理未定义的属性。现在在测试实例时,确实发生了 return this
不能证明与实例相同的情况。我担心由此产生的错误,尽管我可以按预期链接这些方法。
这是一个 mocha chai 测试来显示行为。将最后一条指令中的 o.that().should.not.equal(o);
替换为 o.that().should.equal(o);
,看看它是如何失败的。
require('chai').should();
describe('chai testing classes using Proxy', () => {
it('asserts object identity, if an instance method returns this', () => {
const o = new class NormalClass{ }
o.that = function() { return this; }
o.that().should.equal(o);
});
it('observes the same behaviour for constructors returning a dummy Proxy', () => {
const o = new class ProxyClass{
constructor() { return new Proxy(this, {}); }
}
o.that = function() { return this; }
o.that().should.equal(o);
});
it('requires deep.equal on the other hand, if the Proxy handles get', () => {
const o = new class ProxyClassPlusGet{
constructor() {
return new Proxy(this, {
get: function(target, prop) { return target[prop]; },
});
}
}
o.that = function() { return this; }
o.that().should.deep.equal(o);
o.that().should.not.equal(o);
});
});
只要 o.that() === o
产生 true
.
,您的实施就可以工作
但它不适用于 getter,这会干扰 chai 的 should
。您可以使用
重现此内容
const o = new Proxy({
get self() { return this; },
that() { return this; },
}, {
get(target, prop) { return target[prop]; },
});
console.log(o.self === o);
console.log(o.that() === o);
这是为什么?因为你的get
陷阱被打破了,忽略了 of the property access. It will hold the proxy, o
, but when you do return target[prop]
then target
will be the receiver. You can fix it by using Reflect.get
:
const o = new Proxy({
get self() { return this; },
that() { return this; },
}, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
// ^^^^^^^^
},
});
console.log(o.self === o);
console.log(o.that() === o);
除了@Bergi 给出的简明答案外,我还添加了另一个测试。事实证明,他的解决方案表现出良好的行为:
- 它returns在所有情况下都是代理对象的身份。
- 它将数据存储到底层原始对象中。
- 它不会与作为接收方的代理形成恶性循环。
- 它与 chai expect 和 chai should.
一致
我仍然不完全理解代理的内容。做这个测试,已经给出了深刻的见解。感谢@Bergi 提供正确的解决方案。
require('chai').should();
const expect = require('chai').expect;
describe('general Proxy behaviour', () => {
describe('Proxy with empty handler', () => {
it('shows that original and proxy are two different objects' , () => {
const original = {};
const proxy = new Proxy(original, {});
expect(proxy).not.equal(original);
});
it('evaluates proxy and origianl to be deep equal' , () => {
const original = {};
const proxy = new Proxy(original, {});
expect(proxy).to.deep.equal(original);
});
it('sets and gets down to the original', () => {
const original = {};
const proxy = new Proxy(original, {});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('consistent when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, { });
it('evaluates getters to the proxy', () => {
expect(proxy.that).to.be.equal(proxy)
});
it('evaluates methods to the proxy, too', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should works like chai expect', () => {
proxy.that.should.equal(proxy)
proxy.getThat().should.equal(proxy)
});
});
});
describe('Proxy without reflection', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
set: function(target, prop, value) { target[prop]=value; },
get: function(target, prop) { return target[prop]; },
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
it('detects original as the target in get and set',
() => {
const original = {};
let targetInSet;
let targetInGet;
const proxy = new Proxy(original, {
set: function(target, prop, value, receiver) {
targetInSet = target;
target[prop]=value;
},
get: function(target, prop, receiver) {
targetInGet = target;
expect(target).to.equal(original);
return target[prop];
},
});
proxy.x = 1;
proxy.x;
expect(targetInSet).to.equal(original);
expect(targetInGet).to.equal(original);
});
it('detects proxy as the receiver in get and set',
() => {
const original = {};
let receiverInSet;
let receiverInGet;
const proxy = new Proxy(original, {
set: function(target, prop, value, receiver) {
receiverInSet = receiver;
target[prop]=value;
},
get: function(target, prop, receiver) {
receiverInGet = receiver;
return target[prop];
},
});
proxy.x = 1;
proxy.x;
expect(receiverInSet).to.equal(proxy);
expect(receiverInGet).to.equal(proxy);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
receiverInGet = receiver;
return target[prop];
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy without the receiver being set', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop) {
return Reflect.get(target, prop);
},
set: function(target, prop, value) {
Reflect.set(target, prop, value);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop);
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy with the receiver being set to original', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, target);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, target);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, target);
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy with the receiver being set to proxy', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, receiver);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
it('does not cause a vicious circle in the proxy', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, receiver);
},
});
proxy.x = 1;
expect(proxy.x).to.equal(1);
});
describe('consistent when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
});
it('evaluates getters to the proxy', () => {
expect(proxy.that).to.be.equal(proxy)
});
it('evaluates methods to the proxy, too', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should works like chai expect', () => {
proxy.that.should.equal(proxy)
proxy.getThat().should.equal(proxy)
});
});
});
});
我想写一个 class,它处理未定义的属性。我还希望 return this
能够链接方法来创建领域特定语言 (DSL)。
I return 来自构造函数的代理,用于处理未定义的属性。现在在测试实例时,确实发生了 return this
不能证明与实例相同的情况。我担心由此产生的错误,尽管我可以按预期链接这些方法。
这是一个 mocha chai 测试来显示行为。将最后一条指令中的 o.that().should.not.equal(o);
替换为 o.that().should.equal(o);
,看看它是如何失败的。
require('chai').should();
describe('chai testing classes using Proxy', () => {
it('asserts object identity, if an instance method returns this', () => {
const o = new class NormalClass{ }
o.that = function() { return this; }
o.that().should.equal(o);
});
it('observes the same behaviour for constructors returning a dummy Proxy', () => {
const o = new class ProxyClass{
constructor() { return new Proxy(this, {}); }
}
o.that = function() { return this; }
o.that().should.equal(o);
});
it('requires deep.equal on the other hand, if the Proxy handles get', () => {
const o = new class ProxyClassPlusGet{
constructor() {
return new Proxy(this, {
get: function(target, prop) { return target[prop]; },
});
}
}
o.that = function() { return this; }
o.that().should.deep.equal(o);
o.that().should.not.equal(o);
});
});
只要 o.that() === o
产生 true
.
但它不适用于 getter,这会干扰 chai 的 should
。您可以使用
const o = new Proxy({
get self() { return this; },
that() { return this; },
}, {
get(target, prop) { return target[prop]; },
});
console.log(o.self === o);
console.log(o.that() === o);
这是为什么?因为你的get
陷阱被打破了,忽略了o
, but when you do return target[prop]
then target
will be the receiver. You can fix it by using Reflect.get
:
const o = new Proxy({
get self() { return this; },
that() { return this; },
}, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
// ^^^^^^^^
},
});
console.log(o.self === o);
console.log(o.that() === o);
除了@Bergi 给出的简明答案外,我还添加了另一个测试。事实证明,他的解决方案表现出良好的行为:
- 它returns在所有情况下都是代理对象的身份。
- 它将数据存储到底层原始对象中。
- 它不会与作为接收方的代理形成恶性循环。
- 它与 chai expect 和 chai should. 一致
我仍然不完全理解代理的内容。做这个测试,已经给出了深刻的见解。感谢@Bergi 提供正确的解决方案。
require('chai').should();
const expect = require('chai').expect;
describe('general Proxy behaviour', () => {
describe('Proxy with empty handler', () => {
it('shows that original and proxy are two different objects' , () => {
const original = {};
const proxy = new Proxy(original, {});
expect(proxy).not.equal(original);
});
it('evaluates proxy and origianl to be deep equal' , () => {
const original = {};
const proxy = new Proxy(original, {});
expect(proxy).to.deep.equal(original);
});
it('sets and gets down to the original', () => {
const original = {};
const proxy = new Proxy(original, {});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('consistent when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, { });
it('evaluates getters to the proxy', () => {
expect(proxy.that).to.be.equal(proxy)
});
it('evaluates methods to the proxy, too', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should works like chai expect', () => {
proxy.that.should.equal(proxy)
proxy.getThat().should.equal(proxy)
});
});
});
describe('Proxy without reflection', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
set: function(target, prop, value) { target[prop]=value; },
get: function(target, prop) { return target[prop]; },
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
it('detects original as the target in get and set',
() => {
const original = {};
let targetInSet;
let targetInGet;
const proxy = new Proxy(original, {
set: function(target, prop, value, receiver) {
targetInSet = target;
target[prop]=value;
},
get: function(target, prop, receiver) {
targetInGet = target;
expect(target).to.equal(original);
return target[prop];
},
});
proxy.x = 1;
proxy.x;
expect(targetInSet).to.equal(original);
expect(targetInGet).to.equal(original);
});
it('detects proxy as the receiver in get and set',
() => {
const original = {};
let receiverInSet;
let receiverInGet;
const proxy = new Proxy(original, {
set: function(target, prop, value, receiver) {
receiverInSet = receiver;
target[prop]=value;
},
get: function(target, prop, receiver) {
receiverInGet = receiver;
return target[prop];
},
});
proxy.x = 1;
proxy.x;
expect(receiverInSet).to.equal(proxy);
expect(receiverInGet).to.equal(proxy);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
receiverInGet = receiver;
return target[prop];
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy without the receiver being set', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop) {
return Reflect.get(target, prop);
},
set: function(target, prop, value) {
Reflect.set(target, prop, value);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop);
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy with the receiver being set to original', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, target);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, target);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
describe('chaos when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, target);
},
});
it('evaluates getters to the original', () => {
expect(proxy.that).to.be.equal(original)
});
it('evaluates methods to the proxy', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should differs from chai expect', () => {
expect(proxy.getThat()).to.be.equal(proxy)
proxy.getThat().should.equal(original)
});
it('chai should evaluates to original in both cases', () => {
proxy.getThat().should.equal(original)
proxy.that.should.equal(original)
});
});
});
describe('Reflect in Proxy with the receiver being set to proxy', () => {
it('sets and gets down to the original like the empty handler', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, receiver);
},
});
proxy.x = 1;
expect(original.x).to.equal(1);
original.x = 2;
expect(proxy.x).to.equal(2);
});
it('does not cause a vicious circle in the proxy', () => {
const original = {};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
Reflect.set(target, prop, value, receiver);
},
});
proxy.x = 1;
expect(proxy.x).to.equal(1);
});
describe('consistent when accessing this', () => {
const original = {
getThat() { return this; },
get that() { return this; },
};
const proxy = new Proxy(original, {
get: function(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
});
it('evaluates getters to the proxy', () => {
expect(proxy.that).to.be.equal(proxy)
});
it('evaluates methods to the proxy, too', () => {
expect(proxy.getThat()).to.be.equal(proxy)
});
it('chai should works like chai expect', () => {
proxy.that.should.equal(proxy)
proxy.getThat().should.equal(proxy)
});
});
});
});