首页 > 解决方案 > `Proxy` 将 `this[toString]` 与 `this[Symbol.toStringTag]` 混淆

问题描述

#toString只有当我(尝试)通过missingMethod-like访问它时才会发生这种情况trap

我有一个工厂createIterface,它返回一个Proxy具有大量方法的对象。在这些方法中,我有#toString()#id()#id返回interface与调用者具有相同属性的 a 并且工作得很好;#toString应该将 my 转换interface为字符串,但它失败了。所有interface的方法——包括#id#toString——都在一个#Symbol.for("__methods")属性内。我这样做是为了调试目的:

const __methods = Symbol.for("__methods");

const missingMethod = ({
    get: (obj, prop) => Reflect.has(obj, prop)
        ? Reflect.get(obj, prop)
        : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
});

const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
        id: () => createInterface (...props),
        toString: () => `Interface(${ props.toString() })`
    }
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
interface.toString(); //error: Cannot convert a Symbol value to a string

抛出的错误表示它不能(隐式)将 Symbol 转换为 String(这是真的)。事情是,#toString不是一个符号。但是,有一个众所周知的 Symbol#toStringTag来定义Object#toString()行为。当我用其他方法实现它时,我#toString()被忽略并interface返回'[object Object]'

// see code above
const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
        id: () => createInterface (...props),
        toString: () => `Interface(${ props.toString() })`,
        [Symbol.toStringTag]: () => "Interface"
    }
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
interface.toString(); //bug: '[object Object]'

如果我对外部的方法进行编码,__methods则一切正常:

// see code above
const createInterface = (...props) => new Proxy({
    ...props,
    id: () => createInterface (...props),
    toString: () => `Interface(${ props.toString() })`
}, missingMethod);

const interface = createInterface(0, 1, 2);
const copycat = interface.id();
interface.toString() === copycat.toString(); //true

除了一些奇怪的浏览错误(我正在运行最新的 Chrome,在撰写本文时它是 v. 71.0.3578.98),我不知道为什么会发生这种情况或如何修复它。

有人可以帮忙吗?

标签: javascriptgoogle-chrometostringsymbolsproxy-pattern

解决方案


问题是interface.toString首先访问要通过

get: (obj, prop) => Reflect.has(obj, prop)
    ? Reflect.get(obj, prop)
    : Reflect.has(obj[__methods], prop)
        ...

您期望interface.toString在这里跌破三元并到达,_methodsReflect.has(obj, 'toString')由于true. Object.prototype.toString然后,在对象上调用该函数再次通过代理的 getter 操作,搜索#toStringTag要调用的。getter 遍历了所有的三元组并没有找到任何东西,所以它抛出了在线

console.log(`No #${prop} property exists.`)

因为prop是一个符号,不能连接。

一种可能性是使用不继承自的对象Object.prototype

const obj = Object.create(null);
const createInterface = (...props) => new Proxy(
  Object.assign(obj, {
    ...props,
    [__methods]: {
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`
    }
  })
  , missingMethod
);

const __methods = Symbol.for("__methods");

const missingMethod = ({
    get: (obj, prop) => Reflect.has(obj, prop)
        ? Reflect.get(obj, prop)
        : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
});

    const obj = Object.create(null);
    const createInterface = (...props) => new Proxy(
      Object.assign(obj, {
        ...props,
        [__methods]: {
          id: () => createInterface (...props),
          toString: () => `Interface(${ props.toString() })`
        }
      })
      , missingMethod
    );

const interface = createInterface(0, 1, 2);
interface.id(); //works
console.log(interface.toString());

另一种可能性是让 getter 进行hasOwnProperty检查而不是Reflect.has检查(Reflect.has与 基本相同in,并且几乎'toString'可以是in任何对象):

get: (obj, prop) => obj.hasOwnProperty(prop)

const __methods = Symbol.for("__methods");

const missingMethod = ({
    get: (obj, prop) => obj.hasOwnProperty(prop)
        ? Reflect.get(obj, prop)
        : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
});
const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
        id: () => createInterface (...props),
        toString: () => `Interface(${ props.toString() })`,
    }
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
console.log(interface.toString());

第三种可能性是确保初始找到的属性不是Reflect.has来自方法:Object.prototype

get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]

const __methods = Symbol.for("__methods");

const missingMethod = ({
    get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
        ? Reflect.get(obj, prop)
        : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
});

const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
        id: () => createInterface (...props),
        toString: () => `Interface(${ props.toString() })`
    }
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
console.log(interface.toString());


推荐阅读