全面解析JavaScript Module模式


Posted in Javascript onJuly 24, 2020

简介

Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级使用方式。

首先我们来看看Module模式的基本特征:

  1. 模块化,可重用
  2. 封装了变量和function,和全局的namaspace不接触,松耦合
  3. 只暴露可用public的方法,其它私有方法全部隐藏

关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节)。

基本用法

先看一下最简单的一个实现,代码如下:

var Calculator = function (eq) {
  //这里可以声明私有成员

  var eqCtl = document.getElementById(eq);

  return {
    // 暴露公开的成员
    add: function (x, y) {
      var val = x + y;
      eqCtl.innerHTML = val;
    }
  };
};

我们可以通过如下的方式来调用:

var calculator = new Calculator('eq');
calculator.add(2, 2);

大家可能看到了,每次用的时候都要new一下,也就是说每个实例在内存里都是一份copy,如果你不需要传参数或者没有一些特殊苛刻的要求的话,我们可以在最后一个}后面加上一个括号,来达到自执行的目的,这样该实例在内存中只会存在一份copy,不过在展示他的优点之前,我们还是先来看看这个模式的基本使用方法吧。

匿名闭包

匿名闭包是让一切成为可能的基础,而这也是JavaScript最好的特性,我们来创建一个最简单的闭包函数,函数内部的代码一直存在于闭包内,在整个运行周期内,该闭包都保证了内部的代码处于私有状态。

(function () {
  // ... 所有的变量和function都在这里声明,并且作用域也只能在这个匿名闭包里
  // ...但是这里的代码依然可以访问外部全局的对象
}());

注意,匿名函数后面的括号,这是JavaScript语言所要求的,因为如果你不声明的话,JavaScript解释器默认是声明一个function函数,有括号,就是创建一个函数表达式,也就是自执行,用的时候不用和上面那样在new了,当然你也可以这样来声明:

(function () {/* 内部代码 */})();

不过我们推荐使用第一种方式,关于函数自执行,我后面会有专门一篇文章进行详解,这里就不多说了。

引用全局变量

JavaScript有一个特性叫做隐式全局变量,不管一个变量有没有用过,JavaScript解释器反向遍历作用域链来查找整个变量的var声明,如果没有找到var,解释器则假定该变量是全局变量,如果该变量用于了赋值操作的话,之前如果不存在的话,解释器则会自动创建它,这就是说在匿名闭包里使用或创建全局变量非常容易,不过比较困难的是,代码比较难管理,尤其是阅读代码的人看着很多区分哪些变量是全局的,哪些是局部的。

不过,好在在匿名函数里我们可以提供一个比较简单的替代方案,我们可以将全局变量当成一个参数传入到匿名函数然后使用,相比隐式全局变量,它又清晰又快,我们来看一个例子:

(function ($, YAHOO) {
  // 这里,我们的代码就可以使用全局的jQuery对象了,YAHOO也是一样
} (jQuery, YAHOO));

现在很多类库里都有这种使用方式,比如jQuery源码。

不过,有时候可能不仅仅要使用全局变量,而是也想声明全局变量,如何做呢?我们可以通过匿名函数的返回值来返回这个全局变量,这也就是一个基本的Module模式,来看一个完整的代码:

var blogModule = (function () {
  var my = {}, privateName = "博客园";

  function privateAddTopic(data) {
    // 这里是内部处理代码
  }

  my.Name = privateName;
  my.AddTopic = function (data) {
    privateAddTopic(data);
  };

  return my;
} ());

上面的代码声明了一个全局变量blogModule,并且带有2个可访问的属性:blogModule.AddTopic和blogModule.Name,除此之外,其它代码都在匿名函数的闭包里保持着私有状态。同时根据上面传入全局变量的例子,我们也可以很方便地传入其它的全局变量。

高级用法

上面的内容对大多数用户已经很足够了,但我们还可以基于此模式延伸出更强大,易于扩展的结构,让我们一个一个来看。

扩展

Module模式的一个限制就是所有的代码都要写在一个文件,但是在一些大型项目里,将一个功能分离成多个文件是非常重要的,因为可以多人合作易于开发。再回头看看上面的全局参数导入例子,我们能否把blogModule自身传进去呢?答案是肯定的,我们先将blogModule传进去,添加一个函数属性,然后再返回就达到了我们所说的目的,上代码:

var blogModule = (function (my) {
  my.AddPhoto = function () {
    //添加内部代码 
  };
  return my;
} (blogModule));

这段代码,看起来是不是有C#里扩展方法的感觉?有点类似,但本质不一样哦。同时尽管var不是必须的,但为了确保一致,我们再次使用了它,代码执行以后,blogModule下的AddPhoto就可以使用了,同时匿名函数内部的代码也依然保证了私密性和内部状态。

松耦合扩展

上面的代码尽管可以执行,但是必须先声明blogModule,然后再执行上面的扩展代码,也就是说步骤不能乱,怎么解决这个问题呢?我们来回想一下,我们平时声明变量的都是都是这样的:

var cnblogs = cnblogs || {} ;

这是确保cnblogs对象,在存在的时候直接用,不存在的时候直接赋值为{},我们来看看如何利用这个特性来实现Module模式的任意加载顺序:

var blogModule = (function (my) {

  // 添加一些功能 
  
  return my;
} (blogModule || {}));

通过这样的代码,每个单独分离的文件都保证这个结构,那么我们就可以实现任意顺序的加载,所以,这个时候的var就是必须要声明的,因为不声明,其它文件读取不到哦。

紧耦合扩展

虽然松耦合扩展很牛叉了,但是可能也会存在一些限制,比如你没办法重写你的一些属性或者函数,也不能在初始化的时候就是用Module的属性。紧耦合扩展限制了加载顺序,但是提供了我们重载的机会,看如下例子:

var blogModule = (function (my) {
  var oldAddPhotoMethod = my.AddPhoto;

  my.AddPhoto = function () {
    // 重载方法,依然可通过oldAddPhotoMethod调用旧的方法
  };

  return my;
} (blogModule));

通过这种方式,我们达到了重载的目的,当然如果你想在继续在内部使用原有的属性,你可以调用oldAddPhotoMethod来用。

克隆与继承

var blogModule = (function (old) {
  var my = {},
    key;

  for (key in old) {
    if (old.hasOwnProperty(key)) {
      my[key] = old[key];
    }
  }

  var oldAddPhotoMethod = old.AddPhoto;
  my.AddPhoto = function () {
    // 克隆以后,进行了重写,当然也可以继续调用oldAddPhotoMethod
  };

  return my;
} (blogModule));

这种方式灵活是灵活,但是也需要花费灵活的代价,其实该对象的属性对象或function根本没有被复制,只是对同一个对象多了一种引用而已,所以如果老对象去改变它,那克隆以后的对象所拥有的属性或function函数也会被改变,解决这个问题,我们就得是用递归,但递归对function函数的赋值也不好用,所以我们在递归的时候eval相应的function。不管怎么样,我还是把这一个方式放在这个帖子里了,大家使用的时候注意一下就行了。

跨文件共享私有对象

通过上面的例子,我们知道,如果一个module分割到多个文件的话,每个文件需要保证一样的结构,也就是说每个文件匿名函数里的私有对象都不能交叉访问,那如果我们非要使用,那怎么办呢? 我们先看一段代码:

var blogModule = (function (my) {
  var _private = my._private = my._private || {},

    _seal = my._seal = my._seal || function () {
      delete my._private;
      delete my._seal;
      delete my._unseal;

    },

    _unseal = my._unseal = my._unseal || function () {
      my._private = _private;
      my._seal = _seal;
      my._unseal = _unseal;
    };

  return my;
} (blogModule || {}));

任何文件都可以对他们的局部变量_private设属性,并且设置对其他的文件也立即生效。一旦这个模块加载结束,应用会调用 blogModule._seal()"上锁",这会阻止外部接入内部的_private。如果这个模块需要再次增生,应用的生命周期内,任何文件都可以调用_unseal() ”开锁”,然后再加载新文件。加载后再次调用 _seal()”上锁”。

子模块

最后一个也是最简单的使用方式,那就是创建子模块

blogModule.CommentSubModule = (function () {
  var my = {};
  // ...

  return my;
} ());

尽管非常简单,我还是把它放进来了,因为我想说明的是子模块也具有一般模块所有的高级使用方式,也就是说你可以对任意子模块再次使用上面的一些应用方法。

总结

上面的大部分方式都可以互相组合使用的,一般来说如果要设计系统,可能会用到松耦合扩展,私有状态和子模块这样的方式。另外,我这里没有提到性能问题,但我认为Module模式效率高,代码少,加载速度快。使用松耦合扩展允许并行加载,这更可以提升下载速度。不过初始化时间可能要慢一些,但是为了使用好的模式,这是值得的。

以上就是全面解析JavaScript Module模式的详细内容,更多关于JavaScript Module模式的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
juqery 学习之四 筛选查找
Nov 30 Javascript
javascript面向对象之二 命名空间
Feb 08 Javascript
JAVASCRIPT代码编写俄罗斯方块网页版
Nov 26 Javascript
对象题目的一个坑 理解Javascript对象
Dec 22 Javascript
Javascript实现的SHA-256加密算法完整实例
Feb 02 Javascript
20分钟轻松创建自己的Bootstrap站点
May 12 Javascript
jQuery给div,Span, a ,button, radio 赋值与取值
Jun 24 Javascript
微信小程序的部署方法步骤
Sep 04 Javascript
推荐15个最好用的JavaScript代码压缩工具
Feb 13 Javascript
基于javascript实现贪吃蛇经典小游戏
Apr 10 Javascript
小程序自动化测试的示例代码
Aug 11 Javascript
VUE : vue-cli中去掉路由中的井号#操作
Sep 04 Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
Jul 24 #Javascript
在Vuex中Mutations修改状态操作
Jul 24 #Javascript
Vue自动构建发布脚本的方法示例
Jul 24 #Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
Jul 24 #Javascript
vue data对象重新赋值无效(未更改)的解决方式
Jul 24 #Javascript
VUE项目axios请求头更改Content-Type操作
Jul 24 #Javascript
vue+axios全局添加请求头和参数操作
Jul 24 #Javascript
You might like
php学习 函数 课件
2008/06/15 PHP
PH P5.2至5.5、5.6的新增功能详解
2014/07/14 PHP
Yii框架 session 数据库存储操作方法示例
2019/11/18 PHP
JavaScript CSS 修改学习第四章 透明度设置
2010/02/19 Javascript
jQuery ReferenceError: $ is not defined 错误的处理办法
2013/05/10 Javascript
jquery中邮箱地址 URL网站地址正则验证实例代码
2013/09/15 Javascript
javascript 构造函数方式定义对象
2015/01/02 Javascript
JavaScript中的方法重载实例
2015/03/16 Javascript
jQuery验证插件validate使用详解
2016/05/11 Javascript
Bootstrap实现下拉菜单多级联动
2017/11/23 Javascript
Vue登录注册并保持登录状态的方法
2018/08/17 Javascript
关于vue v-for 循环问题(一行显示四个,每一行的最右边那个计算属性)
2018/09/04 Javascript
vue组件中的样式属性scoped实例详解
2018/10/30 Javascript
vue2路由基本用法实例分析
2020/03/06 Javascript
前端 javascript 实现文件下载的示例
2020/11/24 Javascript
Python pickle模块用法实例分析
2015/05/27 Python
Python File readlines() 使用方法
2018/03/19 Python
Django后台admin的使用详解
2019/07/08 Python
Python多继承以及MRO顺序的使用
2019/11/11 Python
解决pip安装的第三方包在PyCharm无法导入的问题
2020/10/15 Python
新秀丽拉杆箱美国官方网站:Samsonite美国
2016/07/25 全球购物
东方电视购物:东方CJ
2016/10/12 全球购物
水上运动奥特莱斯:Wasterports Outlet
2018/08/08 全球购物
英国第一摩托车和摩托车越野配件商店:GhostBikes
2019/03/10 全球购物
美国渔具店:FishUSA
2019/08/07 全球购物
WEB控件可以激发服务端事件,请谈谈服务端事件是怎么发生并解释其原理?自动传回是什么?为什么要使用自动传回?
2012/02/21 面试题
大一新生学期自我评价
2014/04/09 职场文书
企业文化口号
2014/06/12 职场文书
幼儿园标语大全
2014/06/19 职场文书
ktv周年庆活动方案
2014/08/18 职场文书
机关干部作风建设剖析材料
2014/10/23 职场文书
2015年乡镇党务公开工作总结
2015/05/19 职场文书
tensorflow学习笔记之tfrecord文件的生成与读取
2021/03/31 Python
详解Apache SkyWalking 告警配置指南
2021/04/22 Servers
纯html+css实现奥运五环的示例代码
2021/08/02 HTML / CSS
git中cherry-pick命令的使用教程
2022/06/25 Servers