1. babel 转换过程
- Parsing 解析
这个过程要经 词法分析、语法分析、构建AST(抽象语法树) 一系列操作;
词法分析: 使用 tokenizer(分词器) 或者 lexer(词法分析器),将源码拆分成 tokens,tokens 是一个放置对象的数组,其中的每一个对象都可以看做是一个单元(数字,标签,标点,操作符...)的描述信息。
❌ 词法分析不能帮助我们判断该条语句是否合法 👉 语法解析。
语法解析: 将 tokens 重新整理成语法相互关联的表达形式 ,这种表达形式一般被称为 中间层或者AST(抽象语法树)。
- Transformation 转换
改写AST(抽象语法树),或者根据当前AST(抽象语法树)生成一个新的AST(抽象语法树),这个过程可以是相同语言,或者可以直接将AST(抽象语法树)翻译为其他语言。
需要遍历这个“树”的节点 并读取其内容,由此引出 Traversal(遍历) 和 Visitors (访问器) 。
Traversal(遍历) :遍历 AST 的所有节点,这个过程使用深度优先原则
Visitors (访问器) :访问器最基本的思想是创建一个“访问器”对象,这个对象可以处理不同类型的节点函数
- Code Generation 生成代码
将生成的新 AST 树再转回代码的过程。大部分的代码生成器主要过程是,不断的访问 Transformation 生成的 AST (抽象语法树)或者再结合 tokens,按照指定的规则,将“树”上的节点打印拼接最终还原为新的 code
2. babel 插件
@babel/parser可以把源码转换成AST@babel/traverse用于对AST的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点@babel/generate可以把AST生成源码,同时生成sourcemap@babel/types用于AST节点的 Lodash 式工具库, 它包含了构造、验证以及变换AST节点的方法,对编写处理AST逻辑非常有用@babel/core Babel的编译器,核心 API 都在这里面,比如常见的transform、parse,并实现了插件功能
webpack 配置 babel
babel-loader:架起连接 webpack 和 babel 的桥梁@babel/preset-env将 ES6 语法转成 ES5@babel/polyfill使低版本浏览器也支持所有ES6的语法
3. es6 的一些新语法转 es5
3.1 const、let
ES5 中的变量只有全局作用域和函数作用域,某些场景会出现下列问题:
- 内层变量会覆盖外层变量
- 用于计数的循环(for循环)变量泄露为全局变量
📚 const、let、var 的区别?
-
变量提升
- 『var 声明的变量存在变量提升』,变量可以在声明之前调用,值为
undefined
- 『let 和 const 不存在变量提升』,声明的变量一定要在声明后使用,否则报错
- 『var 声明的变量存在变量提升』,变量可以在声明之前调用,值为
-
暂时性死区
- 『let 和 const 存在暂时性死区』,变量在定义语句之前,如果使用会抛出错误
-
块级作用域
- var 全局作用域和函数作用域
- let 和 const 存在块级作用域
-
重复声明
- let 和 const 不允许重复声明(会抛出错误)
-
修改声明的变量
- var 和 let 可以
- const 声明一个只读的常量,一旦声明,基本数据类型的值就不能改变
es6 转换前
let a = '1'
let b = [1, 2, 3, 4]
for(let a in b){
console.log(b[a])
}
const c = 2
转换后
var a = '1';
var b = [1, 2, 3, 4];
for (var _a in b) {
console.log(b[_a]);
}
var c = 2;
🤔 块级作用域如何实现?
在块级作用域内改变一下变量名,使之与外层不同。
3.2 模板字符串
(``) 基本的字符串格式化,将表达式嵌入字符串中进行拼接,用 ${} 来定义
es6 转换前
let a = 1
let b = [1,2,3,4]
const c = `${b}包含${a}`
转换后
var a = 1;
var b = [1, 2, 3, 4];
var c = b + "\u5305\u542B" + a;
3.3 解构赋值
- 对象
转换前
var props = {
name: "heyli",
getName: function() {
},
setName: function() {
}
};
let { name, getName, setName } = props;
转换后
var props = {
name: "heyli",
getName: function getName() {},
setName: function setName() {}
};
var name = props.name,
getName = props.getName,
setName = props.setName;
- 数组
『匿名数组』
转换前
var [ a1, a2 ] = [1, 2, 3];
转换后
var _ref = [1, 2, 3],
a1 = _ref[0],
a2 = _ref[1];
- 对象深层次解构赋值、字符串解构赋值
babel 在代码顶部生产了一个公共的代码 _slicedToArray。大概过程是『将对象里面的一些属性转换成数组』,方便解构赋值的进行。
3.4 函数参数默认值及扩展运算符
使用 argument 来做判断
第一种情况: 涉及对象的解构赋值,x 和 y 的值有可能是 undefined。所以这里需要当 arguments 的长度大于 0 而且 arguments[0] 不为 undefined 时,才对 arguments[0] 进行解构赋值
第二种情况: 保证分别存在第一个和第二个参数以及参数的值不为 undefined 时,进行赋值,否则为默认值。
转换前
function func({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
function func1(x = 1, y = 2) {
return [x, y];
}
转换后
function func() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
x: 0,
y: 0
},
x = _ref.x,
y = _ref.y;
return [x, y];
}
function func1() {
var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
return [x, y];
}
第三种情况: 扩展运算符,...y 代表 y 接收了 argument 除了第一个参数之外的所有参数,转换后是使用 for 循环为数组 y 设置值。
转换前
function func(x, ...y) {
console.log(x);
console.log(y);
return x * y.length;
}
转换后
function func(x) {
console.log(x);
for (var _len = arguments.length, y = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
y[_key - 1] = arguments[_key];
}
console.log(y);
return x * y.length;
}
3.5 箭头函数
📚 箭头函数与普通函数的区别
-
箭头函数的语法更加简洁
-
箭头函数没有自己的 this,会捕获其所在上下文的 this,作为自己的 this。
-
call()、apply()、bind() 等方法都不能改变箭头函数中的 this 指向
-
箭头函数 this 指向无法改变,所以也不可以作为构造函数,不可以使用 new 命令。
-
箭头函数没有自己的 arguments 对象,在箭头函数里面访问到的 arguments 实际上是外层函数的 arguments 值。可以用 rest 参数解决。
const add = (...args) => { console.log(args.reduce((pre, cur) => pre + cur)) } add(1, 2, 3) // 6 -
箭头函数没有原型对象 prototype
-
箭头函数不能使用 yield 关键字,不能用作 Generator 函数
转换前
var obj = {
prop: 1,
func: function() {
var innerFunc = () => {
this.prop = 1;
};
var innerFunc1 = function() {
this.prop = 1;
};
},
}
转换后
var obj = {
prop: 1,
func: function func() {
var _this = this;
var innerFunc = function innerFunc() {
_this.prop = 1;
};
var innerFunc1 = function innerFunc1() {
this.prop = 1;
};
}
};
箭头函数转 es5 语法: 将简洁的语法补全,同时会在外层多写一个
_this = this使其指向箭头函数所在上下文的 this。
👇 参考: