wtw's Frontend

道阻且长,行则将至💪🏻

0%

react

特性

  • 数据驱动
  • JSX 语法糖动态声明更加灵活
  • Virtual DOM 与 Diff 算法配合使用,可保证性能
  • Fiber 的任务调度和数据结构令 react 的性能更出色

基于 class

  • 生命周期
    • 创建阶段
      • constructor: 初始化
      • getDerivedStateFromProps: 合并组件外的 props 至组件内 state,不推荐使用
      • render: react 组件内唯一的描述 UI 的方法,必须设置
      • componentDidMount: 在组件首次挂载结束之后,需要实行的如异步 http 资源请求的步骤
    • 更新阶段
      • getDerivedStateFromProps: 合并组件外的 props 至组件内 state,不推荐使用
      • shouldComponentUpdate: 判断组件是否可以实行渲染更新
      • getSnapShotBeforeUpdate: 在组件渲染更新前需要的统计计算
      • render: react 组件内唯一的描述 UI 的方法,必须设置
      • componentDidUpdate: 在组件渲染更新完成后要执行的一些操作
    • 卸载阶段
      • componentWillUnmount: 在组件卸载时,用于资源释放

Diff 算法

复杂度

O(n ^ 3) -> O(n),只对比同层兄弟组件节点,不实行跨层节点对比

节点

  • 属性变化
  • 位置变化
  • 类型变化
  • 跨层移动
阅读全文 »

javascript outline

ES6

var

  • 变量提升(会提前分配栈空间内存,用于存储基本类型数据或者引用类型指针)
  • 重复声明
  • 全局作用域绑定

let、const

  • 不再拥有 var 的缺陷特性
  • TDZ(临时死区)
  • 块级作用域绑定

let

  • 变量声明

const

  • 常量声明
  • 优先声明最佳实践
阅读全文 »

Tapable

什么是 Tapable

简单来说,类似于 EventEmitter 的一种发布订阅模式,也就是观察者模式,但不同的是,Tapable 的流程以及应用场景要广泛复杂的多.Tapable 作为 Webpack 的主要骨架而流行,因此也受到前端开发爱好者的研究.

订阅模式类型 —— Hook

在 Tapable 中订阅模式类型被称为 Hook,也就是 “钩子”,基本分为四种.

  • 普通钩子
  • 熔断钩子
  • 瀑布钩子
  • 循环钩子

在此分类基础上,再添加发布时同步(Sync)、异步(Async)以及 Promise 的区分.

整体源码分析

先按照整体流程大体看一遍源码,再具体分析各个分类,就先拿最简单 SyncHook 同步普通钩子举例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

// 内容则是由 HookCodeFactory 生成
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}

const factory = new SyncHookCodeFactory();

// 继承自 Hook 源类
class SyncHook extends Hook {
// 同步的订阅模式类型 Hook 不能调用 tapAsync、tapPromise 异步订阅方法
tapAsync() {
throw new Error("tapAsync is not supported on a SyncHook");
}

tapPromise() {
throw new Error("tapPromise is not supported on a SyncHook");
}

// 编译方法生成并导出最终的函数
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}

module.exports = SyncHook;
阅读全文 »

create Simplepack

搭建一个简易的 webpack —— Simplepack.

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// simplepack.config.js
const {resolve} = require('path');

const OUTPUT_DIR = resolve(process.cwd(), './build');
const simplepackConfig = {
// 入口
entry: './src/index.js',
// 作为 simplepack 构建打包的出口
output: {
// 作为 simplepack 构建打包文件导出的位置目录
path: OUTPUT_DIR,
// 作为 simplepack 构建打包导出文件的名称
filename: 'main.js'
}
};
module.exports = simplepackConfig;

目录

Simplepack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
const {resolve} = require('path');
const {writeFileSync} = require('fs');

const utils = require('../utils');

