《微信小程序:开发入门及案例详解》—— 2.5 模块化-阿里云开发者社区

开发者社区> 华章出版社> 正文

《微信小程序:开发入门及案例详解》—— 2.5 模块化

简介: 小程序逻辑层语言是JavaScript,而JavaScript作为脚本语言在设计初期仅是为了实现简单的页面交互,由Brendan Eich在1995年花了不到十天时间发明出来,语言本身缺失了很多用于支撑大型项目的设计,而现在前端业务逻辑越来越复杂,代码也越来越多,很多问题就暴露出来。

本节书摘来自华章出版社《微信小程序:开发入门及案例详解》一 书中的第2章,第2.5节,作者李骏 边思,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.5 模块化

小程序逻辑层语言是JavaScript,而JavaScript作为脚本语言在设计初期仅是为了实现简单的页面交互,由Brendan Eich在1995年花了不到十天时间发明出来,语言本身缺失了很多用于支撑大型项目的设计,而现在前端业务逻辑越来越复杂,代码也越来越多,很多问题就暴露出来。模块化主要解决JavaScript中命名冲突和文件依赖这两个问题,现在模块化在前端中使用比较广泛,如Nodejs、Requirejs、Seajs、Webpack等,它们大部分都遵循或者接近CommonJS规范,甚至ES6也针对模块化提出了自己的规范。目前前端模块化没有一个统一的解决方案,在不同环境、不同框架中的实现都不一样,本节将重点讨论小程序的模块化规范。

2.5.1 模块化简介

最早前端JavaScript代码量不大,统一放在一个文件内,如下面一段代码:

var name = 'weixin',
    age = 12;

function getName() {
    // 实现代码
}
function getAge() {
    // 实现代码
}

后来前端代码越来越多,为了便于管理和工作拆分,我们不得不把代码拆分为多个文件,这时将上述代码封装到user.js文件中,需要用时引入页面(或打包到一个文件)就行。初期团队成员少,一切都运行正常,直到团队越来越大,开始有人抱怨:我想定义一个name变量但user.js中已经存在,我不得不定义为myName;为什么我在自己代码中定了getAge方法就导致别人代码出问题了呢?通过这种文件拆分的工作我们只是对代码做了物理上的分离,能初步实现多人开发和简单的代码管理,但并没有真正做到作用域的隔离,由于不知道其他文件内已存在的变量名,甚至让全局冲突问题变得更容易、更严重。
再后来为了避免这种全局冲突,大家决定参考Java的方式,引入命名空间和闭包来解决变量冲突问题。于是user.js里的代码变成了如下这样:

( function() {
  myProject = myProject || {}; // 定义全局命名空间
  myProject.user = {};
  myProject.user.name = 'weixin';

  var age = 12; // 闭包内变量,外部不能访问

  myProjecct.user.getName = function() {
    // 实现代码
  }
  myProject.user.getAge = function() {
    // 实现代码
  }
} )();

这样别的同事可以通过myProject.user获取name,调用getName和getAge方法,通过命名空间,的确能缓解大部分冲突,但是为此我们不得不记住很长一串命名空间,同时当我使用user这个空间后,别人就不能使用,这也不能完美地解决问题。同时更可怕的是如果user.js依赖另外一个utils.js,别的同事必须通过阅读user.js源码搞懂这层依赖关系,按顺序引入utils.js、user.js,直接引入user.js将会导致他代码出错,如果utils还依赖别的资源他还得必须搞懂相关的所有依赖,而他仅仅是想调用我的getName方法,这对调用的同事来说无疑是个噩梦。这时我们需要一种新的组织方式,于是诞生了模块化:
模块是一段JavaScript代码,具有统一的基本书写格式。
模块之间通过基本交互规则,能彼此引用,协同工作。
目前模块化的规范不统一,大致可分为CommonJS和ES6两种规范,大家有兴趣可以参考网上相关资料,小程序模块化机制比较接近CommonJS规范,无论哪种规范,学习起来都十分简单。

2.5.2 文件作用域

小程序中一个JavaScript文件就是一个模块,在这个文件中声明的变量和函数只在该文件中有效,不同文件中的相同变量名和函数名是不会互相影响的。模块中可以调用一些全局的方法,如下例中通过调用getApp()获取小程序实例:

App( {
  myGlobalData : { /* 定义全局属性 */
    name : 'weixin'
  }
} );

var myPrivatyData = "value1"; /* myPrivatyData只能在a.js中使用 */
var appData = getApp();
appData.myGlobalData.name += ' app';

var myPrivatyData = "value2"; /* myPrivatyData不会和a.js中同名变量冲突 */
var appData = getApp();
/* 当a.js在b.js前执行后,这里会输出"weixin app value2" */
console.log( appData.myGlobalData.name + ' ' + myPrivatyData ); 

2.5.3 模块的使用

模块接口的暴露和引入十分简单:
通过exports暴露接口。
通过require(path)引入依赖,path是需要引入的模块文件的相对路径。
示例代码如下:

var privateData = 'weixin';

function run( who ) {
  console.log( who + ' run' );
}
function walk( who ) {
  console.log( who + ' walk' );
}
module.exports.run = run;
exports.walk = walk;

/**
  也可以这样 
  module.exports = {
    run : run,
    walk : walk
  };
*/

var otherMod = require( 'mod.js' ); /*  */

Page( {
  onShow : function() {
    /* 这里会打印出somebody run */
    otherMod.run( 'somebody' );
    /* 这里会打印出somebody walk */
    otherMod.walk( 'somebody' );
  }
} );

需要注意的是:
exports是module.exports的一个引用,因此在模块里面随意更改exports的指向会造成未知的错误。所以我们更推荐开发者采用module.exports来暴露模块接口,除非你已经很清晰地知道这两者的关系。
小程序目前不支持直接引入node_modules,开发者需要使用node_modules时建议拷贝出相关代码到小程序目录中。
通过模块化我们能实现代码真正的隔离,可以多人并行开发,降低大型项目管理难度,这对前端工程化具有很大促进作用。

2.5.4 其他

1. JavaScript运行环境

微信小程序逻辑代码运行在三端:iOS、Android 和用于调试的开发者工具,这三端是各自不同的三个解析引擎:
在 iOS 上,小程序的 JavaScript 代码是运行在 JavaScriptCore 中。
在 Android 上,小程序的 JavaScript 代码是通过 X5 内核来解析。
在 开发工具上,小程序的 JavaScript 代码是运行在 nwjs(chrome内核) 中。
虽然尽管三端的环境十分相似,但是至少在目前对一些语法、特性的支持还是有一些区别,在开发过程中要尽可能地在三端进行测试。

2. ES6语法以及API支持

在小程序中,开发者可以使用ES6语法进行编码,在 0.10.101000 以及之后版本的开发工具中,会默认使用babel将开发者ES6代码转换为三端都能很好支持的ES5的代码,帮助开发者解决环境不同所带来的开发问题。如果没有使用ES6语法,开发者可以在项目设置中关闭这个功能。
转化过程中需要注意:
这种转换只会帮助开发者处理语法上问题,新的ES6的 API,例如Promise等需要开发者自行引入Polyfill或者别的类库。
为了提高代码质量,在开启ES6转换功能的情况下,默认启用JavaScript严格模式,请参考“use strict”。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接