Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 51 additions & 52 deletions 1-js/07-object-oriented-programming/13-mixins/article.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Mixins
# JavaScript 中的 Mixin 模式

In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class.
JavaScript 中,我们只能继承单个对象。每个对象只能有一个 `[[Prototype]]` 原型。并且每个类只可以扩展另外一个类。

But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`.
但是有些时候这种设定(译者注:单继承)会让人感到受限制。比如说我有一个 `StreetSweeper` 类和一个 `Bicycle` 类,现在我想要一个 `StreetSweepingBicycle` 类(译者注:实现两个父类的功能)。

Or, talking about programming, we have a class `Renderer` that implements templating and a class `EventEmitter` that implements event handling, and want to merge these functionalities together with a class `Page`, to make a page that can use templates and emit events.
或者,在谈论编程的时候,我们有一个实现模板的 `Renderer` 类和一个实现事件处理的 `EventEmitter` 类,现在想要把这两个功能合并到一个 `Page` 类上以使得一个页面可以同时使用模板和触发事件。

There's a concept that can help here, called "mixins".
有一个概念可以帮助我们,叫做“mixins”。

As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
根据维基百科的定义,[mixin](https://en.wikipedia.org/wiki/Mixin) 是一个包含许多供其它类使用的方法的类,而且这个类不必是其它类的父类。

In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes.
换句话说,一个 *mixin* 提供了许多实现具体行为的方法,但是我们不单独使用它,我们用它来将这些行为添加到其它类中。

## A mixin example
## 一个 Mixin 实例

The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有许多实用方法的对象,通过这个对象我们可以轻易地将这些实用方法合并到任何类的原型中。

For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
例如,这个叫做 `sayHiMixin` 的 mixin 用于给 `User` 添加一些“言语”。

```js run
*!*
Expand All @@ -32,22 +32,22 @@ let sayHiMixin = {
};

*!*
// usage:
// 用法:
*/!*
class User {
constructor(name) {
this.name = name;
}
}

