《JavaScript设计模式与开发实践》一篇文章带你读懂

《JavaScript设计模式与开发实践》是由曾探所著的一本经典技术书籍。

        该书详细介绍了JavaScript中常用的设计模式,并结合实际项目开发经验给出了实践示例和最佳实践建议。这本书首先介绍了设计模式的基本概念和作用,以及为什么要在JavaScript中使用设计模式。接着,书中详细介绍了23种常见的设计模式,如单例模式、工厂模式、适配器模式、观察者模式等。每种设计模式都详细解释了其定义、结构、应用场景和优缺点,并给出了实际的代码示例和案例分析。除了介绍设计模式的理论知识,该书还提供了大量的实践经验和开发技巧。其中包括如何组织和管理JavaScript代码、如何优化性能、如何进行模块化开发、如何进行异步编程等方面的内容。这些实践经验有助于读者更好地理解和应用设计模式,提高JavaScript项目的质量和可维护性。

        《JavaScript设计模式与开发实践》这本书语言简洁明了,通俗易懂,适合初学者入门和有一定经验的开发者进阶学习。通过学习这本书,读者可以深入理解JavaScript设计模式的原理和思想,并能够灵活运用到实际项目中,提高自己的编程水平和软件开发能力。

一、前言:

⭕前置问题:

  1. 什么是模式?
  2. 设计模式的适用性是什么?

📜从一个故事开始

  1. 足球运动包含很多的策略,有一个叫做“下底传中”——用一个名词来去描述一种“战术”,这就是一种“模式”。
  2. 模式:通过一个特有的名词,来描述一类问题对应的解决方案

✍🏻设计模式的适用性 

  1. 不要有了锤子,看什么都像钉子
  2. 模式只有放到具体的环境下才有意义


二、 基础知识

1.面向对象的 JavaScript

(1)程序语言设计风格

  • 命令式语言(过程化语言)
  • 结构化语言
  • 面向对象语言 (OOP)
  • 函数式语言
  • 脚本语言

Java 就是典型的面向对象编程语言。它具备以下三个特点:

  • 封装
  • 继承
  • 多态

(2)JavaScript 的语言特性

通过(脚本)原型来实现面向对象的开发(不是标准的面向对象的编程语言)
动态类型语言 

概念:变量类型由值确定

let t = 'str' // string 类型
t = 007 // number 类型

🌟鸭子类型:

“从前在JavaScript王国里,有一个国王,他觉得世界上最美妙的声音就是鸭子的叫声,于是国王召集大臣,要组建一个1000只鸭子组成的合唱团。大臣们找遍了全国,终于找到999只鸭子,但是始终还差一只,最后大臣发现有一只非常特别的鸡,它的叫声跟鸭子一模一样,于是这只鸡就成为了合唱团的最后一员。”

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('恭喜加入合唱团' );
        console.1og('合唱团已有成员数量:' + choir.length );
    }
};

joinChoir( duck );// 恭喜加入合唱团
joinChoir( chicken );// 恭喜加入合唱团

如果有一个动物,走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。

(3)JavaScript 的多态性

⭕动态类型语言天生具备多态性。

多态:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。

代码:

var makeSound =function( animal ){
    if ( animal instanceof Duck ){
        console.log('嘎嘎嘎');
    }else if ( animal instanceof Chicken ){
        console.log('咯咯咯');
    }
};
var Ducka = function(){};
var Chicken = function()[};

makeSound(new Duck() );  //嘎嘎嘎
makeSound(new Chicken() );//咯咯咯

多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来


2.this、call 和 apply

(1)this 指向问题

  • 构造函数:指向实例对象
  • 普通函数:指向函数调用方
  • 箭头函数:不修改 this 指向,沿用上层作用域的 this
  • call、apply、bind:仅作用于普通函数,this 指向第一个参数

 (2)this“丢失”

var obj = {
    myName: 'sven',
     getName: function(){
        return this.myName;
     }
};

console.log( obj.getName() ); // 输出: sven  
//getName是obj下面的方法,this 会指向obj 本身

var getName2 = obj.getName;
//vr声明了一个新的变量的时候 被绑定到windows下面的
console.log( getName2() );   //相对应 window.getName2
//输出:undefined

普通函数调用方式,this 是指向全局window 的


3.闭包和高阶函数

(1)闭包 

定义:能够访问其他函数作用域中变量的函数

变量的生存周期:var 声明的变量在非函数作用域下是全局变量

<html>
<body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <script>
        var nodes = document.getElementsByTagName( 'div' );
        for( var i = 0,len = nodes.length; i < len; i++ ){
            nodes[ i ].onclick = function(){
                 alert ( i );//永远输出5
            }
        };
    </script>
</body>
</html>

闭包解决生存周期问题:

for( var i = 0,len = nodes.length; i < len; i++ ){
    (function( i ){
        nodes[ i ].onclick = function(){
            console.log(i);
        }
    })( i )
};

⭕注意:ES6之后 let 也可以解决这个问题 (块级作用域)

(2)高阶函数 

满足两个条件:

  • 函数可以作为参数被传递
  • 函数可以作为返回值输出 


三、设计模式

1.单例模式

(1)定义:

保证一个类仅有一个实例

(2)基于场景的单例模式:

 1️⃣场景:

 2️⃣常见代码:

<html>
    <body>
        <button id="loginBtn">登录</button>
    </body>
<script>
    var createLoginLayer = function(){
        var div = document.createElement( 'div' );
        div.innerHTML = '我是登录浮窗';
        div.style.display = 'none';
        document.body.appendChild( div );
        return div:
    };

    document.getElementById( 'loginBtn' ).onclick = function(){
        var loginLayer = createLoginLayer();
        loginLayer.style.display = 'block';
    };
</script>
</html>

 ❌多次点击会生成多个登录框

 3️⃣单例模式:

// 单例生成器
var getSingle = function (fn) {
    var result;
    // 闭包
    return function () {
    // 逻辑中断,利用 apply 改变 fn 下的 this 指向
        return result || (result = fn.apply(this, arguments));
    }
};
// 处理登录窗口的业务逻辑
var createLoginLayer = function () {
    var div = document.createElement('div');
    div.innerHTML ='我是登录浮窗';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
};
// 利用单例生成器,得到一个闭包函数,触发该闭包函数可以得到 div
var createSingleLoginLayer = getSingle(createLoginLayer);
// 监听按钮点击行为
document.getElementById( 'loginBtn').onclick = function () (
    // 得到 div 实例 (单例的)
    var loginLayer = createSingleLoginLayer();
    // 展示
    loginLayer.style.display = 'block';
};

2.策略模式

(1)定义:

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

(2)基于场景的策略模式:

1️⃣场景:奖金计算

  • 绩效为 S 的人年终奖有4 倍工资
  • 绩效为 A 的人年终奖有了倍工资
  • 而绩效为 B 的人年终奖是 2 倍工资

2️⃣常见代码:

	var calculateBonus = function( performanceLevel, salary ){
		if ( performanceLevel === 'S' ){
			return salary * 4;
		}
		if ( performanceLevel === 'A' ){
			return salary * 3;
		}
		if ( performanceLevel === 'B' ){
			return salary * 2;
		}
	};

	calculateBonus( 'B', 20000 ); // 输出:40000
	calculateBonus( 'S', 6000 ); // 输出:24000

3️⃣策略模式:

	//策略对象
	var strategies = {
		"S": function( salary ){
			return salary * 4;
		},
		"A": function( salary ){
			return salary * 3;
		},
		"B": function( salary ){
			return salary * 2;
		}
	};
	//计算方法
	var calculateBonus = function( level, salary ){
		return strategies[ level ]( salary );
	};
	//执行
	console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
	console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

3. 代理模式

(1)定义:

为其他对象提供一种代理以控制对这个对象的访问

(2)基于场景的代理模式:

场景:图片预加载 —— 创建img 标签,在图片加载之前给 img 一个占位图

常见代码:

var MyImage = (function(){
    var imgNode = document.createElement('img');
    document.body.appendchild( imgNode );
    var img = new Image;

    img.onload = function(){
        imgNode.src = img.src;
    };

    return{
        setSrc: function( src ){
          imgNode.src =''
          imq.src = src;
        }
    }
})();

MyImage.setSrc('');

代理模式:

// 自执行函数,创建 img 标签。
// myImage 为返回的闭包函数,可以用来设置 src
var myImage = (function () {
    var imgNode = document.createElement( ' img' );
    document.body.appendChild( imgNode);
    return function (src) {
        imgNode.src = src;
    }
})();

// 代理,自执行函数,创建 Image 图片加载实例
// proxyImage 为闭包函数,可以为 img 标签设置 src 和进行图片预加载
var proxyImage = (function () {
    var img = new Image;
    img.onload = function () {
        myImage(this.src);
    }
    return function (src) {
        myImage( ' ' ) ;
        img.src = src;
    }
})();

proxyImage('');

