【学习笔记javascript设计模式与开发实践----1】

个人吐槽

读了不少的关于js的基础书箱,有必要在js的编码结构和编码效率上有所提高(个人资质很差,所以只代表个人观点。如有说的不对的,还请大神们见谅……),个人一直觉得,不会用设计模式不代表你就不是一个好的程序员,可能在编码的过程中,你已经不知不觉的用到一些模式,有好多人在面试的过程都会被问到各种设计模式的东西。如果被面试的人是背给你听的,那好吧……,中国的应试教育真的是很坑人啊!算啦回到正题。以往也看过一些js设计模式方面的书箱,感觉就是把java语言的方式原样搬到js上;学了以后感觉没有太大的收获。两种语言的特点有差别,感觉把js模拟成java就违背了js的设计初衷。不得不吐槽一下,出书的你们要是抄也要把人家的思想抄来,别TMD人云亦云,即然要传道授业解惑,就别TMD坑和误导(中国技术现状,ctrl+Aàctral+Càctral+V)那些渴望学习真正技术的人。偶然看到在《javascript设计模式与开发实践》这本书,感觉很是我的菜,有必要在读的过程中作一下读书笔记!!!共勉吧!

All road lead to Roman. 这一很喜欢这句话,也是从小到大一个心路历程。又扯远啦啊,其实程序开发过程中也是一样的,实现一种功能的实现,不可能只有一种方法和解决方案。如果你是一个人玩,怎样都行喽,如果你开发的是一个多人协同,要求考虑效率和性能各种balabala……,作为码农的我们就有必须学习和扎实一下数据结构和设计模式这些东东,前人走过的路和踩过的坑,我们没必须重蹈覆辙;站在巨人的肩膀上你才会看的更远奥!!

不扯啦,接下来的章节中一起学习一下这本书吧。

模式的适用性

从某些角度来看设计模式有可能带来代码量的增加,或许也会把系统的逻辑搞得更复杂。但软件开发的成本并非全部在开发阶段,设计模式的作用是让人们写出可复用和可维护的程序。

所有设计模式的实现都要遵循一个原则:即“找出程序中变化的地方,并将变化封闭起来”一个程序的设计总是可以分为可变的部分和不变的部分。当我们找出可变的部分,并且把这些部分封装起来,那么剩下的就是不变和稳定的部分。这些不变和稳定的部分是非常容易利用的。这也是设计模式为什么描写的是可复用面向对象软件基础的原因。

设计模式被人误解的一个重要原因是人们对它的误用和滥用,比如将一些模式用在了错误场景中,或者说在不该使用模式的地方刻意使用模式。别整成“你有把锤子,你就看见什么都当成钉子”

分辨模式的关键是意图而不结构

在设计模式学习中,有人经常发现这样的疑问:代理模式和装饰者模式,策略模式和状态模式,策略模式和智能命令模式,这些模式的类图看起来几乎一模一样,它们到底有什么区别?实际上这种情况是普高存在的,许多模式的类图看起来都差不多,模式只有放在具体的环境下才有意义。比如我们的手机,把它当电话时候,它就是电话;把它当闹钟的时候,它就是闹钟;用它玩游戏的时候,它就是游戏机。

误解

这个我深有体会,因为之前看过几本关于设计模式(js语言的)。不看还好看完直接搞蒙啦!其实就是如下这两个方面的问题,第一个问题是习惯把静态类型语言的设计模式照搬到js上,比如有人为了模拟js的工厂方法,而生硬地把创建对象的步骤延迟到子类中。实际上,在java静态类型语言中,让子类来决定创建何种对象的原因是为了让程序迎合依赖倒置原则。在这些语言中创建对象时,先解开对象类型之间的耦合关系非常重要,这样才有机会在将来让对象表现出多态性。而js不存在类型耦合问题,自然也没有必要刻意去把对象“延迟”到子类创建,也就是说,js实际上是不需要工厂模式的。模式的存在首先是能为我们解决什么问题,这种牵强的模拟只会让人觉得设计模式既难懂又没什么用。

另一个问题是习惯根据模式的名字去臆测该模式的一切。比如命令模式本意是把请求封装到对象中,利用命令模式可以解开请求发送者和请求接受者之间的耦合关系。但命令模式经常被人误解为只是一个名为execute的普通方法

 

第一部分:基础知识

1.1动态类型语言和鸭子类型

编程语言按照数据类型大体可以分为两类:一类是静态语言,一类是动态语言。

静态语言的

优点:在编译时就能发现类型不匹配的错误,编译器可以帮助我们避免程序在运行期间有可能发生的一些错误。其次如果在程序中明确地规定了数据类型,编译器还可以针对这些信息对程序进行一些优化工作,提高程序执行速度。

