聊一聊面试中经常被问到的Tree Shaking,渣本毕业两年经验

42 阅读7分钟

结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。

前端学习书籍导图-1 开源分享:docs.qq.com/doc/DSmRnRG…

export { default as AddonArea } from './AddonArea';

export { default as Answer } from './AddonArea/Answer';

export { default as Analysis } from './AddonArea/Analysis';

export { default as OriginalText } from './AddonArea/OriginalText';

export { default as Labels } from './AddonArea/Labels';

这样的文件结构是无法进行 tree-shaking 的, 因为没有 import?!

自执行的模块 import

自执行模块我们通常会使用 import 'xxx' 来进行模块引用,而不进行显式的调用。因此模块本身就有副作用。

import 'utils/refresh'

对于这种模块可以这样处理:

  • 在 sideEffects 中通过数组声明,使其在 Tree Shaking 的范围之外

  • 模块改造,暴露成员支持显式调用

unused harmony export

如果该模块被标识为 unused harmony export,则说明没有外部引用使用到该成员,webpack 认为是可以安全去除的。

harmony export

部分被标识为 harmony export 的模块也会被去除。这个是跟 UglifyJS 的机制有关系。

没有提供导出成员的模块

// ./src/modules/edu-discount/seckill/index.ts

import * as SeckillTypes from './types';

export { SeckillTypes };

对于只有暴露的成员,但是没有被引用的成员,这种模块会被直接删除。

  • [x] exports provided

  • [ ] exports used

配置

--

babel的配置文件

{

"presets": [

["env", {

"modules": false  // 配置了这个,babel就不会像默认那样转变成 require 形式。

}],

"stage-2",

"react"

]

}

为 webpack 进行 tree-shaking 创造了条件。

⚠️不能引用类似 @babel/plugin-transform-modules-commonjs会把模块编译成 commonjs 的插件;

webpack 的配置文件

webpack 4 通过 optimization 取代了4个常用的插件:

| 废弃插件 | optimization 属性 | 功能 |

|

| --- | --- | --- | --- |

| UglifyjsWebpackPlugin | sideEffects | minimizer | Tree Shaking & Minimize |

| ModuleConcatenationPlugin | concatenateModules | Scope hoisting | 生产环境默认开启 |

| CommonsChunkPlugin | splitChunks | runtimeChunk | OccurrenceOrder |

| NoEmitOnErrorsPlugin | NoEmitOnErrors | 编译出现错误时,跳过输出阶段 | 生产环境默认开启 |

usedExports

Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记。

// Base Webpack Config for Tree Shaking

const config = {

mode: 'production',

optimization: {

usedExports: true,

minimizer: [

new TerserPlugin({...}) // 支持删除死代码的压缩器

]

}

};

package.json 的配置

用过 redux 的童鞋应该对纯函数不陌生,自然也就应该了解函数式编程,函数式编程中就有副作用一说。

照顾一下不知道的同学,那什么是副作用呢?

一个函数会、或者可能会对函数外部变量产生影响的行为。

具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用程序。比如全局样式表及全局的 JS 配置文件。

webpack 总会害怕把你要用的代码删除了,所以默认所有的文件都有副作用,不能被 Tree Shaking。

// 所有文件都有副作用,全都不可 tree-shaking

{

"sideEffects": true

}

// 没有文件有副作用,全都可以 tree-shaking,即告知 webpack,它可以安全地删除未用到的 export。

{

"sideEffects": false

}

// 除了数组中包含的文件外有副作用,所有其他文件都可以 tree-shaking,但会保留符合数组中条件的文件

{

"sideEffects": [

"*.css",

"*.less"

]

}

所以,首先关闭你的 sideEffects,

直接通过 module.rules 中的 sideEffects 配置可缩小你的影响范围。

加了 sideEffect 配置后,构建出来的一些 IIFE 函数也会加上/PURE/注释,便于后续 treeshaking。

组件不支持DCE?


我们的组件用的是 father,可以看到其依赖的father-build 是基于 rollup 的,那就好办了。webpack 的 Tree Shaking 还是 copy 的 rollup家的。

关键是在应用组件的业务项目里面配置optimization.sideEffects: true

// webpack.config.js

const path = require('path')

const webpackConfig = {

module : {

rules: [

{

test: /.(jsx|js)$/,

use: 'babel-loader',

exclude: path.resolve(__dirname, 'node_modules')

}   

]

},

optimization : {

sideEffects: true,

minimizer: [

// 这里配置成空数组是为了使最终产生的 main.js 不被压缩

]

},

plugins:[]

};

module.exports = webpackConfig;

// package.json

{

"name": "treeshaking-test",

"version": "0.1.0",

"description": "",

"main": "src/index.js",

"scripts": {

"build": "webpack --config webpack.config.js"

},

"author": "lu.lu lulu27753@163.com (github.com/lulu27753)",

"license": "MIT",

"dependencies": {

"big-module": "^0.1.0",

"big-module-with-flag": "^0.1.0",

"webpack-bundle-analyzer": "^3.7.0"

},

"devDependencies": {

"babel-preset-env": "^1.7.0",

"webpack": "^4.43.0",

"webpack-cli": "^3.3.11"

}

}

// .babelrc

{

"presets": [

["env", { "modules": false }]

]

}

可以看到最终打包后的文件如下:

// dist/main.js

"use strict";

// ESM COMPAT FLAG

webpack_require.r(webpack_exports);

// CONCATENATED MODULE: ./node_modules/big-module/es/a.js

var a = 'a';

// CONCATENATED MODULE: ./node_modules/big-module/es/b.js

var b = 'b';

// CONCATENATED MODULE: ./node_modules/big-module/es/c.js

var c = 'c';

// CONCATENATED MODULE: ./node_modules/big-module/es/index.js

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/a.js

var a_a = 'a';

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/b.js

var b_b = 'b';

// CONCATENATED MODULE: ./src/index.js

console.log(a, b, a_a, b_b);

/***/ })

/******/ ]);

可以很清楚的看到 big-module-with-flag 中的 c 模块被DCE了。


做个小小的改动,将 .babelrc 中的 modules 改为"commonjs"

{

"presets": [

["env", { "modules": "commonjs" }]

]

}

"use strict";

// ESM COMPAT FLAG

webpack_require.r(webpack_exports);

// EXPORTS

webpack_require.d(webpack_exports, "a", function() { return /* reexport */ a; });

webpack_require.d(webpack_exports, "b", function() { return /* reexport */ b; });

webpack_require.d(webpack_exports, "c", function() { return /* reexport */ c; });

// CONCATENATED MODULE: ./node_modules/big-module/es/a.js

var a = 'a';

// CONCATENATED MODULE: ./node_modules/big-module/es/b.js

var b = 'b';

// CONCATENATED MODULE: ./node_modules/big-module/es/c.js

var c = 'c';

// CONCATENATED MODULE: ./node_modules/big-module/es/index.js

/***/ }),

/* 2 */

/***/ (function(module, webpack_exportswebpack_require) {

"use strict";

// ESM COMPAT FLAG

webpack_require.r(webpack_exports);

// EXPORTS

webpack_require.d(webpack_exports, "a", function() { return /* reexport */ a; });

webpack_require.d(webpack_exports, "b", function() { return /* reexport */ b; });

webpack_require.d(webpack_exports, "c", function() { return /* reexport */ c; });

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/a.js

var a = 'a';

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/b.js

var b = 'b';

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/c.js

var c = 'c';

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/index.js

/***/ })

/******/ ]);

结果是 CDE 失败!

将 modules 的值改回去,并升级big-module-with-flag为0.2.0。CDE 成功,可以打假一波了(????,网上很多文章都是基于webpack3的,过时了)


升级big-module-with-flag为0.5.0, 并更改 src/index.js

import { a as a1, b as b1 } from "big-module";

import { a as a2, b as b2, Apple  } from "big-module-with-flag";

console.log(a1, b1, a2, b2);

const appleModel = new Apple({model: 'IphoneX'}).getModel()

console.log(appleModel)

var Apple = /#PURE/function () {

function Apple(_ref) {

var model = _ref.model;

_classCallCheck(this, Apple);

this.className = 'Apple';

this.model = model;

}

_createClass(Apple, [{

key: "getModel",

value: function getModel() {

return this.model;

}

}]);

return Apple;

}();

// CONCATENATED MODULE: ./src/index.js

console.log(a, b, es_a, es_b);

var appleModel = new Apple({

model: 'IphoneX'

}).getModel();

console.log(appleModel);

DCE 成功!

var _bigModule = webpack_require(2);

var _bigModuleWithFlag = webpack_require(1);

console.log(_bigModule.a, _bigModule.b, _bigModuleWithFlag.a, _bigModuleWithFlag.b);

var appleModel = new _bigModuleWithFlag.Apple({

model: 'IphoneX'

}).getModel();

console.log(appleModel);

/***/ }),

/* 1 */

/***/ (function(module, webpack_exportswebpack_require) {

"use strict";

// ESM COMPAT FLAG

webpack_require.r(webpack_exports);

// EXPORTS

webpack_require.d(webpack_exports, "a", function() { return /* reexport */ es_a; });

webpack_require.d(webpack_exports, "b", function() { return /* reexport */ es_b; });

webpack_require.d(webpack_exports, "c", function() { return /* reexport */ es_c; });

webpack_require.d(webpack_exports, "Person", function() { return /* reexport */ Person; });

webpack_require.d(webpack_exports, "Apple", function() { return /* reexport */ Apple; });

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/a.js

var a = 'a';

/* harmony default export */ var es_a = (a);

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/b.js

var b = 'b';

/* harmony default export */ var es_b = (b);

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/c.js

var c = 'c';

/* harmony default export */ var es_c = (c);

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/Person.js

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var Person = /#PURE/function () {

function Person(_ref) {

var name = _ref.name,

age = _ref.age,

sex = _ref.sex;

_classCallCheck(this, Person);

this.className = 'Person';

this.name = name;

this.age = age;

this.sex = sex;

}

_createClass(Person, [{

key: "getName",

value: function getName() {

return this.name;

}

}]);

return Person;

}();

// CONCATENATED MODULE: ./node_modules/big-module-with-flag/es/Apple.js

function Apple_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function Apple_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function Apple_createClass(Constructor, protoProps, staticProps) { if (protoProps) Apple_defineProperties(Constructor.prototype, protoProps); if (staticProps) Apple_defineProperties(Constructor, staticProps); return Constructor; }

TCP协议

  • TCP 和 UDP 的区别?
  • TCP 三次握手的过程?
  • 为什么是三次而不是两次、四次?
  • 三次握手过程中可以携带数据么?
  • 说说 TCP 四次挥手的过程
  • 为什么是四次挥手而不是三次?
  • 半连接队列和 SYN Flood 攻击的关系
  • 如何应对 SYN Flood 攻击?
  • 介绍一下 TCP 报文头部的字段
  • TCP 快速打开的原理(TFO)
  • 说说TCP报文中时间戳的作用?
  • TCP 的超时重传时间是如何计算的?
  • TCP 的流量控制
  • TCP 的拥塞控制
  • 说说 Nagle 算法和延迟确认?
  • 如何理解 TCP 的 keep-alive?

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

浏览器篇

  • 浏览器缓存?
  • 说一说浏览器的本地存储?各自优劣如何?
  • 说一说从输入URL到页面呈现发生了什么?
  • 谈谈你对重绘和回流的理解
  • XSS攻击
  • CSRF攻击
  • HTTPS为什么让数据传输更安全?
  • 实现事件的防抖和节流?
  • 实现图片懒加载?