上篇我们讲解了JavaScript常见的工厂模式和单体模式,本篇我们继续来了解JavaScript的其他常用的设计模式。这些设计模式看看似离我们比较远难以理解,但是当你深入了解会发现一些设计模式我们平时都在使用,只是我们并不知道其原理。 也正如现在流行的前端框架,如果你看过源码的话你会发现,能研发出这些框架的人也正是对前端这些设计模式掌握比较熟悉的,这样开发出来的框架效率方面自然不错,就像是“庖丁解牛”一样。

1

理解模块模式

我们通过单体模式理解了是以对象字面量的方式来创建单体模式的;比如如下的对象字面量的方式代码如下:

var singleMode = {    name: value,    method: function(){                    }};

模块模式的思路是为单体模式添加私有变量和私有方法能够减少全局变量的使用;如下就是一个模块模式的代码结构:

var singleMode = (function(){    // 创建私有变量    var privateNum = 112;    // 创建私有函数    function privateFunc(){        // 实现自己的业务逻辑代码    }    // 返回一个对象包含公有方法和属性    return {        publicMethod1: publicMethod1,        publicMethod2: publicMethod1    };})();

模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,先定义了私有变量和函数,供内部函数使用,然后将一个对象字面量作为函数的值返回,返回的对象字面量中只包含可以公开的属性和方法。这样的话,可以提供外部使用的方法;由于该返回对象中的公有方法是在匿名函数内部定义的,因此它可以访问内部的私有变量和函数。

我们什么时候使用模块模式?

如果我们必须创建一个对象并以某些数据进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么我们这个时候就可以使用模块模式了。

理解增强的模块模式

增强的模块模式的使用场合是:适合那些单列必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强的情况。比如如下代码:

function CustomType() {    this.name = "tugenhua";};CustomType.prototype.getName = function(){    return this.name;}var application = (function(){    // 定义私有    var privateA = "aa";    // 定义私有函数    function A(){};     // 实例化一个对象后,返回该实例,然后为该实例增加一些公有属性和方法    var object = new CustomType();     // 添加公有属性    object.A = "aa";    // 添加公有方法    object.B = function(){        return privateA;    }    // 返回该对象    return object;})();

下面我们来打印下application该对象;如下:

console.log(application);

继续打印该公有属性和方法如下:

console.log(application.A);// aa

console.log(application.B()); // aa

console.log(application.name); // tugenhua

console.log(application.getName());// tugenhua

2

理解代理模式

代理是一个对象,它可以用来控制对本体对象的访问,它与本体对象实现了同样的接口,代理对象会把所有的调用方法传递给本体对象的;代理模式最基本的形式是对访问进行控制,而本体对象则负责执行所分派的那个对象的函数或者类,简单的来讲本地对象注重的去执行页面上的代码,代理则控制本地对象何时被实例化,何时被使用;我们在上面的单体模式中使用过一些代理模式,就是使用代理模式实现单体模式的实例化,其他的事情就交给本体对象去处理;

代理的优点:

  1. 代理对象可以代替本体被实例化,并使其可以被远程访问;
  2. 它还可以把本体实例化推迟到真正需要的时候;对于实例化比较费时的本体对象,或者因为尺寸比较大以至于不用时不适于保存在内存中的本体,我们可以推迟实例化该对象;

我们先来理解代理对象代替本体对象被实例化的例子;比如现在京东ceo想送给奶茶妹一个礼物,但是呢假如该ceo不好意思送,或者由于工作忙没有时间送,那么这个时候他就想委托他的经纪人去做这件事,于是我们可以使用代理模式来编写如下代码:

// 先申明一个奶茶妹对象var TeaAndMilkGirl = function(name) {    this.name = name;};// 这是京东ceo先生var Ceo = function(girl) {    this.girl = girl;    // 送结婚礼物 给奶茶妹    this.sendMarriageRing = function(ring) {        console.log("Hi " + this.girl.name + ", ceo送你一个礼物:" + ring);    }};// 京东ceo的经纪人是代理,来代替送var ProxyObj = function(girl){    this.girl = girl;    // 经纪人代理送礼物给奶茶妹    this.sendGift = function(gift) {        // 代理模式负责本体对象实例化        (new Ceo(this.girl)).sendMarriageRing(gift);    }};// 初始化var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));proxy.sendGift("结婚戒"); // Hi 奶茶妹, ceo送你一个礼物:结婚戒

代码如上的基本结构,TeaAndMilkGirl 是一个被送的对象(这里是奶茶妹);Ceo 是送礼物的对象,他保存了奶茶妹这个属性,及有一个自己的特权方法sendMarriageRing 就是送礼物给奶茶妹这么一个方法;然后呢他是想通过他的经纪人去把这件事完成,于是需要创建一个经纪人的代理模式,名字叫ProxyObj ;他的主要做的事情是,把ceo交给他的礼物送给ceo的情人,因此该对象同样需要保存ceo情人的对象作为自己的属性,同时也需要一个特权方法sendGift ,该方法是送礼物,因此在该方法内可以实例化本体对象,这里的本体对象是ceo送花这件事情,因此需要实例化该本体对象后及调用本体对象的方法(sendMarriageRing).

最后我们初始化是需要代理对象ProxyObj;调用ProxyObj 对象的送花这个方法(sendGift)即可;

对于我们提到的优点,第二点的话,我们下面可以来理解下虚拟代理,虚拟代理用于控制对那种创建开销很大的本体访问,它会把本体的实例化推迟到有方法被调用的时候;比如说现在有一个对象的实例化很慢的话,不能在网页加载的时候立即完成,我们可以为其创建一个虚拟代理,让他把该对象的实例推迟到需要的时候。

理解使用虚拟代理实现图片的预加载

在网页开发中,图片的预加载是一种比较常用的技术,如果直接给img标签节点设置src属性的话,如果图片比较大的话,或者网速相对比较慢的话,那么在图片未加载完之前,图片会有一段时间是空白的场景,这样对于用户体验来讲并不好,那么这个时候我们可以在图片未加载完之前我们可以使用一个 loading加载图片来作为一个占位符,来提示用户该图片正在加载,等图片加载完后我们可以对该图片直接进行复制即可;下面我们先不用代理模式来实现图片的预加载的情况下代码如下:

第一种方案:不使用代理的预加载图片函数如下

// 不使用代理的预加载图片函数如下var myImage = (function(){    var imgNode = document.createElement("img");    document.body.appendChild(imgNode);    var img = new Image();    img.onload = function(){        imgNode.src = this.src;    };    return {        setSrc: function(src) {            imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif";            img.src = src;        }    }})();// 调用方式myImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");

如上代码是不使用代理模式来实现的代码;

第二种方案:使用代理模式来编写预加载图片的代码如下:

var myImage = (function(){    var imgNode = document.createElement("img");    document.body.appendChild(imgNode);    return {        setSrc: function(src) {            imgNode.src = src;        }    }})();// 代理模式var ProxyImage = (function(){    var img = new Image();    img.onload = function(){        myImage.setSrc(this.src);    };    return {        setSrc: function(src) {                         myImage.setSrc("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");        img.src = src;        }    }})();// 调用方式ProxyImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");

第一种方案是使用一般的编码方式实现图片的预加载技术,首先创建imgNode元素,然后调用myImage.setSrc该方法的时候,先给图片一个预加载图片,当图片加载完的时候,再给img元素赋值,第二种方案是使用代理模式来实现的,myImage 函数只负责创建img元素,代理函数ProxyImage 负责给图片设置loading图片,当图片真正加载完后的话,调用myImage中的myImage.setSrc方法设置图片的路径;他们之间的优缺点如下:

  1. 第一种方案一般的方法代码的耦合性太高,一个函数内负责做了几件事情,比如创建img元素,和实现给未加载图片完成之前设置loading加载状态等多项事情,未满足面向对象设计原则中单一职责原则;并且当某个时候不需要代理的时候,需要从myImage 函数内把代码删掉,这样代码耦合性太高。
  2. 第二种方案使用代理模式,其中myImage 函数只负责做一件事,创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做,当图片加载成功后,代理函数ProxyImage 会通知及执行myImage 函数的方法,同时当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可;

从上面代理模式我们可以看到,代理模式和本体对象中有相同的方法setSrc,这样设置的话有如下2个优点:

  1. 用户可以放心地请求代理,他们只关心是否能得到想要的结果。假如我们不需要代理对象的话,直接可以换成本体对象调用该方法即可。
  2. 在任何使用本体对象的地方都可以替换成使用代理。

当然如果代理对象和本体对象都返回一个匿名函数的话,那么也可以认为他们也具有一致的接口;比如如下代码:

var myImage = (function(){    var imgNode = document.createElement("img");    document.body.appendChild(imgNode);    return function(src){        imgNode.src = src;    }})();// 代理模式var ProxyImage = (function(){    var img = new Image();    img.onload = function(){        myImage(this.src);    };    return function(src) {                myImage("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");        img.src = src;    }})();// 调用方式ProxyImage("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");

虚拟代理合并http请求的理解:

比如在最后端系统中,有表格数据,每一条数据前面有复选框按钮,当点击复选框按钮时候,需要获取该id后需要传递给给服务器发送ajax请求,服务器端需要记录这条数据,去请求,如果我们每当点击一下向服务器发送一个http请求的话,对于服务器来说压力比较大,网络请求比较频繁,但是如果现在该系统的实时数据不是很高的话,我们可以通过一个代理函数收集一段时间内(比如说2-3秒)的所有id,一次性发ajax请求给服务器,相对来说网络请求降低了, 服务器压力减少了;

// 首先html结构如下:

    选择框    

    选择框    

    选择框    

    选择框    

一般的情况下 JS如下编写

下面我们通过虚拟代理的方式,延迟2秒,在2秒后获取所有被选中的复选框的按钮id,一次性给服务器发请求。

通过点击页面的复选框,选中的时候增加一个属性isflag,没有选中的时候删除该属性isflag,然后延迟个2秒,在2秒后重新判断页面上所有复选框中有isflag的属性上的id,存入数组,然后代理函数调用本体函数的方法,把延迟2秒后的所有id一次性发给本体方法,本体方法可以获取所有的id,可以向服务器端发送ajax请求,这样的话,服务器的请求压力相对来说减少了。

代码如下:

// 本体函数var mainFunc = function(ids) {    console.log(ids); // 即可打印被选中的所有的id    // 再把所有的id一次性发ajax请求给服务器端};// 代理函数 通过代理函数获取所有的id 传给本体函数去执行var proxyFunc = (function(){    var cache = [], // 保存一段时间内的id        timer = null; // 定时器    return function(checkboxs) {        // 判断如果定时器有的话,不进行覆盖操作        if(timer) {            return;        }        timer = setTimeout(function(){            // 在2秒内获取所有被选中的id,通过属性isflag判断是否被选中            for(var i = 0,ilen = checkboxs.length; i < ilen; i++) {                if(checkboxs[i].hasAttribute("isflag")) {                    var id = checkboxs[i].getAttribute("data-id");                    cache[cache.length] = id;                }            }            mainFunc(cache.join(',')); // 2秒后需要给本体函数传递所有的id            // 清空定时器            clearTimeout(timer);            timer = null;            cache = [];        },2000);    }})();var checkboxs = document.getElementsByClassName("j-input");for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) {    (function(i){        checkboxs[i].onclick = function(){            if(this.checked) {                // 给当前增加一个属性                this.setAttribute("isflag",1);            }else {                this.removeAttribute('isflag');            }            // 调用代理函数            proxyFunc(checkboxs);        }    })(i);}

理解缓存代理:

缓存代理的含义就是对第一次运行时候进行缓存,当再一次运行相同的时候,直接从缓存里面取,这样做的好处是避免重复一次运算功能,如果运算非常复杂的话,对性能很耗费,那么使用缓存对象可以提高性能;我们可以先来理解一个简单的缓存列子,就是网上常见的加法和乘法的运算。代码如下:

// 计算乘法var mult = function(){    var a = 1;    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {        a = a*arguments[i];    }    return a;};// 计算加法var plus = function(){    var a = 0;    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {        a += arguments[i];    }    return a;}// 代理函数var proxyFunc = function(fn) {    var cache = {}; // 缓存对象    return function(){        var args = Array.prototype.join.call(arguments,',');        if(args in cache) {            return cache[args]; // 使用缓存代理        }        return cache[args] = fn.apply(this,arguments);    }};var proxyMult = proxyFunc(mult);console.log(proxyMult(1,2,3,4)); // 24console.log(proxyMult(1,2,3,4)); // 缓存取 24 var proxyPlus = proxyFunc(plus);console.log(proxyPlus(1,2,3,4)); // 10console.log(proxyPlus(1,2,3,4)); // 缓存取 10

「END」

看到这里你是否对模块模式和代理模式有了一个认识,如果你有过真实的两年左右的前端开发经验,这些应该在你的工作中都有遇到吧。

一般在我们遇到懒加载和代理什么的时候,都会去找对应的应用方法,但是能深入去思考的肯定不多,在这里我们一次不贪多,每篇能有两个设计模式的讲解,篇幅不大能让你有更多的时间去领悟,去理解这些设计模式给我们带来的好处。

老规矩 点赞转发后台私信小编“资料”即可带走学习!

书籍 > Java基础:

基础视频:

javascript设计模式_详解JavaScript的常用设计模式(二)相关推荐

  1. js模板字符串自定义类名_详解JavaScript ES6中的模板字符串

    这篇文章主要介绍了详解JavaScript ES6中的模板字符串,JS的ES6版本带来诸多简洁化方面的重大改进,需要的朋友可以参考下 在 ES6 中引入了一种新的字符串字面量 - 模板字符串,除了使用 ...

  2. javascript 本地对象和内置对象_详解 JavaScript 面向对象

    1. 概述 JavaScript面向对象比较难理解的点是类的继承.不管是es5写法还是es6写法,JavaScript继承的本质是原型链.具体可看我的上一篇文章: 田浩:详解原型.原型链.构造函.实例 ...

  3. 前端中unescape是什么意思_详解JavaScript中的Unescape()和String() 函数

    JavaScript中的Unescape()和String() 函数详解,具体内容如下所示: 定义和用法 JavaScript unescape() 函数可对通过 escape() 编码的字符串进行解 ...

  4. this调用语句必须是构造函数中的第一个可执行语句_详解-JavaScript 的 this 指向和绑定...

    JavaScript 中的 new.bind.call.apply 实际这些都离不开 this,因此本文将着重讨论 this,在此过程中分别讲解其他相关知识点. 注意: 本文属于基础篇,请大神绕路.如 ...

  5. js中every用法_详解JavaScript中的every()方法

    JavaScript 数组中的每个方法测试数组中的所有元素是否经过所提供的函数来实现测试. 语法 array.every(callback[, thisObject]); 下面是参数的详细信息: ca ...

  6. 电阻参数_详解几种常用电阻参数的表示方法

    电阻是非常常见的电子元器件,前面我们介绍过常见的电阻分类,几种常见特殊电阻及电阻的检测方法,今天让我们一起来学一学电阻参数的几种常用的表示方法. 电阻的参数主要有直标法.文字符号法.色标法和数码表示法 ...

  7. mysql8 重启命令_详解MySQL8.0+常用命令

    开启远程访问 通过以下命令开启root用户远程访问权限: CREATE USER 'root'@'%' IDENTIFIED BY 'password'; GRANT ALL ON *.* TO 'r ...

  8. window 程序报错 自动重启_好程序员web前端教程之详解JavaScript严格模式

    好程序员web前端教程之详解JavaScript严格模式,严格模式(Strict mode)是由ECMA-262规范定义的新兴JavaScript标准,发布于2009年12月第五版.旨在改善错误检查功 ...

  9. javascript BOM对象详解

    javascript BOM对象详解 目标:本章节将分为9点详细介绍有关BOM对象的知识点 1.什么是BOM 2.BOM的构成 3.顶级对象window 4.window对象常见事件(页面加载事件和体 ...

  10. es6字符串添加html标签,JavaScript_详解JavaScript ES6中的模板字符串,在 ES6 中引入了一种新的字符 - phpStudy...

    详解JavaScript ES6中的模板字符串 在 ES6 中引入了一种新的字符串字面量 - 模板字符串,除了使用反引号 (`) 表示,它们看上去和普通的字符串没有什么区别.在最简单的情况下,他们就是 ...

最新文章

  1. 计算机视觉:Bag of words算法实现过程中出现错误及解决方案
  2. 资源丨机器学习进阶路上不可错过的28个视频
  3. 设计printf调试宏
  4. 符乐安:2020年短视频创作将迎来新的高峰
  5. Flask和mysql多线程_Flask解析(二):Flask-Sqlalchemy与多线程、多进程
  6. SQL 2005 的存储过程和触发器调试大法
  7. 定义css设备类型-Media Queries
  8. Enterprise Library2.0研究(一)日志组件的使用场景
  9. 计算机文化课每个人都要学吗,计算机文化基础课程
  10. *1LL在c++中的意义
  11. 线程的异常捕获与线程池的异常捕获
  12. 解决跨域form表单post提交时Forbidden的问题。
  13. 通用单目标跟踪综述《Handcrafted and Deep Trackers: A Review of Recent Object Tracking Approaches》
  14. macbook从硬盘复制文件显示“不能更改xx中的一个或多个项目,因为它们正在使用中”
  15. python 三色球问题
  16. Redmi首款超高性价比笔记本明日开售 售价3999元起
  17. A. Equalize Prices Again
  18. Hadoop2.x和3.x版本区别
  19. 音乐给人们带来了什么
  20. 用python快速分析你的微信好友

热门文章

  1. 通过cmd修改注册表并设置cmd窗口的大小
  2. 细数2011TurboMail企业邮箱功能新飞跃
  3. mysql优化之insert/delete/update
  4. ASP.NET CORE的H5上传
  5. plus初始化原理及plus is not defined,mui is not defined 错误汇总
  6. sublime text3之修改注释颜色
  7. my java note -------String 类的实例化
  8. Linux批量替换文本,文件夹内所有文本内容
  9. asp.net 获取当月的第一天和最后一天示例
  10. sql server cross/outer apply 用法