ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 1. 设计模式 ```JS 套路 ==设计的模式 工作中总能总结出方法,成为一种套路 设计模式:不仅仅只有前面的工厂模式,还有很多 ``` ## 2. 什么是模式 ```JS 不是高大上的东西 设计模式:是解决软件设计常见问题的可复用方案----《javascript 设计模式》 设计模式不只是工厂模式还包括以下: 1.单例模式 2.适配器模式 3.装配器模式 4.观察者模式 5.MVC模式 6.MVVM模式 ..... 模式是解决常见问题的可复用的解决方法。对于模式来说,其有以下三大特征: 已验证的解决方案 容易被复用 富有表达力 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E4%BB%80%E4%B9%88%E6%98%AF%E6%A8%A1%E5%BC%8F%201.png) ## 3. 反模式 ```JS 反模式是一种针对某个特定问题的不良解决方案,该方案会导致糟糕的情况发生----《javascript 设计模式》 反模式有以下几种: 1.定义大量污染全局命令空间的变量 2.修改 Object 类的原型 3.使用 document.write 创建页面 4.硬编码,写死功能等 ..... 1.向setTimeout传递字符串,会导致内部触发 eval() 的使用。eval() 方法会造成性能问题和可能会引起安全性的问题。 2.修改Object类的原型,即行为相当于,用其他非标准的方法将内置的标准的类型系统搞乱。 3.定义大量全局变量,会导致出现污染全局命名空间的情况。 4.使用 document.write 来创建接口,是会导致重写我们的页面,而不是增量增加。 ``` ## 4. 单例模式 ```JS 一个网站只有一个登录框 一个教室只有一个黑板 一个时期只有一个皇帝 以上的都是单例模式的实例 单例模式:Singleton Pattern 确保一个类只有一个实例共享使用 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%201.png) ```JS 众多设计模式中,单例模式比较常见的一种,面试和工作中也会经常接触到。 设计模式经典 GoF 定义的单例模式需要满足以下两个条件: 保证一个类只创建一个实例 提供对该实例的全局访问点 如果在我们应用场景中需要有类似的实体(有且只需要有一个实例,且可全局访问),那么我们就可以使用单例模式将其实现为一个单例对象。 因此 A: 对于一个网站而言,许多逻辑都需要有用户登录后才能执行,对于未登录的用户都会弹出登录框提醒用户登录。而登录框非常适合实现为一个单例 B: 网站的计时器,是记录网站的时间相关情况。为了使整个应用有一个统一的时间情况,通常都会只创建一个计数器来统一记录时间情况 C: 应用程序中配置对象,可全局访问。同理,为了统一整个应用的配置信息入口,通常一份配置,只会有一个实例对象。 ``` ## 5. 适配器模式(Adapter Pattern) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F%201.png) ```JS 不影响现有实现方式,兼容调用旧接口的代码 适配器模式的使用场景 事实上,适配器模式可以说是一种“亡羊补牢”的模式适配器模式,其并不是我们设计之初想要使用到的。 因为如果我们系统和应用原本的接口能够正常工作的话,相信我们并不会使用上适配器模式。然而在业务中,我们经 常会发现,对于过去能够好好工作的接口,或许在未来的某一天却发现已经不能适用于新的系统了。此时如果对系统 进行重构耗费的精力和时间太大了。 这时候我们就可以考虑使用适配器模式把旧接口包装成一个新的接口,使它继续保持生命力。 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F%202.png) ``` 我们经常会使用到适配器模式来兼容支持。适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是 怎样实现的,也不考虑它们将来 可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。 ``` ### 适配器模式实例 ```JS /** * 增加数据适配函数 arrToObjAdapter * 来兼容我们新功能接口 getUserDetail */ // 新功能接口,这个接口在新系统中会广泛使用到 function getUserDetail(user) { console.log('当前用户:' + user.name); console.log('性别' + user.sex); console.log('职业' + user.job); console.log('出生日期' + user.date); } /** * TODO: 实现一个数据适配器 arrToObjAdapter * @param arr [Array] * @return [Object] */ function arrToObjAdapter (arr) { return { name: arr[0], sex: arr[1], job: arr[2], date: arr[3] }; } // 旧的数据格式如下 var oldData = [ 'andy', // 名称 'man', // 性别 'engineer', // 职业 '1992-10-10' // 出生日期 ]; ``` ## 6. 装饰器模式(Decorator Pattern) ```JS 在不修改类原来的接口下,动态地为对象添加功能 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E8%A3%85%E9%A5%B0%E5%99%A8%E6%A8%A1%E5%BC%8F%201.png) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E8%A3%85%E9%A5%B0%E5%99%A8%E6%A8%A1%E5%BC%8F%202.png) ## 7. [资料] ES7 decorator ### ES7 的 Decorator ```JS 在许多面向对象的语言中都有装饰器(Decorator),用来增加类的功能和职责。 令人喜悦的是,在未来的 JavaScript 中也引入了这个概念,目前 babel 对这个概念也有很好的支持。如果你是一个喜欢尝试 新技术的开发者,可以借助 babel 来大胆使用它。下面让我们来了解一下更多内容。 ``` ### 如何使用 Decorator 在目前还只是一个提议。如果你现在就想要体验使用的话,可以通过下面两种方法: #### 通过使用 Babel 来转换使用 ```JS 首先全局安装babel组件模块 npm install babel -g 命令行开启 Decorator babel --optional es7.decorators test.js > test.es5.js ``` #### 通过 在线的 REPL 提前体验 Babel 的官方网站提供一个在线转码器,其支持 Decorator 的在线转码。 ### JavaScript 的 Decorator 装饰器(Decorator) 在现代编程语言中是相当普遍的。事实上,JavaScript 装饰器的语法与 Python 装饰器的语法非常相似。 有以下两种用法: #### 装饰类 ```JS 装饰类的方法 装饰类 @superManPower class Man { // some code } // 装饰器函数 function superManPower(target) { target.super = true; } // 等同于 class Man {} // 装饰器函数 superFunc function superManPower(target) { this.super = true; } Man = superManPower(Man) || Man; 上面是简单的一个 装饰器(Decorator) 的例子,其中 @superManPower 则为使用装饰器 superManPower 装饰器 superManPower 修改了类 Man 的行为,给其加上静态属性 super 此时装饰器函数是一个对类进行处理的函数。其第一个参数为需要被装饰的目标类,如下: // 装饰器函数 function superManPower(target) { // target 即为被装饰的目标 target.super = true; } ``` #### 装饰类的方法 ```JS 装饰器除了可以装饰我们的类之外,还可以修饰类的属性方法。如下所示: function superInitPower(attack, defense) { return function(target, key, descriptor) { // 保存方法 const method = descriptor.value; // 修改方法 descriptor.value = (...args)=>{ // 增加攻击力和防御力 args[0] += 100; args[1] += 100; // 调用原本的方法 return method.apply(target, args); } return descriptor; } } class Man { @superInitPower init(attack, defense) { this.attack = attack; // 初始攻击力 this.defense = defense; // 初始攻击力 } } 此时,装饰器函数可以接受三个参数,和我们的 Object.defineProperty() 的参数基本保持一致,分别表示如下: 第一个参数 target 表示修饰的目标对象 即类的实例 这不同于之前装饰类,之前装饰类 target 表示的是类本身 第二个参数 key 表示需要装饰的属性名 第三个参数 descriptor 是该属性的描述对象 // descriptor 对象的值 { value: specifiedFunction, // 指定的方法 enumerable: false, // 是否可遍历迭代 configurable: true, writable: true }; ``` #### 多层装饰器 ```JS 我们可以同时使用多个装饰器,如下所示: @healthy class Person { @runFase @eatHuge init() { } } 例如上面的,我们使用了多个装饰器,增加类和类的方法的职责。 ``` ### 更多参数 ```JS 上面我们看到装饰器参数都是固定的,对于一些场景可能会不够用。此时我们通过包装一层函数的方式来增加参数, 如下所示: // 装饰器函数 - 增加参数 function superFunc(num, canUse) { return function(target) { target.canUse = canUse; target.num = num; } } // 因此下面我们就可以这样调用 @superFunc(1, true) class Base1() {} Base1.canUse; // true Base1.num; // 1 @superFunc(2, false) class Base2() {} Base2.canUse; // false Base2.num; // 2 上面中,我们看到装饰器 superFunc 是可以接受参数,通过各种参数来增强装饰器的能力。 注: 装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器本质就是编译时执行的函数。 ``` #### 不能装饰普通函数 事实上 装饰器(Decorator) 只使用与装饰类和类的方法,不能使用于普通函数。这是因为变量提升会产生系列的问题。 这里大家可以查看更多阅读来了解相关的问题详情。 #### 使用场景 我们常常使用 装饰器(Decorator) 来实现 AOP(面向切面编程)。例如前面我们习题讲到的日志,也是非常经典的应用: [日志系统]。(http://taobaofed.org/blog/2015/11/16/es7-decorator/#4%E3%80%81%E7%BB%8F%E5%85%B8%E5%AE%9E%E7%8E%B0%EF%BC%9ALogger) #### 更多阅读 想要了解更多知识,大家可以阅读以下的资料文献。 javascript-decorators :https://github.com/wycats/javascript-decorators Exploring EcmaScript Decorators ECMAScript Proposal for JavaScript Decorators(and protocols) :https://ponyfoo.com/articles/javascript-decorators-proposal ECMAScript 6 入门 - 阮一峰 :http://es6.ruanyifeng.com/#docs/decorator ## 8. 观察者模式 ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%201.png) ```JS document.body.onclick = function() { alert('我是一个观察者,你一点击,我就知道了'); } document.body.addEventListener('click,function'() { alert('我也是一个观察者,你一点击,我就知道了'); }); 什么是自定义事件 事实上不难理解,在我们 js 中我们有许多事件,如鼠标点击时的 click,键盘的 keyup 等事件,这些都是系统提供给我们的 可以触发使用的事件。但当我们的程序变得越来越复杂的时候,我们发现只靠系统提供的事件监听似乎已经不能满足我们的需求 了。我们就需要我们自己去定义事件。自定义事件 Event 对象 在我们组件开发过程中经常会使用到。 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%202.png) ## 9. MVC 模式 ```JS 应用复杂性的提高,程序变得臃肿,难以分工开发 通过关注点分离改进程序组织: 将复杂问题合理的分解,拆分成不同的部分,分别仔细研究问题的不同部分,综合各部分的结果,得出整体的解决方案 应用程序:界面、逻辑、数据 ``` ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/MVC%20%E6%A8%A1%E5%BC%8F%201.png) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/MVC%20%E6%A8%A1%E5%BC%8F%202.png) ## 10. MVC 模式实践 http://coding.imweb.io/demo/p7/mvc/index.html ![]() ![]() ## 11. MVVM 模式 ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/MVVM%20%E6%A8%A1%E5%BC%8F%20%201.png) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/MVVM%20%E6%A8%A1%E5%BC%8F%202.png) ## 12. MVVM 模式实践 ```JS 1.MVVM模式 :指的是 Model-View-ViewModel 2.ViewModel 层在整个设计模式里起到了一个关键性的作用。一边链接视图,一边与模型关联,通过这样形成了一个双向绑定的关系 3.应用 MVVM模式框架可以通过数据驱动来实现视图更新的自动化,大大提升开发效率。 随着时代的发展,前端逻辑越来越重,前端架构也变成需求。 为了更好地在前端开发,MVVM 模式伴随着各种 MVVM 框架(vuejs等),走进了前端的世界来。 对于简单的 UI 界面,使用 MVVM 可能会过度设计,即杀鸡焉用牛刀的概念,事实上对于一些大型的图形应用程序即视图状态较多的应用程序, 对于 ViewModel 的构建和维护的成本都会比较高。 因此 MVVM 模式 只是适合大部分页面,但并不是适合所有的页面,我们需要根据自身业务去选择。 最后,在这里引用司徒正美的一段话: MVVM让我们换一个视角来看待浏览器的世界,会发现别样的精彩 ``` ## 13. [资料] 双向绑定实现原理 ```JS 数据双向绑定的常见实现原理 前面讲到的 MVVM 框架,其主要亮点就是实现了视图层和数据层的 双向绑定(two-way-binding)。那么双向绑定在前端中是 如何实现的呢?在本文中,将给大家讲解数据双向绑定的常见实现方式。 观察者模式实现 前面我们学习了什么是观察者模式。观察者模式可以来实现我们的数据的双向绑定。只不过这种方式需要我们手动地去调用获取 和设置数据的指定方法(如 updateData 等)。在数据发生变化的时候,我们发布一个叫数据变化的的事件。同理,当视图发生 变化的时候,我们发布一个叫视图变化的事件。在这些事件发生时只要让相关的观察者去订阅这些事件即可。 如下面的代码: // 更新数据 var data = {}; function updateData(attr, value){ data[attr] = value; Event.trigger('数据变化', attr, value); } // 订阅视图变化事件 Event.on('视图变化', function(attr, value){ // 更新数据 updateData(attr, value); }); // 订阅数据变化事件 Event.on('数据变化', function(attr, value){ // 更新绑定的视图 }); //用户操作,发布视图变化事件 document.body.addEventListener('keyup', function( e ){ Event.trigger('视图变化'); }) 这种方式,是最早期的方式,问题是需要手动的方式来绑定关系,对于那种多交互和数据修改的页面就比较繁琐。 脏检测 (Dirty Checking)实现 典型的 angularJS 的数据双向绑定就是基于数据的脏检测机制来实现的。angularJS 通过检查脏数据来进行 UI 层的操作更新, 以下是相关点:大体原理是通过记录所有变量的当前值,然后当发生特定的操作之后,angularJS 会调用 $digest 方法,这个方 法内部做的逻辑就是遍历所有的 watcher(观察者),对被监控的属性做对比,对比其在方法调用前后属性值有没有发生变化,如果 发生变化,则调用对应的 handler (处理函数)。 触发脏检测的操作 DOM事件,譬如用户输入文本,点击按钮(ng-click)等 XHR响应事件 ($http) 浏览器Location变更事件 ($location) Timer事件($timeout, $interval) 执行 $digest() 或 $apply() 如果大家感兴趣 angularJS 双向数据绑定的具体实现,可以阅读下面的一些文章: AngularJS数据双向绑定揭秘 AngularJS的数据双向绑定是怎么实现的? 脏检测方式的缺点十分明显,就是遍历 watcher 是很消耗性能的,特别是当监控数量达到一个数量级的时候。 数据劫持(Hijacking)实现 事实上,目前许多 MVVM 框架(vue.js、avalon.js 等)都是使用这种技术实现,数据劫持的核心是通过 Object.defineProperty() 来实现。我们可以使用 Object.defineProperty() 来定义一个对象的属性情况,如下: Object.defineProperty(obj, key,{ value: someValue , writable: true, // 可写 enumerable: true, // 可被赋值 configurable: true // 可改变和删除 }) 除了上面之外,我们还可以设置属性的 getter,setter。当存在 getter,setter 函数时,会出现下面的情况: 当获取目标属性时触发 getter 函数的执行 当对目标属性进行赋值操作时会触发 setter函数的执行 根据我们行业的术语,这种方式称为 数据劫持。通过使用 数据劫持,我们可以这样实现双向绑定: // 定义一个设置属性 setter 和 getter 的方法 function defineProperty(obj, attr, value){ // 私有变量 value var value; Object.defineProperty(obj, attr, { get:function (){ console.log('获取属性值' + value); return value; }, set:function (val){ console.log('修改属性值为 ' + value); value = val; console.log('修改属性值'); } }) // 设置属性值 obj[attr] = value; } // 通过使用 defineProperty 方法,设置 man 的 name 属性的数据变化监听 var man = {}; defineProperty(man, 'name', "cover"); // 修改属性值为 cover man.name; // 获取属性值 cover man.name = 'kevin'; // 修改属性值为 kevin 通过 Object.defineProperty() ,我们可以轻松地得知数据(对象)发生变化,然后在相应的 setter 函数中设置数据 更新相应的操作(如发布更新事件)。通过这种方式我们可以使用赋值操作 = 来实现数据绑定,相对前面通过手动调用绑定 指定更新数据接口 updateData 来说更加优雅。 注:比较主流的方法,针对部分还不支持 Object.defineProperty 低级浏览器,可采用 VBScript 作了完美兼容。当然 大部分 mvvm 框架已经逐渐放弃对低端浏览器的支持。 面向未来的 Object.observe 实现 一场变革即将到来。Object.observe() 是未来 ECMAScript 标准之一,它是一个可以异步观察 Javascript 中对象变化 的方法,而无需你去使用一个其他的 JS 库。它允许一个观察者接收一个按照时间排序的变化记录序列,这个序列描述的是 一列被观察的对象所发生的变化。例如下面的代码: // 模型 var model = {}; // 对 model进行观察 Object.observe(model, function(changes){ // 数据发生变化执行异步回调函数 changes.forEach(function(change) { // 通知变化 console.log(change.type, change.name, change.oldValue); }); }); 注:通过使用Object.observe(),你可以不需要使用任何框架就能实现双向数据绑定。唯一问题是目前浏览器还不支持, 所以是面向未来的方式。 总结 上面简单的给大家讲解了前端是如何实现 双向绑定(two-way-binding) 的,希望大家可以根据上面的内容尝试去观察上面 四种方式的异同点和不同的理念。从中领悟到我们前端的技术是如何不断更新发展的,以及一个复杂的框架是如何通过最 初简单的思路逐渐形成的。 更多阅读 javascript实现数据双向绑定的三种方式 Object.observe()带来的数据绑定变革 ``` ## 14. jQuery 的设计模式 Iterator Pattern(迭代器模式) Observer Pattern(观察者模式) Composite Pattern(组合模式) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/jQuery%20%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%201.png) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/jQuery%20%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%202.png) ![](https://raw.githubusercontent.com/lz109896/Web-datum/bd8aca6d576510c6ae02c681173af67abd0c10e5/jQuery%20%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%203.png) ## 15. 设计模式结语 ```JS 模式是解决问题的可复用的方案 模式和反模式 勿神话设计模式 不刻意使用,避免反模式 通过实践理解设计模式 师傅领进门,修行靠个人 ```