Javascript: 如何在不复制但链接的情况下将其他对象 B、C 的方法混合到我的对象 A 中?

Javascript: How can I mix in methods of other Objects B, C to my Object A without copying but with linking?

如果我创建对象 A:

let A = {};

并且想要混合来自其他对象 B 和 C 的方法:

let B = {
    foo() {
        alert("Boo!");
    }
};
let C = {
    bar() {
        alert("No!");
    }
};

通常我会打电话给:

Object.assign(A, B, C); 然后我改变我的功能 foo:

Object.assign(B, {
    foo() {
        alert("Hooray!");
    }
});
Objcect.assign(C, {
    bar() {
        alert("Yes!");
    }
});

之后调用 foo 或 bar:

A.foo(); // Actual output: "Boo!", desired: "Hooray!"
A.bar(); // Actual output: "No!",  desired: "Yes!"

到目前为止我发现,Object.assign 只复制目标中的方法,但不会 link 它们。 我已经上传了一个关于仅混合一个对象的问题,已解决:

关于继承和组合我在这里找到了一篇有用的博文:Understanding Prototypes, Delegation & Composition

我想在对象中混合方法,而不是复制方法,更确切地说,我想要对混合函数的定义进行赋值。

这怎么可能? (也许在 ES2015 中?)

对此有两个答案:

它们不是复制的,而是引用的

I want to mix methods in an Object, but not copy the methods...

