在继承或者实例化时,JS的对象机智并不会自动执行复制行为。简单地说,JS中只有对象,并不存在可以被实例化的类。一个对象并不会被赋值到其他对象,他们会被关联起来,
由于在其他语言中类表现出来的都是复制行为,因此JS的开发者们想出一种模拟类的行为。这个方法就是混入。混入又分为显式和隐式
1.显式混入
看以下代码
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
if (!(key in targetObj) && sourceObj.hasOwnProperty(key)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Suv = {
engines: 1,
ignition: function() {
console.log("ignition");
},
drive: function() {
//this.ignition();
console.log("Steering and moving forward!");
}
}
var Car = mixin(
Suv, {
wheels: 2,
drive: function() {
console.log("drive");
}
}
)
console.log(Car);
注意我们处理的不是类而是一个个对象,我们复制Suv对象自身存在的属性(不包含原型链上的)且Car对象没有的属性到Car对象中。
现在Car属性就有了一份Suv属性和函数的副本了,事实上,函数实际上没有被复制,复制的是函数引用。所以Car属性ignition只是从Suv中复制对函数ignition的引用。值类型的engines属性直接复制。然而drive属性以及存在,所以保留了下来,这里模仿了“子类”对“父类”的重写。
1.1显式伪多态
Suv.drive.call(this);
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
if (!(key in targetObj) && sourceObj.hasOwnProperty(key)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Suv = {
engines: 1,
ignition: function() {
console.log("ignition");
},
drive: function() {
this.ignition();
console.log("Steering and moving forward!");
},
stop: function() {
this.ignition();
}
}
var Car = mixin(
Suv, {
wheels: 2,
drive: function() {
Suv.drive.call(this);//显式伪多态
console.log("drive");
}
}
)
console.log(Car.drive());
Suv.drive.call(this)这就是我们说的显式多态
js在ES6之前并没有多态机制,所以,由于Car和Suv中都有drive函数,为了指明调用对象,我们必须使用绝对引用。我们通过名称指定Suv对象并调用它的drive函数。
但是如果执行 Suv.drive() ,this会被绑定到Suv对象而不是Car对象,所以用call硬绑定drive在Car对象的上下文中执行
用显式伪多态shi'xi多重继承的例子
var Suv = {
engines: 1,
ignition: function() {
console.log("ignition");
},
drive: function() {
console.log(this.engines);
console.log("Steering and moving forward!");
},
stop: function() {
this.ignition();
}
}
var Bmw = {
engines:2,
superAccerate:function () {
console.log("superAccerate");
},
accelerate:function () {
console.log(this.engines);
console.log("accelerate!");
}
}
var Car = mixin(
[Suv,Bmw], {
wheels: 3,
drive: function() {
Suv.drive.call(this);
console.log("drive");
},
accelerate:function () {
Bmw.accelerate.call(this);
console.log("Car accelerate");
}
}
)
Car继承了Suv和Bwm的属性,这里有个加载顺序的问题,如果先复制了Suv中有的属性Bwm也有(Car中无)那么只会复制一次这个属性忽略Bwm中的属性。
1.2寄生复制
//传统的js类Maserati
function Maserati() {
this.engines = 1;
}
Maserati.prototype.ignition = function () {
console.log("Turning on my engines.");
};
Maserati.prototype.drive = function () {
this.ignition();
console.log("Steering and moving forward!");
}
//寄生类Car
function Car() {
//首先car是一个Maserati,new会将对象.prototype的指向绑定到Maserati.prototype
var car = new Maserati();
//接着我们对car进行定制
car.wheels = 4;
//保存到MasDrive的Maserati.prototype.drive的特殊引用
var MasDrive = car.drive;
//重写drive
car.drive = function () {
MasDrive.call(this);
console.log("wheels left " + this.wheels);
}
return car;
}
var myCar = new Car();//可以不用new
myCar.drive();
首先,我们复制一份Maserati父类(对象)的定义,然后混入子类(对象)的定义(如果需要还可以保留父类的特殊引用),然后用这个符合对象构建实例
注意 new Car()会创建一个新对象绑定到Car的this上,但是这里我们已经返回了自己的Car对象所以原来创建的对象会被丢弃,因此可以不使用new来调用Car,结果一样,避免创建并丢弃多余对象。
2.隐式混入
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();
console.log(another.greeting);// "Hello World"
another.count;// 1
通过在构造函数调用或者方法调用中使用something.cool.call(this),我们实际上调用了函数的something.cool()并在another上下文中调用了它(通过this绑定到another),
最终结果是something.cool()中的赋值操作都会应用在Another对象上而不是 Something对象上。
虽然这类技术利用了this的重新绑定功能,但是something.cool.call(this)仍然不太灵活,可维护性不佳。
小结:
类是一种设计模式,其他咽炎提供了面向类的原生语法,JS中也有类似的语法但是和其他语言的类完全不同。
类意味着复制
传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被赋值到子类中。
多态(在继承链不同层次名称相同但功能不同的函数)看起来似乎是从子类引用父类,但从本质上引用的其实是复制的结果。
以下是js中的类使用模式
js并不会自动创建对象的副本(和其他语言类的本质区别)
混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆弱的语法,比如显式伪多态(OtherObj.methodName.call(this,...),这会让代码更加复杂且难维护。
此外,显式混入实际上无法完全模仿 类的复制行为,因为对象(包括函数也是对象)只能复制引用,无法复制被引用对象或者函数的本身。
总的来说在js中模拟类的行为并不是一种优雅的解决方式