JavaScript(4)之——前端模块化

栏目: JavaScript · 发布时间: 5年前

内容简介:谈谈对前端模块化的理解是面试时经常会被问到的问题,我以面试者的口吻来写了如何分步骤回答这道问题。将一个复杂程序安装一定的规则封装成几个块儿,并组合在一起。块的内部,数据和函数实现是私有的,只像外部暴露出来一些接口与外部的其他模块通信。把不同功能封装成不同的全局函数,污染全局命名空间,容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系。

  谈谈对前端模块化的理解是面试时经常会被问到的问题,我以面试者的口吻来写了如何分步骤回答这道问题。

第一步:模块化是什么?

  将一个复杂程序安装一定的规则封装成几个块儿,并组合在一起。块的内部,数据和函数实现是私有的,只像外部暴露出来一些接口与外部的其他模块通信。

第二步:模块化的发展

全局function模式

  把不同功能封装成不同的全局函数,污染全局命名空间,容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系。

namespace模式(命名空间)

  简单对象封装,减少了全局变量,解决命名冲突,但是数据不安全,外部可以直接修改模块内部的数据。

IIFE模式:匿名函数自调用(闭包)

  数据是私有的,外部只能通过暴露的方法操作,将数据和行为封装到一个函数内部,通过给window添加属性来向外暴露接口,但是如何解决模块依赖呢?

(function(window) {
    let data = "hello world"

    function sayHi() {
        console.log(data)
    }
    window.myModule = { sayHi }
})(window)

myModule.sayHi()

复制代码

IIFE增强模式:引入依赖

(function(global) {
    const name = 'David'
    global.myModule2 = { name }
})(global);

(function(global, myModule2) {
    let data = "hello world"

    function sayHi() {
        console.log(`${myModule2.name} said: "${data}"`)
    }
    global.myModule = { sayHi }
})(global, myModule2)

myModule.sayHi()
复制代码

存在的问题

  1. 每个模块都需要手动引入,引入过多的script,就会发送过多的请求。
  2. 依赖模糊,很难说清楚具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。
  3. 前两点问题导致难以维护多个引入的js文件。
  4. 因此需要一个好的模块化规范来约束我们实现模块化的方式。

第三步 模块化的好处

  1. 避免命名冲突。
  2. 更好的分离,按需加载。
  3. 更高的复用性。
  4. 高可维护性。

第四步 模块化规范

  现在AMD和CMD已经逐渐退出历史舞台,我们主要介绍常用的两种规范:CommonJS和ES6模块化。

CommonJS

  Node应用中的规范,每个文件就是一个模块,有自己的作用域。在文件中定义的变量,函数和类都是私有的,对其他文件是不可见的。其他文件只能引用它暴露的接口。在服务端,模块的加载是运行时同步加载的。

  特点:所有代码都在其模块作用域不会污染全局,执行的顺序是模块出现的顺序。模块多次加载只会在第一次加载时运行一次,然后缓存。

  基本语法:暴露模块:module.exports = xxx; exports.xxx = value。引入模块:require(xxx)。

  1. 匿名导出:
module.exports = function() {
    console.log('hello world')
}
let sayHi = require('./test')
sayHi()
复制代码
module.exports = 1
const num = require('./test')
console.log(num)
复制代码
  1. 具名导出:
let sayHi = function() {
     console.log('hello world')
 }
 let num = 5
 module.exports = {
     sayHi: sayHi,
     num: num
 }  
 
let { sayHi, num } = require('./test')
sayHi()
console.log(num)
复制代码

  模块的加载机制:输入的是输出值的拷贝,一旦输出一个值,模块内部的变化影响不到已经输出的值。因为它只运行一次,之后都都用的是缓存中的值。

ES6模块化

  ES6 模块的设计思想是尽量的静态化(编译时加载),使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

  特点:一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。export语句输出的接口与其对应的值是动态绑定关系,即通过该接口可以去到模块内部实时的值。

  基本语法:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

  1. 具名导出的两种方式
export const firtsName = 'Wang'
export const secondName = 'Lin'

const firtsName = 'Wang'
const secondName = 'Lin'
export { firtsName, secondName }
复制代码
import { firtsName, secondName } from './test.js'
console.log(firtsName + secondName)
复制代码

  特别注意,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

export 1 //报错
var m = 1; export m;//报错
//正确写法:
export var m = 1;
var m = 1; export {m}
var m = 1; export {n as m}
复制代码
  1. 匿名导出:export default

  使用import命令时用户需要知道多要加载的变量名和函数名,否则无法加载。可以用export default命令为模块指定默认输出。在import时就可以为该匿名函数指定任意名字。

export default function() {
    console.log('hi')
}

import sayHi from './test.js'
sayHi()
复制代码

  一个模块只能有一个默认输出,因此export default就是输出一个叫做defalut的变量或方法。它只能使用一次,所以import命令后面才不用加大括号。

  es6模块在浏览器中的加载规则, <script type=”module” src=”myModule.js”></script> 加一个type属性设为module,浏览器就会认为它是es6模块,默认它是异步加载,等同于打开了defer属性。

  执行机制:遇到模块加载命令import就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用到被加载的模块中取值。ES6模块是动态引用,并不会缓存值,模块里面的变量绑定其所在的模块。由于ES6输入的模块变量只是一个符号链接,所以这个变量是只读的,对它重新赋值会报错。

第五步 commonJs和es6模块化的区别

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第六步 循环加载

  循环加载指的是,a脚本的执行依赖于b脚本,而b脚本的执行又依赖于a脚本。循环加载表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行。ES6模块和CommonJS模块在处理循环加载时的方法是不一样的,返回的结果也不一样。

CommonJS模块的加载原理和循环加载

  CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。因此它的如下方法:

let { sayHi, num } = require('./test')
复制代码

  等同于:

let test = require('./test')
let sayHi = test.sayHi
let num = test.num
复制代码

  它生成一个对象之后,以后每次用到这个模块就会在这个对象中取值。无论CommonJS模块记载多少次,只有第一次加载时会运行一次,以后再加载时返回第一次运行的结果。除非手动清除缓存。

  因为脚本代码在require的时候就会全部执行,一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出。当它循环加载完成,两个脚本都会被全部执行一遍,因此循环加载的模块中的值可能会被改写。

ES6模块循环加载

  因为ES6的模块是动态引用,变量不会被缓存,而是成为一个指向被加载模块的引用。只要引用存在,代码就能执行。Import时不会直接执行它引用的模块,而是遇到它所引用的某个方法时,再去对应模块中执行该方法。而CommonJS中require时就会直接加载引用的模块,能够用到的只有已经执行的部分,如果用到还没有执行的部分就会报错。

参考: github.com/ljianshu/Bl… 以及阮一峰《ES6标准入门》


以上所述就是小编给大家介绍的《JavaScript(4)之——前端模块化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Windows高级调试

Windows高级调试

Mario Hewardt、Daniel Pravat / 聂雪军 / 机械工业出版社 / 2009-5 / 79.00元

本书主要讲解Windows高级调试思想和工具,并涉及一些高级调试主题。本书内容主要包括:工具简介、调试器简介、调试器揭密、符号文件与源文件的管理、栈内存破坏、堆内存破坏、安全、进程间通信、资源泄漏、同步、编写定制的调试扩展、64位调试、事后调试、Windows Vista基础以及应用程序验证器的测试设置等。本书内容详实、条理清楚。 本书适合Windows开发人员、Windows测试人员和Windo......一起来看看 《Windows高级调试》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具