JavaScript设计模式与开发实践 | 01 - 面向对象的JavaScript

动态类型语言

编程语言按数据类型大致能够分为两类:静态类型语言与动态类型语言。编程

静态类型语言在编译时已肯定变量类型,
动态类型语言的变量类型要到程序运行时,待变量被赋值后,才具备某种类型。设计模式

而JavaScript是一门典型的动态类型语言。数组

动态类型语言对变量类型的宽容使得编程变得很灵活。因为不用进行类型检测,咱们能够调用任何对象的任意方法,而无需去考虑它本来是否被设计为拥有该方法。而这是创建在鸭子类型的概念上。浏览器

鸭子类型

鸭子类型通俗的说法是:安全

若是它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。编程语言

鸭子类型指导咱们只关注对象的行为,而不关注对象自己。函数

在动态类型语言的面向对象设计中,利用鸭子类型的思想,咱们没必要借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是面向实现编程”。例如:prototype

  • 一个对象如有push和pop方法,而且这些方法提供了正确的实现,它就能够被看成栈来使用;设计

  • 一个对象如有length属性,且能够依照下标来存取属性,这个对象就能够被看成数组来使用。code

多态

多态的含义

同一操做做用于不一样的对象上面,能够产生不一样的解释和不一样的执行结果。

对象的多态性

咱们说的多态性,其实就是对象的多态性,那么,对象的多态性是怎样的?如何让对象表现出多态性?

对象多态性的一个简单的例子:

// 让动物发声
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()); 

// 若是像让小狗发声,只须要简单地追加相似的代码
var Dog = function(){};
Dog.prototype.sound = function(){
    console.log('汪汪汪');
}
makeSound(new Dog());

类型检查

静态类型语言(例如Java)在编译时会进行类型匹配检查,这种检查在带来安全性的同时,让代码显得僵硬。所以,静态类型语言一般被设计为能够向上转型

当给一个类变量赋值时,这个变量的类型既可使用这个类自己,也可使用这个类的超类。

就像咱们在描述“一只麻雀在飞”,“一只喜鹊在飞”时,若是想忽略他们的具体类型,能够说成“一只鸟在飞”,这时“鸟”就是“麻雀”和“喜鹊”的超类。

而JavaScript是一门没必要进行类型检查的动态类型语言。

多态的做用

多态是面向对象编程语言中最重要的技术。

多态最根本的做用就是经过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。有一个例子能够很好地诠释:

在电影的拍摄现场,当导演喊出“anciton”时,主角开始背台词,照明师负责打灯光,后面的群众演员伪装中枪倒地,道具师往镜头里撒上雪花。在获得同一个消息时,每一个对象都知道本身应该作什么。若是不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么至关于在电影开始拍摄后,导演每次都要走到每一个人的面前,确认他们的职业分工(类型),而后告诉他们要作什么。若是映射到程序中,那么程序中将充斥着条件分支语句。

将行为分布在各个对象中,并让这些对象各自负责本身的行为,这正是面向对象设计的优势。

多态与设计模式

从面向对象设计的角度出发,经过对封装、继承、多态、组合等技术的反复使用,提炼出一些可重复使用的面向对象设计技巧,咱们将其概括为设计模式。而多态在其中是重中之重,绝大多部分设计模式的实现都离不开多态性的思想。例如:

  • 命令模式

  • 组合模式

  • 策略模式

  • ...

Javascript将函数做为一等对象,因此函数自己也是对象,函数用来封装行为而且可以四处传递。当咱们对一些函数发出“调用”的消息时,这些函数会返回不一样的执行结果,这是多态性的一种体现。

封装

封装的目的是将信息隐藏。封装包括:

  • 封装数据

  • 封装实现

  • 封装类型

  • 封装变化

封装数据

在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言可能提供了private、public、protected等关键字来提供不一样的访问权限。但JavaScript并无提供对这些关键字的支持,只能依赖变量的做用域来实现封装特性,并且只能模拟出public和private这两种封装性。

通常咱们经过函数来建立做用域:

var myObject = (function(){
    var __name = 'sven';    //私有(private)变量
    return {
        getName:function(){        //公开(public)方法
            return __name;
        }
    }
})();

console.log(myObject.getName());    //输出:sven
console.log(myObject.__name);        //输出:undefined

封装实现