class Simplepack {
constructor(options) {
this.options = options;
this.resources = [];
}

// 运行
run() {
// 递归获取资源列表
(function getFileInfo(entry, upperLevel = '') {
let path = entry;
const cwd = process.cwd();
// 判断是否是绝对路径
if (!path.includes(cwd)) {
// 拼接父子依赖目录
path = resolve(cwd, upperLevel, `${path.endsWith('.js') ? path : `${path}/index.js`}`);
}
if (!existsSync(path)) {
throw new Error(`请检查目录,不存在 ${entry} 模块`);
}

const fileInfo = this.compile(path);
const { dependencies: fileDependencies } = fileInfo;
if (fileDependencies.length > 0) {
fileDependencies.forEach((item) => {
getFileInfo.call(this, item, entry.substring(0, entry.lastIndexOf('/') + 1));
});
}
fileInfo.filename = entry;
this.resources.push(fileInfo);
}.bind(this))(this.entry);
this.emit();
}

// 解析
compile(path) {
// 对相对路径进行校验,将相对路径转化为绝对路径
const isRelative = /\.\/|\.\\/;
if (isRelative.test(path)) {
path = resolve(process.cwd(), path);
}
// 获取入口或者依赖模块的 AST 抽象语法树
const ast = utils.getAST(path);
return {
// 获取相关依赖列表
dependencies: utils.getDependencies(ast),
// 获取相关代码
source: utils.getTransformFromAST(ast)
}
}

// 生成文件
emitFile() {
const {entry = '', output: {filename = '', path = ''}} = this.options;
const outputPath = resolve(path, filename);
// 模拟 webpack IIFE 自执行函数表达式闭包,将每一个模块外加一层包裹,并将 import 转化为 __WEBPACK_REQUIRE__
const module = this.resources.map(item => `'${item.filename}': function(require, modules, exports) {${item.source}`).join(',');
const bundle = `(function (module) {
function require(filename) {
var fn = module[filename];
var modules = {exports: {}};
fn(require, modules, modules.exports);
return modules.exports;
}
require('${entry}');
})({${module}})`;
writeFileSync(outputPath, bundle);
}
}

Simplepack Parse 解析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const {readFileSync} = require('fs');
const {parse} = require('babylon');
const {traverse, transformFromAST} = require('@babel/core');

const utils = {
// 通过 babylon 解析入口或者依赖模块的抽象语法树
getAST(path) {
const content = readFileSync(path, 'utf-8');
return parse(content, {
sourceType: 'module'
});
},
// 通过 traverse 遍历获取 import 导入的依赖模块
getDependencies(ast) {
const dependencies = [];
traverse(ast, {
ImportDeclaration({node}) {
dependencies.push(node.source.value);
}
});
return dependencies;
},
// 通过 transformFromAST 获取被 babel 处理后的代码
getTransformFromAST(ast) {
const {code} = transformFromAST(ast, null, {
presets: ['@babel/preset-env']
});
return code;
}
};

module.exports = utils;
阅读全文 »

webpack

speed-measure-webpack-plugin

为何 speed-measure-webpack-plugin 速度分析插件对于 webpack 5.x 的支持很有限制,如何完美的解决这些限制?

现阶段并没有发现解决的最完美的方式, speed-measure-webpack-plugin 速度分析插件对于 webpack 5.x 会出现以下两个问题:

使用 .wrap 包裹后,会直接报 TypeError 类型错误,经过查询可知,官方给出的解决方案是,将 optimization.minimizer 的默认值去掉,也就是 ‘…’.
那就有疑问了,如果不合并 minimizer 默认值,对于prod 生产环境的打包速度分析时,我还需要额外下载 terser-webpack-webpack 对构建打包文件实行压缩混淆,还需要额外引入 new webpack.optimize.ModuleConcatenationPlugin 来开启 Scope Hoisting 吗?还是说要放弃 css-minimizer-webpack-plugin 压缩 css 文件,使用其他的比较麻烦的方案呢?这些方式勉强可以处理,但是都太得不偿失.
参考链接: Not working with Webpack 5: “TypeError: Cannot create proxy with a non-object as target or handler”.

