1.1、动态类型语言和鸭子类型
编程语言按照数据类型大体可以分为两类,一类是静态类型语言,另一类是动态类型语言。
- 静态类型语言在编译时便已确定变量的类型,
- 动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。
- 这一切都建立在鸭子类型(duck typing)的概念上,鸭子类型的通俗说法是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”
故事
从前在JavaScript王国里,有一个国王,他觉得世界上最美妙的声音就是鸭子的叫声,于是国王召集大臣,要组建一个1000只鸭子组成的合唱团。大臣们找遍了全国,终于找到999只鸭子,但是始终还差一只,最后大臣发现有一只非常特别的鸡,它的叫声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。代码实现
```javascript var duck = { duckSinging: function () { console.log(‘嘎嘎嘎~’) }, }
var chicken = { duckSinging: function () { console.log(‘嘎嘎嘎~’) }, }
// 合唱团 var choir = []
var joinChoir = function (animal) { if (animal && typeof animal.duckSinging === ‘function’) { choir.push(animal) console.log(‘恭喜加入合唱团!’) console.log(‘合唱团的成员数量:’ + choir.length) } }
joinChoir(duck) joinChoir(chicken)
> [https://jsbin.com/sejanagaju/edit?js,console](https://jsbin.com/sejanagaju/edit?js,console)
<a name="X2PrZ"></a>
##### 结果
```javascript
恭喜加入合唱团!
合唱团的成员数量:1
恭喜加入合唱团!
合唱团的成员数量:2
我们看到,对于加入合唱团的动物,大臣们根本无需检查它们的类型,而是只需要保证它们拥有duckSinging方法。如果下次期望加入合唱团的是一只小狗,而这只小狗刚好也会鸭子叫,我相信这只小狗也能顺利加入。
在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。利用鸭子类型的思想,我们不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是面向实现编程”。例如,一个对象若有push和pop方法,并且这些方法提供了正确的实现,它就可以被当作栈来使用。一个对象如果有length属性,也可以依照下标来存取属性(最好还要拥有slice和splice等方法),这个对象就可以被当作数组来使用。
1.2、多肽
“多态”一词源于希腊文polymorphism,拆开来看是poly(复数)+ morph(形态)+ ism,从字面上我们可以理解为复数形态。
多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。
故事
主人家里养了两只动物,分别是一只鸭和一只鸡,当主人向它们发出“叫”的命令时,鸭会“嘎嘎嘎”地叫,而鸡会“咯咯咯”地叫。这两只动物都会以自己的方式来发出叫声。它们同样“都是动物,并且可以发出叫声”,但根据主人的指令,它们会各自发出不同的叫声。
代码实现
- instanceof判断类型 ```javascript 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())
> [https://jsbin.com/qurukaziko/edit?js,console](https://jsbin.com/qurukaziko/edit?js,console)
<a name="lhje2"></a>
##### 结果
```javascript
嘎嘎嘎~
咯咯咯~
这段代码确实体现了“多态性”,当我们分别向鸭和鸡发出“叫唤”的消息时,它们根据此消息作出了各自不同的反应。但这样的“多态性”是无法令人满意的,如果后来又增加了一只动物,比如狗,显然狗的叫声是“汪汪汪”,此时我们必须得改动makeSound函数,才能让狗也发出叫声。修改代码总是危险的,修改的地方越多,程序出错的可能性就越大,而且当动物的种类越来越多时,makeSound有可能变成一个巨大的函数。
代码实现2.0
- 原型方法 ```javascript // 下面是改写后的代码,首先我们把不变的部分隔离出来,那就是所有的动物都会发出叫声: var makeSound = function (animal) { animal.sound() }
// 然后给可变的部分各自封装起来,我们刚刚谈到的多态性实际上指的是对象的多态性 var Duck = function () {} Duck.prototype.sound = function () { console.log(‘嘎嘎嘎~’) }
var Chicken = function () {} Chicken.prototype.sound = function () { console.log(‘咯咯咯~’) }
makeSound(new Duck()) makeSound(new Chicken())
> [https://jsbin.com/rexavamalo/edit?js,console](https://jsbin.com/rexavamalo/edit?js,console)
- 如果某一天,我们新增了一只狗,那么我们只需要以下操作
```javascript
var Dog = function(){}
Dog.prototype.sound = function(){
console.log( '汪汪汪' );
};
makeSound( new Dog() ); // 汪汪汪
1.3、封装
在许多雨燕的对象系统中,封装数据是由语法解析实现的,这些语言也提供了private、public、protected等关键之来提供不同的访问权限。
但是在JavaScript中并没有这些关键字的支持,我们只能依赖变量的作用域来实现封装特性,而且只能模拟出public、private这两种封装性。
除了es6中的let之外,一般我们使用函数来创建作用域:
var myObj = (function () {
var __name = 'job'
return {
getName: function () {
return __name
},
}
})()
console.log(myObj.__name) // undefined
console.log(myObj.getName()) // job
1.4、原型模式和基于原型基础的JavaScript对象系统
以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来,一个对象是通过克隆另一个对象所得到的。
原型模式不单是一种设计模式,也被称为一种编程泛型。
故事
假设我们在编写一个飞机大战的网页游戏。某种飞机拥有分身技能,当它使用分身技能的时候,要在页面中创建一些和它一模一样的飞机。如果不使用原型模式,那么在创建分身之前,无疑必须先保存该飞机的当前血量、炮弹等级、防御等级等信息,随后将这些信息设置到新创建的飞机上面,这样才能得到一假一模一样的新飞机。<br />如果使用原型模式,我们只需要调用负责克隆的方法,便能够完成同样的功能。
代码实现
var Plane = function () {
this.blood = 100
this.attackLevel = 1
this.defenseLevel = 1
}
var plane = new Plane()
plane.blood = 500
plane.attackLevel = 10
plane.defenseLevel = 7
// 在不支持Object.create方法的浏览器中
Object.create =
Object.create ||
function (obj) {
var F = function () {}
F.prototype = obj
return new F()
}
var clonePlane = Object.create(plane)
console.log(clonePlane)
1.5、JavaScript中的原型继承
- 所有的数据都是对象
- 要想得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
- 对象会记住它的原型
- 如果对象无法响应某个请求,他会把这个请求委托给他自己的原型
我们不能说在JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个空的对象。我们在JavaScript遇到的每一个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。
例
var obj1 = new Object()
var obj2 = {}
console.log(Object.getPrototypeOf(obj1) === Object.prototype) // true
console.log(Object.getPrototypeOf(obj2) === Object.prototype) // true
在JavaScript语言里,我们不需要关系克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用var obj1 = new Object()或者var obj2 = {}。此时,引擎内部会从Object.prototype上面克隆一个对象出来,我们最终得到的就是这个对象。
再来看看如何使用new运算符从构造器中得到一个对象
function Person(name) {
this.name = name
}
Person.prototype.getName = function () {
return this.name
}
var a = new Person('smallbaoo')
console.log(a.name) // smallbaoo
console.log(a.getName()) // smallbaoo
console.log(Object.getPrototypeOf(a) === Person.prototype) // true