单一职责原则: 就一个类 (通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。 

4.迭代器模式

(1)定义:

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

(2)基于场景的迭代器模式: 

场景:根据数据创建 div,并添加到 body 中
常见代码:

var appendDiv = function( data ){
    for ( var i = 0,l = data.length; i < l; i++ ){
        var div = document.createElement('div')_;
        div.innerHTMI = data[ i ];
        document.body.appendchild( div );
    }
};

appendDiv( [ 1,2,3,4,5,6 ] );

迭代器模式:

var each = function( obj,callback ){
    var value,
    i=0.length = obj.length,
    isArray = isArraylike( obj );// isArraylike函数未实现,可以翻阅jQuery源代码
    if(isArray ){ // 迭代类数组 
                for (; i < length; i++ ) {
                    callback.call( obj[ i ], i, obj[ i ] );
                } 
    }else (
        for(i in obj ){ // 迭代object对象 
            value = callback.call( obj[ i ],i,obj[ i ] );
        }
    }
        return obj;
};

var appendDiv = function( data ){
    each( data, function( i,n ){
        var div = document.createElement( 'div' );
        div.innerHTML = n;
        document.body.appendchild( div );
    });
};
appendDiv([ 1,2,3,4,5,6] );
appendDiv({a:1,b:2,c:3,d:4});

JS中已经内置了迭代器:深入理解现代JavaScript


5.发布-订阅模式

(1)定义:

发布-订阅模式(观察者模式):对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

(2)基于场景的观察者模式:

1️⃣场景:一个网站页面,包含:header、nav、消息列表、购物车等模块

2️⃣对比

①常见代码:

login.succ(function(data){
    header.setAvatar( data.avatar); //设置header模块的头像
    nav.setAvatar( data.avatar ); //设置导航模块的头像
    message.refresh(); //刷新消息列表
    cart.refresh); //刷新购物车列表
});

②观察者模式:

发布消息: 

$.ajax( 'http:// xxx.com?login',function(data){   //登录成功
    login.trigger('loginSucc', data);   // 发布登录成功的消息
});

接收消息:

var header = (function(){// header模块
    login.listen('loginSucc', function( data)(
        header.setAvatar( data.avatar );
    });
    return {
        setAvatar: funtion( data ){
            console.log( '设置header模块的头像' );
        }
    }
})();

