javascript设计模式-面向对象编程(封装、继承、多态)

编程的两种风格--面向过程与面向对象

面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了

面向对象:把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

以五子棋为例:

面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。

面向对象的设计则是从另外的思路来解决问题:整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

面向对象编程思想其中有一个特点就是封装,就是说把你需要的功能放在一个对象里

 

1.封装

在ES5中,javascript没有通过class关键字实现的类的封装方式,都是通过一些特性的模仿实现的,但这也带来了极高的灵活性,让我们编写的代码更自由。

 

1.1 创建一个类

// 类的首字母大写, 通过this变量添加属性或者方法
var Book = function(id, bookname, price) {
	// this指向当前这个对象
	this.id = id;
	this.bookname = bookname;
	this.price = price;
}

在类的原型(类也是对象,也有prototype)上添加属性和方法有两种方式,一种是一一为原型对象属性赋值,另一种是将一个对象赋值给类的原型对象。两种方式不能混用,例如:

Book.prototype.display = function() {
	// 展示这本书
};

或者

Book.prototype = {
	display: function() {}
}

接着通过 new 关键字实例化对象,并通过点运算符访问属性或方法

var book = new Book(10, 'Javascript 面向对象编程', 50);
console.log(book.bookname); // Javascript 面向对象编程

通过this添加的属性和方法和通过prototype添加有什么区别?

通过 this 添加的属性和方法是在当前对象上添加的,然而javascript是一种基于原型的语言,所以每创建一个对象时,他都有一个原型prototype用于指向其继承的属性和方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时, 会通过prototype 一级一级查找到. 这样你就会发现通过 this定义的属性和方法是该对象自身拥有的,所以我们每次通过类创建一个新对象this指向的属性和方法都会得到相应的创建,而通过prototype继承的属性和方法是对每个对象通过prototype访问到,所以我们通过类创建一个新的对象时,这些属性和方法不会再次创建

javascript设计模式-面向对象编程(封装、继承、多态)_第1张图片

图中的construct是指什么?

constructor 是一个属性,当创建一个函数 或者 对象是都会为其创建一个 原型对象 prototype, 在 prototype 对象中又会像函数中创建 this 一样创建一个constructor 属性, 那么 constructor 属性指向的就是拥有整个原型对象的 函数 或 对象,例如在本例中 Book prototype 中的 constructor 属性指向的就是Book 类对象

 

1.2 封装私有变量,私有方法

由于javascript 的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有变量和私有方法。然而在函数内部通过 this 创建的属性和方法, 在类创建对象时,每个对象都拥有一份并且可以在外部访问到。因此通过 this 创建的属性可看做是对象共有的属性和方法, 而通过this创建的方法,不但可以访问这些对象公共属性和共有方法,而且还能访问到类(创建时)或对象自身的私有属性和私有方法, 由于这些方法权利比较大,所以我们又将它看做特权方法。 在对象创建时使用这些特权方法我们可以初始化实例对象的一些属性,因此这些在创建对象时调用的特权方法还可以看做是类的构造器

//  私有属性与私有方法,特权方法,对象共有属性和对象公有方法,构造器
var Book = function(id, name ,price){
	var num = 1; // 私有属性
	function checkId(){ // 私有方法

	};

	// 特权方法
	this.getName = function(){};
	this.getPrice = function(){};
	this.setName = function(){};
	this.setPrice = function(){};

	
	this.id = id; // 对象公共属性
	this.copy = function(){}; // 对象公有方法
	
	// 构造器
	this.setName(name);
	this.setPrice(price);

}

 

1.3 封装静态属性和静态方法

通过 new 关键字创建的对象的实质是对新对象this的不断赋值,并将 prototype 指向类的 prototype 所指向的对象,而类的构造函数外面通过点语法定义的属性方法是不会添加到新创建的对象上去的。因此要想在新创建的对象中使用 isChinese 上定义的方法就得通过 Book 类使用而不能通过 this,如 Book.isChinese,而类的原型prototype上定义的属性在新对象里就可以直接使用,这是因为新对象的 prototype 和类的 prototype 指向的是同一个对象

// 类静态公有方法(对象不能访问)
Book.isChinese = true;

