Javascript库中mixin()和extend()有什么区别?

17

我正在查看各种程序库,发现 extend() 经常出现,但是 mixin() 也会出现。YUI 拥有混合和扩展。

这两个概念之间有什么区别?我何时会决定使用混合而不是对象扩展?

谢谢, 马特

回答:在IT技术中,extend() 和 mixin() 都用于将一个对象的方法和属性添加到另一个对象中。相比之下,extend() 将所有属性都直接复制到目标对象,而mixin() 则将属性添加到目标对象中,如果属性已经存在,则跳过。通常,当您想要在一个对象中包含另一个对象的所有属性时,使用extend();如果您只想添加一些新属性和方法,则使用mixin()。
4个回答

16
混合(Mixins)无法使用 instanceof,但是扩展(extends)可以。混合允许多重继承,但只是伪装成实现,而不是正确地链式继承原型。
我将展示一个 Ext-JS 的例子,但这个概念适用于任何提供混合功能的类库,它们都只是将属性复制到对象中,而不是链式继承原型。
Ext.define('Ext.Window', {
    extend: 'Ext.Panel',
    requires: 'Ext.Tool',
    mixins: {
        draggable: 'Ext.util.Draggable'
    }
});

Ext.Window instanceof Ext.Panel //true
Ext.Window instanceof Ext.util.Draggable // false

混入是一种很好的方法,可以在不使用继承的情况下向对象添加一些功能。如果您必须继承某些内容才能获得某些功能,则无法使用两个类的功能。许多人认为这是邪恶的
当Ext-JS想要将Labelable功能添加到FieldSet和其他非输入字段时,就会遇到这个问题。由于它们无法扩展Field,因为它也包含了所有的输入行为,所以它无法从Field中受益于Labelable行为。

1
这个答案非常针对Ext.js,不适用于许多其他JavaScript库(比如jQuery和Underscore只是其中两个例子)。 - natlee75
1
@natlee75 我的回答是说混入不使用原型链,因此它无法通过instanceof测试。Ext-JS是其中一种实现方式(它们让您以不同的方式检查是否为混入)。据我所知,jQuery或Underscore不通过将混入设置在原型链中来实现混入(它们只是复制属性)。您的评论暗示jQuery和Underscore可以具有能够正确与instanceof一起使用的混入?请详细说明。 - Ruan Mendes
2
不过,它们的 extend 函数也不是这样做的。只有“类库”(例如 Ext.define)才会使用“extend”术语进行原型继承。 - Bergi
1
@Bergi ECMAScript 6 还使用 extends 来实现原型继承 - Ruan Mendes

6
扩展方法在JavaScript库中非常常见,通常是一种允许使用代码将一个或多个对象的所有“自有”属性和方法添加到目标对象中的方法。代码通常很简单:迭代超出第一个参数的每个参数的所有自有键,并将存储在那里的值复制到第一个参数中。
“Mixin”是指一种设计模式,其中您使用一个对象作为某个特定属性和方法集合的容器,这些属性和方法要在系统中的许多对象之间共享。例如,您可能具有可应用于应用程序中所有UI组件的宽度和高度getter和setter,因此,在JavaScript的情况下,您可以创建一个可以通过“new”实例化的函数或保存这些方法的对象文字。然后,您可以使用“extend”类型函数将这些方法复制到系统中任意数量的对象上。
Underscore具有一个mixin方法,其本质上仅是扩展,其中传递的所有对象的方法都添加到基础underscore对象中以供链接使用。 jQuery使用其jQuery.fn的extend方法执行类似的操作。
就我个人而言,我喜欢保持扩展不变,即“从这些对象复制所有内容到此对象”的行为,同时拥有一个单独的mixin方法,该方法仅接受单个源对象,然后将所有其他参数视为要复制的属性和方法的名称(如果仅传入目标和源,则它只像单个源扩展一样操作)。
function mixin(target, source) {
    function copyProperty(key) {
        target[key] = source[key];
    }

    if (arguments.length > 2) {
        // If there are arguments beyond target and source then treat them as
        // keys of the specific properties/methods that should be copied over.
        Array.prototype.slice.call(arguments, 2).forEach(copyProperty);
    } else {
        // Otherwise copy all properties/methods from the source to the target.
        Object.keys(source).forEach(copyProperty);
    }
}

1
你能告诉我为什么要使用Array.prototype.slice.call(arguments, 2)而不是arguments.slice(2)吗?只是想知道它更有用的时候。先谢谢了。 - cantfindaname88
3
arguments虽然类似于数组,但不是一个数组实例,因此它没有.slice()方法。 - Andy E

2
您可以使用 extends 来创建 mixins。
Mixins 提供了多重继承的所有好处,但没有层次结构 (JavaScript 中的原型继承)。它们都允许您在多个对象上重用接口(或函数集)。使用 mixins 不会遇到你可能在父子关系中遇到的“菱形问题”。
当一个对象从两个对象中继承相同的函数(甚至函数名称)时,就会出现“菱形问题”。为什么?如果这两个对象中的一个修改了函数并添加了功能 (即在 Java 中称为 "super"),JavaScript 就不知道如何解释/组合这两种方法了。mixins 是避免这种层次结构的一种方式。它们定义了您可以随处放置的功能。mixins 通常也不包含自己的数据。
因此,我可以使用 jQuery 中的 $.extend() 编写 mixin。 var newmixin = $.extend({}, mixin1, mixin2) 将结合两个接口,并使它们变平 (覆盖名称冲突)。
以下是需要考虑的 3 件事:
  1. 两个接口的组合是否具有 "层次结构",例如父/子关系。在 JavaScript 中,这意味着原型继承。
  2. 函数是复制还是引用?如果原始方法更改,继承的方法是否也会更改?
  3. 当组合两个具有相同名称的方法时会发生什么?这些冲突如何处理?

0

为更清晰表述而编辑: 这有时是同一件事情,有时则不是; Mixin 是一种模式名称,用于在多个对象之间重用函数,而 Extend 更多是用于执行此操作的算法/方法。 有些情况下它们是相同的(例如下划线 _.extend),而有些情况下它们是不同的(backbone.js 的 extend)。

在它们不同的情况下,Extend 通常会将原型链接到正在扩展的对象,而 Mixin 则会将一系列方法复制到目标上。


除非您需要两个类所提供的行为,否则不能使用继承。除非您进入多重继承,否则无法使用继承,而多重继承又有其自身的问题。 - Ruan Mendes
我不确定这与继承有什么关系,因为extend和mixin并不直接涉及继承等,它们只是模拟该行为的一种方法。 - taxilian
我只是在解释它们行为不同的情况。 - Ruan Mendes
啊,我明白你的意思了。我猜有一些库使用extend来链接原型,这只能做一次,而mixin则倾向于更多地进行一对一的复制。当然,并不是所有的库都这样做,所以检查这个重要区别很重要。 - taxilian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接