了解JS中的混个对象“类”

类是面向对象的设计模式,它包括实例化、继承和多态

1、理论

面向对象变成强调的是数据和操作的行为本质上是相互关联的,因此好的设计就是把数据以及和他相关的行为打包(封装)起来,我们也叫他数据结构。

类的一个核心概念就是多态,这个概念是说父类的通用行为可以被子类用更特殊的行为重写,实际上,相对多态性允许我们从重写行为中引用基础行为。

在相当长的一段时间里,JS只有一些近似类的语法元素(比如说new和insranceof),只不过后来在ES6中新增了一些元素,比如class关键字,但是,这并不意味着JS这种实际上有类,由于类是一种设计模式,所以可以使用一些方法近似实现类的功能,JS则提供了近似类的语法

在软件设计中类是一种可选的模式,你需要自己决定是否在 JS 中使用它

2、类的机制

类与对象的关系可以看做房屋图纸与实际房屋(类实例)之间的关系

类实例通常是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称作构造函数。这个方法的任务就是初始化实例需要的所有信息

class CoolGuy {
	specialTrick = nothing
	CoolGuy( trick ) {
		specialTrick = trick
	}
	showOff() {
		output( "Here's my trick: ", specialTrick )
	}
}

类构造函数属于类,而且通常和类同名。此外,构造函数大多数需要用new来调,这样语言引擎才可以构造一个新的类实例

3、类继承

在面向类的语言中,我们可以先定义一个类,然后定义一个继承前者的类

在传统的面向类的语言中 super 还有一个功能,就是从子类的构造函数中通过super 可以直接调用父类的构造函数。通常来说这没什么问题,因为对于真正的类来说,构造函数是属于类的。然而,在 JavaScript 中恰好相反——实际上“类”是属于构造函数的(类似Foo.prototype… 这样的类型引用)。由于JavaScript 中父类和子类的关系只存在于两者构造函数对应的 .prototype 对象中,因此它们的构造函数之间并不存在直接联系,从而无法简单地实现两者的相对引用。

多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。

看下面的伪代码:

class Vehicle {
	engines = 1
	ignition() {
		output( "Turning on my engine." );
	}
	drive() {
		ignition();
        output( "Steering and moving forward!" )
	}
}
class Car inherits Vehicle {
	wheels = 4
	drive() {
		inherited:drive()
		output( "Rolling on all ", wheels, " wheels!" )
	}
}
class SpeedBoat inherits Vehicle {
	engines = 2
	ignition() {
		output( "Turning on my ", engines, " engines." )
	}
	pilot() {
		inherited:drive()
		output( "Speeding through the water with ease!" )
	}
}

4、混入

在继承或者实例化时,JS的对象机制并不会自动执行复制行为。简单来说,JS中只有对象,并不存在可以被实例化的类。但是由于在其他语言中类表现出来的都是复制行为,因此JS使用了混入的方法来模拟类这个行为,分为显示和隐式

4.1、显式混入

通常显式混入在许多框架中被称为extend(…),但是为了方便理解,我们称之为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!"
        );
    }
});

从技术角度来说,函数实际上没有被复制,复制的是函数引用。

Vehicle.drive.call(this);就是显式多态,在之前的伪代码中对应的语句是inherited:drive(),称之为相对多态。

JS在ES6之前并没有相对多态的机制,所以由于Car和Vehicle中都有drive()函数,为了指明调用对象,我们必须使用绝对引用,通过名称显示指定Vehicle对象并调用它的drive()函数。

还有混合复制以及寄生继承,这里就不具体介绍了

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();
Another.greeting; // "Hello World"
Another.count; // 1 (count 不是共享状态)

通过在构造函数调用或者方法调用中使用 Something.cool.call( this ),我们实际上“借用”了函数 Something.cool() 并在 Another 的上下文中调用了它(通过 this 绑定)。最终的结果是 Something.cool() 中的赋值操作都会应用在 Another 对象上而不是Something 对象上。
因此,我们把 Something 的行为“混入”到了 Another 中。

5、总结

类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript 也有类似的语法,但是和其他语言中的类完全不同。

类意味着复制。

传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。

多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父类,但是本质上引用的其实是复制的结。

JavaScript 并不会(像类那样)自动创建对象的副本。

混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆弱的语法,比如显式伪多态(OtherObj.methodName.call(this, …)),这会让代码更加难懂并且难以维护。

此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也是对象)只能复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多问题。

总地来说,在 JavaScript 中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋下更多的隐患。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值