文章目录
- JavaScript模块化与作用域
-
- 作用域和作用域链 - 静态
- 执行上下文 - 动态
-
- 执行上下文的两个阶段: 创建阶段和执行阶段
-
- 1.创建阶段
- 2.执行阶段
- 模块化
-
- ES6 模块化
-
- export与import的使用
- import()函数 - 实现动态引入
- Node模块化
-
- module.exports 和 exports
- 模块执行的原理
- 模块加载过程
- commonJs和es module的区别
JavaScript模块化与作用域
作用域和作用域链 - 静态
- 作用域:一个代码段所在区域
- 核心:作用域是静态的,编写代码时就确定
- 作用:绑定变量在这个作用域有效,隔离变量,不同作用域下同名变量不会有冲突
- 作用域链:多个作用域嵌套,就近选择,先在自己作用域找,然后去就近的作用域找。
作用域分类
1.全局作用域
2.函数作用域
3.块级作用域
全局作用域可以理解为
案例
aaa执行时,现在当前函数作用域找a(l理解为aaa.a),没找到。去父级作用域window里面找,找到a=10
let a = 10;
function aaa() {
console.log(a);
}
function bbb() {
let a = 20;
aaa();
}
bbb();
执行上下文 - 动态
抽象当前
全局执行上下文:在执行全局代码前将window确定为全局执行上下文,在整个页面生存周期内存在。
- let定义的全局变量变量提升,添加为window的属性
- function声明的全局函数 --> 赋值(函数体),添加为window的方法
- this --> 赋值window
- 开始执行全局代码
- 函数执行上下文:在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
函数执行上下文,每当调用一个函数时,都会创建一个新的函数执行上下文,函数执行上下文在函数执行结束后被销毁。
- 形参变量 --> 赋值(实参)–> 添加到函数执行上下文的属性
- arguments(形参列表封装成的伪数组)–>赋值(实参列表),添加到函数执行上下文的属性
- let 变量提升,添加为函数执行上下文的属性
- function声明的函数–>赋值(函数体),添加为函数执行上下文的方法
- this–>赋值(调用函数的对象)
- 开始执行函数体代码
执行上下文的两个阶段: 创建阶段和执行阶段
1.在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中(压栈)
4.在当前函数执行完成后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window
| 作用域 | 执行上下文 |
|---|---|
| 定义了几个函数 + 1 = 几个作用域 | 执行了几个函数 + 1 = 几个执行上下文 |
| 函数定义时就确定了,一直存在,不会再变化,是静态的 | 全局执行上下文环境实在全局作用域确定之后,js代码执行之前创建的 调用函数时创建,函数调用结束被释放,是动态的 |

