GOF在《设计模式》中说到:面向接口编程,而非面向实现编程javascript
鉴于此,这个概念可见一斑!java
JS却不像其余面向对象的高级语言(C#,Java,C++等)拥有内建的接口机制,以肯定一组对象和另外一组对象包含类似的的特性。所幸的是JS拥有强大的灵活性,这使得模仿接口特性又变得很是简单。那么究竟是接口呢?编程
接口概念:设计模式
接口提供了一种用以说明一个对象应该具备那些方法的手段闭包
接口,为一些具备类似行为的类之间(可能为同一种类型,也可能为不一样类型)提供统一的方法定义,使这些类之间可以很好的实现通讯app
使用接口的优势:框架
一个需求,须要多个部门协调合做的时候,接口的概念就特别重要了,每一个部门能够循序渐进的作本身的事情,涉及到交互的时候,提供接口处理便可函数
就好像主板上的内存条,CPU,硬盘,主板提供各类接口,其余设备直接按相应的接口插入便可this
javascript语言要实现接口的概念仍是有局限性的问题的spa
javascript中模仿接口
经常使用的几种手法:
接口自己就是一个抽象的概念,至于如何实现各有不一样
这是个人一段项目代码,接口是经过模块闭包实现的,这样的好处更能体现出封装性
//引入编译模块 define('ProcessMgr', [ 'Compile' ], function(compile) { var flipContentProcessed, flipWidgetProcessed, disposeWidgetBind, objectKeys, converWidgetWapper, ActionMgr, getHotspot, //内部消息传递接口 // // 1 构建节点树 // A 构建纯DOM节点 // B 构建content对象, 这是第二种加载模式,动态预加载 // 2 绑定节点事件 // 3 手动触发事件 // 4 自动触发事件 // 5 事件委托处理 // 6 翻页动做 // 7 翻页完成动做 // 8 复位动做 // 9 销毁动做 // 10 移除整个页面结构 //ProcessMgr =
{ 'preCompile' : preCompile, 'executeCompile' : executeCompile, 'assignTrigger' : assignTrigger, 'assignAutoRun' : assignAutoRun, 'assignSuspend' : assignSuspend, 'assignDispose' : assignDispose, 'recovery' : recovery, 'destroy' : destroy, 'removePage' : removePage }, ...........具体处理的方法................... return ProcessMgr; //返回对外接口 })
jQuery的接口更加的直接,直接挂在在对应的原型链上或是静态链
封装
为何要封装?
由于咱们不关心它是如何实现的,咱们只关心如何使用
手机,电脑,咱们接触的形形色色的东西,其实咱们一直都只是在使用它
一样的道理,咱们放到程序设计中也是如此,封装实现的细节 ,下降对象之间的耦合程序,保持数据的完整性与修改的约束
因此说一个设计良好的API可让开发者赏心悦目
javascript实现封装的手段
对于JS语法规则,咱们要紧紧抓住3点
根据这3个规则咱们就能够干不少别的语言干不了的事了
咱们来模拟一个完整的封装
私有属性和方法
var encapsulation = function(){ //私有属性 var name = 'aaron' //私有方法 var getName = function(){ alert(name) } } alert(name) //空
函数做用域实现变量与方法私有化,外边天然没法方法,固然这种彻底没有实际意义了,咱们要配合后面的处理
特权属性和方法
简单的说就可以让实例直接有权力访问内部的属性与方法,因此在设计上要修改下实现手法,
var encapsulation = function(){ //特权 this.name = 'aaron' this.getName = function(){ alert(name) } } alert(new encapsulation().name) //aaron
弊端也显而易见,每次new一个新的对象,特权属性与方法都会被从新拷贝一份,也就是须要单独占据一块堆内存
共有属性和方法
*注意了,命名函数与函数表达式在本质上虽然没什么区别,可是在处理上仍是有很大不一样
函数表达式
var encapsulation = function(){ //静态属性方法 encapsulation.name = 'aaron' encapsulation.getName = function(){ alert(name) } } console.log( encapsulation.name ) // 无
命名函数
即使方法不执行,静态属性也能访问到,就证实了JS有一种预解析的机制,也就是常说的函数提高了
function encapsulation(){ //静态属性方法 encapsulation.name = 'aaron' encapsulation.getName = function(){ alert(name) } } console.log( encapsulation.name ) // aaron
共有静态属性和方法
这是最最经常使用的,JS 的prototype原型特性,能够共享全部属性与方法,固然,属性是引用类型的时候就会存在问题了
var encapsulation = function(){ //共享属性方法 encapsulation.prototype.name = 'aaron' encapsulation.prototype.getName = function(){ alert(name) } } console.log(new encapsulation().name ) // aaron console.log(new encapsulation().name ) // aaron
继承
大型设计中,代码确定是不少的,因此咱们须要减小重复性的代码,尽量的弱化对象之间的耦合:
经过几种手段
固然并不是全部的重复代码均可以使用一种继承方法,因此咱们通常要区分共性是否一致
我项目中使用的继承手段
呵呵,眼熟吧,借鉴EXT的处理机制,有继承与混入,本身改了点,毕竟那种分层结构,倒序调用,还有点不合适套用,下文会介绍实现
类式继承
如今的框架,库大多继承的手段都是类式继承,并且继承的处理方式也基本一致,可是里面的细节问题可能你们没注意到,咱们一块儿分析下
咱们看看几个框架的继承实现
mootools
Class
Backbone
extend
ext
extend
ext比较特殊,由于是经过注入的手法,实现继承了类名转化继承混入静态扩充
对比几个框架,咱们红线部分的相同之处没?实现原理确是同样的,包括EXT也同样,只是在具体实现上增长了各自的方案,
设计的原理
抛开复杂的框架,咱们看看设计的底层原理是什么,又会有什么问题?
原型链做为JS实现继承的主要方法,其根本的思路利用原型链让一个引用类型,继承另外一个引用类型
原型与实例的关系:
构造器有一个原型对象,原型对象有一个指针指向构造器,那么实例则是包含一个指向原型的指针
因此实例只与原型有关系
实现中的细节:
function SuperType(){ //父类 this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ //子类 this.subproperty = false; } //继承了 SuperType SubType.prototype = new SuperType(); //实现原型继承,引用 SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
1 继承的本质是引用,那么N多组实例其实都是操做的同一个引用,那么问题来了,若是父类中有个一引用属性,那么一个子类操做修改了,全部的子类都会被影响
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //继承了 SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
2 在解决原型中包含引用类型值所带来问题的过程当中,开发人员开始使用一种叫作借用构造函数(constructor stealing)的技术(有时候也叫作伪造对象或经典继承)
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //继承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
经过使用 call()方法(或 apply()方法也能够),咱们其实是在(将来将要)新建立的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的全部对象初始化代码。结果,SubType 的每一个实例就都会具备本身的 colors 属性的副本了
借用构造函数的问题
若是仅仅是借用构造函数,那么也将没法避免构造函数模式存在的问题——方法都在构造函数中定义,所以函数复用就无从谈起了。并且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果全部类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是不多单独使用的
经过SubType.prototype = new SuperType(); 的方式实现引用的继承,看似很简单,可是里面还有几个问题
看看Backbone的如何处理
var ctor = function () { };
ctor.prototype = parent.prototype; child.prototype = new ctor(); child.prototype.constructor = child;
可见backbone引用一个中介ctor函数
其实就是一种代理继承
proxy来实现继承,这样就避免了在classical继承中出现的parent的constructor被使用两次带来的效率问题和在原型链中再次继承this的属性
function obj (o){ var f = {} f.prototype = o return new f(); }
最后还要修正一下constructor的指针,由于是继承ctor了
至于原型式继承能够参考高级程序设计3, 比较详细了