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

标签 javascript delegates ecmascript-6 mixins assignment-operator

如果创建对象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仅复制目标中的方法,但未链接它们。
我已经上传了一个仅在一个对象中混合的问题,该问题已解决:Mix in one object, solution: prototypical inheritance

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

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

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

最佳答案

对此有两个答案:

他们没有被复制,他们被引用


  我想在对象中混合方法,但不复制方法...


您正在执行的操作不是复制方法(函数; 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);
snippet.log(A.foo === B.foo); // true
snippet.log(A.bar === C.bar); // true

<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>





您的代码在内存中产生的内容如下所示(省略了一些详细信息):

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

But if you really want a link

If you want to be able to replace the B.foo or C.bar function with something else, and have A see that change, you can do that by making foo and bar on A property accessors:

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


现在A链接到BC



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

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

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

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

A.foo(); // foo2

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

A.bar(); // bar2

<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>





您可以使用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() {
      snippet.log("foo1");
    }
};
let C = {
    bar() {
      snippet.log("bar1");
    }
};

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

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

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

A.foo(); // foo2

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

A.bar(); // bar2

<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="//tjcrowder.github.io/simple-snippets-console/snippet.js"></script>





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



发表您的评论:


  您能否解释一下复制,引用和链接之间的区别是什么?


一些关键的事情:


在JavaScript中,函数是对象。真正的真实对象,具有其他种类的对象的所有功能,以及包含和运行代码的能力。 (其他语言通常不是这种情况。)
变量和属性保存值。
值可以是原始值(例如1或“ foo”),也可以是对象引用。对象引用不是对象,只是对对象的引用,该对象存在于其他地方。
当您将值分配给变量或属性时,就是在复制该值。如果该值是对象引用,则意味着您要复制的是引用,而不是对象。
JavaScript没有(在外部级别)变量引用或属性引用。 (大多数语言不,但有些语言。)


我喜欢这样解释对象引用,变量和值:假设我们有这个家伙Joe。乔真的很高兴,因为他今天已经42岁了,并且是《 The Hitchhiker's Guide to the Galaxy》的粉丝。因此他将数字42写在一张纸上。 42是一个值,在这种情况下为数字。纸是变量或属性。

乔决定参加一个聚会,因此他在休息室通知板上张贴了一个聚会通知,说他正在参加聚会,并在上面写上自己的家庭住址。公告也是变量或属性。乔的房子是一个对象。公告上写的地址是一个对象引用,告诉我们乔的房子在哪里。

乔在休息室进入穆罕默德(Mohammed),并且知道他是HHG的同伴,向他展示了他的论文,上面写着42,并指出了聚会的公告。穆罕默德(Mohammed)拿出一张纸,然后将42张纸复制到纸上,以记住乔的转折年龄(也许是买合适的卡)。也就是说,他将42(值)复制到他的纸上(变量/属性)。然后,因为他以前没有去过乔的家,所以他又拿了一张纸,并从乔的聚会公告中抄了房子的地址。房子不被复制,只是地址。该对象没有被复制,只是对其的引用。

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

返回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之间没有任何联系,也没有链接,只是它们恰好包含相同的值,就像乔的聚会声明和穆罕默德的纸之间没有联系一样,只是它们恰好都具有乔在他们身上的地址(开头)。

稍后,如果您执行B.foo = function() { /*...*/ };,则将B.foo中的值替换为引用另一个函数的新值。这对A.foo无效,因为A.fooB.foo之间也没有链接。就像乔对聚会公告上的地址划掉并在酒馆的地址写信一样,这对穆罕默德的纸没有任何影响。

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

关于javascript - Javascript:如何在不复制但通过链接的情况下将其他对象B,C的方法混合到对象A中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36080643/

相关文章:

Javascript 在 Rails 应用程序中包含标签的最佳实践

javascript - 如何检查是否在字段上设置了必需的属性

javascript - Ng-if 在监视变量变为 false 时不隐藏元素

c# - Lambda\Anonymous 函数作为参数

javascript - 我的函数声明中哪一个更好?生成器还是异步/等待?

javascript - Browserify、Babel 6、Gulp - 传播运算符上的意外标记

javascript - Haschildnodes() 不起作用

c# - 没有 WriteLine 的重载匹配具有相同签名的委托(delegate)

ios - Xcode 7 中没有类型或协议(protocol)命名错误如何解决?

javascript - 如何使用 Array.prototype.filter 过滤对象?