1.创建阶段
1.创建变量对象:根据上下文类型创建一个空的对象
2.建立作用域链:作用域链是一个指向父级作用域的链,用于查找变量的值。
3.确定this的指向
4.初始化变量对象:将函数的参数(仅函数执行上下文?)、函数声明和变量添加到变量对象中。
2.执行阶段
1.执行代码:按照代码的顺序执行,对变量赋值等操作。
2.访问变量:通过作用域链查找变量的值。
3.执行函数:在函数上下文中,执行函数体内的代码。
模块化
模块的概念:一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。
CommonJS 服务于服务器 => NodeJS、BrowserifyES6 模块化 服务于服务器和浏览器
ES6 模块化
ES6模块化的设计思想使尽量静态化,使得编译时就能确定模块的依赖关系,以及输入输出的变量。但是也支持动态引入的方式。
特点
-
ES6 module 的引入和导出是静态的,import 会自动提升到代码的顶层import ,export 不能放在块级作用域或条件语句中。import 关键字是静态导入,import() 函数可以实现动态导入。
这种静态语法,在编译过程中确定了导入和导出的关系,所以更方便去查找依赖,更方便去 tree shaking 。 -
使用
import 导入的变量是只读的,可以理解默认为const 装饰,无法被赋值 -
使用
import 导入的变量是与原变量绑定/引用的,可以理解为 import 导入的变量无论是否为基本类型都是引用传递。 -
import 导入文件时或仅导入文件的部分变量时,都会执行该文件。 -
import 导入相对路径上的./ 不能省略
export与import的使用
前提:
默认暴露export default
export default 表达式 import 给抛出的表达式命名 from '地址'
分别暴露于统一暴露export
// 分别暴露
export 变量
export const name = "ranan";
export const fn = function(){};
import { fn } from '地址'
// 统一暴露
let name = "ranan";
let fun = function(){};
//注意{}不是对象的意思是特定的语法,并给name重命名
export {name1:name,fun};
import { name } from '地址'
import引入
可以通过
//通用引入 将所有输出的变量放在m1对象中,采用m1.xx的方式使用
import * as m1 from './xxx'
/*
统一暴露或分别暴露,这里的{}也是特定语法
并且{ }内的变量名称需要和export导出的变量名称相同
*/
import {name,fun} from './xxx'
//默认暴露的写法,必须要写别名,因为default是关键字
import {default as data} from "path"
//简便形式 针对默认暴露
import data from "path"
说明
-
export不支持直接导出变量和值
变量只存在声明时,声明之后变量都会作为表达式使用。错误写法:
// 错误案例 const name = 'aaa' export name // 导出变量 export 'aaa' // 导出值
-
在一个文件或模块中
export 、import 可以有多个,export default 仅有一个
import()函数 - 实现动态引入
语法:
返回值:
作用:懒加载/按需加载 ->
if(isRequire){
const result = import('./b')
}
Node模块化
Node 是
特点
- 在
commonJs 中每个js文件都是一个单独的模块module - 使用
require() 方法加载其他模块时,会执行被加载模块中的代码 module.exports 指向暴露的对象,exports 是简写情况。默认情况下,exports 和module.exports 指向同一个对象exports==module.exports 。最终引入的是module.exports 。require() 可以在任意的位置,动态加载(运行时加载)模块,不会提升到最开头- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
- CommonJS 同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖。采用深度优先算法。
module.exports 和 exports
在具体引擎的实现中
module.exports = {}
exports = module.exports
所以使用
引入的是module.exports属性
情况1:
exports = {
name: '123'
}
情况2:
exports.name = "123"
module.exports = {}
模块执行的原理
1.在编译过程中,commonJS对js的代码块进行了收尾包装
每个模块文件上存在
2.在模块加载的时候,会传入
const sayName = require('./hello.js')
module.exports = function say(){
return {
name:sayName(),
author:'ranan'
}
}
//包装后
(function(exports,require,module,__filename,__dirname){
const sayName = require('./hello.js')
module.exports = function say(){
return {
name:sayName(),
author:'ranan'
}
}
})
作用域案例
执行
在本案例中
//test2.js
const name = "alex"
const getInfo = () =>(info={name: name})
module.exports = getInfo
//test3.js
const name = 'ranran'
const info = require('./test2');
module.exports = info
//test1.js
const fn = require('./test3');
console.log(fn())
模块加载过程
- 使用
require() 方法加载其他模块时,会执行被加载模块中的代码。 require() 可以在任意的位置,动态加载(运行时加载)模块,不会提升到最开头
每个
/*
根据文件标识符,先查找有没有缓存,有缓存直接返回缓存的内容
没有缓存,创建module对象,将其缓存到module上,然后加载文件,将 loaded 属性设置为 true ,然后返回 module.exports 对象。
*/
// id 为路径标识符
function require(id) {
/* 查找 Module 上有没有已经加载的 js 对象*/
const cachedModule = Module._cache[id]
/* 如果已经加载了那么直接取走缓存的 exports 对象 */
if(cachedModule){
return cachedModule.exports
}
/* 创建当前模块的 module */
const module = { exports: {} ,loaded: false , ...}
/* 将 module 缓存到 Module 的缓存属性中,路径标识符作为 id */
//只会在第一次加载时运行一次,后面都会从缓存中读取,
Module._cache[id] = module
/* 加载文件 */
runInThisContext(wrapper('module.exports = "123"'))(module.exports, require, module, __filename, __dirname)
/* 加载完成 *//
module.loaded = true
/* 返回值 */
return module.exports
}
commonJs和es module的区别
| - | commonJs | es module |
|---|---|---|
| 导入方式 | 导入方式分为静态导入和动态导入 静态导入:不能放在块级作用域和条件中,会提升到最前面 动态导入 |
|
| 构建模块依赖的时期 | require同步加载并执行模块文件,CommonJS 模块在执行阶段分析模块依赖。 | 在编译阶段就建立起了模块之间的依赖关系 ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块 |
| 输出的值 | 输出值的拷贝值,一旦输出了某个值,如果模块内部发生变化,不会影响外部的值 | 输出的是值的引用,JS 引擎对脚本静态分析的时候。遇到模块加载命令 |