// 类静态公有方法(对象不能访问)
Book.resetTime = function() {
	console.log('new TIME');
};

Book.prototype = {
	// 公有属性
	isJSBook: false,
	// 公有方法
	display: function() {}
}

var b = new Book(11, 'javascript设计模式', 50);
console.log(b.num); // undefined
console.log(b.isJSBook); // false
console.log(b.id); // 11
console.log(b.isChinese); // undefined

我们还可以通过闭包实现静态变量和静态方法

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后,也就是说,闭包可以让你从内部函数访问外部函数作用域

闭包的栗子:

function init() {
	var name = "Mozilla"; // name 是一个被 init 创建的局部变量
	function displayName() { // displayName() 是内部函数,一个闭包
		alert(name); // 使用了父函数中声明的变量
	}
	displayName();
}

init(); // 弹出Mozilla

言归正传,我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例对象的函数,即可访问到类函数作用域中的变量如 bookNum 这个变量,此时就叫做静态私有变量,并且checkBook()可称为静态私有方法。当然闭包内部也有其自身的私有变量以及私有方法,如name,checkId()。但是,在闭包外部添加原型属性和方法看上去像脱离了闭包这个类,所以有时候在闭包内部实现一个完整的类然后将其返回。

// 利用闭包实现
var Book = (function() {
	// 静态私有变量
	var bookNum = 0;
	// 静态私有方法
	function checkBook(name) {}
	
	// 创建类
	function _book(newId, newName, newPrice) {
		// 私有变量
		var name, price;
		// 私有方法
		function checkID(id) {};
		
		// 特权方法
		this.getName = function() {};
		this.getPrice = function() {};
		this.setName = function() {};
		this.setPrice = function() {};

		// 公有属性
		this.id = newId;
		// 公有方法
		this.copy = function() {};
		
		bookNum++;
		if (bookNum > 100) throw new Error('我们仅出版 100 本书');
		
		// 构造器
		this.setName(name);
		this.setPrice(price);

	}

	// 构造原型
	_book.prototype = {
		// 静态公有属性
		isJSBook: false,
		// 静态公有方法
		display: function() {}
	};

	// 返回类
	return _book;
})();

 

1.4 创建对象的安全模式

用于解决忘记使用new关键字创建对象造成的错误

错误的栗子

// 图书类
var Book = function(title, time, type) {
	this.title = title;
	this.time = time;
	this.type = type;
}

// 实例化一本书
var book = Book('javascript', '2014', 'js');

console.log(book); // undefined

console.log(window.title); // javascript
console.log(window.time); // 2014
console.log(window.type); // js

book为什么是undefined,而window.title、window.time、window.type都有值?

new 关键字的作用可以看做是对当前对象的 this 不停的赋值,然而在例子中没有用到new就直接执行这个函数,而这个函数在全局作用域中执行了。所以在全局作用域中this 指向的当前对象自然就是全局变量,在你的页面里全局变量就是 window 了,所以添加的属性自然就被添加到 window 上面了,而我们这个 book 变量最终的作用是要得到 Book 这个类(函数)的执行结果,由于函数没有return语句,这个Book类自然不会告诉book变量的执行结果了。所以就是 undefined.

解决方式:

// 图书安全类
var Book = function(title, time, type) {
	// 判断执行过程中 this 是否是当前这个对象(如果是说明是用 new 创建的)
	if (this instanceof Book) {
		this.title = title;
		this.time = time;
		this.type = type;
	} else { // 否则创建这个新对象
		return new Book(title, time, type);
	}
}

var book = Book('javascript', '2014', 'js');

console.log(book); // Book
console.log(book.title); // javascript
console.log(book.time); // 2014
console.log(book.type); // js

console.log(window.title); // undefined
console.log(window.time); // undefined
console.log(window.type); // undefined

 

 

2.继承

继承(英语:inheritance)是面向对象软件技术当中的一个概念。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

ES5并没有继承这一现有的机制,通常可以通过以下模式实现继承

 

2.1 类式继承

原理:将父类的实例对象赋值给子类的原型,那么子类的原型同样可以访问父类原型上的属性和方法与从父类构造函数中赋值的属性和方法