即使按照官方的解决方案,去掉 optimization.minimizer 的默认值 ‘…’,也会报 “You forgot to add ‘mini-css-extract-plugin’ plugin” 这个问题,针对于此官方的解决方案是,先试用 speed-measure-webpack-plugin 速度分析,而后再将 mini-css-extract-plugin 抽取 css 文件插件添加到 webpack plugins 中.感觉还是好愚蠢的做法,很不优雅而且多余.
参考链接: “You forgot to add ‘mini-css-extract-plugin’ plugin”.

阅读全文 »

npm

npm run scripts

为何 npm 执行 package.json 中的 scripts 命令时,即使全局环境变量下不存在的脚本命令也能执行?

解: 首先 npm run package.json 中的 scripts 脚本命令时,会新建一个 shell 脚本,并将 scripts 中的脚本命令放入其中执行,因此 shell 命令(一般是 bash)完全可以执行;其次,shell 命令执行时,node_modules/.bin 子目录下的所有可执行脚本命令会放入至 $PATH 全局环境变量中,等到 shell 命令执行完毕,$PATH 才会恢复原样.最后 node_modules/.bin 子目录下的所有可执行脚本命令实际上是与 node_modules 下可执行模块建立了软链接的,因此在 node 此类项目下,即使全局环境变量下不存在的脚本命令,依然可以通过 node_modules 中的可执行模块在当前项目下转化执行.

线性图: node_modules/(可执行模块) -> 通过软链接 => node_modules/.bin/(可执行脚本命令) -> 通过 shell 脚本命令执行(添加至 $PATH 全局环境变量) => 即使全局环境变量下不存在的脚本命令也能执行.

javascript

闭包

什么是闭包?

解: A 函数内包裹 B 函数,B 函数在非 A 函数作用域内实行调用,依然能够使用或者调用 A 函数内的变量或者函数,这就是闭包.

script

阅读全文 »

nodejs

本 nodejs

概念

Nodejs 是一个基于 Chrome V8 引擎的 Javascript 运行环境

特性

  • 事件驱动
  • 非阻塞 I/O

非阻塞 I/O

实际上就是系统在接收输入与输出之间时,还可以继续处理其他输入

规范

nodejs 中的内置模块基本都遵从回调函数 callback(err, result) 的形式

阅读全文 »

webpack

webpack-cli

作用

  • 引入 yargs,对命令行实行定制
  • 分析命令行参数,对各个参数实行转换,组成编译配置项
  • 导入 webpack,根据编译配置项进行编译和构建

entry

作用

作为 webpack 构建打包的入口,搜索全局资源的起点.

属性值

为何设置 entry 的属性值必须为相对路径,而绝对路径却会报错?

解: entry 是 webpack 构建打包的入口,是搜索全局资源的起点,它的属性值是设置为相对于整个项目而言的,也就是当前项目根目录,在 webpack context 属性不变的情况下, entry 属性值永远相对于当前项目根目录,当然如若 context 属性值发生改变,entry 是可以设置绝对路径的,因为 webpack 构建打包所作用的项目根目录发生了改变,当然也可以强制设置 entry 属性值为绝对路径,但是其只是相对于当前设备目录而言,是存在很大的配置风险的.

阅读全文 »

引言

此博客用于搭建 hexo next 主题的字典工具类博客.基本不会探索深究配置的原因,敬请知悉~

hexo 安装

步骤

首先要用 npm 资源依赖管理工具安装全局命令 hexo-cli.

npm install hexo-cli -g

然后使用 hexo 初始化博客目录,比如 blog 目录,目录名要与后续个人建立的 github 上 hexo 托管代码的 repository 库同名.

hexo init blog

接着 github 建库: 建立一个以 w-t-w(我的账号).github.io 结尾的 repository 库,作为 hexo 托管代码的库, github 默认
.github.io 结尾作为用户的网站二级域名,建立一个新的分支作为创作分支(因为主分支是用来发布呈现网站的); 之后,进入生成的 blog
文件夹,在本地添加与远程 repository 库链接关联的句柄简称,并设置本地句柄简称推送/同步远程库上游的分支,与远程库建立安全关联,最后同步远程最新资源.

git remote add origin git@github.com:w-t-w/w-t-w.github.io.git
git pull --set-upstream origin master
阅读全文 »