├── Note
├── CSS.md
├── CSS面试题.md
├── ES6.md
├── Git.md
├── JS面试题.md
├── ShareDoc
│ └── 函数编程的应用与范式.pptx
├── macos.md
├── react-01.png
├── react-02.png
├── react.md
├── vue.md
├── webpack.md
├── 事件循环.md
├── 函数式编程范式.md
├── 垃圾回收.md
└── 脚手架工具.md
├── README.md
├── TypeScript
├── ts-learn-daily-0629.md
└── ts-learn-daily-0630.md
├── Webpack
├── DllPlugin
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.html
│ │ └── index.js
│ ├── webpack.config.js
│ ├── webpack.react.config.js
│ └── yarn.lock
├── Loader
│ ├── babel-loader.js
│ ├── banner-loader.js
│ ├── file-loader.js
│ └── url-loader.js
├── README.md
└── Tabable
│ ├── AsyncParalleHook-cb.js
│ ├── AsyncParalleHook-promise.js
│ ├── AsyncSeriesHook-cb.js
│ ├── AsyncSeriesHook-promise.js
│ ├── AsyncSeriesWaterfallHook-cb.js
│ ├── SyncBailHook.js
│ ├── SyncHook.js
│ ├── SyncLoopHook.js
│ └── SyncWaterfallHook.js
└── 面试.md
/Note/CSS.md:
--------------------------------------------------------------------------------
1 | ### 三大特性
2 | * 层叠性:
3 | * 继承性:
--------------------------------------------------------------------------------
/Note/CSS面试题.md:
--------------------------------------------------------------------------------
1 |
2 | 盒模型
3 |
4 | #### 盒模型包括:
5 | 1. 外边距(margin)
6 | 2. 边框(border)
7 | 3. 内边距(padding)
8 | 4. 内容(content)
9 |
10 | > 标准(W3C)盒子模型: width = content, 可用 box-sizing: content-box
11 |
12 | > IE 盒子模型: width = content + padding + border,可用 box-sizing: border-box
13 |
14 | #### 获取/设置盒模型的宽高
15 | 1. dom.style.width/height 只能获取内联样式的宽高
16 | 2. dom.currentStyle.width/height 获取渲染后元素的宽高(仅IE支持)
17 | 3. window.getComputedStyle(dom).style/height 获取渲染后元素的宽高(兼容性好)
18 | 4. dom.getBoundingClientRect().width/height(left/top/width/height) 常用于获取元素绝对位置(相对于视窗左上角)
19 |
20 | * padding: 继承 content 颜色
21 | * border: 继承 color 字体颜色
22 |
23 |
24 |
25 |
26 | 元素设置成 inline-block 元素会出现什么问题?怎么解决?
27 |
28 | 使用 CSS 更改非 inline-block 水平元素为 inline-block 水平,元素间会出现留白间距,原因是标签间的空格。
29 |
30 | 解决方案:
31 | 1. 去除 HTML 中的空格,代码连城一行
32 | 2. 借助 HTML 注释不换行
33 | 3. margin 负值
34 | 4. font-size: 0
35 |
36 |
37 |
38 |
39 | 如何创建 BFC
40 |
41 | 1. float 属性不为 none
42 | 2. position 属性为 absolute | fixed
43 | 3. display 属性为 inline-block | table-cell | table-caption | flex | inline-flex
44 | 4. overflow 属性不为 visible ,设置(auto | hidden)
45 |
46 | BFC 可应用:
47 | 1. 清除浮动(解决父级元素高度塌陷问题)
48 | 2. 解决垂直边距 margin 重叠
49 |
50 |
51 |
52 | 清除浮动的方法
53 |
54 | 1. after 伪元素清除浮动(推荐)
55 | 2. 额外标签清除浮动
56 | 3. before 和 after 双伪元素清除浮动
57 | 4. 父级元素添加 overflow 属性触发 BFC
58 | 5. 浮动父级元素
59 |
60 | (IE6/7不支持伪元素,需使用 `zoom: 1` 触发 hasLayout)
61 |
--------------------------------------------------------------------------------
/Note/ES6.md:
--------------------------------------------------------------------------------
1 | ### ES2020
2 | 1. globalThis
3 | 2. ?? 空合并符
4 | 3. ?. 可选链接符
--------------------------------------------------------------------------------
/Note/Git.md:
--------------------------------------------------------------------------------
1 | ### config 作用域
2 | ```shell
3 | // 配置:缺省等同于 local
4 | git config --local // 只对某个仓库生效
5 | git config --global // 对当前用户所有仓库生效
6 | git config --system // 对系统所有登陆的用户生效
7 |
8 | // 显示配置 --list
9 | git config --list
10 |
11 | // 设置用户名
12 | git config --global user.name 'KenTsang'
13 |
14 | ```
15 |
16 | | 命令 | 作用 |
17 | | -- | -- |
18 | | git init [FolderName] | 无 FolderName 会初始化当前文件夹,有 FolderName 会创建新文件夹并初始化项目 |
19 | | git add -u| 会把已经被git跟踪的文件的修改添加到暂存区域 |
20 | | git mv [oldFileName] [newFileName] | 重命名且提交至暂存区 |
21 | | git checkout -b branchName commitCode | 基于哪个更新版本创建分支 |
22 | | git checkout | 以暂存区为基础恢复工作区 |
23 | | git reset | 以HEAD为基础恢复暂存取 |
24 | | git reset --hard | 同时恢复暂存区/工作区(慎用),会删除掉一些commit |
25 | | git rm | 删除文件且提交至暂存区 |
26 | | git stash ||
27 |
28 | ### 标签
29 |
30 | ```shell
31 | // 附注标签
32 | git tag -a [TagName] -m "[Remark]"
33 |
34 | // 删除标签
35 | git tag -d [TagName]
36 | // 删除远程标签
37 | git push origin :refs/tags/[TagName]
38 |
39 | // 查看标签
40 | git tag -l // 所有
41 | git tag -l "*customer*" // 筛选
42 |
43 | // 推送标签
44 | git push origin [TagName]
45 |
46 | // 查看该标签所有commit
47 | git log --pretty=oneline [TagName]
48 |
49 | // vantop 打标签模版
50 | git tag -a test-tag-customer-service-v1.1-20201224 -m "[Remark]"
51 | ```
52 |
53 | ### 分支
54 |
55 | ```shell
56 | // 本地重命名
57 | git branch -m [OldBranchName] [NewBranchName]
58 |
59 | // 删除远程分支
60 | git push --delete origin [BranchName]
61 |
62 | //
63 | ```
64 |
65 |
--------------------------------------------------------------------------------
/Note/JS面试题.md:
--------------------------------------------------------------------------------
1 |
2 | ['1', '2', '3'].map(parseInt) what & why
3 |
4 | ```javascript
5 | ['10', '10', '10', '10'].map(parseInt);
6 | // [10, NaN, 2, 3];
7 | ```
8 | > parseInt(string[, radix]):int|NaN,解析一个字符串参数,并返回一个指定基数的整数
9 | * string: 被解析的值,如果参数不是一个字符串,则将其转换为字符串(toString), 字符串开头空白符会被省略
10 | * radix: 2-36 之间的整数,默认为 10
11 | * 返回一个整数或 `NaN`
12 |
13 | #### radix 为 `undefined/0/没有指定参数`时处理原则:
14 | 1. 如果字符串 string 以 `0x` 或者 `0X` 开头,则基数是 16(十六进制)
15 | 2. 如果字符串 string 以 `0` 开头,则基数是 8(八进制)或 10(十进制),具体情况由实现环境决定,ES5 规定是 10,但并不是所有浏览器都遵守,需明确指定 radix 参数
16 | 3. 如果字符串 string 以其它任何值开头,则基数是 10(十进制)
17 |
18 | > map(callback(currentValue[, index[, array]])[, thsArg]): newArray
19 |
20 | ```javascript
21 | parseInt('10', 0); // 10
22 | parseInt('10', 1); // NaN
23 | parseInt('10', 2); // 2
24 | parseInt('10', 3); // 3
25 |
26 | //
27 | parseInt('1', 0); // 1
28 | parseInt('2', 1); // NaN
29 | parseInt('3', 3); // NaN
30 | ```
31 |
32 |
33 |
34 |
35 |
36 | `var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]],10]`
37 | 将数组扁平化并去除重复数据,得到一个升序且不重复的数据
38 |
39 |
40 | ```javascript
41 | function flatten(arr) {
42 | return arr.reduce((acc, val) => {
43 | return acc.concat(Array.isArray(val) ? flatten(val) : val)
44 | }, [])
45 | }
46 |
47 | let result = Array.from(new Set(flatten(arr)));
48 | result = result.sort((a, b) => a - b);
49 | ```
50 |
51 |
52 |
53 |
54 | 请把两个数组 `['A1', 'A2', 'B1', 'B2', 'C1', 'C2']` 和 `['A', 'B', 'C']` 合并为 `['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C']`
55 |
56 |
57 | ```javascript
58 | let a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
59 | let a2 = ['A', 'B', 'C'];
60 |
61 | // 确保 a2 的 'A' 能排到 a1 的 'A2' 后面
62 | a2 = a2.map(val => val + '3');
63 |
64 | let a3 = [...a1, ...a2].sort(val) => {
65 | if (val.includes('3')) {
66 | return val.split('')[0];
67 | }
68 | return val;
69 | })
70 | ```
71 |
72 |
73 |
74 | JS 的重定向有哪些?
75 |
76 | ```javascript
77 | let url = 'http://wuliv.com';
78 |
79 | location.assign(url);
80 | location.href = url;
81 |
82 | window.location = url;
83 | top.location = url;
84 | self.location = url;
85 |
86 | window.location.href = url;
87 | ```
88 |
89 |
90 |
91 | `substr` 与 `substring` 的区别
92 |
93 | > substr(start[, length])
94 |
95 | > substring(start [, end])
96 |
97 | 相同:当有一个参数时,两者的功能是一样的,返回从 start 指定位置到字符串结束的子串
98 |
99 | 不同:有两个参数时
100 |
101 | 1. `substr(start, length)` 返回从 start 位置开始 length 长度的子串
102 | 2. `substr(start, length)` 当 length 为 0 或负数时,返回空字符串
103 | 3. `substring(start, end)` 返回从 start 位置开始到 end 位置的子串(不含 end)
104 | 4. `substring(start, end)` 使用 start/end 两者中较小值作为起始点
105 | 5. `substring(start, end)` start 或 end 为 NaN 或负数,那么将其替换为 0
106 | 6. str 是字符串时 `str.substring(start,end)` 和 `str.slice(start, end)` 等价
107 |
108 |
109 |
110 | `for...in` 特点及遍历顺序
111 |
112 | 特点:
113 | 1. 循环返回的值是数据结构的“键值名"
114 | 2. 遍历对象返回对象的 key 值,遍历数组返回数组的下标(key)
115 | 3. 不仅可遍历数字键名,还会遍历**原型上的值**和手动添加的其它键
116 | 4. 不可推出循环
117 |
118 | > 排序属性:也即数字属性,指对象中以数字为键名的属性,V8 中被称为 elements。
119 |
120 | > 常规属性:也即字符串属性,指对象中以字符串为键名的属性,V8 中被称为 properties
121 |
122 | **遍历顺序:ECMAScript 规范中定义 "数字属性应该按照索引值大小升序排列,字符串属性按照创建时的顺序升序排列**
123 |
124 |
125 |
126 |
127 | `for..of` 特点
128 |
129 | 特点:
130 | 1. 数据结构只要部署了 `Symbol.iterator` 属性,就被视为具有 iterator 接口,可用 `for...of` 循环
131 | 2. 可结合 `break/continue/throw/return` 退出寻
132 |
133 | 具备 iterator 接口的数据结构:
134 | 1. Array
135 | 2. Map
136 | 3. Set
137 | 4. String
138 | 5. arguments 对象
139 | 6. NodeList(dom 列表集合)
140 |
--------------------------------------------------------------------------------
/Note/ShareDoc/函数编程的应用与范式.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZengLingYong/Blog/dc2466ef2154d22c08607b63da312f7f4856d50a/Note/ShareDoc/函数编程的应用与范式.pptx
--------------------------------------------------------------------------------
/Note/macos.md:
--------------------------------------------------------------------------------
1 |
9 |
10 | ### ⌘ 键妙用
11 |
12 | 1. 移动图标:按住 ⌘ 键不放可移动状态栏图标
13 | 2. 查看和打开文件所在位置:按住 ⌘ 不放(显示完整路径/双击在Finder打开文件所在位置)
14 |
15 | ### 快捷键
16 |
17 | | 快捷键 | 功能 |
18 | | ------------- | ----------------------- |
19 | | ⌘ + Space | 调出 Spotlight |
20 | | ⌘ + H | 回到桌面 |
21 | | ⌃ + ⌘ + Q | 锁屏 |
22 | | ⌃ + ⌘ + Space | 调出 emoji 表情键盘 |
23 | | 按住 fn | 调出 TouchBar 功能键(F1+F12) |
24 | | ⇧ + ⌥ + K | 输入 |
25 |
26 | ### ⌥ 输入特殊符号
27 |
28 | | 快捷键 | 功能 |
29 | | ----- | --- |
30 | | ⌥ + Y | ¥ |
31 | | ⌥ + = | ≠ |
32 | | ⌥ + R | ® |
33 | | ⌥ + G | © |
34 | | ⌥ + P | π |
35 |
36 | ### 调整图片尺寸与类型
37 |
38 | 1. 双击图片预览打开
39 |
40 | 2. 双击图片预览打开 -> 导出 -> 修改格式
41 |
42 | ### 更改文件的默认打开方式
43 |
44 | 右键点击文件时按住 ⌥ 键:出现始终以此方式打开(推荐)
45 |
46 | ### 重命名多个文件
47 |
48 | 1. 选中需要重命名的所有文件→右键点击
49 | 2. 给10个项目重新命名→左上角点击
50 | 3. 格式→左下角输入重命名名称→右下角输入1→点击左下角重新命名→完成
51 |
52 | 
53 |
54 | ### 三指拖移
55 |
56 | ### 触发角
57 |
58 | ### 固定 APP 出现 Touch Bar 功能键(F1-F12)
59 |
60 | 除了上面的智能呼出F1-F12功能键以外,还有一个更智能的方法,那就是在固定的App出现Touch BarF1-F12功能键。像我一般会用到F1-F12功能键无非就是在浏览网页或用到WPS写文时,所以这个时候,在固定App出现F1-F12功能键就显得极为智能。
61 |
62 | 
63 |
64 | 打开系统设置→点击Keyboard→点击上方第三信息栏(?)→点击Fn图标→此时右边会跳出+、-号,点击+号→在弹出的App中选择你想要的添加图标→完成。此时点开你所添加的App后,Touch Bar就会出现F1-F12功能键啦。
65 |
66 | 而聊了那么多F1-F12的功能键,到底这12个键都有哪些功能呢?依次从F1-12为:亮度减小、亮度增加、显示所有窗口、应用程序、键盘背光减小、键盘背光加亮、上一曲、播放暂停、下一曲、静音、减小音量、增加音量,这12个常用功能,所以F1-F12功能键还是挺好用的,记得背下来哈。
67 |
68 | ### Spotlight 便捷查询
69 |
70 | 1. 便捷查询
71 | 2. 计算器计算
72 | 3. 查询天气
73 |
74 | 
75 |
76 | 在 Spotlight 输入栏直接输入想计算的数字后,Spotlight 就会智能的变成计算机,帮你得出想要的答案。而通常的+ - 以外,还能够输入*、/,进行乘、除。
77 |
78 | ### Finder 访达设置
79 |
80 | 1. 自定义工具栏
81 | 2. 默认目录窗口
82 | 3. 30天后移除废纸篓
83 |
84 | ### 空格预览
85 |
86 | 1. 单击空格(弹出预览窗口)
87 | 2. 长按空格(自动回弹窗口)
88 |
89 | ### 浏览器(谷歌浏览器)
90 |
91 | 1. ⌘ + shift + T 恢复关闭的页面
92 | 2. ⌘ + W 关闭标签
93 | 3. ⌘ + T 新开标签
94 | 4. 添加到 Dock
95 |
96 | ### App 推荐
97 |
98 | * 视屏播放器:IINA(万能播放器)
99 | * 压缩工具:Keka(可屏蔽os自带文件)
100 | * 窗口整理:Magnet(win桌面布局)
101 | * Markdown:Typora | MarkText
102 | * 日期工具:Itsycal(可显示日历)
103 | * 屏保工具:Aerial(4K屏保)
104 | * 截图工具:Snipaste(带贴图/取色器) | Jietu(标注/录制)
105 | * 多屏显示器鼠标焦点切换:CatchMouse
--------------------------------------------------------------------------------
/Note/react-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZengLingYong/Blog/dc2466ef2154d22c08607b63da312f7f4856d50a/Note/react-01.png
--------------------------------------------------------------------------------
/Note/react-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZengLingYong/Blog/dc2466ef2154d22c08607b63da312f7f4856d50a/Note/react-02.png
--------------------------------------------------------------------------------
/Note/react.md:
--------------------------------------------------------------------------------
1 | ### setState
2 | 1. 不可变值,`state` 只可在 `constructor` 初始值
3 | 2. 可能是异步更新
4 | * setTimeout/setInterval/自定义 DOM 事件时 `setState` 同步更新
5 | * 其它情况异步更新
6 | 3. 多次 `setState` 可能会被合并
7 | * 有时合并(对象),有时不合并(函数)
8 | * 默认传入对象,多次 `setState` 会合并,类似 `Object.assign`
9 | 4. batchUpdate 机制
10 | 5. transaction(事务)机制
11 |
12 | ```js
13 | // 类似 Vue 的 $nextTick
14 | this.setState({count: 1}, () => {
15 | console.log(this.state.count); // 1
16 | })
17 |
18 | // setState 第一个参数接收函数时,多次不合并(且同步)
19 | this.state = {count: 0}
20 | this.setState((prevState, props) => {
21 | return {
22 | count: prevState.count + 1
23 | }
24 | })
25 | this.setState((prevState, props) => {
26 | return {
27 | count: prevState.count + 1
28 | }
29 | })
30 | // this.state.count = 2
31 | ```
32 |
33 | ### 组件间通讯
34 | 1. 父子组件 props 传递(区别于 Vue 的 props/$emit)
35 | 2. 自定义事件(与 Vue 类似 $on/$emit)
36 | 3. Redux 和 Context
37 |
38 | > JSX 本质会被 babel 编译成 React.createElement(类 h 函数), 执行后返回 VNode(JSX 等同于 Vue 模版)
39 |
40 | (React 中规定组件必须大写开头,小写开头会被当作 HTML 标签)
41 |
42 | > 渲染列表为何使用 key: diff 算法中通过 tag 和 key 来判断是否是 sameNode
43 |
44 |
45 | ### 事件
46 | 1. 合成事件,需手动解决 `this` 绑定
47 | 2. `event.nativeEvent` 访问原生事件
48 | 3. `event.nativeEvent.currentTarget` 指向在 `document` 上(React17 版本是指向挂载节点 `id="root"`,即根 DOM)
49 |
50 |
51 | > 事件触发原理:事件会冒泡到 `document` 上,由 `document` 统一去接收事件,并合成事件对象 SyntheticEvent,并 `dispatchEvent` 触发对应事件回调函数
52 |
53 | * 类似命令式编程时代的事件委托,减少内存消耗,避免子元素频繁绑定解绑事件
54 | * 更好的兼容性和跨平台(旧版本 IE 事件绑定监听兼容问题)
55 | * 方便事件统一管理(如事务机制)
56 |
57 | ### VDom 和 diff
58 | 1. 只比较同一层级,不跨级比较
59 | 2. tag 不相同,直接删掉重建
60 | 3. tag 和 key 相同,则认为是相同节点,不再深度比较
61 |
62 | ### 生命周期
63 |
64 | ### 高级特性
65 | 1. 函数组件
66 | * Hook 之前无 state 跟生命周期钩子
67 | 2. 受控组件
68 | * 表单值 value/checked 受 state 控制
69 | * 需自行监听 onChange 更新 state 进而修改表单值
70 | 2. 非受控组件/[type=file]/富文本编辑器
71 | * defaultValue/defaultChecked
72 | * 需结合 ref 手动获取 DOM 节点和它的值
73 | 3. Portals (传送门)指定节点渲染
74 | * overflow: hidden
75 | * z-index 层级问题
76 | * 需要放在 body 第一层
77 | * 组件结构不变,事件冒泡节点不变
78 | 4. context(语言/主题)
79 | 5. 性能优化
80 | 6. HOC: 类似工厂模式,输入一个组件,输出一个组件
81 | 7. RenderProps: 通过一个函数将 class 组件的 state 作为 props 传递给函数组件
82 | 8. 异步组件
83 | * import()
84 | * React.lazy
85 | * React.Suspense
86 |
87 | ### HOC VS Render Props
88 | 1. HOC: 模式简单,会增加组件层级
89 | 2. Render Props: 代码简洁,学习成本较高
90 |
91 | ### 性能优化
92 | 父组件 state/props 更新,会触发自身的 render 方法,而 render 方法会触发其子组件的更新(即使传给子组件的数据没有更新)
93 |
94 | 1. `shouldComponentUpdate(nextProps, nextState){//...}` (SCU)
95 | 2. `PureComponent`(类组件)/`React.memo`(函数组件), 实现了浅比较的 SCU
96 | 3. immutable.js 不可变值
97 | * 彻底拥抱“不可变值”
98 | * 利用共享数据(非深拷贝)
99 | * 有学习及迁移成本
100 |
101 | ### React 和 Vue 区别
102 | 相同:
103 | 1. 视图层框架
104 | 2. 支持组件化
105 | 3. 使用 VDom 操作 DOM
106 |
107 | 区别:
108 | 1. React 使用 JSX 拥抱 JS,Vue 使用模版拥抱 html
109 | 2. React 函数式编程,Vue 声明式编程
110 | 3. React 更依赖底层 JS,Vue 封装成指令
111 |
112 | ### ReactFiber
113 | 1. patch 分为两阶段(JS与DOM渲染共用一个线程)
114 | 2. reconciliation 阶段:执行 diff 算法,纯 JS 计算
115 | 3. commit 阶段:将 diff 结果渲染 DOM
116 |
117 | > Fiber:将 reconciliation 阶段进行拆分成片段,DOM 需要渲染时暂停 diff,空闲时恢复,通过 window.requestIdleCallback 得知 DOM 需要渲染(该 API 可能存在浏览器兼容,不支持时则放弃 Fiber 机制)
118 |
119 | ### Hooks(16.8)
120 | 背景:复用公共逻辑,this 指向
121 |
122 | 1. useEffect 处理副作用,渲染完成后执行
123 | 2. useMemo 缓存计算值,参与渲染(渲染时处理)
124 | 3. useCallback 缓存函数
125 |
126 |
--------------------------------------------------------------------------------
/Note/vue.md:
--------------------------------------------------------------------------------
1 | 1. 插值只能放表达式,不能放 JS 语句
2 | 2. v-html 插入 html, 会覆盖子元素,有 xss 风险
3 |
4 | ### $nextTick
5 | 1. Vue 是异步渲染
6 | 2. data 改变后,DOM 不会立即渲染(多次改变会合并)
7 | 3. $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点
8 |
9 | ### mixin 缺点:
10 | 1. 变量来源不明确,不利于阅读
11 | 2. 多 mixin 方法或数据对象属性命名冲突,生命周期会全保留
12 | 3. mixin 和组件对应关系多对多,复杂度高
13 |
14 | ### Vue 组件通讯:
15 | 1. props 和 $emit(父子)
16 | 2. 自定义事件 event.$on/$emit(兄弟或跨层级)- V3 移除
17 | 3. Vuex
18 |
19 | ### Vue 高级特性:
20 | 1. 自定义 v-model
21 | 2. $nextTick
22 | 3. slot
23 | 4. 动态、异步组件
24 | 5. keep-alive
25 | 6. mixin
26 |
27 | ### Vue3
28 | 1. Teleport 传送门 (类似 React 的 Portals)
29 | ```html
30 |
31 | 弹窗
32 |
33 | ```
34 | 2. Fragments 组件可拥有多个根,无须显示用 Fragments 包裹(React)
35 | ```html
36 |
37 | Hello world.
38 |
39 |
40 |
41 | ```
42 | 3. emits 选项
43 | * 原生事件会触发两次
44 | * 更好的指示组件工作方式
45 | * 对象形式事件校验
46 | ```html
47 |
48 |
49 | 自定义事件
50 |
51 |
52 |
53 |
59 | ```
60 |
61 | #### 组合式 API:
62 | * 暴露给模板的 property 来源十分清晰,因为它们都是被组合逻辑函数返回的值
63 | * 不存在命名空间冲突,可以通过解构任意命名
64 | * 不再需要仅为逻辑复用而创建的组件实例
65 |
66 | #### 对比 React Hooks
67 | setup() 函数只会调用一次,React Hooks 依赖调用顺序
68 | * 无须顾虑调用顺序,可用于条件语句
69 | * 不会在每次渲染时重复执行
70 | * 自动的依赖跟踪可确保侦听器和计算属性准确,不用像 useEffect/useMemo 指定依赖值
71 | * 不存在内联处理函数导致子组件永远更新的问题,不需要 useCallback
72 |
73 | #### ref/reactive
74 | * ref: 基础类型具有响应性
75 | * reactive: 针对引用类型,一般是对象
76 | * toRefs: 将响应式对象每个属性都转为 ref
77 |
78 | ```js
79 | const name = ref('以乐之名');
80 | const person = reactive({
81 | name: '以乐之名',
82 | sex: '男'
83 | });
84 | ```
85 |
86 | watchEffect 在 mounted 前执行,若想在副作用函数中使用 DOM,则需在 onMounted 中使用 watchEffect
--------------------------------------------------------------------------------
/Note/webpack.md:
--------------------------------------------------------------------------------
1 | ### module/chunk/bundle
2 | 1. module: 各个源码文件,一切皆模块
3 | 2. chunk: 分析得出,多模块合并成的,如 entry/import/splitChunk
4 | 3. bundle: 最终的输出文件
5 |
6 | > module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字:我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。
7 |
8 | ### hash/chunkHash/contentHash
9 | * hash: 计算与整个项目构建相关
10 | * chunkHash: 计算与同一 chunk 内容相关
11 | * contentHash: 计算与文件内容本身相关
12 |
13 | ### webpackPrefetch/webpackPreload/webpackChunkName
14 | webpackPrefetch: 浏览器闲时下载文件
15 | webpackPreload: 父 chunk 加载时并行下载文件
16 | webpackChunkName: 动态 import 时以注释形式为 chunk 文件取名
17 | ```javascript
18 | import(/* webpackChunkName: "lodash" */ 'lodash');
19 | ```
20 |
21 | ### 开发/生产
22 | 1. 开发:cheap-module-eval-source-map
23 | 2. 生产:cheap-module-source-map
24 |
25 | | 生产 | 开发 |
26 | | -- | -- |
27 | | 优化 babel-loader | 自动刷新 |
28 | | IgnorePlugin | 热更新 |
29 | | noParse | DllPlugin |
30 | | happyPack | -- |
31 | | ParalleUglifyPlugin | -- |
32 | |
33 |
34 | 1. 抽离CSS MiniCssExtractPlugin
35 | 2. 抽离公共代码 splitChunk
36 | 3. 多入口文件,多个 HtmlWebpackPlugin 实例
37 |
38 |
39 | ### 性能优化 - 构建
40 | #### 1. 优化 babel-loader
41 | ```javascript
42 | {
43 | test: /\.js$/,
44 | loader: ['babel-loader?cacheDirectory'], // 开启缓存
45 | include: path.resolve(__dirname, 'src') // 明确范围
46 | // 排除范围,include 和 exclude 两者选一个即可
47 | // exclude: path.resolve(__direname, 'node_modules')
48 | }
49 | ```
50 |
51 | #### 2. IgnorePlugin 忽略无用文件(直接不引入,代码没有)
52 | 例如:moment 会支持多语言,如何只引入中文模块?
53 | ```javascript
54 | // 忽略 moment 下的 /locale目录
55 | new webpack.IgnorePlugin(/\.\/locale/, /moment/)
56 |
57 | // 业务代码中自行引入语言包
58 | import 'moment/locale/zh-cn'
59 | ```
60 |
61 | #### 3. noParse 避免重复打包
62 | ```javascript
63 | module: {
64 | noParse: [/react\.min\.js$/]
65 | }
66 | ```
67 |
68 | IgnorePlugin 与 noParse 区别:
69 | 1. IgnorePlugin 直接不引入,代码中没有
70 | 2. noParse (类似vue.min.js已经模块化处理过)引入,但不打包
71 |
72 | #### 4. happyPack 多进程打包(多核CPU)
73 | ```js
74 | const HappyPack = require('happypack');
75 |
76 | {
77 | rules: [
78 | {
79 | test: /\.js$/,
80 | use: ['happypack/loader?id=babel'],
81 | }
82 | ],
83 | plugin: [
84 | new HappyPack({
85 | id: 'babel',
86 | loaders: ['babel-loader?cacheDirectory']
87 | })
88 | ]
89 | }
90 | ```
91 | #### 5. ParalleUglifyPlugin 多进程压缩 JS
92 | * 项目较大,打包较慢,开启多进程可提高速度
93 | * 项目较小,打包很快,开启多进程会降低速度(进程开销)
94 |
95 | ```javascript
96 | // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
97 | const ParallelUglifyPlugin = require('ParallelUglifyPlugin')
98 |
99 | new ParallelUglifyPlugin({
100 | // 传递给 UglifyJS 的参数
101 | // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
102 | uglifyJS: {
103 | output: {
104 | beautify: false, // 最紧凑的输出
105 | comments: false // 删除所有的注释
106 | },
107 | compress: {
108 | // 删除所有的 `console` 语句,可以兼容ie浏览器
109 | drop_console: true,
110 | // 内嵌定义了但是只用到一次的变量
111 | collapse_vars: true,
112 | // 提取出出现多次但是没有定义成变量去引用的静态值
113 | reduce_vars: true
114 | }
115 | }
116 | })
117 |
118 | ```
119 |
120 | #### 6. 自动刷新 `watch: true`
121 | #### 7. 热更新(刷新时留存状态)
122 | 1. HotModuleReplacementPlugin
123 | 2. webpack-dev-server 自带 `hot: true`
124 |
125 | #### DllPlugin 动态链接库
126 | 1. DllPlugin - 打包出 dll 文件
127 | 2. DllReferencePlugin - 使用 dll 文件
128 | 3. 可借用 AutoDllPlugin 简化配置
129 | 4. Dll 加速不明显,可用 HardSourceWebpackPlugin 替代
130 |
131 | vue-cli/create-react-app 已移除,Webpack4 打包速度足够快不再需要使用 DllPlugin
132 |
133 |
134 | ### 性能优化 - 打包
135 | 1. 小图片 base64 编码(对图片使用 url-loader 配置 options.limit)
136 | 2. bundle 加 hash `[name].[contentHash:8].js`
137 | 3. 懒加载 `()=>import()`
138 | 4. 提取公共代码 `splitChunks` 公共代码和第三方库
139 | 5. IgnorePlugin 忽略文件不引入打包
140 | 6. 使用 CDN 加速 (output 配置 publicPath)
141 | * output 配置 publicPath
142 | * url-loader 中的 options 也加入 publicPath (设置图片)
143 | 7. 使用 production
144 | * 自动开启代码压缩
145 | * Vue/React 自动删掉调试代码(如开发环境的 warning)
146 | * 启动 Tree-Shaking (ES Module才可,CommonJS 不可)
147 | 8. Scope Hosting (多个函数合并一个函数,减少作用域)
148 | * 代码体积更小
149 | * 创建函数作用域更少
150 | * 代码可读性更好
151 |
152 | ### 模块化
153 | * ES Module 静态引用,编译时引入
154 | * Commonjs 动态引入,执行时引入
155 | * Tree-Shaking 只支持静态分析
156 |
157 | babel:
158 | 1. Polyfill —— 7.4弃用 补丁(core-js/regenerator)的集合
159 | 2. presets —— 预设,plugin 的集合
160 | 3. plugins —— 实际 babel 转换工作
161 | 4. Polyfill 7.4 弃用,直接使用 core-js/regenerator,会污染全局环境
162 | 5. babel-runtime 不会污染全局环境(开发第三方库)
163 | ```js
164 | {
165 | "presets": [
166 | [
167 | "@babel/preset-env" // 预设,plugin 的集合
168 | ]
169 | ],
170 | "plugins": [ // 实际 babel 转换是依赖多个 plugin 完成
171 | //...
172 | ]
173 | }
174 | ```
175 |
176 | ***
177 |
178 | 参考文章:
179 | * [webpack 中那些最易混淆的 5 个知识点](https://juejin.im/post/6844904007362674701)
180 | * [辛辛苦苦学会的 webpack dll 配置,可能已经过时了](https://juejin.im/post/6844903952140468232)
--------------------------------------------------------------------------------
/Note/事件循环.md:
--------------------------------------------------------------------------------
1 | 参考文章:
2 | * [十四、深入核心,详解事件循环机制](https://mp.weixin.qq.com/s/m3a6vjp8-c9a2EYj0cDMmg)
3 |
4 | 宏任务:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
5 | 微任务:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
6 |
7 | setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
8 |
9 | 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去
10 |
11 | 队列有多种:setTimeout/setImmediate/nextTick/Promise
12 |
13 |
--------------------------------------------------------------------------------
/Note/函数式编程范式.md:
--------------------------------------------------------------------------------
1 | 面向过程:步骤
2 | 面向对象:抽象、接口、类、继承
3 |
4 | 函数是一等公民:
5 | 1. 函数可以存储在变量中
6 | 2. 函数可作为参数
7 | 3. 函数可作为返回值
8 |
9 | ### 函数式编程(Functional Programming, FP)
10 |
11 | > 函数式编程:(Functional Programming, FP) 用来描述数据(函数)之间的映射,或者说是运算过程的抽象
12 |
13 | 学习函数式编程?
14 | 1. React/Vue3 拥抱函数式编程
15 | 2. 可以摒弃 this
16 | 3. 最大程度重用函数
17 | 4. 打包时可以更好的利用 tree shaking 过滤无用代码
18 | 5. 方便测试,并行处理
19 | 6. 第三方库支持:lodash/underscore/ramda
20 |
21 | 函数式编程:
22 | 1. 固定输入固定输出(纯函数)
23 | 2. 指的不是程序中的函数(方法),而是数学中的函数关系,也即映射关系
24 | 3. 描述数据(函数)之间的映射,即是对运算过程的抽象
25 |
26 | ### 高阶函数
27 |
28 | > 高阶函数:函数作为参数或函数作为返回值,作用屏蔽实现细节,抽象通用
29 |
30 | 常用高阶函数:`forEach/map/filter/every/some/find/reduce/sort`
31 |
32 | ##### 高阶函数的意义:
33 |
34 | * 抽象可以屏蔽掉细节,只需关注实现目标
35 | * 用来抽象通用问题
36 |
37 | ### 纯函数(固定的输入固定的输出)
38 |
39 | 副作用来源(所有外部交互都可能带来副作用):
40 |
41 | 1. 配置文件
42 | 2. 数据库
43 | 3. 获取用户的输入
44 |
45 | 优点:
46 | 1. 可缓存
47 | 2. 可测试
48 | 3. 并行处理
49 | * 在多线程环境下操作共享的内存数据很可能会出现意外情况
50 | * 纯函数不需要访问共享的内存数据,所以在并行环境下可任意运行纯函数(Web Worker)
51 |
52 | * 箭头函数声明的函数内部不支持使用 `arguments `
53 | * 形参个数 `funName.length`
54 |
55 | ```javascript
56 | // lodash 中 memoize 实现纯函数缓存
57 | const _ = require('lodash')
58 |
59 | function getArea(r) {
60 | console.log(r); // memoize 封装后对参数相同时缓存,仅输出一次
61 | return Math.PI * r * r
62 | }
63 | let getAreaWithMemory = _.memoize(getArea)
64 | getAreaWithMemory(4);
65 |
66 | // 自实现 memoize
67 | function memoize(fn) {
68 | let cache = {}
69 | return function() {
70 | let key = JSON.stringify(arguments);
71 | cache[key] = cache[key] || fn.apply(fn, arguments);
72 | return cache[key]
73 | }
74 | }
75 |
76 | let getAreaWithMemory = memoize(getArea)
77 | getAreaWithMemory(4);
78 | ```
79 |
80 | ### 柯里化(洋葱代码)
81 | 1. 当一个函数有多个参数时先传递一部分参数调用它(这部分参数以后永远不变)
82 | 2. 然后返回一个新的函数接收处理剩余的参数
83 | 3. **可把任意多元函数转化为一元或少元函数**
84 | 4. 函数粒度更小
85 |
86 | ```javascript
87 | // 柯里化
88 | function curry(fn) {
89 | return function curriedFn(...args) {
90 | if (args.length < fn.length) {
91 | return function() {
92 | return curriedFn(...args.concat(Array.from(arguments)))
93 | }
94 | }
95 | return fn(...args)
96 | }
97 | }
98 |
99 | // ES6精简
100 | function curry(fn) {
101 | return function curriedFn(...args) {
102 | if (args.length < fn.length) {
103 | return (...restArgs) => curriedFn(...args.concat(restArgs));
104 | }
105 | return fn(...args);
106 | }
107 | }
108 | ```
109 |
110 | ### 函数组合 `f(g(h(x))`
111 |
112 | 1. 将多个函数(执行过程)组合成新的函数,多个短管道组合成长管道,不考虑中间结果
113 | 2. 要满足结合律(既可以把 g 和 h 组合,也可把 f 和 g 组合,结合顺序不同得出结果相同)
114 | 3. **函数组合中每个函数需为一元函数,且最终组合后返回的函数也是一元函数**
115 | 4. 默认从右到左执行 `_.flowRight()`
116 |
117 | ```javascript
118 | // 函数组合(从右往左执行)
119 | function compose(...args) {
120 | return value => args.reverse().reduce((acc, fn) => fn(acc), value)
121 | }
122 | ```
123 |
124 | ```javascript
125 | // 函数组合调试(自制log函数)
126 | const log = v => {
127 | console.log(v);
128 | return v;
129 | }
130 |
131 | // log函数改进版(能明确函数调用tag)
132 | const trace = _.curry((tag, v) => {
133 | console.log(tag, v);
134 | return v;
135 | })
136 | ```
137 |
138 | ```javascript
139 | // 函数组合实际应用例子
140 | // NEVER SAY DIE => never-say-die
141 | const split = _.curry((sep, str) => _.split(str, sep));
142 | const join = _.curry((sep, arr) => _.join(arr, sep));
143 | const map = _.curry((fn, arr) => _.map(arr, fn));
144 | const f = _.flowRight(
145 | join('-'),
146 | trace('map后:'),
147 | map(_.toLower),
148 | trace('split后:'),
149 | split(' ')
150 | );
151 | f('NEVER SAY DIE'); // 'never-say-die'
152 | ```
153 |
154 | ### lodash/fp(为函数式编程提供的模块)
155 |
156 | * lodash 默认方法(数据优先,函数置后)
157 | * lodash/fp模块方法(函数优先,数据置后,自动柯里化)
158 |
159 | ```javascript
160 | // 普通方法
161 | _.map(['a', 'b', 'c'], _.toUpper);
162 |
163 | // lodash/fp
164 | const fp = require('lodash/fp')
165 | fp.map(fp.toUpper, ['a', 'b', 'c'])
166 | fp.map(fp.toUpper)(['a', 'b', 'c'])
167 |
168 | // lodash/fp 重写函数组合例子
169 | const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '));
170 | f('NEVER SAY DIE'); // 'never-say-die'
171 | ```
172 |
173 | > lodash 中 _.map 回调参数接收三个参数(parseInt问题),而 lodash/fp 中的 fp.map 回调函数接收一个参数
174 |
175 | ### Point Free(函数组合使用的模式)
176 |
177 | > Point Free: 把数据处理的过程定义成与数据无关的合成运算,无需用到代表数据的那个参数,只需把简单的运算步骤合成到一起。使用这种模式之前需定义一些辅助的基本运算函数
178 |
179 | - 无需指明处理的数据
180 |
181 | - 只需合成运算过程
182 |
183 | - 需定义一些辅助的基本运算函数
184 |
185 | ```javascript
186 | const f = fp.flowRight(
187 | fp.join('-'),
188 | fp.map(_.flowRight(_.first, .toLower)),
189 | fp.split('-')
190 | );
191 | ```
192 |
193 | ### 函子(Functor) —— 控制副作用
194 |
195 | > 容器:包含值和值的变形关系(这个变形关系就是函数)
196 |
197 | > 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象有 map 方法(可以运行一个函数对值进行处理)
198 |
199 | 1. 函数式编程的运算不直接操作值,而是由函子完成
200 |
201 | 2. 函子就是一个实现了 map 契约的对象
202 |
203 | 3. 可把函子想像成一个盒子,这个盒子封装了一个值
204 |
205 | 4. 通过给盒子的 map 方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理(值不对外公布,仅在盒子内部使用)
206 |
207 | 5. 最终 map 方法返回一个包含新值的盒子(函子)
208 |
209 | ```javascript
210 | class Container {
211 | static of(value) {
212 | return new Container(value)
213 | }
214 |
215 | constructor(value) {
216 | this._value = value
217 | }
218 |
219 | map(fn) {
220 | return Container.of(fn(this._value))
221 | }
222 | }
223 |
224 | let result = Container.of(5).map(x => x + 1).map(x => x * x)
225 | // Container [_value: 36]
226 |
227 | // 若 Container.of(null) 时,map 中传入的 fn 函数执行会出现异常(副作用产生),不符合纯函数的要求,可借助 MapBe 函子来解决
228 | ```
229 |
230 | ### MayBe 函子(处理 / “吃掉” 空值,处理函子副作用)
231 |
232 | 对空值情况作处理(控制副作用在允许的范围),不抛出异常
233 |
234 | ```javascript
235 | class MayBe {
236 | static of(value) {
237 | return new MayBe(value)
238 | }
239 |
240 | constructor(value) {
241 | this._value = value
242 | }
243 |
244 | map(fn) {
245 | return this.isNothing() ?
246 | MayBe.of(null) :
247 | MayBe.of(fn(this._value))
248 | }
249 |
250 | isNothing() {
251 | return this._value === null || this.value === undefined
252 | }
253 | }
254 |
255 | let result = MayBe.of(null).map(x => x + 1)
256 | ```
257 |
258 | 虽然 MayBe 函子对空值情况作处理,但对多次链式调用 map 时无法确定空值情况出现的位置信息,可借助 Either 函子解决
259 |
260 | ### Either 函子(捕获异常并记录信息)
261 |
262 | 1. Either 两者中的任何一个,类似于 `if...else` 的处理
263 |
264 | 2. 异常会让函数变的不纯,Either 函子可用来作异常处理
265 |
266 | ```javascript
267 | // 二选一需定义两个函子
268 |
269 | class Left { // 异常错误的函子
270 | static of(value) {
271 | return new Left(value)
272 | }
273 |
274 | constructor(value) {
275 | this._value = value
276 | }
277 |
278 | map() {
279 | return this // 不作处理
280 | }
281 | }
282 |
283 | class Right { // 正确处理的函子
284 | static of(value) {
285 | return new Right(value)
286 | }
287 |
288 | constructor(value) {
289 | this._value = value
290 | }
291 |
292 | map(fn) {
293 | return Right.of(fn(this._value))
294 | }
295 | }
296 |
297 | function parseJSON(str) {
298 | try {
299 | return Right.of(JSON.parseJSON(str))
300 | } catch(e) {
301 | // 若 Right 函子异常,则生成 Left 函子并传入需要记录的异常信息以便查看
302 | return Left.of({error: e.message})
303 | }
304 | }
305 | ```
306 |
307 | ### IO 函子(函数收集,延迟执行不纯操作)
308 |
309 | 1. IO 函子的 `_value` 是一个函数,这里把函数作为值来处理
310 |
311 | 2. IO 函子把不纯的动作(函数)存储到 `_value`中,延迟执行这个不纯的操作(惰性执行)
312 |
313 | 3. 包装返回纯的操作函数
314 |
315 | 4. 不纯的操作交给调用者来处理(甩锅)
316 |
317 | ```javascript
318 | const fp = require('lodash/fp')
319 |
320 | class IO {
321 | static of(x) {
322 | return new IO(() => x)
323 | }
324 |
325 | constructor(fn) {
326 | this._value = fn
327 | }
328 |
329 | map(fn) {
330 | return IO.of(fp.flowRight(fn, this._value))
331 | }
332 | }
333 |
334 | // 返回的包装函数是纯函数
335 | let result = IO.of(process).map(p => p.execPath)
336 | // 调用时函数可能出现异常(组合函数中有不纯函数操作)
337 | result._value()
338 | ```
339 |
340 | IO函子嵌套问题 `IO(IO(X))` => `._value()._value()`,可借助 Monad 函子扁平化解决
341 |
342 | ### Monad 函子(解决函子嵌套)
343 |
344 | * 变扁的 Pointed 函子
345 |
346 | * 一个函子如果具有 `join` 和 `of` 两个方法并遵守一些定律就是一个 Monad 函子
347 |
348 | * 返回值用 map
349 |
350 | * 返回函子用 flatMap 方法
351 |
352 | ### Task 函子(处理异步)
353 |
354 | > folktale 标准的函数式编程库,不同于 lodash,只提供一些函数式处理的操作,如:compose/curry,函子 Task/Either/Maybe
355 |
356 | ### Pointed 函子
357 |
358 | 1. 实现了 `of` 静态方法的函子
359 |
360 | 2. `of` 方法是为了避免使用 `new` 来创建对象,更深层的含义是 `of` 方法用来把值放到上下文 `Context` (把值放到容器中,使用 `map` 来处理)
361 |
362 | ```javascript
363 | class Container {
364 | static of(value) {
365 | return new Container(value)
366 | }
367 | //...
368 | }
369 | ```
370 |
--------------------------------------------------------------------------------
/Note/垃圾回收.md:
--------------------------------------------------------------------------------
1 | 参考文章:
2 | [从 4 个面试题了解「浏览器的垃圾回收」](https://mp.weixin.qq.com/s/hpMGNtHPN_T-Upg9V4L_Jg)
3 |
4 |
5 | 可达对象:
6 | 1. 可以访问到的对象就是可达对象(引用、作用域链)
7 | 2. 可达的标准就是从根出发是否能够被找到
8 | 3. 根可理解为全局变量对象
9 |
10 | > 垃圾回收会阻塞程序执行
11 |
12 | 常见回收算法:
13 | 1. 引用计数
14 | 2. 标记清除
15 | 3. 标记整理
16 |
17 | V8 垃圾回收算法:
18 | 1. 分代回收(新生代/老生代)
19 | 2. 标记清除
20 | 2. 标记整理
21 | 4. 增量标记(回收工作分片执行)
22 |
23 | V8 内存设限:32位: 800M / 64位: 1.5G
24 |
25 | * 左侧新生代 —— 32位: 8M / 64位: 16M
26 | * 右侧老生代 —— 32位: 700M / 64位: 1.4G
27 |
28 | > 新生代对象:存活时间短的对象,如局部变量
29 |
30 | ### 新生代对象回收(副垃圾回收器)
31 | 1. 回收过程采用复制算法 + 标记整理
32 | 2. 新生代内存区分为二个等大小空间: From/To
33 | 3. From: 使用空间,To: 空闲空间
34 | 4. 活动对象存储于 From 空间
35 | 5. From 快满后,标记整理后将活动对象拷贝至 To
36 | 6. From 与 To 交换空间完成释放(清除 From 空间,From 变成 To,To 变成 From)
37 |
38 |
39 | 回收细节:
40 | 1. 拷贝过程中可能出现晋升(新生代对象移动至老生代)
41 | 2. 一轮 GC 还存活的新生代需要晋升
42 | 3. To 空间使用率超过 25%,需将 To 空间所有新生代对象晋升,否则 To 转变为 From 后空间不太够用
43 | 4. 空间换时间(整片复制清除,From/To 切换)
44 |
45 | ### 老生代对象回收(主垃圾回收器)
46 | 1. 标记清除、标记整理、增量标记
47 | 2. 首先使用标记清除完成垃圾空间回收
48 | 3. 标记整理进行空间优化(老生代空间不足就会进行整理)
49 | 4. 增量标记进行效率优化(分片执行,无须整片执行,回收与程序频繁间隔切换执行)
50 |
--------------------------------------------------------------------------------
/Note/脚手架工具.md:
--------------------------------------------------------------------------------
1 | # Plop
2 | * 将 plop 模块作为项目开发依赖安装
3 | * 在项目根目录下创建一个 plpfile.js 文件
4 | * 在 plopfile.js 文件中定义脚手架任务
5 | * 编写用于生成特定类型文件的模版
6 | * 通过 plop 提供的 CLI 运行脚手架任务
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### Star
2 | > "自己喝酒吹过的牛皮,跪着都得含着泪去实现"
3 |
4 | 本站为博文首发,定期分享前端知识,不同于以往零散文章更新,系列更文走起。
5 |
6 | 文章阅过觉得不错,右上角动动手指,Star Star Star!!!
7 |
8 | 最新在整理设计模式系列,敬请期待。
9 |
10 | ***
11 |
12 | #### 前端进击的巨人系列
13 | * [前端进击的巨人(一):执行上下文与执行栈,变量对象](https://github.com/ZengLingYong/blog/issues/1)
14 | * [前端进击的巨人(二):栈、堆、队列、内存空间](https://github.com/ZengLingYong/blog/issues/2)
15 | * [前端进击的巨人(三):从作用域走进闭包](https://github.com/ZengLingYong/Blog/issues/16)
16 | * [前端进击的巨人(四):略知函数式编程](https://github.com/ZengLingYong/Blog/issues/17)
17 | * [前端进击的巨人(五):学会函数柯里化(curry) ](https://github.com/ZengLingYong/Blog/issues/18)
18 | * [前端进击的巨人(六):知否知否,须知this](https://github.com/ZengLingYong/Blog/issues/19)
19 | * [前端进击的巨人(七):走进面向对象,原型与原型链,继承方式](https://github.com/ZengLingYong/Blog/issues/20)
20 | * [前端进击的巨人(八):浅谈函数防抖与节流](https://github.com/ZengLingYong/Blog/issues/21)
21 | * [日积跬步,apply/call/bind 自我实现](https://github.com/ZengLingYong/Blog/issues/30)
22 |
23 | #### JavaScript设计模式系列
24 | * [JavaScript 设计模式(一):单例模式](https://github.com/ZengLingYong/Blog/issues/22)
25 | * [JavaScript 设计模式(二):策略模式](https://github.com/ZengLingYong/Blog/issues/23)
26 | * [JavaScript 设计模式(三):代理模式](https://github.com/ZengLingYong/Blog/issues/24)
27 | * [JavaScript 设计模式(四):适配器模式](https://github.com/ZengLingYong/Blog/issues/25)
28 | * [JavaScript 设计模式(五):迭代器模式](https://github.com/ZengLingYong/Blog/issues/26)
29 | * [JavaScript 设计模式(六):观察者模式与发布订阅模式](https://github.com/ZengLingYong/Blog/issues/27)
30 | * [JavaScript 设计模式(七):命令模式](https://github.com/ZengLingYong/Blog/issues/28)
31 | * [JavaScript 设计模式(八):组合模式](https://github.com/ZengLingYong/Blog/issues/29)
32 | * 马不停蹄地更新中。。。
33 |
34 | #### 读书笔记系列
35 | * [读书笔记(01) - JSON - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/3)
36 | * [读书笔记(02) - 可维护性 - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/4)
37 | * [读书笔记(03) - 性能 - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/5)
38 | * [读书笔记(04) - 错误监控 - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/6)
39 | * [读书笔记(05) - 事件 - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/7)
40 | * [读书笔记(06) - 语法基础 - JavaScript高级程序设计](https://github.com/ZengLingYong/blog/issues/8)
41 |
42 | #### 其它文章
43 | * [Mac终端配置,DIY你的Terminal(iTerm2 + Oh-My-Zsh)](https://github.com/ZengLingYong/blog/issues/12)
44 | * [preventDefault,stopPropagation,return false三者的区别](https://github.com/ZengLingYong/blog/issues/13)
45 | * [JS 中 if / if...else...替换方式](9)
46 | * [一道JS面试题引发的血案](https://github.com/ZengLingYong/blog/issues/11)
47 | * [杂谈:前端Web通信](https://github.com/ZengLingYong/blog/issues/15)
48 | * [杂谈:渐进增强与优雅降级](https://github.com/ZengLingYong/blog/issues/14)
49 | * [Vim 利剑常磨,见血封喉](https://github.com/ZengLingYong/blog/issues/12)
50 |
51 | #### 更新计划
52 | - [ ] 深浅拷贝
53 | - [ ] Promise
54 | - [ ] 生成器迭代器
55 | - [ ] 函数式编程(函数组合/函数柯里化)
56 | - [ ] 事件循环机制
57 | - [ ] 渲染机制(URL 输入开始)
58 | - [ ] 强缓存/协商缓存
59 | - [ ] 垃圾回收机制
60 | - [ ] HTTP 与 HTTPS
61 | - [ ] Vue 源码系列
62 | - [ ] CSS 系列
63 |
64 |
65 | #### 有问题来撩
66 | * [segmentfault: 以乐之名](https://segmentfault.com/u/yilezhiming)
67 | * [简书: 以乐之名](https://www.jianshu.com/u/052cec4cf325)
68 | * [个人Blog: Kenz's Blog](http://wuliv.com)
69 |
--------------------------------------------------------------------------------
/TypeScript/ts-learn-daily-0629.md:
--------------------------------------------------------------------------------
1 | > 基础类型补漏
2 |
3 | * number/string/boolean
4 | * any
5 | * unknown
6 | * 枚举
7 | * 元祖
8 | * void 无任何类型
9 | * never 永不存在(异常或不会有返回值),利用特性作全面类型检查
10 | * null/undefined 所有类型的子类型 --strictNullCheck 开启时,只能赋值给自身/void
11 |
12 | ```js
13 | // never
14 | function error(message: string): never {
15 | throw new Error(message);
16 | }
17 |
18 | function infiniteLoop(): never {
19 | while(true) {}
20 | }
21 | ```
22 |
23 | > 联合类型 string | number
24 | ```ts
25 | const sayHello = (name: string | void = '以乐之名') => {
26 | //...
27 | }
28 |
29 | say(); // '以乐之名'
30 | say('Ken'); // 'Ken'
31 | ```
32 |
33 | > 交叉类型 IPerson & IWorker
34 | 将多个类型合并为一个类型,包含了两个类型的所有特性
35 | ```ts
36 | interface IPerson {
37 | id: string;
38 | age: number;
39 | }
40 |
41 | interface IWorker {
42 | company: string
43 | }
44 |
45 | type IStaff = IPerson & IWorker;
46 |
47 | const staff: IStaff = {
48 | id: 1,
49 | age: 30,
50 | company: 'Ali'
51 | }
52 | ```
53 |
54 | > 函数可选参数
55 |
56 | ```ts
57 | function create(name: string, age?: number): string {
58 | //...
59 | }
60 | ```
61 |
62 | > 对象可选属性
63 | ```ts
64 | interface Person {
65 | readonly name: string;
66 | age?: number;
67 | }
68 | ```
69 |
70 | > 类静态属性和方法
71 | ```ts
72 | class Greeter {
73 | static name: string = '';
74 |
75 | static getName() {
76 | //...
77 | }
78 | }
79 | // 调用静态属性
80 | Greeter.name;
81 | Greeter.getName();
82 | ```
83 |
84 | > 私有字段 #
85 | * 私有字段以 # 开头
86 | * 唯一限定于其包含的类
87 | * 私有字段不能使用可访问性修饰符 public/private
88 | * 私有字段不能在包含的类之外访问,深圳不能被检测
89 |
90 | > 类型守卫
91 | * typeof 判断基础类型 number/string/boolean/symbol
92 | * instanceof 判断实例是否属于某个类
93 | * in 判断属性/方法是否属于某个对象
94 | * 字面量类型保护(自己造 type)
95 |
96 | > 类型断言
97 | as 断言条件:当 S 是 T 的子集,或 T 是 S 的子集,S 能被成功断言成 T,暴力解决使用双重断言
98 |
99 | ```ts
100 | function handler(event: Event) {
101 | const element = event as HTMLElement;
102 | // Error: 'Event' 和 ‘HTMLElement' 中任何一个不能赋值给另一个
103 | }
104 |
105 | // 双重断言暴力解决
106 | function handler(event: Event) {
107 | const element = (event as any) as HTMLElement;
108 | }
109 | ```
110 |
111 | > 可选链/空值联合
112 | * 可选链接 `worker?.person?.name`,避免 `a && a.b && a.b.c`
113 | * 空值联合 `let name = me ?? 'Ken'` `me` 为 `null`时 返回 `Ken`
114 |
115 | > type 与 interface 区别
116 | | type | interface |
117 | | -- | -- |
118 | | 通过 & 合并 | 通过 extends 扩展 |
119 | | 支持类型更多 | 只表达 object/class/function |
120 |
121 | > 泛型
122 | 考虑重用性,支持当前数据类型,也支持未来数据类型
123 | ```ts
124 | function identity(arg: T): T {
125 | return arg;
126 | }
127 | identity(1024);
128 | identity('1024KB');
129 |
130 | // 约束
131 | function identity(arg: T[]): T[] {
132 | console.log(arg.length);
133 | return arg;
134 | }
135 |
136 | interface LengthWise {
137 | length: number
138 | }
139 | function identity(arg: T): T {
140 | console.log(arg.length);
141 | return arg;
142 | }
143 | ```
144 |
145 | > 装饰器
146 | * 它是一个表达式
147 | * 表达式执行后,返回一个函数
148 | * 函数入参为 target、name 和 descriptor
149 | * 执行该函数后,可能返回 descriptor,用于配置 target 对象
150 | * 多个装饰器应用在一个声明上
151 | * 由上至下依次对装饰器表达式求值
152 | * 求值结果会被当作函数,由下至上依次调用
153 |
154 | ```ts
155 | function f() {
156 | console.log("f(): evaluated");
157 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
158 | console.log("f(): called");
159 | }
160 | }
161 |
162 | function g() {
163 | console.log("g(): evaluated");
164 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
165 | console.log("g(): called");
166 | }
167 | }
168 |
169 | class C {
170 | @f()
171 | @g()
172 | method() {}
173 | }
174 |
175 | /*
176 | f(): evaluated
177 | g(): evaluated
178 | g(): called
179 | f(): called
180 | */
181 | ```
182 |
183 | #### 类装饰器
184 | ```ts
185 | function Greeter(target: Function): void {
186 | target.prototype.greet = function(): void {
187 | console.log('Hi, man!');
188 | }
189 | }
190 |
191 | @Greeter
192 | class Greeting {}
193 | let myGreeting = new Greeting();
194 | myGreeting.greet(); // 'Hi, man!'
195 |
196 | // 带参数
197 | function Greeter(greeting: string) {
198 | return function(target: Function): void {
199 | target.prototype.greet = function(): void {
200 | console.log(greeting);
201 | }
202 | }
203 | }
204 | @Greeter('How are you doing?');
205 | class Greeting {}
206 | let myGreeting = new Greeting();
207 | myGreeting.greet(); // 'How are you doing?'
208 | ```
209 | #### 属性装饰器
210 | #### 方法装饰器
211 | #### 参数装饰器
212 |
213 | > 处理第三方库类型问题
214 | 1. react 中 module.hot, 安装 @types/webpack-env
215 | 2. 库本身没有类型定义,需自行声明 `declare module 'lodash'`
216 | 3. 类型声明报错, compileOptions 添加 skipLibCheck: true
217 | 4. 类型声明库有误,使用 //@ts-ignore 忽略
218 |
219 | > React 的 TypeScript 实践
220 | #### 定义组件
221 | ```ts
222 | // 函数声明
223 | function MHead(): React.ReactNode {
224 | return My Header
225 | }
226 |
227 | // 函数表达式
228 | const YHead: React.FC = () => My Header
229 | ```
230 | #### 约束 props
231 | ```ts
232 | interface MProps {
233 | name: string;
234 | color: string;
235 | }
236 |
237 | type Props = {
238 | name: string;
239 | color: string;
240 | }
241 |
242 | function MHead({name, color}: MProps): React.ReactNode {
243 | //...
244 | }
245 | const YHead: React.FC = ({name, color}) => My Header
246 | ```
247 | * 编写库/第三方环境类型定义时,用 interface 定义公共 API
248 | * 组件 state/props 使用 type
249 |
250 | #### 处理表单
251 | ```ts
252 | const MInput = () => {
253 | const [value, setValue] = useState();
254 |
255 | function onChange(e: React.ChangeEvent) {
256 | setValue(e.target.value)
257 | }
258 |
259 | return
260 | }
261 | ```
262 |
263 | #### 扩展 Props
264 | ```ts
265 | // type
266 | type ButtonProps {
267 | color: string;
268 | text: string;
269 | }
270 |
271 | type ContainerProps = ButtonProps & {
272 | height: number;
273 | }
274 |
275 | // interface
276 | interface ButtonProps {
277 | color: string;
278 | text: string;
279 | }
280 |
281 | interface ContainerProps extends Buttons {
282 | height: number;
283 | }
284 | ```
--------------------------------------------------------------------------------
/TypeScript/ts-learn-daily-0630.md:
--------------------------------------------------------------------------------
1 | > readonly vs const
2 | * 作为变量使用:const
3 | * 作为属性使用:readonly
4 |
5 | > 只读属性
6 | 对象属性只能在对象刚刚创建的时候修改其值
7 | ```ts
8 | interface Point {
9 | readonly x: number;
10 | readonly y: number;
11 | }
12 |
13 | let p1: Point = { x: 10, y: 20 };
14 | p1.x = 5; // error
15 | ```
16 |
17 | ReadonlyArray
18 |
19 | > 跳出额外属性检测
20 | 把对象赋值给另一个变量
21 | ```ts
22 | let squareOptions = { colour: 'red', width: 100 };
23 | let mySquare = createSquare(squareOptions);
24 | ```
25 |
26 | > 支持两种索引签名:字符串和数字
27 | 数字索引的返回值必须是字符串索引返回值类型的子类型,因为 JS 使用数字索引时会转换成字符串索引
28 | ```ts
29 | interface NumberDictionary {
30 | [index: string]: number;
31 | length: number;
32 | name: string; `name`的类型与索引类型返回值类型不匹配
33 | }
34 |
35 | // 将索引设为只读,防止给索引赋值
36 | interface ReadonlyStringArray {
37 | readonly [index: number]: string;
38 | }
39 | let myArray: ReadonlyStringArray = ['Alice', 'Bob'];
40 | myArray[2] = 'Ken'; // error
41 | ```
42 |
43 | > interface 定义类接口规范
44 | * 只针对类公共部分,不会检查类的私有成员
45 | * 只检测类的实例类型,constructor 属静态
46 | * 接口可通过 extends 继承多个接口
47 | * 接口可继承类(private/protected),该接口类型只能被这个类或其子类实现
48 |
49 | > 类
50 | * 默认为 public
51 | * private 不能在类外部访问
52 | * protected 在派生类中仍可访问,类外部不能访问
53 | * readonly 只读属性必须在声明时或构造函数中初始化
54 | * 参数属性方便我们在一个地方定义并初始化成员
55 | * 存取器 get/set,只带有 get 不带有 set 的存取器自动被推断为 readonly
56 | * 静态属性 static, 类.访问
57 | * 抽象类 abstract 包含成员的实现细节(不包括具体实现)必须在派生类中实现,抽象类不实例化,abstract 声明的抽象方法可没有具体实现,在派生类中实现
58 | * 类可当作接口使用
59 |
60 | #### 参数属性
61 | ```ts
62 | // 参数属性:声明赋值合并到一处
63 | class Animal {
64 | // public/protected 也通用
65 | constructor(private name: string) { }
66 | }
67 |
68 | // 等同于
69 | class Animal {
70 | private name: string;
71 | constructor(theName: string) {
72 | this.name = theName;
73 | }
74 | }
75 | ```
76 |
77 | #### 存取器
78 | ```ts
79 | // 存取器
80 | class Employee {
81 | private _fullName: string;
82 |
83 | get fullName(): string {
84 | return this._fullName;
85 | }
86 |
87 | set fullName(newName: string) {
88 | // 增加设置值的业务逻辑
89 | if (pwd == '***') {
90 | this._fullName = newName
91 | } else {
92 | throw new Error('密码不对')
93 | }
94 | }
95 | }
96 | ```
97 |
98 | #### 类作接口使用
99 | ```ts
100 | class Point {
101 | x: number;
102 | y: number;
103 | }
104 |
105 | interface Point3d extends Point {
106 | z: number
107 | }
108 |
109 | let point3d: Point3d = {x: 1, y: 2, z: 3};
110 | ```
111 |
112 | > 函数重载:定义重载时,最精确的定义放在最前面
113 | ```ts
114 | function pickCard(x: {suit: string; card: number;}[]): number;
115 | function pickCard(x: number): {suit: string; card: number; };
116 | function pickCard(x): any {
117 | if (typeof x === 'object') {
118 | //...
119 | }
120 |
121 | if (typeof x === 'number) {
122 | //...
123 | }
124 | }
125 |
126 | // pickCard(x): any 不是重载列表,只有两个重载:一个接受对象,一个接受数字
127 | ```
128 |
129 | > 解决 --noImplicitThis 报错 this
130 |
131 | #### 提供一个显示的 `this` 参数
132 | ```ts
133 | // this 是个假参数,出现在参数列表最前面
134 | function f(this: void) {
135 | // make sure `this` is unusable in this standalone function
136 | }
137 |
138 | interface People {
139 | say: () => void;
140 | }
141 | function f(this: People) {
142 | this.say();
143 | }
144 | ```
145 |
146 | #### 回调函数里的 `this` 参数
147 | * 箭头函数
148 | * 不用到 this 时,则两处都显示 `this` 参数为 `void`
149 |
150 | > 泛型:获取类型并作参数使用
151 | ```ts
152 | // 明确传入类型
153 | let output = identity('myIdentity');
154 |
155 | // 不传入类型,ts自动检测获取类型
156 | let output = identity('myIdentity');
157 | ```
158 |
159 | #### 调用签名的对象字面量来定义泛型函数
160 | ```ts
161 | // 字面量
162 | function identity(arg: T): T {
163 | return arg;
164 | }
165 |
166 | let myIdentity: {(arg: T): T} = identity;
167 |
168 | // 接口
169 | interface GenericIdentityFn {
170 | (arg: T): T;
171 | }
172 | ```
173 |
174 | #### 泛型约束
175 | ```ts
176 | interface LengthWise {
177 | length: number
178 | }
179 |
180 | function loginIdentity(arg: T): T {
181 | console.log(arg.length);
182 | return arg;
183 | }
184 | ```
185 |
186 | > 枚举
187 | * 异构枚举:混合字符串枚举和数字枚举,字符串枚举后的数字枚举需重新指定数字索引起始数字
--------------------------------------------------------------------------------
/Webpack/DllPlugin/README.md:
--------------------------------------------------------------------------------
1 | ## 安装初始化
2 | ```
3 | yarn install
4 | ```
5 |
6 | ## 单独打包构建 react/react-dom
7 | ```
8 | yarn build-react
9 | ```
10 |
11 | ## 构建项目
12 | ```
13 | yarn build
14 | ```
--------------------------------------------------------------------------------
/Webpack/DllPlugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DllPlugin",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build-react": "webpack --config webpack.react.config.js",
8 | "build": "webpack"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.6.0",
12 | "@babel/preset-env": "^7.6.0",
13 | "@babel/preset-react": "^7.0.0",
14 | "babel-loader": "^8.0.6",
15 | "html-webpack-plugin": "^3.2.0",
16 | "webpack": "^4.40.2",
17 | "webpack-cli": "^3.3.8"
18 | },
19 | "dependencies": {
20 | "react": "^16.9.0",
21 | "react-dom": "^16.9.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Webpack/DllPlugin/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Webpack/DllPlugin/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | const myName = '以乐之名';
5 |
6 | render({myName}
, document.getElementById('root'));
--------------------------------------------------------------------------------
/Webpack/DllPlugin/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const webpack = require('webpack');
4 |
5 | module.exports = {
6 | mode: 'development',
7 | entry: './src/index.js',
8 | output: {
9 | filename: 'bundle.js',
10 | path: path.resolve(__dirname, 'dist')
11 | },
12 | plugins: [
13 | new webpack.DllReferencePlugin({
14 | manifest: path.resolve(__dirname, 'dist', 'mainfest.json')
15 | }),
16 | new HtmlWebpackPlugin({
17 | template: './src/index.html',
18 | title: 'DllPlugin 单独打包第三方包 react'
19 | })
20 | ],
21 | module: {
22 | rules: [
23 | {
24 | test: /\.js$/,
25 | use: {
26 | loader: 'babel-loader',
27 | options: {
28 | presets: [
29 | '@babel/preset-env',
30 | '@babel/preset-react'
31 | ]
32 | }
33 | }
34 | }
35 | ]
36 | }
37 | }
--------------------------------------------------------------------------------
/Webpack/DllPlugin/webpack.react.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | mode: 'development',
6 | entry: {
7 | react: ['react', 'react-dom']
8 | },
9 | output: {
10 | filename: '_dll_[name].js',
11 | path: path.resolve(__dirname, 'dist'),
12 | library: '_dll_[name]'
13 | },
14 | plugins: [
15 | new webpack.DllPlugin({ // name === library
16 | name: '_dll_[name]',
17 | path: path.resolve(__dirname, 'dist', 'mainfest.json')
18 | })
19 | ]
20 | }
--------------------------------------------------------------------------------
/Webpack/Loader/babel-loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * babel-loader
3 | * 功能:转化 ES6
4 | * 包依赖:
5 | * 1. @babel/core
6 | * 2. @babel/preset-env
7 | * 3. loader-utils
8 | */
9 |
10 | let babel = require('@babel/core');
11 | let loaderUtils = require('loader-utils');
12 |
13 | function loader(source) {
14 | let options = loaderUtils.getOptions(this);
15 | let cb = this.async();
16 | babel.transform(source, {
17 | ...options,
18 | filename: this.resourcePath.split('/').pop(),
19 | sourceMaps: true
20 | }, (err, result) => {
21 | cb(null, result.code, result.map);
22 | })
23 | }
24 |
25 | module.exports = loader;
--------------------------------------------------------------------------------
/Webpack/Loader/banner-loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * banner-loader
3 | * 功能:向输出文件添加注释内容
4 | * 包依赖:
5 | * 1. loader-utils
6 | * 2. schema-utils 验证 options 参数
7 | */
8 |
9 | let loaderUtls = require('loader-utils');
10 | let validateOptions = require('schema-utils');
11 | let fs = require('fs');
12 |
13 | function loader(source) {
14 | this.cacheable && this.cacheable(false); // 不开启缓存
15 | let cb = this.async();
16 | let options = loaderUtls.getOptions(this);
17 | let schema = {
18 | type: 'object',
19 | properties: {
20 | text: {
21 | type: 'string',
22 | },
23 | filename: {
24 | type: 'string'
25 | }
26 | }
27 | }
28 | validateOptions(schema, options, 'banner-loader');
29 |
30 | if (options.filename) {
31 | // 将文件加入依赖,文件变化 webpack 会重新打包
32 | this.addDependency(options.filename);
33 | fs.readFile(options.filename, 'utf8', (err, data) => {
34 | cb(err, `/**${data}**/${source}`);
35 | })
36 | } else {
37 | cb(null, `/**${options.text}**/${source}`);
38 | }
39 | }
40 |
41 | module.exports = loader;
--------------------------------------------------------------------------------
/Webpack/Loader/file-loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * file-loader
3 | * 功能:根据文件生成一个 md5 发射到输出目录,返回当前文件路径
4 | * 包依赖:loader-utils
5 | */
6 |
7 | let loaderUtils = require('loader-utils');
8 |
9 | function loader(source) {
10 | let filename = loaderUtils.interpolateName(this, '[hash:8].[ext]', {content: source});
11 |
12 | this.emitFile(filename, source); // 发射文件到输出目录
13 | return `module.exports = "${filename}"`;
14 | }
15 | loader.raw = true; // source 已被转成二进制
16 |
17 | module.exports = loader;
--------------------------------------------------------------------------------
/Webpack/Loader/url-loader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * url-loader
3 | * 功能:小于限制以 base64 插入文件,否则调用 file-loader 执行
4 | * 包依赖:
5 | * 1. loader-utils
6 | * 2. mime
7 | * 3. file-loader
8 | */
9 |
10 | const loaderUtls = require('loader-utils');
11 | const mime = require('mime');
12 |
13 | function loader(source) {
14 | const { limit } = loaderUtls.getOptions(this);
15 |
16 | if (limit && limit > source.length) {
17 | return `module.exports="data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"`
18 | } else {
19 | return require('file-loader').call(this, source)
20 | }
21 | }
22 |
23 | loader.raw = true; // source 已被转成二进制
24 |
25 | module.exports = loader;
--------------------------------------------------------------------------------
/Webpack/README.md:
--------------------------------------------------------------------------------
1 | webpack 自身只处理 js 文件,其它文件格式需使用 loader 来转换为 webpack 能处理的有效文件,如 css、jpg|png、json 等。
2 |
3 | ## mode 模式
4 | 1. development
5 | 2. production
6 | 3. none
7 |
8 | 默认打包方式为 production,会启用 uglifyjs 对 js 代码压缩。
9 |
10 | ### NODE_ENV
11 | webpack.config.js 无法直接配置 process.env.NODE_ENV 的值,但可以使用 webpack.DefinePlugin 为所有依赖指定该环境值。
12 |
13 | ```js
14 | module.exports = {
15 | //...
16 | plugins: [
17 | new webpack.DefinePlugin({
18 | 'process.env.NODE_ENV': JSON.stringify('production');
19 | }),
20 | ]
21 | }
22 | ```
23 |
24 | ## loader 加载器
25 | loader 本身是一个函数,用于对模块的源代码进行转换,在配置文件的 module.rules 中配置 loader。
26 |
27 | ### loader 特性
28 | 1. 执行顺序:默认从右到左,从下到上
29 | 2. 支持链式传递,支持同异步,命名约定为 `xxx-loader`
30 |
31 | | 常用 loader | 功能 |
32 | | -- | -- |
33 | | css-loader | 处理 CSS 文件,如 @import/url 等 |
34 | | style-loader | 处理 CSS 文件插入到 html 中 |
35 | | post-loader | 补全浏览器前缀,配合 autoprefixer 使用,并先于 css-loader 执行,额外配置文件 .bowerlistrc/postcss.config.js |
36 | | less-loader | 配合 less 处理 |
37 | | sass-loader | 配合 sass 处理 |
38 | | babel-loader | 处理 ES6 转换,@babel/core @babel/preset-env @babel/plugin-transform-runtime |
39 | | eslint-loader | 处理语法校验 |
40 | | file-loader | 生成文件到 build 目录,并返回地址 |
41 | | url-loader | 能限制图片大小转换 base64,超出则类似 file-loader |
42 | | html-withimg-loader | 处理 html 中的图片命名,与 file-loader 同步 |
43 | | expose-loader | 暴露给全局对象 |
44 |
45 | ### loader 类型:
46 | * 前置 loader `enforce: 'pre'` 优先执行
47 | * 后置 loader `enforce: 'post'` 延后执行
48 | * 普通 loader
49 | * 内联 loader `expose?$!jquery`
50 | * 行内 loader `inline-loader!./a.js`
51 |
52 | #### 行内 loader
53 | | 方式 | |
54 | | -- | -- |
55 | | 默认使用 | `inline-loader!./a.js` |
56 | | pre + normal 不处理 | `-!inline-loader!./a.js` |
57 | | normal 不处理 | `!inline-loader!./a.js` |
58 | | 仅 inline-loader 执行 | `!!inline-loader!./a.js` |
59 |
60 | #### loader 组成
61 | 1. pitch
62 | 2. normal
63 |
64 | ```js
65 | // 3 个 loader -> [1, 2, 3]
66 |
67 | // 顺序:pithch: 1 -> 2 -> 3 -> 资源 -> normal: 3 -> 2 -> 1
68 | ```
69 |
70 | pitch 两种情况:
71 | 1. 无返回值,继续后续执行
72 | 2. 有返回值,阻断后续执行,例如 pitch 2 有返回值,将会执行 normal 3
73 |
74 | ```js
75 | loader2.pitch = fn () {
76 | //... 有返回值,loader2 不会执行,执行 loader1
77 | //... 无返回值,执行 loader3 -> loader2 -> loader1
78 | }
79 | ```
80 |
81 | ### 常用 loader 源码实现
82 |
83 | 
84 |
85 | [查看更多](https://github.com/ZengLingYong/Blog/tree/master/Webpack/Loader)
86 |
87 |
88 | ## plugin
89 | plugin 本身是一个具有 `apply()` 方法的类,目的在于解决 loader 无法实现的其它问题。
90 |
91 | | 常用 plugin | 功能 |
92 | | -- | -- |
93 | | html-webpack-plugin | 打包生成 html 文件 |
94 | | mini-css-extract-plugin | 将 CSS 文件抽离成单独文件以 link 方式插入到 html 中 |
95 | | optimize-css-assets-plugin | 处理 CSS 压缩,使用该插件后,需自行处理 js 压缩 |
96 | | uglify-js-plugin | 处理 js 压缩 |
97 | | clean-webpack-plugin | 处理每次打包清除指定文件夹,默认是 dist |
98 | | copy-webpack-plugin | 处理文件拷贝 |
99 | | happpack | 多线程打包 |
100 |
101 | webpack 本身也集成了部分常用的 plugin
102 |
103 | | webpack/plugin | 功能 |
104 | | -- | -- |
105 | | BannerPlugin | 向 js 文件插入注释内容 |
106 | | IgnorePlugin | 忽略打包指定引用文件,如屏蔽 moment 全语言包引入 |
107 | | DllPlugin | 用于第三方分离打包 |
108 | | DefinePlugin | 定义环境变量 |
109 | | ProvidePlugin | 暴露模块,不必通过 import/require 引用 |
110 |
111 | ## 模块解析
112 |
113 | ### resolve
114 |
115 | 功能:
116 | 1. 处理模块查找位置;
117 | 2. 处理 import 后缀简写;
118 | 3. 处理模块默认引用文件,默认为 main.js
119 |
120 | 配置方式:
121 | * modules
122 | * mainFields
123 | * mainFiles
124 | * alias
125 | * estensions
126 |
127 | ```js
128 | module.exports = {
129 | //...
130 | resolve: {
131 | // 指定模块查找目录,当前 node_modules 目录下查找,不指定时则会一直向上查找
132 | modules: [path.resolve('node_modules')],
133 | // 指定目录顺序,先在 style 目录下找,找不到再到 main 目录
134 | mainFields: ['style', 'main'],
135 | // 指定入口文件为 index.js
136 | mainFiles: ['index.js'],
137 | // 指定别名对应的引入文件
138 | alias: {
139 | bootstrap: 'bootstrap/dist/css/bootstrap.css'
140 | },
141 | // 省略文件扩展名,默认按序查找引用 import HomePage from './homePage'
142 | extensions: ['.vue', '.js', '.css']
143 | }
144 | }
145 | ```
146 |
147 | ### resolveLoader
148 | 功能:处理 loader 查找位置
149 |
150 | 配置方式:
151 | * modules
152 | * alias
153 |
154 | ```js
155 | module.exports = {
156 | //...
157 | resolveLoader: {
158 | // 指定 loader 查找目录,node_modules 找不到,再去 loaders 目录查找
159 | modules: [path.resolve('node_modules'), path.resolve(__dirname, 'loaders')],
160 | // 通过别名的方式指定 loader 文件路径
161 | alias: {
162 | myLoader: path.resolve(__dirname, 'loaders', 'myLoader.js')
163 | }
164 | }
165 | }
166 | ```
167 |
168 | ## devtool 的 sourceMap
169 | | 类型 | 功能 |
170 | | -- | -- |
171 | | source-map | 增加映射文件,大而全 |
172 | | eval-source-map | 无单独文件,可显示行列 |
173 | | cheap-module-source-map | 单独映射文件,显示行不显示列 |
174 | | cheap-module-eval-source-map | 无单独文件,有行无列 |
175 |
176 |
177 | ## 全局变量引入,
178 | * expose-loader 暴露到 window 上;
179 | * webpack.ProvidePlugin 暴露到每个模块;
180 | * CDN 外链与 import 共用时,使用 exteneral 避免重复打包
181 |
182 | ### expose-loader 暴露 $ 到 window 对象
183 | ```js
184 | import $ from 'expose-loader?$!jquery';
185 | console.log(window.$);
186 | ```
187 |
188 | 或在 webpack 中配置
189 | ```js
190 | module.exports = {
191 | //...
192 | module: {
193 | rules: [
194 | {
195 | test: require.resolve('jquery'),
196 | use: 'expose-loader?$
197 | }
198 | ]
199 | }
200 | }
201 |
202 | // 正常使用
203 | import $ from 'jquery'
204 | ```
205 |
206 | ### webpack.ProvidePlugin 暴露到每个模块
207 | ```js
208 | module.exports = {
209 | //...
210 | plugins: [
211 | new webpack.ProvidePlugin({
212 | $: 'jquery'
213 | })
214 | ]
215 | }
216 |
217 | // 各模块可直接使用,无需 import/require引入
218 | ```
219 |
220 | ### CDN 引用避免重复引用打包
221 | ```js
222 | module.exports = {
223 | //...
224 | externals: {
225 | jquery: 'jQuery'
226 | }
227 | }
228 | ```
229 |
230 |
231 | ## 前后端接口调试
232 | 1. 代理 proxy
233 | 2. mock 数据 before(app) {...}
234 | 3. 中间件共享端口 webpack-dev-middleware
235 |
236 |
237 | ## webpack-merge 区分打包环境
238 |
239 | ```js
240 | // webpack.common.js
241 | module.exports = {
242 | entry: './src/index.js',
243 | output: {
244 | filename: 'bundle.js',
245 | path: path.resolve(__dirname, 'dist)
246 | }
247 | }
248 | ```
249 |
250 | ```js
251 | // webpack.dev.js
252 | const webpackMerge = require('webpack-merge');
253 | module.exports = webpackMerge(common, {
254 | mode: 'development',
255 | devServer: {
256 | port: '8080',
257 | contentBase: 'dist
258 | })
259 | ```
260 |
261 | ```js
262 | // webpack.prod.js
263 | const webpackMerge = require('webpack-merge');
264 | module.exports = webpackMerge(common, {
265 | mode: 'production',
266 | optimization: {
267 | minimize: true
268 | },
269 | })
270 | ```
271 |
272 | ```
273 | // 开发环境构建
274 | webpack --config webpack.dev.js
275 | // 生成环境构建
276 | webpack --config webpack.prod.js
277 | ```
278 |
279 | ## webpack 优化
280 |
281 | ### webpack 内置优化
282 | * tree-shaking
283 | * scope hosting 作用域提升
284 |
285 | ### 生产环境代码压缩
286 | ```js
287 | // 处理 js/css 压缩
288 | module.exports = {
289 | //...
290 | optimization: {
291 | minimizer: [
292 | new UglifyJsPlugin({
293 | cache: true,
294 | parallel: true,
295 | sourceMap: true
296 | }),
297 | new OptimizeCSSAssetsPlugin()
298 | ]
299 | },
300 | }
301 | ```
302 |
303 | ### 抽取公共代码(多页面)
304 | ```js
305 | module.exports = {
306 | //...
307 | optimization: {
308 | splitChunks: {
309 | cacheGroups: {
310 | common: {
311 | chunks: 'initial',
312 | name: 'common'
313 | },
314 | vendor: {
315 | chunks: 'initial',
316 | test: /node_modules/,
317 | name: 'vendor',
318 | priority: 1
319 | }
320 | }
321 | }
322 | }
323 | }
324 | ```
325 |
326 | ### noParse 不解析包的依赖
327 | ```js
328 | import $ from 'jquery
329 | ```
330 |
331 | webpack 默认会去解析引用的包是否有依赖,如上 webpack 会去解析 jquery 这个包是否还有其它依赖项,但 jquery 是个独立的包,并没有其它依赖,因此可使用 noParse 让 webpack 构建时屏蔽对 jquery 包中依赖项进行检查和解析。
332 |
333 | ```js
334 | module.exports = {
335 | //...
336 | module: {
337 | noParse: /jquery/, // 不去解析 jquery 中的依赖关系
338 | //...
339 | }
340 | }
341 | ```
342 |
343 | ### include/exclude
344 | 缩小 loader 处理文件的范围:include 包含 / exclude 排除
345 |
346 | ```js
347 | module.exports = {
348 | //...
349 | module: {
350 | rules: [
351 | {
352 | test: /\.js$/,
353 | include: path.resolve('src'),
354 | exclude: /node_modules/,
355 | use: {
356 | loader: 'babel-loader',
357 | options: {
358 | preset: ['@babel/preset-env']
359 | }
360 | }
361 | },
362 | ],
363 | }
364 | }
365 | ```
366 |
367 | ### webpack.IgnorePlugin 忽略依赖文件
368 | 针对 moment 语言包全部引入的情况下,使用后需手动引入需要的语言包文件。
369 | ```js
370 | // 使用 moment
371 | import moment from 'moment';
372 | import 'moment/locale/zh-cn';
373 |
374 | moment.locale('zh-cn'),
375 | moment().endOf('day').fromNow();
376 | ```
377 |
378 | ```js
379 | // webpack 配置
380 | module.exports = {
381 | //...
382 | plugins: [
383 | new webpack.IgnorePlugin(/\.\/locale/, /moment/),
384 | // 如果从 moment 中 引入了 ./locale/* 文件,就会自动屏蔽构建
385 | ]
386 | }
387 | ```
388 |
389 | ### DllPlugin 单独打包第三方公共模块
390 | 针对第三包,在开发环境时无需频繁构建,因此可针对第三包单独构建,并生成映射。
391 |
392 | [查看源码](https://github.com/ZengLingYong/Blog/tree/master/Webpack/DllPlugin)
393 |
394 | ### happypack 多线程打包
395 | ```js
396 | const Happypack = require('happypack');
397 |
398 | module.exports = {
399 | //...
400 | plugins: [
401 | new Happpack({
402 | id: 'css',
403 | use: ['style-loader', 'css-loader']
404 | }),
405 | ],
406 | module: {
407 | rules: [
408 | { test: /\.css$/, use: 'Happypack/loader?id=css' },
409 | ],
410 | }
411 | }
412 | ```
413 |
414 | ## webpack 事件机制
415 |
416 | Webpack 事件流机制核心:Tapable,类似 NodeJs 的 events 库,原理是 "发布订阅模式"
417 |
418 | ```js
419 | const {
420 | SyncHook,
421 | SyncBailHook,
422 | SyncWaterfallHook,
423 | SyncLoopHook,
424 | AsyncParalleHook,
425 | AsyncParelleBailHook,
426 | AsyncSeriesHook,
427 | AsyncSeriesBailHook,
428 | AsyncSeriesWaterfailHook
429 | } = require('tapable');
430 | ```
431 |
432 | | Tabable 钩子 | 功能 |
433 | | -- | -- |
434 | | SyncHook | 同步钩子,顺序执行 forEach |
435 | | SyncBailHook | 同步容断钩子,当某个tap事件有返回值时(非 undefined),中断后续执行 do...while |
436 | | SyncWaterfallHook | 同步瀑布钩子,钩子回调函数数据往下传递 reduce |
437 | | SyncLoopHook | 同步多次执行,当某个tap事件有返回值时(非 undefined),循环执行
438 | | AsyncParalleHook | 异步并行钩子,每个tapAsync事件调用回调,所有都执行方可执行caaSync的回调 |
439 | | AsyncParalleBailHook | 异步并行容断钩子 |
440 | | AsyncSeriesHook | 异步串行钩子 |
441 | | AsyncSeriesBailHook | 异步串行容断钩子 |
442 | | AsyncSeriesWaterfallHook | 异步串行瀑布钩子 |
443 |
444 | * Sync 同步钩子使用 `tap/call`
445 | * Async 异步钩子使用 `tapAsync/callAsync` 和 `tapPromie/promise`
446 |
447 | [查看源码](https://github.com/ZengLingYong/Blog/tree/master/Webpack/Tabable)
--------------------------------------------------------------------------------
/Webpack/Tabable/AsyncParalleHook-cb.js:
--------------------------------------------------------------------------------
1 | class AsyncParalleHook {
2 | constructor(...args) {
3 | this.tasks = []
4 | }
5 |
6 | tapAsync(name, task) {
7 | this.tasks.push(task)
8 | }
9 |
10 | callAsync(...params) {
11 | let finalCallback = params.pop();
12 | let len = this.tasks.length;
13 | let index = 0;
14 |
15 | let done = () => {
16 | if (++index == len) {
17 | finalCallback();
18 | }
19 | }
20 |
21 | this.tasks.map(task => {
22 | task(...params, done)
23 | })
24 | }
25 | }
26 |
27 | let count = 0;
28 | let { log } = console;
29 | let hook = new AsyncParalleHook(['name']);
30 | hook.tapAsync('node', (name, cb) => {
31 | setTimeout(() => {
32 | log('node', name);
33 | cb();
34 | }, 1000)
35 | });
36 | hook.tapAsync('react', (name, cb) => {
37 | setTimeout(() => {
38 | log('react', name);
39 | cb();
40 | }, 1000)
41 | })
42 |
43 | hook.callAsync('KenTsang', () => {
44 | log('end');
45 | });
46 |
--------------------------------------------------------------------------------
/Webpack/Tabable/AsyncParalleHook-promise.js:
--------------------------------------------------------------------------------
1 | class AsyncParalleHook {
2 | constructor(...args) {
3 | this.tasks = []
4 | }
5 |
6 | tapPromise(name, task) {
7 | this.tasks.push(task);
8 | }
9 |
10 | promise(...params) {
11 | let tasks = this.tasks.map(task => task(...params));
12 | return Promise.all(tasks);
13 | }
14 | }
15 |
16 | let count = 0;
17 | let { log } = console;
18 | let hook = new AsyncParalleHook(['name']);
19 | hook.tapPromise('node', name => {
20 | return new Promise((resolve, reject) => {
21 | setTimeout(() => {
22 | log('node', name);
23 | resolve();
24 | }, 1000)
25 | })
26 | });
27 | hook.tapPromise('react', (name, cb) => {
28 | return new Promise((resolve, reject) => {
29 | setTimeout(() => {
30 | log('react', name);
31 | resolve();
32 | }, 1000)
33 | })
34 | })
35 |
36 | hook.promise('KenTsang').then(() => {
37 | log('end');
38 | });
--------------------------------------------------------------------------------
/Webpack/Tabable/AsyncSeriesHook-cb.js:
--------------------------------------------------------------------------------
1 | class AsyncSeriesHook {
2 | constructor () {
3 | this.tasks = [];
4 | }
5 |
6 | tapAsync(name, task) {
7 | this.tasks.push(task);
8 | }
9 |
10 | callAsync(...params) {
11 | const finalCallback = params.pop();
12 | let index = 0, len = this.tasks.length;
13 | let next = () => {
14 | if (index === len) {
15 | return finalCallback();
16 | }
17 | this.tasks[index++](...params, next);
18 | }
19 | next();
20 | }
21 | }
22 |
23 | let { log } = console;
24 | let hook = new AsyncSeriesHook(['name']);
25 | hook.tapAsync('node', (name, cb) => {
26 | setTimeout(() => {
27 | log('node', name);
28 | cb();
29 | }, 1000);
30 | })
31 | hook.tapAsync('react', (name, cb) => {
32 | setTimeout(() => {
33 | log('react', name);
34 | cb();
35 | }, 1000);
36 | })
37 | hook.callAsync('KenTsang', () => {
38 | log('end');
39 | })
--------------------------------------------------------------------------------
/Webpack/Tabable/AsyncSeriesHook-promise.js:
--------------------------------------------------------------------------------
1 | class AsyncSeriesHook {
2 | constructor () {
3 | this.tasks = [];
4 | }
5 |
6 | tapPromise(name, task) {
7 | this.tasks.push(task);
8 | }
9 |
10 | promise(...params) {
11 | let [firstTask, ...otherTask] = this.tasks;
12 | return otherTask.reduce((prev, next) => {
13 | return prev.then(() => next(...params));
14 | }, firstTask(...params))
15 | }
16 | }
17 |
18 | let { log } = console;
19 | let hook = new AsyncSeriesHook(['name']);
20 | hook.tapPromise('node', name => {
21 | return new Promise((resolve, reject) => {
22 | setTimeout(() => {
23 | log('node', name);
24 | resolve()
25 | }, 1000);
26 | })
27 | })
28 | hook.tapPromise('react', name => {
29 | return new Promise((resolve, reject) => {
30 | setTimeout(() => {
31 | log('react', name);
32 | resolve();
33 | }, 1000);
34 | })
35 | })
36 | hook.promise('KenTsang').then(() => {
37 | log('end');
38 | })
--------------------------------------------------------------------------------
/Webpack/Tabable/AsyncSeriesWaterfallHook-cb.js:
--------------------------------------------------------------------------------
1 | class AsyncSeriesWaterfallHook {
2 | constructor () {
3 | this.tasks = [];
4 | }
5 |
6 | tapAsync(name, task) {
7 | this.tasks.push(task);
8 | }
9 |
10 | callAsync(...params) {
11 | const finalCallBack = params.pop();
12 | let index = 0;
13 |
14 | let next = (err, data) => {
15 | let task = this.tasks[index];
16 | if (err != null || !task) {
17 | return finalCallBack();
18 | }
19 |
20 | if (index === 0) {
21 | task(...params, next);
22 | } else {
23 | task(data, next);
24 | }
25 |
26 | ++index;
27 | }
28 | next();
29 | }
30 | }
31 |
32 | let { log } = console;
33 | let hook = new AsyncSeriesWaterfallHook(['name']);
34 | hook.tapAsync('node', (name, cb) => {
35 | setTimeout(() => {
36 | log('node', name);
37 | cb(null, '学得还行');
38 | }, 1000);
39 | })
40 | hook.tapAsync('react', (name, cb) => {
41 | setTimeout(() => {
42 | log('react', name);
43 | cb();
44 | }, 1000);
45 | })
46 | hook.callAsync('KenTsang', () => {
47 | log('end');
48 | })
--------------------------------------------------------------------------------
/Webpack/Tabable/SyncBailHook.js:
--------------------------------------------------------------------------------
1 | // 同步容断性钩子,比如学得太累学不下去了,可中断
2 | class SyncBailHook {
3 | constructor(args) {
4 | this.task = [];
5 | }
6 |
7 | tap(name, task) {
8 | this.task.push(task);
9 | }
10 |
11 | call(...args) {
12 | let ret, index = 0, len = this.task.length;
13 | do {
14 | ret = this.task[index++](...args);
15 | } while(!ret && index < len);
16 | }
17 | }
18 |
19 | // 使用
20 | let hook = new SyncBailHook(['name']);
21 | hook.tap('react', name => {
22 | console.log('react', name);
23 | return '学不下去了';
24 | });
25 | hook.tap('node', name => {
26 | console.log('node', name);
27 | })
28 | hook.call('KenTsang');
--------------------------------------------------------------------------------
/Webpack/Tabable/SyncHook.js:
--------------------------------------------------------------------------------
1 | // 同步钩子
2 | class SyncHook {
3 | constructor(args) {
4 | this.task = [];
5 | }
6 |
7 | call(...args) {
8 | this.task.forEach(task => task(...args))
9 | }
10 |
11 | tap(name, task) {
12 | this.task.push(task);
13 | }
14 | }
15 |
16 | // 使用
17 | const hook = new SyncHook(['name']);
18 | hook.tap('react', name => {
19 | console.log('react', name);
20 | })
21 | hook.tap('node', name => {
22 | console.log('node', name);
23 | })
24 | hook.call('KenTsang');
25 |
--------------------------------------------------------------------------------
/Webpack/Tabable/SyncLoopHook.js:
--------------------------------------------------------------------------------
1 | // 同步遇到某个不返回 undefined 的监听函数多次执行
2 | class SyncLoopHook {
3 | constructor(args) {
4 | this.task = [];
5 | }
6 |
7 | tap(name, task) {
8 | this.task.push(task);
9 | }
10 |
11 | call(name) {
12 | this.task.forEach(task => {
13 | let ret;
14 | do {
15 | ret = task(name);
16 | } while(!ret);
17 | })
18 | }
19 | }
20 |
21 | const hook = new SyncLoopHook(['name']);
22 | let nodeCount = 0;
23 | hook.tap('react', name => {
24 | console.log('react', name);
25 | if (++nodeCount < 3) {
26 | return undefined;
27 | } else {
28 | return true;
29 | }
30 | })
31 | hook.tap('node', name => {
32 | console.log('node', name);
33 | return true;
34 | })
35 | hook.call('KenTsang');
36 |
37 |
--------------------------------------------------------------------------------
/Webpack/Tabable/SyncWaterfallHook.js:
--------------------------------------------------------------------------------
1 | // 同步瀑布流钩子,能往下传递数据
2 | class SyncWaterfallHook {
3 | constructor(args) {
4 | this.task = []
5 | }
6 |
7 | tap(name, task) {
8 | this.task.push(task);
9 | }
10 |
11 | call(...args) {
12 | let [first, ...others] = this.task;
13 | others.reduce((ret, task) => {
14 | return task(ret);
15 | }, first(...args));
16 | }
17 | }
18 |
19 | const hook = new SyncWaterfallHook(['name']);
20 | hook.tap('react', name => {
21 | console.log('react', name);
22 | return 'React is Better';
23 | })
24 | hook.tap('node', name => {
25 | console.log('node', name);
26 | return 'Node is Better';
27 | })
28 | hook.tap('webpack', name => {
29 | console.log('webpack', name);
30 | })
31 | hook.call('KenTsang');
32 |
--------------------------------------------------------------------------------
/面试.md:
--------------------------------------------------------------------------------
1 | ### CSS 相关
2 |
3 |
4 | CSS 选择器有哪些
5 |
6 | 分类可分:基本选择器、属性选择器、伪类选择器
7 |
8 | | 常用选择器 | - |
9 | | -- | -- |
10 | | 通配符 | `*` |
11 | | ID | `#id` |
12 | | 类 | `.class` |
13 | | 元素 | `div`、`p`、`a`|
14 | | 后代 | `div > p` |
15 | | 伪类 | `a:hover` |
16 | | 属性 | `[type="text"]` |
17 | | 子元素 | `li:first-child`、`li:nth-child(1)`... |
18 |
19 | 优先级:`!important` > inline style > `#id` > `.class` > 元素和伪元素 > `*` > 继承 > 默认
20 |
21 |
22 |
23 |
24 | 伪类和伪元素
25 |
26 | 伪元素:可创建文档虚拟元素(虚拟容器) `::before`
27 |
28 | | 伪元素| - |
29 | | -- | -- |
30 | | ::before | 被选元素前插入 |
31 | | ::after | 被选元素后插入 |
32 | | ::first-letter | 元素文本首字母 |
33 | | ::first-line | 元素第一行文本 |
34 | | ::selection | 元素选中部分 |
35 |
36 | 伪类:提供 CSS 选择器获取,但不存在 DOM 树 `:link`
37 |
38 | | 伪类 | - |
39 | | -- | -- |
40 | | :first-child | 第一个子元素 |
41 | | :last-child | 最后一个子元素 |
42 | | :first-of-type | 父元素第一个特定类型的子元素 |
43 | | :last-of-type | 父元素最后一个特定类型的子元素 |
44 |
45 |
46 |
47 |
48 | 盒子模型
49 |
50 | | 盒子模型 | 宽度计算 | CSS 设置 |
51 | | -- | -- | -- |
52 | | 标准 | `width = content-width` | box-sizing: content-box |
53 | | IE | `width = content-width + padding-width + border-width` | box-sizing: border-box |
54 |
55 |
56 |
57 |
58 | 移动端适配 1px 问题
59 |
60 | Retina 屏幕像素比为 2,CSS 1px 会被渲染成 2px 的物理像素。
61 |
62 | 解决方案:伪类 + transform 实现
63 |
64 | 单边框:
65 | ```stylus
66 | border-1px($color) {
67 | position: relative
68 | &:after {
69 | display: block
70 | position: absolute
71 | left: 0
72 | bottom: 0
73 | width: 100%
74 | border-top: 1px solid $color
75 | content: ' '
76 | }
77 | }
78 | @media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5) {
79 | .border-1px {
80 | &::after {
81 | -webkit-transform: scaleY(0.7)
82 | transform: scaleY(0.7)
83 | }
84 | }
85 | }
86 |
87 | @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2) {
88 | .border-1px {
89 | &::after {
90 | -webkit-transform: scaleY(0.5)
91 | transform: scaleY(0.5)
92 | }
93 | }
94 | }
95 | ```
96 |
97 | 多边框:伪类宽高 200%,绝对定位 -50%,同等缩放实现。
98 |
99 |
100 |
101 |
102 | b 和 strong 的区别
103 |
104 | 两者都有加粗字体的作用,但 strong 带有语义,表示强调,利于 SEO。
105 |
106 |
107 |
108 |
109 | BFC 于 margin 重叠
110 |
111 | #### margin 重叠
112 | * 相邻兄弟元素
113 | * 父子元素
114 |
115 | 以上两种情况,元素同属一个BFC,且为块级元素,且元素间没有阻挡(边框、非空内容、padding等)时,会发生 margin 重叠。
116 |
117 | #### 何为 BFC?
118 | 块级格式化上下午,简单的理解为一块独立的渲染区域,一个用于管理块级元素的容器,规定了容器内的布局规则,与外部区域隔离且不受影响。
119 |
120 | #### 创建 BFC
121 | * float 设置非 none
122 | * overflow 设置
123 | * display 设置 table-cell/table-caption/inline-block/inline-flex/flex
124 | * position 设置 absolute/fixed
125 | * 根元素
126 |
127 | #### BFC 布局规则
128 | * BFC 计算高度时,float 元素也参与(BFC 自带清浮)
129 | * BFC 不会重叠浮动元素(分栏布局)
130 | * BFC 阻止垂直 margin 折叠
131 |
132 |
133 |
134 |
135 | CSS 实现宽高等比例自适应矩形
136 |
137 | **知识点:padding 设置百分比,是按元素的宽度来计算,此外 padding 还可撑开元素宽高**
138 |
139 | ```html
140 |
141 |
144 |
145 |
160 | ```
161 |
162 | > 16:9 padding-bottom: 9/16 = 56.25%
163 |
164 | 参考文章: [CSS实现宽高等比例自适应矩形](https://juejin.im/post/5b0784566fb9a07abd0e14ae)
165 |
166 |
167 |
168 | 隐藏页面元素的方法有哪些?
169 |
170 | 隐藏类型:
171 | 1. 完全隐藏:从渲染树消失,不占据空间
172 | 2. 视觉隐藏:仍占据控件,只是视屏中不显示
173 | 3. 语义隐藏:阅读软件不可读,但正常占据空间
174 |
175 | #### 1. 完全隐藏
176 |
177 | ##### 1-1. display
178 | `display: none`
179 |
180 | ##### 1-2. hidden
181 | HTML5 新增属性,相当于 `display: none`
182 |
183 | ```html
184 |
185 | ```
186 |
187 | #### 2. 视觉隐藏
188 | ```css
189 | position: absolute;
190 | left: -99999px;
191 | ```
192 |
193 | #### 4. 语义隐藏
194 | ```html
195 |
196 | ```
197 |
198 |
199 |
200 |
201 | 三栏布局/圣杯布局/双飞翼布局方式
202 |
203 | | 布局 | |
204 | | -- | -- |
205 | | float 浮动 | 脱离文档流,需清浮解决父层高度塌陷 |
206 | | absoulute 绝对定位 | 自身跟后代元素都脱离文档流,需定高 |
207 | | table 表格 | 兼容性好,高度统一撑开,无法设置边距,SEO不友好 |
208 | | flex | 较完美,IE10开始支持(-ms) |
209 | | grid 网格 | IE10+支持,没有内容结构,子元素可自行定义位置 |
210 |
211 | #### 1. float 浮动布局
212 | **`DOM` 结构先写浮动部分,再写中间,否则右浮动会掉到下一行**
213 |
214 | * 优点:简单,兼容性好
215 | * 缺点:脱离文档流,父层高度塌陷需清浮解决
216 |
217 | ```html
218 |
219 |
220 |
221 |
222 |
223 |
224 |
234 | ```
235 | #### 2. absoulute 绝对定位布局
236 | * 优点:方便稳定
237 | * 脱离文档流,后代元素也脱离文档流,高度未知时,会有问题
238 |
239 | ```html
240 |
241 |
242 |
243 |
244 |
245 |
246 |
266 | ```
267 |
268 | #### 3. table 表格布局
269 | * 优点:兼容性好
270 | * 缺点:
271 | * 无法设置 margin 边距;
272 | * 对 SEO 不友好;
273 | * 单元格高度超出时,两侧单元格高度会一并变高,左右会撑开;
274 | ```html
275 |
276 |
277 |
278 |
279 |
280 |
281 |
292 | ```
293 | #### 4. flex 布局
294 | * 优点:比较完美
295 | * 缺点:低版本浏览器兼容问题,IE10开始支持(-ms)
296 |
297 | ```html
298 |
299 |
300 |
301 |
302 |
303 |
304 |
315 | ```
316 |
317 | #### 5. grid 网格布局
318 |
319 | CSS3 推出的网格布局,按列或行对其排列,不同于表格,没有内容结构。子元素可定位自己的位置,可以重叠(IE10+支持)。
320 |
321 | ```css
322 | article {
323 | display: grid;
324 | grid-template-columns: 300px auto 300px;
325 | }
326 | ```
327 |
328 |
329 |
330 | rem 与 flex 的区别
331 |
332 | * rem 布局是维持各端 PC/Mobile/IPad 统一排版,利用 rem 进行缩放适应,简单理解就是一份设计稿在各端显示效果一致
333 | * flex 布局是弹性伸缩自适应,并不是等比例缩放
334 |
335 |
336 |
337 |
338 | px、pt、em、rem
339 |
340 | | 单位 | 含义 |
341 | | -- | -- |
342 | | px | 虚拟长度单位,像素|
343 | | pt | 物理长度单位,72分之一英寸|
344 | | em | 相对长度单位 |
345 | | rem | CSS3 新增的相对长度单位,相对于 HTML |
346 |
347 | #### px 与 em
348 | * px 是相对于显示器屏幕分辨率,而 em 是相对于当前对象内文本的字体尺寸,如果字体尺寸未被设置,则相对于浏览器的默认字体尺寸
349 |
350 | * em 指字体高,浏览器默认字体高是 16px,未调整的浏览器:1em = 16px。为简化换算,设置 html 样式 font-size = 64.5%,这样 12px = 1.2em
351 |
352 | * 1em 指一个字体的大小,会继承父级元素的字体大小,不是一个固定的值。因此文本样式使用 em,支持浏览器缩放时字体调整,IE 精度问题使用 63%
353 |
354 |
355 |
356 |
357 | CSS3 position 新增支持 / position: sticky
358 |
359 | | 属性 | 作用 |
360 | | -- | -- |
361 | | static | 没有定位(默认) |
362 | | relative | 相对定位 |
363 | | absolute | 绝对定位 |
364 | | fixed | 固定定位(相对body) |
365 | | sticky | 粘滞定位 |
366 |
367 | * absolute 会被 relative/absolute 的父元素限制,否则被 body
368 | * stick 是 relative 和 fixed 的混合体
369 | * 元素在视口内,top/left 无效;
370 | * 滚动超出时,表现像 fixed;
371 | * left/right 同时设置保留前者,top/bottom 同理
372 |
373 |
374 |
375 |
376 | CSS 水平垂直居中的方式
377 |
378 | 居中元素固定宽高:
379 | 1. absolute + 负 margin
380 | 2. absolute + margin auto
381 | 3. absolute + calc
382 |
383 | 居中元素不定宽:
384 | 1. absolute + transform(移动端优先)
385 | 2. line-height
386 | 3. writing-mode
387 | 4. table (可弃用)
388 | 5. css-table
389 | 6. flex(优先)
390 | 7. grid
391 |
392 |
393 |
394 |
395 | 居中为何优先使用 transform (而不用 margin/top)
396 |
397 | * 支持居中元素不定宽;
398 | * 不用计算宽高减半值;
399 |
400 |
401 |
402 | ### JavaScript 相关
403 |
404 |
405 | JavaScript 中假值有哪些?
406 |
407 | JavaScript 中共有 6 个假值:
408 | 1. `undefined`
409 | 2. `null`
410 | 3. `NaN`
411 | 4. `0`
412 | 5. `''`
413 | 6. `false`
414 |
415 | 包装对象类型都是真值,如 `new Number(0)` / `new Boolean(false)`)。
416 |
417 |
418 |
419 | 类型判断
420 |
421 | 1. `typeof` 只能用于基本类型判断,无法判断 `null`;
422 | 2. `instanceof` 用于判断引用类型,缺点是 `null`;
423 | 3. `Object.prototype.toString.call()` 较好的类型判断方式;
424 | 4. `isPrototypeOf` ES6 新增的原型判断;
425 |
426 | 思路:先 `instanceof` 判断是不是复杂类型,再用 `typeof` 判断基本类型
427 |
428 | [正确的类型判断](https://github.com/YvetteLau/Blog/blob/master/JS/data-type.js)
429 |
430 |
431 |
432 |
433 | 判断变量是不是数组
434 |
435 | 1. ES6 `Array.isArray()` 返回 `true` 是;
436 | 2. ES6 `Array.prototype.isPrototypeOf([])` 值为 `true` 是;
437 | 3. `instanceof Array` 返回 `true` 是;
438 | 4. `Object.prototype.toString.call()` 值为 `[object Array]` 是
439 |
440 |
441 |
442 |
443 | ES6 对象的扩展
444 |
445 | * 支持属性简写,属性解构赋值,**表达式属性名不能与属性简写同时使用**;
446 | * 新增 `super` 关键字,指向对象,仅在对象方法中调用;
447 | * 对象方法的 `name` 属性;
448 | * 普通属性方法,返回方法名;
449 | * `get/set` 取存函数,返回方法名前加 `get/set` -> `get fnName`;
450 | * `new`,返回 `anonymous`;
451 | * `bind`, 返回 `bound fnName`
452 | * 对象的扩展运算符 `{...obj}`(可实现浅拷贝)
453 |
454 | | Object 新增方法 | 作用 |
455 | | -- | -- |
456 | | is | 完善 `===` 判断(-0 不等于 +0 / NaN 等于自身)|
457 | | assign | 对象属性合并(浅拷贝)|
458 | | getOwnPropertyDescriptiors | 多个s,获取对象所有属性的描述 |
459 | | setPrototypeOf/getPrototypeOf | 原型设置与读取 |
460 | | keys/values/entires | - |
461 | | fromEntries | 用于将 Map 结构转为 Object |
462 |
463 | ```js
464 | // Object.is 实现
465 | Object.prototype.is = function(x, y) {
466 | if (x === y) {
467 | return x !== 0 || 1 / x === 1 / y;
468 | } else {
469 | return x !== x && y !== y;
470 | }
471 | }
472 |
473 | ```
474 | 注意点:
475 | 1. `Object.assign` 合并对象时遭遇 `get` 方法时,拷贝的是 `get` 方法的执行结果;
476 | 2. 字符串的包装类型可实现枚举;
477 |
478 |
479 |
480 |
481 | ES6 新增数据类型 Symbol
482 |
483 | > Symbol 表示独一无二的值,一种类似字符串类型
484 |
485 | 1. 通过 `Symbol` 函数生产,该函数不能使用 `new` 实例化;
486 | 2. 不能与其它值做运算,包括模版字符串 `${}` 的引用;
487 | 3. 可转为布尔值,用于条件判断;
488 | 4. 可显示转为字符串 `String(mySymbol)/mySmbol.toString()`;
489 | 5. 定义的描述符,`Symbol.description()` 获取;
490 | 6. `Symbol.for()` 返回已存在的 Symbol, 不存在则创建(避免重复创建);
491 | 7. `Symbol.keyFor`,返回已存在的 Symbol key 值,不存在返回 `undefined`;
492 | 8. 作为对象属性名的 Symbol,必须用方括号,不能被普通方法枚举,`Reflect.ownKeys` 可枚举全部属性,或 `Object.getOwnPropertySymbols`
493 |
494 | #### 用途:
495 | 1. 借助不太容易枚举,可作私有属性使用;
496 | 2. 引用第三方对象方法时,避免属性名覆盖;
497 | 3. 消除魔术字符串,作判断条件使用;
498 | 4. 内置的 Symbol 方法可监听对象/类的用法
499 |
500 |
501 |
502 |
503 | ES6 新增数据类型 Set/Map/WeakSet/WeakMap
504 |
505 | * Set: 不重复的类似数组解构,`size` 属性表示成员数量
506 | * 操作方法:`add/delete/has/clear`
507 | * 遍历方法:`keys/values/entires/forEach/for...of`
508 | * 可转为数组后,使用数组方法:`map/filter`等,实现数组去重,交集/差集/并集
509 | * WeakSet: 弱引用,成员只能是对象,不支持遍历,没有 `size` 属性
510 | * 用途:类中对象引用/DOM对象存储
511 | * Map: 键值对,不同于 Object 的 “字符串-值”,是 ”值-值“,`size` 属性表示成员数量
512 | * 操作方法:`add/delete/has/clear`
513 | * 遍历方法:`keys/values/entires/forEach/for...of`
514 | * 同样可借助转为数组后,调用方法
515 | * 用途:属性名更灵活,不会冲突
516 | * WeakMap: 弱引用,键只能是对象,不支持遍历,没有 `size` 属性
517 | * 用途:类私有方法/DOM 元素对象属性值
518 |
519 |
520 |
521 |
522 | 类数组和数组的区别
523 |
524 | 类数组是一个普通对象,有 `length` 属性,而真实的数组是 `Array` 类型,类数组不具备数组的方法。
525 |
526 | 常见类数组:
527 | 1. 函数参数 `arguments`;
528 | 2. DOM 对象列表 `document.querySelectorAll('li')`;
529 | 3. jQuery 对象 `$('div')`
530 |
531 | 类数组转数组:
532 | ```js
533 | // eg.1
534 | Array.prototype.slice.call(arrLike);
535 |
536 | // eg.2
537 | [...arrLike];
538 |
539 | // eg.3
540 | Array.from(arrLike);
541 | ```
542 |
543 | 拥有遍历器接口的对象,都可用扩展运算符 `...` 和 `Array.from` 转为数组,如字符串。
544 |
545 |
546 |
547 |
548 | 深浅拷贝原理及实现
549 |
550 | #### 深浅拷贝概念
551 |
552 | > 浅拷贝:拷贝一个对象(创建一个新的对象),对其属性值做拷贝,如果其属性值是基本类型,则拷贝的是该基本类型的值,如果其属性值是引用类型,则拷贝的是内存地址。
553 |
554 | 浅拷贝与赋值不同,将引用类型赋值给变量,并不会创建新对象,栈内存共享该引用类型的指针。
555 |
556 | > 深拷贝:完整拷贝一个对象,包括其属性值,都是独立的内存存储。
557 |
558 | #### 代码实现
559 | 待更
560 |
561 |
562 |
563 | Array.from 与 map
564 |
565 | map 的 callback 只会在有值的索引上被调用(未赋值或 delete 的索引不会被调用)
566 |
567 | ```js
568 | const mapResult = Array(3).map(() => 0);
569 | mapResult; // => [undefined, undefined, undefined]
570 |
571 | const fromResult = Array.from({length: 3}, () => 0);
572 | fromResult; // => [0, 0, 0]
573 | ```
574 |
575 | Array.from 可用于数组初始化赋值,与 fill 不同是 callback 每次都返回一个新的值
576 |
577 | ```js
578 | const resultA = Array.from({length: 3}, () => ({}));
579 | const resultB = Array(3).fill({});
580 |
581 | resultA[0] === resultA[1]; // false;
582 | resultB[0] === resultB[1]; // true;
583 | ```
584 |
585 | Array.from 实现数组的深层拷贝
586 | ```js
587 | function recursiveClone(value) {
588 | return Array.isArray(value) ? Array.from(value, recursiveClone) : value;
589 | }
590 | const numbers = [[0,1,2], ['one','two','three']];
591 | const numbersClone = recursiveClone(numbers); // => [[0,1,2], ['one','two','three']]
592 | ```
593 |
594 |
595 |
596 |
597 | 手动实现 ES6 数组的 map 方法
598 |
599 | 语法:
600 | > var new_array = arr.map(function callback(currentValue[, index[, array]]) {
601 | > // Return element for new_array
602 | > }[, thisArg])
603 |
604 | 实现:
605 | ```js
606 | Array.prototype.MyMap = function(fn, context) {
607 | var arr = Array.prototype.slice.call(this);
608 | var mappedArr = [];
609 | for (var i = 0, len = arr.length; i < len; i++) {
610 | mappedArr.push(fn.call(context, arr[i], i, this));
611 | }
612 | return mappedArr;
613 | }
614 |
615 | var arr = [1, 3, 9, 16];
616 | var newArr = arr.MyMap(x => x * 2);
617 |
618 | newArr; // => [2, 6, 18, 32]
619 | ```
620 |
621 |
622 |
623 | 手动实现 ES6 数组的 reduce 方法
624 |
625 | 语法:
626 | > arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
627 |
628 | 要点:初始值不传处理(初始值不传时,第一个累计器取数组第一项)
629 |
630 | ```js
631 | Array.prototype.MyReduce = function(fn, initialValue) {
632 | var arr = Array.prototype.slice.call(this);
633 | var res, startIndex;
634 | res = initialValue ? initialValue : arr[0];
635 | startIndex = initialValue ? 0 : 1;
636 |
637 | for (var i = startIndex, len = arr.length; i < len; i++) {
638 | res = fn.call(null, res, i, arr[i], this);
639 | }
640 | return res;
641 | }
642 |
643 | var arr = [1, 2, 3];
644 | var sumResult = arr.MyReduce((acc, curValue) => acc + curValue);
645 |
646 | sumResult; // 6
647 | ```
648 |
649 |
650 |
651 |
652 | 表达式和语句的区别
653 |
654 | * 表达式:产生一个值;
655 | * 语句:执行一个操作;
656 | * 表达式语句:产生一个值和执行一个操作
657 |
658 |
659 |
660 |
661 | for of, for in 和 forEach, map的区别
662 |
663 | * for...of:遍历具有 itertaor 接口对象(数组、Set、Map、类数组对象、字符串)可中断循环;
664 | * for...in:遍历对象自身和继承的可枚举的属性,不能直接获取属性值,可中断循环;
665 | * forEach:只能遍历数组,没有返回值,不能中断;
666 | * map:只能遍历数组,返回值是修改后的新数组,不能中断
667 |
668 | 退出循环:`break/throw/continue/return`
669 |
670 |
671 |
672 | for...in 和 Object.keys() 的区别
673 |
674 | 1. `Object.keys()` 返回自身可枚举属性组成的数组,顺序与 `for...in` 一致;
675 | 2. `for...in` 除了遍历自身可枚举,还可以枚举原型链中属性
676 |
677 | **与 in 有关都会检索原型链**
678 |
679 |
680 |
681 | == 和 === 的区别
682 |
683 | * `===` 全等运算符不需要类型转换,比较类型和值都相等时,返回 `true`;
684 | * `==` 类型不同,需先进行转换,如下
685 | * 判断是否为 `null` 或 `undefined`,是返回 `true`;
686 | * 判断是否为 `string` 或 `number`,是字符串转 `number` 再比对;
687 | * 判断其中一方是否为 `boolean`,是转为 `number` 再比对;
688 | * 判断其中一方为 `object` 另一方为 `string/number/symbol`,是将 `object` 转原始类型再比对,调用 `toString()/valueOf()`
689 |
690 |
691 |
692 |
693 | Object.is() 与 === 的区别
694 |
695 | `Object.is()` 与 `===` 类似,有两点区别:
696 | 1. `Object.is()` 判断 `NaN` 时,返回 `true`;
697 | 2. `Object.is()` 判断 `+0/-0` 时,返回 `false`
698 |
699 | ```js
700 | Object.is(NaN, NaN); // true
701 | Object.is(+0, -0); // false
702 |
703 | NaN === NaN; // false
704 | +0 === -0; // true
705 | ```
706 |
707 |
708 |
709 |
710 | [] == ![]
711 |
712 | 1. 优先级 `!` 高于 `==`;
713 | 2. `![]`,`object` 转为 `boolean` 为 `true`,因此这里为 `false`;
714 | 3. 其中一方为 `boolean`,先将 `boolean` 转为 `number`,0;
715 | 4. 其中一方为 `number`, `object` 类型转原始类型(空数组转数字为 0,若数组中仅有一个数字,转数字时就是该数字,其它情况为 `NaN`);
716 | 5. `0 == 0` 为 `true`
717 |
718 | [运算符优先级](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
719 |
720 |
721 |
722 |
723 | ES6 中的 Class 类和 ES5 的类区别
724 |
725 | 1. 定义的方法不可枚举,ES5 可以枚举;
726 | 2. 必须使用 `new` 实例化,ES5 可当普通函数使用;
727 | 3. 不存在变量提升,默认即是严格模式;
728 | 4. 方法是定义在 `prototype` 原型上,属性可在 `constructor` 初始化定义(支持传参),也可在顶层定义(无法通过传参初始化);
729 | 5. 子类构造函数必须通过 `super()` 调用父类构造函数实现继承,才有 `this` 对象(ES5 中先有子类的 `this`, 再将父类的属性应用在 `this` 上);
730 | 6. 类中 `static` 声明的方法为静态方法,属于类自身,可与类中普通方法重名,方法中的 `this` 指向类而不是对象实例,子类可以通过 `super` 使用父类静态方法 `super.parentStaticFunc`;
731 | 7. `new.target` 返回构造函数(可实现类必须继承);
732 | 8. 提案:静态属性和私有方法`#name`
733 |
734 |
735 |
736 |
737 | 数组 API 中纯与不纯的函数
738 |
739 | 不纯函数,会修改原数组:
740 | `splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift`
741 |
742 | 纯函数,不会修改原数组:
743 | `slice/map/forEach/every/filter/reduce/entries/find`
744 |
745 |
746 |
747 |
748 | 不会正常排序的 Array.prototype.sort
749 |
750 | > arr.sort([compareFunction])
751 |
752 | #### 不能正常排序原因
753 | 没有 compareFunction 时,元素将会转换为字符串的各个字符的 Unicode 位点进行排序,如 "Banana" 会排在 "cherry" 前, "80" 排在 "9" 前。
754 |
755 | #### 解决方案:指定 compareFunction(a,b) 比较规则
756 | * a, b, 返回值 < 0,a 排 b 前
757 | * a, b, 返回值 == 0,位置不变
758 | * a, b, 返回值 > 0,a 排 b 后
759 |
760 |
761 |
762 |
763 | slice/splice 的区别
764 |
765 | > arr.slice([begin[, end]])
766 |
767 | > array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
768 |
769 | * 同:两者都可返回一个新数组;
770 | * 异:`splice` 会改变原数组,为不纯函数,`slice` 多用于拷贝,`splice` 用于修改数组
771 |
772 |
773 |
774 |
775 | 判断 this 指向,箭头函数的 this
776 |
777 | this 绑定:默认绑定、隐式绑定、显示绑定、new 绑定
778 |
779 | 1. `new` 绑定,构造函数没有返回,`this` 指向新创建对象,若有返回,指向返回对象;
780 | 2. 显示绑定,通过 `apply/call/bind`,`this` 指向传入对象,如果传入对象为 `null/undefined`,则应用默认绑定规则;
781 | 3. 隐式绑定,取决于上下文对象调用者,指向该对象,`obj.foo()`;
782 | 4. 默认绑定,以上规则不匹配时,严格模式指向 `undefined`,否则指向全局对象;
783 | 5. 箭头函数, `this` 取决于词法作用域,在函数声明时就确定,来自上一层代码块的 `this`
784 |
785 |
786 |
787 |
788 | 事件流
789 |
790 | * 冒泡:子到父 div->body->window
791 | * 捕获:父到子 window->body->div
792 |
793 |
794 |
795 |
796 | event.target 与 event.currentTarget
797 |
798 | * `event.target`: 当前触发事件的元素
799 | * `event.currentTarget`: 当前绑定事件的元素
800 |
801 | 事件处理函数内部 `this` 指向 `event.currentTarget`。
802 |
803 | | event 常用属性 | 作用 |
804 | | -- | -- |
805 | | preventDefault() | 阻止默认事件 |
806 | | stopPropagation() | 阻止事件流传递(冒泡/捕获) |
807 | | stopImmediatePropagation() | 阻止事件流传递与剩余未执行事件 |
808 |
809 |
810 |
811 |
812 | DOM0级跟DOM2级事件区别
813 |
814 | 区别:DOM0级只能绑定一个事件处理函数,DOM2级可绑定多个事件处理函数,并且新增**冒泡/捕获**事件触发机制。
815 |
816 | ```js
817 | // DOM0级
818 | elem.onclick = function() {
819 | //...
820 | };
821 |
822 | // DOM2级
823 | elem.addEventListener('click', function() {
824 | //...
825 | }, true); // useCapture 默认为 false 冒泡阶段触发, true 捕获阶段触发
826 | ```
827 |
828 |
829 |
830 | 不同浏览器 DOM2 级事件的不同
831 |
832 | * `addEventListener/removeEventListener`
833 | * IE 兼容用 `attchEvent/detachEvent`
834 |
835 |
836 |
837 | 事件委托(代理)以及优缺点
838 |
839 | 事件委托是基于事件流的“冒泡”机制来实现,子节点触发事件,冒泡到父节点,由父级节点做事件接收处理。
840 |
841 | 优点:
842 | 1. 减少事件函数注册,仅在父节点绑定一个函数,节省内存;
843 | 2. 支持动态绑定事件,针对动态子元素,不用频繁绑定和解绑
844 |
845 | 缺点:
846 | 1. 基于冒泡和捕获实现,不支持冒泡和捕获的事件不支持;
847 | 2. 层级过多时,若中间层阻止,可能无法到达事件元素,建议就近委托
848 |
849 | 不支持冒泡的事件:`load/unload/scroll/resize/blur/focus/mouseleave/mouseenter/自定义事件`
850 |
851 |
852 |
853 |
854 | 自定义事件 Event/CustomEvent
855 |
856 | 区别:CustomEvent 可传递一个 Object 对象来传输数据
857 |
858 | ```js
859 | let eve = new CustomEvent('custome', {
860 | detail: {
861 | name: 'KenTsang',
862 | }
863 | });
864 |
865 | let elem = document.body;
866 | elem.addEventListener('custome', function(event){
867 | console.log(event.CustomEvent.detail.name); // 'KenTsang'
868 | })
869 |
870 | // 事件触发
871 | elem.dispatchEvent(eve);
872 | ```
873 |
874 |
875 |
876 | 闭包特权函数的使用场景
877 |
878 | 1. ES6 前实现块级作用域;
879 | 2. 模块化实现;
880 | 3. 公私有属性;
881 | 4. 函数柯里化实现
882 |
883 |
884 |
885 |
886 | 尾调用优化
887 |
888 | 1. 尾部函数前 + `return` 返回,作为函数外层函数结束语句;
889 | 2. 尾部函数中未使用自由变量(非自身环境声明,需借助作用域链查找的外部变量),若需使用,以参数传递的方式进入 `arguments`
890 |
891 |
892 |
893 |
894 | 函数参数是按值传递还是按引用传递
895 |
896 | 函数参数是按值传递,引用类型的值是其栈中存储的值,一个地址指针。
897 |
898 |
899 |
900 |
901 | defineProperty 的应用场景
902 |
903 | 使用 defineProperty 定义的对象属性,以下几个属性值默认 `false`
904 | * `configurable` 可配置
905 | * `enumerable` 可枚举
906 | * `writable` 可设置值
907 |
908 | 应用场景:
909 | * Vue 双向数据绑定的实现;
910 | * MVVM 视图与数据变化的绑定;
911 | * 对象属性设置/读取/配置/枚举等钩子
912 |
913 |
914 |
915 |
916 | setInterval 的注意点
917 |
918 |
919 |
920 | setTimeout(1) 和 setTimeout(2) 的区别
921 |
922 | setTimeout 最小时间是 400, 即 4 毫秒
923 |
924 |
925 |
926 | ES5 实现继承的方案
927 |
928 | 经典:”构造函数 + 原型“ 的组合继承
929 |
930 | ```js
931 | // 父类 Person
932 | function Person(name, age) {
933 | this.name = name;
934 | this.age = age;
935 | }
936 | Person.prototype.eat = function() {
937 | //...
938 | };
939 | Person.prototype.walk = function() {
940 | //...
941 | };
942 |
943 | // 子类 Student
944 | function Student(name, age, grade) {
945 | Person.call(this, name, age);
946 | this.grade = grade;
947 | }
948 | Student.prototype = new Person();
949 | // Student.prototype = Object.create(Person.prototype);
950 | Student.prototype.constructor = Student;
951 | ```
952 |
953 |
954 |
955 |
956 | 手动实现一个 Object.create
957 |
958 | > Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__。
959 |
960 | ```js
961 | function create(proto) {
962 | const F = function();
963 | F.prototype = proto;
964 | F.prototype.constructor = F;
965 | return new F();
966 | }
967 | ```
968 |
969 |
970 |
971 | 变量 a, b 如何交换值
972 |
973 | 1. ES6 解构:`[a, b] = [b, a]`;
974 |
975 | ```js
976 | function swap(a, b) {
977 | a = a + b;
978 | b = a - b;
979 | a = a - b;
980 | }
981 |
982 | function swap(a, b) {
983 | a = a * b;
984 | b = a / b;
985 | a = a / b;
986 | }
987 |
988 | function swap(a, b) {
989 | a = a ^ b;
990 | b = a ^ b;
991 | a = a ^ b;
992 | }
993 | ```
994 |
995 |
996 |
997 | 解构的妙用
998 |
999 | #### 数组解构
1000 | 1. 变量交换值 `[a, b] = [b, a]`;
1001 | 2. 获取指定值 `[, second] = arr`(不可变数据对象维护);
1002 |
1003 | #### 对象属性解构
1004 | 1. 匹配模式可使用:方括号+属性表达式(动态属性名的方式);
1005 | 2. 可自行实现对象 [Symbol.iterator] 迭代器生成方法 ;
1006 | 3. 删除属性操作 `[foo, ...small] = big`(删除了 `big` 的 `foo` 属性)
1007 |
1008 |
1009 |
1010 |
1011 | 原生事件不移除为什么会内存泄露
1012 |
1013 | 老版本浏览器 BOM 对象是基于引用计数来作垃圾回收机制的,浏览器无法检测 DOM 元素与事件处理器的循环引用问题,造成内存泄露。
1014 |
1015 |
1016 |
1017 |
1018 | 内存泄露常见
1019 |
1020 | 1. 意外的全局变量;
1021 | 2. 未销毁的定时器;
1022 | 3. 变量对象引用已经销毁的 DOM,造成该 DOM 对象未被正常回收;
1023 |
1024 |
1025 |
1026 | bind / call / apply 的区别
1027 | **三者都可用于显式绑定 this,bind 返回的是待执行函数,call/apply 返回执行结果**
1028 |
1029 | `call` 和 `apply` 功能相同,区别在于传参方式不同:
1030 | 1. `fn.call(obj, arg1, arg2, ...)`, `call` 传参数列表,逗号隔开
1031 | 2. `fn.call(obj, [arg1, arg2, ...])`, `apply` 传参数数组
1032 |
1033 | #### call 实现
1034 | ```js
1035 | Function.prototype.call = function(context, ...args) {
1036 | // 判断传入对象是否为 null
1037 | if (!context) {
1038 | context = typeof window === 'undefined' ? global : window;
1039 | }
1040 |
1041 | // 改变函数调用者,作为传入对象的属性方法调用
1042 | context.fn = this;
1043 | const result = context.fn(...args);
1044 | delete context.fn;
1045 | return result;
1046 | }
1047 | ```
1048 |
1049 | #### apply 实现
1050 | ```js
1051 | Function.prototype.call = function(context, args) {
1052 | // 判断传入对象是否为 null
1053 | if (!context) {
1054 | context = typeof window === 'undefined' ? global : window;
1055 | }
1056 |
1057 | // 改变函数调用者,作为传入对象的属性方法调用
1058 | context.fn = this;
1059 | const result = context.fn(...args);
1060 | delete context.fn;
1061 | return result;
1062 | }
1063 | ```
1064 |
1065 | #### bind 实现
1066 | ```js
1067 | Function.prototype.bind = function(context, ...initArgs) {
1068 | // 预先判断是否为函数
1069 | if (typeof this !=== 'function') {
1070 | throw new TypeError('no a function');
1071 | }
1072 |
1073 | const self = this;
1074 | // 返回一个待执行函数
1075 | return function(...finalArgs) {
1076 | // 合并预先传入的参数、最后传入的参数
1077 | self.apply(context, [...initArgs, ...finalArgs]);
1078 | }
1079 | }
1080 | ```
1081 |
1082 |
1083 |
1084 | async / await 的实现原理
1085 |
1086 |
1087 |
1088 | promise 与 async 的区别
1089 |
1090 |
1091 |
1092 | async 中多个 await 请求,如何优化(是否有依赖)
1093 |
1094 | 1. 没有依赖的话,集合到一个 Promise 数组中,并行执行,类似 Promise.all;
1095 | 2. 有依赖的尽量划分,可以并行的放到同数组中做并行执行
1096 |
1097 |
1098 |
1099 |
1100 | Promise 的理解
1101 |
1102 | 简单理解为是一个容器,存储着将来(异步)的结果。
1103 |
1104 | 特点:
1105 | 1. 状态一经改变后不会再变更;
1106 | 2. 状态不受外界影响跟改变
1107 |
1108 | Promis 的状态:
1109 | 1. `pending` 初始状态;
1110 | 2. `fulfilled` 操作已完成;
1111 | 3. `rejected` 操作已失败
1112 |
1113 | 优点:
1114 | 1. 解决异步嵌套的问题(并未彻底解决嵌套)
1115 | 2. 可链式调用 `then`
1116 |
1117 | 缺点:
1118 | 1. 无法取消 Promise
1119 | 2. 无回调函数或 `catch` 时,会吃掉内部错误
1120 | 3. 处于 `pending` 时,无法得知进展
1121 |
1122 | 一个 Promise 中如果 `resolve` 另一个 Promise 实例,那么将放弃自身的状态,以 `resolve` 中的 Promise 实例状态为准。
1123 |
1124 | 建议用 `catch` 代理 `then` 中 `error` 的回调函数,可捕获 `then` 中的代码错误。并且处于最后,这样 `catch` 中内部错误才会抛出。
1125 |
1126 |
1127 |
1128 |
1129 | Promise.resolve() 的转换规则
1130 |
1131 | > 功能:转换为 Promise 对象
1132 |
1133 | | 参数情况 | 作用 |
1134 | | -- | -- |
1135 | | Promise 实例 | 原封不动返回 |
1136 | | thenable 对象 | 立即执行该对象的 `then` 方法 |
1137 | | 不具有 `then` 或不是对象 | 状态为 `resolved` |
1138 | | 无参数 | 状态为 `resolved` |
1139 |
1140 | **立即 resolved 的 Promise 对象,在本轮“事件循环”结束时执行。**
1141 |
1142 | 关联:`Promise.reject()` 会原封不动将参数作为 `reject` 的理由输出。
1143 |
1144 |
1145 |
1146 | Promise.try()
1147 |
1148 | > 功能:将参数封装成 Promise 对象方便流程调用,同步方法同步执行,异步方法异步执行,不同于 Promise.resolve()
1149 |
1150 |
1151 |
1152 |
1153 | 设计 Promise.all
1154 |
1155 | > 用于将多个 Promise 实例包装成一个 Promise 实例,全部实例为 fulfilled 或第一个 rejected 触发,then 接收是一个数组参数,[p1,p2]
1156 |
1157 | 如果单个实例自身处理了 `catch` ,默认是执行了 `resolve`,不会触发到 `all` 的 `catch`。
1158 |
1159 | ```js
1160 | // Promise.all 源码实现
1161 | Promise.all = function(promise) {
1162 | // Promise.all 返回一个 Promise 实例对象
1163 | return new Promise((resolve, reject) => {
1164 | const { length } = promise;
1165 |
1166 | // Promise.all 中 resolve 的参数格式是一个数组
1167 | if (!length) {
1168 | return resolve([]);
1169 | }
1170 |
1171 | let index = 0;
1172 | const result = [];
1173 |
1174 | function processValue(i, data) {
1175 | result[i] = data;
1176 | if (++index === length) {
1177 | resolve(result);
1178 | }
1179 | }
1180 |
1181 | for (let i = 0; i < length; i++) {
1182 | // 利用 Promise.resolve 转换数组中的对象
1183 | Promise.resolve(promise[i]).then(data => {
1184 | processValue(i, data);
1185 | }, err => {
1186 | // 这里使用 reject 回调,避免单个 promise 使用 catch 自身处理错误而无法抛出给 all 的 catch
1187 | reject(err);
1188 | })
1189 | }
1190 | })
1191 | }
1192 | ```
1193 |
1194 |
1195 |
1196 |
1197 | 涉及 Promise.race
1198 |
1199 | > 竞态,第一个 fulfilled 或 rejected 触发
1200 |
1201 |
1202 |
1203 |
1204 | 设计 Promise.finally
1205 |
1206 | > Promise 实例 resolve/reject 都会执行的回调,与状态无关,回调函数不接受任何参数。
1207 |
1208 | ```js
1209 | Promise.prototype.finally = function (callback) {
1210 | return this.then((value) => {
1211 | return Promise.resolve(callback()).then(() => {
1212 | return value;
1213 | });
1214 | }, (err) => {
1215 | return Promise.resolve(callback()).then(() => {
1216 | throw err;
1217 | });
1218 | });
1219 | }
1220 | ```
1221 |
1222 |
1223 |
1224 |
1225 | 如何比较两个对象
1226 |
1227 | * 工具类:借用 Immutable 对比两个对象
1228 | * 辅助函数,迭代对象属性,值为原始类型,则比对值,为引用类型,则继续递归跌打比对(注意函数、日期以及正则等)
1229 |
1230 |
1231 |
1232 |
1233 | 防抖 debounce 和节流 throttle 的区别
1234 |
1235 | * 同:两者的作用都是防止一定时间内函数被多次调用;
1236 | * 异:
1237 | * 防抖:在事件被触发 n 秒后再执行回调函数,如果在这 n 秒内又被触发,则重新计时延迟时间,例:R 大招 60 秒;
1238 | * 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效(间隔执行),例:FPS射击;
1239 | * 应用:
1240 | * 防抖:文本输入联想、文本输入验证;
1241 | * 节流:鼠标点击、mousemove 拖拽、resize、监听滚动 scroll等;
1242 |
1243 |
1244 |
1245 | JavaScript 的回收机制是什么,常用的是哪种,怎么处理
1246 |
1247 | 标记清除和引用计数,常用为手动标记清除,将变量值设置为 `null`。
1248 |
1249 |
1250 |
1251 |
1252 | 垃圾回收时堆和栈的区别
1253 |
1254 |
1255 |
1256 | 手写数组去重函数 uniq
1257 |
1258 | ```js
1259 | uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5]
1260 | ```
1261 |
1262 | #### 1. ES6 新增数据类型 set
1263 | ```
1264 | function uniq(arr) {
1265 | return [...new Set(arr)];
1266 | }
1267 | ```
1268 |
1269 | #### 2. indexOf
1270 | ```js
1271 | function uniq(arr) {
1272 | let result = [];
1273 | for (let val of arr) {
1274 | if (result.indexOf(val) < 0) {
1275 | result.push(val);
1276 | }
1277 | }
1278 | return result;
1279 | }
1280 | ```
1281 |
1282 | #### 3. includes
1283 | ```js
1284 | function uniq(arr) {
1285 | let result = [];
1286 | for (let val of arr) {
1287 | if (!result.includes(val)) {
1288 | result.push(val);
1289 | }
1290 | }
1291 | }
1292 | ```
1293 |
1294 | #### 4. reduce
1295 | ```js
1296 | function uniq(arr) {
1297 | return arr.reduce([prev, cur] => {
1298 | if (prev.includes(cur)) {
1299 | return prev;
1300 | } else {
1301 | return [...prev, cur]
1302 | }
1303 | }, []);
1304 | }
1305 | ```
1306 |
1307 | #### 5. map 或 object[val]
1308 | ```js
1309 | function uniq(arr) {
1310 | let map = new Map();
1311 | let result = [];
1312 | for (let val of arr) {
1313 | if (!map.has(val)) {
1314 | result.push(val);
1315 | map.set(val, true);
1316 | }
1317 | }
1318 | return result;
1319 | }
1320 | ```
1321 |
1322 |
1323 |
1324 |
1325 | 手写数组扁平化函数
1326 |
1327 | ```js
1328 | flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5]
1329 | ```
1330 |
1331 | #### 1. ES6 新增的 Array.prototype.flat
1332 | > var newArray = arr.flat([depth])
1333 |
1334 | depth 默认为一,表示提取嵌套数组的结构深度,depth 为 Infinity 表示提取任意深度
1335 |
1336 | ```js
1337 | function flattenDeep(arr, deepLength = 1) {
1338 | return arr.flat(deepLength);
1339 | }
1340 | flattenDeep([1, [2, [3, [4]], 5]], 3);
1341 | flattenDeep([1, [2, [3, [4]], 5]], Infinity);
1342 | // => [1, 2, 3, 4, 5];
1343 | ```
1344 |
1345 | #### 2. reduce 与 concat
1346 | ```js
1347 | // 单层深度
1348 | function SingleFlattenDeep(arr) {
1349 | return arr.reduce((acc, value) => arr.concat(value), []);
1350 | }
1351 | SingleFlattenDeep([1, 2, [3, 4]]); // => [1, 2, 3, 4]
1352 |
1353 | // 全部深度(递归)
1354 | function AllFlattenDeep(arr) {
1355 | return acc.reduce((acc, value) => arr.concat(Array.isArray(value) ? AllFlattenDeep(value) : value)), []);
1356 | }
1357 | ```
1358 |
1359 |
1360 |
1361 |
1362 | new 实现原理 / 手写 new 实现
1363 |
1364 | 1. 创建一个空对象 `{}`,构造函数的 `this` 指向这个空对象;
1365 | 2. 对该对象进行原型链接,`__proto__` 指向函数的 `prototype`;
1366 | 3. 执行构造函数方法,将属性添加到 `this` 指向的对象;
1367 | 4. 若函数没有返回其它值,则返回 `this` 指向的对象,若返回基本类型值,则返回它们的包装对象
1368 |
1369 | ```js
1370 | function _new() {
1371 | let target = {};
1372 | let [constructor, ...args] = [...arguments];
1373 | target.__proto__ = constructor.prototype;
1374 | let result = constructor.apply(target, args);
1375 |
1376 | if (result && (typeof result == 'object' || typeof result == 'function') ) {
1377 | return result;
1378 | }
1379 | return target;
1380 | }
1381 | ```
1382 |
1383 |
1384 |
1385 |
1386 | 字面量创建对象和 new Object 对比
1387 |
1388 | 1. 字面量创建对象,不会调用 `Object` 构造函数,简洁性能更佳;
1389 | 2. `new Object()` 本质上是方法调用,会涉及到原型链查找,函数调用涉及到执行栈等
1390 |
1391 |
1392 |
1393 |
1394 | 异步加载 JS 脚本的方式
1395 |
1396 | 1. defer 页面文档加载完执行,顺序执行,onLoad 之前执行;
1397 | 2. async 对应 js 文件下载完执行,顺序不定,会中断渲染;
1398 | 3. 动态创建 script;
1399 | 4. XHR 异步加载 JS
1400 |
1401 | ```js
1402 | // 动态创建 script
1403 | let script = document.createElement('script');
1404 | script.src = 'xxx.js';
1405 | document.body.append(script); // 添加到body才会执行
1406 |
1407 | // XHR 异步加载 JS + eval
1408 | let xhr = new XMLHttpRequest();
1409 | xhr.open('get', 'js/xxx.js', true);
1410 | xhr.send();
1411 | xhr.onreadystatechange = function() {
1412 | if (xhr.readyState == 4 && xhr.status == 200) {
1413 | eval(xhr.responseText);
1414 | }
1415 | }
1416 | ```
1417 |
1418 |
1419 |
1420 | ### 网络相关
1421 |
1422 | 介绍同源策略
1423 |
1424 | 浏览器同源策略限制不同源文档脚本不能进行交互。
1425 | 同源:同协议,同域名,同端口
1426 |
1427 |
1428 |
1429 | 前端跨域方案 / 表单可以跨域吗?/ 有无使用过 Apache 方案
1430 |
1431 | #### 表单支持跨域
1432 | 为何?自我理解:因为表单提交的数据,管控处理在后端,而其它数据的读取设置,还是要在前端限制
1433 |
1434 | 后端接口请求不存在跨域问题,只有前端浏览器同源(同协议,同域名,同端口)限制导致跨域问题。
1435 |
1436 | 1. JSONP:利用 script 标签的 src 属性来实现跨域,仅支持 GET 请求,url 长度限制;
1437 | 2. websocket;
1438 | 3. Nginx 反向代理;
1439 | 4. fetch;
1440 | 5. CORS:兼容性 IE10 +
1441 |
1442 |
1443 |
1444 | 什么是 CORS
1445 |
1446 | CORS (Cros-Origin Resource Sharing,跨域资源共享),额外的 HTTP 头授权浏览器访问跨域资源的机制
1447 |
1448 |
1449 |
1450 |
1451 | 输入 URL 到页面加载的过程
1452 |
1453 | 1. DNS 解析,将域名解析成 IP地址;
1454 | 2. TCP 连接(三次握手),客户端发送 HTTP 请求
1455 | 3. 服务端处理请求并返回 HTTP 响应报文
1456 | 3. 浏览器,解析 DOM 树 / CSS 规则树生成渲染树,进行页面渲染
1457 | 4. TCP 断开连接(四次挥手)
1458 |
1459 |
1460 |
1461 | Cookie, LocalStorage, SessionStorage, InndexDB
1462 |
1463 | **前三者只能存储字符串,后者能存储大量结构化数据(包括文件和blob)**
1464 |
1465 | #### Cookie
1466 | * 大小 4 K,`name=value` 中 `value` 值大小 4K;
1467 | * 每次请求在请求头携带,占用带宽;
1468 | * 初衷是解决 HTTP 无状态,服务端可共享,可设置过期时间;
1469 | * 服务端标记 cookie 为 HttpOnly 时,客户端不能读写,只能传输;
1470 |
1471 | ```js
1472 | document.cookie = 'name=KenTsang;domain=wuliv.com'
1473 | ```
1474 |
1475 | #### localStorage/sessiongStorage
1476 | * 大小 5 M,数据只保留本地,不参与服务端交互;
1477 | * localStorage:永久存在,需手动清除;
1478 | * sessionStorage:仅存在会话中,tab 关闭就失效,用于存储会话数据(如表单值),同源同窗口;
1479 |
1480 | ```js
1481 | window.localStorage.setItem('name', 'KenTsang');
1482 | window.localStorage.getItem('name'); // 'KenTsang
1483 |
1484 | // sessionStorage 用法一样
1485 | ```
1486 |
1487 | #### IndexedDB
1488 | * 键值对存储
1489 | * 异步
1490 | * 支持事务
1491 | * 空间大
1492 | * 支持二进制存储
1493 |
1494 | ```js
1495 | function openDB(dbName) {
1496 | const request = window.indexedDB.open(dbName);
1497 | request.onerror = function(e) {
1498 | //...
1499 | }
1500 | request.onsuccess = function(e) {
1501 | console.log(e.target.result); // IndexedDB对象
1502 | myDB.db = e.target.result;
1503 | }
1504 | }
1505 |
1506 | const myDB = {
1507 | name: 'testDB',
1508 | db: null
1509 | };
1510 | openDB(myDB.name);
1511 | ```
1512 |
1513 |
1514 |
1515 | cookie 和 token 都存放在 header 中,为何只劫持前者
1516 |
1517 |
1518 |
1519 | TCP 属于哪一层
1520 |
1521 | TCP 属于 OSI 的传输层,通讯过程是全双工
1522 |
1523 |
1524 |
1525 |
1526 | 网络(TCP/IP)的五层模型
1527 |
1528 | 1. 应用层(DNS)[应用、表示、会话]
1529 | 2. 传输层
1530 | 3. 网络层
1531 | 4. 数据链路层
1532 | 5. 物理层
1533 |
1534 |
1535 |
1536 |
1537 | TCP 与 UDP 的区别
1538 |
1539 | 1. TCP 面向链接,UDP 无连接,即发送数据之前不需要建立链接;
1540 | 2. TCP 提供可靠的服务,无差错、不丢失、不重复、且按序到达,UDP 不保证可靠支付;
1541 | 3. TCP 面向字节流,UDP 面向报文;
1542 | 4. TCP 是点对点,UDP 支持一对一,一对多,多对一和多对多;
1543 | 5. TCP 首部开心 20 字节,UDP 首部 8字节;
1544 | 6. TCP 逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道
1545 |
1546 |
1547 |
1548 |
1549 | HTTP 报文
1550 |
1551 |
1552 |
1553 | HTTP 请求头/响应头
1554 |
1555 |
1556 |
1557 | HTTP 状态码
1558 |
1559 | | - | 类型 | 意义 |
1560 | | -- | -- | -- |
1561 | | 1XX | 信息 | 接收的请求正在处理 |
1562 | | 2XX | 成功 | 请求正常处理完毕 |
1563 | | 3XX | 重定向 | 需要进行附加操作以完成请求 |
1564 | | 4XX | 客户端错误 | 服务器无法处理请求 |
1565 | | 5XX | 服务端错误 | 服务器处理请求出错 |
1566 |
1567 | 常见状态码:
1568 | #### 1XX
1569 | * 100
1570 | #### 2XX
1571 | * 200 服务器正确处理请求成功
1572 | * 204 请求成功,但没有响应实体
1573 | * 206 范围请求
1574 | #### 3XX
1575 | * 301 永久重定向
1576 | * 302 临时重定向
1577 | * 303 该资源存在另一个 URL,应使用 GET 方法获取
1578 | * 304 服务器允许访问,但该请求未满足条件
1579 | * 307 临时重定向
1580 | #### 4XX
1581 | * 400 请求报文存在语法错误
1582 | * 401 该请求需通过 HTTP 认证
1583 | * 403 服务器拒绝该资源的访问
1584 | * 404 服务器没有找到请求的资源
1585 | #### 5XX
1586 | * 500 服务器处理请求出错
1587 | * 503 服务器暂时处于超负载或正在停机维护,无法处理请求
1588 |
1589 |
1590 |
1591 |
1592 | HTTP/HTTPS 的区别
1593 |
1594 | HTTPS 是在 HTTP 上建立 SSL/TLS 安全协议加密层,并对传输数据进行加密,是 HTTP 协议的安全版本。
1595 |
1596 | HTTP 明文传输,数据有可能被传输过程被篡改。
1597 |
1598 | HTTPS:
1599 | 1. 数据隐私(明文对称加密加密,每次连接生成一个唯一的加密密钥)
1600 | 2. 数据完整(传输数据不可更改,校验数字签名)
1601 | 3. 身份认证(验证通信双方)
1602 |
1603 | HTTPS 握手需要五步,HTTP 则是三次。
1604 | 1. 客户端:发送 [ 协议版本号、生成随机数、支持的加密方法 ]
1605 | 2. 服务端:确认加密方法,发送 [ 数字证书,生成随机数 ]
1606 | 3. 客户端:确认证书有效,生成新的随机数,使用数字证书的公钥,加密这个随机数,发给服务端
1607 | 4. 服务端:确认自己的私钥,获取客户端发送的随机数
1608 | 5. 双方根据约定的加密方法,使用前面的3个随机数生成“对话密钥”(session key),加密接下来的对话过程
1609 |
1610 |
1611 |
1612 | HTTP1.x 和 HTTP2
1613 |
1614 | * HTTP1.x 是明文传输,数据安全欠缺(非HTTPS)
1615 | * header 每次携带,增加传输成本
1616 |
1617 | * HTTP1.0
1618 | * 每次请求都需要重新建立链接,增加延迟,
1619 | * HOLB (线头)阻塞,”请求1->应答1->请求2->应答2“
1620 | * HTTP1.1
1621 | * 管道化 pipeling 可一次性发送多个请求(同域名),但返回必须按顺序。实现 “请求1->请求2->应答1->应答2”, 前面请求未返回时,无法处理后面的应答;
1622 | * 长链接 keep-alive,复用一部分链接,只能处理同域名下,不同域名仍旧需要重新建立链接
1623 | * HTTP2
1624 | * 二进制传输
1625 | * 头部压缩,只更新变更部分(渐进更新),像 cookie 这种每次请求都携带的,只会在请求中发送一次(无变更时)
1626 | * 多路复用,打乱请求顺序,谁先完成谁先执行,以帧为单位,以标识组合(可指定优先级)
1627 | * 长链接,同域名下通信在单个链接上完成
1628 | * 服务端推送,可预先推送静态资源
1629 |
1630 |
1631 | ### React
1632 |
1633 | MVVM
1634 |
1635 | * M: Model; 数据模型,定义数据修改的业务处理逻辑
1636 | * V: View; UI 视图,负责数据渲染
1637 | * VM: ViewModel;一个同步 view 和 model 的对象,自动同步两者的修改
1638 |
1639 |
1640 |
1641 | 深度优先遍历(DFS)/广度优先遍历(BFS)
1642 |
1643 | #### 深度优先遍历
1644 | 一个节点向子节点查找,直到字节点为根节点,**子节点优先**。
1645 |
1646 | #### 广度优先遍历
1647 | 又称“宽度优先遍历”/“横向优先遍历”,**相邻节点优先**。
1648 |
1649 |
1650 |
1651 |
1652 | React 生命周期
1653 |
1654 | > 版本:16.8
1655 |
1656 | 生命周期阶段:
1657 | 1. 挂载阶段
1658 | 2. 更新阶段
1659 | 3. 卸载阶段
1660 |
1661 | #### 挂载阶段
1662 | * constructor
1663 | 构造函数,最先执行,初始化 state 或函数 this 绑定
1664 |
1665 | * getDerivedStateFromProps
1666 | 静态方法,接收到新属性 props 修改 state 时触发
1667 |
1668 | * render
1669 | 纯函数,处理渲染,不应包含业务逻辑或计算,返回:原生 Dom、React 组件、字符串/数字、布尔/null等
1670 |
1671 | * componentDidMount
1672 | 组件挂载完成,已获取到 DOM 节点,在在此处理:请求、订阅等(订阅配套要在 componentWillUnmount 取消)
1673 |
1674 | #### 更新阶段
1675 | * getDerivedStateFromProps
1676 |
1677 | * shouldComponentUpdate(nextProps, nextState)
1678 | 返回一个布尔值,ture(默认)表示重新渲染,false 反之。(在此做优化)
1679 |
1680 | * render
1681 |
1682 | * getSnapshotBeforeUpdate(prevProps, prevState)
1683 | 返回一个值给 componentDidUpdate,DOM元素状态的对比/计算
1684 |
1685 | * componentDidUpdate(prevProps, prevState, snapshot)
1686 |
1687 | #### 卸载阶段
1688 | * componentWillUnmount
1689 | 组件卸载或销毁时调用,(定时器销毁、取消网络请求、订阅等)
1690 |
1691 | #### 请求放置在哪个生命周期
1692 | **componentDidMount**
1693 |
1694 | > 为何不是 componentWillMount 做数据请求
1695 |
1696 | * 服务端渲染时,会执行两次,一次在服务端,一次在客户端
1697 | * 16版本的fiber重写后,会多次调用
1698 | * JS 异步机制不会等待 componentWillMount 完成后再 render
1699 |
1700 |
1701 |
1702 |
1703 | setState 是是同步还是同步
1704 |
1705 | * 异步:合成事件和钩子函数(可批量更新 `state`)
1706 | * 同步:原生事件和 `setTimeout` 中(不可批量更新)
1707 |
1708 | `setState` 本身执行过程和代码都是同步,只是合成事件和钩子函数的调用更新钱,无法立马拿到更新后的值,形成“异步”,需借助 `setState(callback)` 中 `callback` 来拿到。
1709 |
1710 | 批量更新 `state` 的优化也是借助于此,多次更新 `state`,会取最后一次执行,同时 `setState` 多个不同 `key` 值,会合并更新。
1711 |
1712 |
1713 |
1714 | React 组件通信方式
1715 |
1716 | * 父子组件通信:`props` 传递;
1717 | * 兄弟组件通信:状态提升到共有父组件,由父组件管控转发属性;
1718 | * 跨层级通信:`context` (组件树传递);
1719 | * 发布订阅模式(事件总线):`Event` 模块,发布者发布事件,订阅者监听事件并做出反馈;
1720 | * 全局状态管理:redux/mbox,维护全局 `store`
1721 |
1722 |
1723 |
1724 | React 组件/逻辑复用
1725 |
1726 | #### HOC 高阶组件 (组件包含来实现)
1727 | 缺点:
1728 | * 扩展性:HOC 无法从外部访问子组件的 `state`,因此无法通过 `shouldComponentUpdate` 过滤,可采用 `PureComponent` 来解决;
1729 | * Ref 传递:需借助 `React.forwardRef` 来解决
1730 | * 嵌套包装:增加复杂度和理解成本,命名冲突等
1731 |
1732 | #### Render Props 渲染属性
1733 | 缺点:解决来 HOC 组件嵌套问题,换成来函数回调的嵌套
1734 |
1735 | #### 自定义 Hooks
1736 | 优点:
1737 | * 简洁:解决 HOC 和 Render Props 的嵌套问题
1738 | * 解耦:UI 与 逻辑分离,彻底解耦
1739 | * 组合:Hooks 可引用其它 Hooks,多样化组合使用
1740 | * 对函数组件友好,
1741 | * 不同生命周期逻辑维护更简单
1742 | * 解决 `this` 指向
1743 | * 复用成本与理解降低
1744 |
1745 | 缺点:
1746 | * 写法限制,不能出现在条件、循环中
1747 | * 破话 `PureComponent`、`React.memo` 的性能优化
1748 | * `React.memo` 不能完全替代 `ShouldComponentUpdate`,只比对 `props`
1749 |
1750 |
1751 |
1752 |
1753 | Redux 和全局变量(window)的区别
1754 |
1755 | Redux 不能直接修改,由 reducer 维护,需要修改时需要使用 dispatch 去发起修改请求。并且 redux 的修改会通知到订阅方。
1756 |
1757 |
1758 |
1759 |
1760 | 解决 props 层级过深的问题
1761 |
1762 | 借用 Imuteable 不可变数据,解决。
1763 |
1764 |
1765 |
1766 | Base64 为什么能提升性能,优缺点
1767 |
1768 | #### Base64 转化图片
1769 | 优点:
1770 | 1. 图片文件以编码的形式嵌入到 HTML/CSS 文件中,减少 HTTP 请求
1771 | 2. 针对极小图片,借助 webpack 的 url-loader 可实现转化与内嵌
1772 | 3. 无跨域问题,无需考虑缓存、文件头或 cookie 问题
1773 | 5. 低版本浏览器不兼容(IE6/IE7)
1774 |
1775 | *可用于移动端首屏加载优化*
1776 |
1777 | 缺点:
1778 | 1. 过度使用会造成 CSS 文件过大
1779 | 2. 比原始二进制表示略大
1780 |
1781 | #### 插播:雪碧图
1782 | 优点:
1783 | 1. 将多个图片请求合并为一个
1784 |
1785 | 缺点:
1786 | 1. 难以维护和更新,多张小图合成
1787 | 2. 被迫加载全图
1788 |
1789 |
1790 | ### Webpack
1791 |
1792 | module、chunk、bundle 的区别
1793 |
1794 | * module: 编写的源码文件(单个文件)
1795 | * chunk: webpack 处理的文件(根据引用关系生成chunk文件)
1796 | * bundle: 最终生成给浏览器使用的文件
1797 |
1798 | **我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可执行的 bundle**
1799 |
1800 |
1801 |
1802 | filename、chunkFilename 的区别
1803 | entry 未引用的文件,但又需要使用的,会被处理为 chunk 文件,文件默认以 chunkId 开头 `1.chunk.js`
1804 |
1805 | * filename: 指 entry 输入的文件最后生成的文件名
1806 | * chunkFilename: 指未在 entry 中输入,却又需要生成的 chunk 文件名
1807 |
1808 |
1809 |
1810 | webpackPrefetch、webpackPreload、webpackChunkName?
1811 |
1812 | webpackPrefetch/webpackPreload 都可作为懒加载文件的预加载。
1813 |
1814 | * webpackPrefetch: 空闲时加载
1815 | * webpackPreload: 并行加载
1816 | * webpackChunkName: 定义 chunk 文件名(注释的形式)
1817 |
1818 |
1819 |
1820 |
1821 | hash、chunkhash、contenthash 的区别
1822 |
1823 | * hash: 与项目构建相关、整个版本
1824 | * chunkhash: 相关 chunk 文件更新,hash 改变
1825 | * contenthash: 单个文件内容更新,hash 改变
1826 |
1827 |
1828 |
1829 | source-map 常用
1830 |
1831 | * 最全:source-map
1832 | * 开发环境:cheap-module-eval-source-map
1833 | * 生产环境:cheap-module-source-map
1834 |
1835 |
1836 |
1837 | ### Git
1838 |
1839 | 常用知识点汇总
1840 |
1841 | #### git add 错误提交到暂存区,想恢复时
1842 | > git checkout --
1843 |
1844 | #### git add 错误提交到暂存区并 commit,想恢复时
1845 | > git reset HEAD
1846 | > git checkout --
1847 |
1848 | #### git commit 时描述填写错误,想修改时
1849 | > git commit --amend -m "新的描述"
1850 |
1851 | #### git commit 漏提交时
1852 | > git commit --amend --no-edit
1853 |
1854 |
1855 |
1856 |
1857 | ### 算法
1858 |
1859 | 冒泡排序
1860 |
1861 | 待整理...
1862 |
1863 |
1864 |
1865 | 快速排序
1866 |
1867 | 待整理...
1868 |
1869 |
1870 |
1871 | 归并排序
1872 |
1873 | 待整理...
1874 |
1875 |
1876 |
1877 | ### 设计模式
1878 |
1879 | 观察者和订阅-发布的区别,各自应用
1880 |
1881 | 待整理...
1882 |
1883 |
1884 | ### 性能优化与安全
1885 |
1886 | 懒加载与预加载
1887 |
1888 | #### 懒加载
1889 | 懒加载又称“按需加载”,多用与图片/列表等。
1890 |
1891 | 图片懒加载原理:
1892 | 1. 图片 image 默认 src 属性为空字符串,添加 data-original 属性存储真实图片 url
1893 | 2. 监听页面滚动 scroll 事件,判断图片是否进入可视区域,若是,则将 src 设置为真实的 url 地址,进行图片资源请求
1894 |
1895 | #### 预加载
1896 | 预先将资源请求加载到本地
1897 |
1898 | 图片预加载方式:
1899 | 1. HTML 标签
1900 | 2. ajax 请求
1901 |
1902 | #### 懒加载/预加载对比
1903 | 1. 懒加载:迟缓甚至不加载,减少初始化请求数量,缓解服务器压力
1904 | 2. 预加载:提前加载,增加服务器压力,方便用户后续使用
1905 |
1906 |
1907 |
1908 |
1909 | DNS 预解析
1910 |
1911 | ```html
1912 |
1913 | ```
1914 |
1915 | 浏览器会对页面只能够 `a` 标签的 `href` 启用 DNS 预解析,所以无需在 link 中设置,但是 HTTPS 下不生效,需使用 meta 来强制开启
1916 |
1917 | ```html
1918 |
1919 | ```
1920 |
1921 |
1922 |
1923 | 强缓存/协商缓存
1924 |
1925 | #### 强缓存
1926 | 1. Expires
1927 | 2. Cache-Control
1928 |
1929 | #### 协商缓存
1930 | 1. Last-Modified
1931 | 2. E-Tag/If-none-match
1932 |
1933 | #### 禁止浏览器缓存的头字段
1934 | 1. Expires: -1/0
1935 | 2. Cache-Control: no-cache
1936 | 3. Pragma: no-cache
1937 |
1938 |
1939 |
1940 |
1941 | 前端性能优化
1942 |
1943 | * 减小文件体积(文件压缩)
1944 | * 减少请求数量(文件合并,请求合并)
1945 | * 缓存加速(DNS/强缓存/协商缓存)
1946 | * DNS 预解析
1947 | * 资源加载方式(懒加载/预加载/异步加载)
1948 | * 尽量避免重绘/回流
1949 |
1950 |
1951 |
1952 |
1953 | 前端安全问题
1954 |
1955 | * XSS 跨站脚本攻击(反射型、存储型)
1956 | * 完善过滤
1957 | * CSRF 跨站请求伪造
1958 | * 验证 refer
1959 | * 添加 token 验证
1960 | * iframe 放置第三方网页,实现对顶层窗体的操控
1961 | * H5 新增 iframe 的 sandbox 属性限制 iframe 操作
1962 | * 透明 iframe 实现点击劫持
1963 | * 设置 X-Frame-Options: DENY 禁止网页可被 iframe 嵌套
1964 | * 浏览器错误内容判断,将非图片的文件自解析执行
1965 | * 后端设置 X-Content-Type-OptionsHTTP Header 参数为 nosniff,浏览器不会去推断
1966 | * HTTPS 中间人攻击,首次 HTTP 请求拦截,强制降级为HTTP
1967 | * HSTS 设置 header 强制任何通信都是 https
1968 |
1969 |
--------------------------------------------------------------------------------