// 类式继承

// 声明父类
function SuperClass() {
	this.superValue = true;
}
// 为父类添加共有方法
SuperClass.prototype.getSuperValue = function() {
	return this.superValue;
};

// 声明子类
function SubClass() {
	this.subValue = false;
}
// 继承父类
SubClass.prototype = new SuperClass();
// 为子类添加共有方法
SubClass.prototype.getSubValue = function() {
	return this.subValue;
};

使用子类,并通过instanceof检测实例对象是否继承父类和子类

var instance = new SubClass();

console.log(instance.getSuperValue()); // true;
console.log(instance.getSubValue()); // false

console.log(instance instanceof SuperClass); // true
console.log(instance instanceof SubClass); // true
console.log(SubClass instanceof SuperClass); // false SubClass.prototype继承了SuperClass而不是SubClass继承了SuperClass
console.log(SubClass.prototype instanceof SuperClass); // true

javascript中万物皆对象,Object原生对象是所有对象的祖先,所有对象都继承自Object

console.log(instance instanceof Object); // true

类式继承的缺点:

其一,由于子类通过其原型prototype对父类实例化,继承了父类。父类中的共有属性如果是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类

其二,由于子类实现的继承是靠其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化

function SuperClass() {
	this.books = ['javascript', 'html', 'css'];
}

function SubClass() {}

SubClass.prototype = new SuperClass(); // 无法将SubClass的参数传递给SuperClass构造函数

var instance1 = new SubClass();
var instance2 = new SubClass();

console.log(instance2.books); // ['javascript', 'html', 'css']
instance1.books.push('设计模式');
console.log(instance2.books); // ['javascript', 'html', 'css', '设计模式']

解决无法对父类构造函数内的属性进行初始化的问题,可以使用构造函数继承模式

 

2.2 构造函数继承

缺点:无法继承父类的原型方法和属性

// 构造函数式继承

// 声明父类
function SuperClass(id) {
	// 引用类型共有属性
	this.books = ['javascript', 'html', 'css'];
	
	// 值类型共有属性
	this.id = id;
}

// 父类声明原型方法
SuperClass.prototype.showBooks = function() {
	console.log(this.books);
}

// 声明子类
function SubClass(id) {
	// 继承父类
	SuperClass.call(this, id);
}

var instance1 = new SubClass(10);
var instance2 = new SubClass(11);

instance1.books.push('设计模式');
console.log(instance1.books); // ['javascript', 'html', 'css', '设计模式']
console.log(instance1.id); // 10
console.log(instance2.books); // ['javascript', 'html', 'css']
console.log(instance2.id); // 11

instance1.showBooks(); // TypeError

SuperClass.call(this, id) 这条语句是构造函数式继承的精华,由于call这个方法可以更改函数的作用环境,因此在子类中,对superClass调用这个方法就是将子类中的变量在父类中执行一遍,由于父类中是给this绑定属性的,因此子类自然也就继承了父类的共有属性。由于这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而如果要想被子类继承就必须要放在构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。为了综合这两种模式的优点,后来有了组合式继承

 

2.3 组合继承

类式继承是通过子类的原型prototype对父类实例化来实现的,构造函数式继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的,所以只要在继承中同时做到这两点即可解决类式继承和构造函数式继承的缺点

// 组合式继承

// 声明父类
function SuperClass(name) {
	// 值类型共有属性
	this.name = name;
	// 引用类型共有属性
	this.books = ["html", "css", "JavaScript"];
}

// 父类原型共有方法
SuperClass.prototype.getName = function() {
	console.log(this.name);
};

// 声明子类
function SubClass(name, time) {
	// 构造函数式继承父类name属性
	SuperClass.call(this, name);
	// 子类中新增共有属性
	this.time = time;
}

// 类式继承 子类原型继承父类
SubClass.prototype = new SuperClass();

// 子类原型方法
SubClass.prototype.getTime = function() {
	console.log(this.time);
};

测试


var instance1 = new SubClass("js book", 2014);
instance1.books.push("设计模式");  
console.log(instance1.books); // ["html", "css", "JavaScript", "设计模式"] 
instance1.getName(); // js book
instance1.getTime(); // 2014 

var instance2 = new SubClass("css book", 2013);
console.log(instance2.books); // ["html", "css", "JavaScript"] 
instance2.getName(); // css book 
instance2.getTime(); // 2013

组合式继承的缺点:在使用构造函数继承时执行了一遍父类的构造函数,而在实现子类原型的类式继承时又调用了一遍父类构造函数。因此父类构造函数调用了两遍,可以使用原型式继承解决这一问题

 

2.4 原型式继承

2006年道格拉斯·克罗克福德发表一篇《JavaScript中原型式继承》的文章,他的观点是,借助原型prototype可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象类型,以下是实现代码

// 原型是继承
function inheritObject(o) {
	// 声明一个过渡函数对象
	function F() {}
	// 过渡对象的原型继承父对象
	F.prototype = o;
	// 返回过渡对象的一个实例,该实例的原型继承了父对象
	return new F();
}

这种方式与类式继承有些类似,它是对类式继承的一个封装,其实其中的过渡对象就相当于类式继承中的子类,只不过在原型式中作为一个过渡对象出现的,目的是为了创建要返回的新的实例化对象。

这种方式由于F过渡类的构造函数中无内容,所以开销比较小,使用起来也比较方便。当然如果你感觉有必要可以将F过渡类缓存起来,不必每次创建一个新过渡类F。当然这种顾虑也是不必要的。随着对这种思想的深入,后来就出现的Object.create()的方法。

var book = {
  name: "js book",
  alikeBook: ["css book", "html book"]
};

var newBook = inheritObject(book);
newBook.name = "ajax book";
newBook.alikeBook.push("xml book");

var otherBook = inheritObject(book);
otherBook.name = "flash book";
otherBook.alikeBook.push("as book");

console.log(newBook.name); // ajax book 
console.log(newBook.alikeBook); // ["css book", "html book", "xml book", "as book"] 

console.log(otherBook.name); // flash book 
console.log(otherBook.alikeBook); // ["css book", "html book", "xml book", "as book"]

console.log(book.name); // js book
console.log(book.alikeBook); // ["css book", "html book", "xml book", "as book"]

这种继承方式的缺点:跟类式继承一样,父类对象book中的值类型的属性被复制,引用类型的属性被共用。为了解决这一问题,道格拉斯·克罗克福德在此基础上做了一些增强而推出一种寄生式继承

 

2.5 寄生式继承

寄生式继承就是对原型继承的第二次封装,并且在这第二次封装过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加新的属性和方法。寄生大概指的就是像寄生虫一样寄托于某个对象内部生长。当然寄生式继承这种增强新创建对象的继承思想也是寄托于原型继承模式。

// 寄生式继承

// 声明基对象
var book = {
	name: "js book",
	alikeBook: ["css book", "html book"]
};

// 原型是继承
function inheritObject(o) {
	// 声明一个过渡函数对象
	function F() {}
	// 过渡对象的原型继承父对象
	F.prototype = o;
	// 返回过渡对象的一个实例,该实例的原型继承了父对象
	return new F();
}

function createBook(obj) {
	// 通过原型继承方式创建新对象
	var o = new inheritObject(obj);
	
	// 拓展新对象
	o.getName = function() {
		console.log(name);
	};
	
	// 返回拓展后的新对象
	return o;
}

测试

var book = {
  name: "js book",
  alikeBook: ["css book", "html book"]
};

var newBook = createBook(book);
newBook.name = "ajax book";
newBook.alikeBook.push("xml book");

var otherBook = createBook(book);
otherBook.name = "flash book";
otherBook.alikeBook.push("as book");

console.log(newBook.name); // ajax book 
console.log(newBook.alikeBook); // ["css book", "html book", "xml book", "as book"] 

console.log(otherBook.name); // flash book 
console.log(otherBook.alikeBook); // ["css book", "html book", "xml book", "as book"]

console.log(book.name); // js book
console.log(book.alikeBook); // ["css book", "html book", "xml book", "as book"]

缺点:跟类式继承一样,父类对象book中的值类型的属性被复制,引用类型的属性被共用。这种思想的作用也是为了寄生组合式继承模式的实现

 