缺点:程序员依照强契约来编写程序,为每个变量规定数据,归根结底只是辅助我们编写可靠性高的程序的一种手段,而不是编写程序的目的,毕竟大部分人写程序的目的是为了完成需求交付生产。其次类型的声明也会增加更多的代码,在程序编写过程中,这些细节会让程序员的精力从思考业务逻辑上分散开来。

动态语言

优点是编写的代码数量更少,看起来也更加简洁,程序员可以把精力更多地放在业务逻辑上面。虽然不区分类型在某些情况下会让程序变得很难理解,但整体而言,代码量越少,越专注于逻辑表达,对阅读程序是越有帮助。

缺点:无法保证变量的类型,从而在程序的运行期间有可能发生类型相关的错误。

动态类型语言对变量类型的宽容给实际编码带来了很大的灵活性。由于无需进行类型检测,我们可能尝试调用任何对象的任意方法,而无需去考虑它原本是否被设计拥有该方法。

鸭式辨型

该方式的白话说法就是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子”。

这个例子很形像:

从前在javascript王国里,国王喜欢听鸭子合唱,于是手下拍马屁的召集100只鸭子来合唱,可以找了99只后,就找不到啦。这时候有个鸡,它即会发出鸭子的叫声,走路也很像鸭子,于是把它抓过来凑进合唱团。完成任务(凑齐一百只,成功解决问题,感觉好像有点像中国的黑猫和白猫抓耗子,但这还是同类,如果要是猫凑不够,要是有只管闲事儿的狗,也可以凑进捕鼠队噢!!!)

上面个这故事告诉我们,我们只需要关注对象的行为,而不用关心它是什么?即关注Has-A,而不是IS-A

实现如下

var dark = function(){
 darkSinging:function(){
    console.log(‘嘎嘎嘎’);
 }
};
var chicken = function(){
 darkSinging:function(){
      console.log(‘嘎嘎嘎’);
}
};
var  choir= []; //合唱团
var joinChior = function(animal){
 if(animal&& typeof animal.duckSinging===’function’){
   choir.push(animal); //入团儿喽
   console.log(‘恭喜加入合唱团’);
   console.log(‘合唱团已有成员数量:’+choir.length);
 }
}
 

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

多态(ploymorphism)

其实际的含义:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果,换句话说,给不同的对象发送同一个消息的时候,这些对象根据这个消息分别给出不同的反馈。

例:主人家里养了两只动物,分别是一只鸭子和一只鸡,当主人向它们发出“叫”的命令时,鸭子会“嘎嘎嘎”叫,而鸡会“咯咯咯”。这两种动物会以自己的方式来发出叫声。它们同样“都是动物”并且可以发出叫声,但根据主人的主指令,它们会发出不同的叫声。

一段“多态”的js代码

var makeSound = function(animal){
  if(animal  instanceof Duck){
    console.log(‘嘎嘎嘎’);
  }elseif(animal  instanceof Chicken){
   console.log(‘咯咯咯’);
  }
}
 
var Duck = function(){};
var  Chicken=function(){};
makeSound(new Duck());//嘎嘎嘎
makeSound(new Chicken());//咯咯咯

如上代码,我们如果再添加新的动物进来,就会在makeSound方法中添加更的分支判断。修改代码总是危险的,修改的地方越多,程序出错的可能性越大,而且当动物种类越来越多时makeSound有可能变成一个巨大的函数。

多态背后的思想是将“做什么”和“谁去做及怎样去做”分离开来,也就是“不变的事物”与“可能改变的事物”分离开来。不变的是都会叫,可变的是怎么叫。把不变的部分隔离出来,把可变的部分封装起来,这给予我们扩展程序的能力,程序看起来是可生长的,也是符合开放封闭原则的。相对于修改代码来说,仅仅增加代码就能完成同样的功能,这显然优雅和安全得多

 

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

Brendan Eich为javascript设计面向对象系统时,借鉴了Self和Smalltalk这两门基于原型的语言。之所以选择基于原型的面向对象系统,并不是因为时间匆忙,它设计起来相对简单,而是国为从一开始Eich就没打算在js中加入类的概念。

使用克隆的原型模式

从设计模式的角度讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象,一种方法是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。

ECAMScript5提供了Object.create方法,可以用来克隆对象。

var Plane = function(){
 this.blood =100;
 this.attackLevel= 1;
 this.defenseLevel=1;
}
var plane = new Plane();
plane.blood=500;
plane.attackeLevel=10;
plane.defenseLevel=7;
var clonePlane = Object.create(plane);
console.log(clonePlane);//{blood:500,attackLevel:10,defenseLevel:7}
在不支持Object.create方法的浏览器中,则可以使用以下代码
Object.create = Object.create ||function(obj){
 var  F = function(){};
 F.prototype=obj;
 return new F();
}

克降是创建对象的手段

通过上一节的代码,我们看到如何通过原型模式来克隆出一个一模一样的对象。但原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。但javascript本身是一门基于原型的面向对象语言,它的对象系统就是使用原型模式来搭建的,在这里称之为原型编程范型编程也许更合适。

原型编程范型的一些规则

原型编程中一个重要的特性,即当对象无法响应某个请求时,会把该请求委托给它自己的原型。即原型编程范型至少包括以下基本原则

o  所有数据都是是对象

o  要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆。

o  对象会记住它的原型。

o  如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

javascript中的原型继承

1.    所有的数据都是对象

javascript在设计的时候,模仿java引入了两套类型机制:基本数据类型(undefined、null这个可以理解为object、string、number、boolean)和对象类型

按照javascript设计者的本意,除了undefined之外,一切都应是对象。为了实现这一目标,number/boolean/string这几种基本数据类型也可以通过“包装类”的方式变成对象数据来处理。我们不能说javascript中所有数据类型是对象,但可以说绝大多数数据是对象。那么相信在javascript中也一定会有一个根对象存在,这些对象追根溯源都来源于这个根对象。事实上,js的根对象就是Object.prototype对象。它是一个空对象如:

var  obj1 = new Object();
var  obj2 = {};

可以用ECMScript5提供的Object.getPrototypeOf来查看这两个对象的原型:

Object.getPrototypeOf(obj1)===Object.prototype//true
Object.getPrototypeOf(obj2)===Object.prototype//true<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

2.    要得到一个对象,不是通过实例化类,而是找到一个对象作为原型克隆它

通过new运算符从构造器中得到一个对象,下面的代码:

functionPerson(name){
   this.name = name;
}
Person.prototype.getName=function(){
   return this.name;
}
var  a = new Person(‘sven’);
console.log(a.name);//output:sven
console.log(a.getName());//output:sven
console.log(Object.getPrototypeOf(a)===Person.prototype);//output:true

在javascript中没有类的概念,我们已经反复强调过啦。但这明明是调用new Person()来创建对象了吗?

这里的Person并不是类,而是函数构造器,javascript的函数既可以作为普通函数调用,也可以作为构造器被调用。当使用new运算符来调用函数时,此时的函数是一个构造器。用new运算符来创建对象的过程,实际上只是先克隆Object.prototype对象,再进行一些其它额外操作的过程(见之前的博客中有一个extends的方法来获取一个对象)。如在chrome和firefox等向外暴露了对象__prototype__属性的浏览器下,我们可以通过下面这段代码来理解new运算的过程

functionPerson(name){
   this.name = name;
}
Person.prototype.getName= function(){
  return this.name;
}
var  objectFactory = function(){
var  obj = new  Object(), //从Object.prototype上克隆一个空的对象
Constructor = [].shift.call(arguments); //取得外部传入的构造器
obj.__proto__=Constructor.prototype;
var ret = Constructor.apply(obj,arguments);
return typeof ret===’object’?ret:obj; //确保构造器总是返回一个对象
}
 
var  a = objectFactory(Person,’sven’);
console.log(a.name);
console.log(a.getName());
console.log(Object.getPrototypeOf(a)===Person.prototype);//输出true

调用如下代码产生一样的结果

var   a = objectFactory(A,’sven’);
var   b = new A(‘sven’);<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

3.    对象会记住它的原型

要实现原型链查找机制,每个对象至少应该先记住它自己的原型。目前我们一直在讨论“对象的原型”,针对javascript的真正实现来说,其实并不能说是对象的原型,而只能说对象的构造器的原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。

javascript给对象提供了一个名为___proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器原型对象,即{Constructor}.prototype。在一些浏览器中,__proto__被公开出来,如chrome或firefox上用代码来验证:

var  a = new Object();
console.log(a.__proto__===Object.prototype);//true

这里就是__proto__对象跟“对象构造器的原型”联系起来的纽带。正因为对象通过__proto__属性记住它的构造器的原型,所以我们用上一节用objectFactory函数来模拟用new创建对象时,需要手动给obj对象设置正确的__proto__指向。通过种句使得obj.__proto__指向Person.prototype而不是Object.prototype。

4.    如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型

原型继承的未来

另外,ECMSript 6带来了新的Class语法。这让javascript看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象如下

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());

小结

原型模式是一种设计模式,也是一种编程泛型,它构成了javascript这门语言的根本。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值