一、背景
js是一门动态的语言,没有类型之分,不需要利用抽象类或者interface给对象进行“向上转型”,或者说,天生就是向上转型的。即在动态类型语言中,对象的多态性是与生俱来的。
因为不需要向上转型,因此接口在js中的最大作用就退化到了检查对象的规范性。
动态类型语言中广泛采用“鸭子类型”的思想:如果它走起来像鸭子,叫起来也像鸭子,那么它就是鸭子。
这种思想运用的地方很多,如一个对象有length属性,也可以根据数字下标来存取属性,该对象可以当做数组来使用。一个对象如果有push和pop方法,就能当做栈来使用。
二、什么是设计模式
设计模式是一种被多人熟知的、反复使用的、经过分类编码的、代码设计经验的总结。
三、多态
同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。
多态背后的思想是将“做什么”和“谁去做及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来。
这是一段“多态”的代码:
var makeSound = function (animal) {
if (animal instanceof Duck) {
console.log('嘎嘎')
} else if (animal instanceof Chicken) {
console.log('咯咯')
}
}
var Duck = function () {}
var Chicken = function () {}
makeSound(new Duck())
makeSound(new Chicken())
现在,如果需要加一只狗的sound方法,那么就需要修改makeSound方法,这种写法在将来的维护中很容易出错。而多态的思想是动静分离,将不变的部分隔离出来,将可变的部分封装起来,程序看起来是可生长的。
var Duck = function () {}
var Chicken = function () {}
Duck.prototype.sound = function () {
console.log('嘎嘎')
}
Chicken.prototype.sound = function () {
console.log('咯咯')
}
var makeSound = function (animal) {
animal.sound()
}
makeSound(new Duck())
makeSound(new Chicken())
如果需要追加一只狗:
var Dog = function () {}
Dog.prototype.sound = function () {
console.log('旺旺')
}
makeSound(new Dog())
这样,就不需要去动makeSound方法,“做什么”就不需要改变了;需要改变的是“谁去做及怎样去做”,也就是后来加的Dog类
多态的写法就是把过程化的分支语句转化为对象的多态性,从而消除这些条件分支语句。
将行为分布在各个对象中,并让这些对象各自负责自己的行为,这就是面向对象设计的优点。
四、继承
使用继承来得到多态效果,是让对象表现出多态性的最常用手段。
继承通常包括实现继承
和接口继承
五、封装
将信息隐藏
5.1 封装数据
js中通过函数的作用域来实现封装的特性,而且只能模拟出public和private这两种封装性
在ES6的let之前,一般通过函数来创建作用域:
var obj = (function () {
var name = 'xx' // 私有(private)变量
return {
getName: function () { // 公开(public)方法
return name
}
}
})()
5.2 封装实现
封装不仅仅是隐藏数据,还包括隐藏实现细节、设计细节以及隐藏对象的类型等。从封装实现细节来讲,封装使得对象内部的变化对其他对象而言是不透明的,也就是不可见的。对象对它自己的行为负责。其他对象或者用户都不关心它的内部实现。封装使得对象之间的耦合变松散,对象之间只通过暴露的api接口来通信。当我们修改一个对象时,可以随意地修改它的内部实现,只要对外的接口没有变化,就不会影响到程序的其他功能。
5.3 封装类型
js封装类型时不需要判断类型
5.4 封装变化
找到变化封装之。
创建型模式:要创建一个对象,是一个抽象行为,具体创建什么对象是可以变化的。比如创建一个“人”的对象,这个人的姓名、年龄、性别、职业等都是未知的
结构型模式:封装对象之间的组合关系。如父子关系、兄弟关系、里外、上下等关系
行为型模式:封装对象的行为变化。比如操作电视,可以是打开、关闭、换台等操作
通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。如此可以最大程度保证程序的稳定性和拓展性。
六、原型模式和基于原型继承的js对象
在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。在原型编程的思想中,类并不是必须的,对象可以通过克隆另一个对象所得。
原型模式不止是js在使用,其他语言也有使用的。
var Animal = function () {
this.name = '动物'
this.age = 10
}
var animal = new Animal()
animal.name = '小动物'
animal.age = 20
var cat = Object.create(animal)
console.log(cat.name) // 小动物
console.log(cat.age) // 20
原型继承:
在js中,我们不需要关心克隆的细节,这是引擎内部实现的。我们所需要做的只是显示地调用var obj = new Object()或var obj = {},此时引擎内部会从Object.prototype上克隆一个对象出来,最终得到的就是这个对象。
var obj1 = {}
var obj2 = new Object()
console.log(Object.getPrototypeOf(obj1) === Object.prototype) // true
console.log(Object.getPrototypeOf(obj2) === Object.prototype) // true
obj1和obj2自动拥有Object.prototype
在new的过程中,new会将Object.prototype
赋值给obj.__proto__
function Person(name) {
this.name = name
}
Person.prototype.getName = function () {
return this.name
}
var objFactory = function () {
var obj = {} // 创建一个空对象
var Constructor = arguments[0] // Person函数
obj.__proto__ = Constructor.prototype // 相当于new,获取Person的原型
var args = Array.prototype.slice.call(arguments, 1)
Constructor.apply(obj, args) // 借用Person函数,设置obj的属性
return obj
}
var p1 = objFactory(Person, 'xx')
var p2 = new Person('xx')
console.log(p1)
console.log(p2)
典型的原型继承:
var A = function () {}
var B = function () {}
B.prototype = new A() // 将构造器的原型指向另一个对象
继承总是发生在对象和对象之间。函数和函数之间的变量向上查找是作用域链,对象和对象的向上查找是原型链。