2.6 寄生组合式继承

/**
 * 寄生式继承 继承原型
 * 传递参数 subClass  子类
 * 传递参数 superClass 父类
 **/ 
function inheritPrototype(subClass, superClass){
  // 复制一份父类的原型副本保存在变量中
  var p = inheritObject(superClass.prototype);  

  // 修正因为重写子类原型导致子类的constructor属性被修改
  p.constructor = subClass;  

  // 设置子类的原型      
  subClass.prototype = p;        
}

组合式继承中,通过构造函数继承的属性和方法是没有问题的,所以这里我们主要探究通过寄生式继承重新继承父类的原型。我们需要继承的仅仅是父类的原型,不再需要调用父类的构造函数,换句话说,在构造函数继承中我们已经调用了父类的构造函数。

因此我们需要的就是父类的原型对象的一个副本,而这个副本我们通过原型继承便可得到,但是这么直接赋值给子类会有问题的,因为对父类原型对象复制得到的复制对象p中的constructor指向的不是subClass子类对象,因此在寄生式继承中要对复制对象p做一次增强,修复其constructor属性指向不正确的问题,最后将得到的复制对象p赋值给子类的原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。

测试

// 定义父类           
function SuperClass(name) {
	this.name = name;
	this.colors = ["red", "blue", "green"];
}
// 定义父类原型方法
SuperClass.prototype.getName = function() {
	console.log(this.name);
};

// 定义子类
function SubClass(name, time) {
	// 构造函数式继承
	SuperClass.call(this, name);
	// 子类新增属性
	this.time = time;
}
// 寄生式继承父类原型
inheritPrototype(SubClass, SuperClass);
// 子类新增原型方法
SubClass.prototype.getTime = function() {
	console.log(this.time);
};

// 创建两个测试方法
var instance1 = new SubClass("js book", 2014);
var instance2 = new SubClass("css book", 2013);

instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.getName(); // css book
instance2.getTime(); // 2013

这种方式继承如下图所示,其中最大的改变就是对子类原型的处理,被赋予父类原型的一个引用,这是一个对象,因此这里有一点你要注意,就是子类再想添加原型方法必须通过prototype.对象,通过点语法的形式一个一个添加方法了,否则直接赋予对象就会覆盖掉从父类原型继承的对象了

javascript设计模式-面向对象编程(封装、继承、多态)_第2张图片

 

2.7 多继承

讲解多继承之前先引出当前很流行的一个用来继承单对象属性的extend方法

// 单继承 属性复制 (浅拷贝) 
var extend = function(target, source) {
	// 遍历源对象中的属性
	for (var property in source) {
		// 将源对象中的属性复制到目标对象中
		target[property] = source[property];
	}
	// 返回目标对象
	return target;
};

测试

var book = {
	name: 'JavaScript设计模式',
	alike: ['css', 'html', 'JavaScript']
}
var anotherBook = {
	color: 'blue'
}

extend(anotherBook, book);

console.log(anotherBook.name); // JavaScript设计模式 
console.log(anotherBook.alike); // ["css", "html", "JavaScript"] 

anotherBook.alike.push('ajax');
anotherBook.name = '设计模式';

console.log(anotherBook.name); // 设计模式
console.log(anotherBook.alike); // ["css", "html", "JavaScript", "ajax"] 
console.log(book.name); // JavaScript设计模式 
console.log(book.alike); // ["css", "html", "JavaScript", "ajax"]

多继承的实现

// 多继承 属性复制  
var mix = function() {
	var i = 1, // 从第二个参数起为被继承的对象
		len = arguments.length, // 获取参数长度
		target = arguments[0], // 第一个对象为目标对象
		arg; // 缓存参数对象
		
	// 遍历被继承的对象
	for (; i < len; i++) {
		// 缓存当前对象
		arg = arguments[i];
		// 遍历被继承对象中的属性
		for (var property in arg) {
			// 将被继承对象中的属性复制到目标对象中
			target[property] = arg[property];
		}
	}

	// 返回目标对象
	return target;
};

可以将它绑定到原生对象Object上,这样所有的对象就可以拥有这个方法了