// copy the methods
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
// 现在 User 可以说 hi 了
new User("Dude").sayHi(); // Hello Dude!
```

There's no inheritance, but a simple method copying. So `User` may extend some other class and also include the mixin to "mix-in" the additional methods, like this:
没有继承,只有一个简单的方法拷贝。因此 `User` 可以扩展其它类并且同样包含 mixin 来“mix-in”其它方法,就像这样:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

空格

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


```js
class User extends Person {
Expand All @@ -57,9 +57,9 @@ class User extends Person {
Object.assign(User.prototype, sayHiMixin);
```

Mixins can make use of inheritance inside themselves.
Mixin 可以在自己内部使用继承。

For instance, here `sayHiMixin` inherits from `sayMixin`:
比如,这里的 `sayHiMixin` 继承于 `sayMixin`

```js run
let sayMixin = {
Expand All @@ -69,11 +69,11 @@ let sayMixin = {
};

let sayHiMixin = {
__proto__: sayMixin, // (or we could use Object.create to set the prototype here)
__proto__: sayMixin, // (或者,我们可以在这里通过 Object.create 来设置原型。)

sayHi() {
*!*
// call parent method
// 调用父类中的方法
*/!*
super.say(`Hello ${this.name}`);
},
Expand All @@ -88,39 +88,39 @@ class User {
}
}

// copy the methods
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
// 现在 User 可以说 hi 了
new User("Dude").sayHi(); // Hello Dude!
```

Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class.
请注意在 `sayHiMixin` 内部对于父类方法 `super.say()` 的调用会在 mixin 的原型上查找方法而不是在 class 自身查找。

![](mixin-inheritance.png)

That's because methods from `sayHiMixin` have `[[HomeObject]]` set to it. So `super` actually means `sayHiMixin.__proto__`, not `User.__proto__`.
那是因为 `sayHiMixin` 内部的方法设置了 `[[HomeObject]]` 属性。因此 `super` 实际上就是 `sayHiMixin.__proto__` ,而不是 `User.__proto__`

## EventMixin

Now let's make a mixin for real life.
现在让我们为了实际运用构造一个 mixin

The important feature of many objects is working with events.
许多对象的重要特征是与事件一起工作。

That is: an object should have a method to "generate an event" when something important happens to it, and other objects should be able to "listen" to such events.
也就是说:对象应该有一个方法在发生重要事件时“生成事件”,其它对象应该能够“监听”这样的事件。

An event must have a name and, optionally, bundle some additional data.
一个事件必须有一个名称,并可以选择性的捆绑一些额外的数据。

For instance, an object `user` can generate an event `"login"` when the visitor logs in. And another object `calendar` may want to receive such events to load the calendar for the logged-in person.
比如说,一个 `user` 对象能够在访问者登录时产生`“login”`事件。另一个 `calendar` 对象可能在等待着接受一个这样的事件以便为登录后的用户加载日历。

Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may want to get that information and react on that event.
或者,`menu` 在菜单选项被选择之后会产生 `"select"` 事件,并且其它对象可能在等待着接受事件的信息并且对事件做出反应。

Events is a way to "share information" with anyone who wants it. They can be useful in any class, so let's make a mixin for them:
事件是一种与任何想要得到信息的人分享信息的方式。它在任何类中都可以使用,因此现在为它构造一个 mixin

```js run
let eventMixin = {
/**
* Subscribe to event, usage:
* 订阅事件,用法:
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
Expand All @@ -132,7 +132,7 @@ let eventMixin = {
},

/**
* Cancel the subscription, usage:
* 取消订阅,用法:
* menu.off('select', handler)
*/
off(eventName, handler) {
Expand All @@ -146,60 +146,59 @@ let eventMixin = {
},

/**
* Generate the event and attach the data to it
* 触发事件并传递参数
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // no handlers for that event name
return; // 对应事件名没有事件处理函数。
}

// call the handlers
// 调用事件处理函数
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
```

There are 3 methods here:
有三个方法:

1. `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. The handlers are stored in the `_eventHandlers` property.
2. `.off(eventName, handler)` -- removes the function from the handlers list.
3. `.trigger(eventName, ...args)` -- generates the event: all assigned handlers are called and `args` are passed as arguments to them.
1. `.on(eventName, handler)` — 指定函数 `handler` 在具有对应事件名的事件发生时运行。这些事件处理函数存储在 `_eventHandlers` 属性中。
2. `.off(eventName, handler)` — 在事件处理函数列表中移除指定的函数。
3. `.trigger(eventName, ...args)` — 触发事件:所有被指定到对应事件的事件处理函数都会被调用并且 `args` 会被作为参数传递给它们。


Usage:
用法:

```js run
// Make a class
// 新建一个 class
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// Add the mixin
// 添加 mixin
Object.assign(Menu.prototype, eventMixin);

let menu = new Menu();

// call the handler on selection:
// 被选中时调用事件处理函数:
*!*
menu.on("select", value => alert(`Value selected: ${value}`));
*/!*

// triggers the event => shows Value selected: 123
menu.choose("123"); // value selected
// 触发事件 => 展示被选中的值:123
menu.choose("123"); // 被选中的值
```

Now if we have the code interested to react on user selection, we can bind it with `menu.on(...)`.
现在如果我们已经有了针对用户选择事件做出具体反应的代码,可以将代码使用 `menu.on(...)` 进行绑定。

And the `eventMixin` can add such behavior to as many classes as we'd like, without interfering with the inheritance chain.
只要我们喜欢,就可以通过 `eventMixin` 将这些行为添加到任意个数的类中而不影响继承链。

## Summary
## 总结

*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
*Mixin* — 是一个通用的面向对象编程术语:一个包含其他类的方法的类。

Some other languages like e.g. python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying them into the prototype.
一些其它语言比如 python 允许通过多继承实现 mixin。JavaScript 不支持多继承,但是可以通过拷贝多个类中的方法到某个类的原型中实现 mixin。

We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
我们可以使用 mixin 作为一种通过多种行为来增强类的方式,就像我们上面看到的事件处理一样。

Mixins may become a point of conflict if they occasionally overwrite native class methods. So generally one should think well about the naming for a mixin, to minimize such possibility.
如果 Mixins 偶尔会重写原生类中的方法,那么 Mixins 可能会成为一个冲突点。因此通常情况下应该好好考虑 mixin 的命名,以减少这种冲突的可能性。
10 changes: 5 additions & 5 deletions 1-js/07-object-oriented-programming/13-mixins/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let eventMixin = {

/**
* Subscribe to event, usage:
* 订阅事件,用法:
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
Expand All @@ -14,7 +14,7 @@
},

/**
* Cancel the subscription, usage:
* 取消订阅,用法:
* menu.off('select', handler)
*/
off(eventName, handler) {
Expand All @@ -28,15 +28,15 @@
},

/**
* Generate the event and attach the data to it
* 触发事件并传递参数
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // no handlers for that event name
return; // 对应事件名没有事件处理函数
}

// call the handlers
// 调用事件处理函数
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
Expand Down