本文是学习了《JavaScript设计模式》(谢廷晟 译)做的学习笔记

一、JavaScript的灵活性

1. 普通 functon

    function startAnimation() {...}
function stopAnimation() {...}

2. 类


var Anim = function() {} #构造函数 # 方式一
Anim.prototype.start = function() {...}
Anim.prototype.stop = function() {...} # 方式二
Anim.prototype = {
start: function() {...},
stop: function() {...}
} # usage
var myAnim = new Anim()
myAnim.start()
myAnim.stop()

3. 为实例创建方法


# 方式一
Function.prototype.method = function(name, fn) {
this.prototype[name] = fn
}
# usage
var Anim = function() {}
Anim.method('start', function(){...})
Anim.method('stop', function(){...}) # 方式二 (链式)
Function.prototype.method = function(name, fn) {
this.prototype[name] = fn
return this
}
# usage
Anim.method('start', function(){...})
.method('stop', function(){...})

4. 弱类型语言

  • 原始数据类型按值传递,其他数据类型按引用传递
  • javascript 类型系统可以分为标准类型对象类型,进一步标准类型又可以分为原始类型和引用类型,而对象类型又可以分为内置对象类型、普通对象类型、自定义对象类型。

  1. 原始类型(值类型):

    • Undefined undefined
    • Null null
    • Boolean true
    • String 'hello'
    • Number 123
  2. 引用类型(对象类型):
    • Object
    • Array
    • Data
    • RegExp
    • Function

二、接口

1. JavaScript 中模拟接口

注释描述接口

     # 只是使用注释说明接口

     /*

     interface Composite {
function add (child)
function remove (clild)
function getChild(index)
}
interface FormItem {
function save ()
} */

使用属性检查模仿接口

假如我们要创建一个叫 CompositeForm 的类,并且它有两个规定好的接口 CompositeFormItem需要实现,我们可以在实现这个类的时候给它添加一个 implementsInterfaces 数组用于声明该类有哪些方法或属性,这样我们就能在需要的地方用统一的检测方法 implements 来检测是否该类作者已经实现了这些接口。

     var CompositeForm = function(id, method, action) {
this.implementsInterfaces = ['Composite', 'FormItem']; //它自己说它实现了
} // 该方法需要传入一个具有 Composite、FormItem 接口的对象,所以我们在里面使用 implements 方法进行检查
function addForm(formInstance) {
if(!implements(formInstance, 'Composite', 'FormItem')) {
throw new Error("Object dost not implement a require interface.");
}
// 检查通过后进行其他处理
} function implements(object) {
for(var i=1; i < arguments.length; i++) {
var interfaceName = arguments[i];
var interfaceFound = false;
for(var j=0; j<object.implementsInterfaces.length; j++) {
if(object.implementsInterfaces[j] == interfaceName) {
interfaceFound = true;
break;
}
}
if(!interfaceFound) {
return false;
}
}
return true
} // 使用
addForm(new CompositeForm()) //只能知道它是否‘说’自己实现了接口,并未确保类真正实现了接口

用鸭式辩型模仿接口

假如我们要实现一个 Composite 类,它继承了两个类的方法 Composite:['add', 'remove', 'getchild'] FormItem:['save'],那我们可以实现专门的类:Interface ,他的每个实例用于指定一些接口方法(有点像 Java 的接口类),它有一个公共的方法 ensureImplements 可以用来检测某个对象是否有实现对应的 Interface 实例指定的接口。

   var Interface = function(name, methods) {
if(arguments.length !== 2) {
throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
}
this.name = name;
this.methods = [];
for(var i=0, len=methods.length; i<len; i++) {
if(typeof methods[i] !== 'string') {
throw new Error("Interface constructor expects method names to be passed in as a string")
}
this.methods.push(methods[i]);
}
} Interface.ensureImplements = function(object) {
if(arguments.length < 2) {
throw new Error("Function Interface.ensureImplements call with " + arguments.length + "arguments, but expected at least 2")
}
for(var i=1,len=arguments.length; i<len; i++) {
var interface = arguments[i];
if(interface.constructor !== Interface) {
throw new Error("Function Interface.ensureImplements expects arguments two and above to be instances of Interface");
}
for(var j=0, methodsLen = interface.methods.length; j<methodsLen; j++) {
var method = interface.methods[j];
if(!object[method] || typeof object[method] !== 'function') {
throw new Error('Function Interface.ensureImplements: Object does not implement the '+ interface.name + " interface. Method " + method + " was not found")
}
}
}
} // 创建两个 Interface 实例并指定各自需要实现的接口
var Composite = new Interface('Composite', ['add', 'remove', 'getchild']);
var FormItem = new Interface('FormItem', ['save']) // 使用 var CompositeForm = function(id, method, action) {
this.add = function() {} // 这里我们只实现了一个方法,所以会报错
}
function addForm(formInstance) {
// 这里先检查传进来的对象是否实现对应的 Interface 实例所指定接口
Interface.ensureImplements(formInstance, Composite, FormItem) // 通过后……
} addForm(new CompositeForm()) // 调用方法 // 它只关心方法的名称,并不检查其参数的名称、数目或类别

