《JavaScript设计模式与开发实践》是由曾探所著的一本经典技术书籍。
该书详细介绍了JavaScript中常用的设计模式,并结合实际项目开发经验给出了实践示例和最佳实践建议。这本书首先介绍了设计模式的基本概念和作用,以及为什么要在JavaScript中使用设计模式。接着,书中详细介绍了23种常见的设计模式,如单例模式、工厂模式、适配器模式、观察者模式等。每种设计模式都详细解释了其定义、结构、应用场景和优缺点,并给出了实际的代码示例和案例分析。除了介绍设计模式的理论知识,该书还提供了大量的实践经验和开发技巧。其中包括如何组织和管理JavaScript代码、如何优化性能、如何进行模块化开发、如何进行异步编程等方面的内容。这些实践经验有助于读者更好地理解和应用设计模式,提高JavaScript项目的质量和可维护性。
《JavaScript设计模式与开发实践》这本书语言简洁明了,通俗易懂,适合初学者入门和有一定经验的开发者进阶学习。通过学习这本书,读者可以深入理解JavaScript设计模式的原理和思想,并能够灵活运用到实际项目中,提高自己的编程水平和软件开发能力。
⭕前置问题:
从一个故事开始
✍设计模式的适用性
Java 就是典型的面向对象编程语言。它具备以下三个特点:
- 封装
- 继承
- 多态
通过(脚本)原型来实现面向对象的开发(不是标准的面向对象的编程语言)
动态类型语言
概念:变量类型由值确定
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 );// 恭喜加入合唱团
如果有一个动物,走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。
⭕动态类型语言天生具备多态性。
多态:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。
代码:
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() );//咯咯咯
多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来
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 的
定义:能够访问其他函数作用域中变量的函数
变量的生存周期:var 声明的变量在非函数作用域下是全局变量
1
2
3
4
5
闭包解决生存周期问题:
for( var i = 0,len = nodes.length; i < len; i++ ){
(function( i ){
nodes[ i ].onclick = function(){
console.log(i);
}
})( i )
};
⭕注意:ES6之后 let 也可以解决这个问题 (块级作用域)
满足两个条件:
保证一个类仅有一个实例
2️⃣常见代码:
❌多次点击会生成多个登录框
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';
};
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
1️⃣场景:奖金计算
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
为其他对象提供一种代理以控制对这个对象的访问
场景:图片预加载 —— 创建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('');
单一职责原则: 就一个类 (通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
场景:根据数据创建 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
(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模块的头像' );
}
(1)定义:
类似于继承。公用的部分统一封装(模板),不同的部分单独处理。
(2)Coffee or Tea:
1️⃣咖啡:
2️⃣茶:
(1) 把水煮沸
(2) 用沸水浸泡茶叶
(3) 把茶水倒进杯子
定义一个对象(中介者),该对象封装了系统中对象间的交互方式。
(2)基于场景的中介者模式:
1️⃣场景:SKU
⭕代码的实现需要分别监听颜色以及数量的变化。
2️⃣常见代码实现:
①颜色切换
②数量切换
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 );
};
(1)定义:
让类或者对象一开始只具有一些基础的职责,更多的职责在代码运行时被动态装饰到对象上面
(2)基于场景的装饰者模式:
一个对象(方法)应该只做一件事情
案例:小明买酱油
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('小明用钥匙打开门')
+}
(3)实际应用:违反原则并不奇怪
一个对象应当尽可能少地与其他对象发生相互作用
(2)应用:
(1)定义:
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
(2)应用:
1️⃣用多态来消除条件分支
初始代码:
不符合开发多态原则
3️⃣模板方法模式
4️⃣策略模式
5️⃣代理模式
6️⃣职责链模式
无论你进行了再多的努力,重构都是一个会在未来发生的事情,只不过是一个早晚的问题。
(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 );
}
};