js常见设计模式
首先来了解下对象和模块种类的概念;
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>更多模式参考依据:https://www.w3cschool.cn/zobyhd/1ldb4ozt.html</title> </head> <body> <script type="text/javascript"> // 对象创建的基本方式 var newObject = {}; console.log(newObject) // __proto__: Object // or var newObject1 = Object.create( null ); // Object No properties console.log(newObject1) // or var newObject2 = new Object(); console.log(newObject2) // __proto__: Object // 在Javascript中函数有一个prototype的属性。当我们调用Javascript的构造器创建一个对象时, // 构造函数prototype上的属性对于所创建的对象来说都可见 /* AMD模块:在浏览器中编写模块化Javascript的格式 通过它模块本身和模块之间的引用可以被异步的加载 通过它模块本身和模块之间的引用可以被异步的加载 一个帮助定义模块的define方法和一个处理依赖加载的require方法。define被用来通过下面的方式定义命名的或者未命名的模块 define( module_id, // 可选的 当不存在module_id参数的时候,我们称这个模块为匿名模块 [dependencies], // 可选的 definition function //用来实例化模块或者对象的方法 ) CommonJS 模块:理解CommonJS:require()和exports ECMAScript Harmony 模块:未来的模块 ES5-next next语法; UMD是一种是实验性质的模块形式,允许在编写代码的时候,所有或者大多数流行的实用脚本加载技术对模块的定义在客户端和服务器环境下都能够起作用 */ </script> </body> </html>
1.单例模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> // 单例模式 【一个实例,并提供一个访问它的全局访问点】 /* 全局对象是最简单的单例模式 let obj = {属性、方法} 但是我们并不建议这么实现单例,因为全局对象/全局变量会有一些弊端: (1)、污染命名空间(容易变量名冲突) (2)、维护时不容易管控 (搞不好就直接覆盖了) */ var oneInstance = (function(){ var instance; function init() { // 定义私有方法和属性 // 操作逻辑 name: 'bob' return { // 定义公共方法和属性 age: 20, getName: function(){ console.log(111) return 'salo' } }; } return { // 获取实例 getInstance:function(){ if(!instance){ instance = init(); } return instance; } } })(); var obj1 = oneInstance.getInstance(); var obj2 = oneInstance.getInstance(); console.log(obj1,obj2,obj1.getName()) console.log(obj1 === obj2, '判断是否相等'); // ture // 例二 let createPeople = (function(){ let instance = null; return function(name){ if(instance){ return instance } return instance = new People(name); } })(); let People = function(name){ this.name = name; } People.prototype.getName = function(){ console.log(this.name); } let man1 = new createPeople("bob"); console.log(man1.getName()); let man2 = new createPeople("lala"); console.log(man2.getName()); console.log(man1 === man2, '判断是否相等'); // ture </script> </body> </html>
2.工厂模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> // 工厂模式 工厂函数就是做一个对象创建的封装,并将创建的对象return出去 function Animal(options){ var obj = new Object(); obj.color = options.color; obj.name= options.name; obj.getDesc = function(){ console.log(obj,options) return '名称:'+ obj.name+', 颜色:'+ obj.color; } return obj; } var cat = Animal({name: '蓝猫', color: '白色'}); cat.getDesc(); </script> </body> </html>
3.构造函数模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> // 构造函数模式 // 使用new关键字来调用该函数,我们可以告诉Javascript把这个函数当做一个构造器来用,它可以用自己所定义的成员来初始化一个对象 function Animal(name, color){ this.name = name; this.color = color; this.getName = function(){ return this.name; } } // 实例一个对象 var dog = new Animal('小狗', '白色'); console.log(dog,dog.getName()); // 打印得到 Animal对象实例 '小狗' </script> </body> </html>
4.发布订阅模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> /* 订阅发布模式又称为观察者模式,定义了一种一对多的关系,让多个观察者同时监听某一个主题对象, 这个主题对象的状态发生改变时就会通知所有的观察者对象。 */ // 订阅/发布模式 发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应的操作 // 一个发布者,功能就是负责发布消息 - publish var pub = { publish: function() { dep.notify(); } } // 多个订阅者 subscribers, 在发布者发布消息之后执行函数 var sub1 = { update: function() { console.log(1); } } var sub2 = { update: function() { console.log(2); } } var sub3 = { update: function() { console.log(3); } } var sub4 = { update: function() { console.log(4); } } // 一个主题对象 // 收集依赖 function Dep() { this.subs = [sub1, sub2, sub3, sub4]; } Dep.prototype.notify = function() { console.log(Dep.prototype) this.subs.forEach(function(sub) { sub.update(); }); } // 发布者发布消息, 主题对象执行notify方法,进而触发订阅者执行Update方法 var dep = new Dep(); pub.publish(); /* new这个过程中到底发生了什么? 1.创建一个空对象,将它的引用赋给this,继承函数的原型。 2.通过this将属性和方法添加至这个对象。 3.最后返回this指向的新对象。 */ function Person(name,age){ // let this = {}; 1.将一个空对象的引用赋值给this,这是隐式的 // 2.给this对象赋值; this.name = name; this.age = age; // return this 3.隐式的返回this对象 } let person = new Person('bob',20) console.log(person, 'new一个构造函数发生了什么?') // 得到Person对象 //ES6 class类 实例化的过程又叫构造,更是new的过程 class Animal { constructor(color, weight) { // 这里就是构造器(构造函数) // 第1步(隐含):let this = new Object(); // 第2步(看得见):加东西 this.color = color; this.weight = weight; //第3步(隐含):return this; } sayColor() { console.log(this.color); } }; /* ### 同时注意 这也就是我们为什么在面向对象里,频繁使用this和找this指向问题的原因 ### 因为系统创建和返回对象,都是通过this来执行的 ### 另外,面向对象的程序编写的过程中,遇到的大多数错误,一般都是this指向问题的错误 */ const animal = new Animal('red', 100); // new 一个实例 console.log(animal, 'new一个构造函数发生了什么?') // 得到Animal对象 animal.sayColor() // 'red' // 实现一个简单的new方法 function _new(fn, ...arg) { const obj = {}; // 创建一个新的对象 obj.__proto__ = fn.prototype; // 把obj的__proto__指向fn的prototype,实现继承 fn.apply(obj, arg) // 改变this的指向 return Object.prototype.toString.call(obj) == '[object Object]'? obj : {} // 说返回新的对象obj } const person1 = _new(Person,'bos', 28); // new 一个实例 console.log(person1, '手写new1') // 得到Person对象 // 实现一个简单的new方法 let newMethod = function (fn, ...rest) { // 1.以构造器的prototype属性为原型,创建新对象; let obj = Object.create(fn.prototype); // 等价 const obj = {}; obj.__proto__ = fn.prototype; // 2.将this和调用参数 传给 [构造]器执行 let result = fn.apply(obj, rest); // 3.如果构造器没有手动返回对象,则返回第一步的对象 return typeof result === 'object' ? result : obj; }; const person2 = newMethod(Person,'soa', 26); // new 一个实例 console.log(person2, '手写new2') // 得到Person对象 </script> </body> </html>
5.策略模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> // 策略模式 采用很多方法实现同一个目标 // 某公司年终奖计算方式 var Performance = { "A": function(bonus) { return bonus * 5; }, "B": function(bonus) { return bonus * 4; }, "C": function(bonus) { return bonus * 3; }, "D": function(bonus) { return bonus * 2; }, "E": function(bonus) { return bonus * 1; } }; var caculateMoney = function(level, bonus) { return Performance[level](bonus); }; console.log(caculateMoney("D", 5000)); // 10000 // 场景:antd 中的 message组件有info、success、error 以及表单校验 /* 策略模式的优点: 1. 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句 2. 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展 策略模式的缺点: 1. 会在程序中增加许多策略类或者策略对象,但实际上比把他们负责的逻辑堆砌在 Context 中要好 2. 使用策略模式,必须了解所有的策略,才能更好的选择一个合适的策略 */ </script> </body> </html>
6.代理模式
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构</title> </head> <body> <script type="text/javascript"> // 代理模式 /* 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。 使用代理的原因是我们不愿意或者不想对原对象直接进行操作 */ // 保护代理:通过代理来处理一些不必要的东西,过滤掉无用信息 // 虚拟代理:是把一些开销很大的对象,延迟到真正需要它的时候才去创建执行 // 缓存代理:可以作为一些开销大的运算结果提供暂时的存储,下次运算时,如果传递进来堵塞参数跟之前一致,则可以直接返回前面存储的运算结果 // 例子:虚拟代理实现图片预加载 在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现 // 创建一个本体对象 var myImage = (function() { // 创建标签 var imgDom = document.createElement('img'); // 添加到页面 document.body.appendChild(imgDom); return { // 设置图片的src setSrc: function(src) { // 更改src imgDom.src = src; } } })(); // 创建代理对象 var proxyImage = (function() { // 创建一个新的img标签 var img = new Image(); // img 加载完成事件 img.onload = function() { // 调用 myImage 替换src方法 myImage.setSrc(this.src); } return { // 代理设置地址 setSrc: function(src) { // 预加载 loading myImage.setSrc('https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3416610594,1723780823&fm=26&gp=0.jpg'); // 赋值正常图片地址 img.src = src; } } })(); // 最终显示的这个图片 如果这张图地址有问题则显示 预加载 loading proxyImage.setSrc( 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.yzcdn.cn%2Fupload_files%2F2018%2F05%2F16%2FFqWOA3q9QlpxYBMDlX-A27qXSAOA.jpg%3FimageView2%2F2%2Fw%2F580%2Fh%2F580%2Fq%2F75%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.yzcdn.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1616668616&t=3babe5237f18b1d3149a827d16b9508b' ); /* 创建一个Image对象:var a=new Image(); 定义Image对象的src: a.src="xxx.jpg"; 这样做就相当于给浏览器缓存了一张图片。 图像对象: 建立图像对象:图像对象名称 = new Image([宽度],[高度]) 图像对象的属性: border complete height hspace lowsrc name src vspace width 图像对象的事件:onabort onerror onkeydown onkeypress onkeyup onload 需要注意的是:src 属性一定要写到 onload 的后面,否则程序在 IE 中会出错。 */ // 例子:缓存代理 // 主体 function add() { var arg = [].slice.call(arguments); console.log(arg,'arg1') return arg.reduce(function(a, b) { return a + b; }); } // 代理 var proxyAdd = (function() { var cache = []; return function() { var arg = [].slice.call(arguments).join(','); console.log(arg,'arg2') // 如果有,则直接从缓存返回 if (cache[arg]) { return cache[arg]; } else { var rest = add.apply(this, arguments); return rest; } }; })(); console.log(add(1, 2, 3, 4), proxyAdd(10, 20, 30, 40)); // 10 10 100 100 </script> </body> </html>
以上自己整理。转载注明出处!!!