类的出现最终使 JavaScript
非常容易使用继承语法,JavaScript
类比大多数人意识到的更强大,它是构建真正的 mixins
的良好基础。
什么是 Mixin
在 JavaScript
中,把不同类的行为集中到一个类是一种常见的模式,虽然 ES6
没有显示支持多类继承,但通过现有特性可以轻松地模拟这种行为。
Object.assign()
方法是为了混入对象行为而设计的,只有在需要混入类的行为时才有必要自己实现混入表达式,如果只是需要混入多个对象的属性,那么使用 Object.assign()
就可以了。
在集成或者实例化时,JavaScript
的对象机制并不会自动执行复制行为,简单来说,在 JavaScript
中只有对象,并不存在可以被实例化的类,一个对象并不会被复制到其他对象,它们会被关联起来。
在其他语言中类表现出来的都是复制行为,因此 JavaScript
开发者也想出了一个方法来模拟类的复制行为,这个方法就是混入。
那么在接下来的内容中我们会看到两种类型的混入,它们分别是显示混入和隐式混入。
显示混入
首先我们举一个简单的例子,在这里我们实现一个 mixin(...)
函数,这个功能在许多库和框架中被称为 extend
,具体代码如下所示:
function mixin(source, target) {
for (const key in source) {
if (!(key in target)) target[key] = source[key];
}
return target;
}
接下来我们实现一个 foo
类 和 bar
类,然后将两个类进行 mixin
,生成一个新的类,具体代码如下所示:
const bar = {
technical: function () {
console.log("跳");
},
moment: function () {
this.technical();
},
};
const foo = {
nickname: "xun",
hobby: "nba",
age: 18,
moment: function () {
bar.moment.call(this);
},
};
console.log(mixin(bar, foo));
现在返回的新对象中就有了一份 bar
对象的属性和函数的副本了,从技术角度来说,函数实际上没有被复制,复制的是函数引用。
所以返回的新对象中的属性 thchnical
方法只是从 bar
中复制过来的对与 moment
函数的引用,相反 moment()
就是直接从 bar
中复制了值 1。
foo
已经有了 moment
方法,所以这个属性引用并没有被 mixin
重写,从而保留了 foo
中定义的同名方法,实现了子类对子类属性的重写。
显示混入模式的一种变体被称为 寄生继承
,它既是显示的又是隐式的,具体代码如下所示:
function Foo() {
this.nickname = "moment";
}
Foo.prototype.ignition = function () {
console.log("小黑子");
};
Foo.prototype.小黑子 = function () {
this.ignition();
console.log("叼毛");
};
// 寄生类
function Bar() {
const foo = new Foo();
foo.age = 18;
const 你小子 = foo.小黑子;
foo.小黑子 = function () {
你小子.call(this);
console.log("不是所有的牛奶都是特仑苏");
};
return foo;
}
const result = new Bar();
console.log(result);
该代码的最终输出结果如下所示:
就像你看到的一样,首先我们复制一份 Foo
父类对象的定义,然后混入子类独享的定义。
隐式混入
隐式混入示例代码如下所示:
const something = {
cool: function () {
this.greeting = "hello";
this.count = this.count ? this.count + 1 : 1;
},
};
console.log(something.cool()); // undefined
console.log(something.greeting); // hello
console.log(something.count); // 1
const another = {
cool: function () {
something.cool.call(this);
},
};
console.log(another.cool()); // undefined
console.log(another.greeting); // hello
console.log(another.count); // 1
在上面代码中通过在构造函数调用或者方法调用中使用 something.cool.call(this)
,让我们实际上 借用
了函数 something.cool()
函数并在 another
的上下文中调用了它(通过 this
指向),最终的结果是 something.cool()
函数中的赋值操作都会应用在 another
对象上而不是 something
对象上。
因此我们把 something
的行为混入到了 another
中。
总结
混入模式可以用来模拟类的赋值行为,但是通常会产生丑陋并且脆弱的语法,这会让代码更加难懂并且难以维护。
此外,显示混入实际上无法完全模拟类的赋值行为,因为对象或者函数只能复制引用,无法复制引用对象或者函数本身,忽视这一点会导致许多问题。
参考资料
- 书籍:
你不知道的JavaScript上卷
原文链接:https://juejin.cn/post/7226665757881466938 作者:Moment