if (true && !res) {
warn(
Failed to mount app: mount target selector "${container}" returned null.
)
}
可以看到这里的 __DEV__ 被替换成了字面量 true ,所以这段代码在开发环境是肯定存在的。
当 Vue 构建用于生产环境的资源时,会把 __DEV__ 常量设置为 false,这时上面那段输出警告信息的代码就等价于:
if (false && !res) {
warn(
Failed to mount app: mount target selector "${container}" returned null.
)
}
可以看到 __DEV__ 常量被替换为字面量 false ,这时我们发现这段分支代码永远都不会执行,因为判断条件始终为假,这段永远不会执行的代码被称为 Dead Code,它不会出现在最终的产物中,在构建资源的时候就会被移除,因此在 vue.global.prod.js 中是不会存在这段代码的。
这样我们就做到了在开发环境为用户提供友好的警告信息的同时,还不会增加生产环境代码的体积。
框架要做到良好的 Tree-Shaking
上文中我们提到通过构建工具设置预定义的常量 __DEV__ ,就能够做到在生产环境使得框架不包含打印警告信息的代码,从而使得框架自身的代码量变少。但是从用户的角度来看,这么做仍然不够,还是拿 Vue 来举个例子,我们知道 Vue 提供了内置的组件例如 <Transition> ,如果我们的项目中根本就没有使用到该组件,那么 <Transition> 组件的代码需要包含在我们项目最终的构建资源中吗?答案是当然不需要,那如何做到这一点呢?这就不得不提到本节的主角 Tree-Shaking。
那什么是 Tree-Shaking 呢?在前端领域这个概念因 rollup 而普及,简单的说所谓 **Tree-Shaking **指的就是消除哪些永远不会执行的代码,也就是排除 dead-code,现在无论是 rollup 还是 webpack 都支持 Tree-Shaking。
想要实现 Tree-Shaking 必须满足一个条件,即模块必须是 ES Module,因为 Tree-Shaking 依赖 ESM 的静态结构。我们使用 rollup 通过一个简单的例子看看 Tree-Shaking 如何工作,我们 demo 的目录结构如下:
├── demo
│ └── package.json
│ └── input.js
│ └── utils.js
首先安装 rollup:
yarn add rollup -D # 或者 npm install rollup -D
下面是 input.js 和 utils.js 文件的内容:
// input.js
import { foo } from ‘./utils.js’
foo()
// utils.js
export function foo(obj) {
obj && obj.foo
}
export function bar(obj) {
obj && obj.bar
}
代码很简单,我们在 utils.js 文件中定义并导出了两个函数,分别是 foo 和 bar,然后在 input.js 中导入了 foo 函数并执行,注意我们并没有导入 bar 函数。
接着我们执行如下命令使用 rollup 构建:
npx rollup input.js -f esm -o bundle.js
这句命令的意思是以 input.js 文件问入口,输出 ESM 模块,输出的文件名叫做 bundle.js 。命令执行成功后,我们打开 bundle.js 来查看一下它的内容:
// bundle.js
function foo(obj) {
obj && obj.foo
}
foo();
可以看到,其中并不包含 bar 函数,这说明 Tree-Shaking 起了作用,由于我们并没有使用 bar 函数,因此它作为 dead-code 被删除了。但是如果我们仔细观察会发现,foo 函数的执行也没啥意义呀,就是读取了对象的值,所以它执行还是不执行也没有本质的区别呀,所以即使把这段代码删了,也对我们的应用没啥影响,那为什么 rollup 不把这段代码也作为 dead-code 移除呢?
这就涉及到 Tree-Shaking 中的第二个关键点,即副作用。如果一个函数调用会产生副作用,那么就不能将其移除。什么是副作用?简单地说副作用的意思是当调用函数的时候,会对外部产生影响,例如修改了全局变量。这时你可能会说,上面的代码明显是读取对象的值怎么会产生副作用呢?其实是有可能的,想想一下如果 obj 对象是一个通过 Proxy 创建的代理对象那么当我们读取对象属性时就会触发 Getter ,在 Getter 中是可能产生副作用的,例如我们在 Getter 中修改了某个全局变量。而到底会不会产生副作用,这个只有代码真正运行的时候才能知道, JS 本身是动态语言,想要静态的分析哪些代码是 dead-code 是一件很有难度的事儿,上面只是举了一个简单的例子。
正因为静态分析 JS 代码很困难,所以诸如 rollup 等这类工具都会给我提供一个机制,让我们有能力明确的告诉 rollup :”放心吧,这段代码不会产生副作用,你可以放心移除它“,那具体怎么做呢?如下代码所示,我们修改 input.js 文件:
import {foo} from ‘./utils’
/#PURE/ foo()
注意这段注释代码 /*#__PURE_*_/,该注释的作用就是用来告诉 rollup 对于 foo() 函数的调用不会产生副作用,你可以放心的对其进行 Tree-Shaking,此时再次执行构建命令并查看 bundle.js 文件你会发现它的内容是空的,这说明 Tree-Shaking 生效了。
基于这个案例大家应该明白的是,在编写框架的时候我们需要合理的使用 /*#__PURE_*_/ 注释,如果你去搜索 Vue 的源码会发现它大量的使用了该注释,例如下面这句:
export const isHTMLTag = /#PURE/ makeMap(HTML_TAGS)
也许你会觉得这会不会对编写代码带来很大的心智负担?其实不会,这是因为通常产生副作用的代码都是模块内函数的顶级调用,什么是顶级调用呢?如下代码所示:
foo() // 顶级调用
function bar() {
foo() // 函数内调用
}
可以看到对于顶级调用来说是可能产生副作用的,但对于函数内调用来说只要函数 bar 没有被调用,那么 foo 函数的调用当然不会产生副作用。因此你会发现在 Vue 的源码中,基本都是在一些顶级调用的函数上使用 /*#__PURE__*/ 注释的。当然该注释不仅仅作用与函数,它可以使用在任何语句上,这个注释也不是只有 rollup 才能识别,webpack 以及压缩工具如 terser 都能识别它。
框架应该输出怎样的构建产物
上文中我们提到 Vue 会为开发环境和生产环境输出不同的包,例如 vue.global.js 用于开发环境,它包含了必要的警告信息,而 vue.global.prod.js 用于生产环境,不包含警告信息。实际上 Vue 的构建产物除了有环境上的区分之外,还会根据使用场景的不同而输出其他形式的产物,这一节我们将讨论这些产物的用途以及在构建阶段如何输出这些产物。
不同类型的产物一定是有对应的需求背景的,因此我们从需求讲起。首先我们希望用户可以直接在 html 页面中使用 <script> 标签引入框架并使用:
为了能够实现这个需求,我们就需要输出一种叫做 IIFE 格式的资源,IIFE 的全称是 Immediately Invoked Function Expression ,即”立即调用的函数表达式“,可以很容易的用 JS 来表达:
(function () {
// …
}())
如上代码所示,这就是一个立即执行的函数表达式。实际上 vue.globale.js 文件就是 IIFE 形式的资源,大家可以看一下它的代码结构:
var Vue = (function(exports){
// …
exports.createApp = createApp;
// …
return exports
}({}))
这样当我们使用 <script> 标签直接引入 vue.global.js 文件后,那么全局变量 Vue 就是可用的了。
在 rollup 中我们可以通过配置 format: 'iife' 来实现输出这种形式的资源:
// rollup.config.js
const config = {
input: ‘input.js’,
output: {
file: ‘output.js’,
format: ‘iife’ // 指定模块形式
}
}
export default config
不过随着技术的发展和浏览器的支持,现在主流浏览器对原生 ESM 模块的支持都不错,所以用户除了能够使用 <script> 标签引用 IIFE 格式的资源外,还可以直接引如 ESM 格式的资源,例如 Vue3 会输出 vue.esm-browser.js 文件,用户可以直接用 <script> 标签引入:
为了输出 ESM 格式的资源就需要我们配置 rollup 的输出格式为:format: 'esm'。
你可能已经注意到了,为什么 vue.esm-browser.js 文件中会有 -browser 字样,其实对于 ESM 格式的资源来说,Vue 还会输出一个 vue.esm-bundler.js 文件,其中 -browser 变成了 -bundler。为什么这么做呢?我们知道无论是 rollup 还是 webpack 在寻找资源时,如果 package.json 中存在 module 字段,那么会优先使用 module 字段指向的资源来代替 main 字段所指向的资源。我们可以打开 Vue 源码中的 packages/vue/package.json 文件看一下:
{
“main”: “index.js”,
“module”: “dist/vue.runtime.esm-bundler.js”,
}
其中 module 字段指向的是 vue.runtime.esm-bundler.js 文件,意思就是说如果你的项目是使用 webpack 构建的,那你使用的 Vue 资源就是 vue.runtime.esm-bundler.js ,也就是说带有 -bundler 字样的 ESM 资源是给 rollup 或 webpack 等打包工具使用的,而带有 -browser 字样的 ESM 资源是直接给 <script type="module"> 去使用的。
那他们之间的区别是什么呢?那这就不得不提到上文中的 __DEV__ 常量,当构建用于 <script> 标签的 ESM 资源时,如果是用于开发环境,那么 __DEV__ 会设置为 true;如果是用于生产环境,那么 __DEV__ 常量会被设置为 false ,从而被 Tree-Shaking 移除。但是当我们构建提供给打包工具的 ESM 格式的资源时,我们不能直接把 __DEV__ 设置为 true 或 false,而是使用 (process.env.NODE_ENV !== 'production') 替换掉 __DEV__ 常量。例如下面的源码:
if (DEV) {
warn(useCssModule() is not supported in the global build.)
}
在带有 -bundler 字样的资源中会变成:
if ((process.env.NODE_ENV !== ‘production’)) {
warn(useCssModule() is not supported in the global build.)
}
这样用户侧的 webpack 配置可以自己决定构建资源的目标环境,但是最终的效果其实是一样的,这段代码也只会出现在开发环境。
用户除了可以直接使用 <script> 标签引入资源,我们还希望用户可以在 Node.js 中通过 require 语句引用资源,例如:
const Vue = require(‘vue’)
为什么会有这种需求呢?答案是服务端渲染,当服务端渲染时 Vue 的代码是运行在 Node.js 环境的,而非浏览器环境,在 Node.js 环境下资源的模块格式应该是 CommonJS ,简称 cjs。为了能够输出 cjs 模块的资源,我们可以修改 rollup 的配置:format: 'cjs' 来实现:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。






既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)

文末
js前端的重头戏,值得花大部分时间学习。

推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。

CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。
学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
aScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

被折叠的 条评论
为什么被折叠?



