├── 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 | ![10个你可能不知道的MacBook使用小技巧_新浪众测](http://sinastorage.com/storage.miaosha.sina.com.cn/product/20190610/b7188c5bec6c8757d91279f83e1859ee.gif) 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 | ![10个你可能不知道的MacBook使用小技巧_新浪众测](http://sinastorage.com/storage.miaosha.sina.com.cn/product/20190610/6e0947501e32baccc8178f77a9ecb7dd.gif) 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 | ![10个你可能不知道的MacBook使用小技巧_新浪众测](http://sinastorage.com/storage.miaosha.sina.com.cn/product/20190610/9217297d637808defbc1e272efbc82a8.gif) 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 | 41 | ``` 42 | 3. emits 选项 43 | * 原生事件会触发两次 44 | * 更好的指示组件工作方式 45 | * 对象形式事件校验 46 | ```html 47 | 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 | ![url-loader](http://img.wuliv.com/1568626043156.png) 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 |
142 |
子元素
143 |
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 |
--------------------------------------------------------------------------------