从封装实现细节来说,封装使得对象内部的变化对其余对象而言是透明的(即不可见)。对象对它本身的行为负责。其余对象或者用户都不关心它的内部实现。对象使得对象之间的耦合变得松散,对象之间只经过暴露的API接口来通讯。

封装实现细节的例子很是多,例如迭代器。迭代器的做用是在不暴露一个聚合对象的内部表示的前提下,提供一种方式来顺序访问这个聚合对象。如一个each函数,它的做用就是遍历一个聚合对象,使用这个each函数的人不用关心它的内部代码是怎么实现的,只要它提供的功能正确即可以了。

封装类型

封装类型是静态类型语言的一种重要封装方式。封装类型是经过抽象类和接口来进行的。

在JavaScript中,并无对抽象类和接口的支持。JavaScript自己也是一门类型模糊的语言。在封装类型方面,JavaScript没有能力,也没有必要作得更多。

封装变化

从设计模式的角度出发,封装在更重要的层面体现为封装变化。

经过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程当中,咱们只须要替换掉那些容易变化的部分,若是这些部分是已经封装好的,替换起来也想对容易。这能够最大程度地保证程序的稳定性和可扩展性。

原型模式

原型模式

原型模式是用于建立对象的一种模式。

原型模式不用关心对象的具体类型,只需找到一个对象,而后经过克隆来创造一个如出一辙的对象。

原型模式的实现关键是语言自己是否提供了clone方法,ECMAScript5提供了Object.create方法,能够用来克隆对象。

原型模式的真正目的不在于须要获得如出一辙的对象,而是提供了一种便捷的方式去建立某个类型的对象,克隆只是建立这个对象的过程和手段。

在JavaScript这种类型模糊的语言中,建立对象很是容易,也不存在类型耦合的问题。从设计模式的角度来看,原型模式的意义并不算大。但JavaScript自己是一门基于原型的面向对象语言,它的对象系统就是使用原型模式来搭建的,在这里称为原型编程范型也许更合适。

原型编程范型

原型编程中有一个重要特性,即当对象没法响应某个请求时,会把该请求委托给它本身的原型。

而原型编程范型至少包括如下基本原则:

  • 全部的数据都是对象

  • 要获得一个对象,不是经过实例化类,而是找到一个对象做为原型并克隆它

  • 对象会记住它的原型

  • 若是对象没法响应某个请求,它会把这个请求委托给它本身的原型

JavaScript中的原型继承

JavaScript在原型编程范型的规则的基础上来构建它的对象系统。

全部的数据都是对象

JavaScript在设计的时候,模仿Java引入了两套类型机制:基本类型和对象类型。

按照JavaScript设计者的本意,除了undefined以外,一切都应是对象。为了实现这一目标,number、boolean等几种基本类型数据能够经过“包装类”的方式变成对象类型数据。

JavaScript绝大部分数据都是对象。事实上,JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个空对象。JavaScript的每一个对象,都是从Object.prototype对象克隆而来。

要获得一个对象,不是经过实例化类,而是找到一个对象做为原型并克隆它

JavaScript经过显式地调用 var obj1 = new Object() , 或者 var obj2 = {} 。此时,引擎内部会从Object.prototype上面克隆一个对象出来。

这里用了new运算符从构造器中获得了一个对象。在JavaScript里,函数既能够做为普通的函数被调用,也能够做为构造器被调用。用new运算符来建立对象的过程,实际上也只是先克隆Object.prototype对象,再进行一些其余额外操做的过程。

对象会记住它的原型

就JavaScript的真正实现来讲,其实并不能说对象有原型,而只能说对象的构造器有原型。“对象把请求委托给它本身的原型”就是对象把请求委托给它的构造器的原型。

JavaScript给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。在一些浏览器中,__proto__被公开出来。

若是对象没法响应某个请求,它会把这个请求委托给它本身的原型

这条规则是原型继承的精髓所在。当一个对象没法响应某个请求时,它会顺着原型链把请求传递下去,直到遇到一个能够处理请求的对象为止。

虽然JavaScript的对象最初都是由Object.prototype对象克隆而来,但对象构造器的原型并不只限于Object.prototype上,而是能够动态地指向其余对象。例如,当对象A须要对象B的能力时,能够有选择地把对象A的构造器的原型指向对象B,从而达到继承的效果。

PS:本节内容为《JavaScript设计模式与开发实践》第一章 笔记。

相关文章
相关标签/搜索