毛笋

Keep Coding

  • home
  • about
所有文章

毛笋

Keep Coding

  • home
  • about

《JavaScript设计模式与开发实践》——面向对象的JavaScript

2017-05-18

第1章 面向对象的JavaScript

JavaScript没有提供传统面向对象语言中的类式继承,对象与对象间的继承关系有多种实现方式。
在正式学习之前,要了解JavaScript在面向对象方面的知识。

动态类型语言和鸭子类型

静态类型语言与动态类型语言的区别为,静态类型语言在编译时便已确定变量类型,动态类型语言则要到程序运行时,变量被赋值后,才具有类型。
很明显JavaScript属于动态类型语言,其优点为代码量少,简洁,灵活性高。
这一切都建立在鸭子类型的概念上,其通俗说法为“如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。”
鸭子类型指导我们只关注对象行为,而不关注对象本身。例如,如果一个对象有pop和push方法并正确实现,它就可以被当作栈来使用。

多态

多态的实际含义是同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。
其背后的思想是将“做什么”和“谁去做以及怎样去做”分离开,实现过程即,把不变的部分隔离出来,可变的部分封装起来,对应的最常用的手段为继承。
而JavaScript因其动态类型语言的特性,一个对象可以表示多种类型的对象,其多态性是与生俱来的。
设计模式可以理解为在某种场合下对某个问题的一种解决方案,绝大多数的设计模式的实现都离不开多态性的思想。

封装

封装的目的是隐藏信息,广义上的封装包括封装数据,封装实现,封装类型,封装变化。
JavaScript对数据的封装是通过用函数创建作用域来实现的。ES6中可以通过Symbol来创建私有属性
封装实现细节使得对象之间的耦合变松散,只通过暴露的API接口通信,其他的对象或用户不关心内部实现的细节,接口不变就不会影响程序的其他功能。
封装类型是静态类型语言中的一种重要的封装方式,对JavaScript来说是没有必要的。
从设计模式的角度出发,封装更重要的层面体现在封装变化,通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分分离开来,使得容易变化的部分在系统演变的过程中更容易替换,最大程度的保证程序的稳定性和可扩展性。当我们把程序中变化的部分封装好后,剩下的即是稳定的,可复用的。

原型模式和基于原型继承的JavaScript对象系统

在以类为中心的面向对象编程语言中,对象是从类中创建来的。而在原型编程的思想中,类不是必须的,对象是通过克隆另一个对象所得到的。
原型模式不单是一种设计模式,也被成为一种编程泛型,从设计模式的角度来讲,原型模式是用于创建对象的一种模式,不必关心对象的类型,而是直接通过克隆来创建。
原型模式的实现关键是语言本身是否提供了clone方法。ECMAScript5提供了Object.create方法用来克隆对象。
在不支持Object.create方法的浏览器中,则可以使用以下代码:

1
2
3
4
5
6
Object.create = Object.create || function (obj) {
var F = function(){};
F.prototype = obj;

return new F();
}

原型模式的真正目的并非需要得到一个一模一样的对象,而是提供一种便捷的方式去创建某种类型的对象,克隆也只是实现这个目的的过程和方法。
JavaScript本身是基于原型的面向对象语言,从设计模式的角度来讲,原型模式的意义不大,它的对象系统就是使用原型模式搭建的,所以这里将原型模式成为原型编程范型也许更合适。
所有的JavaScript对象都是从某个对象上克隆来的。
原型编程范型至少包含以下基本规则:

  • 所有的数据都是对象。
    这种说法并不标准,JavaScript中是分为基本类型(undefined/null/number/string/boolean)和对象类型的,但基本数据类型也可以通过“包装类”的方法变成对象类型数据来处理。
    JavaScript中的根对象是Object.prototype对象。可以利用ECMAScript5提供的Object.getPrototypeOf查看对象的原型:

    1
    2
    3
    4
    5
    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 = {}。当时用new运算符来调用函数时,Object()并不是类,而是构造器。

  • 对象会记住它的原型。
    JavaScript给对象提供了一个名为_proto_的隐藏属性,某个对象的_proto_属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。在Chrome或FireFox这些把_proto_暴露出来的浏览器中,我们可以通过下述方法验证:

    1
    2
    var a = new Object();
    console.log(a._proto_ === Object.prototype); //true
  • 如果对象无法响应某个请求,它会把这个请求委托给自己的原型。
    这条规则涉及到JavaScript很重要的一个特性,即原型链(如果A对象是从B对象克隆而来,那么B对象就是A对象的原型,同时C对象又是B对象的原型,它们之间就形成了一条原型链)。这条规则即规定了请求是顺着原型链向“父类”方向查找的,原型链的委托机制就是原型继承的本质。下面我们通过最常用的原型继承的方法,来理解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = {name: 'sven'};

    var A = function(){};
    A.prototype = obj;

    var B = function(){};
    B.prototype = new A();

    var b = new B();
    console.log(b.name); // sven

    执行这段代码时,引擎做了以下事情:

    1. 遍历对象b中的所有属性,没有找到name这个属性。
    2. 查找name属性的请求被委托给对象b的构造器的原型,它被_proto_记录着并指向B.prototype,而B.prototype被设置为一个通过new A()创建出来的对象。
    3. 在该对象中依然没有找到name属性,于是请求被委托给这个对象的构造器的原型A.prototype,而A.prototype被设置为对象obj。
    4. 在对象obj中找到了name属性,并返回它的值。

注: ES6带来了新的Class语法,但其背后仍是通过原型机制来创建对象。简单代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Class Animal {
constructor(name) {
this.name = name;
},
getName() {
return this.name;
}
}

Class Dog extends Animal{
constructor(name) {
super(name);
}
speak() {
return "woof";
}
}

var dog = new Dog("Scamp");
console.log(dog.getName() + ' says ' + dog.speak);

小结

本书第1章前部分主要讲JavaScript面向对象的一些基本知识,有JS基础的读者可以不必花心思在上面,本文也只是对主要内容做了简短概括。

后半部分讲述到了本书的第一个设计模式——原型模式,它构成了JavaScript这门语言的基本。

  • JS
  • 设计模式
  • 读书笔记
  • 面向对象
《JavaScript设计模式与开发实践》——this、call和apply
© 2019 毛笋
Hexo Theme Yilia by Litten
  • 所有文章

tag:

  • JS
  • 翻译
  • Node
  • h5
  • 小程序
  • 设计模式
  • 读书笔记
  • 面向对象
  • SVG
  • 字体
  • preload
  • 预加载
  • Flash
  • 动画
  • 多媒体

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true