首页 前端知识 浏览器模块化详解

浏览器模块化详解

2024-08-12 10:08:43 前端知识 前端哥 797 563 我要收藏

浏览器模块化详解

ES6 ModuleES6中规定的模块体系。传统script标签的代码加载容易导致全局作用域污染。

<script>
  const a = 1;
</script>
<script>
  console.log(a)       // 1
</script>

像上面的demo,即使是不同的script,但是能互相访问。项目一大,维护起来越来越困难。

Javascript社区做了很多努力,在现有的运行环境中,实现”模块”的效果。

最原始的写法应该就是把不同的函数简单放在一起,当作一个模块。

function module1() {
  // ...
}

function module2() {
  // ...
}

这种写法其实缺点很明显,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

为了解决函数的缺点,把模块写成一个对象,所有模块成员都放入对象中。

const module = {
  counter: 0,
  module1: () => {
    // ...
  },
  module2: () => {
    // ...
  }
}

这样在调用模块的时候直接调用对象的属性就可以了,但是这样又把所有的模块成员暴露了,使得内部的状态可以被外部改写。

module.counter++;  // 外部修改couter值

使用立即执行函数就可以达到不暴露私有成员的目的了。

const module = (function() {
  let counter = 0;
  const module1 = () => {
    // ...
  }
  const module2 = () => {
    // ...
  }
  return {
    module1,
    module2
  }
})()

module.counter // undefined

主流模块

es6以前,还没有提出一套官方的规范,从社区和框架推广程度而言,目前通行的javascript模块规范有两种:CommonJSAMD

CommonJS

主要用于node服务端。

CommonJS中,暴露模块使用module.exportsexports

CommonJS中,有一个全局性方法require(),用于加载模块。

// 引入http模块
const http = require('http');

// 使用http模块中的方法
http.createServer(/*...*/);

AMD

有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

但是CommonJS不适用于浏览器环境,如果要在浏览器中使用,如果想要使用对应的模块,需要提前加载。像上方的http模块,如果像使用对应的方法,就需要等到http加载完成后才能使用。如果加载时间很长,整个应用就会停在那里等。

但是由于服务端所有的模块都存放在本地硬盘上,可以同步加载完成(硬盘读取时间很快),但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。
因此,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。

AMDAsynchronous Module Definition的缩写,采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

模块必须采用特定的define()函数来定义。

语法

define(id?, dependencies?, factory)
  • id:字符串,模块名称(可选)
  • dependencies: 是我们要载入的依赖模块(可选),使用相对路径。注意是数组格式
  • factory: 工厂方法,返回一个模块函数

如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

define(function() {
  let counter = 0;
  const add = () => {
    console.log('add')
    return counter++;
  }
  return {
    add
  }
})

如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

define(['Lib'], function(Lib){
 function foo(){
  Lib.doSomething();
 }
 return {
  foo : foo
 };
});

使用require()函数来加载上面定义的模块。

AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数。

语法:

require([module], callback);
  • module: 数组,里面的成员就是要加载的模块。
  • callback: 加载成功之后的回调函数,这个函数接收一个参数,就是definefactory的返回值。
require(['./test.js'], function(test) {
  test.add();
})

test这个文件中定义的模块的加载跟require中的回调函数不是同步执行的,并不会出现假死的状态。

所以很显然,AMD比较适合浏览器环境。

目前,主要有两个Javascript库实现了AMD规范:require.jscurl.js

如果想要使用requiredefine,需要提前引入对应的模块。
像使用require.js,需要在页面中引入。
去官网下载最新版本,直接放到页面进行加载

  <script src="js/require.js"></script>

CMD

CMD (Common Module Definition), 是seajs推崇的规范。与AMD不同的是,CMD是在需要模块的时候才使用require引入。

define的使用跟AMD一样。

require的调用时机不同。

比如我们想在某个模块中导入其他模块

define(function() {
  const xxx = require('xxx');
})

AMD就需要提前在define中说明。当然在CMD也可以这样使用。

CMDAMD区别

AMDCMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;
CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

现阶段标准

ES6标准发布后,module成为标准。就是我们熟悉的importexport语法。

标准使用是以export指令导出接口,以import引入模块,但是在node服务端中,依然采用的是CommonJS规范,使用require引入模块,使用module.exports导出接口。

export

export语法声明用于导出函数、对象、指定文件(或模块)的原始值。

注意:在node中使用的是exports,不要混淆了

export有两种模块导出方式:命名式导出(名称导出)和默认导出(定义式导出),命名式导出每个模块可以多个,而默认导出每个模块仅一个。

命名式导出

通过export前缀关键词声明导出对象,导出对象可以是多个。这些导出对象用名称进行区分,称之为命名式导出。

function module1() {
  // ...
}

// 导出一个定义的函数
export {
  module1
}

// 导出一个遍历
export const a = 1;

使用*from关键字来实现的模块的继承

export * from 'module1';

使用as关键字对导出成员进行重命名

export {a as b};
默认导出

默认导出也被称做定义式导出。命名式导出可以导出多个值,但在在import引用时,也要使用相同的名称来引用相应的值。而默认导出每个导出只有一个单一值,这个输出可以是一个函数、类或其它类型的值,这样在模块import导入时也会很容易引用。

// 可以导出一个函数
export default function() {}; 

// 也可以出一个类
export default class(){}; 

默认导出可以理解为另一种形式的命名导出,默认导出可以认为是使用了default名称的命名导出。

const a = 1;
 
export default a;
export { a as default };

import

import语法声明用于从已导出的模块、脚本中导入函数、对象、指定文件(或模块)的原始值。

import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入和默认导入。

命名式导入

通过指定名称,就是将这些成员插入到当作用域中。导出时,可以导入单个成员或多个成员。

function module1() {
  // ...
}

// 导出一个定义的函数
export {
  module1
}

// 引入的函数名需要跟导出的一致
import { module1 } from 'xxx';

通过*符号,我们可以导入模块中的全部属性和方法。当导入模块全部导出内容时,就是将导出模块所有的导出绑定内容,插入到当前模块的作用域中。

function module1() {
  // ...
}
const a = 1;

export {
  module1,
  a
}

import * as module1 from 'xxx';
// 使用
module1.a;
module1.module1

导入模块对象时,也可以使用as对导入成员重命名,以方便在当前模块内使用:

function module1() {
  // ...
}
const a = 1;

export {
  module1,
  a
}

import { a as b } from 'xxx';
console.log(b)
默认导出
const a = 1;
 
export default a;

import a from 'xxx';
console.log(a)

在script标签中使用模块

如果文件中使用importexport这些标准语法,需要在script标签中携带type="module"属性,不然会报错。

<script src="xxx.js" type="module"></script>

这样,不同的script之前就不能互相访问属性了。

像使用vue2.x时候我们发现,index.htmlscript标签为什么没有type="module"属性呢?是因为底层使用webpack,对import语法进行了处理,之后并不存在import语法了。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/15381.html
标签
评论
发布的文章

jQuery Knob 项目教程

2024-08-19 22:08:18

jquery 实现倒计时

2024-08-19 22:08:06

echarts柱状图属性

2024-08-19 22:08:57

word模版导出(echarts)ftl

2024-08-19 22:08:57

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!