前言
浏览器将前端的代码(html,css,js等)从服务端下载到浏览器端执行,文件越大,下载的时间越久,用户的体验也就越差,那么减小前端的代码体积可以提升一些用户体验,提高首屏渲染的速度。 现如今都有脚手架来创建项目了,上线的代码都是编译后的代码,而不是在项目中写的代码。 这样的话,在项目中实现同样的功能而采用不同的写法得到编译后的体积越小越好。
面对的问题
rollup webpack 等打包工具压缩代码
rollup webpack 等打包工具,最终编译时,会将代码变得更加简洁。
- 减少空格,缩进,换行等
- 值类型不变,但更简洁。例如:false 编译成 !1 ,undefined 编译成 void 0 等
- 长变量名编译成短变量名,解构赋值重命名
- ...
摇树 tree-shaking
Tree shaking 是一个通常用于描述移除 JavaScript 上下文中的未引用代码 (dead-code) 行为的术语。 它基于 ES2015 中 import 和 export 语句,即 es modules,在现代 JavaScript 应用程序中,我们通常使用 webpack 或 rollup 进行打包,将使用到的代码才打包到文件中。 示例:
javascript
export function add(a, b) {
return a + b;
}
export function sub(a, b) {
return a - b;
}
javascript
import { add } from './utils';
const sum = add(1, 2)
该工具方法均无副作用,打包后,由于 sub 函数文件没有被使用,被引用,将不会被打包到最终的 js 文件中。
babel 高版本 js | jsx 编译成 低版本
babel 试用 浏览器在更新迭代,ES2015,ES2016 等 es 版本,一直在推出了新的 es 语法 API,使开发提效,代码风格更好。 用户电脑上的浏览器未必是最新的浏览器,甚至说是很老的浏览器,要使项目中的高版本 js 语法能够在低版本浏览器中打开,可以使用 babel 将高版本 js 编译成能够在低版本浏览器执行的 js 代码。 例如: async await ; 箭头函数;可选链;Fragment等。
Ts 编译成 Js
TypeScript 是 JavaScript 的超集,在项目中使用它,可以使一些错误在编译期发现,不至于使一些常见的错误到运行时才发现。 编译后会失去 type 类型代码,使代码量更少,所以多定义 type 是不影响编译后代码量的。 但 ts 存在一些运行时的语法代码,即 ts 的特定语法会被编译成 js 代码,例如:枚举,装饰器等。
问题的解决方式
不建议把 undefined 当成 false 使用
webpack rollup 等打包工具,会压缩代码,将 undefined 编译成 void 0 ,false 编译成 !1 ,void 0 五个字符,而 !0 只有两个字符。而且对于一个 boolean 类型的值而言,false 的值类型语义化正确,且编译后的字符更少。
typescript
const un = undefined;
const fal = false;
console.log('un', un)
console.warn('fal', fal)
javascript
const l=void 0,s=!1;
console.log("un",l);
console.warn("fal",s);
解构赋值,临时变量替代 多个. 或原型链查找变量
webpack rollup 等打包工具会进行变量重命名,变成一个简短的变量名,无论代码编写者声明多么长的变量名,都可以减少编译后的代码量。 但属性不能被重命名,若多次的使用同一个对象里的属性,可使用解构赋值来给一个临时变量,从而减少编译后的代码量。 解构赋值前
typescript
const pen = {
calc: {
rect: {
x: 50,
y: 50,
width: 100,
height: 100
}
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
drawRect(ctx!)
function drawRect(ctx: CanvasRenderingContext2D) {
ctx.moveTo(pen.calc.rect.x, pen.calc.rect.y);
ctx.lineTo(pen.calc.rect.x + pen.calc.rect.width, pen.calc.rect.y);
ctx.lineTo(pen.calc.rect.x + pen.calc.rect.width, pen.calc.rect.y + pen.calc.rect.height);
ctx.lineTo(pen.calc.rect.x, pen.calc.rect.y + pen.calc.rect.height);
ctx.closePath();
ctx.fill();
}
javascript
const t={calc:{rect:{x:50,y:50,width:100,height:100}}},
s=document.createElement("canvas"),
a=s.getContext("2d");
d(a);
function d(r){
r.moveTo(t.calc.rect.x,t.calc.rect.y),
r.lineTo(t.calc.rect.x+t.calc.rect.width,t.calc.rect.y),
r.lineTo(t.calc.rect.x+t.calc.rect.width,t.calc.rect.y+t.calc.rect.height),
r.lineTo(t.calc.rect.x,t.calc.rect.y+t.calc.rect.height),
r.closePath(),r.fill()
}
解构赋值后
typescript
const pen = {
calc: {
rect: {
x: 50,
y: 50,
width: 100,
height: 100
}
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
drawRect(ctx!)
function drawRect(ctx: CanvasRenderingContext2D) {
// 解构赋值
const { x, y, width, height } = pen.calc.rect;
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + height);
ctx.lineTo(x, y + height);
ctx.closePath();
ctx.fill();
}
javascript
const l={calc:{rect:{x:50,y:50,width:100,height:100}}},
s=document.createElement("canvas"),
u=s.getContext("2d");
d(u);
function d(r){
const{x:o,y:n,width:i,height:e}=l.calc.rect;
r.moveTo(o,n),
r.lineTo(o+i,n),
r.lineTo(o+i,n+e),
r.lineTo(o,n+e),
r.closePath(),
r.fill()
}
解构赋值后的代码量很明显的减少了。 在 JavaScript 中,数据存储位置可以对代码整体性能产生重要影响。有四种数据访问类型:直接量,变 量,数组项,对象成员,它们有不同的性能考虑。 直接量和局部变量访问速度非常快,数组项和对象成员需要更长时间。局部变量比域外变量快,因为它位于作用域链的第一个对象中。变量在作用域链中的位置越深,访问所需 的时间就越长。 解构赋值使对象成员变成局部变量,采用空间换时间,既提高了读取变量的速度,又减少了编译后的代码量。(注意:对对象成员进行写操作时,要注意引用问题)
避免全量引入
在项目的开发中,会经常的使用到第三方的 npm 包,若第三方 npm 包使用的是 esModules ,根据需要的情况进行按需引入,而非全量引入,例如: lodash-es (lodash 的 esModules 方式的包)
javascript
// 不推荐
import * as lodash from 'lodash-es';
// lodash.get
// 推荐
import { get } from 'lodash-es';
移除不必要的 Fragment
React 组件可以返回多个根标签,只需要在外面套一个 <></> 即可,于是在开发过程中,会出现一些不必要的 <></> ,虽然它不影响最终在 dom 上的表现,但会影响编译后的代码量。 存在不必要的 <></>
javascript
function Button (props) {
return <>
<button>123</button>
</>
}
javascript
function Button(props) {
return React.createElement(React.Fragment, null, React.createElement("button", null, "123"));
}
移除不必要的 <></>
javascript
function Button (props) {
return <button>123</button>
}
javascript
function Button(props) {
return React.createElement("button", null, "123");
}
编写代码时,若最开始无法确定该组件是否存在多个子元素,可先使用 <></> 包裹,等到代码完成了,清除不必要的 <></> 。
减少可选链 ?. 以及 ??
当尝试访问可能不存在的对象属性时,可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链运算符也是很有帮助的。 可选链 ?. ,?? 是 es2020 的新规范,如果要考虑兼容性的话,babel 会将其编译成低版本浏览器所支持的语法,会使编译后的代码变长。
javascript
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};
const dogName = adventurer.dog?.name;
console.log(dogName);
console.log(adventurer.someNonExistentMethod?.());
// ??
const type = adventurer.type ?? 'person';
javascript
var _adventurer$dog, _adventurer$someNonEx, _adventurer$type;
var adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};
var dogName = (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0 ? void 0 : _adventurer$dog.name;
console.log(dogName);
console.log((_adventurer$someNonEx = adventurer.someNonExistentMethod) === null || _adventurer$someNonEx === void 0 ? void 0 : _adventurer$someNonEx.call(adventurer));
// ??
var type = (_adventurer$type = adventurer.type) !== null && _adventurer$type !== void 0 ? _adventurer$type : 'person';
总结:允许使用 ?. 和 ?? ,但是要减少它们的使用,理解场景再去决定是否使用,尽可能不要出现这种情况:a?.b?.c?.d?.e ;从执行代码来看,它需要先判断后执行;从编译后代码体积来看,可选链的体积更大了。 场景:后端本来应该返回一个数组或者一个对象,结果返回了一个 null ,可以在请求到响应结果时,给一个默认值。
常量枚举
常量枚举不编译出 反向映射 的对象(与配置项和编译工具有关),可以减少运行时执行的代码,减少编译后的代码量。
tsc 规范
普通枚举
typescript
enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
javascript
var Directions;
(function (Directions) {
Directions[Directions["Up"] = 0] = "Up";
Directions[Directions["Down"] = 1] = "Down";
Directions[Directions["Left"] = 2] = "Left";
Directions[Directions["Right"] = 3] = "Right";
})(Directions || (Directions = {}));
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
常量枚举
typescript
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
javascript
var directions = [0 /* Directions.Up */, 1 /* Directions.Down */, 2 /* Directions.Left */, 3 /* Directions.Right */];
isolatedModules
tsconfig.json 中配置 isolatedModules true 时,常量枚举会变成普通枚举。
esbuild 问题
esbuild 并行编译,常量枚举的编译后表现与 tsc 不同。esbuild 常量枚举 是否有 const 无法决定它编译后是否存在对象。