We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
平时写代码的时候,知道如果导出变量,如何引入变量。可见模块化就在我们的身边,可是为什么前端会引入模块化的概念,以及为什么有同步加载和异步加载呢?
在之前的项目中,如果没有模块化的概念,很多变量都有重名或者不小心重新赋值的危险。而且用 script 有可能阻塞 HTML 的下载或者渲染,影响用户体验。
在平时编码中,我们都习惯把一些通用的方法提出来放在一个文件里,哪个地方需要用到就引用,这样能够很好的梳理页面的逻辑,维护代码的成本也降低了不少。所以模块化给我们带来的好处是显而易见的。
现有的一些模块化方案有以下几种:
下面我就自身的理解对这几种方案做一个对比和总结:
ES6 模块遇到 import 命令时,不会去执行模块,而是生成一个引用,等用到的时候,才去模块中取值。因为是动态引用,所以不存在缓存的问题。可以看一下下面的例子:
ES6
import
// util.js export let env = 'qa'; setTimeout(() => env = 'local', 1000); // main.js import {env} from './util'; console.log('env:', env); setTimeout(() => console.log('new env:', env), 1500);
执行 main.js,会输出下面的结果:
main.js
// env: qa // new env: local
可以看出 ES6 模块是动态的取值,不会缓存运行的结果。
目前浏览器尚未支持 ES6 模块 ,所以需要使用 babel 转换,大家可以在 Babel 提供的 REPL 在线编译器 中查看编译后的结果。
// es 6 import {add} from './config'; // es 5 'use strict'; var _config = require('./config');
可以看出,最后转换成 require 的方式了。ES6 模块在浏览器和服务器端都可以用,ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
require
import 命令具有提升效果,会提升到整个模块的头部,首先执行,所以不能把 import 写在表达式里面。这和 ES 6 模块的概念不符合。
ES 6
node 的模块遵循 CommonJS 规范。在服务器端,依赖是保存在本地硬盘的,所以读取的速度非常快,使用同步加载不会有什么影响。
node
CommonJS
看一下 CommonJS 的语法:
// header.js module.exports = { title: '我是柚子' }; // main.js var header = require('./header');
这里的 module 代表的是当前模块,它是一个对象,把它打印出来是下面的结果:
{ Module { id: '/Users/yanmeng/2017FE/css-animation/js/b.js', exports: { item: 'item' }, parent: Module { id: '.', exports: {}, parent: null, filename: '/Users/yanmeng/2017FE/css-animation/js/main.js', loaded: false, children: [ [Circular] ], paths: [ '/Users/yanmeng/2017FE/css-animation/js/node_modules', '/Users/yanmeng/2017FE/css-animation/node_modules', '/Users/yanmeng/2017FE/node_modules', '/Users/yanmeng/node_modules', '/Users/node_modules', '/node_modules' ] }, filename: '/Users/yanmeng/2017FE/css-animation/js/b.js', loaded: false, children: [], paths: [ '/Users/yanmeng/2017FE/css-animation/js/node_modules', '/Users/yanmeng/2017FE/css-animation/node_modules', '/Users/yanmeng/2017FE/node_modules', '/Users/yanmeng/node_modules', '/Users/node_modules', '/node_modules' ] }
之后调用这个模块的时候,就会从 exports 中取值,即使再执行,也不会再执行改模块,而是从缓存中取值,返回的是第一次运行的结果,除非手动清除缓存。
// 删除指定模块的缓存 delete require.cache[moduleName]; // 删除所有模块的缓存 Object.keys(require.cache).forEach(function(key) { delete require.cache[key]; })
缓存是根据绝对路径识别模块的,如果同一个模块放在不同的路径下,还是会重新加载这个模块。
require 命令第一次执行的时候,会加载并执行整个脚本,然后在内存中生成此脚本返回的 exports 对象。
ES6 模块是动态引用,并且不会缓存值。
ES6 模块在对脚本静态分析的时候,遇到 import 就会生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里取值,所以说 ES6 模块是动态引用。 从依赖中引入的模块变量是一个地址引用,是只读的,可以为它新增属性,可是不能重新赋值。
// lib.js export let obj = {}; // main.js import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError
// fs.js module.exports = { readfile: 'readfile' } // main.js import {readfile} from 'fs';
上面的写法不对,因为 commonjs 是运行时加载的, es 6 模块是编译时加载的,所以在编译的时候,无法确认readfile 接口。
在用 require 命令加载 es6 模块的时候,ES6 模块的所有输出接口,会成为输入对象的属性。
// es.js let foo = {bar:'my-default'}; export default foo; foo = null; // cjs.js const es_namespace = require('./es'); console.log(es_namespace.default); // {bar:'my-default'}
又称异步加载模块(Asynchronous Module Definition)
js
如果在浏览器环境,就需要在服务端加载模块,那么采用同步加载的方法就会影响用户体验,所以浏览器端一般采用 AMD 规范。
AMD
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
// lib.js define('[./util.js]', function(util){ function bar() { util.log('it is sunshine'); }; return { bar: bar }; }); // main.js require(['./lib.js'], function(lib){ console.log(lib.bar()); })
Sea.js 实现了这个规范,Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。
Sea.js
JS
define(function(require, exports, module) { var a = require('./a') a.doSomething() // ... var b = require('./b') b.doSomething() // ... })
本文只是浅显的介绍了一些模块的概念和用法,关于 ES6 模块、 CommonJs 的循环加载和 ES 6 模块和 CommonJs的互相引用,大家可以动手实践一下,会受益匪浅。
参考:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
为什么要模块化
在之前的项目中,如果没有模块化的概念,很多变量都有重名或者不小心重新赋值的危险。而且用 script 有可能阻塞 HTML 的下载或者渲染,影响用户体验。
在平时编码中,我们都习惯把一些通用的方法提出来放在一个文件里,哪个地方需要用到就引用,这样能够很好的梳理页面的逻辑,维护代码的成本也降低了不少。所以模块化给我们带来的好处是显而易见的。
现有的一些模块化方案有以下几种:
下面我就自身的理解对这几种方案做一个对比和总结:
ES6 Module
ES6
模块遇到import
命令时,不会去执行模块,而是生成一个引用,等用到的时候,才去模块中取值。因为是动态引用,所以不存在缓存的问题。可以看一下下面的例子:执行
main.js
,会输出下面的结果:可以看出
ES6
模块是动态的取值,不会缓存运行的结果。目前浏览器尚未支持
ES6
模块 ,所以需要使用 babel 转换,大家可以在 Babel 提供的 REPL 在线编译器 中查看编译后的结果。可以看出,最后转换成
require
的方式了。ES6
模块在浏览器和服务器端都可以用,ES6
模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。import
命令具有提升效果,会提升到整个模块的头部,首先执行,所以不能把import
写在表达式里面。这和ES 6
模块的概念不符合。CommonJS
node
的模块遵循CommonJS
规范。在服务器端,依赖是保存在本地硬盘的,所以读取的速度非常快,使用同步加载不会有什么影响。看一下
CommonJS
的语法:module
这里的 module 代表的是当前模块,它是一个对象,把它打印出来是下面的结果:
之后调用这个模块的时候,就会从 exports 中取值,即使再执行,也不会再执行改模块,而是从缓存中取值,返回的是第一次运行的结果,除非手动清除缓存。
缓存是根据绝对路径识别模块的,如果同一个模块放在不同的路径下,还是会重新加载这个模块。
require
require 命令第一次执行的时候,会加载并执行整个脚本,然后在内存中生成此脚本返回的 exports 对象。
ES6 模块与 CommonJS 模块的差异
ES6
模块是动态引用,并且不会缓存值。ES6
模块在对脚本静态分析的时候,遇到import
就会生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里取值,所以说ES6
模块是动态引用。从依赖中引入的模块变量是一个地址引用,是只读的,可以为它新增属性,可是不能重新赋值。
import 命令加载 CommonJS 模块
上面的写法不对,因为 commonjs 是运行时加载的, es 6 模块是编译时加载的,所以在编译的时候,无法确认readfile 接口。
require 命令加载 ES6 模块
在用 require 命令加载 es6 模块的时候,ES6 模块的所有输出接口,会成为输入对象的属性。
AMD
js
文件的异步加载,避免网页失去响应如果在浏览器环境,就需要在服务端加载模块,那么采用同步加载的方法就会影响用户体验,所以浏览器端一般采用
AMD
规范。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
CMD
Sea.js
实现了这个规范,Sea.js
遇到依赖后只会去下载JS
文件,并不会执行,而是等到所有被依赖的JS
脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。本文只是浅显的介绍了一些模块的概念和用法,关于 ES6 模块、 CommonJs 的循环加载和 ES 6 模块和 CommonJs的互相引用,大家可以动手实践一下,会受益匪浅。
参考:
The text was updated successfully, but these errors were encountered: