安装nodejs
官网地址:https://nodejs.org/zh-cn/
浏览器运行的是html页面,并加载页面中通过script元素引入的js
nodejs
nodejs直接运行某个js文件,该文件被称之为入口文件
nodejs遵循EcmaScript标准,但由于脱离了浏览器环境,因此:
你可以在nodejs中使用EcmaScript标准的任何语法或api,例如:循环、判断、数组、对象等
你不能在nodejs中使用浏览器的 web api,例如:dom对象、window对象、document对象等
由于大部分开发者是从浏览器端开发转向nodejs开发的,为了降低开发者的学习成本,nodejs中提供了一些和浏览器web api同样的对象或函数,例如:console、setTimeout、setInterval等
在nodejs中,由于有且仅有一个入口文件(启动文件),而开发一个应用肯定会涉及到多个文件配合,因此,nodejs对模块化的需求比浏览器端要大的多
由于nodejs刚刚发布的时候,前端没有统一的、官方的模块化规范,因此,它选择使用社区提供的CommonJS作为模块化规范
在学习CommonJS之前,首先认识两个重要的概念:模块的导出和模块的导入
要理解模块的导出,首先要理解模块的含义
什么是模块?
模块就是一个JS文件,它实现了一部分功能,并隐藏自己的内部实现,同时提供了一些接口供其他模块使用
模块有两个核心要素:隐藏和暴露
隐藏的,是自己内部的实现
暴露的,是希望外部使用的接口
任何一个正常的模块化标准,都应该默认隐藏模块中的所有实现,而通过一些语法或api调用来暴露接口
暴露接口的过程即模块的导出
当需要使用一个模块时,使用的是该模块暴露的部分(导出的部分),隐藏的部分是永远无法使用的。
当通过某种语法或api去使用一个模块时,这个过程叫做模块的导入
CommonJS规范
CommonJS使用exports导出模块,require导入模块
具体规范如下:
如果一个JS文件中存在exports或require,该JS文件是一个模块
模块内的所有代码均为隐藏代码,包括全局变量、全局函数,这些全局的内容均不应该对全局变量造成任何污染
如果一个模块需要暴露一些API提供给外部使用,需要通过exports导出,exports是一个空的对象,你可以为该对象添加任何需要导出的内容
如果一个模块需要导入其他模块,通过require实现,require是一个函数,传入模块的路径即可返回该模块导出的整个内容
nodejs对CommonJS的实现
为了实现CommonJS规范,nodejs对模块做出了以下处理
为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块
为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
(function(){
//模块中的代码
})()
为了保证顺利的导出模块内容,nodejs做了以下处理
在模块开始执行前,初始化一个值module.exports = {}
module.exports即模块的导出值
为了方便开发者便捷的导出,nodejs在初始化完module.exports后,又声明了一个变量exports = module.exports
(function(module){
module.exports = {};
var exports = module.exports;
//模块中的代码
return module.exports;
})()
为了避免反复加载同一个模块,nodejs默认开启了模块缓存,如果加载的模块已经被加载过了,则会自动使用之前的导出结果
CommonJS的工作原理
当使用require(模块路径)导入一个模块时,node会做以下两件事情(不考虑模块缓存):
通过模块路径找到本机文件,并读取文件内容
将文件中的代码放入到一个函数环境中执行,并将执行后module.exports的值作为require函数的返回结果
正是这两个步骤,使得CommonJS在node端可以良好的被支持
可以认为,CommonJS是同步的,必须要等到加载完文件并执行完代码后才能继续向后执行
当浏览器遇到CommonJS
当想要把CommonJS放到浏览器端时,就遇到了一些挑战
浏览器要加载JS文件,需要远程从服务器读取,而网络传输的效率远远低于node环境中读取本地文件的效率。由于CommonJS是同步的,这会极大的降低运行性能
如果需要读取JS文件内容并把它放入到一个环境中执行,需要浏览器厂商的支持,可是浏览器厂商不愿意提供支持,最大的原因是CommonJS属于社区标准,并非官方标准
新的规范
基于以上两点原因,浏览器无法支持模块化
可这并不代表模块化不能在浏览器中实现
要在浏览器中实现模块化,只要能解决上面的两个问题就行了
解决办法其实很简单:
远程加载JS浪费了时间?做成异步即可,加载完成后调用一个回调就行了
模块中的代码需要放置到函数中执行?编写模块时,直接放函数中就行了
基于这种简单有效的思路,出现了AMD和CMD规范,有效的解决了浏览器模块化的问题。
全称是Asynchronous Module Definition,即异步模块加载机制
require.js实现了AMD规范
在AMD中,导入和导出模块的代码,都必须放置在define函数中
define([依赖的模块列表], function(模块名称列表){
//模块内部的代码
return 导出的内容
})
全称是Common Module Definition,公共模块定义规范
sea.js实现了CMD规范
在CMD中,导入和导出模块的代码,都必须放置在define函数中
define(function(require, exports, module){
//模块内部的代码
})
基本导入导出
模块的引入
注意:这一部分非模块化标准
目前,浏览器使用以下方式引入一个ES6模块文件
<script src="入口文件" type="module">
ES6中的模块导入导出分为两种:
基本导入导出
默认导入导出
基本导出
类似于 exports.xxx = xxxx
基本导出可以有多个,每个必须有名称
基本导出的语法如下:
export 声明表达式或export {具名符号}
由于基本导出必须具有名称,所以要求导出内容必须跟上声明表达式或具名符号
基本导入
由于使用的是依赖预加载,因此,导入任何其他模块,导入代码必须放置到所有代码之前
对于基本导出,如果要进行导入,使用下面的代码
import {导入的符号列表} from “模块路径”
注意以下细节:
导入时,可以通过关键字as对导入的符号进行重命名
导入时使用的符号是常量,不可修改
可以使用*号导入所有的基本导出,形成一个对象
默认导出
每个模块,除了允许有多个基本导出之外,还允许有一个默认导出
默认导出类似于CommonJS中的module.exports,由于只有一个,因此无需具名
具体的语法是
export default 默认导出的数据或export {默认导出的数据 as default}
由于每个模块仅允许有一个默认导出,因此,每个模块不能出现多个默认导出语句
默认导入
需要想要导入一个模块的默认导出,需要使用下面的语法
import 接收变量名 from “模块路径”
类似于CommonJS中的
var 接收变量名 = require("模块路径")
由于默认导入时变量名是自行定义的,因此没有别名一说
如果希望同时导入某个模块的默认导出和基本导出,可以使用下面的语法
import 接收默认导出的变量, {接收基本导出的变量} from "模块路径"
注:如果使用*号,会将所有基本导出和默认导出聚合到一个对象中,默认导出会作为属性default存在