其他方式

  • TypeScript :可以检查函数参数,对类成员可以显式声明访问级别:public、protected、private 等

三、封装和信息隐藏

1. 创建对象的基本模式

1.1 门户大开型对象

   var Book = function(isbn, title) {
this.setIsbn(isbn)
this.setTitle(title)
} Book.prototype = {
checkIsbn: function(isbn) {...}, getIsbn: function(isbn) { return this.isbn },
setIsbn: function(isbn) {
if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN')
this.isbn = isbn
}, getTitle: function() { return this.title },
setTitle: function(title) {
this.title = title || 'No title specified' //验证
}
} // 虽然设置了赋值器,但是属性依然是公开的,可以被直接赋值
// 如 var b = new Book('aa', 'title1') 可直接通过 b.isbn 修改 isbn

1.2 用命名规范区别私有成员

   var Book = function(isbn, title) {
this.setIsbn(isbn)
this.setTitle(title)
} Book.prototype = {
_checkIsbn: function(isbn) {...}, getIsbn: function(isbn) {
return this._isbn
},
setIsbn: function(isbn) {
if(!this._checkout(isbn)) throw new Error('Book: Invalid ISBN')
this._isbn = isbn
}, getTitle: function() {
return this._title
},
setTitle: function(title) {
this._title = title || 'No title specified' //验证
}
} // 下划线只能防止程序员无意间使用某个属性,却不能防止对它的有意使用

1.3 作用域、嵌套函数和闭包

   function foo() {
var a = 10;
return function bar() {
a *= 2;
return a;
}
} #usage
var a = foo()
var b = foo()
a() // 20
a() // 40
b() // 20

1.4 用闭包实现私有成员

   var Book = function(newIsbn, newTitel) {
var isbn, title, author; // Private attrbute 【私有属性】 function checkIsbn(isbn) { return true} // Private methods 【私有方法】 this.getIsbn = function() { // Privileged methods 【特权方法】
return isbn
};
this.setIsbn = function(newIsbn) {
if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN');
isbn = newIsbn
} this.getTitle = function() {
return title
};
this.setTitle = function(newTitel) {
title = newTitel || 'No title specified' //验证
} this.setIsbn(newIsbn)
this.setTitle(newTitel)
} Book.prototype = {
display: function() {} //不需要访问私有属性的方法
}
// usage
var b1 = new Book('11111', 'title1') // 每生成一个新的对象实例都将为每个私用方法和特权方法生成一个新的副本,这会耗费更多内存
// 这种对象创建模式不利于派生子类,因为派生的子类不能访问超类的任何私用属性或方法,这种方式被称为“继承破坏封装”
// 如果创建的类以后可能会需要派生出子类,最好使用【门户大开型】或【命名规范区别私有成员】两种方式

2. 更多高级对象创建方式

2.1 静态方法和属性

   var Book = (function() {
var numOfBooks = 0; // Private static attributes 【静态私有属性】所有实例共享 function checkIsbn(isbn) {return true} // Private static methods【静态私有方法】所有实例共享 return function(newIsbn, newTitel) {
// 单个实例独有
var isbn, title, author; // Private attribute 【私有属性】 this.getIsbn = function() { // Privileged methods 【私有方法】
return isbn
};
this.setIsbn = function(newIsbn) {
if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN');
isbn = newIsbn
} this.getTitle = function() {
return title
};
this.setTitle = function(newTitel) {
title = newTitel || 'No title specified' //验证
} numOfBooks++;
if(numOfBooks > 50) {
throw new Error('Book: only 50 instances of Book can be created')
} this.setIsbn(newIsbn)
this.setTitle(newTitel)
}
})() Book.convertToTitleCase = function(inputstring) { //无需创建实例即可访问的方法
return inputstring
} Book.prototype = { //不需要访问私有属性的方法
display: function() {}
} // usage
var b1 = new Book('11111', 'title1')

2.2 常量

   var Class = (function(){

        // private static attributes【静态私有属性】所有实例共享
var constants = {
UPPER_BOUND: 100,
LOWER_BOUND: -100
} var ctor = {}
ctor.getConstant = function(name) {
return constants[name]
} return ctor
})() # usage Class.getContant('UPPER_BOUNDA') // 创建只有取值器而没有赋值器的私有变量来模仿常量

四、继承

1. 类式继承

1.1 原型链

    function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name
} function Author(name, books) {
Person.call(this, name); // 将 Person 中 this.xxx 复制到这里
this.books = books
} Author.prototype = new Person();
Author.prototype.constructor = Author;
Author.prototype.getBooks = function() {
return this.books
} # usage
var a1 = new Author('aaa', ['a','b'])

1.2 extend 函数

    function extend(subClass, superClass) {
var F = function() {}
F.prototype = superClass.prototype; // 使用 F 是为了避免创建超类的新实例,superClass实例做为原型时,其属性会在subClass的实例里面被共享(如果是引用类型如Array,里面的每一个项修改会导致所有实例都被改),这显然不是我们想要的,所以应该通过 superclass.call(this, ...) 把实例的属性直接引入到 subClass的构造函数中
subClass.prototype = new F();
subClass.prototype.constructor = subClass; /* 增强版 */
subClass.superclass = superClass.prototype // 可直接通过 subClass.superclass.prototype.constructor 访问到超类的构造函数,弱化子类与超类的联系。subClass 的实例是访问不了 superClass 属性的。 // 检查超类的 prototype.contructor 是否指向自身构造函数,如果不是则改指导超类的构造函数
if(superClass.prototype.contructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
} # usage
/* Class Person */
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name
} /* Class Author */
function Author(name, books) {
Person.call(this, name); // 将 Person 中 this.xxx 复制到这里
this.books = books
} extend(Author, Person) //需要在 prototype 上添加新方法前被调用 Author.prototype.getBooks = function() {
return this.books
}

2. 原型式继承

得益于原型链查找的工作机制

    function clone(o) {     // 返回以给定对象为原型的【空对象】
function F() {}
F.prototype = o
return new F()
} // 注意这里是一个对象,不是构造函数
var Person = {
name: 'default name',
getName: function() {
return this.name
}
} var reader = clone(Person)
alert(reader.getName()) // 'default name' reader.name = 'John Smith'
alert(reader.getName()) // 'John Smith' // 原型对象 Person 为其他对象应有的模样提供了一个原型,这正是原型式继承这个名字的由来

对继承而来的成员的读和写的不对等性

第一次读 name 时是读到 Person 上的 name 属性,而第一次写是写 reader.name = 'xxx' 写在 reader 上(引用类型变量还是在原型对象上进行修改,这很糟糕,需要先 reader.books= [] , reader.books.push())


# 工厂方法辅助 function clone(o) {
function F() {}
F.prototype = o
return new F()
} var CompoundObject = {};
CompoundObject.string1 = 'default value';
CompoundObject.createChildObject = function() { // 在父类中实现某个父类属性的【工厂方法】
return {
bool: true,
num: 10
}
}
CompoundObject.childObject = CompoundObject.createChildObject() // 如果父类的属性是引用类型,那么可以通过工厂方法返回这个引用对象赋给对应属性,这样的话其子类在继承了这个对象后可以马上为其子类的相同属性附上对应工厂方法返回的默认引用对象,这样就能避免读写不对称而出现不符合我们想象的情况(如多个实例的属性彼此影响)。 var c1 = clone(CompoundObject); // 继承
c1.childObject = CompoundObject.createChildObject() // 使用父类的工厂方法来产生默认引用类型用于覆盖原型链上的父类对象
c1.childObject.num = 5 // 添加新属性

3. 掺元类

将某个类中指定的方法复制到其他类中,而不是整体继承过去

    function argument(receivingClass, givingClass) {
// 查看是否有其他参数用于指定复制特定方法
if(argument[2]) {
for(var i=2, len=arguments.length; i<len; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]
}
}else {
// 如果没有则全部方法都复制过去 receivingClass
for(methodName in givingClass.prototype) {
if(!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName]
}
}
}
} /* Mixin class */
var Mixin = function () {} // 掺元类
Mixin.prototype = {
serialize: function() {
var output = [];
for(key in this) {
output.push(key + ': ' + this[key])
}
return output.join(', ')
}
} function Author(name, books) {
this.name = name
this.books = books
} argument(Author, Mixin); // 进行复制,后面可以继续加参数指定具体要从 Mixin 复制的方法 var author = new Author('czs', ['js', 'nodejs'])
var serializeStr = author.serialize()
console.log(serializeStr) // 将某个类中指定的方法复制到其他类中,而不是整体继承过去

注意

转载、引用,但请标明作者和原文地址