您所做的不是复制方法(函数;JavaScript doesn't really have methods),而是重用您已有的方法。仅存在一个 foobar 函数; AB 以及 AC 上只有属性指的是同一个函数。

A.foo === B.fooA.bar === C.bar:

可以看出

let B = {
    foo() {
        alert("Boo!");
    }
};
let C = {
    bar() {
        alert("No!");
    }
};
let A = Object.assign({}, B, C);
console.log(A.foo === B.foo); // true
console.log(A.bar === C.bar); // true

您的代码在内存中生成的内容如下所示(省略了一些细节):

                        +−−−−−−−−−−+  
                    B>−>| (object) |  
                        +−−−−−−−−−−+     +−−−−−−−−−−−−−−−−+
                        | foo      |>−+−>|   (function)   |
                        +−−−−−−−−−−+  |  +−−−−−−−−−−−−−−−−+
                                      |  | (code for foo) |
                  +−−−−−−−−−−−−−−−−−−−+  +−−−−−−−−−−−−−−−−+
                  |
                  |     +−−−−−−−−−−+      
                  | C>−>| (object) |      
                  |     +−−−−−−−−−−+     +−−−−−−−−−−−−−−−−+
                  |     | bar      |>−+−>|   (function)   |
                  |     +−−−−−−−−−−+  |  +−−−−−−−−−−−−−−−−+
                  |                   |  | (code for bar) |
                  |                   |  +−−−−−−−−−−−−−−−−+
                  |                   |
    +−−−−−−−−−−+  |                   |
A>−>| (object) |  |                   |
    +−−−−−−−−−−+  |                   |
    | foo      |>−+                   |
    | bar      |>−−−−−−−−−−−−−−−−−−−−−+
    +−−−−−−−−−−+

但是如果你真的想要一个link

如果您希望能够 替换 B.fooC.bar 函数,并让 A 看到该更改,您可以通过在 A 属性 访问器 :

上制作 foobar 来做到这一点
let A = {
    get foo() { return B.foo; },
    get bar() { return C.bar; }
};

现在 A 被 link 编辑为 BC:

let B = {
    foo() {
      console.log("foo1");
    }
};
let C = {
    bar() {
      console.log("bar1");
    }
};

let A = {
    get foo() { return B.foo; },
    get bar() { return C.bar; }
};

A.foo(); // foo1
A.bar(); // bar1

B.foo = function() {
    console.log("foo2");
};

A.foo(); // foo2

C.bar = function() {
    console.log("bar2");
};

A.bar(); // bar2

您可以使用 Object.defineProperty:

为属性的动态组合做到这一点
function linkMethods(to, ...from) {
    from.forEach(source => {
        for (let key in source) { // We're intentionally not filtering out inherited
            if (typeof source[key] === "function") {
                Object.defineProperty(to, key, {
                    get: function() {
                        return source[key];
                    }
                });
            }
        }
    });
    return to;
}

进行中:

function linkMethods(to, ...from) {
    from.forEach(source => {
        for (let key in source) { // We're intentionally not filtering out inherited
            if (typeof source[key] === "function") {
                Object.defineProperty(to, key, {
                    get: function() {
                        return source[key];
                    }
                });
            }
        }
    });
}

let B = {
    foo() {
      console.log("foo1");
    }
};
let C = {
    bar() {
      console.log("bar1");
    }
};

let A = {};
linkMethods(A, B);
linkMethods(A, C);

A.foo(); // foo1
A.bar(); // bar1

B.foo = function() {
    console.log("foo2");
};

A.foo(); // foo2

C.bar = function() {
    console.log("bar2");
};

A.bar(); // bar2

尽管我在上面使用了 ES2015 箭头函数,但如果将它们转换为 function 函数,以上所有功能都适用于 ES5(但不适用于没有 属性 的 ES3 或更早版本存取器)。


回复您的评论:

Could you please explain what the difference between copying, a reference and a link is?

一些关键的事情:

  1. 在JavaScript中,函数是对象。真正的、真实的对象,具有其他类型对象的所有特征,并且能够包含和 运行 代码。 (其他语言通常不会出现这种情况。)

  2. 变量和属性持有

  3. A value 要么是原始值(如 1 或“foo”),要么是 对象引用 。对象引用不是对象,它只是对存在于其他地方的对象的引用。

  4. 当您为变量或 属性 赋值时,您正在复制该值。如果该值是对象引用,则意味着您正在复制引用,而不是对象。

  5. JavaScript 没有(在外部级别)变量引用属性 引用。 (大多数语言不会,但有些会。)

我喜欢这样解释对象引用、变量和值:假设我们有这个人 Joe。乔真的很高兴,因为他今天 42 岁了,而且他是 银河系漫游指南 的粉丝。所以他在一张纸上写下了数字 42。 42 是一个值,在本例中是一个数字。这张纸是变量还是属性.

Joe 决定举办派对,所以他在他工作的休息室布告栏上张贴了派对公告,说他正在举办派对,并在上面写下了他的家庭住址。公告也是一个变量或属性。乔的房子是一个对象。公告上写的地址是一个对象引用,告诉我们乔的房子在哪里。

Joe 运行 在休息室遇见了 Mohammed,知道他是 HHG 的粉丝,向他展示了他的论文,上面有 42,并指出了派对公告。 Mohammed 拿出一张纸,将 42 复制到上面,以记住 Joe 的年龄(也许是为了购买合适的卡片)。也就是说,他 将 42(值)复制 到他的纸上(variable/property)。然后,因为他以前没有去过乔的家,所以他又拿了一张纸,从乔的聚会公告上抄了房子的地址。 house 没有被复制,只是地址。 对象没有被复制,只是引用

后来,乔意识到聚会对他的房子来说太大了,于是决定搬到城里的一家酒吧。他划掉公告上的地址,写下酒吧的地址。但他忘了告诉穆罕默德。所以到了聚会时间,穆罕默德去了乔的家而不是酒吧。

回到JavaScript和函数,它们是对象:

如果你有:

let B = {
    foo() {
        console.log("I'm foo");
    }
};

B.foo 包含 foo 函数,它包含 引用 foo 函数。然后当你这样做时:

let A = {};
A.foo = B.foo;

...您 将值(对象引用)从 B.foo 复制A.fooA.fooB.foo 之间没有任何联系,没有任何 link ,只是它们碰巧包含相同的值,就像乔的派对公告和穆罕默德的那张纸之间没有联系一样,除此之外,他们碰巧都有乔的地址(开头)。

稍后,如果您执行 B.foo = function() { /*...*/ };,您会将 B.foo 中的值替换为引用不同函数的 new 值。这对 A.foo 没有影响,因为 A.fooB.foo 之间没有 link。就像乔在聚会公告上划掉地址,写上酒吧的地址一样,对穆罕默德的那张纸没有任何影响。

我上面的 属性 访问器机制起作用的原因是 属性 访问器是一个 函数 每次你阅读它都会得到 运行 属性 的值。因此,当您获得 A.foo 的值时,它实际上 运行 是一个函数 returns B.foo 的值,因为它是 然后 ,不像以前那样。类比是穆罕默德在他的纸上写下“看公告”而不是写下乔的地址,然后当准备去参加聚会时,看着他的纸以记住去哪里,看到“看看公告,”回到休息室,看到更新后的地址——然后去酒吧。

您可以使用 Proxies 创建 mixin:

let A = {
    aa: "AA",
    bb: 'BB'

};

let B = {
    foo() {
        console.log(this.aa);
    }
};
let C = {
    bar() {
        console.log(this.bb);
    }
};


mixin = function(target, ...others) {

  return new Proxy(target, {
    others,
    get(target, prop) {
       for (let p of this.others)
          if (prop in p)
             return p[prop];
        return target[prop];      
    }
  });
}  


let mA = mixin(A, B, C);
mA.foo();
mA.bar();

这可以通过索引属性来优化以避免每次查找。