Object.prototype.mix = function() {
	var i = 0, // 从第一个参数起为被继承的对象
		len = arguments.length, // 获取参数长度
		arg; // 缓存参数对象
		
	// 遍历被继承的对象
	for (; i < len; i++) {
		// 缓存当前对象
		arg = arguments[i];
		// 遍历被继承对象中的属性
		for (var property in arg) {
			// 将被继承对象中的属性复制到目标对象中
			this[property] = arg[property];
		}
	}
}

测试

var otherBook = {
	about: '一本JavaScript书'
}
var book1 = {
	color: 'red'
}
var book2 = {
	color: 'blue',
	name: 'JavaScript设计模式'
}

otherBook.mix(book1, book2);
console.log(otherBook); // Object {color: "blue", name: "JavaScript设计模式", mix: function, about: "一本JavaScript书"}

 

 

3.多态

多态,就是同一个方法多种调用方式

利用switch_case实现多态的栗子

// 多态
function add() {
	var arg = arguments, // 获取参数
		len = arg.length; // 获取参数长度

	switch (len) {
		// 如果没有参数
		case 0:
			return 10;

			// 如果只有一个参数
		case 1:
			return 10 + arg[0];

			// 如果有两个参数
		case 2:
			return arg[0] + arg[1];
	}
}

// 测试用例
console.log(add()); // 10
console.log(add(5)); // 15
console.log(add(6, 7)); // 13
function Add() {
	// 无参数算法
	function zero() {
		return 10;
	}
	
	// 一个参数算法
	function one(num) {
		return 10 + num;
	}
	
	// 两个参数算法
	function two(num1, num2) {
		return num1 + num2;
	}
	
	// 相加共有方法
	this.add = function() {
		var arg = arguments,
			len = arg.length; // 获取参数长度
			
		switch (len) {
			// 如果没有参数
			case 0:
				return zero();
				
			/// 如果只有一个参数
			case 1:
				return one(arg[0]);
				
			// 如果有两个参数
			case 2:
				return two(arg[0], arg[1]);
		}
	}
}

// 实例化类
var A = new Add();

console.log(A.add()); // 10
console.log(A.add(5)); // 15
console.log(A.add(6, 7)); // 13

 

 

总结

封装与继承是面向对象中的两个主要特性,继承即是对原有对象的封装,从中创建私有属性、私有方法、特权方法、共有属性、共有方法等,对于每种属性与每种方法特点是不一样的,有的不论对类如何实例化,它只创建一次,那么这类属性或者方法我们称之为静态的。有的只被类所拥有,那么这类属性和方法又是静态类方法与静态类属性。当然可被继承的方法与属性无外乎两类,一类在构造函数中,这类属性与方法在对象实例化时被复制一遍。另一类在类的原型对象中,这类属性与方法在对象实例化时被所有实例化对象所共用。

提到类的实例化我们就引出了继承,当然如果实例化的是对象那么则为对象继承,如果实例化的是类(当然类也是一种对象,只不过是用来创建对象的),那么就是一种类的继承。对于类的继承我们根据继承的方式又分为很多种,通过原型链继承的方式我们称之为类式继承,通过构造函数继承的方式我们称之为构造函数式继承,那么将这两种方式组合起来的继承方式我们称之为组合继承,由于类式继承过程中会实例化父类,这样如果父类构造函数极其复杂,那么这种方式对构造函数的开销是不值得的,此时有了一种新的继承方式,通过在一个函数内的过渡对象实现继承并返回新对象的方式我们称之为寄生式继承,此时我们在结合构造函数时继承,这样再融合构造函数继承中的优点并去除其缺点,得到的继承方式我们称之为寄生组合式继承。当然有时候子类对父类实现继承可以通过拷贝方法与属性的方式来实现,这就有了多继承,即将多个父类(对象)的属性与方法拷贝给子类实现继承。

对于面向对象中的多态,在JavaScript中实现起来就容易得多了,通过对传递的参数判断来决定执行逻辑,即可实现一种多态处理机制。

 

 

 

 

 

 

 

你可能感兴趣的:(javascript,设计模式,面向对象编程,设计模式,javascript)