JavaScript中的类
在相当长的一段时间里,JavaScript只有一些近似类的语法元素(比如new和instanceof),不过在后来的ES6中新增了一些元素,比如class关键字。这是不是以为着JavaScript中实际上有类呢?简单来说:不是。
由于类是一种设计模式,所以你可以用一些方法近似实现类的功能。为了满足对于类设计模式的最普遍需求,JavaScript提供了一些近似类的语法。
构造函数
类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。这个方法的任务就是初始化实例需要的所有信息(状态)。
类的伪代码:
class CoolGuy {
specialTrick = nothing
CoolGuy(trick) {
specialTrick = trick
}
showOff() {
output("Here's my trick: ", specialTrick)
}
}
我们可以调用类构造函数来生成一个CoolGuy实例:
Joe = new CoolGuy( "jumping rope" )
Joe.showOff() // 这是我的绝技:跳绳
类构造函数属于类,而且通常和类同名。此外,构造函数大多需要用new来调用,这样语言引擎才知道你想要构造一个新的类实例。
混入
在继承或者实例化时,JavaScript的对象机制并不会自动执行复制行为。简单来说,JavaScript中只有对象,并不存在可以被实例化的“类”。一个对象并不会被复制到其他对象,它们会被关联起来。
由于在其他语言中类表现出来的都是复制行为,因此JavaScript开发者也想出了一个方法来模拟类的复制行为,这个方法就是混入。
显示混入
手动实现复制功能,在许多库和框架中被称为extend(..),但为了方便理解我们称之为mixin(..):
// 非常简单的mixin(..) 例子:
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
// 只会在不存在的情况下复制
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Vehicle = {
engines: 1,
ignition: function () {
console.log("Turning on my engine.");
},
drive: function () {
this.ignition();
console.log("Steering and moving forward!");
}
};
var Car = mixin(Vehicle, {
wheels: 4,
drive: function () {
Vehicle.drive.call(this);
console.log(
"Rolling on all " + this.wheels + " wheels!"
);
}
});
有一点需要注意,我们处理的已经不再是类了,因为在JavaScript中不存在类,Vehicle和Car都是对象,供我们分别进行复制和黏贴。
现在Car中就有了一份Vehicle属性和函数的副本了。从技术角度来说,函数实际上没有被复制,复制的是函数引用。所以,Car中的属性ignition只是从Vehicle中复制过来的对于ignition()函数的引用。相反,属性engines就是直接从Vehicle中复制了值1。
Car已经有了drive属性(函数),所以这个属性引用并没有被mixin重写,从而保留了
Car中定义的同名属性,实现了“子类”对“父类”属性的重写。
隐式混入
var Something = {
cool: function () {
this.greeting = "Hello World";
this.count = this.count ? this.count + 1 : 1;
}
};
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
var Another = {
cool: function () {
// 隐式把Something 混入Another
Something.cool.call(this);
}
};
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (count 不是共享状态)
通过在构造函数调用或者方法调用中使用Something.cool.call(this),我们实际上“借用”了函数Something.cool()并在Another的上下文中调用了它(通过this绑定)。最终的结果是Something.cool()中的赋值操作都会应用在Another对象上而不是Something对象上。
虽然这类技术利用了this的重新绑定功能,但是Something.cool.call(this)仍然无法变成相对(并且更灵活的)引用,所以使用时千万要小心。通常来说,尽量避免使用这样的结构,以保证代码的整洁和可维护性。