文章目录
- 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 引擎对脚本静态分析的时候。遇到模块加载命令 |