JavaScript设计模式笔记-面向对象的JavaScript
1.1动态类型语言和鸭子类型
动态类型的程序变量需要到程序运行时候,被赋值后才会有某种类型
var a=1;// number
var str='123';//string
静态类型的语言在编译时就已经确定好值了
int b;//number
string c;//string
鸭子类型
如果它走起来路像鸭子,叫起来也是鸭子,那么它就是鸭子.内涵就是:用代码来模拟一下鸭子类型
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('恭喜加入合唱团','目前合唱团数量为'+choir.length)
}
}
joinChoir(duck);
joinChoir(chicken);
利用鸭子类型的思想,不必借助超类型的帮助就能实现一个原则:’面向接口编程,而不是面向实现编程’
1.2 多态
多态的含义是:同一操作作用于不同对象上面,可以产生不同的解释和不同的执行结果.
1.2.1 一段多态的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()) //咯咯咯
可以从这段代码看出,我们仅仅只写了一个函数 通过判断object的instanceof来发送不同的消息,这就是多态.多态的背后是将’做什么’和’谁去做以及怎么样去做’分离开来.将可变的与不可变的分离开来,比如 叫声是不会变的,所以我们写了个function,但是动物的类型是会变的.
1.2.2 对象的多态性
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函数,把可变的封装起来 也就是sound函数。这样如果要添加其他的功能就不用更改核心函数了.
1.2.3 类型检查和多态
在静态类语言会检查类型类型而导致传递的变量只能接受超类也就是继承类.比如上述的makeSound在java中会检查传递进来的animal类型.
1.2.4 使用继承得到多态的效果
java跪了..这部分可以理解为 静态类需要通过继承然后得到同一类的超类 然后调用makeSound函数实现多态.
1.2.5 JavaScript多态
要实现多态必须得消除类型之间的耦合关系,也就是makeSound不能规定某个函数,比如makeSound只接受Duck,这就是一种耦合性.某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象.也就是消除类型耦合.
1.2.6 多态在面向对象程序设计中的作用
多态的最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句….原来如此…用现实中的例子来举例,我们要编写一个地图应用,有两家API可以选择,过程化的函数是这么写的
var gooleMap={
show:function(){
console.log('开始渲染谷歌地图')
}
}
var renderMap=function(){
gooleMap.show()
}
renderMap()
后来因为某些原因需要用百度地图.同时让renderMap支持两种地图
var baiduMap={
show:function(){
console.log('开始渲染百度地图')
}
}
var renderMap=function(type){
if(type ==='google'){
googleMap.show()
}else if(type ==== 'baidu'){
baiduMap.show()
}
}
renderMap('google')
renderMap('baidu')
抽象化这个函数把相同的地方抽象出来,也就是显示某个地图
var renderMap=function(map){
if(map.show instanceof Function){
map.show()
}
}
这样抽象后,我们就不用关心map是什么地图了,就算是soso地图我们只需要增加一个sosoMap对象就行了.
sosoMap={
show:function(){
//do sth
}
}
renderMap(sosoMap)
实际开发中需要借助适配器模式来解决问题
1.2.7 设计模式与多态
在JavaScript中,函数作为一等公民,函数的本身也是对象,函数用来封装行为并且可以四处传递,当我们对一些函数发出’调用’的消息时,这些函数会返回不同的执行结果,这就是多态性的一种表现.利用高阶函数来实现设计模式.
1.3 封装
封装的目的就是将信息隐藏
1.3.1 封装数据
许多语言提供了static,public,private来实现封装某些属性,但是JavaScript中没有这些关键字,所以只能通过函数创建作用域来封装数据
var myObject=(function(){
var _name='sven';//私有变量
return{
getName:function(){
return _name;
}
}
})();
console.log(myObject.getName());//sven
console.log(myObject._name);//undefined
1.3.2 封装实现
封装使得对象之间的耦合性变得松散,对象之间只通过暴露的API接口来通信,当我们修改一个对象时就可以随便修改它的内部实现,只要对外的接口没有变化,就不会影响到程序的其他功能.比如each函数.
1.3.3 封装类型
JavaScript在这方面是解脱~解脱~脱!
1.3.4 封装变化
通过封装变化,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也比较方便.
1.4 原型模式和基于原型继承的JavaScript对象系统
在原型编程思想中,类不是必须要的,对象未必需要从类中创建而来.原型模式不单是一种设计模式,也称为一种编程泛型
1.4.1 使用克隆的原型模式
从设计模式的角度讲,原型模式是用于创建对象的一种模式.比如我们在编写一个飞机大战游戏,飞机拥有分身技能,在原型模式中我们只需要克隆一个飞机当前的信息来创建出一个新的飞机.原型模式的实现关键在于语言是否提供了clone功能,在ECMAScript5中提供了Object.create
var Plane=function(){
this.blood=100;
this.attackLevel=1;
this.defenseLevel=1;
}
var Plane=new Plane();
Plane.blood=500;
Plane.attackLevel=10;
Plane.defenseLevel=7;
var clonePlane=Object.create(Plane);
console.log(clonePlane);// object{blood:500,....}
不支持Object.create方法中可以用
Object.create=Object.create||function(obj){
var F=function(){}
F.prototype=obj
return new F();
}
1.4.2 克隆是创建对象的手段
原型模式的真正目的并非在于需要得到一个一模一样的对象,克隆只是创建这个对象的过程和手段
——中间略了IO语言那块 直接写总结
原型模式编程泛型
- 所有的数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
- 对象会记住它的原型
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
1.4.5 JavaScript的原型继承
1.所有的数据都是对象JavaScript中有以下几种类型 number,boolean,string,object,function.除了undefined以外一切都为对象.按照这么说,JavaScript也有一个根对象.那么就是Object,所有的类型都是通过Object.prototype所克隆而来的
var obj1=new Object();
var obj2={};
console.log(Object.getProwotypeOf(obj1)===Object.prototype)//true
console.log(Object.getProwotypeOf(obj2)===Object.prototype)//true
2.要得到一个对象,不是通过实例化类,而是找到一个对象并且作为原型克隆它JavaScript的克隆有var obj1=new Object()或者var obj2={}利用new运算符来从构造器中得到一个对象
function Person(name){
this.name=name;
};
Person.prototype.getName=function(){
return this.name
}
var a=new Person('sven');
console.log(a.name)//sven
console.log(a.getName())//sven
console.log(Object.getProwotypeOf(a)===Person.prototype)//true
实际上当new运算符来调用函数时,此时的函数就是一个构造器,用new运算符来创建对象的过程,实际上也只是先克隆Object.prototype对象,再进行一些其他额外的操作罢了.利用属性来理解
function Person(name){
this.name=name;
}
Person.prototype.getName=function(){
return this.name;
}
var objectFactory=function(){
var obj=new Object(),//从Object.prototype上克隆一个新的对象
Constructor=[].shift.call(arguments);//取得外部传入的构造器,此例是Person
obj.__proto__=Constructor.prototype;//指向正确的原型
var ret=Constructor.apply(obj,arguments);//借用外部传入的构造器给obj设置属性
return typeof ret==='object'?ret:obj;//确保构造器总是会返回一个对象
}
var a=objectFactory(Person,'sven');
console.log(a.name);//sven
console.log(a.getName());//sven
console.log(Object.getProwotypeOf(a)===Person.prototype);//true
var a=objectFactory(Person,’sven’)等同于var a=new Person(‘sven’)
3 对象会记住它的原型JavaScript不能说对象有原型,而是对象的构造器有原型.在JavaScript给对象提供了一个隐藏属性,某个对象的属性会默认指向它构造器的原型对象,即{Constructor}.prototype
var a=new Object();
console.log(a.__proto__===Object.prototype);//true
实际上,就是对象跟’对象构造器的原型‘联系起来的纽带,正是因为对象要通过属性来记住它的构造器原型,所以上一节的ObjectFactory函数需要手动设置的指向.obj.=Constructor.prototype这样就让obj,指向构造器的prototype而不是Object.prototype
4 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型虽然JavaScript的对象最初都是由Object.prototype克隆而来的,但对象的构造器的原型并不仅仅限于Object.prototype上,可以动态指向其他对象.这样一来可以有选择性的把对象a的构造器的原型指向对象b,从而来达到继承的效果
var obj={name:'sven'};
var A=function(){}
A.prototype=obj;
var a=new A();
console.log(a.name);
引擎在解析这段代码的时候做了以下的事情
- 首先,尝试遍历对象a中的所有属性,但没有找到name这个属性.
- 查找name属性的这个请求被委托给对象a的构造器原型,它被a.proto记录着并且指向A.prototype,而A.prototype被设置为对象obj.
- 在对象obj中找到了name属性,并返回它的值
比如继承类
//A类
var A=function(){}
A.prototype={name:'sven'}
//B类继承A类
var B=function(){}
B.prototype=new A();
var b=new B()
console.log(b.name);//sven
引擎在解析这段代码的时候做了以下的事情
- 首先,尝试遍历对象b中的所有属性,但没有找到name这个属性.
- 查找name属性的请求被委托给对象b的构造器原型,它被b.proto记录着并且指向B.prototype,而B.prototype被设置为通过new A()创建出来的对象.
- 在该对象中依然没有找到name属性,于是请求被继续委托给对象构造器的原型A.prototype
- 在A.prototype找到了name属性,并且返回它的值
A.prototype的构造器原型为Object.prototype,Object.prototype的构造器原型为null 所以会在这里打住
1.4.6 原型继承的未来
期待ES6吧!
总结
第一章讲述了多态在设计模式的重要性,同时讲述了封装的概念与JavaScript中的原型模式,这样都是有助于我们更好的去了解JavaScript如何实现设计模式.