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)

  1. > [https://jsbin.com/sejanagaju/edit?js,console](https://jsbin.com/sejanagaju/edit?js,console)
  2. <a name="X2PrZ"></a>
  3. ##### 结果
  4. ```javascript
  5. 恭喜加入合唱团!
  6. 合唱团的成员数量:1
  7. 恭喜加入合唱团!
  8. 合唱团的成员数量: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())

  1. > [https://jsbin.com/qurukaziko/edit?js,console](https://jsbin.com/qurukaziko/edit?js,console)
  2. <a name="lhje2"></a>
  3. ##### 结果
  4. ```javascript
  5. 嘎嘎嘎~
  6. 咯咯咯~

这段代码确实体现了“多态性”,当我们分别向鸭和鸡发出“叫唤”的消息时,它们根据此消息作出了各自不同的反应。但这样的“多态性”是无法令人满意的,如果后来又增加了一只动物,比如狗,显然狗的叫声是“汪汪汪”,此时我们必须得改动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())

  1. > [https://jsbin.com/rexavamalo/edit?js,console](https://jsbin.com/rexavamalo/edit?js,console)
  2. - 如果某一天,我们新增了一只狗,那么我们只需要以下操作
  3. ```javascript
  4. var Dog = function(){}
  5. Dog.prototype.sound = function(){
  6. console.log( '汪汪汪' );
  7. };
  8. makeSound( new Dog() ); // 汪汪汪

1.3、封装

在许多雨燕的对象系统中,封装数据是由语法解析实现的,这些语言也提供了private、public、protected等关键之来提供不同的访问权限。
但是在JavaScript中并没有这些关键字的支持,我们只能依赖变量的作用域来实现封装特性,而且只能模拟出public、private这两种封装性。
除了es6中的let之外,一般我们使用函数来创建作用域:

  1. var myObj = (function () {
  2. var __name = 'job'
  3. return {
  4. getName: function () {
  5. return __name
  6. },
  7. }
  8. })()
  9. console.log(myObj.__name) // undefined
  10. console.log(myObj.getName()) // job

https://jsbin.com/gaqajebima/edit?js,console

1.4、原型模式和基于原型基础的JavaScript对象系统

以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来,一个对象是通过克隆另一个对象所得到的。
原型模式不单是一种设计模式,也被称为一种编程泛型。

故事
  1. 假设我们在编写一个飞机大战的网页游戏。某种飞机拥有分身技能,当它使用分身技能的时候,要在页面中创建一些和它一模一样的飞机。如果不使用原型模式,那么在创建分身之前,无疑必须先保存该飞机的当前血量、炮弹等级、防御等级等信息,随后将这些信息设置到新创建的飞机上面,这样才能得到一假一模一样的新飞机。<br />如果使用原型模式,我们只需要调用负责克隆的方法,便能够完成同样的功能。

代码实现
  1. var Plane = function () {
  2. this.blood = 100
  3. this.attackLevel = 1
  4. this.defenseLevel = 1
  5. }
  6. var plane = new Plane()
  7. plane.blood = 500
  8. plane.attackLevel = 10
  9. plane.defenseLevel = 7
  10. // 在不支持Object.create方法的浏览器中
  11. Object.create =
  12. Object.create ||
  13. function (obj) {
  14. var F = function () {}
  15. F.prototype = obj
  16. return new F()
  17. }
  18. var clonePlane = Object.create(plane)
  19. console.log(clonePlane)

https://jsbin.com/bocogidaco/edit?js,console

1.5、JavaScript中的原型继承

  • 所有的数据都是对象
  • 要想得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
  • 对象会记住它的原型
  • 如果对象无法响应某个请求,他会把这个请求委托给他自己的原型

我们不能说在JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个空的对象。我们在JavaScript遇到的每一个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。

  1. var obj1 = new Object()
  2. var obj2 = {}
  3. console.log(Object.getPrototypeOf(obj1) === Object.prototype) // true
  4. console.log(Object.getPrototypeOf(obj2) === Object.prototype) // true

https://jsbin.com/ricamevuka/1/edit?js,console

在JavaScript语言里,我们不需要关系克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用var obj1 = new Object()或者var obj2 = {}。此时,引擎内部会从Object.prototype上面克隆一个对象出来,我们最终得到的就是这个对象。

再来看看如何使用new运算符从构造器中得到一个对象

  1. function Person(name) {
  2. this.name = name
  3. }
  4. Person.prototype.getName = function () {
  5. return this.name
  6. }
  7. var a = new Person('smallbaoo')
  8. console.log(a.name) // smallbaoo
  9. console.log(a.getName()) // smallbaoo
  10. console.log(Object.getPrototypeOf(a) === Person.prototype) // true

https://jsbin.com/janesapugi/edit?js,console

  • 在JavaScript中没有类的概念,这句话我们已经重复很多次了。但是刚刚不是明明调用了new Person()吗?
  • 在这里的Person并不是类,而是函数构造器,JavaScript的函数既可以作为普通函数被调用,也可以作为构造器被调用。当使用new运算符调用函数的时候,此时的函数就是一个构造器。用new运算符来创建对象的过程,实际上也只是克隆Object.prototype对象,再进行一些其他额外操作的过程。
    理解new运算的过程