Javascript设计模式(1)的更多相关文章

  1. 《JavaScript设计模式 张》整理

    最近在研读另外一本关于设计模式的书<JavaScript设计模式>,这本书中描述了更多的设计模式. 一.创建型设计模式 包括简单工厂.工厂方法.抽象工厂.建造者.原型和单例模式. 1)简单 ...

  2. 《JavaScript设计模式与开发实践》整理

    最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...

  3. Javascript设计模式学习一

    学习Javascript设计模式之前,需要先了解一些相关知识,面向对象的基础知识.this等重要概念,以及掌握一些函数式编程的技巧. Js多态 多态的思想:实际上是把“做什么”和“谁去做”分离开来.例 ...

  4. javascript设计模式实践之职责链--具有百叶窗切换图片效果的JQuery插件(三)

    在上一篇<javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)>里,通过采用模板方法模式完成了切换效果对象的构建编写. 接下来就是完成各效果对象的调 ...

  5. javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)

    在上一篇<javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)>里,通过采用迭代器模式完成了各初始化函数的定义和调用. 接下来就要完成各个切换效果的编 ...

  6. javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)

    类似于幻灯片的切换效果,有时需要在网页中完成一些图片的自动切换效果,比如广告,宣传,产品介绍之类的,那么单纯的切就没意思了,需要在切换的时候通过一些效果使得切换生动些. 比较常用之一的就是窗帘切换了. ...

  7. 常用的Javascript设计模式

    <parctical common lisp>的作者曾说,如果你需要一种模式,那一定是哪里出了问题.他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案. 不管是弱类型 ...

  8. Javascript设计模式(摘译)

    说明: 未完成...更新中.... 一.javascipt设计模式分类 设计模式分类有很多标准,最流行的三种如下 1)  creational  --  主要关注对象创建 Creational des ...

  9. JavaScript设计模式学习笔记

    1 JavaScript设计模式深入分析 私有属性和方法:函数有作用域,在函数内用var 关键字声明的变量在外部无法访问,私有属性和方法本质就是你希望在对象外部无法访问的变量. 特权属性和方法:创建属 ...

  10. JavaScript设计模式:读书笔记(未完)

    该篇随我读书的进度持续更新阅读书目:<JavaScript设计模式> 2016/3/30 2016/3/31 2016/4/8 2016/3/30: 模式是一种可复用的解决方案,可用于解决 ...

随机推荐

  1. flask入门与发送邮件与QQ邮箱

    前言: 快两个月没写博客了, 原因是懒了, 没有最初写博客那种看到阅读量上涨, 别人给自己文章点赞后的开心. 心态也发生了不少变化. 有机会再来写写. 前两个月我去厦门某公司实习, 本着去厦门玩一玩还 ...

  2. VS2008集成PC-lint

    引言 C/C++语言的语法拥有其它语言所没有的灵活性,这种灵活性带来了代码效率的提升,但相应也使得代码编写具有很大的随意性,另外C/C++编译器不进行强制类型检查,也不做任何边界检查,这就增加了代码中 ...

  3. jquary 单选,多选,select 获取和设置值 jquary自定义函数

    <%@ page contentType="text/html; charset=UTF-8"%> <%@ taglib prefix="c" ...

  4. bzoj2876 [NOI2012]骑行川藏(拉格朗日乘数法)

    题目描述 蛋蛋非常热衷于挑战自我,今年暑假他准备沿川藏线骑着自行车从成都前往拉萨.川藏线的沿途有着非常美丽的风景,但在这一路上也有着很多的艰难险阻,路况变化多端,而蛋蛋的体力十分有限,因此在每天的骑行 ...

  5. 网卡name-eth1如何修改为eth0

    正常来说,Linux在识别网卡时第一张会是eth0,第二张才是eth1. 有时候我们使用虚拟机克隆技术后网卡的信息就会改变,新克隆出来的虚拟主机网卡名字可能变为eth1.无论我们怎么修改都无法改变,这 ...

  6. Spring mvc学习指南

    使用flash attribute(闪存传值) 在配置文件中添加<mvc:annotion-driven/> 在controller方法参数里面添加RedirectAttributes r ...

  7. MySQL的InnoDB引擎与MyISAM引擎

    MyISAM:这个是默认类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法.与 ...

  8. CentOS Crontab(定时任务)

    安装crontab: yum install crontabs 说明: service crond start //启动服务 service crond stop //关闭服务 service cro ...

  9. Angular Universal(统一平台)笔记

    angular官网高级文档AngularUniversal部分的翻译总结,这东西在angular4开始正式被官方支持了,目前其实支持的服务器端还没有很多,但好歹包括了node和DotNetCore,算 ...

  10. hdu1394 分治 or 线段树

    利用分治求一次逆序数,然后每次把第一个元素放到末尾,设该交换元素的值为x,设上一次求得的逆序数为y,那么此时的逆序数等于y - x + (n - x - 1),减去x是因为x作为第一个元素,其后共有x ...