var nav = (funtion(){  // nav模块
    login.listen( 'loginSucc', funtion( data ){
        nav.setAvatar( data.avatar );
    });
    return {
        setAvatar: funtion( avatar ){
            console.log( '设置nav模块的头像' );
    }
    
    

6.命令模式 

7.组合模式

8.模板方法模式

(1)定义:

类似于继承。公用的部分统一封装(模板),不同的部分单独处理。

(2)Coffee or Tea:

1️⃣咖啡:

  • 把水煮沸
  • 用沸水冲泡咖啡
  • 把咖啡倒进杯子

2️⃣茶:

(1) 把水煮沸
(2) 用沸水浸泡茶叶
(3) 把茶水倒进杯子

 

9.享元模式

10.职责链模式

11.中介者模式

(1)定义:

定义一个对象(中介者),该对象封装了系统中对象间的交互方式。 

(2)基于场景的中介者模式:

1️⃣场景:SKU

 

 

⭕代码的实现需要分别监听颜色以及数量的变化。

2️⃣常见代码实现: 

①颜色切换

<script>
var colorSelect = document.getElementById(' colorSelect'),
numberInput = document.getElementById( 'numberInput'),
colorInfo = document.getElementById('colorInfon'),
umberInfo = document.getElementByid(' numberInfo'),
nextBtn = document.getElementById('nextBtn');

var goods = {// 手机库存
    "red":3
    "blue":6
};

colorSelect.onchange = function(){
    var color = this.value, //颜色
    number = numberInput.value,
    stock = goods[ color ]; // 该颜色手机对应的当前库存

    colorInfo.innerHTMI = color;

    if ( !color ){
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择手机颜色';
        return;
    }

    if ((( number - 0) | 0)!== number - 0){ // 用户输入的购买数量是否为正整数
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请输入正确的购买数量';
        return;
    }

    if( number > stock){ // 当前选择数量超过库存量
        nextBtn.disabled = true;
        nextBtn.innerHTML ='库存不足';
        return ;
    }
    nextBtn.disabled = false;
    nextBtn.innerHTML = '放入购物车';
</script>

 ②数量切换

numberInput.oninput = function(){
    var color = colorSelect.value, // 颜色
    number = this.value, //数量
    stock = goods[ color ]; // 该颜色手机对应的当前库存

    numberInfo.innerHTML = number;

    if ( !color ){
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请选择手机颜色';
        return;
    }


    if ((( number - 0) | 0)!== number - 0){ // 输入购买数量是否为正整数
        nextBtn.disabled = true;
        nextBtn.innerHTML = '请输入正确的购买数量';
        return;
    }

    if (number > stock ){//当前选择数量没有超过库存量
        nextBtn.disabled = true;
        nextBtn.innerHTML = '库存不足';
        return ;
    }
    nextBtn.disabled = false;
    nextBtn.innerHTML = '放入购物车 ;
};

3️⃣中介者模式:

var goods = {
    "red | 32G":3,
    "red  | 16G":0,
    "blue |32G":1,
    "blue | 16G":6
};
var mediator = (function(){
    var colorSelect = document.getElementById( 'colorSelect'),
    var memorySelect = document.getElementById('memorySelect'),
    numberInput = document.getElementById('numberInput'),
    colorInfo = document.getElementById('colorInfo'),
    memoryInfo = document.getElementById('memoryInfo'),
    numberInfo = document.getElementById('numberInfo'),
    nextBtn = document.getElementById('nextBtn');

    return{
        changed: function( obj ){
            var color = colorSelect.value, //颜色
            memory = memorySelect.value,// 内存
            number = numberInput.value,//数量
            stock = goods[ color + '|'+ memory ]; //颜色和内存对应的手机库存数量


        if ( obj === colorSelect ){  //如果改变的是选择颜色下拉框
            colorInfo.innerHTMI = color;
        }else if ( obj === memorySelect ){
            memoryInfo.innerHTML = memory;
        }else if ( obj === numberInput ){
            numberInfo.innerHTML = number;
        }

        
        if ( !color){
            nextBtn.disabled = true;
            nextBtn.innerHTML = '请选择手机颜色';
            return;
        }
        
        
        if(!memory){
            nextBtn.disabled = true;
            nextBtn.innerHTML = '请选择内存大小';
            return;
        }

    
        if(((number-0) |0 )!== number - 0){ // 输入购买数量是否为正整数
            nextBtn.disabled = true;
            nextBtn.innerHTML = '请输入正确的购买数量';
            return;
        }

        nextBtn.disabled = false;
        nextBtn.innerHTM = '放入购物车';
     }
    }
})();
        
//事件函数:
colorSelect.onchange = function(){
    mediator.changed( this );
};
tememorySelect.onchange = function(){
        mediator.changed( this );
}
numberInput.oninput = function(){
    mediator.changed(this );
};

12.装饰者模式

(1)定义:

 让类或者对象一开始只具有一些基础的职责,更多的职责在代码运行时被动态装饰到对象上面

(2)基于场景的装饰者模式:

  • 场景
  • 常见代码
  • 装饰者模式

13.状态模式

14.适配器模式


四、设计原则和编程技巧 

1.单一职责原则

(1)定义:

一个对象(方法)应该只做一件事情 

📜案例:小明买酱油

1️⃣旧实现: 

function shoping() {
    console.log('小明用钥匙打开门')
    console.log('小明去超市')
    console.log('小明买酱油')
    console.log('小明回家')
}

❌修改现有函数的实现则意味着需要重新测试整个的流程

 2️⃣单一职责原则:

function shoping() {
    open()
    goSupermarket()
    buy()
    goHome()
}

function open() {
    console.Log('小明用钥匙打开门')
}

function goSupermarket(){
    console.log('小明去超市')
}


function buy() {
    console.log('小明买酱油')
}


function goHome(){
    console.log('小明回家')
}

 修正如下:

function shoping(){
+ openPasswordDoor()
···
}

function open() {
    console.log('小明用钥匙打开门')
}
+function openPasswordDoor({
+ console.log('小明用钥匙打开门')
+}

(2)应用:

  • 单例模式
  • 代理模式
  • 迭代器模式
  • 装饰者模式 

(3)实际应用:违反原则并不奇怪 


 2.最少知识原则 (迪米特法则)

 (1)定义:

一个对象应当尽可能少地与其他对象发生相互作用

(2)应用:

  • 中介者模式
  • 外观模式

3.开放封闭原则

(1)定义:

 软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。

(2)应用:

1️⃣用多态来消除条件分支

初始代码:

不符合开发多态原则 

多态写法:
2️⃣发布-订阅模式


3️⃣模板方法模式
4️⃣策略模式
5️⃣代理模式
6️⃣职责链模式

 


4.接口和面向接口编程

5.代码重构原则 

(1)概念:

无论你进行了再多的努力,重构都是一个会在未来发生的事情,只不过是一个早晚的问题。

(2)尽量拖慢重构发生的时间的方法:

① 提炼函数:

	var getUserInfo = function(){
		ajax( 'http:// xxx.com/userInfo', function( data ){
			console.log( 'userId: ' + data.userId );
			console.log( 'userName: ' + data.userName );
			console.log( 'nickName: ' + data.nickName );
		});
	};

 改为:

var getUserInfo = function(){
	ajax( 'http:// xxx.com/userInfo', function( data ){
		printDetails( data );
	});
};

var printDetails = function( data ){
	console.log( 'userId: ' + data.userId );
	console.log( 'userName: ' + data.userName );
	console.log( 'nickName: ' + data.nickName );
};

② 合并重复的代码片段

	var paging = function( currPage ){
		if ( currPage <= 0 ){
			currPage = 0;
			jump( currPage ); // 跳转
		}else if ( currPage >= totalPage ){
			currPage = totalPage;
			jump( currPage ); // 跳转
		}else{
			jump( currPage ); // 跳转
		}
	};

改为:

	var paging = function( currPage ){
		if ( currPage <= 0 ){
			currPage = 0;
		}else if ( currPage >= totalPage ){
			currPage = totalPage;
		}
		jump( currPage ); // 把jump 函数独立出来
	};

③ 把条件分支语句提炼成函数

	var getPrice = function( price ){
		var date = new Date();
		if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){ // 夏天
			return price * 0.8;
		}
		return price;
	};

 改为:

var isSummer = function(){
		var date = new Date();
		return date.getMonth() >= 6 && date.getMonth() <= 9;
	};

	var getPrice = function( price ){
		if ( isSummer() ){ // 夏天
			return price * 0.8;
		}
		return price;
	};

④ 合理使用循环

	var createXHR = function(){
		var xhr;
		try{
			xhr = new ActiveXObject( 'MSXML2.XMLHttp.6.0' );
		}catch(e){
			try{
				xhr = new ActiveXObject( 'MSXML2.XMLHttp.3.0' );
			}catch(e){
				xhr = new ActiveXObject( 'MSXML2.XMLHttp' );
			}
		}
		return xhr;
	};
	var xhr = createXHR();

 改为:

var createXHR = function(){
		var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
		for ( var i = 0, version; version = versions[ i++ ]; ){
			try{
				return new ActiveXObject( version );
			}catch(e){
			}
		}
	};
	var xhr = createXHR();

⑤ 提前让函数退出代替嵌套条件分支

	var del = function( obj ){
		var ret;
		if ( !obj.isReadOnly ){ // 不为只读的才能被删除
			if ( obj.isFolder ){ // 如果是文件夹
				ret = deleteFolder( obj );
			}else if ( obj.isFile ){ // 如果是文件
				ret = deleteFile( obj );
			}
		}
		return ret;
	};

 改为以下代码减少嵌套:

	var del = function( obj ){
		if ( obj.isReadOnly ){ // 反转if 表达式
			return;
		}
		if ( obj.isFolder ){
			return deleteFolder( obj );
		}
		if ( obj.isFile ){
			return deleteFile( obj );
		}
	};

 ⑥ 传递对象参数代替过长的参数列表

<script type="text/javascript">
	var setUserInfo = function( id, name, address, sex, mobile, qq ){
		console.log( 'id= ' + id );
		console.log( 'name= ' +name );
		console.log( 'address= ' + address );
		console.log( 'sex= ' + sex );
		console.log( 'mobile= ' + mobile );
		console.log( 'qq= ' + qq );
	};
	setUserInfo( 1314, 'sven', 'shenzhen', 'male', '137********', 377876679 );
	var setUserInfo = function( obj ){
		console.log( 'id= ' + obj.id );
		console.log( 'name= ' + obj.name );
		console.log( 'address= ' + obj.address );
		console.log( 'sex= ' + obj.sex );
		console.log( 'mobile= ' + obj.mobile );
		console.log( 'qq= ' + obj.qq );
	};
	setUserInfo({
		id: 1314,
		name: 'sven',
		address: 'shenzhen',
		sex: 'male',
		mobile: '137********',
		qq: 377876679
	});

⑦ 尽量减少参数数量

	var draw = function( width, height, square ){};
var draw = function( width, height ){
		var square = width * height;
	};

⑧ 合理使用链式调用

	var User = function(){
		this.id = null;
		this.name = null;
	};
	User.prototype.setId = function( id ){
		this.id = id;
		return this;
	};
	User.prototype.setName = function( name ){

		this.name = name;
		return this;
	};
	console.log( new User().setId( 1314 ).setName( 'sven' ) );

 改为:

	var User = {
		id: null,
		name: null,
		setId: function( id ){
			this.id = id;
			return this;
		},
		setName: function( name ){
			this.name = name;
			return this;
		}
	};
	console.log( User.setId( 1314 ).setName( 'sven' ) );

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值