在曾探的《Javascript设计模式与开发实践》中,有这样一段话:
设计模式在很多时候其实都体现了语言的不足之处。Peter Norvig 曾说,设计模式是对语言不足的补充,如果使用设计模式,不如去找一门更好的语言。这句话非常正确。
而在我看来,语言未必要封装设计模式,设计模式也不必须封装在语言内部。设计模式并不完全是语言不足的补充,而是一种不分语言针对特定问题的通用性解决方案。
我在研究生阶段曾经研究过过去五年的统考数学试卷。得出的结论是试卷的考题分布是有规律的。数学一共21道题(现在不知道了),分别来自高数、概率论和数理统计。
通过分析过去五年试卷:国家教育部考试司仁慈,每个位置的题目,大概来自那本书其实也是明确的,三科分别可能出现的题目个数也是确定的。
然后就是一定量的题海战术,研究试卷里面出现的考试题目,研究考试题目的变种。我把多个变种题目分析之后,得出一道母题,母题是相对的,表征这是一类题目。一类题目也就有相似的解决方案。
言归正传,回到设计模式,我认为需要设计模式解决的开发场景一定是具有代表性的,是有一定难度的。而开发这件事总体有难度的场景其实是有限的,因为对于有边界的事情我认为都是有限的,业务开发肯定是有限的。
因为有限,总结其中代表性的,常见的问题场景就很必要。搞懂这些之后,可以更加专注于开发的难点或者核心点。
当然常见的问题场景未必需要设计模式解决。但这回只说需要用设计模式解决的场景。
- 全局只需要一个这样的东西,比如全局只有一个window,全局缓存对象,登录页面的登录窗口……
- 这个时候用单例模式解决,单例模式的通用模式
var getSingle = function( fn ){ var result; return function(){ return result || ( result = fn .apply(this, arguments ) ); } }; var getSingle = function( fn ){ var result; return function(){ return result || ( result = fn .apply(this, arguments ) ); } };
2,多个if else怎么重构?简单的用策略模式
// 原来 function ifbox(type){ if(type === 'a'){ return 1 } if(type === 'b'){ return 2 } if(type === 'c'){ return 3 } } // 新 function ifbox(type){ var obj = { a: 1, b: 2, c: 3 } return obj[type] }
复杂的用迭代器模式
// 原来 function ifbox2(){ // type, num 为全局变量 if(type === 'a' && num === 10){ return 1 } if(type === 'b' && num === 11){ return 2 } if(type === 'c' && num === 12){ return 3 } } // 改 function ifbox2(){ var ten = function (){ if(type === 'a' && num === 10){ return 1 } } var eleven = function (){ if(type === 'a' && num === 11){ return 1 } } var twelve = function (){ if(type === 'a' && num === 12){ return 1 } } return [ten, eleven, twelve] } // 使用 const arr = ifbox2() const len = arr.length for (let i = 0; i < len; i++) { if (arr[i]()) { return arr[i]() } }
3,图片懒加载以及接口缓存?
- 用代理模式,出于各种原因当一个对象不方便直接访问另一个对象时候,找个中间人来操作达到某个目的,举一个缓存代理
先创建一个用于求乘积的函数: var mult = function(){ console.log( '开始计算乘积' ); var a = 1; for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i]; } return a; }; mult( 2, 3 ); // 输出:6 mult( 2, 3, 4 ); // 输出:24 现在加入缓存代理函数: var proxyMult = (function(){ var cache = {}; return function(){ var args = Array.prototype.join.call( arguments, ',' ); if ( args in cache ){ return cache[ args ]; } return cache[ args ] = mult.apply( this, arguments ); } })(); proxyMult( 1, 2, 3, 4 ); // 输出:24 proxyMult( 1, 2, 3, 4 ); // 输出:24
4,当功能代码的使用者和开发者不是一个人时候
- 用观察者模式或者发布订阅模式
var salesOffices = {}; // 定义售楼处 salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数 salesOffices.listen = function( key, fn ){ if ( !this.clientList[ key ] ){ // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表 this.clientList[ key ] = []; } this.clientList[ key ].push( fn ); // 订阅的消息添加进消息缓存列表 }; salesOffices.trigger = function(){ // 发布消息 var key = Array.prototype.shift.call( arguments ), // 取出消息类型 fns = this.clientList[ key ]; // 取出该消息对应的回调函数集合 if ( !fns || fns.length === 0 ){ // 如果没有订阅该消息,则返回 return false; } for( var i = 0, fn; fn = fns[ i++ ]; ){ fn.apply( this, arguments ); // (2) // arguments 是发布消息时附送的参数 } }; salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅 88 平方米房子的消息 console.log( '价格= ' + price ); // 输出:2000000 }); salesOffices.listen( 'squareMeter110', function( price ){ // 小红订阅 110 平方米房子的消息 console.log( '价格= ' + price ); // 输出:3000000 }); salesOffices.trigger( 'squareMeter88', 2000000 ); // 发布 88 平方米房子的价格 salesOffices.trigger( 'squareMeter110', 3000000 ); // 发布 110 平方米房子的价格
先总结这几个。后面两个例子代码直接使用了书中的。