前言
最近在学习js的设计模式,针对对象创建以及继承所使用的模式进行讲解。
工厂模式——不关注过程,只注重最终的创建结果
1. 简单工厂模式
// 篮球基类
var basketBall = function (){
this.info = '篮球类'
}
basketBall.prototype.getMember = function () {
console.log('获取篮球队的成员数量')
}
// 足球基类
var footBall = function (){
this.info = '足球类'
}
footBall.prototype.getMember = function () {
console.log('获取足球队的成员数量')
}
// 网球基类
var tennisBall = function (){
this.info = '网球类'
}
tennisBall.prototype.getMember = function () {
console.log('获取网球队的成员数量')
}
// 简单工厂函数
var SportsFactory = function (type) {
switch(type) {
case 'NBA':
return new basketBall();
case 'WordCup':
return new footBall();
case 'FrenchOpen':
return new tennisBall();
}
}
2. 工厂方法模式
var toolbar = toolbar || {};
toolbar.method = toolbar.method || {};
toolbar.add = function () {
// do something...
}
toolbar.delete = function () {
// do something...
}
toolbar.edit = function () {
// do something...
}
toolbar.factory = function (type) {
// 当然也可以传入一个数组或者字符串,然后做相应的处理,使得调用多个子函数
return new toolbar.method[type];
}
3. 抽象工厂模式——抽象类将作为父类创建子类,即实现继承
// 抽象工厂方法
var VehicleFactory = function (subType,superType) {
if(typeof VehicleFactory[superType] === 'function') {
function F() {};
F.prototype = new VehicleFactory[superType]();
subType.constructor = subType;
superType.prototype = new F();
} else {
new Error('未创建该抽象类');
}
}
// 小汽车抽象类
VehicleFactory.Car = function () {
this.type = 'Car';
}
VehicleFactory.Car.prototype = {
getPrice: function () {
return new Error('该抽象方法不能调用,需要重写');
}
getSpend: function () {
return new Error('该抽象方法不能调用,需要重写');
}
}
// 公交车抽象类
VehicleFactory.Bus = function () {
this.type = 'Bus';
}
VehicleFactory.Bus.prototype = {
getPrice: function () {
return new Error('该抽象方法不能调用,需要重写');
}
getPassengerNum: function () {
return new Error('该抽象方法不能调用,需要重写');
}
}
// 实现与使用
var BMW = function (price,speed) { // 宝马汽车子类
this.price = price;
this.speed = speed;
}
VehicleFactory(BMW,'Car'); // 抽象工厂对Car抽象类的继承
BMW.prototype.getPrice = function () {
return this.price;
}
BMW.prototype.getSpend = function () {
return this.speed;
}
4. 构造函数模式
function Foo (name,age) {
if (!(this instanceof Foo)) { // 为了以防万一实例化的时候,忘了new关键字
return new Foo(name,age);
}
this.name = name;
this.age = age;
}
var person1 = Foo('Tom',23); // {name: 'Tom',age:23}
var person2 = new Foo('Jack',16); // {name: 'Jack',age:16}
5. 建造者模式
将复杂对象的构建层与其表现层,使得相同的构建可以有不同的表现。在该模式中,我们关心的对象创建的过程,将一个复杂对象分解分步创建,最后得到一个完整的对象。 而相对于工厂模式,就只关心创建的结果,用户对创建的过程不可知。
// 下面的实例是创建求职者的简历,包含个人信息,期望职位
// 创建人类的类
var Human = function (param) {
this.skill = param && param.skill || '保密';
this.hobby = param && param. hobby || '保密';
}
Human.prototype = {
constructor: Human,
getSkill: function () {
return this.skill;
},
getHobby: function () {
return this.hobby;
}
}
// 实例化姓名类
var Named = function (name){
let _this = this;
(function (name, _this) {
_this.wholeName = name;
let index = name.indexOf(' ');
if(index > -1) {
_this.firstName = name.slice(0,index);
_this.lastName = name.slice(index);
}
})(name, _this);
}
// 实例化职位类
var Work = function (work) {
let _this = this;
(function (work, _this) {
switch(work) {
case 'code':
_this.work = '程序员';
_this.description = '每天沉迷于编程'
break;
case 'ui':
_this.work = 'UI设计';
_this.description = '设计更是一门艺术'
break;
case 'teach':
_this.work = '教师';
_this.description = '辛勤的园丁'
break;
default:
_this.work = work;
_this.description = '抱歉,该职位的详细描述还未补充';
}
})(work, _this);
}
Work.prototype = {
constructor: Work,
changeWork: function (work) {
this.work = work;
},
changeDescription: function (description) {
this.hobby = description;
}
}
/*
* 求职者建造者
* @param{string} name(姓名)
* @param{string} work(职位)
*/
var Person = function (name,work) {
let _person = new Human({hobby: '看书',skill: '编程'});
_person.name = new Named(name);
_person.work = new Work(work);
return _person;
}
console.log(new Person()); // 打印结果详见下图
6. 原型模式
function Person() {}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 26;
Person.prototype.sayName = function (){
console.log(this.name);
}
var person1 = new Person(); // {}
person1.sayName(); // 'Nicholas';
1. 原型链继承
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。我们都知道,每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如果此时让原型对象等于另一个类型的实例,则此时的原型对象将包含指向另一个原型的指针,以此来实现继承。即子类的原型对象等于父类的实例。
但该种方式也是存在两个缺点的:
(1)由于子类通过其原型prototype对父类实例化,继承了父类所以说父类中的共有属性要是引用类型,就会在所有实例中共用,因此一个子类的实例更改共有属性就会直接影响到其他子类,即多个实例对引用类型的操作会被篡改,这是有很大的风险的,所以使用的时候需要注意。
(2)由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因为在实例化父类的时候也无法对父类的构造函数内的属性进行初始化。
function SuperType () {
this.property = true;
this.type = 'parent';
}
SuperType.prototype.getSuperType = function () {
return this.type;
}
function SubType (id,name) {
this.prototype = false;
this.type = 'children';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubType = function () {
return this.type;
};
var child1 = new SubType(); // {property:false,type:'children',__proto__:Supertype}
child1.getSubType(); // 'children'
child1.getSuperType(); // 'children'
2. 借用构造函数继承
SuperType.call(this)这句代码是关键,在子类中执行call语句就是将子类中的变量在父类中执行一遍,由于父类中是给this绑定属性的,因此子类自然也就继承了父类的共有属性。
缺点:
(1)因为没有涉及到原型prototype,所以父类的原型属性和方法不会被子类继承,只能继承父类实例的属性和方法;
(2)无法实现代码复用,每个子类都有父类实例函数的副本,影响性能;
function SuperType () {
this.colors = ['red','green','blue'];
}
function SubType () {
SuperType.call(this); // 继承父类
}
var s1 = new SubType();
s1.number = s1.colors.length;
console.log(s1); // {number:3,colors:['red','green','blue']}
var s2 = new SubType();
console.log(s2); // {colors:['red','green','blue']}
3. 原型式继承
功能上相当于ES5的标准方法:Object.create()一样,对传入的对象执行了一次浅复制。利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。从形式上看,和原型链的继承很类似,所以原型链继承的问题,这上面也会出现。
// 原型继承 等同于Object.create()
function inherit(o) {
function F() {}
F.prototype = o;
return new F();
}
4. 寄生式继承
与原型式继承紧密相关,就是对原型继承的第二次封装,并且在封装的过程中对继承的对象进行扩展增强,最后返回该对象。即在原型继承的基础上,增强对象,返回构造函数。这样新创建的对象中不仅仅有父类中的属性和方法而且还添加了新的属性和方法。但使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。
缺点(同原型式继承):
(1)原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
(2)无法传递参数。
// 寄生式继承
function createAnother(obj){
var temp = inherit(obj); // 通过调用inherit方法创建一个新对象
temp.sayHello = function () { // 以某种方式增强对象
console.log('Hi');
}
return temp; // 返回这个对象
}
var person = {
name: 'Nicholes',
friends:['Tom','Jack','Van']
}
var p1 = createAnother(person);
console.log(p1.friends); // ['Tom','Jack','Van']
console.log(p1.name); // 'Nicholes'
p1.sayHello(); // hi
5. 组合继承
将原型链继承和借用构造函数继承两者综合使用,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
缺点:无论什么情况下,都会调用两次父类构造函数:第一次在创建子类型原型的时候,第二次是在子构造函数内部。
function SuperClass (id) {
this.id = id || '123';
this.books = ['JS','HTML','CSS'];
}
SuperClass.prototype.showBooks = function () {
console.log('目前已有的书籍:'+this.books);
}
function SubClass (id,name) {
SuperClass.call(this,id); // 第二次调用父类的构造函数
this.name = name;
this.flag = true;
}
SubClass.prototype = new SuperClass(); // 第一次调用父类的构造函数
SubClass.prototype.sayHello = function () {
console.log('Hello,'+this.name);
};
var child1 = new SubClass(10,'Jack'); // {id:10,books:['JS','HTML','CSS'],name:'Jack',flag:true}
child1.showBooks(); // 打印显示书籍books
child1.books.push('test'); // books: ['JS','HTML','CSS','test']
var child2 = new SubClass(20,'Tom'); // {id:20,books:['JS','HTML','CSS'],name:'Tom',flag:true}
child2.sayHello(); //“Hello,Tom”
6. 寄生组合式继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。该种继承方式相比其他方式而言,比较高效,因为只调用了一次SuperType构造函数,避免了在SubType.prototype上面创建不必要的、多余的属性,而且原型链来保持不变,可以使用instanceof 和 isPrototypeOf
// 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
function inheritProperty(subType, superType){
var prototype = inherit(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype // 指定对象
}
// 下面是将组合继承的例子进行改进
function SuperClass (id) {
this.id = id || '123';
this.books = ['JS','HTML','CSS'];
}
SuperClass.prototype.showBooks = function () {
console.log('目前已有的书籍:'+this.books);
}
function SubClass (id,name) {
SuperClass.call(this,id); // 第一次调用父类的构造函数
this.name = name;
this.flag = true;
}
inheritProperty(SubClass,SuperClass); // 不需要调用父类的构造函数
SubClass.prototype.sayHello = function () {
console.log('Hello,'+this.name);
};
7. 混入方式实现多个继承
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它——Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do something
};
8. ES6类继承extends
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
extends
继承的核心代码如下,其实现和上述的寄生组合式继承方式一样
function _inherits(subType, superType) {
// 创建对象,创建父类原型的一个副本
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新创建的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}