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

    xiaoxiao2024-10-12  102

    本节书摘来自华章出版社《微信小程序:开发入门及案例详解》一 书中的第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”。

    最新回复(0)