设计模式的学习作为初步入门阶段,主要的学习过程是参考张容铭老师的《javascript设计模式》来练习的,书中对各个设计模式做了其应用设计原则,在其基础上多做了解与实践,基本可以使我们从一个面向过程的开发模式向面向对象模式改变,从而能应对更加大型的项目开发。
-javascript设计模式学习(一)
-javascript设计模式学习(二)
-javascript设计模式学习(三)
javascript design pattern learning
设计模式的学习作为初步入门阶段,主要的学习过程是参考张容铭老师的《javascript设计模式》来练习的,书中对各个设计模式做了其应用设计原则,在其基础上多做了解与实践,基本可以使我们从一个面向过程的开发模式向面向对象模式改变,从而能应对更加大型的项目开发。
-javascript设计模式学习(一)
-javascript设计模式学习(二)
-javascript设计模式学习(三)
用于创建同一类对象
//简单工厂模式
function create(name, age){
//创建一个对象,并对对象拓展属性和方法
var o = new Object();
o.name = name;
o.age = age;
o.getName = function(){
//get the name
};
//将对象返回
return o;
}
//安全工厂方法模式
var Factory = function(type, content){
if(this instanceof Factory){
var s = new this[type](content);
return s;
}else{
return new Factory(type, content);
}
}
//工厂原型创建所有类型数据对象的基类
Factory.prototype = {
type1: function(content){
//do some thing
},
type2: function(content){
//do some thing
}
}
通过对类的工厂抽象使其业务用于对产品类簇的创建而不负责创建某一类产品的实例。抽象类是一种声明但不能使用的类,因为定义了一种类,并定义了该类所具备的方法,如果在子类没有重写这些方法,那么当调用时能找到这些方法便会报错
//抽象工厂方法
var VehicleFactory = function(subType, superType){
//判断抽象工厂中是否有该抽象类
if(typeof VehicleFactory[superType] === 'function'){
//缓存类
function F(){};
//继承父类属性和方法
F.prototype = new VehicleFactory[superType]();
//将子类constructor指向子类
subType.constructor = subType;
//子类原型继承"父类"
subType.prototype = new F();
}else{
//不存在该抽象类抛出错误
throw new Error('未创建该抽象类')
}
}
将一个复杂对象的构建层与其表示层相互分离。
工厂模式主要是为了创建对象实例或者类簇(抽象工厂),关心的是最终产出(创建)的是什么。建造者模式主要是创建这个对象的整个过程,甚至于创建对象的每一个细节。总的话,这种模式创建的是一个复合对象。
详细见chap06
原型模式是将原型对象指向创建对象的类,使这些类共享原型对象的方法与属性。
原型对象作为基类是要被子类继承的,作为一些共有方法,可以提供一套共享机制,这样每当创建基类时,对于每次创建的一些简单而又差异化的属性可以放在构造函数中,而一些消耗资源比较大的方法放在基类的原型中。
同时原型作为一个共享对象,不论是父类的实例对象或者是子类的继承,都是对它的一个指向引用,所以原型对象才会被共享,那么对原型对象的拓展,不管是子类或者父类的实例对象都会继承下来。
又称单体模式,是指允许实例化一次的对象类,这有利于在变量空间中防止冲突产生,同时做到模块分明的方式,提高代码阅读效率。
其中单例模式最主要的是对于静态变量的管理。
将变量放在一个函数内部,那必须通过特权方法访问,如果不提供赋值变量的方法,只提供获取变量的方法,就可以做到限制变量的修改并且可以供外界访问。
var Conf = (function(){
//私有变量
var conf = {
MAX_NUM: 100,
MIN_NUM: 1,
COUNT: 1000
}
//返回取值器对象
return {
//取值器方法
get: function(name){
return conf[name] ? conf[name] : null;
}
}
})()
有时单例对象需要延迟创建,所以在单例中还存在一种延迟创建的形式,也成"惰性创建"(详细见chap08)
为一组复杂的子系统接口提供一个更高级的统一接口。外观与基础的面向对象编程基础中用一个对象来包含属性方法类似,但其最主要的还是在同一功能的方法上套上一个高级的接口,同时在同个元素上不会因为相同的方法造成重复定义,方法被覆盖等问题。例如在兼容性程度上,不同的浏览器所能实现的javascript方法不同。
function addEvent(dom, type, fn){
if(dom.addEventListener){
dom.addEventListener(type, fn, false);
}else if(dom.attachEvent){
dom.attachEvent('on' + type, fn);
}else{
dom['on' + type] = fn;
}
}
var input = document.getElementById('input');
addEvent(input, 'click', function(){
console.log('第一个事件');
})
addEvent(input, 'click', function(){
console.log('第二个事件');
})
使用外观模式也可以封装多个功能,实现一个小型代码库
将一个类(对象)的接口(方法或属性)转化成另一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决,即为两个代码库所写的代码兼容运行而书写的额外代码。
对于相似的框架引用来说,可以在全局对象上进行兼容,以JQuery为例,
//所写框架与JQuery类似
window.A = A = JQuery;
如果两者书写方式不同,在一定的基础上需要进行框架替换,以JQuery选取元素为例,
A.g = function(id){
return $(id).get(0);
}
A.on = function(id, type, fn){
var dom = typeof id === 'string' ? $('#'+id) : $(id);
dom.on(type, fn);
}
适配器模式也可应用于函数参数适配,使函数参数传递不在需要按参数顺序来传递。在数据适配及服务器端数据适配上也同样可以应用。
当一个对象不能直接引用另一个对象时,可以通过代理对象在两者之间起到中介的作用。在浏览其他人的文章过程中,代理模式用于保障当前对象的单一职责(相对独立性),而需要创建另一个对象来处理调用当前对象之前的一些逻辑以提高代码的效率、状态判断等。常用的是虚拟代理和缓存代理。
在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户更复杂的需求。
//以input点击为例
var decorator = function(input, fn){
var input = document.getElementById(input);
if(typeof input.onclick === 'function'){
var oldClickFn = input.onclick;
input.onclick = function(){
oldClickFn();
fn();
}
}else{
input.onclick = fn;
}
}
对比适配器模式来说,适配器方法是对原有对象适配,添加的方法与原有方法功能上大致相似,但装饰者模式提供的方法与原来的方法功能项是有所区别的。再者,使用适配器模式时新增的方法是要调用原有的方法,而装饰者模式不需要了解原有的功能。
系统沿多个维度变化的同时,利用桥接模式在对其不增加复杂度的情况下达到解耦。对于所创建的桥接方法,在使用上可以通过匿名函数中this的上下文关系来解耦。即将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化。
组合模式又称部分-整体模式,用于将对象组合成树形结构以表示“部分整体”的层次结构。
对于该模式应用,最主要的就是在于对接口的统一,这里可以通过集成同一个虚拟类来实现
详见chap14
运用共享技术有效地支持大量的细粒度对象,避免对象间拥有相同内容造成多余开销。
享元模式主要是对数据、方法共享分离,它将数据和方法分成内部数据、内部方法和外部数据、外部方法。内部方法与内部数据指的是相似或者共有的数据和方法,将这一部分提取出来减少开销,以提高性能。
在目前的前端开发模式过程中,一直以来都以面向过程为主,没有仔细考虑到过程中代码的性能优化,这对产品迭代及后续的javascript开发带来一定影响,毕竟完全以面向过程为主,按照产品需求去实现页面交互不能更高效提高自己的javascript编程水平。
采用对象收编变量来防止团队开发过程中的变量冲突,避免定义的相同方法覆盖掉原有功能。
在收编的过程中,可以在对象中返回(return)属性或者方法,实现属性与方法的复制。
var MethodsObject = function(){
return {
method: function(){
//do some thing
}
//...
}
}
//实现方法复制
var a = MethodsObject();
a.method();
这种模式在开发过程中也可以使用类来实现方法复制。通过this的指向创建(new)新的对象。
var MethodsObject = function(){
this.method = function(){}
//...
}
var a = new MethodsObject();
a.method();
使用类复制对象方法与属性的过程中,在每次创建(new)的过程中,对一些共有的方法会造成比较高的消耗,所以我们也将共有方法放置在对象原型(prototype)中。
var MethodsObject = function(){}
//type1
MethodsObject.prototype = {
method: function(){}
//...
}
//type2
MethodsObject.prototype.method = function(){}
在每个方法实现的过程中可以返回对象本身实现方法链式引用
var MethodsObject = function(){
method1: function(){
return this;
},
method2: function(){
return this;
}
}
MethodsObject.method1().method2();
在原生对象Function中添加方法(在团队开发过程中不采用此方式,防止函数污染)
Function.prototype.addMethod = function(name, fn){
this[name] = fn;
return this;
}
面向对象变成就是将需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法)。也称之为类。
其中通过this定义的属性或者方法是该对象自身拥有的,所以每次通过类创建一个新对象时,this指向的属性与方法会得到相应的创建,而通过prototype继承的属性与方法是每个对象通过prototype访问到,这些属性与方法并不随着新对象创建而再次创建。
在每个prototype对象中会生成一个constructor属性指向拥有整个原型对象的函数或对象。
继承
继承的基本原理在于声明父类与子类,同时将父类的实例赋值给子类的原型
类的原型对象的作用就是为类的原型添加共有方法,但类不能直接访问这些属性和方法,需要通过prototype来访问。通过实例化父类,子类复制了父类的属性与方法,并将原型__proto__指向了父类的原型,这样在可以访问父类构造函数中的属性和方法外,同时拥有了父类原型上的属性与方法。
但是这样的继承会共用父类的"引用"类型,间接影响了其他继承该父类的其他子类数据,而且因为子类实现继承是靠原型prototype对父类的实例化实现的,所以在创建父类时,无法向父类传递参数,也就无法在实例化父类的时候对父类构造函数的属性进行初始化。
针对此除了这种模式的继承外,可以在子类中通过.call()方法实现继承
//声明子类
function SubClass(arg){
//继承父类
SuperClass.call(this, arg)
}
.call()可以更改函数的作用环境,子类调用此方法将子类中的变量在父类中执行
因此可以结合两者的优点进行组合继承,在子类构造器中执行父类构造函数(call),在子类原型上实例化父类
//原型式继承
function inheritObject(o){
//声明一个过渡函数对象
function F(){}
//过渡对象的原型继承父对象
F.prototype = o;
//返回过渡对象的一个实例
return new F();
}
原型式集成跟类式继承一样,将父类构造函数的属性与方法复制,同时"引用"类型的属性被共用
//寄生式继承
function create(obj){
//通过原型继承方式创建新对象
var o = new inheritObject(obj);
//扩展新对象
o.method = function(){
//do some thing
}
//返回扩展后新对象
return o;
}
在原型式继承的基础上进行二次封装,并且对继承的对象进行扩展
//寄生式组合继承
function inheritPrototype(subClass, superClass){
//复制一份父类的原型副本保存在变量中
var p = inheritObject(superClass.prototype);
//修正因为重写子类导致子类的constructor属性被修改
p.constructor = subClass;
//设置子类的原型
subClass.prototype = p;
}
//使用组合继承会造成子类不是父类的实例,而子类的原型是父类的实例
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.