├── CSS ├── CSS-Note-1.md ├── CSS-Note-2.md └── 性能优化.md ├── HTML ├── Canvas性能优化.md └── HTML-Note-1.md ├── JavaScript ├── JS-算法-note.md ├── JS编码规范.md ├── JavaScript容易被忽视的零碎知识点.md ├── Promise的一种实现.md ├── js四则运算精度问题.md ├── leetcode │ ├── 1-20.md │ └── 21-40.md ├── 元编程.md └── 手写实现bind、call等原生api.md ├── LICENSE ├── README.md ├── React ├── Next.js-README.md ├── React16.x相关.md ├── img │ └── 1.png ├── somecase.md ├── 性能优化.md └── 自定义实现简单路由.md ├── Solution ├── JavaScript反调试技巧.md ├── 保持原样上传而不旋转图片的方向 │ └── flipPic.js ├── 实现Date类的继承.md ├── 实现JavaScript的私有变量.md ├── 编写可维护的现代化前端项目.md └── 网页端调起分享功能 │ └── browserShareTo.js ├── Vue ├── Vue-Note-1.md ├── Vue-Router-Note.md ├── solution │ ├── 使用Vue.extend构造工具组件toast │ │ ├── app.vue │ │ └── toast │ │ │ ├── index.js │ │ │ └── index.vue │ └── 输入手机号时334加入以及删除空格的组件.md ├── 异步组件.md ├── 性能优化.md ├── 新特性.md └── 无渲染Vue组件 │ ├── README.md │ └── project │ ├── index.vue │ └── toggle.js ├── Webpack └── somecase.md ├── getDirStruct.js ├── img ├── 200px-Telephone-keypad2.svg.png ├── aframe.gif ├── appinn.png ├── awesomplete.gif ├── caniuse.png ├── chinaz.png ├── cleavejs.gif ├── csspin.gif ├── dragdealer.gif ├── dummyimage.png ├── dynamicsjs.gif ├── emojipedia.png ├── iconfont.jpg ├── iconmoon.png ├── ios-app-icon.png ├── jsbin.png ├── makeappicon.png ├── mathjax.png ├── notie.gif ├── onepage-scroll.gif ├── oschina.png ├── parallax.gif ├── pgyer.png ├── picker.gif ├── pushjs.gif ├── react-sortable-hoc.gif ├── regexper.png ├── reveal.png ├── scroll-over.gif ├── scrollrevealjs.gif ├── sigma.png ├── sitepointstatic.png ├── slick.gif ├── trackingjs.gif ├── typeahead.gif ├── video-to-gif.png ├── videojs.png ├── vivus.gif ├── waypoint.gif └── zhitu.png ├── 实用代码段 ├── 代码段(1).md ├── 代码段(2).md └── 代码段(3).md ├── 实用工具 ├── github上实用、轻量级、无依赖的插件和库(1).md ├── github上实用、轻量级、无依赖的插件和库(2).md └── 常用的工具、网站.md ├── 常用技巧 ├── 前端技巧(1).md └── 前端技巧(2).md ├── 笔试题&面试题 ├── 前端面试笔试题(1).md └── 前端面试笔试题(2).md └── 读书笔记 ├── CSS揭秘.md ├── HTML5与CSS3权威指南(上).md ├── HTML5与CSS3权威指南(下).md ├── Javascript权威指南与高级编程(1).md ├── Javascript权威指南与高级编程(2).md ├── Javascript权威指南与高级编程(3).md ├── 你不知道的JavaScript(1).md ├── 你不知道的JavaScript(2).md ├── 锋利的JQuery.md └── 高性能JavaScript.md /CSS/CSS-Note-2.md: -------------------------------------------------------------------------------- 1 | ## `nth-child`负值 2 | 3 | `nth-child`可以接受负值 4 | ```css 5 | /* 第 1 到 3个元素字体颜色为 red */ 6 | li:nth-child(-n+3) { 7 | color: red; 8 | } 9 | ``` 10 | 11 | 可以配合 `:not()` 12 | ```css 13 | /* 选择除前3个之外的所有元素,设置颜色为 red */ 14 | li:not(:nth-child(-n+3)) { 15 | color: red; 16 | } 17 | ``` 18 | 19 | `:nth-of-type`同上用法 20 | 21 | ## 利用属性选择器来选择空链接 22 | 23 | 当 元素没有文本内容,但有 `href` 属性的时候,显示它的 `href` 属性: 24 | ```css 25 | a[href^="http"]:empty::before { 26 | content: attr(href); 27 | } 28 | ``` 29 | 30 | ## 图片链接无效的处理 31 | 32 | 当 `img`元素 `src`指向的地址无效时,会呈现为一种不太友好的样子,例如破碎图片的图标或者干脆什么都不显示,利用下述 `css`可以在图片链接失效时,呈现指定的内容: 33 | ```css 34 | img::before { 35 | /* 显示文字 */ 36 | content: "We're sorry, the image below is broken :("; 37 | display: block; 38 | margin-bottom: 10px; 39 | } 40 | 41 | img::after { 42 | /* 显示链接 */ 43 | content: "(url: " attr(src) ")"; 44 | display: block; 45 | font-size: 12px; 46 | } 47 | ``` 48 | 49 | 上述原理是,当 `src`地址无效,则显示 `::before` 和 `::after`的内容,否则这两个伪元素将不显示 50 | 51 | ## 相同属性就近覆盖原则 52 | 53 | ```html 54 |
first
55 |
second
56 | ``` 57 | ```css 58 | .red { 59 | color: red; 60 | } 61 | .blue { 62 | color: blue; 63 | } 64 | ``` 65 | 66 | 上述两个元素中的 字体分别是什么颜色?答案:都是 blue 67 | 68 | 原因是元素最终应用的 class是 **定义在样式表最靠后位置的那个**,跟元素 `class`属性值的书写顺序无关 69 | 70 | ## 提前加载体积较大图片 71 | 72 | 有些懒加载图片,需要等到我们触发了相应操作才进行加载,例如点击某个按钮,但是由于图片体积较大,或者网络信号不好,操作完毕之后,图片需要一定的时间才能完全加载完毕并显示,用户体验不好,这里有一个小技巧可应用于提前加载此图片 73 | 74 | ```css 75 | .box:before { 76 | display: none; 77 | content: url(https://dummyimage.com/200x200/fafff0) 78 | } 79 | ``` 80 | 81 | 通过这种手段,不会在页面上添加额外的 `DOM`元素,也不会对页面显示产生什么影响,但浏览器会提前缓存 `https://dummyimage.com/200x200/fafff0`,需要使用的时候可以实现即时地展现,提升用户体验 82 | 83 | ## 借助 `wbr`标签实现连续英文字符的精准换行 84 | 85 | 既能换行,又不影响单词阅读,借助 ``标签,相比于直接设置 `word-break:break-all` 或 `word-wrap:break-word`更加智能 86 | 87 | ```html 88 |
89 | CanvasRenderingContext2D.globalCompositeOperation 90 |
91 | ``` 92 | 93 | 除了 `IE`不支持此属性外,其他浏览器支持度很好,对于 `IE`浏览器,可以通过 `css`进行兼容 94 | ```css 95 | wbr:after { content: '\00200B'; } 96 | ``` 97 | 98 | ## CSS Sub-pixel 99 | 100 | 指当给元素设置一个浮点数的 `css`属性时,例如 `width: 3.2px;`,某些浏览器会将浮点数四舍五入,实际给元素设置了一个整数值 101 | 102 | 另外,`canvas`绘图时,可能也会出现这种问题,例如计算某个元素持续性移动的坐标,可能会得到浮点数值的坐标 `(20.3, 30.8)`,当给某些 `api`,例如 `drawImage`传入 这种浮点数值的时候,实际渲染出来的坐标就会变成 `(20, 31)`,导致元素出现抖动 103 | 104 | 解决的办法是尽量让计算出来的坐标为整数 105 | 106 | ## 是否阻塞文档 107 | 108 | 使用`link`标签引用的 `css`文件,会异步加载,所以不会阻塞 `DOM`的 **解析**,但是会阻塞 `DOM`的 **渲染** 109 | 110 | ## 多行省略 111 | 112 | - `-webkit-line-clamp` 113 | 114 | `-webkit`内核浏览器的私有特性,一般移动端浏览器都是使用这种内核,所以可以使用,作为渐进增强的体验方案 115 | 116 | 需要注意的是,如果是英文,则长英文不会自动换行,需要增加额外代码: 117 | ```css 118 | word-wrap:break-word; 119 | word-break:break-all; 120 | ``` 121 | 122 | - 利用 `float`的特性 123 | 124 | ```html 125 |
126 |
腾讯成立于1998年11月,是目前中国领先的互联网增值服务提供商之一。成立10多年来,腾讯一直秉承“一切以用户价值为依归”的经营理念,为亿级海量用户提供稳定优质的各类服务,始终处于稳健发展状态。2004年6月16日,腾讯控股有限公司在香港联交所主板公开上市(股票代号700)。
127 |
placeholder
128 |
...更多
129 |
130 | ``` 131 | 132 | ```css 133 | .box { 134 | height: 108px; 135 | overflow: hidden; 136 | } 137 | .box1 { 138 | float:right; 139 | margin-left:-50px; 140 | width:100%; 141 | background: hsla(229, 100%, 75%, 0.5); 142 | } 143 | .box2 { 144 | float:right; 145 | width:50px; 146 | height:108px; 147 | color:transparent; 148 | background: hsla(334, 100%, 75%, 0.5); 149 | } 150 | .box3 { 151 | float:right; 152 | width:50px; 153 | height:18px; 154 | position: relative; 155 | background-color: yellowgreen; 156 | left: 100%; 157 | transform: translate(-100%,-100%); 158 | } 159 | ``` 160 | 161 | ## [iphoneX 刘海屏适配](https://juejin.im/post/5be95fbef265da61327ed8e0) 162 | 163 | 通过以下几个步骤,即可实现所有 `IOS11.x`系统的适配了,也就是主体内容显示在安全区域内,包括横屏情况 164 | 165 | - 设置网页在可视区域的布局方式 166 | 167 | 新增 `viweport-fit` 属性,使得页面内容完全覆盖整个窗口: 168 | ```html 169 | 170 | ``` 171 | 172 | - 让主体内容控制在安全区域内 173 | 174 | 可以通过 `constant()` 可以获取到非安全边距,再结合 `padding` 或 `margin` 来控制页面元素避开非安全区域。 `Webkit` 在 `iOS11`中新增`CSS Functions: env()`替代 `constant()`,`constant()` 从 `Safari Techology Preview 41` 和 `iOS11.2 Beta`开始会被弃用。在不支持`env()`的浏览器中,会自动忽略这一样式规则,不影响网页正常的渲染。为了达到最大兼容目的,可以 `constant()` 和 `env()` 同时使用 175 | 176 | ```css 177 | body { 178 | padding-bottom: constant(safe-area-inset-top); /* iOS 11.0 */ 179 | padding-top: env(safe-area-inset-top); /* iOS 11.2+ */ 180 | padding-right: constant(safe-area-inset-right); 181 | padding-right: env(safe-area-inset-right); 182 | padding-bottom: constant(safe-area-inset-bottom); 183 | padding-bottom: env(safe-area-inset-bottom); 184 | padding-left: constant(safe-area-inset-left); 185 | padding-left: env(safe-area-inset-left); 186 | } 187 | ``` 188 | 189 | ## box-shadow技巧 190 | 191 | ### 单侧投影 192 | 193 | 如果阴影的模糊半径,与负的扩张半径一致,那么我们将看不到任何阴影,因为生成的阴影将被包含在原来的元素之下,除非给它设定一个方向的偏移量。所以这个时候,我们给定一个方向的偏移值,即可实现单侧投影,例如: 194 | 195 | 单左侧投影: 196 | ```css 197 | box-shadow: -7px 0 5px -5px red; 198 | ``` 199 | 200 | 单下测投影: 201 | ```css 202 | box-shadow: 0 7px 5px -5px red; 203 | ``` 204 | 205 | ### 投影背景 / 背景动画 206 | 207 | 当 `box-shadow` 的模糊半径和扩张半径都为 `0` 的时候,我们也可以得到一个和元素大小一样的阴影,只不过被元素本身遮挡住了,我们尝试将其偏移出来: 208 | 209 | ```css 210 | width: 80px; 211 | height: 80px; 212 | border: 1px solid #333; 213 | box-sizing: border-box; 214 | box-shadow: 80px 80px 0 0 #000; 215 | ``` 216 | 217 | `box-shadow` 是可以设置多层的,也就是多层阴影,而且可以进行过渡变换动画(补间动画)。但是 `background-image: linear-gradient()`,也就是渐变背景是不能进行补间动画的,例如 [box-shadow实现背景动画](https://codepen.io/Chokcoco/pen/WaBYZL) 218 | 219 | ### 立体投影 220 | 221 | - 立体投影的关键点在于利于伪元素生成一个大小与父元素相近的元素,然后对其进行 rotate 以及定位到合适位置,再赋于阴影操作 222 | - 颜色的运用也很重要,阴影的颜色通常比本身颜色要更深,这里使用 `hsl` 表示颜色更容易操作,`l` 控制颜色的明暗度 223 | 224 | 示例:[立体投影](https://codepen.io/Chokcoco/pen/LgdRKE?editors=1100) 225 | 226 | ## FLIP 227 | 228 | 即`First, Last, Invert, Play`,一种让 `css`动画更流畅和更简单的技术,具体参见 [FLIP技术给Web布局带来的变化](https://www.w3cplus.com/javascript/animating-layouts-with-the-flip-technique.html) 229 | 230 | ## 网站主题色切换方案 231 | 232 | 详见 [聊一聊前端换肤](https://blog.souche.com/untitled-17/) 233 | 234 | ## nth-child 进阶用法 235 | 236 | 上面的用法中的第三部分,一般都是使用 `n`,而有时候也会用到 `-n`,比如选取前2个元素就是:`nth-child(-n + 2)` 237 | 238 | 为什么是这样呢?其实运算:`-n + 2 ≥ 0`,就可以推出 `n ≤ 2`。 239 | 240 | 由此,结合两者自动取交集,我们就可以限制选择某一范围。比如选择第 `6`个到第 `9`个,就是:`:nth-child(-n+9):nth-child(n+6)` 241 | 242 | 注意:不是 `nth-child(2 - n)`,`-n`要写在一起! 243 | -------------------------------------------------------------------------------- /CSS/性能优化.md: -------------------------------------------------------------------------------- 1 | ## 内联CSS 2 | 3 | 使用 ``标签引入 `css`文件,这会阻塞页面的继续加载,如果 `css`文件较大,会使页面呈现时间较长的空白,页面首次有效绘制(FMP)将变大,不利于用户体验。 4 | 5 | 所以可以将首页所需的 **基本**样式设为内联样式,剩下那些首页用不到或者暂时用不到的样式,则异步加载,并将 ``放到页面的底部,这样既不会影响 FMP,也不会增大太多 `HTML`的体积 6 | 7 | ## 异步渲染 8 | 9 | - js动态加载样式 10 | 11 | ```js 12 | const link = document.createElement('link') 13 | link.rel = 'stylesheet' 14 | link.href = 'mystyle.css' 15 | const head = document.head 16 | head.insertBefore(link, head.childNodes[head.childNodes.length - 1].nextSibling) 17 | ``` 18 | 19 | - 设置 `link`元素的 `media`属性 20 | 21 | 将 `link`元素的 `media`属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如 `media="print"`,甚至可以是完全不存在的类型`media="noexist"`。对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。 22 | 23 | 当然,这么做只是为了实现 `CSS`的异步加载,别忘了在文件加载完成之后,将 `media` 的值设为 `screen `或 `all`,从而让浏览器开始解析`CSS`: 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | ## 文件压缩 30 | 31 | ## 去除无用CSS 32 | 33 | ## 有选择地使用选择器 34 | 35 | >`CSS`选择器的匹配是从右向左进行的 36 | 37 | - 保持简单,不要使用嵌套过多过于复杂的选择器。 38 | - 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用。 39 | - 不要使用类选择器和ID选择器修饰元素标签,如h3#markdown-content,这样多此一举,还会降低效率。 40 | - 不要为了追求速度而放弃可读性与可维护性。 41 | 42 | ## 减少使用昂贵的属性 43 | 44 | 在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写`CSS`时,**除非**真的需要,我们应该尽量减少使用昂贵属性,如`box-shadow/border-radius/filter/透明度/:nth-child`等。 45 | 46 | ## 优化重排与重绘 47 | 48 | - 减少重排 49 | 50 | 包括但不限于下面的动作,将触发重排 51 | > 52 | > * 改变font-size和font-family 53 | > * 改变元素的内外边距 54 | > * 通过JS改变CSS类 55 | > * 通过JS获取DOM元素的位置相关属性(如width/height/left等) 56 | > * CSS伪类激活 57 | > * 滚动滚动条或者改变窗口大小 58 | 59 | - 避免不必要的重绘 60 | 61 | 包括但不限于下面的动作,将触发重排 62 | 63 | >color,background,visibility 64 | 65 | ## 不要使用@import 66 | 67 | 首先,使用 `@import`引入 `CSS`会影响浏览器的并行下载。使用 `@import`引用的 `CSS`文件只有在引用它的那个 `css`文件被下载、解析之后,浏览器才会知道还有另外一个 `css`需要下载,这时才去下载,然后下载后开始解析、构建 `render tree`等一系列操作。这就导致浏览器无法并行下载所需的样式文件。 68 | 69 | 其次,多个 `@import`会导致下载顺序紊乱。在IE中, `@import`会引发资源文件的下载顺序被打乱,即排列在 `@import`后面的 `js`文件先于 `@import`下载,并且打乱甚至破坏 `@import`自身的并行下载。 -------------------------------------------------------------------------------- /HTML/Canvas性能优化.md: -------------------------------------------------------------------------------- 1 | 最近对 `html5`小游戏有点兴趣,因为我感觉将来这个东西或许是前端一个重要的应用场景,例如现在每到某些节假日,像支付宝、淘宝或者其他的一些 `APP`可能会给你推送通知,然后点进去就是一个小游戏,基本上点进去的人,只要不是太抵触,都会玩上一玩的,如果要是恰好 `get`到用户的 `G`点,还能进一步增强业务,无论是用户体验,还是对业务的发展,都是一种很不错的提升方式。 2 | 3 | 另外,我说的这个 `html5`小游戏是包括 `WebGL`、`WebVR`等在内的东西,不仅限于游戏,也可以是其他用到相关技术的场景,例如商品图片 `360°`在线查看这种,之所以从小游戏入手,是因为小游戏需要的技术包罗万象,能把游戏做好,再用相同的技术去做其他的事情,就比较信手拈来了 4 | 5 | 查找资料,发现门道还是蛮多的,看了一圈下来,决定从基础入手,先从较为简单的 `canvas` 游戏看起,看了一些相关文章和书籍,发现这个东西虽然用起来很简单,但是真想用好,发挥其该有的能力还是有点难度的,最好从实战入手 6 | 7 | 于是最近准备写个 `canvas`小游戏练手,相关 `UI`素材已经搜集好了,不过俗话说 **工欲善其事必先利其器**,由于对这方面没什么经验,所以为了避免过程中出现的各种坑点,特地又看了一些相关的踩坑文章,其中性能我感觉是必须要注意的地方,而且门道很多,所以整理了一下 8 | 9 | ## 使用 `requestNextAnimationFrame`进行动画循环 10 | 11 | `setTimeout` 和 `setInterval`并非是专为连续循环产生的 `API`,所以可能无法达到流畅的动画表现,故用 `requestNextAnimationFrame`,可能需要 `polyfill`: 12 | 13 | `requestNextAnimationFrame`: 14 | ```js 15 | const raf = requestAnimationFrame 16 | || webkitRequestAnimationFrame 17 | || mozRequestAnimationFrame 18 | || function(callback) { 19 | setTimeout(callback, 1000 / 60) 20 | } 21 | ``` 22 | 23 | `requestAnimationFrame`: 24 | ```js 25 | const cancelRaf = cancelAnimationFrame 26 | || webkitCancelAnimationFrame 27 | || mozCancelAnimationFrame 28 | || (handler => { 29 | clearTimeout(handler) 30 | }) 31 | ``` 32 | 33 | ## 利用剪辑区域来处理动画背景或其他不变的图像 34 | 35 | 如果只是简单动画,那么每一帧动画擦除并重绘画布上所有内容是可取的操作,但如果背景比较复杂,那么可以使用 **剪辑区域**技术,通过每帧较少的绘制来获得更好的性能 36 | 37 | 利用剪辑区域技术来恢复上一帧动画所占背景图的执行步骤: 38 | 39 | - 调用 `context.save()`,保存屏幕 `canvas`的状态 40 | - 通过调用 `beginPath`来开始一段新的路径 41 | - 在 `context`对象上调用 `arc()`、`rect()`等方法来设置路径 42 | - 调用 `context.clip()`方法,将当前路径设置为屏幕 `canvas`的剪辑区域 43 | - 擦除屏幕 `canvas`中的图像(实际上只会擦除剪辑区域所在的这一块范围) 44 | - 将背景图像绘制到屏幕 `canvas`上(绘制操作实际上只会影响剪辑区域所在的范围,所以每帧绘制图像像素数更少) 45 | - 恢复屏幕 `canvas`的状态参数,重置剪辑区域 46 | 47 | ## 离屏缓冲区(离屏canvas) 48 | 49 | 先绘制到一个离屏 `canvas`中,然后再通过 `drawImage`把离屏 `canvas` 画到主 `canvas`中,就是把离屏 `canvas`当成一个缓存区。把需要重复绘制的画面数据进行缓存起来,减少调用 `canvas`的 `API`的消耗 50 | 51 | ```js 52 | const cacheCanvas = document.createElement('canvas') 53 | const cacheCtx = cacheCanvas.getContext('2d') 54 | cacheCtx.width = 200 55 | cacheCtx.height = 200 56 | // 绘制到主canvas上 57 | ctx.drawImage(0, 0) 58 | ``` 59 | 60 | 虽然离屏 `canvas`在绘制之前视野内看不到,但其宽高最好设置得跟缓存元素的尺寸一样,避免资源浪费,也避免绘制多余的不必要图像,同时在 `drawImage`时缩放图像也将耗费资源 61 | 必要时,可以使用多个离屏 `canvas` 62 | 另外,离屏 `canvas`不再使用时,最好把手动将引用重置为 `null`,避免因为 `js`和 `dom`之间存在的关联,导致垃圾回收机制无法正常工作,占用资源 63 | 64 | ## 尽量利用 `CSS` 65 | 66 | ### 背景图 67 | 68 | 如果有大的静态背景图,直接绘制到 `canvas`可能并不是一个很好的做法,如果可以,将这个大背景图作为 `background-image` 放在一个 `DOM`元素上(例如,一个 `div`),然后将这个元素放到 `canvas`后面,这样就少了一个 `canvas`的绘制渲染 69 | 70 | ### transform变幻 71 | 72 | `CSS`的 `transform`性能优于 `canvas`的 `transfomr API`,因为前者基于可以很好地利用 `GPU`,所以如果可以,`transform`变幻请使用 `CSS`来控制 73 | 74 | ## 关闭透明度 75 | 76 | 创建 `canvas`上下文的 `API`存在第二个参数: 77 | ```js 78 | canvas.getContext(contextType, contextAttributes) 79 | ``` 80 | 81 | `contextType` 是上下文类型,一般值都是 `2d`,除此之外还有 `webgl`、`webgl2`、`bitmaprenderer`三个值,只不过后面三个浏览器支持度太低,一般不用 82 | 83 | `contextAttributes` 是上下文属性,用于初始化上下文的一些属性,对于不同的 `contextType`,`contextAttributes`的可取值也不同,对于常用的 `2d`,`contextAttributes`可取值有: 84 | 85 | - alpha 86 | 87 | `boolean`类型值,表明 `canvas`包含一个 `alpha`通道. 默认为 `true`,如果设置为 `false`, 浏览器将认为 `canvas`背景总是不透明的, 这样可以加速绘制透明的内容和图片 88 | 89 | - willReadFrequently 90 | 91 | `boolean`类型值,表明是否有重复读取计划。经常使用 `getImageData()`,这将迫使软件使用 `2D canvas` 并节省内存(而不是硬件加速)。这个方案适用于存在属性 `gfx.canvas.willReadFrequently`的环境。并设置为 `true` (缺省情况下,只有`B2G / Firefox OS`) 92 | 93 | 支持度低,目前只有 `Gecko`内核的浏览器支持,不常用 94 | 95 | - storage 96 | 97 | `string` 这样表示使用哪种方式存储(默认为:持久(`persistent`)) 98 | 99 | 支持度低,目前只有 `Blink`内核的浏览器支持,不常用 100 | 101 | 上面三个属性,看常用的 `alpha`就行了,如果你的游戏使用画布而且不需要透明,当使用 `HTMLCanvasElement.getContext()` 创建一个绘图上下文时把` alpha` 选项设置为 `false` ,这个选项可以帮助浏览器进行内部优化 102 | 103 | ```js 104 | const ctx = canvas.getContext('2d', { alpha: false }) 105 | ``` 106 | 107 | ## 尽量不要频繁地调用比较耗时的API 108 | 109 | 例如 110 | 111 | `shadow`相关 `API`,此类 `API`包括 `shadowOffsetX`、`shadowOffsetY`、`shadowBlur`、`shadowColor` 112 | 113 | 绘图相关的 `API`,例如 `drawImage`、`putImageData`,在绘制时进行缩放操作也会增加耗时时间 114 | 115 | 当然,上述都是尽量避免 **频繁**调用,或用其他手段来控制性能,需要用到的地方肯定还是要用的 116 | 117 | ## 避免浮点数的坐标 118 | 119 | 利用 `canvas`进行动画绘制时,如果计算出来的坐标是浮点数,那么可能会出现 `CSS Sub-pixel`的问题,也就是会自动将浮点数值四舍五入转为整数,那么在动画的过程中,由于元素实际运动的轨迹并不是严格按照计算公式得到,那么就可能出现抖动的情况,同时也可能让元素的边缘出现抗锯齿失真 120 | 这也是可能影响性能的一方面,因为一直在做不必要的取证运算 121 | 122 | ## 渲染绘制操作不要频繁调用 123 | 124 | 渲染绘制的 `api`,例如 `stroke()`、`fill`、`drawImage`,都是将 `ctx`状态机里面的状态真实绘制到画布上,这种操作也比较耗费性能 125 | 126 | 例如,如果你要绘制十条线段,那么先在 `ctx`状态机中绘制出十天线段的状态机,再进行一次性的绘制,这将比每条线段都绘制一次要高效得多 127 | 128 | ```js 129 | for (let i = 0; i < 10; i++) { 130 | context.beginPath() 131 | context.moveTo(x1[i], y1[i]) 132 | context.lineTo(x2[i], y2[i]) 133 | // 每条线段都单独调用绘制操作,比较耗费性能 134 | context.stroke() 135 | } 136 | 137 | for (let i = 0; i < 10; i++) { 138 | context.beginPath() 139 | context.moveTo(x1[i], y1[i]) 140 | context.lineTo(x2[i], y2[i]) 141 | } 142 | // 先绘制一条包含多条线条的路径,最后再一次性绘制,可以得到更好的性能 143 | context.stroke() 144 | ``` 145 | 146 | ## 尽量少的改变状态机 ctx的里状态 147 | 148 | `ctx`可以看做是一个状态机,例如 `fillStyle`、`globalAlpha`、`beginPath`,这些 `api`都会改变 `ctx`里面对于的状态,频繁改变状态机的状态,是影响性能的 149 | 150 | 可以通过对操作进行更好的规划,减少状态机的改变,从而得到更加的性能,例如在一个画布上绘制几行文字,最上面和最下面文字的字体都是 `30px`,颜色都是 `yellowgreen`,中间文字是 `20px pink`,那么可以先绘制最上面和最下面的文字,再绘制中间的文字,而非必须从上往下依次绘制,因为前者减少了一次状态机的状态改变 151 | 152 | ```js 153 | const c = document.getElementById("myCanvas") 154 | const ctx = c.getContext("2d") 155 | 156 | ctx.font = '30 sans-serif' 157 | ctx.fillStyle = 'yellowgreen' 158 | ctx.fillText("大家好,我是最上面一行", 0, 40) 159 | 160 | ctx.font = '20 sans-serif' 161 | ctx.fillStyle = 'red' 162 | ctx.fillText("大家好,我是中间一行", 0, 80) 163 | 164 | ctx.font = '30 sans-serif' 165 | ctx.fillStyle = 'yellowgreen' 166 | ctx.fillText("大家好,我是最下面一行", 0, 130) 167 | ``` 168 | 169 | 下面的代码实现的效果和上面相同,但是代码量更少,同时比上述代码少改变了一次状态机,性能更加 170 | ```js 171 | ctx.font = '30 sans-serif' 172 | ctx.fillStyle = 'yellowgreen' 173 | ctx.fillText("大家好,我是最上面一行", 0, 40) 174 | ctx.fillText("大家好,我是最下面一行", 0, 130) 175 | 176 | ctx.font = '20 sans-serif' 177 | ctx.fillStyle = 'red' 178 | ctx.fillText("大家好,我是中间一行", 0, 80) 179 | ``` 180 | 181 | ## 尽量少的调用 `canvas API` 182 | 183 | 嗯,`canvas`也是通过操纵 `js`来绘制的,但是相比于正常的 `js`操作,调用 `canvas API`将更加消耗资源,所以在绘制之前请做好规划,通过 **适量** `js`原生计算减少 `canvas API`的调用是一件比较划算的事情 184 | 185 | 当然,请注意 **适量**二字,如果减少一行 `canvas API`调用的代价是增加十行 `js`计算,那这事可能就没必要做了 186 | 187 | ## 避免阻塞 188 | 189 | 在进行某些耗时操作,例如计算大量数据,一帧中包含了太多的绘制状态,大规模的 `DOM`操作等,可能会导致页面卡顿,影响用户体验,可以通过以下两种手段: 190 | 191 | ### web worker 192 | 193 | `web worker`最常用的场景就是大量的频繁计算,减轻主线程压力,如果遇到大规模的计算,可以通过此 `API`分担主线程压力,此 `API`兼容性已经很不错了,既然 `canvas`可以用,那 `web worker`也就完全可以考虑使用 194 | 195 | ### 分解任务 196 | 197 | 将一段大的任务过程分解成数个小型任务,使用定时器轮询进行,想要对一段任务进行分解操作,此任务需要满足以下情况: 198 | 199 | - 循环处理操作并不要求同步 200 | - 数据并不要求按照顺序处理 201 | 202 | 分解任务包括两种情形: 203 | 204 | - 根据任务总量分配 205 | 206 | 例如进行一个千万级别的运算总任务,可以将其分解为 `10`个百万级别的运算小任务 207 | ```js 208 | // 封装 定时器分解任务 函数 209 | function processArray(items, process, callback) { 210 | // 复制一份数组副本 211 | var todo=items.concat(); 212 | setTimeout(function(){ 213 | process(todo.shift()); 214 | if(todo.length>0) { 215 | // 将当前正在执行的函数本身再次使用定时器 216 | setTimeout(arguments.callee, 25); 217 | } else { 218 | callback(items); 219 | } 220 | }, 25); 221 | } 222 | 223 | // 使用 224 | var items=[12,34,65,2,4,76,235,24,9,90]; 225 | function outputValue(value) { 226 | console.log(value); 227 | } 228 | processArray(items, outputValue, function(){ 229 | console.log('Done!'); 230 | }); 231 | ``` 232 | 233 | 优点是任务分配模式比较简单,更有控制权,缺点是不好确定小任务的大小 234 | 235 | 有的小任务可能因为某些原因,会耗费比其他小任务更多的时间,这会造成线程阻塞;而有的小任务可能需要比其他任务少得多的时间,造成资源浪费 236 | 237 | - 根据运行时间分配 238 | 239 | 例如运行一个千万级别的运算总任务,不直接确定分配为多少个子任务,或者分配的颗粒度比较小,在每一个或几个计算完成后,查看此段运算消耗的时间,如果时间小于某个临界值,比如 `10ms`,那么就继续进行运算,否则就暂停,等到下一个轮询再进行进行 240 | 241 | ```js 242 | function timedProcessArray(items, process, callback) { 243 | var todo=items.concat(); 244 | setTimeout(function(){ 245 | // 开始计时 246 | var start = +new Date(); 247 | // 如果单个数据处理时间小于 50ms ,则无需分解任务 248 | do { 249 | process(todo.shift()); 250 | } while (todo.length && (+new Date()-start < 50)); 251 | 252 | if(todo.length > 0) { 253 | setTimeout(arguments.callee, 25); 254 | } else { 255 | callback(items); 256 | } 257 | }); 258 | } 259 | ``` 260 | 261 | 优点是避免了第一种情况出现的问题,缺点是多出了一个时间比较的运算,额外的运算过程也可能影响到性能 262 | 263 | 264 | ## 总结 265 | 266 | 我准备做的 `canvas`游戏似乎需要的制作时间有点长,每天除了上班之外,剩下的时间实在是不多,不知道什么时候能搞完,如果一切顺利,我倒是还想再用一些游戏引擎,例如 `Egret`、`LayaAir`、`Cocos Creator` 将其重制一遍,以熟悉这些游戏引擎的用法,然后到时候写个系列教程出来…… 267 | 268 | 诶,这么看来,似乎是要持久战了啊 269 | 270 | -------------------------------------------------------------------------------- /HTML/HTML-Note-1.md: -------------------------------------------------------------------------------- 1 | ## 防盗链 2 | 3 | 对于一些根据 `referrer`进行防盗链的简单手段,可以通过删除请求 `Header`中的 `Referrer`来解决,一种手段是给页面添加一个额外的 `meta`标签。 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | 设置 `referrer`属性的值为 `never`,即可删除请求 `Header`中的 `Referrer`,不过这种写法的标准目前已经不建议使用了,虽然暂时支持度还是很不错的,除此之外,目前标准的写法是: 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | 这种写法在 `IE/Edge`中可能不被支持,所以实际情况中,最好两个都写上 16 | 17 | 上述写法相当于对文档中的所有链接都取消了 `Referrer`,如果只是想精确地指定取消某一个或几个,则可以使用 `referrerPolicy`: 18 | 19 | ```html 20 | 21 | ``` 22 | 23 | ## base标签 24 | 25 | ` `标签为页面上的所有链接规定默认地址或默认目标 26 | 27 | 通常情况下,浏览器会从当前文档的 `URL` 中提取相应的元素来填写相对 `URL` 中的空白。 28 | 29 | 使用 `` 标签可以改变这一点。浏览器随后将不再使用当前文档的 `URL`,而使用指定的基本 `URL` 来解析所有的相对 `URL`。这其中包括`
` 标签中的 `URL`。 30 | 31 | ```html 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 跳转 40 | 41 | 42 | ``` 43 | 44 | ## title标签 45 | 46 | `` 标签是 `<head>` 标签中唯一要求包含的东西 47 | -------------------------------------------------------------------------------- /JavaScript/JS编码规范.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >摘自 《深入浅出Node.js》 4 | 5 | --- 6 | 7 | ## 代码风格 8 | 9 | - 缩进 10 | 11 | 采用2个空格缩进,而不是tab缩进,空格在编辑器中与字符是等宽的, 12 | 而tab可能因编辑器的设置不同,并且2个空格会让代码看起来更紧凑、明快。 13 | 14 | - 变量声明 15 | 16 | 永远在声明 `var` (或 `let const`)之后再使用变量,避免污染全局上下文, 17 | 每行声明都应该带上 `var` (或`let const`),而不是只有一个 `var (或let const)` 18 | 推荐的代码: 19 | ```js 20 | var assert=require('assert'); 21 | var fork=require('child_process'); 22 | ``` 23 | 24 | 不推荐的代码: 25 | ```js 26 | // 不会产生全局变量,但是不太好看 27 | var assert=require('assert') 28 | ,fork=require('child_process'); 29 | ``` 30 | 31 | - 空格 32 | 33 | 在操作符前后加上空格,比如 `+ - * / % =` 等。 34 | 推荐的代码: 35 | ```js 36 | var foo='bar' + baz; 37 | ``` 38 | 39 | 不推荐的代码: 40 | ```js 41 | var foo='bar'+baz; 42 | ``` 43 | 44 | - 单双引号的使用 45 | 46 | 在 `Node`中使用字符串尽量使用单引号。 47 | 48 | - 大括号的位置: 49 | 50 | 一般情况下,大括号无需另起一行: 51 | 推荐的代码: 52 | 53 | ```js 54 | if (true) { 55 | // some code 56 | } 57 | ``` 58 | 不推荐的代码: 59 | ```js 60 | if (true) 61 | { 62 | // some code 63 | } 64 | ``` 65 | 66 | - 逗号 67 | 68 | 逗号用于变量声明的分割或是元素的分割, 69 | 如果逗号不在行结尾,后面需要一个空格:var foo='hello', bar='world'; 70 | 逗号不要出现在行首。 71 | 不推荐的代码: 72 | ```js 73 | var hello={foo:'hello' 74 | ,bar:world' 75 | }; 76 | ``` 77 | - 分号 78 | 79 | 尽管 `JavaScript`编译器会自动给行尾添加分号,但还是要尽量给表达式的结尾添加分号,因为 `JS`引擎推测分号也是需要时间的,并且可以避免不可预测的语法错误 80 | 81 | >_Note: 目前大多数项目都使用 压缩与混淆 ,会自动给代码加分号,所以现在不用考虑这个事情了 82 | 83 | ## 命名规范 84 | 85 | - 变量命名 86 | 87 | 变量名采用小驼峰式命名(即除了第一个单词的首字母不大写外,其余单词的首字母都大写,词与词之间没有任何符号): 88 | 89 | ```js 90 | var adminUser = {}; 91 | ``` 92 | 93 | - 方法命名 94 | 95 | 与变量一样,采用小驼峰式命名,不过,方法名尽量采用动词或判断行词汇: 96 | ```js 97 | var getUser=function() {}; 98 | var isAdmin = function() {}; 99 | ``` 100 | 101 | - 类命名 102 | 103 | 采用大驼峰式命名,即所有单词的首字母都大写: 104 | ```js 105 | function User() { 106 | } 107 | ``` 108 | 109 | - 常量命名 110 | 111 | 单词的所有字母都大写,单词之间用下划线分割: 112 | ```js 113 | var PINK_COLOR = 'pink'; 114 | ``` 115 | 116 | - 文件命名 117 | 118 | 尽量采用下划线分割单词: 119 | ```js 120 | child_process.js 121 | ``` 122 | 123 | 如果不想将文件暴露给其他用户,约定以下划线开头(只是一种约定,并不会真的在代码层面进行阻止): 124 | 125 | ```js 126 | _private.js 127 | ``` 128 | 129 | ## 比较操作 130 | 131 | 如果是“无容忍”的操作,尽量使用'==='代替'==' 132 | 133 | ## 字面量 134 | 135 | 尽量使用 `{} []`代替`new Object() new Array()`,不要使用`string bool number`对象类型, 136 | 即不要调用`new String new Boollean new Number`。 137 | 138 | ## 作用域 139 | 140 | - 慎用 `with` 141 | 142 | 这可能导致作用域混乱 143 | 144 | - 慎用 eval() 145 | 146 | ## 类与模块 147 | 148 | - 类继承 149 | 150 | 一般写法 151 | ```js 152 | function Socket(options) { 153 | stream.Stearm.call(this); 154 | } 155 | ``` 156 | 推荐使用Nodejs的类继承方式 157 | ```js 158 | util.inherits(Socket,stream.Stearm); 159 | ``` 160 | 161 | - 导出 162 | 163 | 所有供外部调用的方法或变量均需挂载在 `exports` 变量上: 164 | ```js 165 | exports.addUser() { 166 | //some code 167 | } 168 | ``` 169 | 当需要将文件当做一个类导出时,需要通过 `module.exports`: 170 | ```js 171 | module.exports=Class; 172 | ``` 173 | >_Note: `module.exports` 是对 `exports`的一个引用 174 | 175 | ## 更多 176 | [腾讯Web团队编码规范 Code Guide by @AlloyTeam](http://alloyteam.github.io/CodeGuide/) 177 | -------------------------------------------------------------------------------- /JavaScript/JavaScript容易被忽视的零碎知识点.md: -------------------------------------------------------------------------------- 1 | ## node程序 `require` 引入文件时带上文件名 2 | 3 | ```js 4 | const myUtil = require('./myUtil') 5 | ``` 6 | 7 | 对于上述引入文件的方法,如果所引入的文件是 `.json`或者 `.node`类型,则最好带上扩展名,即 `myUtil.json`,原因在于如果不加扩展名,`Node`会依次尝试 `.js` `.node` `.json`的扩展名,如果指定了扩展名,就省去了这一步尝试,在引入文件量较大的情况下,可以提升性能 8 | 9 | ## exports 与 module.exports 10 | 11 | `module.exports` 是 `exports`对象的一个引用,`exports`对象是通过形参的方式引入的,所以不能直接对 `exports`进行赋值,这会改变形参的引用,但是可以对 `module.exports`进行赋值,因为其本来就是一个引用 12 | 13 | ```js 14 | // 错误用法 15 | exports = function() {} 16 | // 正确用法 17 | module.exports = function() {} 18 | ``` 19 | 20 | ## `setTimout`方法的第三个参数 21 | 22 | 绝大部分情况下,大家都只是使用了 `setTimeout`的前两个参数,很少有人知道此方法还有第三个参数 23 | 24 | ```js 25 | /** 26 | * fn: 必需。要调用一个代码串,也可以是一个函数。 27 | * milliseconds: 可选。执行或调用 code/function 需要等待的时间,以毫秒计。默认为 0 28 | * param1, param2, ...: 可选。 传给执行函数的其他参数(IE9 及其更早版本不支持该参数)。 29 | */ 30 | setTimeout(fn, milliseconds, param1, param2, ...) 31 | ``` 32 | 33 | 所以对于一个经典的面试题: 34 | 35 | ```js 36 | for (var i = 1; i <= 5; i++) { 37 | (function(j) { 38 | setTimeout(function timer() { 39 | console.log(j); 40 | }, j * 1000); 41 | })(i); 42 | } 43 | ``` 44 | 45 | 上述代码,就是利用闭包实现输出 `1 2 3 4 5` 46 | 47 | 解决方法除了闭包以及使用 `let`关键字之外,还可以利用 `setTimeout`方法的第三个参数解决: 48 | ```js 49 | for ( var i=1; i<=5; i++) { 50 | setTimeout(function timer(j) { 51 | console.log(j); 52 | }, i*1000, i); 53 | } 54 | ``` 55 | 56 | ## 浏览器渲染与 Event Loop 57 | 58 | 对于以下代码: 59 | ```js 60 | document.body.appendChild(el) 61 | el.style.display = 'none' 62 | ``` 63 | 64 | 这段代码的意思是,给 `body`追加一个元素,并隐藏此元素,有的人可能下意识认为这会导致页面闪动,从而交换这两行代码的书写顺序,即: 65 | ```js 66 | el.style.display = 'none' 67 | document.body.appendChild(el) 68 | ``` 69 | 70 | 实际上,无论是哪种顺序,最后的渲染效果都是一样的,因为这两行代码都是同步代码,浏览器会把这两行代码合并,然后一次性渲染出结果,而不是一步步执行 71 | 72 | 对于下述代码: 73 | ```js 74 | 75 | ``` 76 | 77 | 本意是 让 `box` 元素的位置从 `0` 一下子 移动到 `1000`,然后 动画移动 到 `500`,但是由于浏览器的渲染机制,所以实际情况是从 `0` 动画移动 到 `500`,中间那一步的 `1000`直接被跳过了,想要让上述代码达到预期的效果,那就必须让上述操作不发生在一次渲染中,可以使用 `setTimeout`、`requestAnimationFrame`、强制浏览器分别渲染 78 | 79 | - setTimeout 80 | 81 | 这是最常用的方法了: 82 | ```js 83 | box.style.transform = 'translateX(1000px)' 84 | setTimeout(() => { 85 | box.style.tranition = 'transform 1s ease' 86 | box.style.transform = 'translateX(500px)' 87 | }) 88 | ``` 89 | 90 | - requestAnimationFrame 91 | 92 | `requestAnimationFrame`与 `setTimeout`的实现方式是不一样的, `requestAnimationFrame`可以看做是 `microtask`,而 `setTimeout` 是 `macrotask`,`requestAnimationFrame`将会在浏览器进行渲染之前完成,而 `setTimeout`则是在下一帧执行,所以如果你这么写: 93 | ```js 94 | box.style.transform = 'translateX(1000px)' 95 | requestAnimationFrame(() => { 96 | box.style.tranition = 'transform 1s ease' 97 | box.style.transform = 'translateX(500px)' 98 | }) 99 | ``` 100 | 101 | 其实实现的效果和不加 `requestAnimationFrame`是一样的,但是如果是两个 `requestAnimationFrame`嵌套那就可以了: 102 | ```js 103 | box.style.transform = 'translateX(1000px)' 104 | requestAnimationFrame(() => { 105 | requestAnimationFrame(() => { 106 | box.style.tranition = 'transform 1s ease' 107 | box.style.transform = 'translateX(500px)' 108 | }) 109 | }) 110 | ``` 111 | 第一个 `requestAnimationFrame`在第一帧随同第一行代码一同被浏览器执行并渲染,然后在下一帧才会执行第二个 `requestAnimationFrame`中的内容 112 | 113 | - 强制浏览器分别渲染 114 | 115 | 例如: 116 | ```js 117 | box.style.transform = 'translateX(1000px)' 118 | getComputedStyle(box) // 伪代码,只要获取一下当前的计算样式,就可以打断浏览器合并渲染的过程 119 | box.style.tranition = 'transform 1s ease' 120 | box.style.transform = 'translateX(500px)' 121 | ``` 122 | 123 | ## addEventListener 124 | 125 | 对于以下代码: 126 | ```js 127 | button.addEventListener('click', () => { 128 | Promise.resolve().then(() => console.log('microtask 1')) 129 | console.log('listener 1') 130 | }) 131 | button.addEventListener('click', () => { 132 | Promise.resolve().then(() => console.log('microtask 2')) 133 | console.log('listener 2') 134 | }) 135 | // button.click() 136 | ``` 137 | 138 | 如果执行上述代码,然后在浏览器中用鼠标点击 `button`这个元素,那么将输出: 139 | ```js 140 | listener 1 141 | microtask 1 142 | listener 2 143 | microtask 2 144 | ``` 145 | 146 | 但是如果不是手动触发,而是使用代码触发,例如打开上述代码中的 `button.click()`注释,然后执行代码,将输出: 147 | ```js 148 | listener 1 149 | listener 2 150 | microtask 1 151 | microtask 2 152 | ``` 153 | 154 | 原因如下: 155 | 156 | - 用户直接点击的时候,浏览器先后触发 `2` 个 `listener`。第一个 `listener` 触发完成 (`listener 1`) 之后,队列空了,就先打印了 `microtask 1`。然后再执行下一个 `listener`。重点在于浏览器并不实现知道有几个 `listener`,因此它发现一个执行一个,执行完了再看后面还有没有。 157 | 158 | - 而使用 `button.click()` 时,浏览器的内部实现是把 `2` 个 `listener` 都同步执行。因此 `listener 1 `之后,执行队列还没空,还要继续执行 `listener 2` 之后才行。所以 `listener 2` 会早于 `microtask 1`。重点在于浏览器的内部实现,`click` 方法会先采集有哪些 `listener`,再依次触发。 159 | 160 | ## __defineGetter__ && __defineSetter__ 161 | 162 | 每个对象都有 `__defineGetter__` 和 `__defineSetter__` 方法,这两个方法可以 在对象定义后来给对象追加 `getter`与 `setter`的定义(但是如果已经定义过了,就不能覆盖,会报错) 163 | 164 | ```js 165 | const obj = { a: 2 } 166 | obj.__defineGetter__('a', () => { 167 | return 10 168 | }) 169 | console.log(obj.a) // => 10 170 | ``` 171 | 172 | >该方法是非标准方法,已从标准中删除,不要使用 173 | 174 | ## new 与 构造函数 175 | 176 | 一道有意思的题目,如下: 177 | 178 | ```js 179 | function Foo() { 180 | getName = function () { console.log (1); }; 181 | return this; 182 | } 183 | Foo.getName = function () { console.log (2);}; 184 | Foo.prototype.getName = function () { console.log (3);}; 185 | var getName = function () { console.log (4);}; 186 | function getName() { console.log (5);} 187 | 188 | // 输出 2 189 | // 就是Foo上的静态方法getName() 190 | Foo.getName(); 191 | 192 | // 输出 4 193 | // 变量与函数都有提升能力,但函数提升将在变量提升前面,所以最后两句相当于 194 | // function getName() { console.log (5);} 195 | // var getName 196 | // getName = function () { console.log (4);}; 197 | getName(); 198 | 199 | // 输出 1 200 | // 先执行Foo(),返回this,但此时没有实例对象,所以指向 window,没有调用 new 关键字的 function作用域内的 this都指向 window,执行 Foo()就相当于在全局环境执行了 `getName = function () { console.log (1); };`,所以原先全局环境中的 `getName`函数被重写,this.getName() === window.getName() === getName(),最后的 getName就是重写后的,所以输出 1 201 | Foo().getName(); 202 | 203 | // 输出 1 204 | // 因为前面执行了 Foo().getName(),导致全局 getName被重写,所以这里输出 1,如果没有上一句,则输出 4 205 | getName(); 206 | 207 | // 输出 2 208 | // 下面代码相当于 new (Foo.getName()) 209 | new Foo.getName(); 210 | 211 | // 输出 3 212 | // 下面代码相当于 (new Foo()).getName(),new Foo()创建了一个匿名实例,再对这个匿名实例调用 getName方法,这个实例本身没有 getName,所以向上寻找原型链上的 getName,找到了 Foo.prototype.getName 213 | new Foo().getName(); 214 | ``` 215 | 216 | >Tips: 217 | >如果没加括号,例如 `new Foo` ,这种时候 `new`的优先级是仅次于括号(点和括号是同级别的) 218 | >如果加了括号,例如 `new Foo()`,即使构造函数的参数为空,这种情况下 `new` 就有括号的性质,即 `(new Foo())` 219 | 220 | ## [前端日志上报的新姿势 Beacon](https://github.com/berwin/Blog/issues/27) 221 | 222 | 一般简单的日志上报,都会选择使用 `img`标签: 223 | ```js 224 | (new Image).src = `/haopv.gif?a=xx&b=xxx` 225 | ``` 226 | 227 | 优点如下: 228 | - 无跨域问题 229 | - 日志上报不需要关心服务器响应问题 230 | 231 | 但很多包括 `img`在内的日志上报方案,都存在可能会与主线程抢夺资源的情况,所以需要一种既不会阻塞线程又能即时上报的手段:`Beacon` 232 | 233 | ```js 234 | var data = JSON.stringify({ 235 | name: 'Berwin' 236 | }); 237 | navigator.sendBeacon('/haopv', data) 238 | ``` 239 | 240 | - 信标(`Beacon`)请求优先避免与关键操作和更高优先级的网络请求竞争。 241 | - 信标请求可以有效地合并,以优化移动设备上的能量使用。 242 | - 保证页面卸载之前启动信标请求,并允许运行完成且不会阻塞请求或阻塞处理用户交互事件的任务 243 | 244 | 还有一个需要注意的是,通过信标发送的请求,请求方法均为 `POST`,且不支持修改 245 | 246 | >此 `API`兼容性不太好(Android 6.x,IOS 11.4) 247 | 248 | ## requestAnimationFrame && requestIdleCallBack 249 | 250 | `React 16`实现了新的调度策略(`Fiber`), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 `requestIdleCallback`和 `requestAnimationFrame`两个 `API` 251 | 252 | - requestAnimationFrame 253 | 254 | 会在每一帧确定执行,属于高优先级任务 255 | 256 | - requestIdleCallBack 257 | 258 | 低优先级任务,执行的前提条件是当前浏览器处于空闲状态,利用的是帧的空闲时间,所以如果浏览器一直处于繁忙状态,那么回调将一直无法执行 259 | 260 | ## fetch无法拦截 302 261 | 262 | [当fetch遇到302状态码,会发生什么?](https://zcfy.cc/article/what-will-happen-when-fetch-meets-a-302-status-code) 263 | 264 | `fetch`无法拦截 `302`重定向,导致页面重定向失败,但可以通过修改 `302`为其他可拦截的状态码,来让前端自行重定向: 265 | ```js 266 | fetch('http://www.somecompany.com/someapi') 267 | .then(response => { 268 | if (response.ok) { 269 | // process the data 270 | } else if (response.status == 401) { // something bad happened... 271 | // do something like throwing an Error, or making a jump 272 | } else { // some other status like 400, 403, 500, etc 273 | // take proper actions 274 | } 275 | }) 276 | .catch(error => { 277 | // do some clean-up job 278 | }) 279 | ``` 280 | 281 | ## ES6中的 class 与 普通函数的区别 282 | 283 | - `class` 声明会提升,但不会初始化赋值。`Foo` 进入暂时性死区,类似于 `let、const` 声明变量 284 | 285 | ```js 286 | const bar = new Bar(); // it's ok 287 | function Bar() { 288 | this.bar = 42; 289 | } 290 | 291 | const foo = new Foo(); // ReferenceError: Foo is not defined 292 | class Foo { 293 | constructor() { 294 | this.foo = 42; 295 | } 296 | } 297 | ``` 298 | 299 | - `class` 声明内部会启用严格模式 300 | 301 | ```js 302 | // 引用一个未声明的变量 303 | function Bar() { 304 | baz = 42; // it's ok 305 | } 306 | const bar = new Bar(); 307 | 308 | class Foo { 309 | constructor() { 310 | fol = 42; // ReferenceError: fol is not defined 311 | } 312 | } 313 | const foo = new Foo(); 314 | ``` 315 | 316 | - `class` 的所有方法(包括静态方法和实例方法)都是不可枚举的。 317 | 318 | - `class` 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有`[[construct]]`,不能使用 `new` 来调用。 319 | 320 | - 必须使用 `new` 调用 `class` 321 | 322 | - `class` 内部无法重写类名 323 | 324 | ## 箭头函数 325 | 326 | - 箭头函数没有 原型(`prototype`),所以箭头函数本身没有 `this` 327 | - 箭头函数的 `this`在 **定义的时候**(不是执行的时候)继承自外层第一个普通函数的 `this` 328 | 329 | ```js 330 | function fn1 () { 331 | const fn2 = () => { 332 | // 这里的 this 继承自 fn1的 this 333 | console.log(this) 334 | } 335 | fn2() 336 | } 337 | ``` 338 | - 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的 `this`都会指向全局对象(浏览器环境下是 `window`, `node`下是 `global`) 339 | 340 | ```js 341 | let fn3 = () => { 342 | // 这里的 this 指向 window 343 | console.log(this) 344 | } 345 | ``` 346 | 347 | - 箭头函数本身的 `this`指向无法改变,但可以修改它要继承的普通函数的 `this` 348 | 349 | ```js 350 | const name = 'tom' 351 | const obj = { 352 | name: 'john' 353 | } 354 | function fn1 () { 355 | const fn2 = () => { 356 | console.log(this.name) 357 | } 358 | // 这里想要使用 call 来修改 this指向 obj,但实际上会静默失败,因为箭头函数的 this 无法直接修改 359 | // 所以 这里的 this还是继承自 fn1,而 fn1的 this 指向 window,所以最终输出 tom 360 | fn2.call(obj) 361 | } 362 | 363 | function fn3 () { 364 | const fn2 = () => { 365 | // 这里的 this 继承自 fn1的 this 366 | console.log(this.name) 367 | } 368 | // 这里想要使用 call 来修改 this指向 obj,但实际上会静默失败 369 | // 最终输出 tom 370 | fn2() 371 | } 372 | // 输出 john,直接改变 fn3的 this指向 obj,相当于是间接改变 fn2的 this指向 373 | fn3.call(obj) 374 | ``` 375 | 376 | - 如果箭头函数的 `this`指向全局(即箭头函数外层不存在普通函数),使用 `arguments`会报未声明的错误;如果箭头函数外层存在普通函数,则在箭头函数中使用的 `arguments`实际上是外层普通函数的 `arguments` 377 | 378 | - 使用 `new`调用箭头函数会报错,因为箭头函数没有 `constructor` 379 | - 箭头函数不支持 `new.target` 380 | - 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名 381 | 382 | ```js 383 | function func1 (a, a) { 384 | console.log(a, arguments) // 2 [1,2] 385 | } 386 | 387 | const func2 = (a, a) => { 388 | console.log(a) // 报错:在此上下文中不允许重复参数名称 389 | } 390 | func1(1, 2) 391 | func2(1, 2) 392 | ``` 393 | -------------------------------------------------------------------------------- /JavaScript/Promise的一种实现.md: -------------------------------------------------------------------------------- 1 | 来源 https://juejin.im/post/5b83cb5ae51d4538cc3ec354 2 | 3 | ```js 4 | // 判断变量否为function 5 | const isFunction = variable => typeof variable === 'function' 6 | // 定义Promise的三种状态常量 7 | const PENDING = 'PENDING' 8 | const FULFILLED = 'FULFILLED' 9 | const REJECTED = 'REJECTED' 10 | 11 | class MyPromise { 12 | constructor (handle) { 13 | if (!isFunction(handle)) { 14 | throw new Error('MyPromise must accept a function as a parameter') 15 | } 16 | // 添加状态 17 | this._status = PENDING 18 | // 添加状态 19 | this._value = undefined 20 | // 添加成功回调函数队列 21 | this._fulfilledQueues = [] 22 | // 添加失败回调函数队列 23 | this._rejectedQueues = [] 24 | // 执行handle 25 | try { 26 | handle(this._resolve.bind(this), this._reject.bind(this)) 27 | } catch (err) { 28 | this._reject(err) 29 | } 30 | } 31 | // 添加resovle时执行的函数 32 | _resolve (val) { 33 | const run = () => { 34 | if (this._status !== PENDING) return 35 | // 依次执行成功队列中的函数,并清空队列 36 | const runFulfilled = (value) => { 37 | let cb; 38 | while (cb = this._fulfilledQueues.shift()) { 39 | cb(value) 40 | } 41 | } 42 | // 依次执行失败队列中的函数,并清空队列 43 | const runRejected = (error) => { 44 | let cb; 45 | while (cb = this._rejectedQueues.shift()) { 46 | cb(error) 47 | } 48 | } 49 | /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后, 50 | 当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态 51 | */ 52 | if (val instanceof MyPromise) { 53 | val.then(value => { 54 | this._value = value 55 | this._status = FULFILLED 56 | runFulfilled(value) 57 | }, err => { 58 | this._value = err 59 | this._status = REJECTED 60 | runRejected(err) 61 | }) 62 | } else { 63 | this._value = val 64 | this._status = FULFILLED 65 | runFulfilled(val) 66 | } 67 | } 68 | // 为了支持同步的Promise,这里采用异步调用 69 | setTimeout(run, 0) 70 | } 71 | // 添加reject时执行的函数 72 | _reject (err) { 73 | if (this._status !== PENDING) return 74 | // 依次执行失败队列中的函数,并清空队列 75 | const run = () => { 76 | this._status = REJECTED 77 | this._value = err 78 | let cb; 79 | while (cb = this._rejectedQueues.shift()) { 80 | cb(err) 81 | } 82 | } 83 | // 为了支持同步的Promise,这里采用异步调用 84 | setTimeout(run, 0) 85 | } 86 | // 添加then方法 87 | then (onFulfilled, onRejected) { 88 | const { _value, _status } = this 89 | // 返回一个新的Promise对象 90 | return new MyPromise((onFulfilledNext, onRejectedNext) => { 91 | // 封装一个成功时执行的函数 92 | let fulfilled = value => { 93 | try { 94 | if (!isFunction(onFulfilled)) { 95 | onFulfilledNext(value) 96 | } else { 97 | let res = onFulfilled(value); 98 | if (res instanceof MyPromise) { 99 | // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调 100 | res.then(onFulfilledNext, onRejectedNext) 101 | } else { 102 | //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数 103 | onFulfilledNext(res) 104 | } 105 | } 106 | } catch (err) { 107 | // 如果函数执行出错,新的Promise对象的状态为失败 108 | onRejectedNext(err) 109 | } 110 | } 111 | // 封装一个失败时执行的函数 112 | let rejected = error => { 113 | try { 114 | if (!isFunction(onRejected)) { 115 | onRejectedNext(error) 116 | } else { 117 | let res = onRejected(error); 118 | if (res instanceof MyPromise) { 119 | // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调 120 | res.then(onFulfilledNext, onRejectedNext) 121 | } else { 122 | //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数 123 | onFulfilledNext(res) 124 | } 125 | } 126 | } catch (err) { 127 | // 如果函数执行出错,新的Promise对象的状态为失败 128 | onRejectedNext(err) 129 | } 130 | } 131 | switch (_status) { 132 | // 当状态为pending时,将then方法回调函数加入执行队列等待执行 133 | case PENDING: 134 | this._fulfilledQueues.push(fulfilled) 135 | this._rejectedQueues.push(rejected) 136 | break 137 | // 当状态已经改变时,立即执行对应的回调函数 138 | case FULFILLED: 139 | fulfilled(_value) 140 | break 141 | case REJECTED: 142 | rejected(_value) 143 | break 144 | } 145 | }) 146 | } 147 | // 添加catch方法 148 | catch (onRejected) { 149 | return this.then(undefined, onRejected) 150 | } 151 | // 添加静态resolve方法 152 | static resolve (value) { 153 | // 如果参数是MyPromise实例,直接返回这个实例 154 | if (value instanceof MyPromise) return value 155 | return new MyPromise(resolve => resolve(value)) 156 | } 157 | // 添加静态reject方法 158 | static reject (value) { 159 | return new MyPromise((resolve ,reject) => reject(value)) 160 | } 161 | // 添加静态all方法 162 | static all (list) { 163 | return new MyPromise((resolve, reject) => { 164 | /** 165 | * 返回值的集合 166 | */ 167 | let values = [] 168 | let count = 0 169 | for (let [i, p] of list.entries()) { 170 | // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve 171 | this.resolve(p).then(res => { 172 | values[i] = res 173 | count++ 174 | // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled 175 | if (count === list.length) resolve(values) 176 | }, err => { 177 | // 有一个被rejected时返回的MyPromise状态就变成rejected 178 | reject(err) 179 | }) 180 | } 181 | }) 182 | } 183 | // 添加静态race方法 184 | static race (list) { 185 | return new MyPromise((resolve, reject) => { 186 | for (let p of list) { 187 | // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变 188 | this.resolve(p).then(res => { 189 | resolve(res) 190 | }, err => { 191 | reject(err) 192 | }) 193 | } 194 | }) 195 | } 196 | finally (cb) { 197 | return this.then( 198 | value => MyPromise.resolve(cb()).then(() => value), 199 | reason => MyPromise.resolve(cb()).then(() => { throw reason }) 200 | ); 201 | } 202 | } 203 | ``` -------------------------------------------------------------------------------- /JavaScript/js四则运算精度问题.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | // 解决 js 四则运算可能出现的精度问题 3 | // 没直接引入一个库的原因是,库体积太大了,现有项目基本上只涉及四则运算,引入库的性价比太低 4 | 5 | /** 6 | * 获取小数位的位数 7 | * @param num 数字 8 | */ 9 | function decimalLen (num: number): number { 10 | let len: number = 0 11 | try { 12 | len = num.toString().split('.')[1].length 13 | } catch (e) {/**/} 14 | return len 15 | } 16 | 17 | function toBigInt (num: number): number { 18 | return Number(num.toString().replace('.', '')) 19 | } 20 | 21 | /** 22 | * js 四则运算 加法 23 | * @param num1 第一个数字 24 | * @param num2 第二个数字 25 | */ 26 | export const numberAdd = (num1: number, num2: number): number => { 27 | let m = Math.pow(10, Math.max(decimalLen(num1), decimalLen(num2))) 28 | // 由于存在类似于 4.94 * 100 === 494.00000000000006 的情况,所以这里使用 toFixed 再截取一下 29 | return (Number((num1 * m).toFixed()) + Number((num2 * m).toFixed())) / m 30 | } 31 | /** 32 | * js 四则运算 减法 33 | * @param num1 第一个数字 34 | * @param num2 第二个数字 35 | */ 36 | export const numberSub = (num1: number, num2: number): number => { 37 | return numberAdd(num1, -num2) 38 | } 39 | /** 40 | * js 四则运算 乘法 41 | * @param num1 第一个数字 42 | * @param num2 第二个数字 43 | */ 44 | export const numberMul = (num1: number, num2: number): number => { 45 | return toBigInt(num1) * toBigInt(num2) / Math.pow(10, decimalLen(num1) + decimalLen(num2)) 46 | } 47 | /** 48 | * js 四则运算 除法 49 | * @param num1 第一个数字 50 | * @param num2 第二个数字 51 | */ 52 | export const numberDivision = (num1: number, num2: number): number => { 53 | return toBigInt(num1) / toBigInt(num2) * Math.pow(10, decimalLen(num2) - decimalLen(num1)) 54 | } 55 | ``` -------------------------------------------------------------------------------- /JavaScript/leetcode/21-40.md: -------------------------------------------------------------------------------- 1 | ## 21. 合并两个有序链表 2 | 3 | ### 迭代 4 | 5 | ```js 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val) { 9 | * this.val = val; 10 | * this.next = null; 11 | * } 12 | */ 13 | /** 14 | * @param {ListNode} l1 15 | * @param {ListNode} l2 16 | * @return {ListNode} 17 | */ 18 | var mergeTwoLists = function(l1, l2) { 19 | let dummyHead = new ListNode() 20 | let ret = dummyHead 21 | while (l1 && l2) { 22 | if (l1.val <= l2.val) { 23 | dummyHead.next = l1 24 | l1 = l1.next 25 | } else { 26 | dummyHead.next = l2 27 | l2 = l2.next 28 | } 29 | dummyHead = dummyHead.next 30 | } 31 | if (l1) { 32 | dummyHead.next = l1 33 | } 34 | if (l2) { 35 | dummyHead.next = l2 36 | } 37 | return ret.next 38 | }; 39 | ``` 40 | 41 | ### 递归 42 | 43 | ```js 44 | var mergeTwoLists = function(l1, l2) { 45 | if (!l1) return l2 46 | if (!l2) return l1 47 | if (l1.val <= l2.val) { 48 | l1.next = mergeTwoLists(l1.next, l2) 49 | return l1 50 | } else { 51 | l2.next = mergeTwoLists(l2.next, l1) 52 | return l2 53 | } 54 | } 55 | ``` 56 | 57 | ## 22. 括号生成 58 | 59 | 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。 60 | 61 | 例如,给出 n = 3,生成结果为: 62 | ```js 63 | [ 64 | "((()))", 65 | "(()())", 66 | "(())()", 67 | "()(())", 68 | "()()()" 69 | ] 70 | ``` 71 | 72 | ### 回溯(递归) 73 | 74 | ```js 75 | /** 76 | * @param {number} n 77 | * @return {string[]} 78 | */ 79 | var generateParenthesis = function(n) { 80 | let result = [] 81 | function trackback(s = '', left = 0, right = 0) { 82 | console.log('123:', s, left, right) 83 | if (s.length === 2 * n) { 84 | result.push(s) 85 | } 86 | if (left < n) { 87 | trackback(s + '(', left + 1, right) 88 | } 89 | if (right < left) { 90 | trackback(s + ')', left, right + 1) 91 | } 92 | } 93 | trackback() 94 | return result 95 | }; 96 | ``` 97 | 98 | ## 23. 合并K个排序链表 (D) 99 | 100 | 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 101 | 102 | 示例: 103 | 104 | ``` 105 | 输入: 106 | [ 107 | 1->4->5, 108 | 1->3->4, 109 | 2->6 110 | ] 111 | 输出: 1->1->2->3->4->4->5->6 112 | ``` 113 | 114 | ### 分治 115 | 116 | ```js 117 | /** 118 | * Definition for singly-linked list. 119 | * function ListNode(val) { 120 | * this.val = val; 121 | * this.next = null; 122 | * } 123 | */ 124 | /** 125 | * @param {ListNode[]} lists 126 | * @return {ListNode} 127 | */ 128 | var mergeTwoLists = function(l1, l2) { 129 | if (!l1) return l2 130 | if (!l2) return l1 131 | if (l1.val <= l2.val) { 132 | l1.next = mergeTwoLists(l1.next, l2) 133 | return l1 134 | } else { 135 | l2.next = mergeTwoLists(l2.next, l1) 136 | return l2 137 | } 138 | } 139 | /** 140 | * @param {ListNode[]} lists 141 | * @return {ListNode} 142 | */ 143 | var mergeKLists = function(lists) { 144 | const len = lists.length 145 | if (len === 0) return lists 146 | let interval = 1 147 | while (interval < len) { 148 | for (let i = 0; i < len - interval; i += interval * 2) { 149 | lists[i] = mergeTwoLists(lists[i], lists[i + interval]) 150 | } 151 | interval *= 2 152 | } 153 | return lists[0] 154 | } 155 | ``` 156 | 157 | ## 24. 两两交换链表中的节点 (M) 158 | 159 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 160 | 161 | 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 162 | 163 | 示例: 164 | ``` 165 | 给定 1->2->3->4, 你应该返回 2->1->4->3. 166 | ``` 167 | 168 | 参考: 169 | [画解算法:24. 两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/hua-jie-suan-fa-24-liang-liang-jiao-huan-lian-biao/) 170 | 171 | ### 递归 172 | 173 | ```js 174 | /** 175 | * Definition for singly-linked list. 176 | * function ListNode(val) { 177 | * this.val = val; 178 | * this.next = null; 179 | * } 180 | */ 181 | /** 182 | * @param {ListNode} head 183 | * @return {ListNode} 184 | */ 185 | var swapPairs = function(head) { 186 | if (!head || !head.next) return head 187 | const next = head.next 188 | head.next = swapPairs(next.next) 189 | next.next = head 190 | return next 191 | } 192 | ``` 193 | 194 | ### 非递归 195 | 196 | ```js 197 | var swapPairs = function(head) { 198 | const pre = new ListNode() 199 | pre.next = head 200 | let temp = pre 201 | let start, end 202 | while (temp.next && temp.next.next) { 203 | start = temp.next 204 | end = temp.next.next 205 | temp.next = end 206 | start.next = end.next 207 | end.next = start 208 | temp = start 209 | } 210 | return pre.next 211 | }; 212 | ``` -------------------------------------------------------------------------------- /JavaScript/元编程.md: -------------------------------------------------------------------------------- 1 | # 元编程 2 | 3 | 关于元编程到底是什么意思,可以参见 [怎么理解元编程?](https://www.zhihu.com/question/23856985) 4 | 5 | 我的理解是,可以改变程序原有的默认行为,例如数组的内置方法、对象的原型链方法、字符串属性等,就叫元编程 6 | 7 | 在 `JavaScript`中,`eval`、`new Function`、`Proxy`、`Reflect`、`Symbols` 都是可用于进行元编程的特性 8 | 9 | ## eval 10 | 11 | ```js 12 | eval('function a(){console.log(2)};a()') 13 | // 执行输出 => 2 14 | ``` 15 | 16 | `eval`执行了一段字符串,将字符串当成代码执行,却让这段字符串拥有了函数般的能力,所以可以算是元编程 17 | 18 | ## new Function 19 | 20 | ```js 21 | // args 是传入函数的参数,functionBody是 字符串形式的函数体 22 | new Function ([arg1[, arg2[, ...argN]],] functionBody) 23 | ``` 24 | 25 | ```js 26 | const fn = new Fcuntion(a, b, 'return a + b') 27 | fn(2, 3) // ==> 5 28 | ``` 29 | 30 | `new Function` 将自己的字符串参数当成代码来执行,拼装成了一个函数 31 | 32 | ## Proxy 33 | 34 | `ES6` 新增特性 35 | 36 | `Proxy` 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种 **元编程**(`meta programming`),即对编程语言进行编程 37 | 38 | ```js 39 | const obj = new Proxy({}, { 40 | get(target, key, receiver) { 41 | return target[key] 42 | }, 43 | set(target, key, value, receiver) { 44 | target[key] = value % 2 === 0 ? 2 * value : value 45 | } 46 | }) 47 | 48 | obj.age = 4 49 | obj.gender = 1 50 | console.log(obj.age) // => 8 51 | console.log(obj.gender) // => 1 52 | ``` 53 | 54 | `Proxy` 共支持 `13`种拦截操作: 55 | 56 | - `get(target, propKey, receiver)`:拦截对象属性的读取,比如 `proxy.foo和proxy['foo']` 57 | - `set(target, propKey, value, receiver)`:拦截对象属性的设置,比如 `proxy.foo = v` 或 `proxy['foo'] = v`,返回一个布尔值 58 | - `has(target, propKey)`:拦截 `propKey in proxy`的操作,返回一个布尔值 59 | - `deleteProperty(target, propKey)`:拦截 `delete proxy[propKey]`的操作,返回一个布尔值 60 | - `ownKeys(target)`:拦截 `Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in`循环,返回一个数组该方法返回目标对象所有自身的属性的属性名,而 `Object.keys()`的返回结果仅包括目标对象自身的可遍历属性 61 | - `getOwnPropertyDescriptor(target, propKey)`:拦截 `Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象 62 | - `defineProperty(target, propKey, propDesc)`:拦截 `Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值 63 | - `preventExtensions(target)`:拦截 `Object.preventExtensions(proxy)`,返回一个布尔值 64 | - `getPrototypeOf(target)`:拦截 `Object.getPrototypeOf(proxy)`,返回一个对象 65 | - `isExtensible(target)`:拦截 `Object.isExtensible(proxy)`,返回一个布尔值 66 | - `setPrototypeOf(target, proto)`:拦截 `Object.setPrototypeOf(proxy, proto)`,返回一个布尔值如果目标对象是函数,那么还有两种额外操作可以拦截 67 | - `apply(target, object, args)`:拦截 `Proxy `实例作为函数调用的操作,比如 `proxy(...args)、proxy.call(object, ...args)、proxy.apply(...) 68 | construct(target, args)`:拦截 `Proxy` 实例作为构造函数调用的操作,比如 `new proxy(...args)` 69 | 70 | 关于 `Proxy`更多参见 [proxy](http://es6.ruanyifeng.com/#docs/proxy) 71 | 72 | ## Reflect 73 | 74 | `ES6` 新增特性 75 | 76 | `Reflect`的特性有如下几点: 77 | 78 | - 将 `Object`对象的一些明显属于语言内部的方法(比如`Object.defineProperty`),放到 `Reflect`对象上 79 | - 修改某些 `Object`方法的返回结果,让其变得更合理 80 | - 让 `Object`操作都变成函数行为。某些 `Object`操作是命令式 81 | - `Reflect`对象的方法与 `Proxy`对象的方法一一对应,只要是 `Proxy`对象的方法,就能在 `Reflect`对象上找到对应的方法。这就让`Proxy`对象可以方便地调用对应的 `Reflect`方法,完成默认行为 82 | 83 | `Reflect` 存在 `13`个静态方法,与 `Proxy`的 `13`个拦截操作一一对应 84 | 85 | ## Symbol 86 | 87 | `ES6` 新增特性,一种新的原始数据类型 88 | 89 | ```js 90 | const obj = { 91 | toString() { 92 | return 'abc' 93 | } 94 | } 95 | 96 | const sym = Symbol(obj) 97 | console.log(sym) // => Symbol(abc) 98 | ``` 99 | 100 | `Symbol`改变了 `obj`默认的 `toString`方法,可以认为是一种元编程 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 QuietNight 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DayNote 3 | 4 | 读书笔记,记录学习过程遇到的任何知识点 5 | 6 | 此仓库会频繁更新,推荐 `star` 或 `watch`以即时得到更新通知 7 | 8 | --- 9 | 10 | 目录结构 11 | 12 | - [CSS](/CSS) 13 | - [CSS-Note-1.md](/CSS/CSS-Note-1.md) 14 | - [CSS-Note-2.md](/CSS/CSS-Note-2.md) 15 | - [性能优化.md](/CSS/性能优化.md) 16 | - [HTML](/HTML) 17 | - [Canvas性能优化.md](/HTML/Canvas性能优化.md) 18 | - [HTML-Note-1.md](/HTML/HTML-Note-1.md) 19 | - [JavaScript](/JavaScript) 20 | - [JavaScript容易被忽视的零碎知识点.md](/JavaScript/JavaScript容易被忽视的零碎知识点.md) 21 | - [JS-算法-note.md](/JavaScript/JS-算法-note.md) 22 | - [js四则运算精度问题.md](/JavaScript/js四则运算精度问题.md) 23 | - [JS编码规范.md](/JavaScript/JS编码规范.md) 24 | - [Promise的一种实现.md](/JavaScript/Promise的一种实现.md) 25 | - [元编程.md](/JavaScript/元编程.md) 26 | - [手写实现bind、call等原生api.md](/JavaScript/手写实现bind、call等原生api.md) 27 | - [leetcode](/JavaScript/leetcode) 28 | - [1-100.md](/JavaScript/leetcode/1-100.md) 29 | - [React](/React) 30 | - [Next.js-README.md](/React/Next.js-README.md) 31 | - [React16.x相关.md](/React/React16.x相关.md) 32 | - [somecase.md](/React/somecase.md) 33 | - [性能优化.md](/React/性能优化.md) 34 | - [自定义实现简单路由.md](/React/自定义实现简单路由.md) 35 | - [Solution](/Solution) 36 | - [JavaScript反调试技巧.md](/Solution/JavaScript反调试技巧.md) 37 | - [实现Date类的继承.md](/Solution/实现Date类的继承.md) 38 | - [实现JavaScript的私有变量.md](/Solution/实现JavaScript的私有变量.md) 39 | - [编写可维护的现代化前端项目.md](/Solution/编写可维护的现代化前端项目.md) 40 | - [保持原样上传而不旋转图片的方向](/Solution/保持原样上传而不旋转图片的方向) 41 | - [flipPic.js](/Solution/保持原样上传而不旋转图片的方向/flipPic.js) 42 | - [网页端调起分享功能](/Solution/网页端调起分享功能) 43 | - [browserShareTo.js](/Solution/网页端调起分享功能/browserShareTo.js) 44 | - [Vue](/Vue) 45 | - [Vue-Note-1.md](/Vue/Vue-Note-1.md) 46 | - [Vue-Router-Note.md](/Vue/Vue-Router-Note.md) 47 | - [异步组件.md](/Vue/异步组件.md) 48 | - [性能优化.md](/Vue/性能优化.md) 49 | - [新特性.md](/Vue/新特性.md) 50 | - [solution](/Vue/solution) 51 | - [输入手机号时334加入以及删除空格的组件.md](/Vue/solution/输入手机号时334加入以及删除空格的组件.md) 52 | - [使用Vue.extend构造工具组件toast](/Vue/solution/使用Vue.extend构造工具组件toast) 53 | - [app.vue](/Vue/solution/使用Vue.extend构造工具组件toast/app.vue) 54 | - [toast](/Vue/solution/使用Vue.extend构造工具组件toast/toast) 55 | - [index.js](/Vue/solution/使用Vue.extend构造工具组件toast/toast/index.js) 56 | - [index.vue](/Vue/solution/使用Vue.extend构造工具组件toast/toast/index.vue) 57 | - [无渲染Vue组件](/Vue/无渲染Vue组件) 58 | - [project](/Vue/无渲染Vue组件/project) 59 | - [index.vue](/Vue/无渲染Vue组件/project/index.vue) 60 | - [toggle.js](/Vue/无渲染Vue组件/project/toggle.js) 61 | - [Webpack](/Webpack) 62 | - [somecase.md](/Webpack/somecase.md) 63 | - [实用代码段](/实用代码段) 64 | - [代码段(1).md](/实用代码段/代码段(1).md) 65 | - [代码段(2).md](/实用代码段/代码段(2).md) 66 | - [代码段(3).md](/实用代码段/代码段(3).md) 67 | - [实用工具](/实用工具) 68 | - [github上实用、轻量级、无依赖的插件和库(1).md](/实用工具/github上实用、轻量级、无依赖的插件和库(1).md) 69 | - [github上实用、轻量级、无依赖的插件和库(2).md](/实用工具/github上实用、轻量级、无依赖的插件和库(2).md) 70 | - [常用的工具、网站.md](/实用工具/常用的工具、网站.md) 71 | - [常用技巧](/常用技巧) 72 | - [前端技巧(1).md](/常用技巧/前端技巧(1).md) 73 | - [前端技巧(2).md](/常用技巧/前端技巧(2).md) 74 | - [笔试题&面试题](/笔试题&面试题) 75 | - [前端面试笔试题(1).md](/笔试题&面试题/前端面试笔试题(1).md) 76 | - [前端面试笔试题(2).md](/笔试题&面试题/前端面试笔试题(2).md) 77 | - [读书笔记](/读书笔记) 78 | - [CSS揭秘.md](/读书笔记/CSS揭秘.md) 79 | - [HTML5与CSS3权威指南(上).md](/读书笔记/HTML5与CSS3权威指南(上).md) 80 | - [HTML5与CSS3权威指南(下).md](/读书笔记/HTML5与CSS3权威指南(下).md) 81 | - [Javascript权威指南与高级编程(1).md](/读书笔记/Javascript权威指南与高级编程(1).md) 82 | - [Javascript权威指南与高级编程(2).md](/读书笔记/Javascript权威指南与高级编程(2).md) 83 | - [Javascript权威指南与高级编程(3).md](/读书笔记/Javascript权威指南与高级编程(3).md) 84 | - [你不知道的JavaScript(1).md](/读书笔记/你不知道的JavaScript(1).md) 85 | - [你不知道的JavaScript(2).md](/读书笔记/你不知道的JavaScript(2).md) 86 | - [锋利的JQuery.md](/读书笔记/锋利的JQuery.md) 87 | - [高性能JavaScript.md](/读书笔记/高性能JavaScript.md) 88 | -------------------------------------------------------------------------------- /React/React16.x相关.md: -------------------------------------------------------------------------------- 1 | >更详细的 `React 16.x`的进化步骤参见 [精读《React16 新特性》](https://zhuanlan.zhihu.com/p/52016989?utm_source=75weekly&utm_medium=75weekly) 2 | 3 | ## React 16.x新的生命周期函数和 `deprecate`掉的生命周期函数 4 | 5 | `deprecate`掉了三个生命周期函数,都是 `render`之前的: 6 | - `componentWillReceiveProps` 7 | - `componentWillMount` 8 | - `componentWillUpdate` 9 | 10 | 对应地为这三个函数增加了带有 `UNSAFE` 的替代方法, `UNSAFE_componentWillReceiveProps()`、`UNSAFE_componentWillMount()`、`UNSAFE_componentWillUpdate()` 11 | 12 | 在 `16.4`及之前的版本中可以继续使用 不带 `UNSAFE`标识的上述三个生命周期函数,但会给出警告,但是 `17`版本之后将删除,只能使用 `UNSAFE`前缀的了 13 | 14 | ### getDerivedStateFromProps 15 | 16 | 上述被 `deprecate`的函数都可以用新增的生命周期函数 `getDerivedStateFromProps`来代替实现 17 | 无论是 `Mounting`还是`Updating`,也无论是因为什么引起的 `Updating`,此函数都会被调用 18 | 19 | `getDerivedStateFromProps`: 静态纯函数,函数体内无法访问 `this`,主要作用是用于运算而非其他类似于请求数据之类的动作 20 | ```js 21 | static getDerivedStateFromProps(nextProps, prevState) { 22 | //根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState 23 | } 24 | ``` 25 | 26 | >Note: 废弃三个生命周期函数并增加此函数的目的在于,强制开发者在 `render`之前只做无副作用的操作,而且能做的操作局限在根据 `props`和`state`决定新的 `state` 27 | 28 | ### getSnapshotBeforeUpdate 29 | 30 | 此函数会在 `render`之后执行,而执行之时 `DOM`元素还没有被更新,给了一个机会去获取 `DOM`信息,此函数的返回值 `snapshot`会作为 `componentDidUpdate`的第三个参数传入: 31 | ```js 32 | getSnapshotBeforeUpdate(prevProps, prevState) { 33 | return 'foo' 34 | } 35 | 36 | componentDidUpdate(prevProps, prevState, snapshot) { 37 | console.log('#enter componentDidUpdate snapshot = ', snapshot) 38 | } 39 | ``` 40 | 41 | >Note: 在不知道是否需要使用此生命周期 `API`的情况下,那就不要用 42 | 43 | ## Portal 44 | 45 | >`render`到一个组件里面去,实际改变的是网页上另一处的 `DOM`结构,减少组件杂糅,避免不可预知的组件样式等之间的影响 46 | 47 | `Dialog.jsx`组件: 48 | ```jsx 49 | import React from 'react' 50 | import { createPortal } from 'react-dom' 51 | 52 | export default class Dialog extends React.Component { 53 | constructor(props) { 54 | super(props) 55 | const doc = window.document 56 | this.node = doc.createElement('div') 57 | doc.body.appendChild(this.node) 58 | } 59 | render() { 60 | return createPortal( 61 | <div className="dialog"> 62 | {this.props.children} 63 | </div>, 64 | this.node 65 | ) 66 | } 67 | componentWillUnmount() { 68 | document.body.removeChild(this.node) 69 | } 70 | } 71 | ``` 72 | 73 | `App.jsx`引用: 74 | ```jsx 75 | class App extends Component { 76 | render() { 77 | return ( 78 | <div className="App"> 79 | <Dialog> 80 | <p className="lp">121dwed</p> 81 | </Dialog> 82 | </div> 83 | ) 84 | } 85 | } 86 | ``` 87 | 上述,尽管 `App.jsx`是在 `.App`这个元素内引用的 `Dialog`组件,但是实际上最后渲染出来的 `<Dialog />`组件的位置是在 `<body>`的下面 88 | 89 | ## `Error Boundary` 90 | 91 | ```jsx 92 | class ErrorBoundary extends React.Component { 93 | constructor(props) { 94 | super(props) 95 | this.state = { hasError: false } 96 | } 97 | 98 | componentDidCatch(error, info) { 99 | this.setState({ hasError: true }) 100 | 101 | // 将异常信息上报给服务器 102 | logErrorToMyService(error, info); 103 | } 104 | 105 | render() { 106 | if (this.state.hasError) { 107 | return '出错了' 108 | } 109 | return this.props.children 110 | } 111 | } 112 | ``` 113 | 114 | 将此组件作为最顶级的组件,所有页面组件都作为其子组件,当任何层级的子组件发生错误时,都会被此组件接到 115 | 116 | ## Context API 117 | 118 | >解决 `props`多级嵌套传递的问题,只要父组件 `Provider`对应的 `props`,则其下任何层级的子组件都可通过 `Consumer`获得此 `props` 119 | 120 | ```jsx 121 | import React, { Component } from 'react' 122 | 123 | const defaultTheme = { background: 'pink' } 124 | const fooTheme = { background: '#58a' } 125 | 126 | const { Provider, Consumer } = React.createContext(defaultTheme) 127 | 128 | const Banner = () => ( 129 | <Consumer> 130 | { context => <div className="banner" style={context}> world</div> } 131 | </Consumer> 132 | ) 133 | 134 | const Content = () => <div className="content">hello<Banner /></div> 135 | 136 | export default class App extends Component { 137 | state = { theme: defaultTheme } 138 | render() { 139 | return ( 140 | <Provider value={this.state.theme}> 141 | <Content /> 142 | <button onClick={() => { 143 | this.setState(state => ({ 144 | theme: state.theme === defaultTheme ? fooTheme : defaultTheme 145 | })) 146 | }}>Click</button> 147 | </Provider> 148 | ) 149 | } 150 | } 151 | ``` 152 | 153 | ## createRef API 154 | 155 | 除了沿用之前创建 `ref`的两种方法: `string ref` 和 `callback ref`之外,另增 `createRef`的方法 156 | 此方法创建的 `ref`,不会有使用 `string ref`方法创建的副作用,更多关于 `string ref`的副作用详见 [React ref 的前世今生](https://zhuanlan.zhihu.com/p/40462264) 157 | 158 | ```jsx 159 | export default class CreateRefApi extends Component { 160 | inputRef = React.createRef() 161 | btnClick = () => this.inputRef.current.focus() 162 | render() { 163 | return ( 164 | <div className="box"> 165 | <input type="text" ref={this.inputRef} /> 166 | <button onClick={this.btnClick}>click</button> 167 | </div> 168 | ) 169 | } 170 | } 171 | ``` 172 | 173 | ## forwardRef API 174 | 175 | 通过 `ref`穿透组件,直接控制组件内的指定元素 176 | 177 | ```jsx 178 | const ChildInput = React.forwardRef((props, ref) => ( 179 | <div className="child-box"> 180 | <input type="text" ref={ref}/> 181 | <p>other content</p> 182 | </div> 183 | )) 184 | 185 | export default class ForwardRefApi extends React.Component { 186 | inputRef = React.createRef() 187 | btnClick = () => { 188 | this.inputRef.current.value = 'hello world' 189 | } 190 | render() { 191 | return ( 192 | <div className="box"> 193 | <button onClick={this.btnClick}>Click</button> 194 | <ChildInput ref={this.inputRef} /> 195 | </div> 196 | ) 197 | } 198 | } 199 | ``` 200 | 201 | ## Strict Mode 202 | 203 | 此组件不会渲染任何的 `visible UI`,用于对其所有子元素进行检测,只在开发环境起作用,不会对生产版本产生任何影响 204 | 检测内容如下: 205 | 206 | - 不安全的生命周期函数 207 | - 使用`string ref API`创建 `ref` 208 | - 不期望的副作用 209 | - 老式(v16.x之前)的 `Context API` 210 | 211 | ## lazy 212 | 213 | 异步操作,例如异步加载组件,异步获取数据 214 | ```js 215 | import React, {lazy, Suspense} from 'react'; 216 | const OtherComponent = lazy(() => import('./OtherComponent')); 217 | 218 | function MyComponent() { 219 | return ( 220 | <Suspense fallback={<div>Loading...</div>}> 221 | <OtherComponent /> 222 | </Suspense> 223 | ); 224 | } 225 | ``` 226 | 227 | ## React.memo 228 | 229 | 用于给无状态组件实现类似 `PureComponent`的浅比较功能,即浅比较 `props`是否有变化,如果没有变化,就不重新渲染当前组件 230 | 231 | ```js 232 | const MyComponent = React.memo(function MyComponent(props) { 233 | /* only rerenders if props change */ 234 | }) 235 | ``` 236 | 237 | ## static contextType 238 | 239 | `Context API`的一种渐进方式,功能相同,可以不用关心,一般直接使用 `Context API`即可 240 | 241 | ## static getDerivedStateFromError() 242 | 243 | 在发布 `Error Boundaries`的时候,`React`提供了一个新的生命周期方法 `componentDidCatch`,在捕获到错误的时候会触发,你可以在里面修改 `state`以显示错误提醒的 `UI`,或者将错误信息发送给服务端进行 `log`用于后期分析。但是这里有个问题,就是在捕获到错误的瞬间,`React`会在这次渲染周期中将这个组件渲染为 `null`,这就有可能导致他的父组件设置他上面的`ref`获得 `null`而导致一些问题,所以现在提供了这个方法。 244 | 这个方法跟 `getDerivedStateFromProps`类似,唯一的区别是他只有在出现错误的时候才触发,他相对于 `componentDidCatch`的优势是在当前的渲染周期中就可以修改 `state`,以在当前渲染就可以出现错误的 `UI`,而不需要一个 `null`的中间态。 245 | 而这个方法的出现,也意味着以后出现错误的时候,修改 `state`应该放在这里去做,而后续收集错误信息之类的放到 `componentDidCatch`里面 246 | 247 | 248 | ## Hooks 249 | 250 | ### useState 251 | 252 | 作用: 253 | 254 | - 方便组件的复用 255 | - 摒弃 `class`,摒弃 `this`, 让组件易于写成 `function`的形式, 256 | 257 | ```js 258 | import { useState } from 'react' 259 | 260 | function Example() { 261 | const [count, setCount] = useState(0) 262 | 263 | return ( 264 | <div> 265 | <p>You clicked {count} times</p> 266 | <button onClick={() => setCount(count + 1)}> 267 | Click me 268 | </button> 269 | </div> 270 | ) 271 | } 272 | ``` 273 | 274 | `Tips`: 275 | 276 | - 数组的结构赋值开销较大,可以考虑写成对象结构或直接赋值 277 | 278 | ```js 279 | const [count, setCount] = useState(0) 280 | // 改成 281 | const _useState = useState(0) 282 | let count = _useState[0] 283 | let setCount = _useState[1] 284 | ``` 285 | - `useState`可以多次调用,用于构造多个 `state`,接收的值不仅限于原始类型(`string/number/boolean`),同样包括对象和数组,需要注意的是,之前我们的 `this.setState`做的是合并状态后返回一个新状态,而 `useState`是直接替换老状态后返回新状态,最后,`react`也给我们提供了一个 [useReducer](https://react.docschina.org/docs/hooks-reference.html#usereducer) 的 `hook`,如果你更喜欢 `redux`式的状态管理方案的话 286 | 287 | - `react`是怎么保证这组件内多个 `useState`找到它对应的 `state`呢 288 | 289 | `react`是根据 `useState`出现的顺序来定的,所以 `react`规定我们必须把 `hooks`写在函数的最外层,不能写在 `if...else`等条件语句当中,来确保 `hooks`的执行顺序一致 -------------------------------------------------------------------------------- /React/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/React/img/1.png -------------------------------------------------------------------------------- /React/somecase.md: -------------------------------------------------------------------------------- 1 | # 一些有关 React 的知识点 2 | 3 | ## `setSate`具有确定的顺序 4 | 5 | >来源 [React 是否保持 state 更新的顺序?](https://mp.weixin.qq.com/s?__biz=MzI0NTAyNjE0NQ==&mid=2675577690&idx=1&sn=15fab0f2843a3c01866545a20efa0962&chksm=f3da6f54c4ade64220e8f5d7a2b05f980a43e47cba48c5f44d533b48f147832f8392b415b5c0&mpshare=1&scene=23&srcid=0206ZZS6lkbMjlxelHxLeHTe#rd) 6 | 7 | >**无论是同一个组件,还是不同的组件,`setSatte`都具有确定的顺序。** 8 | >状态始终是按照特定的顺序更新的。无论你是否看到介于两个状态之间的一个中间状态,无论你是否在批处理内。 9 | 10 | 在 `React` 事件处理程序中(可以理解为由 `React`引发的事件处理,例如 `onClick` `onChange`),不论 `setState()` 调用了多少次,也不论 `setState()` 被多少个组件调用,它们在事件结束时只会生成一次重新渲染,也就是在事件结束时,所有的 `setState`调用都会被合并到一起。 11 | 12 | 更新总是按照它们发生的顺序进行浅合并(`shallowly merge`)。 13 | 14 | 15 | *但是*,截至目前,也就是 `React 16` 和更早版本中,**React 事件处理程序** 之外还没有默认的批处理 16 | 17 | 例如,下述的 `handleClick`函数中的代码,就是在 **React 事件处理程序**之内: 18 | ```jsx 19 | <button onClick={this.handleClick}>click</button> 20 | 21 | handleClick = () => { 22 | this.setState({ a: 1 }); // 不立即重新渲染 23 | this.setState({ b: 2 }); // 不立即重新渲染 24 | console.log('be clicked'); 25 | } 26 | // 当所有的 React 事件处理程序 全部结束后,会一次性改变状态,组件重新渲染一次 27 | ``` 28 | 29 | 下述的 `getData`函数中的代码,则是在 **React 事件处理程序**之外: 30 | ```js 31 | getData() { 32 | promise.then(data => { 33 | this.setState({ a: data }); // 使用 { a: data }重新渲染一次 34 | this.setState({ b: 2 }); // 使用 { b: 2 }重新渲染一次 35 | }); 36 | } 37 | ``` 38 | 39 | `React`未来的版本(或许是从 `React 17`开始),可能会默认支持批量更新所有的更新,在此之前,`React`也提供了一个 `API`用于强制批量处理: 40 | ```js 41 | promise.then(() => { 42 | // 强制批量处理 43 | ReactDOM.unstable_batchedUpdates(() => { 44 | this.setState({ a: true }); // 不立即重新渲染 45 | this.setState({ b: false }); // 不立即重新渲染 46 | }); 47 | // 当我们退出 unstable_batchedUpdates函数后,重新渲染一次 48 | }); 49 | ``` 50 | 51 | ## 不要在 `componentWillMount`中获取数据 52 | 53 | 在 `componentWillMount`里发起`AJAX`,不管多快得到结果也赶不上首次`render`,而且 `componentWillMount`可能会被调用多次,所以类似的 `IO`操作最好放到 `componentDidMount` 54 | 55 | ## `react-router 4.x`:切换路由后,页面仍然停留在上一个页面的位置 56 | 57 | >更多 `react-router`使用技巧可见 [React Router 4.x 开发,这些雷区我们都帮你踩过了](https://jdc.jd.com/archives/212552) 58 | 59 | 由 A 页面跳转到 B 页面,B 页面停留在 A 页面的位置,没有返回到顶部。 60 | 61 | 3.x以及早期版本中,可以使用滚动恢复的开箱即用功能,但是在 4.x中路由切换并不会回复滚动位置。 62 | 63 | 解决方法:**使用 withRouter**: 64 | >`withRouter(MyComponent)` 65 | >`withRouter` 可以访问历史对象的属性和最近的 `<Route`> 匹配项,当路由的属性值 `{ match, location, history }` 改变时, 66 | >`withRouter` 都会重新渲染。该组件可以携带组件的路由信息,避免组件之间一层层传递。 67 | 68 | 所以可以利用上述特点,使用 `withRouter` 封装出一个 `ScrollToTop` 组件。 69 | 这里就用到了 `withRouter` 携带路由信息的特性,通过对比`props` 中 `location` 的变化,实现页面的滚动。 70 | 71 | ```jsx 72 | import React, { Component } from 'react'; 73 | import { Route, withRouter } from 'react-router-dom'; 74 | class ScrollToTop extends Component { 75 | componentDidUpdate(prevProps) { 76 | if (this.props.location !== prevProps.location) { 77 | window.scrollTo(0, 0); 78 | } 79 | } 80 | render() { 81 | return this.props.children; 82 | } 83 | } 84 | 85 | export default withRouter(ScrollToTop); 86 | ``` 87 | 88 | 在定义路由出引用该组件,例如: 89 | ```jsx 90 | ReactDOM.render(( 91 | <BrowserRouter> 92 | <ScrollToTop> 93 | <div className="container"> 94 | <Route path={routePaths.INDEX} exact component={Index} /> 95 | <Route path={routePaths.CARD} component={Card} /> 96 | </div> 97 | </ScrollToTop> 98 | </BrowserRouter> 99 | ), 100 | document.getElementById('app') 101 | ); 102 | ``` 103 | 104 | ## [Render Props](https://react.docschina.org/docs/render-props.html) 105 | 106 | >指一种在 `React` 组件之间使用一个值为函数的 `prop` 在 `React` 组件间共享代码的简单技术,主要用于简化 `react` 107 | 组件的复用方式 108 | 109 | ```jsx 110 | class DataProvider extends React.Component { 111 | constructor(props) { 112 | super(props); 113 | this.state = { target: 'Zac' }; 114 | } 115 | 116 | render() { 117 | return ( 118 | <div> 119 | {this.props.render(this.state)} 120 | </div> 121 | ) 122 | } 123 | } 124 | 125 | // 使用 render Props 126 | <DataProvider render={data => ( 127 | <Cat target={data.target} /> 128 | )} /> 129 | ``` 130 | 虽然这个模式叫 `Render Props`,但不是说非用一个叫 `render`的 `props`不可,习惯上大家更常写成下面这种: 131 | ```jsx 132 | <DataProvider> 133 | {data => ( 134 | <Cat target={data.target} /> 135 | )} 136 | </DataProvider> 137 | ``` 138 | 139 | ## $$typeof 140 | 141 | `React` 在解析 `jsx`为 `vdom`的时候,会在 `vdom`的对象中额外添加一个 `$$typeof`的属性: 142 | ```js 143 | const element = { 144 | // This tag allows us to uniquely identify this as a React Element 145 | $$typeof: REACT_ELEMENT_TYPE, 146 | 147 | // Built-in properties that belong on the element 148 | type: type, 149 | key: key, 150 | ref: ref, 151 | props: props, 152 | 153 | // Record the component responsible for creating this element. 154 | _owner: owner, 155 | }; 156 | ``` 157 | `$$typeof` 是一个 `Symbol`类型的值,主要用于防止 `XSS`攻击,因为此类型的值无法序列化,所以如果对 -------------------------------------------------------------------------------- /React/性能优化.md: -------------------------------------------------------------------------------- 1 | ## 首屏优化 2 | 3 | 一般来说,`SPA`应用首屏渲染的时间等于 `html + css + js`加载并解析的时间,在此之前,页面将会表现为空包状态 4 | 所以可以在加载 `css + js`之前,在 `html`模板中加入 `loading`,可以自己写一个简单的 `loading`,也可以选择使用现有的插件,例如 [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin)。 5 | 6 | ## 动态 `polyfill` 7 | 8 | 一般 `es2015+`的网站都需要引入 `polyfill`,`polyfill`文件往往体积较大,可能包含大量无需用到的代码,并且有些版本较新的浏览器是不需要 `polyfill`的,特别是移动端。 9 | 10 | 对此,可以根据当前浏览器的支持程度动态引入所需的 `polyfill`,[polyfill.io](https://polyfill.io/v2/docs/)就是一个支持动态引入 `polyfill`的网站,**保证只有在需要时,才会引入 polyfill** 11 | 12 | 具体的使用方法非常简单,只需要外链一个 js: 13 | ```js 14 | <script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script> 15 | ``` 16 | 17 | 当然这样是加载全部的 polyfill,实际上你可能并不需要这么多,比如你只需要 Map/Set 的话: 18 | ```js 19 | <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Map,Set"></script> 20 | ``` 21 | 22 | 上述 `js`文件的内容会依照当前发起请求浏览器种类和版本的不同而不同,例如,对于最新版的 `Chrome`,文件内容几乎为空,而如果使用一个老旧的浏览器访问,则内容量将会大很多。 23 | 24 | ## Code Splitting 25 | 26 | 参考链接: 27 | - [react-router官网-code spliting](https://reacttraining.com/react-router/web/guides/code-splitting) 28 | - [React-router-v4 - Webpack 实现按需加载(code-splitting)](https://blog.csdn.net/mjzhang1993/article/details/79094594) 29 | - [React-router4 使用bundle-loader实现按需加载(code-splitting)](https://blog.csdn.net/dknightl/article/details/79261867) 30 | 31 | ## 动态 import 32 | 33 | [React Loadable](https://github.com/jamiebuilds/react-loadable) 是一个专门用于动态 `import` 的 `React` 高阶组件,你可以把任何组件改写为支持动态 `import` 的形式。 34 | 35 | ```js 36 | import Loadable from 'react-loadable'; 37 | import Loading from './loading-component'; 38 | 39 | const LoadableComponent = Loadable({ 40 | loader: () => import('./my-component'), 41 | loading: Loading, 42 | }); 43 | 44 | export default class App extends React.Component { 45 | render() { 46 | return <LoadableComponent />; 47 | } 48 | } 49 | ``` 50 | 51 | 上面的代码在首次加载时,会先展示一个 `<LoadableComponent />`,然后动态加载 `my-component` 的代码,组件代码加载完毕之后,便会替换掉 `<LoadableComponent />`。 52 | 53 | ## 编译到 `ES2015+` ,提升代码运行效率 54 | 55 | 对于 `es6+`代码构建的项目,一般会将其编译到 `es5`运行,但是目前支持 `es6+`的浏览器占据的份额越来越多,完全没必要让这部分的浏览器加载编译后的体积更大、运行速度更慢的 `es5`代码,**我们需要做的,就是把代码编译到 ES2015+,然后为少数使用老旧浏览器的用户保留一个 ES5 标准的备胎即可** 56 | 57 | 具体的解决方法就是 `<script type="module">` 标签。 58 | 59 | 支持 `<script type="module">` 的浏览器,必然支持下面的特性: 60 | ``` 61 | async/await 62 | Promise 63 | Class 64 | 箭头函数、Map/Set、fetch 等等... 65 | 而不支持 <script type="module"> 的老旧浏览器,会因为无法识别这个标签,而不去加载 ES2015+ 的代码。 66 | 另外老旧的浏览器同样无法识别 nomodule 熟悉,会自动忽略它,从而加载 ES5 标准的代码。 67 | ``` 68 | 69 | ## LazyLoad 70 | 71 | - 首先加载一张低像素的模糊图片,然后等真实图片加载完毕之后,再替换掉。 72 | - 使用一张默认图片占位,等图片进入可视区域内后,再开始加载真实图片,存在一些成熟的组件: 73 | * [react-lazyload](https://github.com/jasonslyvia/react-lazyload) 74 | * [react-lazy-load](https://github.com/loktar00/react-lazy-load) 75 | 76 | 实际上目前几乎所有 lazyload 组件都不外乎以下两种原理: 77 | - 监听 `window` 对象或者父级对象的 `scrol`l 事件,触发 `load`; 78 | - 获取元素的可见性,例如可以使用 [Intersection Observer API](http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html) 79 | 80 | ## placeholder 81 | 82 | 我们在加载文本、图片的时候,经常出现“闪屏”的情况,比如图片或者文字还没有加载完毕,此时页面上对应的位置还是完全空着的,然后加载完毕,内容会突然撑开页面,导致“闪屏”的出现,造成不好的体验。 83 | 84 | 为了避免这种突然撑开的情况,我们要做的就是提前设置占位元素,也就是 placeholder: 85 | ![img](img/1.png) 86 | 87 | 已经有一些现成的第三方组件可以用了: 88 | - [react-placeholder](https://github.com/buildo/react-placeholder) 89 | - [react-hold](https://github.com/buildo/react-placeholder) 90 | 91 | -------------------------------------------------------------------------------- /React/自定义实现简单路由.md: -------------------------------------------------------------------------------- 1 | - `hash`路由 2 | 3 | ```html 4 | <body> 5 | <ul> 6 | <li><a href="#/">pink</a></li> 7 | <li><a href="#/yellowgreen">yellowgreen</a></li> 8 | <li><a href="#/indianred">indianred</a></li> 9 | </ul> 10 | <button class="btn1">back</button> 11 | <button class="btn2">forward</button> 12 | </body> 13 | ``` 14 | 15 | ```js 16 | class Routers { 17 | constructor() { 18 | // 储存hash与callback键值对 19 | this.routes = {}; 20 | // 当前hash 21 | this.currentUrl = ''; 22 | // 记录出现过的hash 23 | this.history = []; 24 | // 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash 25 | this.currentIndex = this.history.length - 1; 26 | this.refresh = this.refresh.bind(this); 27 | this.back = this.back.bind(this); 28 | this.forward = this.forward.bind(this); 29 | // 默认不是后退操作 30 | this.isBack = false; 31 | // 默认不是前进操作 32 | this.isForward = false; 33 | // IE9+ 34 | window.addEventListener('load', this.refresh, false); 35 | // IE8+,浏览器导航的前进和后退按钮可触发此事件 36 | window.addEventListener('hashchange', this.refresh, false); 37 | } 38 | route(path, callback) { 39 | this.routes[path] = callback || function() {}; 40 | } 41 | refresh() { 42 | this.currentUrl = location.hash.slice(1) || '/'; 43 | console.log(this.currentUrl); 44 | if (!this.isBack && !this.isForward) { 45 | this.history.push(this.currentUrl) 46 | this.currentIndex++ 47 | } else { 48 | this.isBack = false; 49 | this.isForward = false; 50 | } 51 | this.routes[this.currentUrl](); 52 | } 53 | // 后退功能 54 | back() { 55 | // 后退操作设置为true 56 | this.isBack = true; 57 | if (this.currentIndex !== 0) { 58 | this.currentIndex = this.currentIndex - 1; 59 | } 60 | location.hash = `#${this.history[this.currentIndex]}`; 61 | } 62 | forward() { 63 | // 前进操作设置为true 64 | this.isForward = true; 65 | if (this.currentIndex !== this.history.length - 1) { 66 | this.currentIndex = this.currentIndex + 1; 67 | } 68 | location.hash = `#${this.history[this.currentIndex]}`; 69 | } 70 | } 71 | // 使用 72 | window.Router = new Routers(); 73 | const content = document.querySelector('body'); 74 | const btn1 = document.querySelector('.btn1'); 75 | const btn2 = document.querySelector('.btn2'); 76 | function changeBgColor(color) { 77 | content.style.backgroundColor = color; 78 | } 79 | 80 | Router.route('/', function() { 81 | changeBgColor('pink'); 82 | }); 83 | Router.route('/yellowgreen', function() { 84 | changeBgColor('yellowgreen'); 85 | }); 86 | Router.route('/indianred', function() { 87 | changeBgColor('indianred'); 88 | }); 89 | 90 | btn1.addEventListener('click', Router.back, false); 91 | btn2.addEventListener('click', Router.forward, false); 92 | ``` 93 | 94 | - `browser`路由 95 | 96 | 借助 `HTML5 History API` 97 | 98 | ```html 99 | <body> 100 | <ul> 101 | <li><a href="/">pink</a></li> 102 | <li><a href="/yellowgreen">yellowgreen</a></li> 103 | <li><a href="/indianred">indianred</a></li> 104 | </ul> 105 | <button class="btn1">back</button> 106 | <button class="btn2">forward</button> 107 | </body> 108 | ``` 109 | 110 | ```js 111 | class Routers { 112 | constructor() { 113 | this.routes = {}; 114 | this._bindPopState(); 115 | } 116 | init(path) { 117 | history.replaceState({path: path}, null, path); 118 | this.routes[path] && this.routes[path](); 119 | } 120 | 121 | route(path, callback) { 122 | this.routes[path] = callback || function() {}; 123 | } 124 | back() { 125 | history.back(); 126 | } 127 | forward() { 128 | history.forward(); 129 | } 130 | go(path) { 131 | history.pushState({path: path}, null, path); 132 | this.routes[path] && this.routes[path](); 133 | } 134 | _bindPopState() { 135 | // IE10+ 136 | window.addEventListener('popstate', e => { 137 | const path = e.state && e.state.path; 138 | this.routes[path] && this.routes[path](); 139 | }); 140 | } 141 | } 142 | // 使用 143 | window.Router = new Routers(); 144 | Router.init(location.pathname); 145 | const content = document.querySelector('body'); 146 | const ul = document.querySelector('ul'); 147 | const btn1 = document.querySelector('.btn1'); 148 | const btn2 = document.querySelector('.btn2'); 149 | function changeBgColor(color) { 150 | content.style.backgroundColor = color; 151 | } 152 | 153 | Router.route('/', function() { 154 | changeBgColor('pink'); 155 | }); 156 | Router.route('/yellowgreen', function() { 157 | changeBgColor('yellowgreen'); 158 | }); 159 | Router.route('/indianred', function() { 160 | changeBgColor('indianred'); 161 | }); 162 | ul.addEventListener('click', e => { 163 | if (e.target.tagName === 'A') { 164 | e.preventDefault(); 165 | Router.go(e.target.getAttribute('href')); 166 | } 167 | }); 168 | 169 | btn1.addEventListener('click', Router.back, false); 170 | btn2.addEventListener('click', Router.forward, false); 171 | ``` -------------------------------------------------------------------------------- /Solution/JavaScript反调试技巧.md: -------------------------------------------------------------------------------- 1 | ## 函数重定义 2 | 3 | 最基本、最常用的代码反调试技术,一般代码调试很可能会用到 `console.log`来输出调试结果, 4 | 如果对此函数进行重定义,则可以修改调试输出的结果 5 | 6 | ```js 7 | window['console']['log'] = () =>{} 8 | // 执行了一个空函数,所以无任何输出 9 | console.log('Great') 10 | ``` 11 | 12 | ## 断点干扰 13 | 14 | `debugger`函数会在控制台被打开的时候运行,控制台关闭后不会产生任何作用, 15 | 所以可以在代码中设置一个能够干扰调试的 `debugger`方法,例如无限循环 `debugger` 16 | 17 | ```js 18 | setTimeout(function(){while (true) {eval("debugger") 19 | ``` 20 | 21 | ## 时间差异 22 | 23 | 当脚本在DevTools等工具环境下执行时,运行速度会非常慢(时间久),所以我们就可以根据运行时间来判断脚本当前是否正在被调试。比如说,我们可以通过测量代码中两个设置点之间的运行时间,然后用这个值作为参考,如果运行时间超过这个值,说明脚本当前在调试器中运行。 24 | 25 | ```js 26 | set Interval(function(){ 27 | var startTime = performance.now(), check,diff; 28 | for (check = 0; check < 1000; check++){ 29 | console.log(check); 30 | console.clear(); 31 | } 32 | diff = performance.now() - startTime; 33 | if (diff > 200){ 34 | alert("Debugger detected!"); 35 | } 36 | },500); 37 | ``` 38 | 39 | ## DevTools检测(Chrome) 40 | 41 | 这项技术利用的是div元素中的id属性,当div元素被发送至控制台(例如 `console.log(div)`)时,浏览器会自动尝试获取其中的元素 `id`。如果代码在调用了`console.log`之后又调用了 `getter`方法,说明控制台当前正在运行。 42 | 43 | ```js 44 | let div = document.createElement('div'); 45 | let loop = setInterval(() => { 46 | console.log(div); 47 | console.clear(); 48 | }); 49 | Object.defineProperty(div,"id", {get: () => { 50 | clearInterval(loop); 51 | alert("Dev Tools detected!"); 52 | }}); 53 | ``` -------------------------------------------------------------------------------- /Solution/保持原样上传而不旋转图片的方向/flipPic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 上传图片,保持原样上传而不旋转图片的方向 3 | * 用法示例: 4 | * if (file.type.indexOf('image/') !== -1) { 5 | * flipPic(file); 6 | * } 7 | * 其中 file是从<input type="file" />元素上读取的file 8 | */ 9 | 10 | // 需要使用此库来获取图片方向 https://github.com/exif-js/exif-js/ 11 | import EXIF from 'exif-js'; 12 | 13 | const getImgOrientation = (img) => new Promise((resolve) => { 14 | EXIF.getData(img, function call() { 15 | const orientation = EXIF.getTag(this, 'Orientation'); 16 | resolve(orientation); 17 | }); 18 | }); 19 | const rotateImg = (orientation, width, height) => { 20 | let x; 21 | let y; 22 | let deg; 23 | let isRotate; 24 | switch (orientation) { 25 | case 8: 26 | deg = -Math.PI / 2; 27 | x = -width; 28 | y = 0; 29 | isRotate = true; 30 | break; 31 | case 6: 32 | deg = Math.PI / 2; 33 | x = 0; 34 | y = -height; 35 | isRotate = true; 36 | break; 37 | case 3: 38 | deg = Math.PI; 39 | x = -width; 40 | y = -height; 41 | isRotate = false; 42 | break; 43 | default: 44 | deg = 0; 45 | x = 0; 46 | y = 0; 47 | isRotate = false; 48 | } 49 | return { 50 | x, y, deg, isRotate, 51 | }; 52 | }; 53 | const isExecutable = () => { 54 | if (!window.File 55 | || !window.Blob 56 | || !window.URL) { 57 | return false; 58 | } 59 | return true; 60 | }; 61 | const canvasGetBlob = (canvas, file, q) => new Promise((resolve) => { 62 | if (!HTMLCanvasElement.prototype.toBlob) { 63 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 64 | value(callback, type, quality) { 65 | const base64Data = this.toDataURL(type, quality); 66 | const blob = getBlobFromBase64Data(base64Data, type || 'image/png'); 67 | blob.url = base64Data; 68 | callback(blob, true); 69 | }, 70 | }); 71 | } 72 | canvas.toBlob((blob, flag) => { 73 | if (!blob.url && !flag) { 74 | blob = new window.File([blob], file.name, { type: file.type }); 75 | } 76 | resolve(blob); 77 | }, file.type || 'image/png', q); 78 | }); 79 | export default (file) => new Promise((resolve) => { 80 | if (!isExecutable()) { 81 | resolve(file); 82 | } 83 | const canvas = document.createElement('canvas'); 84 | const context = canvas.getContext('2d'); 85 | const img = new Image(); 86 | const url = URL.createObjectURL(file); 87 | img.onload = async () => { 88 | const orientation = await getImgOrientation(img); 89 | const originWidth = img.width; 90 | const originHeight = img.height; 91 | const { x, y, deg, isRotate } = rotateImg(orientation, originWidth, originHeight); 92 | if (isRotate) { 93 | canvas.height = originWidth; 94 | canvas.width = originHeight; 95 | } else { 96 | canvas.height = originHeight; 97 | canvas.width = originWidth; 98 | } 99 | context.rotate(deg); 100 | context.drawImage(img, 0, 0, originWidth, originHeight, x, y, originWidth, originHeight); 101 | const blob = await canvasGetBlob(canvas, file, 0.6); 102 | blob.uid = file.uid; 103 | URL.revokeObjectURL(url); 104 | resolve(blob); 105 | }; 106 | img.src = url; 107 | }); 108 | -------------------------------------------------------------------------------- /Solution/实现Date类的继承.md: -------------------------------------------------------------------------------- 1 | >要调用 `Date`上方法的实例对象必须通过 `Date`构造出来,否则不允许调用 `Date`的方法 2 | 3 | 由于上述限制,所以想要继承 `Date`类,就无法使用经典的**寄生组合式继承法**进行继承。 4 | 5 | - ES5实现1 6 | 7 | ```js 8 | // 需要考虑polyfill情况 9 | Object.setPrototypeOf = Object.setPrototypeOf || 10 | function(obj, proto) { 11 | obj.__proto__ = proto; 12 | return obj; 13 | }; 14 | 15 | /** 16 | * 用了点技巧的继承,实际上返回的是Date对象 17 | */ 18 | function MyDate() { 19 | // 下面这句话主要是为了能够拿到传入参数,换成下面两种写法也是可以的 20 | // new(Date.bind(([Date].concat(Array.prototype.slice.call(arguments))).join(','))) 21 | // new(Date.bind(Date, ...arguments)) 22 | var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); 23 | 24 | // 更改原型指向,否则无法调用MyDate原型上的方法 25 | // ES6方案中,这里就是[[prototype]]这个隐式原型对象,在没有标准以前就是__proto__ 26 | Object.setPrototypeOf(dateInst, MyDate.prototype); 27 | 28 | dateInst.abc = 1; 29 | 30 | return dateInst; 31 | } 32 | 33 | // 原型重新指回Date,否则根本无法算是继承 34 | Object.setPrototypeOf(MyDate.prototype, Date.prototype); 35 | 36 | MyDate.prototype.getTest = function getTest() { 37 | return this.getTime(); 38 | }; 39 | 40 | var date = new MyDate(); 41 | var date2 = new Date(); 42 | 43 | console.log(date instanceof Date); // => true 44 | console.log(date2 instanceof Date); // => true 45 | console.log(date.abc) // => 1 46 | console.log(date2.abc) // => undefined 47 | console.log(date.getTest()); // => 正常输出调用 getTime() 48 | console.log(date2.getTest()); // => Uncaught TypeError: date2.getTest is not a function 49 | ``` 50 | 51 | - ES5实现2 52 | 53 | ```js 54 | function MyDate() {} 55 | MyDate.prototype.getTest = function() { 56 | return this.getTime() 57 | } 58 | 59 | let d = new Date() 60 | Object.setPrototypeOf(d, MyDate.prototype) 61 | Object.setPrototypeOf(MyDate.prototype, Date.prototype) 62 | 63 | console.log(d.getTime()) // 1537532987673 64 | ``` 65 | 66 | - ES5实现3 67 | 68 | ```js 69 | Date.prototype.clone = function() { 70 | return new Date(this.valueof()) 71 | } 72 | var date = new Date('2010') 73 | var cloneDate = date.clone() 74 | console.log(cloneData) // => Fri Jan 01 2010 08:00:00 GMT+0800 (中国标准时间) 75 | ``` 76 | 77 | - ES6实现 78 | 79 | ```js 80 | class MyDate extends Date { 81 | constructor() { 82 | super(); 83 | this.abc = 1; 84 | } 85 | getTest() { 86 | return this.getTime(); 87 | } 88 | } 89 | 90 | let date = new MyDate(); 91 | 92 | // 正常输出,譬如1515638988725 93 | console.log(date.getTest()); 94 | ``` 95 | 96 | 可以看到,使用 `ES6`的 `class`关键字,配合 `super`方法可以很容易实现继承,不过上述写法只限于 `ES6`,如果使用 `Babel`等工具对上述代码进行转换,同样不可行 -------------------------------------------------------------------------------- /Solution/实现JavaScript的私有变量.md: -------------------------------------------------------------------------------- 1 | `JavaScript`世界中至今为止没有私有变量的概念,不过在编写程序的时候可能需要这个东西,这就需要我们自己来实现了。 2 | 3 | _源自 [Private Variables in JavaScript](https://marcusnoble.co.uk/2018-02-04-private-variables-in-javascript/)、 [JavaScript 中的私有变量](https://github.com/xitu/gold-miner/blob/master/TODO/private-variables-in-javascript.md)_ 4 | 5 | ## 命名约定 6 | 7 | `JS`中实现私有变量惯常的方式,就是加个下划线,但这只防君子不防小人,只是从道德约束层面而非从技术上实现。 8 | 9 | ```js 10 | // 下述变量被认为是私有变量 11 | const _privateName = 'xiaoming' 12 | ``` 13 | 14 | ## WeakMap 15 | 16 | `WeakMap`是 `ES6`中新添加的数据结构,类似于 `Object`,能够维护一个 **键值对**,更多关于可见 [WeakMap](http://es6.ruanyifeng.com/#docs/set-map) 17 | 18 | **这里主要是利用 `WeakMap`能够将一个 Object类型的数据作为键的特点** 19 | 20 | >想要稍有一些限制性,您可以使用 WeakMap 来存储所有私有值。这仍然不会阻止对数据的访问,但它将私有值与用户可操作的对象分开。对于这种技术,我们将 WeakMap 的关键字设置为私有属性所属对象的实例,并且我们使用一个函数(我们称之为 internal )来创建或返回一个对象,所有的属性将被存储在其中。这种技术的好处是在遍历属性时或者在执行 JSON.stringify 时不会展示出实例的私有属性,但它依赖于一个放在类外面的可以访问和操作的 WeakMap 变量。 21 | 22 | ```js 23 | const map = new WeakMap() 24 | const internal = obj => { 25 | if (!map.has(obj)) { 26 | map.set(obj, {}) 27 | } 28 | return map.get(obj) 29 | } 30 | 31 | class Shape { 32 | constructor(width, height) { 33 | internal(this).width = width 34 | internal(this).height = height 35 | } 36 | get area() { 37 | return internal(this).width * internal(this).height 38 | } 39 | } 40 | 41 | const square = new Shape(2, 5) 42 | console.log(square.area) // => 10 43 | console.log(map.get(square)) // => { height: 2, width: 5 } 44 | ``` 45 | 46 | ## Symbol 47 | 48 | 主要是利用 `Symbol`不会被类似于 `for...in`、`for...of`、`Object.keys()`等方法访问到的特性 49 | 50 | >Symbol 的实现方式与 WeakMap 十分相近。在这里,我们可以使用 Symbol 作为 key 的方式创建实例上的属性。这可以防止该属性在遍历或使用 JSON.stringify 时可见。不过这种技术需要为每个私有属性创建一个 Symbol。如果您在类外可以访问该 Symbol,那你还是可以拿到这个私有属性。 51 | 52 | ```js 53 | const widthSymbol = Symbol('width') 54 | const heightSymbol = Symbol('height') 55 | 56 | class Shape { 57 | constructor(width, height) { 58 | this[widthSymbol] = width 59 | this[heightSymbol] = height 60 | } 61 | get area() { 62 | return this[widthSymbol] * this[heightSymbol] 63 | } 64 | } 65 | 66 | const square = new Shape(10, 10); 67 | console.log(square.area); // 100 68 | console.log(square.widthSymbol); // undefined 69 | console.log(square[widthSymbol]); // 10,这种方法还是可以访问到的 70 | ``` 71 | 72 | ## 闭包 73 | 74 | >到目前为止所显示的所有技术仍然允许从类外访问私有属性,闭包为我们提供了一种解决方法。如果您愿意,可以将闭包与 WeakMap 或 Symbol 一起使用,但这种方法也可以与标准 JavaScript 对象一起使用。闭包背后的想法是将数据封装在调用时创建的函数作用域内,但是从内部返回函数的结果,从而使这一作用域无法从外部访问。 75 | 76 | ```js 77 | function Shape() { 78 | // 私有变量集 79 | const this$ = {}; 80 | 81 | class Shape { 82 | constructor(width, height) { 83 | this$.width = width; 84 | this$.height = height; 85 | // 手动指定 area,解决 将square.area 视为未定义的问题 86 | Object.defineProperty(this, 'area', { 87 | get: function() { 88 | return this$.width * this$.height; 89 | } 90 | }); 91 | } 92 | } 93 | // 将 this 设置为 new Shape(...arguments)的原型 94 | // 解决 square instanceof Squrea 返回 false 95 | return Object.setPrototypeOf(new Shape(...arguments), this); 96 | } 97 | const square = new Shape(10, 10); 98 | console.log(square.area); // 100 99 | console.log(square.width); // undefined 100 | console.log(square instanceof Shape); // true 101 | ``` 102 | 103 | ## Proxy 104 | 105 | >Proxy 是 JavaScript 中一项美妙的新功能,它将允许你有效地将对象包装在名为 Proxy 的对象中,并拦截与该对象的所有交互。我们将使用 Proxy 并遵照上面的 命名约定 来创建私有变量,但可以让这些私有变量在类外部访问受限。 106 | 107 | >Proxy 可以拦截许多不同类型的交互,但我们要关注的是 get 和 set,Proxy 允许我们分别拦截对一个属性的读取和写入操作。创建 Proxy 时,你将提供两个参数,第一个是您打算包裹的实例,第二个是您定义的希望拦截不同方法的 “处理器” 对象。 108 | 109 | ```js 110 | class Shape { 111 | constructor(width, height, otherParams) { 112 | this._width = width; 113 | this._height = height; 114 | this.otherParams = otherParams; 115 | } 116 | get area() { 117 | return this._width * this._height; 118 | } 119 | } 120 | 121 | const handler = { 122 | // 禁止访问带 `_`前缀的变量 123 | get: function(target, key) { 124 | if (key[0] === '_') { 125 | throw new Error('Attempt to access private property'); 126 | } else if (key === 'toJSON') { 127 | // 禁止 JSON.stringify对私有属性的格式化 128 | const obj = {}; 129 | for (const key in target) { 130 | if (key[0] !== '_') { 131 | obj[key] = target[key]; 132 | } 133 | } 134 | return () => obj; 135 | } 136 | return target[key]; 137 | }, 138 | // 禁止设置带 `_`前缀的变量 139 | set: function(target, key, value) { 140 | if (key[0] === '_') { 141 | throw new Error('Attempt to access private property'); 142 | } 143 | target[key] = value; 144 | }, 145 | // 禁止 遍历私有属性,例如 for...in、Object.keys 146 | getOwnPropertyDescriptor(target, key) { 147 | const desc = Object.getOwnPropertyDescriptor(target, key); 148 | if (key[0] === '_') { 149 | desc.enumerable = false; 150 | } 151 | return desc; 152 | } 153 | } 154 | 155 | const square = new Proxy(new Shape(10, 10, 12), handler); 156 | console.log(square.area); // 100 157 | console.log(square instanceof Shape); // true,没有破坏 instanceof 158 | console.log(JSON.stringify(square)); // "{otherParams: 12}",只能列出非私有属性 159 | for (const key in square) { 160 | console.log(key); // otherParams, 只能列出非私有属性 161 | } 162 | square._width = 200; // 错误:试图访问私有属性 163 | square.otherParams = 200; // 正常设置非私有属性 164 | ``` -------------------------------------------------------------------------------- /Solution/编写可维护的现代化前端项目.md: -------------------------------------------------------------------------------- 1 | ## 弹窗 2 | 3 | ### 布局 4 | 5 | 弹窗或 `toast`最好放在整个页面结构的最顶层,防止 `z-index`之间的相互覆盖 6 | 7 | 例如,弹窗遮罩层所在的父元素因 `z-index`太低,而无法遮住父元素的同级元素 8 | 9 | ### 弹窗管理 -------------------------------------------------------------------------------- /Solution/网页端调起分享功能/browserShareTo.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | *网页端分享功能,只支持 qq浏览器 和 UC浏览器(uc 可能有的版本不支持) 4 | 支持分享到 新浪微博 微信好友 微信朋友圈 QQ好友 QQ空间 5 | */ 6 | // es6 实现 7 | class SocialShare { 8 | constructor () { 9 | this.init() 10 | } 11 | 12 | init () { 13 | this.initLocal() 14 | this.getPlantform() 15 | this.getCanShare() 16 | 17 | // this.supportShare && this.html() 18 | } 19 | 20 | initLocal () { 21 | this.qApiSrc = { 22 | lower: '//3gimg.qq.com/html5/js/qb.js', 23 | higher: '//jsapi.qq.com/get?api=app.share' 24 | } 25 | 26 | // 支持分享到的 手机app 27 | this.appList = { 28 | sinaWeibo: ['kSinaWeibo', 'SinaWeibo', 11, '新浪微博'], 29 | weixin: ['kWeixin', 'WechatFriends', 1, '微信好友'], 30 | weixinFriend: ['kWeixinFriend', 'WechatTimeline', '8', '微信朋友圈'], 31 | QQ: ['kQQ', 'QQ', '4', 'QQ好友'], 32 | QZone: ['kQZone', 'QZone', '3', 'QQ空间'] 33 | } 34 | 35 | this.bLevel = { 36 | qq: { 37 | forbid: 0, 38 | lower: 1, 39 | higher: 2 40 | }, 41 | uc: { 42 | forbid: 0, 43 | allow: 1 44 | } 45 | } 46 | 47 | this.UA = navigator.appVersion.toLowerCase() 48 | this.isqqBrowser = (this.UA.split('mqqbrowser/').length > 1) ? this.bLevel.qq.higher : this.bLevel.qq.forbid 49 | this.isucBrowser = (this.UA.split('ucbrowser/').length > 1) ? this.bLevel.uc.allow : this.bLevel.uc.forbid 50 | 51 | this.version = { 52 | uc: this.isucBrowser ? this.getVersion(this.UA.split('ucbrowser/')[1]) : 0, 53 | qq: this.isqqBrowser ? this.getVersion(this.UA.split('mqqbrowser/')[1]) : 0 54 | } 55 | 56 | this.isWxOrQQ = this.isWxOrQQ() 57 | 58 | this.platform_os = this.getPlantform() 59 | 60 | this.supportShare = false 61 | } 62 | 63 | getPlantform () { 64 | let nu = navigator.userAgent 65 | if ((nu.indexOf('iPhone') > -1 || nu.indexOf('iPod') > -1)) { 66 | return 'iPhone' 67 | } 68 | return 'Android' 69 | } 70 | 71 | getVersion (versionInfo) { 72 | let realVersion = versionInfo.split('.') 73 | return parseFloat(realVersion[0] + '.' + realVersion[1]) 74 | } 75 | 76 | isWxOrQQ () { 77 | // 判断是否是 微信 或者 QQ app 78 | let ua = this.UA 79 | if ((/mobile\smqqbrowser/i).test(ua)) { 80 | if ((/micromessenger/i).test(ua)) { 81 | // 是微信 app 82 | return 'wxApp' 83 | } 84 | // 是 QQ App 85 | return 'qqApp' 86 | } 87 | return false 88 | } 89 | 90 | initConfig (config) { 91 | let url = config.url || document.location.href || '' 92 | let title = config.title || document.title || '' 93 | let desc = config.desc || document.title || '' 94 | let img = config.img || document.getElementsByTagName('img').length > 0 && document.getElementsByTagName('img')[0].src || '' 95 | let img_title = config.img_title || document.title || '' 96 | let from = config.from || window.location.host || '' 97 | 98 | this.config = { 99 | url, 100 | title, 101 | desc, 102 | img, 103 | img_title, 104 | from 105 | } 106 | } 107 | 108 | share (to_app) { 109 | let { 110 | url, 111 | title, 112 | desc, 113 | img, 114 | img_title, 115 | from 116 | } = this.config 117 | 118 | let appList = this.appList 119 | let isWxOrQQ = this.isWxOrQQ 120 | 121 | if (this.isucBrowser) { 122 | // uc浏览器 123 | to_app = to_app === '' ? '' : (this.platform_os === 'iPhone' ? appList[to_app][0] : appList[to_app][1]) 124 | if (to_app === 'QZone') { 125 | let shareToUrl = 'mqqapi://share/to_qzone?src_type=web&version=1&file_type=news&req_type=1&image_url=' + img + '&title=' + title + '&description=' + desc + '&url=' + url + '&app_name=' + from 126 | let shareEle = document.createElement('div') 127 | shareEle.style.visibility = 'hidden' 128 | shareEle.innerHTML = '<iframe src="' + shareToUrl + '" scrolling="no" width="1" height="1"></iframe>' 129 | document.body.appendChild(shareEle) 130 | setTimeout(() => { 131 | shareEle && shareEle.parentNode && shareEle.parentNode.removeChild(shareEle) 132 | }, 5E3) 133 | } 134 | if (typeof ucweb !== 'undefined') { 135 | ucweb.startRequest('shell.page_share', [title, img, url, to_app, '', '', '']) 136 | } else { 137 | if (typeof ucbrowser !== 'undefined') { 138 | web_share(title, img, url, to_app, '', '@' + from, '') 139 | } 140 | } 141 | } else if (this.isqqBrowser && !this.isWxOrQQ) { 142 | // qq浏览器,并且不是微信app 或者 QQ app 143 | let isqqBrowser = this.isqqBrowser 144 | to_app = to_app === '' ? '' : appList[to_app][2] 145 | let shareConfig = { 146 | url: url, 147 | title: title, 148 | description: desc, 149 | img_url: img, 150 | img_title: img_title, 151 | to_app: to_app, // 微信好友1,腾讯微博2,QQ空间3,QQ好友4,生成二维码7,微信朋友圈8,啾啾分享9,复制网址10,分享到微博11,创意分享13 152 | cus_txt: '请输入此时此刻想要分享的内容' 153 | } 154 | 155 | shareConfig = to_app === '' ? '' : shareConfig 156 | if (typeof browser !== 'undefined') { 157 | if (typeof browser.app !== 'undefined' && isqqBrowser === this.bLevel.qq.higher) { 158 | browser.app.share(shareConfig) 159 | } 160 | } else { 161 | if (typeof window.qb !== 'undefined' && isqqBrowser === this.bLevel.qq.lower) { 162 | window.qb.share(shareConfig) 163 | } 164 | } 165 | 166 | } 167 | } 168 | 169 | isloadqqApi () { 170 | let qApiSrc = this.qApiSrc 171 | var version = (this.version.qq < 5.4) ? qApiSrc.lower : qApiSrc.higher 172 | var scriptEle = document.createElement('script') 173 | var body = document.querySelector('body') 174 | scriptEle.setAttribute('src', version) 175 | body.appendChild(scriptEle) 176 | } 177 | 178 | html () { 179 | // 以下为使用的 DOM示例 180 | // let ele = document.getElementById(this.elementNode) 181 | let html = ` 182 | <div class="label">分享到</div> 183 | <div class="list clearfix"> 184 | <span data-app="sinaWeibo" class="nativeShare weibo"><i></i>新浪微博</span> 185 | <span data-app="weixin" class="nativeShare weixin"><i></i>微信好友</span> 186 | <span data-app="weixinFriend" class="nativeShare weixin_timeline"><i></i>微信朋友圈</span> 187 | <span data-app="QQ" class="nativeShare qq"><i></i>QQ好友</span> 188 | <span data-app="QZone" class="nativeShare qzone"><i></i>QQ空间</span> 189 | <span data-app="" class="nativeShare more"><i></i>更多</span> 190 | </div>` 191 | document.body.innerHTML = html 192 | } 193 | 194 | getCanShare () { 195 | let isqqBrowser = this.isqqBrowser 196 | let isucBrowser = this.isucBrowser 197 | let versionQQ = this.version.qq 198 | let versionUC = this.version.uc 199 | 200 | let platform_os = this.platform_os 201 | 202 | if ((isqqBrowser && versionQQ < 5.4 && platform_os === 'iPhone') || (isqqBrowser && versionQQ < 5.3 && platform_os === 'Android')) { 203 | this.isqqBrowser = this.bLevel.qq.forbid 204 | } else { 205 | if (isqqBrowser && versionQQ < 5.4 && platform_os === 'Android') { 206 | this.isqqBrowser = this.bLevel.qq.lower 207 | } else { 208 | if (isucBrowser && ((versionUC < 10.2 && platform_os === 'iPhone') || (versionUC < 9.7 && platform_os === 'Android'))) { 209 | this.isucBrowser = this.bLevel.uc.forbid 210 | } 211 | } 212 | } 213 | this.isqqBrowser && this.isloadqqApi() 214 | 215 | if ((this.isqqBrowser && !this.isWxOrQQ) || this.isucBrowser) { 216 | this.supportShare = true 217 | // this.html() 218 | } else { 219 | this.supportShare = false 220 | // document.getElementById(this.elementNode).innerHTML = '目前该分享插件仅支持手机UC浏览器和QQ浏览器' 221 | } 222 | } 223 | 224 | goShare (shareEle, config) { 225 | if (this.supportShare) { 226 | this.initConfig(config) 227 | 228 | let items = document.querySelectorAll(shareEle) 229 | let len = items.length 230 | let that = this 231 | for (let i = 0; i < len; i++) { 232 | items[i].addEventListener('click', function() { 233 | that.share(this.getAttribute('data-app')) 234 | }) 235 | } 236 | } 237 | } 238 | } 239 | 240 | export default SocialShare 241 | 242 | 243 | 244 | // 用法示例: 245 | 246 | const mshare = () => { 247 | const socialShare = new SocialShare() 248 | 249 | // 检测是否是在 微信 或者 QQ app 内置的浏览器中 250 | const isWxOrQQ = socialShare.isWxOrQQ 251 | // 检测浏览器是否支持分享 252 | const isSupportShare = socialShare.supportShare 253 | if (!socialShare.isWxOrQQ) { 254 | if (isSupportShare) { 255 | // 当前浏览器支持分享 256 |      let title = '分享的标题' // 标题 257 | let url = 'http://example.com/index.html' // 分享的网页链接 258 |      let img = 'http://example.com/share.png' // 分享的图片链接 259 |      let desc = '分享的内容描述' // 描述 260 |      let img_title = '分享的图片描述' 261 |      let from = '分享的来源' // 一般此项都是由浏览器自动设置 262 | 263 |      socialShare.goShare('.share-item', { title, url, img, desc, img_title, from }) 264 | } else { 265 | console.log('当前浏览器不支持分享') 266 | } 267 | } 268 | } 269 | 270 | // 调用分享函数 271 | mshare() 272 | 273 | 274 | -------------------------------------------------------------------------------- /Vue/Vue-Note-1.md: -------------------------------------------------------------------------------- 1 | ## nextTick 2 | 3 | 内部分为 `macroTask` 和 `microTask`,一般用户外部调用 `nextTick`都是调用的 `microTask` 4 | `macroTask`依次通过 `setImmediate`、`MessageChannel`、`setTimeout`降级实现,`microTask`先尝试通过 `Promise`来实现,如果当前浏览器环境不支持 `Promise`,则降级为 `macroTask` 5 | 6 | ```js 7 | // 将 microTask降级为 macroTask 8 | // fallback to macro 9 | microTimerFunc = macroTimerFunc 10 | ``` 11 | 12 | ## keep-alive 13 | 14 | `Vue`内置组件,其代码定义在 `src/core/components/keep-alive.js` 15 | 16 | `keep-alive` 同样也是通过渲染一个组件来实现,只不过相比于我们手写的组件,其 `DOM`是直接通过一个 `render`函数渲染获得而非 `template`: 17 | 18 | ```js 19 | // src/core/components/keep-alive.js 20 | render () { 21 | const slot = this.$slots.default 22 | const vnode: VNode = getFirstComponentChild(slot) 23 | const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions 24 | ... 25 | } 26 | ``` 27 | 28 | 其在内部制定了一个 `abstract`属性,使得在组件实例建立父子关系的时候被忽略,`keep-alive`的子组件的父组件被设定为 `keep-alive`的父组件: 29 | ```js 30 | // src\core\instance\lifecycle.js 31 | let parent = options.parent 32 | if (parent && !options.abstract) { 33 | while (parent.$options.abstract && parent.$parent) { 34 | parent = parent.$parent 35 | } 36 | parent.$children.push(vm) 37 | } 38 | ``` 39 | 40 | 并且 `keep-alive`最后返回的组件也是其子组件: 41 | ```js 42 | render () { 43 | const slot = this.$slots.default 44 | const vnode: VNode = getFirstComponentChild(slot) 45 | ... 46 | return vnode || (slot && slot[0]) 47 | } 48 | ``` 49 | 50 | `keep-alive`每次只会渲染其第一个子元素,所以诸如 `v-for`等能够渲染出多个子组件的方式都是不生效的: 51 | ```js 52 | // 只获取第一个子组件实例 53 | const vnode: VNode = getFirstComponentChild(slot) 54 | ``` 55 | 56 | 如果指定了 `keep-alive`的 `max`属性,则其将在内部通过 `LRU`(最新最少使用)算法来管理缓存的组件: 57 | ```js 58 | // src/core/components/keep-alive.js 59 | if (cache[key]) { 60 | vnode.componentInstance = cache[key].componentInstance 61 | // make current key freshest 62 | remove(keys, key) 63 | keys.push(key) 64 | } 65 | ``` 66 | 67 | ## transition 68 | 69 | 代码在 `src\platforms\web\runtime\components\transition.js` 和 `src/platforms/web/modules/transition.js`中 70 | 71 | `<transition>` 组件和 `<keep-alive>` 组件有⼏点实现类似,同样是抽象组件,同样直接实现 `render` 函数,同样利⽤了默认插槽(`slot`) 72 | 73 | 通过 `autoCssTransition` 处理 `name` 属性,⽣成⼀个⽤来描述各个阶段的 `class` 名称的对象,扩展到 `def` 中并返回给`data` ,这样我们就可以从 `data` 中获取到过渡 74 | 相关的所有数据,`vue`后续通过对这些 `class`名的处理,结合用户自定义的 `css`来控制动画的实现 75 | ```js 76 | const autoCssTransition: (name: string) => Object = cached(name => { 77 | return { 78 | enterClass: `${name}-enter`, 79 | enterToClass: `${name}-enter-to`, 80 | enterActiveClass: `${name}-enter-active`, 81 | leaveClass: `${name}-leave`, 82 | leaveToClass: `${name}-leave-to`, 83 | leaveActiveClass: `${name}-leave-active` 84 | } 85 | }) 86 | ``` 87 | 88 | 通过 `requestAnimationFrame`(降级为 `setTimeout`)来控制动画间的过渡状态,以及控制动画钩子函数的执行 89 | ```js 90 | // binding to window is necessary to make hot reload work in IE in strict mode 91 | const raf = inBrowser 92 | ? window.requestAnimationFrame 93 | ? window.requestAnimationFrame.bind(window) 94 | : setTimeout 95 | : /* istanbul ignore next */ fn => fn() 96 | 97 | export function nextFrame (fn: Function) { 98 | raf(() => { 99 | raf(fn) 100 | }) 101 | } 102 | ``` 103 | 104 | ## 编译 105 | 106 | 除去一部分对 `platform`、缓存等逻辑外,编译的核心分为三步: 107 | 108 | - 解析模板字符串生成 `AST` 109 | 110 | ```js 111 | const ast = parse(template.trim(), options) 112 | ``` 113 | 114 | 主要是通过大量的正则表达式、压栈出栈等算法对 `template`字符串进行一段段的逐步解析,来构造初步的 `AST Tree` 115 | 116 | - 优化语法树 117 | 118 | ```js 119 | optimize(ast, options) 120 | ``` 121 | 122 | 对上一步构造出的 `AST Tree`进行优化,例如标记静态节点、增加缓存等手段,来提升 `AST Tree`的解析速度 123 | 124 | - 生成代码 125 | 126 | ```js 127 | const code = generate(ast, options) 128 | ``` 129 | 130 | 将上一步的 `AST Tree`转换成可执行的代码, 131 | 132 | ## v-once 133 | 134 | 这个指令的内部实现,就是先调用了一次 `v-on`绑定事件,等到事件执行一遍之后,再通过回调函数调用 `v-off`来取消事件实现的 135 | ```js 136 | Vue.prototype.$once = function (event: string, fn: Function): Component { 137 | const vm: Component = this 138 | function on () { 139 | vm.$off(event, on) 140 | fn.apply(vm, arguments) 141 | } 142 | on.fn = fn 143 | vm.$on(event, on) 144 | return vm 145 | } 146 | ``` 147 | ## v-model 148 | 149 | `v-model `实际上都是语法糖,通过给 `$el`增加对应的 `prop`变量,以及绑定响应事件来实现的, 150 | `v-html` 以及`v-text`同样有异曲同工之妙 151 | 152 | ```js 153 | // v-model 154 | // input textarea 155 | addProp(el, 'value', `(${value})`) 156 | // check 157 | addProp(el, 'checked', 158 | `Array.isArray(${value})` + 159 | `?_i(${value},${valueBinding})>-1` + ( 160 | trueValueBinding === 'true' 161 | ? `:(${value})` 162 | : `:_q(${value},${trueValueBinding})` 163 | ) 164 | ) 165 | ``` 166 | 167 | ## composition 168 | 169 | `v-model`内部借助了 `compositionstart` 和 `compositionend`事件来处理输入,所以能够更好地处理拼音字符的输入 170 | 在不支持 `composition` 事件的浏览器上,则降级为 `change`事件 171 | 172 | ```js 173 | el.addEventListener('compositionstart', onCompositionStart) 174 | el.addEventListener('compositionend', onCompositionEnd) 175 | // Safari < 10.2 & UIWebView doesn't fire compositionend when 176 | // switching focus before confirming composition choice 177 | // this also fixes the issue where some browsers e.g. iOS Chrome 178 | // fires "change" instead of "input" on autocomplete. 179 | el.addEventListener('change', onCompositionEnd) 180 | ``` -------------------------------------------------------------------------------- /Vue/solution/使用Vue.extend构造工具组件toast/app.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="app" @click="showToast">app</div> 3 | </template> 4 | <script> 5 | // 导入 toast组件 6 | import toast from './toast' 7 | 8 | export default { 9 | methods: { 10 | showToast() { 11 | // 使用 12 | toast('我是toast') 13 | } 14 | } 15 | } 16 | </script> 17 | -------------------------------------------------------------------------------- /Vue/solution/使用Vue.extend构造工具组件toast/toast/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import toast from './index.vue' 3 | 4 | const ToastConstructor = Vue.extend(toast) 5 | // 避免重复创建 6 | let toastPool = [] 7 | 8 | // 获取 toast实例 9 | const getAnInstance = () => { 10 | // 已经有存在的实例了,则隐藏然后重现用于当前最新的提示 11 | if (toastPool.length) { 12 | const instance = toastPool[0] 13 | toastPool.splice(0, 1) 14 | instance.visible = false 15 | return instance 16 | } 17 | const instance = new ToastConstructor({ 18 | el: document.createElement('div') 19 | }) 20 | toastPool.push(instance) 21 | return instance 22 | } 23 | 24 | const Toast = (options = {}) => { 25 | const instance = getAnInstance() 26 | clearTimeout(instance.timer) 27 | instance.message = typeof options !== 'string' ? options.message : options 28 | instance.position = options.position || 'bottom' 29 | document.body.appendChild(instance.$el) 30 | Vue.nextTick(() => { 31 | instance.visible = true 32 | instance.timer = setTimeout(() => { 33 | instance.visible = false 34 | }, options.duration || 2000) 35 | }) 36 | } 37 | export default Toast 38 | -------------------------------------------------------------------------------- /Vue/solution/使用Vue.extend构造工具组件toast/toast/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <transition name="toast-pop"> 3 | <div class="toast" v-if="visible" :class="customClass"> 4 | <span class="toast-text">{{message}}</span> 5 | </div> 6 | </transition> 7 | </template> 8 | <script> 9 | export default { 10 | props: { 11 | message: { 12 | type: String, 13 | default: '' 14 | }, 15 | position: { 16 | type: String, 17 | default: 'bottom' 18 | } 19 | }, 20 | data () { 21 | return { 22 | visible: false 23 | } 24 | }, 25 | computed: { 26 | customClass () { 27 | switch (this.position) { 28 | case 'top': 29 | return ['is-top'] 30 | case 'bottom': 31 | return ['is-bottom'] 32 | case 'middle': 33 | return ['is-middle'] 34 | } 35 | } 36 | } 37 | } 38 | </script> 39 | <style lang="scss"> 40 | .toast-pop-enter-active, .toast-pop-leave-active { 41 | transition: opacity .3s linear; 42 | } 43 | .toast-pop-enter, .toast-pop-leave-to { 44 | opacity: 0; 45 | } 46 | .toast { 47 | position: fixed; 48 | max-width: 80%; 49 | border-radius: 5px; 50 | background: rgba(0, 0, 0, 0.7); 51 | color: #fff; 52 | box-sizing: border-box; 53 | text-align: center; 54 | z-index: 1000; 55 | padding: 10px; 56 | &-text { 57 | font-size: 14px; 58 | display: block; 59 | text-align: center; 60 | } 61 | } 62 | .is-top { 63 | top: 50px; 64 | left: 50%; 65 | transform: translate(-50%, 0); 66 | } 67 | .is-middle { 68 | left: 50%; 69 | top: 50%; 70 | transform: translate(-50%, -50%); 71 | } 72 | .is-bottom { 73 | bottom: 50px; 74 | left: 50%; 75 | transform: translate(-50%, 0); 76 | } 77 | </style> 78 | -------------------------------------------------------------------------------- /Vue/solution/输入手机号时334加入以及删除空格的组件.md: -------------------------------------------------------------------------------- 1 | 当用户输入手机号的时候,例如 `18888888888`,为了能带来一种更好的用户体验,通常会根据 `344`原则自动在手机号的数字间增加空格,以便用户能更方便的确认自己输入,例如 `188 8888 8888`,下面是一种解决方案 2 | 3 | 首先,需要给输入框加 `v-model`,以进行监听: 4 | ```js 5 | // type为 tel 6 | <input v-model="phoneNum" type="tel" placeholder="请输入手机号" /> 7 | ``` 8 | 然后通过 `watch`进行监听变化: 9 | ```js 10 | watch: { 11 | // 输入过程中的手机号验证 12 | phoneNum (newValue, oldValue) { 13 | if (!newValue) return '' 14 | // 只能存在数字和空格 15 | if (!/^(\d|\s)+$/g.test(newValue)) { 16 | console.log('请输入正确的手机号码') 17 | return this.phoneNum = oldValue 18 | } 19 | // 最多 11位数字,加上空格一共最多13个字符 20 | if (newValue.length > 13) { 21 | return this.phoneNum = oldValue 22 | } 23 | // 增删空格 24 | if (newValue.length > oldValue.length) { 25 | if (newValue.length === 3 || newValue.length === 8) { 26 | this.phoneNum += ' ' 27 | } 28 | } else { 29 | if (newValue.length === 3 || newValue.length === 8) { 30 | this.phoneNum = this.phoneNum.slice(0, -1) 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | 最后提交验证的时候,再过一遍正则匹配: 38 | ```js 39 | verifyPhoneNum () { 40 | // 去除所有的空格 41 | const phoneNumber = this.phoneNum.replace(/\s/g, '') 42 | // 验证是否是符合格式的手机号 43 | if (!/^(13[0-9]|14[579]|15([0-3]|[5-9])|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/.test(phoneNumber)) { 44 | console.log('请输入正确的手机号码') 45 | return 46 | } 47 | /// 检测通过 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /Vue/异步组件.md: -------------------------------------------------------------------------------- 1 | 通常为了提升性能,例如首屏渲染速度,我们会异步加载组件,`Vue`异步加载组件的方式有以下三种: 2 | 3 | - 工厂函数 4 | - `Promise` 5 | - 高级异步组件 6 | 7 | ## 工厂函数 8 | 9 | ```js 10 | Vue.component('async-component', function(resolve, reject) => { 11 | require(['./async-component.vue'], resolve) 12 | }) 13 | ``` 14 | 15 | 上述代码,加载了一个文件名为 `async-component.vue`的单文件组件,将之命名为 `asynv-component`,并注册为全局组件。 16 | 利用上述写法,`vue-loader`会将 `async-component.vue`打包成一个独立文件,实现异步加载。 17 | 18 | 虽然是异步组件,但除了加载顺序比同步组件稍慢外,其他的逻辑和同步组件都是一样的,同样会借助 `vue`源码中的 `src/core/vdom/create-component/js`文件中的 `createComponent`方法进行组件的初始化和渲染,由于是异步加载,所以组件在一开始时是不会同步加载的,而是会暂时同步渲染出一个注释节点作为占位节点,源代码中的逻辑如下: 19 | ```js 20 | // src/core/vdom/create-component.js 21 | // async component 22 | let asyncFactory 23 | if (isUndef(Ctor.cid)) { 24 | asyncFactory = Ctor 25 | Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context) 26 | if (Ctor === undefined) { 27 | // return a placeholder node for async component, which is rendered 28 | // as a comment node but preserves all the raw information for the node. 29 | // the information will be used for async server-rendering and hydration. 30 | return createAsyncPlaceholder( 31 | asyncFactory, 32 | data, 33 | context, 34 | children, 35 | tag 36 | ) 37 | } 38 | } 39 | ``` 40 | 41 | ## `Promise` 42 | 43 | ```js 44 | // 通过 import 返回一个 promise对象 45 | Vue.component('async-component', () => import('./async-component.vue')) 46 | ``` 47 | 48 | 加载这种写法的异步组件,在源码中的逻辑位于 `src/core/vdom/helpers/resolve-async-component.js`: 49 | ```js 50 | if (isObject(res)) { 51 | if (typeof res.then === 'function') { 52 | // () => Promise 53 | if (isUndef(factory.resolved)) { 54 | // 加载异步组件 55 | res.then(resolve, reject) 56 | } 57 | } 58 | // ... 59 | ``` 60 | 61 | 这里其实是利用了 `webpack`的 `import`语法糖能够直接返回一个 `promise`的优势,从而实现异步加载,但实际上这种方法除了加载方式与工厂函数不同之外,剩下的逻辑都是一样的。 62 | 63 | ## 高级异步组件 64 | 65 | ```js 66 | // index.vue 67 | import Vue from 'vue' 68 | import App from './App.vue' 69 | 70 | const LoadingComp = { 71 | template: '<div>loading</div>' 72 | } 73 | 74 | const ErrorComp = { 75 | template: '<div>error</div>' 76 | } 77 | 78 | const AsyncComp = () => ({ 79 | // 真正需要加载的组件,应当是一个 Promise 80 | component: import('./components/HelloWorld.vue'), 81 | // 在加载完成之前进行占位渲染的组件 82 | loading: LoadingComp, 83 | // 出错时渲染的组件 84 | error: ErrorComp, 85 | // 渲染 loading组件前的等待时间(如果超出这个时间真正需要加载的组件还没来得及渲染好,则渲染laoding组件),默认 200 86 | delay: 200, 87 | // 最长等待时间,如果超过此时间,真正需要加载的组件还没渲染好,则渲染 error组件 88 | timeout: 1000 89 | }) 90 | 91 | Vue.component('helloWOrld', AsyncComp) 92 | // ... 93 | ``` 94 | 加载这种写法的异步组件,在源码中的逻辑位于 `src/core/vdom/helpers/resolve-async-component.js`: 95 | ```js 96 | else if (isDef(res.component) && typeof res.component.then === 'function') { 97 | res.component.then(resolve, reject) 98 | 99 | if (isDef(res.error)) { 100 | factory.errorComp = ensureCtor(res.error, baseCtor) 101 | } 102 | 103 | if (isDef(res.loading)) { 104 | factory.loadingComp = ensureCtor(res.loading, baseCtor) 105 | if (res.delay === 0) { 106 | factory.loading = true 107 | } else { 108 | setTimeout(() => { 109 | if (isUndef(factory.resolved) && isUndef(factory.error)) { 110 | factory.loading = true 111 | forceRender() 112 | } 113 | }, res.delay || 200) 114 | } 115 | } 116 | 117 | if (isDef(res.timeout)) { 118 | setTimeout(() => { 119 | if (isUndef(factory.resolved)) { 120 | reject( 121 | process.env.NODE_ENV !== 'production' 122 | ? `timeout (${res.timeout}ms)` 123 | : null 124 | ) 125 | } 126 | }, res.timeout) 127 | } 128 | } 129 | ``` 130 | 131 | -------------------------------------------------------------------------------- /Vue/性能优化.md: -------------------------------------------------------------------------------- 1 | ## Vue 应用运行时性能优化措施 2 | 3 | - 引入生产环境的 Vue 文件 4 | 5 | 更小的资源开销 6 | 7 | - 使用单文件组件预编译模板 8 | 9 | 预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。 10 | 11 | - 提取组件的 CSS 到单独到文件 12 | 13 | 当使用单文件组件时,组件内的 CSS 会以 `<style>` 标签的方式通过 `JavaScript` 动态注入。这有一些小小的运行时开销,将所有组件的 CSS 提取到同一个文件可以避免这个问题,也会让 CSS 更好地进行压缩和缓存。 14 | 15 | - 利用Object.freeze()提升性能 16 | 17 | Vue 在遇到像 `Object.freeze()` 这样被设置为不可配置之后的对象属性时,不会为对象加上 `setter getter` 等数据劫持的方法 18 | 19 | 如果你确定某些 `data`数据只是用于展示,后续不会有任何改变,那么可以尝试使用 20 | 21 | - 扁平化 Store 数据结构 22 | 23 | ```js 24 | { 25 | "id": "123", 26 | "author": { 27 | "id": "1", 28 | "name": "Paul" 29 | }, 30 | "title": "My awesome blog post", 31 | "comments": [ 32 | { 33 | "id": "324", 34 | "commenter": { 35 | "id": "2", 36 | "name": "Nicole" 37 | } 38 | } 39 | ] 40 | } 41 | ``` 42 | 43 | 类似上述层级较深的数据结构,在进行频繁查找的时候会带来额外的性能开销,假设我们把用户信息在 `store` 内统一存放成 `users[id]`这样的结构,修改和读取用户信息的成本就变得非常低。 44 | 45 | 可以自己手动写个工具函数,或者使用 [normalizr](https://github.com/paularmstrong/normalizr) 46 | 47 | - 优化无限列表性能 48 | 49 | 如果你的应用存在非常长或者无限滚动的列表,那么采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。】 50 | 51 | 相关的开源项目[vue-virtual-scroll-list](https://github.com/tangbc/vue-virtual-scroll-list)、[vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) 52 | 53 | - 组件渲染懒加载优化超长应用内容初始渲染性能 54 | 55 | 上面提到的无限列表的场景,比较适合列表内元素非常相似的情况,不过有时候,你的 Vue 应用的超长列表内的内容往往不尽相同,例如在一个复杂的应用的主界面中,整个主界面由非常多不同的模块组成,而用户看到的往往只有首屏一两个模块。在初始渲染的时候不可见区域的模块也会执行和渲染,带来一些额外的性能开销。 56 | 使用组件懒加载在不可见时只需要渲染一个骨架屏,不需要真正渲染组件 57 | 58 | ## Vue 应用加载性能优化措施 59 | 60 | - 利用服务端渲染(`SSR`)和预渲染(`Prerender`)来优化加载性能 61 | 62 | - 组件懒加载 63 | 64 | ## `Vuex`模块的代码拆分 65 | 66 | ### 模块动态加载 67 | 68 | 我们通常会在项目初始化的时候,同时进行 `Vuex`的初始化,静态集中注册 `Vuex`的全部模块 69 | ```js 70 | // store.js 71 | import { userAccountModule } from './modules/userAccount' 72 | import { adminModule } from './modules/admin' 73 | const store = new Vuex.Store({ 74 | modules: { 75 | user: userAccountModule, 76 | admin: adminModule 77 | } 78 | }) 79 | ``` 80 | 但是可能并不是每个用户都会使用其中所有的模块,比如一部分用户不需要 `adminModule`,那么加载的 `adminModule`就相当于是无用代码,可通过模块的动态注册来解决: 81 | 82 | ```js 83 | // store.js 84 | import { userAccountModule } from './modules/userAccount' 85 | export const store = new Vuex.Store({ 86 | modules: { 87 | user: userAccountModule, 88 | } 89 | }) 90 | // Admin.vue 91 | import adminModule from './admin.js' 92 | export default { 93 | mounted () { 94 | // 在组件内部进行 vuex模块的动态注册 95 | this.$store.registerModule('admin', adminModule) 96 | }, 97 | beforeDestroy () { 98 | // 在组件内部进行 vuex模块的动态卸载, 99 | // 防止同一模块被多次注册 100 | this.$store.unregisterModule('admin') 101 | } 102 | } 103 | ``` 104 | 105 | ### 模块延迟加载 106 | 107 | 假设 `Home.vue` 上有客户评价部分,我们希望显示客户对服务的积极评价。因为有很多,所以我们不想在用户进入网站后立即显示它们,而是在用户需要查看时才显示它们。我们可以添加一个`Show Testimonials`按钮,点击这个按钮后将加载并显示客户评价。 108 | 为了保存客户评价数据,我们还需要另外一个 `Vuex` 模块,我们把它叫作 `testimonials`。这个模块将负责显示之前添加的评价和添加新的评价,但我们不需要了解实现细节。 109 | 我们希望只在用户单击了按钮后才下载 `testimonials` 模块,因为在这之前不需要它。让我们来看看如何利用动态模块注册和动态导入来实现这个功能。`Testimonials.vue` 是 `Home.vue` 中的一个组件 110 | 111 | ```html 112 | // Testimonials.vue 113 | <template> 114 | <button @click="showTestimonials">Testimonials</button> 115 | </template> 116 | 117 | <script> 118 | const getTestimonialsModule = () => import('./testimonials.js') 119 | 120 | export default { 121 | methods: { 122 | showTestimonials () { 123 | getTestimonialsModule().then(testimonialsModule => { 124 | this.isModuleRegistered = true 125 | this.$store.registerModule('testimonials', testimonialsModule) 126 | this.$store.dispatch('testimonials/load') 127 | }) 128 | } 129 | }, 130 | beforeDestroy () { 131 | if (this.isModuleRegistered) { 132 | this.$store.unregisterModule('testimonials') 133 | } 134 | } 135 | } 136 | </script> 137 | ``` -------------------------------------------------------------------------------- /Vue/新特性.md: -------------------------------------------------------------------------------- 1 | ## 深度选择器 `>>>` 2 | 3 | >严格来说,这个应该是 `vue-loader`的功能。`vue-loader`: `^12.2.0` 4 | 5 | 父组件定制有 `CSS作用域` 的子组件的样式 6 | 7 | 子组件 `child.vue` 8 | ``` 9 | <!-- 模板 --> 10 | <div class="child">我是子组件</div> 11 | 12 | <!-- 样式,使用了 scoped 作用域 --> 13 | <style scoped> 14 | .child { 15 | color: blue; 16 | } 17 | <style> 18 | ``` 19 | 20 | 父组件 `parent.vue` 21 | ``` 22 | <!-- 模板,使用子组件 child.vue --> 23 | <div class="parent"> 24 | <child /> 25 | <div> 26 | 27 | <!-- 样式 --> 28 | <!-- 通过 深度选择器 >>> 改变了子组件的字体颜色,就算是子组件使用了 css作用域也可以达到效果 --> 29 | .parent >>> .child { 30 | color: red; 31 | } 32 | ``` 33 | 34 | 如果用的是 `less`,为了也使用这个特性同时避免报错,需要下面这样做 35 | ```less 36 | @deep: ~'>>>'; 37 | .parent { 38 | @{deep} .child { 39 | color: red; 40 | } 41 | } 42 | ``` 43 | 44 | `sass`可以使用 `/deep/` 操作符: 45 | ```scss 46 | .parent { 47 | /deep/ .child { 48 | color: red; 49 | } 50 | } 51 | ``` 52 | 53 | ## 组件配置项 inheritAttrs (2.4.0新增) 54 | 55 | 我们都知道假如使用子组件时传了 `a,b,c`三个 `prop`,而子组件的 `props`选项只声明了`a` 和 `b`,那么渲染后 `c`将作为 `html`自定义属性显示在子组件的根元素上。 56 | 如果不希望这样,可以设置子组件的配置项 `inheritAttrs:false`,根元素就会干净多了。 57 | 58 | ```js 59 | export default { 60 | name: 'child', 61 | props:['a','b'], 62 | inheritAttrs:false 63 | } 64 | ``` 65 | 66 | ## 组件实例属性 $attrs 和 $listeners (2.4.0新增) 67 | 68 | `vm.$attrs` 是组件的内置属性,值是父组件传入的所有 `prop`中未被组件声明的 `prop`(`class` 和 `style`除外) 69 | 70 | ```html 71 | <!-- parent.vue --> 72 | <child a="a" b="b" c="c"></child> 73 | 74 | <!-- child.vue --> 75 | <script> 76 | export default { 77 | name: 'child', 78 | props:['a','b'], 79 | inheritAttrs:false, 80 | mounted(){ 81 | //控制台输出: 82 | //child:$attrs: {c: "c"} 83 | console.log('child:$attrs:',this.$attrs); 84 | } 85 | } 86 | </script> 87 | ``` 88 | 89 | 组件可以通过在自己的子组件上使用 `v-bind='$attrs'`,进一步把值传给自己的子组件。也就是说子组件会把 `$attrs`的值当作传入的 prop`处理,同时还要遵守第一点的规则。 90 | 91 | ```html 92 | <!-- parent.vue --> 93 | <child a="a" b="b" c="c"></child> 94 | 95 | <!-- child.vue --> 96 | <grand-child v-bind="$attrs" d="d"></grand-child> 97 | 98 | <script> 99 | export default { 100 | name: 'child', 101 | props:['a','b'], 102 | inheritAttrs:false 103 | } 104 | </script> 105 | 106 | <!-- grandchild.vue --> 107 | <script> 108 | export default { 109 | name: 'grandchild', 110 | props:[], 111 | //props:['c'], 112 | inheritAttrs:false, 113 | mounted(){ 114 | //控制台输出: 115 | //grandchild:$attrs: {d: "d", c: "c"} 116 | console.log('grandchild:$attrs:',this.$attrs); 117 | 118 | //如果props:['c'] 119 | //控制台输出: 120 | //grandchild:$attrs: {d: "d"} 121 | }, 122 | } 123 | </script> 124 | ``` 125 | 126 | ## vm.$listeners 127 | 128 | 只读,包含了父作用域中的 (不含 `.native` 修饰器的) `v-on` 事件监听器。它可以通过 `v-on='$listeners'` 传入内部组件——在创建更高层次的组件时非常有用。 129 | 130 | ## 组件选项 provide/inject 131 | 132 | 类似于 `React` 的 `context` 133 | 134 | >provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。 135 | 136 | ## Vue的错误捕获 137 | 138 | - 全局配置errorHandler 139 | 140 | - 生命周期钩子errorCaptured -------------------------------------------------------------------------------- /Vue/无渲染Vue组件/README.md: -------------------------------------------------------------------------------- 1 | 无渲染组件是指不渲染任何内容的组件。那么为什么我们需要不渲染任何内容的组件? 2 | 我们可以这样理解无渲染组件:为一个组件创建通用功能抽象,然后通过扩展这个组件来创建更好更健壮的组件,或者说,遵循 [S.O.L.I.D 原则](https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design)。 3 | 4 | - 单一责任原则 5 | 一个类应该只有一个用途。 6 | - 开放封闭原则 7 | 类或组件应该为扩展而开放,为修改而封闭,意即 你应该扩展它,而不是直接修改组件的源代码 8 | 9 | 本示例展示如何使用 `render`函数、`scopedSlots`构建一个无渲染 `Vue`组件 -------------------------------------------------------------------------------- /Vue/无渲染Vue组件/project/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="test1"> 3 | <toggle> 4 | <div slot-scope="{ on, setOn, setOff, change }" class="container"> 5 | <button @click="click(setOn)" class="button">Blue pill</button> 6 | <button @click="click(setOff)" class="button isRed">Red pill</button> 7 | <button @click="click(change)" class="button isDefault">toggle</button> 8 | <div v-if="buttonPressed" class="message"> 9 | <span v-if="on">It's all a dream, go back to sleep.</span> 10 | <span v-else>I don't know how far the rabbit hole goes</span> 11 | </div> 12 | </div> 13 | </toggle> 14 | </div> 15 | </template> 16 | 17 | <script> 18 | import toggle from './toggle'; 19 | 20 | export default { 21 | components: { 22 | toggle, 23 | }, 24 | data() { 25 | return { 26 | buttonPressed: false, 27 | }; 28 | }, 29 | methods: { 30 | click(fn) { 31 | this.buttonPressed = true; 32 | fn(); 33 | }, 34 | }, 35 | }; 36 | </script> 37 | 38 | <style lang="scss" scoped> 39 | body { 40 | display: flex; 41 | height: 100vh; 42 | align-items: center; 43 | justify-content: center; 44 | } 45 | 46 | .container { 47 | position: relative; 48 | } 49 | 50 | .button { 51 | border: none; 52 | padding: 1rem 2rem; 53 | border-radius: 2rem; 54 | cursor: pointer; 55 | background: blue; 56 | color: white; 57 | text-transform: uppercase; 58 | font-weight: bold; 59 | outline: none; 60 | box-shadow: 0 4px 2px 1px rgba(slategray, .2); 61 | &.isRed { 62 | background: red; 63 | } 64 | &.isDefault { 65 | background: yellow; 66 | } 67 | } 68 | 69 | .message { 70 | position: absolute; 71 | top: 100%; 72 | left: 50%; 73 | transform: translateX(-50%); 74 | width: 100%; 75 | padding: 1rem; 76 | text-align: center; 77 | font-size: 1.25rem; 78 | } 79 | </style> 80 | 81 | -------------------------------------------------------------------------------- /Vue/无渲染Vue组件/project/toggle.js: -------------------------------------------------------------------------------- 1 | const toggle = { 2 | props: { 3 | on: { type: Boolean, default: false } 4 | }, 5 | render() { 6 | return this.$scopedSlots.default({ 7 | on: this.currentState, 8 | setOn: this.setOn, 9 | setOff: this.setOff, 10 | change: this.change, 11 | }) 12 | }, 13 | data() { 14 | return { currentState: this.on } 15 | }, 16 | methods: { 17 | setOn() { 18 | this.currentState = true 19 | }, 20 | setOff() { 21 | this.currentState = false 22 | }, 23 | change() { 24 | this.currentState = !this.currentState 25 | } 26 | } 27 | } 28 | 29 | export default toggle 30 | -------------------------------------------------------------------------------- /Webpack/somecase.md: -------------------------------------------------------------------------------- 1 | # 一些有关 Webpack 的知识点 2 | 3 | ## Source Map 4 | 5 | 通过 [devtool](https://www.webpackjs.com/configuration/devtool/) 进行配置 6 | 7 | - `eval`:通过 `eval`关键字进行映射,将每个模块转化为字符串,使用eval包裹,并将打包前每个模块的sourcemap信息转换为Base64编码,拼接在每个打包后文件的结尾,效率最高,但不复杂代码下错误提示信息量较少 8 | - `source-map`:如果包含此值,会自动打包出一个 `.map`的文件 9 | - `inline`:如果包含此值,会将 `source map`映射关系直接放到打包出来的目标文件(例如 `main.js`)中(一般是文件的最底部) 10 | - `cheap`:如果包含此值,则错误信息中只包含出错的行数而不包括列数,并且值提示业务代码中的错误,而不提示第三方库(例如 `vue-router`)中的错误 11 | - `module`:如果包含此值,则错误信息中也包含第三方库(例如 `vue-router`)中的错误 12 | 13 | >开发环境推荐 `cheap-module-source-map` 14 | > 15 | >线上环境推荐 `source-map` 16 | 17 | ## Babel 18 | 19 | ### 业务代码配置 20 | 21 | 使用 `presets` 22 | 23 | ```js 24 | module: { 25 | rules: [{ 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | loader: 'babel-loader', 29 | options: { 30 | presets: [['@babel/preset-env', { 31 | targets: '> 0.25%, not dead', 32 | useBuiltIns: 'usage' 33 | }]] 34 | } 35 | }] 36 | } 37 | ``` 38 | 39 | ### 库项目(第三方包) 40 | 41 | 使用 `plugins`,这样 `polyfill`的代码将会通过闭包的方式提供使用,避免污染全局环境 42 | 43 | ```js 44 | module: { 45 | rules: [{ 46 | test: /\.js$/, 47 | exclude: /node_modules/, 48 | loader: 'babel-loader', 49 | options: { 50 | plugins: [['@babel/plugin-transform-runtime', { 51 | "absoluteRuntime": false, 52 | "corejs": false, 53 | "helpers": true, 54 | "regenerator": true, 55 | "useESModules": false 56 | }]] 57 | } 58 | }] 59 | } 60 | ``` 61 | 62 | 如果 `options`的配置项比较多,推荐将 `options`的内容直接放到 `.babelrc` 文件中,例如: 63 | ```js 64 | // .babelrc 65 | { 66 | plugins: [["@babel/plugin-transform-runtime", { 67 | "absoluteRuntime": false, 68 | "corejs": false, 69 | "helpers": true, 70 | "regenerator": true, 71 | "useESModules": false 72 | }]] 73 | } 74 | ``` 75 | ## Tree-Shaking 76 | 77 | 依赖于 `ES2015` 模块系统中的 **静态结构特性**,即 只有`ES Module`风格的代码才能被 `shaking`,例如 `import export`,而对于其他风格的代码不适用,例如 `const module = require('module')`,这种是无法 `shaking`的 78 | 79 | ## optimization.splitChunks 80 | 81 | ### 配置解析 82 | 83 | ```js 84 | splitChunks: { 85 | // async:只对异步引入代码进行分割(例如 import进来的代码);initial:只对同步代码进行分割;all:对异步和同步引入的代码都进行分割 86 | chunks: "async", 87 | // 只有当文件体积大于 minSize 字节时才进行分割 88 | minSize: 30000, 89 | // 只有当文件被引用的次数不小于 minChunks才进行分割 90 | minChunks: 1, 91 | // 按需加载时候最大的并行请求数(如果需要分割的文件超过此值,则只分割前面 maxAsyncRequests个文件,超过的则不分割) 92 | maxAsyncRequests: 5, 93 | // 入口文件所引入的文件最多只分割三个出去,多出的不分割(例如入口文件引入了 10个文件,这10个文件都符合分割的规则,但maxInitialRequests的值为 3,则只有前 3 个引入的文件才会被分割,多出的不分割) 94 | maxInitialRequests: 3, 95 | // 如果 cacheGroups 的组没有显示指定 fileName参数,则被分割出去的文件的文件名由原文件名与 cacheGroups 组名称连接而成,这个连接符就是 automaticNameDelimiter的值,例如 `vendors~lodash.js` 96 | automaticNameDelimiter: '~', 97 | // 分割打包出来的文件的文件名由 cacheGroups 指定 98 | name: true, 99 | // 代码分割的规则 100 | cacheGroups: { 101 | // node_modules 文件下的代码按照此规则(vendors组)进行分割 102 | vendors: { 103 | test: /[\\/]node_modules[\\/]/, 104 | // 如果一个文件同时符合多个组的 test规则,则优先按照 priority值大的那个组 105 | // 例如 lodash同时满足 vendors 和 default,但vendors组的 priority更大,所以 lodash按照 vendors的规则进行分割 106 | priority: -10, 107 | // fileName: 'vendors' 108 | }, 109 | // 如果要分割的代码没有显式声明分割的规则(不符合 vendors等规则),则按照此 default规则进行分割 110 | default: { 111 | minChunks: 2, 112 | priority: -20, 113 | // 如果一个模块已经被打包过了,则忽略当前打包,直接复用之前已经被打包过的 114 | // 例如,index.js中引入了 a.js 和 b.js 两个文件,并且 a.js中也引入了 b.js,则在打包 a.js的时候就已经打包了 b.js,那么在 index.js中想要打包 b.js的时候发现 a.js中已经对 b.js打包了,所以就忽略这次打包,直接复用之前的 115 | reuseExistingChunk: true 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | ### bundle 分析(bundle analysis) 122 | 123 | >下述摘自 [webpack 官网 bundle-分析-bundle-analysis-](https://www.webpackjs.com/guides/code-splitting/#bundle-%E5%88%86%E6%9E%90-bundle-analysis-) 124 | 125 | 如果我们以分离代码作为开始,那么就以检查模块作为结束,分析输出结果是很有用处的。[官方分析工具](https://github.com/webpack/analyse) 是一个好的初始选择。下面是一些社区支持(`community-supported`)的可选工具: 126 | 127 | - [webpack-chart](https://alexkuz.github.io/webpack-chart/): webpack 数据交互饼图。 128 | - [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/): 可视化并分析你的 `bundle`,检查哪些模块占用空间,哪些可能是重复使用的。 129 | - [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer): 一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。 130 | 131 | ### Prefetching/Preloading modules 132 | 133 | 对于一些首页非必须立即加载执行的代码,可以设为异步加载(`import`),但是如果当只有使用的时候才去加载,可能会导致交互的卡顿,所以需要在页面加载完必须的首屏代码之后,在网络空闲时段将这些代码就下载下来,以保证使用的时候能够即时响应,这就需要借助 `webpack`的 `Prefetching/Preloading`能力 134 | 135 | `Prefetching`:只有当页面所必须的核心代码加载完毕后,才会加载 `prefecth`的代码,通常用于加载辅助类非必须资源 136 | `Preloading`:会与页面所必须的核心代码一同并行请求加载,通常用于加载关键 `js`、字体、`css`等 137 | 138 | ### Progressive Web Application(PWA) 139 | 140 | `webpack`能够让你很轻松地将项目构建成离线状态依旧可运行的,只需要借助一些简单的配置即可,详细参见:[渐进式网络应用程序](https://www.webpackjs.com/guides/progressive-web-application/#%E7%8E%B0%E5%9C%A8%E6%88%91%E4%BB%AC%E5%B9%B6%E6%B2%A1%E6%9C%89%E7%A6%BB%E7%BA%BF%E7%8E%AF%E5%A2%83%E4%B8%8B%E8%BF%90%E8%A1%8C%E8%BF%87) 141 | 142 | ### DllPlugin 143 | 144 | `DllPlugin` 和 `DllReferencePlugin` 用某种方法实现了拆分 `bundles`,同时还大大提升了构建的速度。主要思想在于,将一些不做修改的依赖文件(例如第三方库等),提前打包,这样我们开发代码发布的时候就不需要再对这部分代码进行打包,从而节省了打包时间。 145 | 146 | ## webpackchunkname 147 | 148 | 可以通过指定 `webpackchunkname` 来自定义文件打包后的名字 149 | 这里有个小技巧,如果两个文件使用了相同的 `webpackchunkname`,那么这两个文件就会被打包到同一个文件中,适用以下场景: 150 | 151 | `A`页面功能较为复杂,于是将其分成了 `c`、`d`、`e`三个模块,这三个模块都必须要在首页加载时立即加载,为了减少 `http`请求,就可以通过 `webpackchunkname` 将三个文件最终打包到一个文件中,只需要一次 `http`请求即可 152 | 153 | ## HappyPack/thread-loader 154 | 155 | `HappyPack` 可以将原有的 `webpack` 对 `loader` 的执行过程,从单一进程的形式扩展为多进程的模式,从而加速代码构建 156 | 157 | ```js 158 | const HappyPack = require('happypack'); 159 | const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 160 | 161 | // ... 162 | module: { 163 | loaders: [{ 164 | test: /\.less$/, 165 | loader: ExtractTextPlugin.extract( 166 | 'style', path.resolve(__dirname, './node_modules', 'happypack/loader') + '?id=less' 167 | ) 168 | }] 169 | }, 170 | plugins: [ 171 | new HappyPack({ 172 | id: 'less', 173 | loaders: ['css!less'], 174 | threadPool: happyThreadPool, 175 | cache: true, 176 | verbose: true 177 | }) 178 | ] 179 | ``` 180 | 181 | `Vue-loader` 不支持 `happypack`,可以使用 `thread-loader` 来进行加速 182 | ```js 183 | module: { 184 | rules: [{ 185 | test: /\.vue$/, 186 | use: [ 187 | 'thread-loader', 188 | 'vue-loader' 189 | ] 190 | }] 191 | } 192 | ``` 193 | 194 | >`v4` 版本中的 `UglifyjsWebpackPlugin` 已经可以通过 设置 `parallel`选项来自动开启多线程 195 | 196 | ## 缓存loader的执行结果(cacheDirectory/cache-loader) 197 | 198 | 两种方法缓存 `loader` 199 | 200 | - 直接配置 201 | 202 | ```js 203 | loader: 'babel-loader?cacheDirectory=true' 204 | ``` 205 | 206 | - [cache-loader](https://webpack.docschina.org/loaders/cache-loader/) 207 | 208 | 推荐这种方法 209 | 210 | 首先需要安装 211 | ```js 212 | npm install --save-dev cache-loader 213 | ``` 214 | 然后使用 215 | ```js 216 | rules: [{ 217 | test: /\.vue$/, 218 | use: [ 219 | 'cache-loader', 220 | 'vue-loader' 221 | ] 222 | }] 223 | ``` 224 | 225 | >Note: 保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 `loader` 使用此 `loader` 226 | 227 | ## splitChunks 228 | 229 | `v4`新增特性,用于取代之前的 `CommonsChunkPlugin`,`splitChunks` 的默认配置已经足够我们日常使用,没有特殊需求可以不必特意处理 230 | 231 | ```js 232 | optimization: { 233 | splitChunks: { 234 | cacheGroups: { 235 | commons: { 236 | test: /[\\/]node_modules[\\/]/, 237 | name: 'vendors', 238 | chunks: 'all' 239 | }, 240 | styles: { 241 | name: 'index', 242 | test: /.stylus|css$/, 243 | chunks: 'all', 244 | enforce: true 245 | } 246 | } 247 | } 248 | } 249 | ``` 250 | `commons` 部分的作用是分离出 `node_modules` 中引入的模块,`styles` 部分则是合并 CSS 文件 251 | 252 | ## 使用 DllPlugin拆分模块 253 | 254 | 开发过程中,我们经常需要引入大量第三方库,这些库并不需要随时修改或调试,我们可以使用 `DllPlugin`和 `DllReferencePlugin`单独构建它们 255 | 256 | ```js 257 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 258 | module.exports = { 259 | entry: { 260 | vendor: [ 261 | 'axios', 262 | 'vue-i18n', 263 | 'vue-router', 264 | 'vuex' 265 | ] 266 | }, 267 | output: { 268 | path: path.resolve(__dirname, '../static/'), 269 | filename: '[name].dll.js', 270 | library: '[name]_library' 271 | }, 272 | plugins: [ 273 | new webpack.DllPlugin({ 274 | path: path.join(__dirname, 'build', '[name]-manifest.json'), 275 | name: '[name]_library' 276 | }) 277 | ] 278 | } 279 | ``` 280 | 281 | 执行 `webpack`命令,`build`目录下即可生成 `dll.js` 文件和对应的 `manifest` 文件,使用 `DLLReferencePlugin` 引入 282 | 283 | ```js 284 | plugins: [ 285 | new webpack.DllReferencePlugin({ 286 | context: __dirname, 287 | manifest: require('./build/vendor-manifest.json') 288 | }) 289 | ] 290 | ``` 291 | 292 | ## sideEffects 293 | 294 | >来源 [Webpack 中的 sideEffects 到底该怎么用?](https://zhuanlan.zhihu.com/p/40052192) 295 | 296 | `webpack v4` 开始新增了一个 `sideEffects` 特性,通过给 `package.json` 加入 `sideEffects`声明该 包/模块 是否包含 `sideEffects(副作用)`,从而可以为 `tree-shaking` 提供更大的优化空间。 297 | 298 | _注:v4 beta 版时叫 `pure module`, 后来改成了 `sideEffects`_ 299 | 300 | ```js 301 | // package.json 302 | { 303 | "name": "your-project", 304 | "sideEffects": false 305 | } 306 | ``` 307 | 308 | **_结论_:只要你的包不是用来做 `polyfill` 或 `shim` 之类的事情,就尽管放心的给它加上 `sideEffects: false` 吧!** 309 | 310 | ## `preload-webpack-plugin` 311 | 312 | 给外联资源加上 `preload`标识: 313 | ```js 314 | const preloadWebpackPlugin = require('preload-webpack-plugin') 315 | ... 316 | 317 | // webpack配置 318 | plugins: [ 319 | new htmlWebpackPlugin(), 320 | new preloadWebpackPlugin({ 321 | as(entry) { 322 | if (/\.woff2$/.test(entry)) return 'font'; 323 | return 'script'; 324 | }, 325 | include: 'allAssets', 326 | rel: 'preload', 327 | fileWhitelist: [/\.woff2/] 328 | }) 329 | ] 330 | ``` 331 | 332 | 编译出的字体标签链接为: 333 | ```html 334 | <link as="font" crossorigin href='/dist/static/font.woff2' rel="preload" /> 335 | ``` 336 | 337 | ## 全局组件注册 338 | 339 | 对于如下结构: 340 | ``` 341 | components 342 | │ componentRegister.js 343 | ├─BasicTable 344 | │ BasicTable.vue 345 | ├─MultiCondition 346 | │ index.vue 347 | ``` 348 | 349 | ```js 350 | import Vue from 'vue' 351 | 352 | /** 353 | * 首字母大写 354 | * @param str 字符串 355 | * @example heheHaha 356 | * @return {string} HeheHaha 357 | */ 358 | function capitalizeFirstLetter(str) { 359 | return str.charAt(0).toUpperCase() + str.slice(1) 360 | } 361 | 362 | /** 363 | * 对符合'xx/xx.vue'组件格式的组件取组件名 364 | * @param str fileName 365 | * @example abc/bcd/def/basicTable.vue 366 | * @return {string} BasicTable 367 | */ 368 | function validateFileName(str) { 369 | return /^\S+\.vue$/.test(str) && 370 | str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => capitalizeFirstLetter($1)) 371 | } 372 | 373 | const requireComponent = require.context('./', true, /\.vue$/) 374 | 375 | // 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名 376 | requireComponent.keys().forEach(filePath => { 377 | const componentConfig = requireComponent(filePath) 378 | const fileName = validateFileName(filePath) 379 | const componentName = fileName.toLowerCase() === 'index' 380 | ? capitalizeFirstLetter(componentConfig.default.name) 381 | : fileName 382 | Vue.component(componentName, componentConfig.default || componentConfig) 383 | }) 384 | ``` 385 | -------------------------------------------------------------------------------- /getDirStruct.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用于生成仓库目录的层级结构 3 | */ 4 | const path = require('path') 5 | const fs = require('fs') 6 | 7 | // 文件名在下面数组中项的都排除掉 8 | const excludeFile = ['img', 'LICENSE', 'README.md', path.basename(__filename)] 9 | // 文件名以下面数组中项为前缀开头的都排除掉 10 | const excludePrefix = ['.'] 11 | // 所有文件的绝对全路径缓存数组 12 | const absolutePath = [] 13 | 14 | // 获取所有文件路径 15 | ;(function getDirStruct(basePath = __dirname) { 16 | const files = fs.readdirSync(basePath) 17 | // 空文件夹处理 18 | if (files.length === 0) { 19 | return absolutePath.push(basePath) 20 | } 21 | files.forEach(file => { 22 | // 排除掉不想显示的文件 23 | if (excludeFile.indexOf(file) !== -1 || excludePrefix.some(pre => file.indexOf(pre) === 0)) return 24 | const fullPath = path.resolve(basePath, file) 25 | const fileStats = fs.statSync(fullPath) 26 | // 如果是文件夹,则继续遍历其子文件 27 | return fileStats.isDirectory() ? getDirStruct(fullPath) : absolutePath.push(fullPath) 28 | }) 29 | })() 30 | // 文件的相对路径数组,用于拼接 url地址 31 | const isWin = path.sep.indexOf('\\') !== -1 32 | let relativePath = absolutePath.map(apath => { 33 | // 得到相对路径 34 | const rPath = path.relative(__dirname, apath) 35 | // 不同系统平台的分隔符处理 36 | return isWin ? rPath.replace(/\\/g, '/') : rPath 37 | }) 38 | 39 | // 层级结构 40 | const structs = {} 41 | relativePath.forEach(filePath => { 42 | // 格式化路径为数组 43 | const fileArrs = filePath.split('/') 44 | let currentProp = eval('structs' + fileArrs.slice(0, -1).reduce((t, c) => { 45 | if (!eval('structs' + t + `['${c}']`)) { 46 | eval('structs' + t + `['${c}']` + '= {}') 47 | } 48 | return t + `['${c}']` 49 | }, '')) 50 | if (currentProp._children) { 51 | currentProp._children.push(fileArrs.slice(-1)[0]) 52 | } else { 53 | currentProp._children = fileArrs.slice(-1) 54 | } 55 | }) 56 | 57 | // README.md 中的内容 58 | let readmeContent = ` 59 | # DayNote 60 | 61 | 读书笔记,记录学习过程遇到的任何知识点 62 | 63 | 此仓库会频繁更新,推荐 \`star\` 或 \`watch\`以即时得到更新通知 64 | 65 | --- 66 | 67 | 目录结构 68 | 69 | ` 70 | // 整理输出结构 71 | ;(function formatLink(obj = structs, basePath = '', level = 1) { 72 | Object.keys(obj).forEach(k => { 73 | if (k === '_children') { 74 | // 这个是针对根目录下存在的独立文件 75 | return readmeContent += obj[k].reduce((t, c) => t + `- [${c}](/${c})\n`, '') 76 | } 77 | readmeContent += ('\t'.repeat(level - 1) + `- [${k}](${basePath}/${k})\n`) 78 | // 如果存在子层级,则遍历子层级 79 | if (obj[k]._children) { 80 | readmeContent += obj[k]._children.reduce((t, c) => { 81 | return t + '\t'.repeat(level) + `- [${c}](${basePath}/${k}/${c})\n` 82 | }, '') 83 | } 84 | const objKeys = Object.keys(obj[k]) 85 | // 如果子层级存在并且不止一个,或者子层级只有一个但属性名不是 _children 86 | if (objKeys.length > 1 || (objKeys.length && objKeys[0] !== '_children')) { 87 | const tempObj = {} 88 | objKeys.filter(d1 => d1 !== '_children').forEach(d2 => { 89 | tempObj[d2] = obj[k][d2] 90 | }) 91 | return formatLink(tempObj, `${basePath}/${k}`, level + 1) 92 | } 93 | }) 94 | })() 95 | 96 | // 保存 README.md 97 | fs.writeFile(path.resolve(__dirname, 'README.md'), readmeContent, () => { 98 | console.log('done') 99 | }) 100 | -------------------------------------------------------------------------------- /img/200px-Telephone-keypad2.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/200px-Telephone-keypad2.svg.png -------------------------------------------------------------------------------- /img/aframe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/aframe.gif -------------------------------------------------------------------------------- /img/appinn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/appinn.png -------------------------------------------------------------------------------- /img/awesomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/awesomplete.gif -------------------------------------------------------------------------------- /img/caniuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/caniuse.png -------------------------------------------------------------------------------- /img/chinaz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/chinaz.png -------------------------------------------------------------------------------- /img/cleavejs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/cleavejs.gif -------------------------------------------------------------------------------- /img/csspin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/csspin.gif -------------------------------------------------------------------------------- /img/dragdealer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/dragdealer.gif -------------------------------------------------------------------------------- /img/dummyimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/dummyimage.png -------------------------------------------------------------------------------- /img/dynamicsjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/dynamicsjs.gif -------------------------------------------------------------------------------- /img/emojipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/emojipedia.png -------------------------------------------------------------------------------- /img/iconfont.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/iconfont.jpg -------------------------------------------------------------------------------- /img/iconmoon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/iconmoon.png -------------------------------------------------------------------------------- /img/ios-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/ios-app-icon.png -------------------------------------------------------------------------------- /img/jsbin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/jsbin.png -------------------------------------------------------------------------------- /img/makeappicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/makeappicon.png -------------------------------------------------------------------------------- /img/mathjax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/mathjax.png -------------------------------------------------------------------------------- /img/notie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/notie.gif -------------------------------------------------------------------------------- /img/onepage-scroll.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/onepage-scroll.gif -------------------------------------------------------------------------------- /img/oschina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/oschina.png -------------------------------------------------------------------------------- /img/parallax.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/parallax.gif -------------------------------------------------------------------------------- /img/pgyer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/pgyer.png -------------------------------------------------------------------------------- /img/picker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/picker.gif -------------------------------------------------------------------------------- /img/pushjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/pushjs.gif -------------------------------------------------------------------------------- /img/react-sortable-hoc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/react-sortable-hoc.gif -------------------------------------------------------------------------------- /img/regexper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/regexper.png -------------------------------------------------------------------------------- /img/reveal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/reveal.png -------------------------------------------------------------------------------- /img/scroll-over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/scroll-over.gif -------------------------------------------------------------------------------- /img/scrollrevealjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/scrollrevealjs.gif -------------------------------------------------------------------------------- /img/sigma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/sigma.png -------------------------------------------------------------------------------- /img/sitepointstatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/sitepointstatic.png -------------------------------------------------------------------------------- /img/slick.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/slick.gif -------------------------------------------------------------------------------- /img/trackingjs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/trackingjs.gif -------------------------------------------------------------------------------- /img/typeahead.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/typeahead.gif -------------------------------------------------------------------------------- /img/video-to-gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/video-to-gif.png -------------------------------------------------------------------------------- /img/videojs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/videojs.png -------------------------------------------------------------------------------- /img/vivus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/vivus.gif -------------------------------------------------------------------------------- /img/waypoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/waypoint.gif -------------------------------------------------------------------------------- /img/zhitu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accforgit/DayLearnNote/62d7d2845a21da11af94d53c267b358d0ab3b132/img/zhitu.png -------------------------------------------------------------------------------- /实用代码段/代码段(3).md: -------------------------------------------------------------------------------- 1 | ### async/await 并发执行 2 | `async/await`并不一定必须单个顺序执行,配合 `Promise.all`可以并发: 3 | ```js 4 | function sleep(timer = 1000) { 5 | return new Promise(resolve => { 6 | setTimeout(()=>{ resolve(timer) }, timer) 7 | }) 8 | } 9 | async function fn1() { 10 | const arr = [1, 2, 3, 4, 5, 6] 11 | // 并行执行所有的 12 | const down = await Promise.all(arr.map(i => sleep(i * 1000))) 13 | console.log('down:', down) 14 | } 15 | 16 | fn1() 17 | ``` 18 | 19 | ### 自定义滚动条相关的七个伪元素 20 | 21 | - `::-webkit-scrollbar`:整个滚动条 22 | - `::-webkit-scrollbar-button`:滚动条上的按钮(下下箭头) 23 | - `::-webkit-scrollbar-thumb`:滚动条上的滚动滑块 24 | - `::-webkit-scrollbar-track`:滚动条轨道 25 | - `::-webkit-scrollbar-track-piece`:滚动条没有滑块的轨道部分 26 | - `::-webkit-scrollbar-corner`:当同时有垂直和水平滚动条时交汇的部分 27 | - `::-webkit-resizer`:某些元素的交汇部分的部分样式(类似 `textarea`的可拖动按钮) 28 | 29 | ### 16进制颜色代码生成 30 | 31 | ```js 32 | function createColor() { 33 | return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6) 34 | } 35 | ``` 36 | 37 | ### 驼峰命名转下划线 38 | 39 | ```js 40 | function camel2Underline(name) { 41 | return name.match(/[a-z][0-9]+|[A-Z][a-z0-9]*/g).join('_').toLowerCase() 42 | } 43 | ``` 44 | 45 | ### 日期格式化 46 | 47 | ```js 48 | // 版本1 49 | function format1(x, y) { 50 | var z = { 51 | y: x.getFullYear(), 52 | M: x.getMonth() + 1, 53 | d: x.getDate(), 54 | h: x.getHours(), 55 | m: x.getMinutes(), 56 | s: x.getSeconds() 57 | } 58 | return y.replace(/(y+|M+|d+|h+|m+|s+)/g, function(v) { 59 | return ((v.length > 1 ? '0' : '') + z[v.slice(-1)]).slice(-(v.length > 2 ? v.length : 2)) 60 | }) 61 | } 62 | // 版本2 63 | Date.prototype.format2 = function(fmt) { 64 | var o = { 65 | "M+": this.getMonth() + 1, //月份 66 | "d+": this.getDate(), //日 67 | "h+": this.getHours(), //小时 68 | "m+": this.getMinutes(), //分 69 | "s+": this.getSeconds(), //秒 70 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度 71 | "S": this.getMilliseconds() //毫秒 72 | } 73 | if (/(y+)/.test(fmt)) { 74 | // 这里 RegExp的用法挺有意思的 75 | fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) 76 | } 77 | for (var k in o) { 78 | if (new RegExp('(' + k + ')').test(fmt)) { 79 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 80 | } 81 | } 82 | return fmt 83 | } 84 | 85 | // 使用 86 | format1(new Date(), 'yy-MM-dd hh:m:ss') // 18-08-23 11:46:11 87 | new Date().format2('yyyy-M-d h:m:s') // 2018-8-23 11:46:11 88 | ``` 89 | 90 | ### 数字四舍五入 91 | 92 | ```js 93 | // v: 值, p: 精度 94 | function round(v, p) { 95 | p = Math.pow(10, p >>> 31 ? 0 : p | 0) 96 | v *= p 97 | return (v + 0.5 + (v >> 31) | 0) / p 98 | } 99 | // 使用 100 | round(123.456788, 2) // 123.46 101 | ``` 102 | 103 | ### 浏览器原生 `Base64`编码和解码方法 104 | 105 | 支持度为 `IE10+`,其他主流浏览器完全支持 106 | 107 | 编码(`btoa`): 108 | ```js 109 | window.btoa(stringToEncode) 110 | ``` 111 | 112 | 解码(`atob`): 113 | ```js 114 | // encodedData 代表 base64字符串 115 | window.atob(encodedData) 116 | ``` 117 | 118 | 对中文进行编码会报错,可以先将中文转码,再进行编码,解码同样如此: 119 | ```js 120 | window.btoa(window.encodeURIComponent('嘻嘻哈哈')) 121 | window.atob(window.decodeURIComponent('JUU1JTk4JUJCJUU1JTk4JUJCJUU1JTkzJTg4JUU1JTkzJTg4')) 122 | ``` 123 | 124 | 如果 `IE9-`浏览器需要此功能,可使用 [polyfill](https://github.com/davidchambers/Base64.js/blob/master/base64.js), 125 | 然后像下面这样引入即可: 126 | ```js 127 | <!--[if IE]> 128 | <script src="./base64-polyfill.js"></script> 129 | <![endif]--> 130 | ``` 131 | 132 | 任意文件也可以使用此 `api`进行编码和解码,使用 `FileReader`: 133 | ```js 134 | var reader = new FileReader(); 135 | reader.onload = function(e) { 136 | // e.target.result就是该文件的完整Base64 Data-URI 137 | }; 138 | reader.readAsDataURL(file); 139 | ``` 140 | ## 获取当前时区 141 | 142 | 可以通过 `new Date().getTimezoneOffset()`获取到当前时区与 `0`时区的时间差(单位是分钟) 143 | 例如在东八区,值为 `-480`,也就是 `8`小时,就是东八区 144 | 145 | ## compose函数的实现 146 | 147 | ```js 148 | function compose(...funcs) { 149 | if (funcs.length === 0) return arg => arg 150 | if (funcs.length === 1) return funcs[0] 151 | return funcs.reduce((a, b) => (...args) => a(b(...args))) 152 | } 153 | // 使用 154 | const a = x => x + 1.2 155 | const b = x => x * 10 156 | const c = (x, y) => x + y 157 | // 从右向左执行 158 | compose(a, b, c)(2, 3) // => 51.2 159 | ``` 160 | 161 | ## 模拟实现 new关键字 162 | 163 | ```js 164 | function myNew() { 165 | const obj = Object.create(null) 166 | const Con = [].shift.call(arguments) 167 | obj.__proto__ = Constructor.prototpe 168 | const result = Con.apply(obj, arguments) 169 | return typeof result === 'object' ? result : obj 170 | } 171 | ``` 172 | 173 | ## JavaScript获取图片的原始尺寸 174 | 175 | - 直接获取宽高属性值 176 | 177 | 适用于 **没有设置宽高样式或标签宽高属性**的 `img`标签 178 | 179 | ```js 180 | const img = document.querySelector('.imgEle') 181 | console.log(img.width, img.height) 182 | ``` 183 | 184 | - 通过中间 `img`标签 185 | 186 | 如果 目标`img`元素设置了宽高样式或标签上设置了宽高属性,则上述方法获取到尺寸就是设置的尺寸,而不一定是原始尺寸: 187 | ```html 188 | <img width="100" height="100" src="https://dummyimage.com/200x200/fafff0" /> 189 | <img class="imgEle" src="https://dummyimage.com/200x200/fafff0" /> 190 | <style> 191 | .imgEle { 192 | width: 200px; 193 | height: 200px; 194 | } 195 | </style> 196 | ``` 197 | 198 | 这时就需要通过一个新建的中间 `img`标签来测量目标图片的原始尺寸,直接创建一个新img对象,然后把旧img的src赋值给新的,这时候获取新img的宽度即可: 199 | 200 | ```js 201 | const img = document.querySelector('imgEle') 202 | const image = new Image() 203 | image.src = img.src 204 | console.log(img.width, img.height) 205 | ``` 206 | 207 | - HTML5新增方法 208 | 209 | `HTML5` 提供了一个新属性`naturalWidth/naturalHeight`可以直接获取图片的原始宽高: 210 | ```js 211 | const img = document.querySelector('imgEle') 212 | img.src = 'https://pic5.zhuanstatic.com/zhuanzh/n_v20a70b48b1e2b4858963e014f42c6e226.png' 213 | img.onload = () => { 214 | console.log(img.naturalWidth, img.naturalHeight) 215 | } 216 | ``` 217 | `naturalWidth/naturalHeight`的兼容性很好,`IE8+`即可,完全可以用于实际生产环境 218 | 219 | - 颜色表示,hsl 转 rgb 220 | 221 | ```js 222 | function hslToRgb(hue, sat, light) { 223 | if( light <= .5 ) { 224 | var t2 = light * (sat + 1); 225 | } else { 226 | var t2 = light + sat - (light * sat); 227 | } 228 | var t1 = light * 2 - t2; 229 | var r = hueToRgb(t1, t2, hue + 2); 230 | var g = hueToRgb(t1, t2, hue); 231 | var b = hueToRgb(t1, t2, hue - 2); 232 | return [r,g,b]; 233 | } 234 | 235 | function hueToRgb(t1, t2, hue) { 236 | if(hue < 0) hue += 6; 237 | if(hue >= 6) hue -= 6; 238 | 239 | if(hue < 1) return (t2 - t1) * hue + t1; 240 | else if(hue < 3) return t2; 241 | else if(hue < 4) return (t2 - t1) * (4 - hue) + t1; 242 | else return t1; 243 | } 244 | ``` 245 | -------------------------------------------------------------------------------- /实用工具/github上实用、轻量级、无依赖的插件和库(2).md: -------------------------------------------------------------------------------- 1 | ## reveal.js – 使用前端代码打造精美 ppt文稿 2 | github:https://github.com/hakimel/reveal.js 3 | 4 | 官方网站:https://slides.com/ 5 | 6 | star: 37k+ 7 | 8 | 功能介绍: 9 | >使用前端代码打造精美 ppt文稿 10 | 11 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/reveal.png) 12 | 13 | --- 14 | 15 | ## ace – 用JavaScript编写的独立代码编辑器 16 | github:https://github.com/ajaxorg/ace 17 | 18 | 官方网站:https://ace.c9.io/ 19 | 20 | star: 37k+ 21 | 22 | 功能介绍: 23 | >Ace的目标是创建一个基于浏览器的编辑器,匹配和扩展现有的本地编辑器(如TextMate,Vim或Eclipse)的功能、可用性和性能。 它可以轻松地嵌入任何网页或JavaScript应用程序。 Ace被开发为Cloud9 IDE的主要编辑器。 24 | 25 | ## react-ace 26 | github: https://github.com/securingsincity/react-ace 27 | 28 | 官方网站: http://securingsincity.github.io/react-ace/ 29 | 30 | star:800+ 31 | 32 | 功能介绍: 33 | >ace 的 react 版本 34 | 35 | 36 | --- 37 | 38 | ## sigma – 专用于图形绘制的JavaScript库 39 | github:https://github.com/jacomyal/sigma.js 40 | 41 | 官方网站:http://sigmajs.org/ 42 | 43 | star: 7.5k+ 44 | 45 | 功能介绍: 46 | >专用于图形绘制的JavaScript库 47 | 48 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/sigma.png) 49 | 50 | --- 51 | 52 | ## babel-plugin-inline-react-svg - 让 React应用支持 inline-svg的 babel插件 53 | github:https://github.com/kesne/babel-plugin-inline-react-svg 54 | 55 | star:80k+ 56 | 57 | 功能介绍: 58 | >如果你在 react项目中使用了 babel的话,那么此插件可以给你的 react项目提供 inline-svg的功能 59 | 60 | ## json-server - 30s内快速伪造 REST API 61 | github:https://github.com/typicode/json-server 62 | 63 | star:26k+ 64 | 65 | 功能介绍: 66 | >只需要编写 json文件,即可快速伪造出需要的 REST API 67 | 68 | ## cookie.js - 操作cookie 69 | 70 | github: https://github.com/florian/cookie.js/blob/master/cookie.js 71 | 72 | star: 1k+ 73 | 74 | 功能介绍: 75 | >易于操作cookie 76 | 77 | ## store.js - 跨浏览器、兼容低版本(ie6+)浏览器 78 | 79 | github: https://github.com/marcuswestin/store.js 80 | 81 | star: 10k+ 82 | 83 | 功能介绍: 84 | >兼容PC、移动端各大浏览器,兼容低版本浏览器(IE6+) 85 | 86 | ## gcoord - 兼容不同标准下的坐标系精确度 87 | 88 | 不同电子地图由于可能使用了不同的坐标系标准,例如百度地图使用 `BD-09`标准,高德地图使用 `GCL-02`标准, 89 | 所以同一个坐标在不同的电子地图上可能对应不同的位置,存在几十到几百米的偏移, 90 | 此库用于消除这些偏移 91 | 92 | github: https://github.com/hujiulong/gcoord 93 | 94 | star: 500+ 95 | 96 | 97 | -------------------------------------------------------------------------------- /实用工具/常用的工具、网站.md: -------------------------------------------------------------------------------- 1 | ## regexper 2 | >在线测试正则表达式代表的含义 3 | 4 | 官网: https://regexper.com/ 5 | 6 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/regexper.png) 7 | 8 | 9 | ## oschina 10 | >各种常见在线工具网站 11 | >例如 在线 js压缩、合并、JSON格式化、jQuery文档、RGB颜色参考、代码对比、Less编译、图片转 Base64、加密、进制转换、二维码生成等。 12 | 13 | 官网: http://tool.oschina.net/ 14 | 15 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/oschina.png) 16 | 17 | ## 网页实时编辑预览 18 | >在线HTML CSS JS Console Output 动态实时编辑工具 19 | >此类网站有 [jsbin.com](http://jsbin.com/?html,output)、[codepen.io](http://codepen.io/)、[jsfiddle](http://jsfiddle.net/)等 20 | 21 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/jsbin.png) 22 | 23 | ## caniuse 24 | >在线查询各类主流浏览器对html5、css3等新技术的支持程度 25 | 26 | 官网:http://caniuse.com/ 27 | 28 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/caniuse.png) 29 | 30 | ## video-to-gif 31 | >对GIF文件进行各种操作,包括调整大小、剪辑,以及将各种格式的视频文件在线转换成GIF 32 | 33 | 官网:https://ezgif.com/video-to-gif 34 | 35 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/video-to-gif.png) 36 | 37 | ## 自动合成雪碧图并生成对应的 `CSS`样式 38 | 39 | https://www.toptal.com/developers/css/sprite-generator 40 | 41 | ## iconmoon 42 | >制作图标字体,将SVG转换成字体文件 43 | 44 | 官网:https://icomoon.io/ 45 | 46 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/iconmoon.png) 47 | 48 | ## cloudinary 49 | >国外图床,除了一般图床功能之外,还带有混合视频与视频以及视频与音频,甚至为视频去除声音的功能 cloudinary.com 50 | 51 | 官网:http://cloudinary.com/ 52 | 53 | ## ios-app-icon 54 | >可以下载到各种 app图标的PSD文件,定制化程度比较高,并且对于IOS支持较好,但是需要具备一定的PS能力 55 | 56 | 官网:https://applypixels.com/template/ios-app-icon/ 57 | 58 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/ios-app-icon.png) 59 | 60 | ## APP分发测试 61 | >提供足够多的机型资源来内测 app应用 62 | 63 | 官网:https://www.pgyer.com/ 64 | 65 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/pgyer.png) 66 | 67 | ## makeappicon 68 | >在线上传、下载、调整 app图标,一次性导出适配各种平台的图标,方便快捷(收费) 69 | 70 | 官网:https://makeappicon.com/ 71 | 72 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/makeappicon.png) 73 | 74 | 75 | ## dummyimage 76 | >直接根据链接请求生成需要的图片 77 | 78 | 官网:http://dummyimage.com/ 79 | 80 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/dummyimage.png) 81 | 82 | ## 阿里icon图标 iconfont.cn 83 | 84 | >导出 Svg、AI、Png格式的图片文件,与之类似的图标网站还有 http://fontawesome.io/ http://www.easyicon.net/ 等 85 | 86 | 官网:http://www.iconfont.cn/ 87 | 88 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/iconfont.jpg) 89 | 90 | ## 站长工具 91 | 92 | >提供 IP查询、DNS查询、Alexa排名查询、robots.txt生成、机器人模拟抓取、网站GZIP压缩、SEO综合查询、SEO优化建议、关键字排名查询、 93 | 百度权重查询、加密解密、配色工具等。等 94 | 95 | 官网:http://tool.chinaz.com/ 96 | 97 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/chinaz.png) 98 | 99 | 100 | ## [YSlow](http://yslow.org/) 101 | 102 | >统计网站加载的JS CSS 图片等文件体积,分析网站加载速度的工具,可以直接在 Chrome FireFox等浏览器上直接安装此插件 103 | 104 | 官网:http://yslow.org/ 105 | 106 | 107 | ## CSS3 transform To IE filter 108 | >将适用于现代浏览器的CSS3 transform 在线转换成适用于IE的矩阵滤镜工具 IE’s CSS3 Transforms Translator 109 | 110 | 官网:http://www.useragentman.com/IETransformsTranslator/ 111 | 112 | 113 | ## 字符表情`Emoji` 114 | 115 | - 获取 Emoji 表情的 HTML实体:https://mothereff.in/html-entities, 116 | 使用手机进入此网站,在输入框输入 emoji表情,可实时输出对应的HTML实体. 117 | - 获取 Emoji 表情:http://emojipedia.org/apple/, 118 | 此网站可以看到每个系统的高清表情图片,每一个子表情页面,可以看到每个手机系统不同的设计,还有表情的最新unicode编码。 119 | 120 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/emojipedia.png) 121 | 122 | 123 | ## 智库 124 | >腾讯ISUX前端团队开发的一个专门用于图片压缩和图片格式转换的平台,其功能包括针对png,jpeg,gif等各类格式图片的压缩, 125 | 以及为上传图片自动选择最优的图片格式。同时,智图平台还会为用户转换一份webp格式的图片 126 | 127 | 官网:https://zhitu.isux.us/ 128 | 129 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/zhitu.png) 130 | 131 | ## 三次贝塞尔曲线在线绘制 132 | >能够根据曲线自动输出 canvas路径参数 133 | 134 | 官网:http://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html 135 | 136 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/sitepointstatic.png) 137 | 138 | ## 高清图库 pixabay 139 | >免费自由公开的高清图片、插画、矢量图、短视频网站,具备对外开放的 [API](https://pixabay.com/api/docs/) 140 | 141 | 官网:https://pixabay.com/ 142 | 143 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/sitepointstatic.png) 144 | 145 | ## builtwith 146 | >查看某个网站运用到了哪些技术,例如后端语言、后端框架、前端框架、前端库、域名解析服务商、调用了哪些 API等 147 | 148 | 官网:https://builtwith.com/ 149 | 150 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/sitepointstatic.png) 151 | 152 | ## 玉兔 153 | >腾讯出品的 H5制作、预览工具,有模板和各种特效,像做PPT一样创建H5 154 | 155 | 官网:http://yutu.qq.com/ 156 | 157 | ## 绿色小巧的录屏工具(支持导出 gif) 158 | 159 | - [LICEcap](https://www.cockos.com/licecap/) 快速录制,安装包不到 500KB,支持拖动选取一个合适的大小范围 160 | - GifCam 相比于上一个,GifCam 功能更加丰富,录制过程可调整窗口大小/位置,可以暂停/继续,录制范围内无变化则不增加新帧而只增加延时, 161 | 有效较小文件尺寸,可手动录制单帧,编辑状态可查看每一帧,下可以删除帧、增加当前帧延时,解压后大小 1.5MB 162 | - ScreenToGif 相比于上面两个,功能更加齐全强大,所以安装包稍大,但也只有 9MB,目前好像没有 Mac版 163 | 164 | ## 小众软件 165 | >分享免费、小巧、实用、有趣、绿色的软件,上面几个录屏软件就是来源于此。 166 | 167 | 官网:http://www.appinn.com/ 168 | 169 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/appinn.png) 170 | 171 | ## TeamViewer 172 | 远程控制软件,`qq`桌面版也有远程控制的功能,但是那效果太惨不忍睹,用起来简直就是折磨人,此款软件是收费软件,但是如果是个人用户使用则免费,比较是专业软件,使用起来流畅程度甩 `qq`几条街,并且完美缩放对方屏幕大小,推荐使用。 173 | 174 | 官网:https://www.teamviewer.com/zhcn/ 175 | 176 | ## 在线 mardown 编辑器 177 | 178 | - [cvbox](http://cv.ftqq.com/) 179 | 主要是针对在线简历编写,提供一些简历相关段落(例如 联系方式、经历证明、个人信息等)的快捷输入 180 | 支持即时预览,支持导出 `pdf` 181 | - [有道云笔记Web版](https://note.youdao.com/web) 182 | 支持即时预览,支持导出 `pdf` 183 | 184 | 185 | ## 在 `MarkDown`中写数学公式 186 | `MarkDown`支持 `LaTex`语法,但是这语法可能有点复杂,懒得学,然而又想在 `MarkDown`中写数学公式,此 [网站](https://webdemo.myscript.com/views/math.html)提供在浏览器中直接手写数学公式,然后将其转化为包括 `LaTex`、`MathML`、`SymbolTree`在内的语法,然后在 `MarkDown`文件中引入下面这段 `js`(有的在线 `md`编辑器可能已经帮你引好了,这用的是[MathJax引擎](https://www.mathjax.org/)),接着直接将转化好的公式复制到 `md`文件中即可~ 187 | 188 | ``` 189 | <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script> 190 | ``` 191 | 192 | 官网:https://webdemo.myscript.com/views/math.html 193 | 194 | ![showpicture](https://github.com/accforgit/DayLearnNote/blob/master/img/mathjax.png) 195 | 196 | -------------------------------------------------------------------------------- /常用技巧/前端技巧(1).md: -------------------------------------------------------------------------------- 1 | ## 利用委托冒泡,检测`ul`下被点击的`li`标签 2 | 3 | ```js 4 | window.onload = function(){ 5 | var oUl = document.getElementById("ul"); 6 | var aLi = oUl.getElementsByTagName("li"); 7 | oUl.onclick = function(ev){ 8 | var ev = ev || window.event; 9 | var target = ev.target || ev.srcElement; 10 | if(target.nodeName.toLowerCase() == "li"){ 11 | var that=target; 12 | var index; 13 | for(var i=0;i<aLi.length;i++){ 14 | if(aLi[i]===target){ 15 | index=i; 16 | } 17 | } 18 | if(index>=0){ 19 | alert('我的下标是:'+index); 20 | } 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | ## 直接把函数赋值给变量 27 | 28 | 凡是使用 `return` 返回函数调用的(也就是回调),都可以去掉这个间接包裹层,最终连参数和括号一起去掉 29 | 30 | ```js 31 | const getServerStuff = function(callback) { 32 | return ajaxCall(function(json) { 33 | return callback(json) 34 | }) 35 | } 36 | // 上述代码等价于下面这句: 37 | const getServerStuff = function(callback) { 38 | return ajaxCall(callback) 39 | } 40 | // 上述代码等价于下面这句: 41 | const getServerStuff = ajaxCall 42 | ``` 43 | 44 | ## 遍历对象 45 | 46 | 当使用 `for in` 循环遍历对象的属性时,原型链上的所有属性都将被访问,同时如果遍历的是数组,遍历顺序有可能不是按照实际数组的内部顺序,因此如果遍历数组,尽可能使用传统的 `for` 循环 47 | 48 | ## `hasOwnProperty` 49 | 50 | 当检查对象上某个属性是否存在时,`hasOwnProperty` 是唯一可用的方法。 同时在使用 `for in`遍历对象时,推荐总是使用 `hasOwnProperty` 方法,这将会避免原型对象扩展带来的干扰。 51 | 52 | ## 创建数组 53 | 54 | 应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。 55 | 56 | ## 检测对象的类型 57 | 58 | 为了检测一个对象的类型,强烈推荐使用 `Object.prototype.toString` 方法; 因为这是唯一一个可依赖的方式。 59 | 60 | ## instanceof 61 | 62 | instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如 typeof 操作符一样,任何其它的用法都应该是避免的。 63 | 64 | ## 手工清空定时器 65 | 66 | ```js 67 | // 每个setTimeout 都会返回一个 ID 标识 68 | var id = setTimeout(foo, 1000); 69 | clearTimeout(id); 70 | 71 | // 清空"所有"的定时器 72 | for(var i = 1; i < 1000; i++) { 73 | clearTimeout(i); 74 | } 75 | ``` 76 | 77 | ## 优化选择器 78 | 79 | - 选择器以 ID 开始总是最好的。 80 | 81 | ```js 82 | // 快 83 | $('#container div.robotarm'); 84 | 85 | // 非常快 86 | $('#container').find('div.robotarm'); 87 | 88 | // 使用 $.fn.find 的方式会更快是因为在第一步里只有 ID, 根本没用到快速选择器引擎,而是使用了浏览器内置的极速的 document.getElementById()。 89 | ``` 90 | 91 | - 选择器的右侧部分应该尽可能具体,左侧则不需要那么具体 92 | 93 | ```js 94 | / 未优化的 95 | $('div.data .gonzalez'); 96 | 97 | // 优化过的 98 | $('.data td.gonzalez'); 99 | ``` 100 | 101 | - 避免使用无定向选择器 102 | 103 | ```js 104 | $('.buttons > *'); // 极慢 105 | $('.buttons').children(); // 快很多 106 | 107 | $('.gender :radio'); // 暗示这是一个无定向的搜索 108 | $('.gender *:radio'); // 同上,这只不过是说出来了 109 | $('.gender input:radio'); // 这就差不多了 110 | ``` 111 | 112 | - 使用事件委派 113 | 114 | ```js 115 | // 如果列表里面元素很多,不堪想象 116 | $('li.trigger').click(handlerFn); 117 | 118 | // 这样写好一点,用 $.fn.live 做事件委派 119 | $('li.trigger').live('click', handlerFn); 120 | 121 | // 最好的做法,用 $.fn.delegate 做事件委派,还可以指定一个上下文 122 | $('#myList').delegate('li.trigger', 'click', handlerFn); 123 | ``` 124 | 125 | ## 先将元素 detach 出来再操作 126 | 127 | DOM 操作是慢的,你应该尽量避免去操作它。 128 | jQuery 在 1.4 版引入了 $.fn.detach 来帮助解决这个问题:让你可以先将元素从 DOM 树中剥离出来再进行操作。 129 | 130 | ```js 131 | var $table = $('#myTable'); 132 | var $parent = $table.parent(); 133 | 134 | $table.detach(); 135 | // ... 添加大量的行到表格中去 136 | $parent.append(table); 137 | ``` 138 | 139 | ## 应该使用样式表给大量元素修改 CSS 140 | 141 | 如果你在用 $.fn.css 给多于 20 个元素修改 CSS,考虑一下添加一个 style 标签, 这样可以速度可以提升 60%。 142 | 操作大于 20 个元素是不错的,但少于时就不值了 143 | 144 | ```js 145 | // 少量css操作 146 | $('a.swedberg').css('color', '#asd123'); 147 | // 大量css操作,最好使用样式表 148 | $('<style type="text/css">a.swedberg { color : #asd123 }</style>').appendTo('head'); 149 | ``` 150 | 151 | ## 使用 $.data 而不是 $.fn.data 152 | 153 | ```js 154 | // 常规写法 155 | $(elem).data(key,value); 156 | 157 | // 快 10 倍 158 | $.data(elem,key,value); 159 | ``` 160 | 161 | ## 别费时间在空白的选择结果上了 162 | 163 | ```js 164 | // 太糟了,执行了三个方法后才意识到里面是空的 165 | $('#nosuchthing').slideUp(); 166 | 167 | // 这会好点,就是代码有点长 168 | var $mySelection = $('#nosuchthing'); 169 | if ($mySelection.length) { mySelection.slideUp(); } 170 | 171 | // 最好的做法是添加一个 doOnce 插件 172 | jQuery.fn.doOnce = function(func){ 173 | this.length && func.apply(this); 174 | return this; 175 | } 176 | 177 | $('li.cartitems').doOnce(function(){ 178 | // make it ajax! \o/ 179 | }); 180 | ``` 181 | 182 | ## 判定 `IE7-` 183 | 184 | ```js 185 | // 其中 'p'[0],`p`可以换成任何单字符,如果原样返回这个字符,则说明是 IE8+,否则返回 undefined 则表明是 IE7- 186 | if('p'[0]){ 187 | console.log("It\'s IE8+"); 188 | } else { 189 | console.log("It\'s IE7-"); 190 | } 191 | ``` 192 | 193 | ## 处理大量的数据 194 | 195 | 将数据的数据量很大,一次性处理这些数据所需花费的时间较长,并且可能导致诸如浏览器暂时失去响应的结果, 196 | 那么最好将这些数据分割成数个小的 “进程”,利用事件轮询来取得一个更加流畅的效果。 197 | 198 | 使用 `setTimeout`(黑科技)来异步排程,基本上它的意思是“将这个函数贴在事件轮询队列的末尾”。 199 | 200 | 下面这些是一般处理数据的方法,当数据量很大时,可能导致浏览器失去响应: 201 | ```js 202 | var res = []; 203 | 204 | // `response(..)`从Ajax调用收到一个结果数组 205 | function response(data) { 206 | // 连接到既存的`res`数组上 207 | res = res.concat( 208 | // 制造一个新的变形过的数组,所有的`data`值都翻倍 209 | data.map( function(val){ 210 | return val * 2; 211 | } ) 212 | ); 213 | } 214 | 215 | // ajax(..) 是某个包中任意的Ajax函数 216 | ajax( "http://some.url.1", response ); 217 | ajax( "http://some.url.2", response ); 218 | ``` 219 | 220 | 最好使用以下写法,利用 `setTimeout` 来将处理数据的过程,分割成数个小的“进程”: 221 | ```js 222 | var res = []; 223 | 224 | // `response(..)`从Ajax调用收到一个结果数组 225 | function response(data) { 226 | // 我们一次只处理1000件 227 | var chunk = data.splice( 0, 1000 ); 228 | 229 | // 连接到既存的`res`数组上 230 | res = res.concat( 231 | // 制造一个新的变形过的数组,所有的`data`值都翻倍 232 | chunk.map( function(val){ 233 | return val * 2; 234 | } ) 235 | ); 236 | 237 | // 还有东西要处理吗? 238 | if (data.length > 0) { 239 | // 异步规划下一个批处理,将剩余的数据追加到下一个事件轮询队列中 240 | setTimeout( function(){ 241 | response( data ); 242 | }, 0); 243 | } 244 | } 245 | 246 | // ajax(..) 是某个包中任意的Ajax函数 247 | ajax( "http://some.url.1", response ); 248 | ajax( "http://some.url.2", response ); 249 | ``` 250 | 251 | ## 字符输出 252 | 253 | - `HTML`中字符输出使用`&#x`配上 `charCode`值; 254 | - 在`JavaScript`文件中为防止乱码转义,则是 `\u`配上 `charCode`值; 255 | - 而在 `CSS`文件中,如 `CSS`伪元素的 `content`属性,直接使用 `\`配上 `charCode`值。 256 | 257 | 因此,想在 `HTML/JS/CSS`中转义“我”这个汉字,分别是: 258 | 259 | - `我` 260 | - `\u6211, 如console.log('\u6211')` 261 | - `\6211`, 如`.xxx:before { content: '\6211'; }` 262 | 263 | 使用 `js`获取字符 `charCode`方法: 264 | 265 | ```js 266 | // 例如获取 `好`的 charCode 267 | '你好'.charCodeAt(1).toString(16) 268 | ``` 269 | 270 | 考虑到直接 ` `这种形式暴露在`HTML`中,可能会让屏幕阅读器等辅助设备读取,从而影响正常阅读流,因此, 271 | 我们可以进一步优化下,使用标签,利用伪元素,例如: 272 | ```css 273 | .full:before { content: '\2003'; speak: none; } 274 | ``` 275 | 这样,占位的空格字符即不能读,也不能选了。 276 | 277 | ## `CSS`中层叠上下文 278 | 279 | 形成层叠上下文的方法: 280 | 281 | - 根元素`<html></html>` 282 | - `position`值为 `absolute | relative`,且 `z-index`值不为 `auto` 283 | - `position` 值为 `fixed | sticky` 284 | - `z-index` 值不为 `auto` 的 `flex`元素,即:父元素`display: flex | inline-flex` 285 | - `opacity` 属性值小于 `1` 的元素 286 | - `transform` 属性值不为 `none`的元素 287 | - `mix-blend-mode` 属性值不为 `normal` 的元素 288 | - `filter、perspective、clip-path、mask、mask-image、mask-border、motion-path` 值不为 `none` 的元素 289 | - `perspective` 值不为 `none` 的元素 290 | - `isolation` 属性被设置为 `isolate` 的元素 291 | - `will-change` 中指定了任意 `CSS` 属性,即便你没有直接指定这些属性的值 292 | - `-webkit-overflow-scrolling` 属性被设置 `touch`的元素 293 | 294 | 由上到下,层次等级依次递减 295 | 296 | - 正`z-index` 297 | - `z-index:auto` / `z-index:0` / 不依赖 `z-index`的层叠上下文 298 | - `inline/inline-block`水平盒子 299 | - `float`浮动盒子 300 | - `block`块状水平盒子 301 | - 负`z-index ` 302 | - 层叠上下文`background / border` 303 | 304 | ## 异常捕获 305 | 306 | - `try...catch` 307 | 308 | 使用`try catch`能够很好的捕获异常并对应进行相应处理,不至于让页面挂掉,但是其存在一些弊端,比如需要在捕获异常的代码上进行包裹,会导致页面臃肿不堪,不适用于整个项目的异常捕获。 309 | 310 | - `window.onerror` 311 | 312 | `window.onerror`提供了全局监听异常的功能,不过可能会存在以下问题: 313 | - `Script error` 314 | `window.onerror`无法准确捕获到跨域资源的异常信息,例如 CDN上的静态资源,只会统一返回 `Script error` 315 | 解决方案: 316 | `script`标签配置 `crossorigin="anonymous"` 并且服务器添加 `Access-Control-Allow-Origin: *`(一般 CDN服务器都会配置此属性): 317 | ```js 318 | <script src="http://cdn.xxx.com/index.js" crossorigin="anonymous"></script> 319 | ``` 320 | 321 | - `sourceMap` 322 | 323 | 线上代码一般是混淆压缩的,无法正确清晰地定位报错代码的位置,所以需要启用 `sourceMap`,例如,在 webpack打包工具中启用: 324 | ```js 325 | module.exports = { 326 | // ... 327 | devtool: '#source-map', 328 | // ... 329 | } 330 | ``` 331 | 332 | - MVVM框架中的异常捕获 333 | 334 | 一般的 MVVM框架自带异常捕获机制,并且通常会屏蔽外部添加的异常捕获代码,例如 `window.onerror`。 335 | 336 | 下面是 `vue2.x`中捕获全局异常的方法: 337 | ```js 338 | Vue.config.errorHandler = function (err, vm, info) { 339 | let { 340 | message, // 异常信息 341 | name, // 异常名称 342 | script, // 异常脚本url 343 | line, // 异常行号 344 | column, // 异常列号 345 | stack // 异常堆栈信息 346 | } = err; 347 | 348 | // vm为抛出异常的 Vue 实例 349 | // info为 Vue 特定的错误信息,比如错误所在的生命周期钩子 350 | // 只在 2.2.0+ 可用 351 | } 352 | ``` 353 | 354 | - 异常上报 355 | 356 | 捕获到异常后,需要进行上报,这一步比较简单,例如直接将捕获到的错误通过 `ajax`全部上传即可。 357 | 358 | - `sourceMap` 异常解析 359 | 360 | 上传的错误日志可能是混淆加密后的代码的错误日志,不好分析,所以还需要使用 `soureMap`分析工具对其进行解析。 361 | 这样的工具,例如 [source-map](https://github.com/mozilla/source-map) 362 | 363 | 364 | ## 深度判等 365 | 366 | 两个对象结构和数据完全一致,即认为相等,而不要求是同一引用 367 | ```js 368 | export function deepEqual (o1, o2) { 369 | if (typeof o1 !== 'object' || typeof o2 !== 'object') { return o1 === o2 } 370 | // 完全遍历 o1,保证 o2 的数据结构可以覆盖 o1的,也就是说 o2的数据结构大于等于 o1的 371 | for (var p in o1) { 372 | if (!deepEqual(o1[p], o2[p])) { return false } 373 | } 374 | // 保证 o2中存在的数据结构在 o1中也存在,结合上一步,即可证明二者数据结构完全一致 375 | for (var q in o2) { 376 | if (!(q in o1)) { return false } 377 | } 378 | return true 379 | } 380 | ``` 381 | 382 | ## 深度覆盖 383 | 384 | 将源对象的值覆盖目标对象,相同结构相同参数部分直接覆盖,其它部分保持不变,例如: 385 | 修改前: 386 | 387 | ```js 388 | target = {x: 1, y: {a: 1, b:1 }, z: 1}; 389 | source = {x: 2, y: {a: 2}}; 390 | ``` 391 | 392 | 修改后: 393 | ```js 394 | target = {x: 2, y: {a: 2, b:1 }, z: 1} 395 | ``` 396 | 实现: 397 | ```js 398 | function deepAssign (target, ...sources) { 399 | if (typeof target !== 'object') { 400 | console.error('[deepAssign] bad parameters, target should be an object, parameters:', arguments) 401 | return target 402 | } 403 | 404 | for (let source of sources) { 405 | if (source != null && typeof source !== 'object') { 406 | console.warn('[deepAssign] bad parameters, source should all be object, parameters:', arguments) 407 | continue 408 | } 409 | 410 | for (var p in source) { 411 | if (typeof target[p] === 'object' && typeof source[p] === 'object') { deepAssign(target[p], source[p]) } 412 | else { target[p] = source[p] } 413 | } 414 | } 415 | 416 | return target 417 | } 418 | ``` -------------------------------------------------------------------------------- /常用技巧/前端技巧(2).md: -------------------------------------------------------------------------------- 1 | ## HTML元素方法 2 | 3 | 一些大部分人不知道的HTML元素方法。 4 | 5 | - `hidden` 6 | 7 | 隐藏元素,类似于 `display: none;`: 8 | ```js 9 | element.hidden = true; 10 | ``` 11 | 浏览器支持度: 12 | >IE 11+ Android 4.0+ 13 | 14 | - `toggle()` 15 | 16 | 这是一种为元素添加或删除某个 class 的方法,具体做法是: 17 | ```js 18 | myElement.classList.toggle('some-class', ifHas); 19 | ``` 20 | 21 | 上述代码的 `toggle`方法的第二个参数为 `Boolean`类型,`true`表示添加 `some-class`类,`false`表示删除。 22 | 23 | - `closest` 24 | 25 | 浏览器支持度: 26 | >IE系列全不支持,Android 和 IOS 支持度很差 27 | 28 | - `matches` 29 | 30 | ```js 31 | element.className.indexOf('some-class') > -1 32 | // 相当于 33 | element.matches('.some-class') 34 | ``` 35 | 36 | 浏览器支持度: 37 | >IE9+,需要加前缀 38 | 39 | ## 尾递归 40 | ```js 41 | function tail(i) { 42 | if (i > 3) return 43 | console.log('a:', i) 44 | tail(i + 1) 45 | console.log('b:', i) 46 | } 47 | tail(1) 48 | ``` 49 | 50 | 函数每次执行到 `tail(i + 1)`处,都会进入到下一个递归自身的函数中,`JS`会记录当前函数的堆栈,直到函数依次出栈退出,所以上述代码将输出: 51 | ```js 52 | a: 1 53 | a: 2 54 | a: 3 55 | b: 3 56 | b: 2 57 | b: 1 58 | ``` 59 | 尾递归会记录堆栈,所以可能会导致栈溢出,改为以下写法: 60 | ```js 61 | function tail(i) { 62 | if (i > 3) return 63 | console.log('a:', i) 64 | // 直接 return 65 | return tail(i + 1) 66 | console.log('b:', i) 67 | } 68 | tail(1) 69 | ``` 70 | 由于每次都 `return`掉当前函数,所以不会记录堆栈,上述代码将输出: 71 | ```js 72 | a: 1 73 | a: 2 74 | a: 3 75 | ``` 76 | 77 | ## 获取滚动条宽度 78 | 79 | 在某些情况下,例如页面弹出模态框时,禁止页面滚动,可能会给页面设置 `overflow: hidden`属性,滚动条消失,会带来页面横向的跳动,这个时候可以在滚动条消失的提示,给页面的右侧设置一个等于滚动条宽度的边距,这个时候就就需要首先获取滚动条宽度 80 | 81 | 各浏览器的滚动条宽度不一而同: 82 | >- macOS: 15px 83 | >- Chrome FireFox IE: 17px 84 | >- Edge: 16px 85 | >- Opera: 15px 86 | 87 | 除此之外,可以通过 `js`进行测量: 88 | ```js 89 | const outer = document.createElement('div'); 90 | const inner = document.createElement('div'); 91 | outer.style.overflow = 'scroll'; 92 | document.body.appendChild(outer); 93 | outer.appendChild(inner); 94 | const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; 95 | document.body.removeChild(outer); 96 | // scrollbarWidth 即为滚动条宽度 97 | console.log(scrollbarWidth); 98 | ``` 99 | 100 | >__Tips__: 当设置 `html::-webkit-scrollbar { display: none; }`,会让页面隐藏滚动条,但仍然可以滚动 101 | 102 | ## 滚动边界 103 | 104 | 如下图所示: 105 | ![scroll-over](img/scroll-over.gif) 106 | 107 | 模态框滚动结束后,页面的滚动条会紧接着被触发导致页面滚动,想要将滚动限制在模态框中,有如下两种解决方案。 108 | 109 | - 使用 js进行限制 110 | 111 | ```html 112 | <div class="outer"> 113 | <div class="inner"></div> 114 | </div> 115 | ``` 116 | 117 | ```js 118 | var elem = document.querySelector('.outer') 119 | // 注意,这里监听的是 wheel 而不是 scroll 事件 120 | elem.addEventListener('wheel',handleOverscroll) 121 | function handleOverscroll(event) { 122 | const delta = -event.deltaY; 123 | if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) { 124 | elem.scrollTop = elem.scrollHeight; 125 | event.preventDefault(); 126 | return false; 127 | } 128 | if (delta > elem.scrollTop) { 129 | elem.scrollTop = 0; 130 | event.preventDefault(); 131 | return false; 132 | } 133 | return true; 134 | } 135 | ``` 136 | 137 | - 使用 CSS新特性 138 | 139 | ```css 140 | .element { 141 | overscroll-behavior: contain; 142 | } 143 | ``` 144 | 上述 css新特性兼容性较差,目前只能作为备选方案 145 | 146 | ## Passive event listeners 147 | 148 | 现代的浏览器虽然知道如何使得滚动变得平滑,但为确认(滚动)事件处理函数中是否执行了 `event.preventDefault()` 以取消默认行为,有时仍可能需要花费 `500`毫秒来等待事件处理函数执行完毕。 149 | 即使是一个空的事件监听器,从不取消任何行为,鉴于浏览器仍会期待 `preventDefault` 的调用,也会对性能造成负面影响。 150 | 为了准确地告诉浏览器不必担心(事件处理函数中)取消了默认行为,为监听事件增加 `passive`: 151 | 152 | ```js 153 | ele.addEventListener('touchstart', e => { 154 | // do something 155 | }, { passive: true }) 156 | ``` 157 | 158 | ## 监控网页的崩溃 159 | 160 | 由于网页崩溃以后,页面上的 `js`很可能也是无法运行的了,所以无法使用页面上的 `js`进行网页崩溃的监控,需要另寻他法。 161 | 162 | >参见 [如何监控网页崩溃?](https://zhuanlan.zhihu.com/p/40273861) 或者 [如何监控网页崩溃?](https://mp.weixin.qq.com/s/HnMZLI7hZ5sXU7bOXh615A) 163 | 164 | ## 解决堆栈溢出 165 | 166 | ```js 167 | const nextListItem = () => { 168 | // 假设 list是一个已经存在的数组 169 | if (list.pop()) { 170 | nextListItem(); 171 | } 172 | } 173 | ``` 174 | 上述代码在 `list`数组长度较大的时候,由于可能会不断地进行回调自身的操作,不断在申请内存用于存放堆栈信息,一直到函数达到一定条件不再进行持续的回调自身,最初的函数才运行结束,释放掉所申请的所有内存,而如果在此期间所申请的总内存超出了线程的栈空间,则将发生栈溢出。 175 | 176 | 有两种解决方式: 177 | 178 | - 异步 179 | 180 | ```js 181 | const nextListItem = () => { 182 | if (list.pop()) { 183 | setTimeout(nextListItem, 0) 184 | } 185 | } 186 | ``` 187 | 188 | `setTimeout`的作用就是让代码异步运行,将每次回调自身函数的执行放到事件队列中,上一个函数执行完就可以退出,释放所占用的内存,不会造成内存累计的情况。 189 | 190 | - 闭包 191 | 192 | ```js 193 | const nextListItem = () => { 194 | if (list.pop()) { 195 | return function() { 196 | return nextListItem() 197 | } 198 | } 199 | } 200 | // 为了避免不断调用,例如 nextListItem()()(),增加一个自动调用器 201 | const autoRun = func => { 202 | const value = func() 203 | typeof value === 'function' && autoRun(value) 204 | } 205 | // 使用 206 | autoRun(nextListItem) 207 | ``` 208 | 209 | 闭包返回的是一个新函数,之前的函数在运行结束后可以释放内存,所以也不存在内存堆积的情况。 210 | 211 | ## 事件循环 212 | 213 | >macrotasks: setTimeout setInterval setImmediate I/O UI渲染 214 | >microtasks: process.nextTick Promise Object.observe MutationObserver 215 | 216 | 一次事件循环中,先执行 `microtasks`,再执行 `macrotasks` 217 | `microtasks`中,`process.nextTick`最先执行,比 `promise` 先执行 218 | 219 | ## 移动端滚动穿透问题解决方案 220 | 221 | 弹窗弹出后,整个背景不可滚动,但是弹窗内部可滚动 222 | 223 | ```js 224 | function getScrollTop () { 225 | return document.body.scrollTop || document.documentElement.scrollTop 226 | } 227 | const toggleForbidScrollThrough = (function toggleForbidScrollThrough () { 228 | let scrollTop 229 | return function toggleForbidScrollThroughInner (isForbide) { 230 | if (isForbide) { 231 | scrollTop = getScrollTop() 232 | // position fixed会使滚动位置丢失,所以利用top定位 233 | document.body.style.position = 'fixed' 234 | document.body.style.top = `-${scrollTop}px` 235 | } else { 236 | // 恢复时,需要还原之前的滚动位置 237 | document.body.style.position = 'static' 238 | document.body.style.top = 'auto' 239 | window.scrollTo(0, scrollTop) 240 | } 241 | } 242 | }()) 243 | // 使用 244 | // 当显示弹窗时调用 245 | toggleForbidScrollThrough(true) 246 | // 当隐藏弹窗时调用 247 | toggleForbidScrollThrough(false) 248 | ``` 249 | 250 | ## Emoji表情转义处理 251 | 252 | 编码,用于存入数据库: 253 | ```js 254 | // emoji表情需要转义 防止后端报错 255 | const utf16toEntities = (str) => { 256 | const patt = /[\ud800-\udbff][\udc00-\udfff]/g // 检测utf16字符正则 257 | str = str.replace(patt, (char) => { 258 | if (char.length === 2) { 259 | const H = char.charCodeAt(0) 260 | // 取出高位 261 | const L = char.charCodeAt(1) 262 | // 取出低位 263 | const code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00 264 | // 转换算法 265 | return '&#' + code + ';' 266 | } 267 | return char 268 | }) 269 | return str 270 | } 271 | ``` 272 | 273 | 解码,用于前端显示: 274 | ```js 275 | /** 276 | * 用于反解开 emoji编码后的字符串 277 | * @param str emoji编码后的字符串 278 | */ 279 | const uncodeUtf16 = (str) => { 280 | const reg = /\&#.*?;/g 281 | const result = str.replace(reg, char => { 282 | if (char.length === 9) { 283 | const code = +char.match(/[0-9]+/g) 284 | const H = Math.floor((code - 0x10000) / 0x400) + 0xD800 285 | const L = (code - 0x10000) % 0x400 + 0xDC00 286 | return decodeURI('%u' + H.toString(16) + '%u' + L.toString(16)) 287 | } 288 | return char 289 | }) 290 | return result 291 | } 292 | ``` 293 | 294 | ## 数字取整 295 | 296 | 对一个数字 `num | 0`可以取整,负数也同样适用 297 | ```js 298 | 1.3 | 0 // 1 299 | 1.9 | 0 // 1 300 | -1.9 | 0 // -1 301 | ``` 302 | 303 | ## 惰性载入函数 304 | 305 | 在某个场景下我们的函数中有判断语句,这个判断依据在整个项目运行期间一般不会变化,所以判断分支在整个项目运行期间只会运行某个特定分支,那么就可以考虑惰性载入函数 306 | 307 | ```js 308 | function foo() { 309 | if (a !== b) { 310 | console.log('aaa') 311 | } else { 312 | console.log('bbb') 313 | } 314 | } 315 | // 优化后 316 | function foo() { 317 | if (a != b) { 318 | // 重新赋值,下次就无需进行判断了 319 | foo = function(){ 320 | console.log('aaa') 321 | } 322 | } else { 323 | // 重新赋值,下次就无需进行判断了 324 | foo = function() { 325 | console.log('bbb') 326 | } 327 | } 328 | return foo() 329 | } 330 | ``` 331 | 332 | ## 数组平铺 333 | 334 | ```js 335 | const flatten = (arr, depth = 1) => 336 | depth != 1 337 | ? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), []) 338 | : arr.reduce((a, v) => a.concat(v), []); 339 | // 使用 340 | flatten([1, [2], 3, 4]); // [1, 2, 3, 4] 341 | flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8] 342 | ``` 343 | 344 | ## quicklink 345 | 346 | `quicklink` 是一个通过预加载资源来提升后续速度的轻量级工具库。旨在提升浏览过程中,用户访问后续页面的加载速度 347 | 348 | [GoogleChromeLabs](https://github.com/GoogleChromeLabs)发布了相关开箱即用的库 [quicklink](https://github.com/GoogleChromeLabs/quicklink) 349 | 350 | 或者也可以自行实现简易的 `prefetcher`,判断是否支持 `Resource Hints` 中的 `prefetch`,支持则使用它,否则回退使用 `XHR` 加载: 351 | ```js 352 | function quicklink(url) { 353 | const link = document.createElement('link') 354 | const supports = (link.relList || {}).supports && link.relList.supports('prefetch') 355 | if (supports) { 356 | link.rel = 'prefetch' 357 | link.href = url 358 | link.as = 'script' 359 | document.head.appendChild(link) 360 | } else { 361 | const xhr = new XMLHttpRequest() 362 | xhr.open('GET', url, true) 363 | xhr.send() 364 | } 365 | } 366 | ``` -------------------------------------------------------------------------------- /笔试题&面试题/前端面试笔试题(2).md: -------------------------------------------------------------------------------- 1 | # 前端笔试面试题 2 | 3 | ## **HTML && CSS** 4 | 5 | ##### 清除浮动有哪些方式?比较好的方式是哪一种? 6 | 7 | ``` 8 | (Q1) 9 | (1)父级div定义height。 10 | (2)结尾处加空div标签clear:both。 11 | (3)父级div定义伪类:after和zoom。 12 | (4)父级div定义overflow:hidden。 13 | (5)父级div定义overflow:auto。 14 | (6)父级div也浮动,需要定义宽度。 15 | (7)父级div定义display:table。 16 | (8)结尾处加br标签clear:both。 17 | 18 | (Q2) 比较好的是第3种方式,好多网站都这么用。 19 | ``` 20 | 21 | #### box-sizing常用的属性有哪些?分别有什么作用? 22 | 23 | ``` 24 | (Q1) 25 | box-sizing: content-box | border-box | inherit 26 | 27 | (Q2) 28 | content-box:宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框(元素默认效果)。 29 | border-box:元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。 30 | 通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。 31 | ``` 32 | 33 | #### 为什么要使用`CSS sprites` 34 | ``` 35 | CSS Sprites其实就是把网页中一些背景图片整合到一张图片文件中, 36 | 再利用CSS的“background-image”,“background-position”的组合进行背景定位, 37 | 这样可以减少很多图片请求的开销,因为请求耗时比较长; 38 | 请求虽然可以并发,但是如果请求太多会给服务器增加很大的压力。 39 | ``` 40 | 41 | #### 对`BFC`规范(块级格式化上下文:`block formatting context`)的理解? 42 | 43 | >`W3C CSS 2.1` 规范中的一个概念,它是一个独立容器,决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。 44 | >一个页面是由很多个 Box 组成的,元素的类型和 display 属性,决定了这个 Box 的类型。 45 | >不同类型的 Box,会参与不同的 Formatting Context(决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染, 46 | >也就是说BFC内部的元素和外部的元素不会互相影响。 47 | 48 | 触发 `BFC`的条件,常见如下: 49 | 50 | >1. `float`的值不为`none`。(不推荐) 51 | >2. `overflow`的值为`auto,scroll`或`hidden`。 52 | >3. `display`的值为`table, table-cell, table-caption, inline-block`中的任何一个。 53 | >4. `position`的值不为 `relative`和 `static`。(不推荐) 54 | 55 | 56 | 57 | ## **JavaScript** 58 | 59 | #### 用Javascript获取页面元素的位置 60 | 61 | 转自 [阮一峰文章专栏](http://www.ruanyifeng.com/blog/2009/09/find_element_s_position_using_javascript.html) 62 | 63 | #### js有哪些内置对象? 64 | ``` 65 | 数据封装类对象:Object、Array、Boolean、Number 和 String<br> 66 | 其他对象:Function、Arguments、Math、Date、RegExp、Error 67 | ``` 68 | 69 | #### ajax 有那些优缺点?如何解决跨域问题? 70 | ``` 71 | (Q1) 72 | 优点: 73 | (1)通过异步模式,提升了用户体验. 74 | (2)优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用. 75 | (3)Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。 76 | (4)Ajax可以实现动态不刷新(局部刷新) 77 | 缺点: 78 | (1)安全问题 AJAX暴露了与服务器交互的细节。 79 | (2)对搜索引擎的支持比较弱。 80 | (3)不容易调试。 81 | (Q2)jsonp、 iframe、window.name、window.postMessage、服务器上设置代理页面。 82 | ``` 83 | 84 | #### JavaScript原型,原型链 ? 有什么特点? 85 | ``` 86 | (1)原型对象也是普通的对象,是对象一个自带隐式的 `__proto__` 属性,原型也有可能有自己的原型, 87 | 如果一个原型对象的原型不为null的话,我们就称之为原型链。 88 | (2)原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链。 89 | ``` 90 | 91 | #### GET和POST的区别,何时使用POST? 92 | ``` 93 | GET:一般用于信息获取,使用URL传递参数,对所发送信息的数量也有限制,一般在2000个字符 94 | POST:一般用于修改服务器上的资源,对所发送的信息没有限制。 95 | GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值, 96 | 也就是说Get是通过地址栏来传值,而Post是通过提交表单来传值。 97 | 然而,在以下情况中,请使用 POST 请求: 98 | 无法使用缓存文件(更新服务器上的文件或数据库) 99 | 向服务器发送大量数据(POST 没有数据量限制) 100 | 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠。 101 | ``` 102 | 103 | #### 请解释一下 JavaScript 的同源策略 104 | ``` 105 | 概念:同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。 106 | 它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。 107 | 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。 108 | 指一段脚本只能读取来自同一来源的窗口和文档的属性。 109 | 为什么要有同源限制? 110 | 我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上, 111 | 当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容, 112 | 这样用户名,密码就轻松到手了。 113 | ``` 114 | #### Flash、Ajax各自的优缺点,在使用中如何取舍? 115 | ``` 116 | Flash适合处理多媒体、矢量图形、访问机器;对CSS、处理文本上不足,不容易被搜索。 117 | Ajax对CSS、文本支持很好,支持搜索;多媒体、矢量图形、机器访问不足。 118 | 共同点:与服务器的无刷新传递消息、用户离线和在线状态、操作DOM 119 | ``` 120 | 121 | #### 什么是闭包? 122 | ``` 123 | 闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数), 124 | 因而这些变量也是该表达式的一部分。闭包的特点: 125 | (1)作为一个函数变量的一个引用,当函数返回时,其处于激活状态。 126 | (2) 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。 127 | 简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。 128 | 而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。 129 | 当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。 130 | ``` 131 | 132 | ## **WEB 安全** 133 | 134 | #### XSS(Cross Site Script): 跨站脚本攻击 135 | 136 | XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。 137 | 分为 3类:反射型(非持久型)、存储型(持久型)、基于DOM 138 | 139 | * 反射型 140 | 反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。 141 | * 存储型 142 | 存储型 XSS 会把用户输入的数据 “存储” 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。 143 | * 基于DOM 144 | 基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。 145 | 146 | XSS 攻击的防范: 147 | * HttpOnly 防止劫取 Cookie 148 | * 输入、输出检查 149 | 150 | #### CSRF(Cross Site Request Forgery): 跨站请求伪造 151 | 152 | 一种劫持受信任用户向服务器发送非预期请求的攻击方式。通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。 153 | 154 | CSRF 攻击的防范: 155 | * 验证码 156 | * 验证 Referer Check 157 | * 添加 token 验证 158 | 159 | ## **其他** 160 | 161 | #### 线程与进程的区别 162 | 163 | 一个程序至少有一个进程,一个进程至少有一个线程。 164 | 线程的划分尺度小于进程,使得多线程程序的并发性高。 165 | 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 166 | 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。 167 | 但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 168 | 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。 169 | 但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 170 | 171 | ## 常见排序 172 | 173 | - 冒泡排序 174 | 175 | ```js 176 | function bubbleSort(arr) { 177 | var len = arr.length 178 | for(let i = 0; i < len - 1; i++) { 179 | for(let j = 0; j < len - 1 - i; j++) { 180 | if(arr[j] < arr[j + 1]) { 181 | var temp = arr[j + 1] 182 | arr[j + 1] = arr[j] 183 | arr[j] = temp 184 | } 185 | } 186 | } 187 | return arr 188 | } 189 | ``` 190 | 191 | - 选择排序 192 | 193 | ```js 194 | function selectSort(arr) { 195 | var len = arr.length 196 | var minIndex, temp 197 | for(var i = 0; i < len - 1; i++) { 198 | minIndex = i 199 | for (var j = i + 1; j < len; j ++) { 200 | if (arr[j] < arr[minIndex]) { 201 | minIndex = j 202 | } 203 | } 204 | temp = arr[i] 205 | arr[i] = arr[minIndex] 206 | arr[minIndex] = temp 207 | } 208 | } 209 | ``` 210 | 211 | - 快速排序 212 | 213 | ```js 214 | function quickSort(arr) { 215 | var len = arr.length 216 | if (len <= 1) { 217 | return arr 218 | } 219 | var priotIndex = Math.floor(len / 2) 220 | var priot = arr.splice(priotIndex, 1)[0] 221 | var left = [] 222 | var right = [] 223 | for (var i = 0; i < arr.length ; i++) { 224 | if (arr[i] < priot) { 225 | left.push(arr[i]) 226 | } else { 227 | right.push(arr[i]) 228 | } 229 | } 230 | return quickSort(left).concat(priot, quickSort(right)) 231 | } 232 | ``` 233 | 234 | - 插入排序 235 | ```js 236 | function insertSort(arr) { 237 | var len = arr.length 238 | var preIndex, current 239 | for(var i = 1; i< len; i++) { 240 | preIndex = i - 1 241 | current = arr[i] 242 | while(preIndex >= 0 && arr[preIndex] > current) { 243 | arr[preIndex + 1] = arr[preIndex] 244 | preIndex-- 245 | } 246 | arr[preIndex + 1] = current 247 | } 248 | return arr 249 | } 250 | ``` 251 | - 归并排序 252 | 253 | ```js 254 | function mergeSort(arr) { 255 | var len = arr.length 256 | if (len < 2) { 257 | return arr 258 | } 259 | var middle = Math.floor(len / 2) 260 | var left = arr.slice(0, middle) 261 | var right = arr.slice(middle) 262 | return merge(mergeSort(left), mergeSort(right)) 263 | } 264 | 265 | function merge(left, right) { 266 | var result = [] 267 | while(left.length && right.length) { 268 | if (left[0] <= right[0]) { 269 | result.push(left.shift()) 270 | } else { 271 | result.push(right.shift()) 272 | } 273 | } 274 | if (left.length) { 275 | result = result.concat(left.reverse()) 276 | } 277 | if (right.length) { 278 | result = result.concat(right.reverse()) 279 | } 280 | return result 281 | } 282 | ``` -------------------------------------------------------------------------------- /读书笔记/HTML5与CSS3权威指南(上).md: -------------------------------------------------------------------------------- 1 | # HTML5 与 CSS3 权威指南(上) 2 | --- 3 | 4 | ## `HTML5` 的结构元素 5 | 6 | - 主体结构元素: 7 | 8 | ``` 9 | article section nav aside time pubdate 10 | ``` 11 | 12 | - 非主体结构元素: 13 | 14 | ``` 15 | header footer address main 16 | ``` 17 | 18 | - 表单新增元素 19 | - 图形绘制:`Canvas` 20 | - 多媒体相关 `API` 21 | ``` 22 | video audio 23 | ``` 24 | - 浏览器历史记录: `History API` 25 | - 本地存储 26 | ``` 27 | (1) Web Storage: 28 | I. localStorage 29 | II. sessionStorage 30 | 31 | (2) 本地数据库 32 | I. SQLite 33 | II. IndexedDB 34 | 35 | (3) 离线应用程序 36 | I. manifest 文件 37 | II. applicationCache 对象 38 | ``` 39 | 40 | - 文件 `API` 41 | 42 | ``` 43 | (1) FileList 对象与 file 对象 44 | (2) ArrayBuffer 对象与 ArrayBufferView 对象 45 | (3) Blob 对象 46 | (4) FileReader 对象 47 | (5) FileSystem API: 包括对文件以及文件目录的空间申请、创建、写入、读取、复制、删除 48 | (6) Base64 编码支持 49 | ``` 50 | - 通信`API` 51 | 52 | 53 | ## 具有 `boolean` 值的属性 (例如 `disable readonly` ) 写法 54 | 55 | ```html 56 | <!--不写属性名默认属性为 false--> 57 | <input type="checkbox"> 58 | 59 | <!--只写属性名,代表属性值为 true--> 60 | <input type="checkbox" checkbox> 61 | 62 | <!--属性值 等于 属性名,代表属性为 true--> 63 | <input type="checkbox" checkbox='checkbox'> 64 | 65 | <!--属性值 等于 空字符串 "",代表属性为 true--> 66 | <input type="checkbox" checkbox=""> 67 | ``` 68 | 69 | ## 属性值引号省略 70 | 71 | 当属性值不包括空字符串、`<、>、=、`单引号、双引号等字符时,属性值两边的引号可以省略。 72 | ```html 73 | <!--以下三种写法效果完全相同--> 74 | <input type='text'> 75 | <input type="text"> 76 | <input type=text> 77 | ``` 78 | 79 | ## `artile` 与 `section` 标签 80 | 81 | `article` 元素代表文档、页面或应用程序中**独立的、完整的、可以独自被外部引用的内容**, 82 | 例如一篇博客、报纸上的文章、论坛帖子、用户评论等独立内容。 83 | 84 | 一个标准完整的 `article` 示例如下: 85 | 86 | ```html 87 | <article> 88 | <header> 89 | <h1>苹果</h1> 90 | <p>发表日期:</p><time pubdate='pubdate'>2010/10/09</time> 91 | </header> 92 | <p><b>苹果</b>,植物类水果...</p> 93 | <footer> 94 | <p><small>著作权归xxx公司所有</small></p> 95 | </footer> 96 | </article> 97 | ``` 98 | 99 | 以上代码中,`article` 元素中包括头部`header`、标题`h1`、段落`p`、页脚`footer` 100 | 等元素,`article` 内容相对比较独立、完整,适合使用`article`标签元素。 101 | 102 | - `section` 103 | 相对于 `article` 来说,`section` 元素适用于对页面上的内容进行分块,或者说对文章进行分段, 104 | 105 | - `artile` 与 `section` 之间的区别在于,`artile`可以看成是一种特殊种类的 `section`, 106 | 但是比 `section` 更加强调**独立性**,**二者可以互相嵌套使用**。 107 | 108 | - 需要注意的事情 109 | 110 | (1) 不要将 section 用作设置样式的页面容器,也就是说,如果一个页面容器元素, 111 | 其主要表现都由 CSS 来进行规范,那么直接使用 div 。 112 | (2) 如果 article 元素、aside 元素或者 nav 元素更符合状况,那么不要使用 section 。 113 | (3) 不要为没有标题的内容区块使用 section 元素。 114 | 115 | - 非主体结构元素 116 | 117 | >1. `header footer address` 标签元素都可以在同一个网页内存在多个, 118 | 比如为每个`article`内容区加一个`header` 和 `footer`. 119 | 120 | >2. 每个网页内部职能防止一个 `main` 元素,不能将 `main` 元素放置在任何 121 | `article aside footer header nav` 等元素内部。 122 | 123 | - 对新的结构元素使用样式 124 | 125 | 由于很多浏览器尚未对 `HTML5` 中新增的结构元素提供支持,所以可能需要使用 `CSS` 追加 126 | 以下声明,目的是通知浏览器页面中使用的 `HTML5` 新增元素都是以块方式显示的。 127 | ``` 128 | article, aside, dialog, figure, header, legend, nav, section, main { 129 | display: block; 130 | } 131 | ``` 132 | 另外,IE 8及之前的浏览不支持使用 `CSS` 的方法来使用这些尚未支持的结构元素, 133 | 这就需要用到 `JavaScript` 脚本动态创建元素标签。 134 | ``` 135 | <script> 136 | document.createElement('nav'); 137 | document.createElement('article'); 138 | document.createElement('footer'); 139 | document.createElement('main'); 140 | </script> 141 | ``` 142 | 143 | - `WebStorage` 144 | 145 | >1. 利用 `storage` 事件监视 `Web Storage` 中的数据 146 | 147 | ```js 148 | window.addEventListener('storage', function(event){ 149 | // 当sessionStorage 或 localStorage 的值发生变动时所要执行的处理 150 | }, false) 151 | ``` 152 | 以上代码,其中 `event` 事件属性: 153 | ``` 154 | (1) event.key: 被修改的数据的键值 155 | (2) event.oldValue: 被修改的数据在修改之前的值 156 | (3) event.oldValue: 被修改的数据在修改之后的值 157 | (4) event.url: 被修改的数据值的URL地址 158 | (5) event.storageArea: 被修改的 sessionStorage 或 localStorage 对象 159 | ``` 160 | 161 | - `input` 文件类型 162 | 163 | >为 `file` 类型的 `input` 标签添加 `multiple` 属性,则允许一次性放置多个文件,以数组类型呈现。 164 | 165 | ``` 166 | HTML: 167 | <!--size 限制上传文件大小,multiple 表明允许一个 input 控件中放置多个文件--> 168 | <input type="file" id='file' size='80' multiple> 169 | <input type="button" onclick='ShowFileInfo()' value='显示文件信息'> 170 | 171 | JS: 172 | function ShowFileInfo() { 173 | var file; 174 | // 获取 input 标签中所有放置的文件 175 | var uploadFiles=document.getElementById('file'); 176 | for(var i=0; i<uploadFiles.files.length; i++) { 177 | file=uploadFiles.files[i]; 178 | // 输出每个上传文件的名称 179 | console.log(file.name) 180 | // 输出每个上传文件的类型 181 | console.log(file.type) 182 | // 输出每个上传文件的字节大小 183 | console.log(file.size) 184 | } 185 | } 186 | ``` 187 | 188 | **对于图片类型的文件,其 `type` 都是以 `image/`为前缀,根据此特性,可以直接判断上传文件** 189 | **是否为图片文件** 190 | 191 | - `Blob` 对象 192 | >`Blob` 是 `HTML5` 中新增的对象,代表原始二进制数据,`file` 对象就继承了 `Blob` 对象。 193 | 此对象有两个属性, `size` 属性表示一个 `Blob` 对象的字节长度,`type` 属性表示`Blob` 对象的 194 | `MIME` 类型,如果是未知类型,则返回一个空字符串。 195 | 196 | 创建 `Blob` 对象 197 | ``` 198 | HTML: 199 | <textarea name="" id="text" cols="30" rows="10"></textarea> 200 | <button id="btndownload" onclick='Blob_test()'>create</button> 201 | <output id='result'></output> 202 | 203 | JS: 204 | function Blob_test() { 205 | var text = document.getElementById('text').value; 206 | var blob; 207 | var result = document.getElementById('result'); 208 | if(!window.Blob){ 209 | result.innerHTML = '您的浏览器不支持使用Blob对象!'; 210 | } else { 211 | // Blob 第一个参数值为一个数组,其中放置任意数量的类型对象, 212 | // 例如 String 对象、ArrayBuffer对象、ArrayBufferView 对象、Blob对象 213 | // 这里很明显是一个 String 对象 214 | blob = new Blob([text]); 215 | } 216 | 217 | if(window.URL){ 218 | var temp = window.URL.createObjectURL(blob); 219 | result.innerHTML = '<a download href="'+temp+'" target="_blank">文件下载</a>'; 220 | } 221 | } 222 | ``` 223 | 224 | 上述代码,在 `textarea` 中输入任意字符后,点击按钮,将在按钮后追加一个带有`download` 属性的 `a` 标签, 225 | 其`href` 值类似于`blob:http%3A//null.jsbin.com/c8e1bd7b-5cc4-4c9c-a476-6c192dba6f68`,点击此链接之后, 226 | 将会下载文件到本地,用编辑器打开,文件里的内容就是刚才输入在 `textarea` 里的内容。 227 | 228 | - `Base64` 编码 229 | 230 | >`Base64` 编码是一种使用`64`个可打印字符(大部分情况下指得是 A-Z、 a-z、0-9、"+"、"/")来表示二进制数据的一种编码方法 231 | 编码后的数据比原始数据略长。 232 | 在 `HTML5` 中,新增`btoa()`与`atob()` 方法来支持`Base64` 编码,在这两个方法的命名中, 233 | `b` 可以被理解为一串二进制数据,`a` 可以被理解为一个`ASCII`码字符串,**分别用来编码和解码**。 234 | 235 | ```js 236 | // 编码 237 | var result = window.btoa(data); 238 | 239 | // 解码 240 | var result = window.atob(data); 241 | ``` 242 | 243 | >1. `btoa()` 编码方法应用。 244 | > 让 `img` 标签的 `src` 属性指向一个以 `data:` 开头的 `Base64` 图片,这对于数据库中保存的二进制图片文件的显示, 245 | 或者统一指定使用的图片文件的格式非常有用。 246 | 247 | ``` 248 | HTML: 249 | <p> 250 | <label>选择文件:</label> 251 | <input type="file" name="" id="file" onchange='file_onchange()'> 252 | <input type="button" id="btnReadPicture" value='显示图片' onclick='readPicture()' disabled> 253 | </p> 254 | <div name='result' id="result"></div> 255 | 256 | JS: 257 | var result=document.getElementById('result'); 258 | var file=document.getElementById('file'); 259 | if(typeof FileReader==='undefined'){ 260 | result.innerHTML='<p>您的浏览器不支持FileReader</p>'; 261 | file.setArribute('disabled','disabled'); 262 | } 263 | 264 | function file_onchange() { 265 | document.getElementById('btnReadPicture').disabled=false; 266 | } 267 | function readPicture() { 268 | var file=document.getElementById('file').files[0]; 269 | if(!/image\/\w+/.test(file.type)){ 270 | console.log('选取的不是图片文件!'); 271 | return false; 272 | } 273 | // 使用 FileReader 读取图片文件 274 | var reader=new FileReader(); 275 | reader.readAsBinaryString(file); 276 | reader.onload=function(f){ 277 | // 对选择的图片文件使用 btoa 编码为 Base64 格式 278 | var src='data:'+file.type+';base64,'+window.btoa(this.result); 279 | result.innerHTML='<img src="'+src+'" alt=""/>'; 280 | } 281 | } 282 | ``` 283 | 284 | >2. `atoa()` 解码方法应用。 285 | >获取 `canvas` 元素 `URL`地址中的 `Base64` 格式的字符串,并将其解码为一串二进制数据,保存到数据库。 286 | 287 | ``` 288 | HTML: 289 | <body onload="draw('canvas')"> 290 | <input type="button" value="上传图片" onclick='imgSave()'> 291 | <canvas id="canvas" width='400' height='300'></canvas> 292 | </body> 293 | 294 | JS: 295 | var canvas; 296 | function draw(id) { 297 | canvas=document.getElementById(id); 298 | var context=canvas.getContext('2d'); 299 | context.fillStyle='rgb(0,0,255)'; 300 | context.fillRect(0,0,canvas.width,canvas.height); 301 | context.fillStyle='rgb(255,255,0)'; 302 | context.fillRect(10,20,50,50); 303 | } 304 | 305 | function imgSave() { 306 | // toDataURL: 获取canvas的URL地址,并设置格式为jpeg 307 | var data=canvas.toDataURL('image/jpeg'); 308 | data=data.replace('dat:image/jpeg;base64,',''); 309 | // 使用 ajax 将二进制数据发送到服务器 310 | var xhr=new XMLHttpRequest(); 311 | xhr.open('POST','uploadImage.php'); 312 | xhr.sendAsBinary(window.atob(data)); 313 | } 314 | ``` 315 | 316 | - 通信 `API` 317 | 318 | >1. `WebSocket`:服务器与客户端之间的 **双向** 通信机制 319 | >2. `Server-Sent Events`:服务器发送到客户端的 **单向** 通信机制 320 | 321 | - `WebRTC` 通信 322 | >视频与音频的实时通信 323 | 324 | >1. `getUserMedia()` 方法访问本地设备(摄像头、麦克风) 325 | 326 | 浏览器开启视频实例: 327 | ``` 328 | HTML: 329 | <video id='myVideo' width='400' height='300' autoplay></video> 330 | 331 | JS: 332 | navigator.getUserMedia=navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia; 333 | window.URL=window.URL || window.webkitURL; 334 | 335 | var video=document.getElementById('myVideo'); 336 | // video: true 表示开启摄像头 337 | // audio:false 表示不使用麦克风 338 | navigator.getUserMedia({video:true,audio:false},function(stream){ 339 | video.src=window.URL.createObjectURL(stream) 340 | },function(err){ 341 | console.log(err); 342 | }) 343 | ``` 344 | 345 | 借助 `canvas` 截取摄像头中的图像实现拍照功能: 346 | ``` 347 | HTML5: 348 | <video id='myVideo' width='400' height='300' autoplay></video> 349 | <img src="" id='img'> 350 | // canvas 起到一个暂存的作用,可选择性的将其 display:none; 隐藏起来 351 | <canvas id="canvas" width='400' height='300'></canvas> 352 | 353 | JS: 354 | navigator.getUserMedia=navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia; 355 | window.URL=window.URL || window.webkitURL; 356 | 357 | var video=document.getElementById('myVideo'); 358 | video.addEventListener('click',snapshot,false); 359 | var canvas = document.getElementById('canvas'); 360 | var ctx=canvas.getContext('2d'); 361 | var localMediaStream=null; 362 | 363 | navigator.getUserMedia({video:true,audio:false},function(stream){ 364 | video.src=window.URL.createObjectURL(stream); 365 | localMediaStream=stream; 366 | },function(err){ 367 | console.log(err); 368 | }); 369 | 370 | function snapshot() { 371 | if(localMediaStream) { 372 | // 首先将数据流绘制在 canvas上 373 | ctx.drawImage(video,0,0,400,300); 374 | // 然后利用 canvas 的toDataUrl 方法将绘制出来的图片解码为 base64格式,输出到 img 标签 375 | document.getElementById('img').src=canvas.toDataURL(); 376 | } 377 | } 378 | ``` 379 | 380 | - `Web Worker` 381 | 382 | >适用场合 383 | ``` 384 | (1) 预先抓取并缓存一些数据以供后期使用 385 | (2) 代码高亮处理货其他一些页面上的文字格式化处理 386 | (3) 拼写检查 387 | (4) 分析视频或音频数据 388 | (5) 后台 I/O 处理 389 | (6) 大数据量分析或计算处理 390 | (7) canvas 元素中的图像数据的运算及生成处理 391 | (8) 本地数据库中数据的存取及处理 392 | ``` 393 | 394 | - 其他 `API` 395 | 396 | >1. `Web` 页面可见性 `API`:`Page Visibility API` 397 | 需要为不同的内核单独指定私有属性。 398 | 399 | 应用场合: 400 | ``` 401 | (1) 图片、视频、音频等连续播放多媒体,当页面处于不可见状态时,控制停止播放。 402 | (2) 实时显示服务器端信息的应用程序,当页面处于不可见状态时,停止向服务器请求数据的处理。 403 | ``` 404 | 405 | >2. `FullScreen API` : 将页面整体或页面中某个局部区域设为全屏显示状态 406 | 需要为不同的内核单独指定私有属性,同时可以使用伪类 `:fullscreen` 来为全屏元素单独设定 `CSS` 样式。 407 | 408 | >3. 鼠标指针锁定 `API` : `pointerlockchange` 409 | >4. JS动画: `requestAnimationFrame` 410 | >5. 检测页面变化: `MutationObserve()` 411 | ``` 412 | var mo=new window.MutationObserver(onchange); 413 | var options={childList:true}; 414 | mo.observe(div, options); 415 | ``` 416 | >6. `Promise` 417 | 418 | >7. `Beacon API` : 允许开发者在离开页面触发的 `unload` 事件回调函数中向服务器端发送携带少量数据的异步请求, 419 | 并且此请求不会阻塞 `unload` 事件回调中的其他代码,不需要用户做出任何等待就可以立即看见下一个页面。 420 | -------------------------------------------------------------------------------- /读书笔记/HTML5与CSS3权威指南(下).md: -------------------------------------------------------------------------------- 1 | # HTML5与CSS3权威指南(下) 2 | 3 | --- 4 | 5 | - `before` 与 `after` 6 | 7 | >1. `before` 是在元素标签内容的前面插入,`after` 则是在元素标签内容的后面插入 8 | 9 | >2. 可插入的元素:<br> 10 | (1) 文本字符串: `content:'text'` <br> 11 | (2) 图片文件:`content:url(mark.png)`<br> 12 | (3) 项目编号:`content: counter(mycounter)`<br> 13 | 14 | >3. 插入项目编号扩充 15 | 16 | (1) 插入连续编号的时候,需要在元素上设置 `counter-increment`<br> 17 | ```css 18 | h1:before{ 19 | content: counter(mycounter); 20 | } 21 | /* 可以在项目编号前后增加其他文本字符串 */ 22 | h1.sp.before{ 23 | content: '第'counter(mycounter)'个'; 24 | /* 应用样式 */ 25 | color:skyblue; 26 | } 27 | h1{ 28 | counter-increment:mycounter; 29 | } 30 | ``` 31 | 32 | (2) 指定编号的种类:数字编码(默认)、字母编号(大小写)、罗马数字(大小写)编号:<br> 33 |   `content:counter(mycounter, type)` 34 | ```css 35 | h1:before { 36 | /* 指定大写英文字母 */ 37 | content:counter(mycounter, upper-alpha); 38 | color:greenyellow; 39 | } 40 | h2:brfore { 41 | /* 指定小写罗马字母 */ 42 | content:counter(mycounter, lower-roman); 43 | font-size: 18px; 44 | } 45 | ``` 46 | 47 | (3) 编号嵌套 48 | 49 | 50 | >4. 使用 `content: none` 或者 `content: normal` 指定个别元素不进行插入 51 | ```css 52 | h2:before { 53 | content:'text'; 54 | color:skyblue; 55 | } 56 | h2.foo:before { 57 | content:none; 58 | } 59 | /* 与 none 所起的作用完全相同,但是 normal 在其他方面还有更多的用处,这里只是其中之一 */ 60 | h2.bar:before { 61 | content:normal; 62 | } 63 | ``` 64 | 65 | - 字体 66 | >1. 使用客户端字体 67 | 使用客户端字体的好处是,可以让浏览在显示字体的时候,首先在用户客户端查找,如果存在就直接使用<br> 68 | 客户端的字体,否则才下载服务器端的字体,**优化性能和用户体验**。 69 | ```css 70 | @font-face { 71 | font-family: MyHelvetica; 72 | /* 指定客户端本地的字体 */ 73 | src: local('Helvetica Neue'); 74 | /* 如果客户端没有找到,才会下载服务器上的字体 */ 75 | url(MyOpenModernaRegular.ttf); 76 | } 77 | ``` 78 | 79 | >2. 修改字体种类而保持字体尺寸不变 : `font-size-adjust` 80 | ```css 81 | div{ 82 | font-size: 16px; 83 | font-family: Times New Romans; 84 | font-size-adjust: 0.46; 85 | } 86 | ``` 87 | **`font-size-adjust` 的值就是当前字体的 `aspect` 值,根据这个值就可以计算字体的大小。** 88 | 89 | >3. 使用 `rem` 单位定义字体大小 90 | 一般浏览器中,默认字体大小为 `16px`,可以用以下方法获取: 91 | ```css 92 | var ele=document.getElementById('bdshare_l'); 93 | /* 兼容 IE 和 webkit */ 94 | var size = ele.currentStyle ? 95 | ele.currentStyle['font-size'] : 96 | window.getComputedStyle(ele, null)['font-size']; 97 | console.log(size); 98 | ``` 99 | 可以根据默认字体大小(例如 `16px` )将根元素字体大小设置为 `10px`, 便于计算使用。 100 | ```css 101 | html { 102 | /* 可以进行微调 */ 103 | font-size: 62.5%; 104 | } 105 | small { 106 | /* 兼容 IE8 及之前的浏览器 */ 107 | font-size: 18px; 108 | /* 相当于 10px */ 109 | font-size: 1.8rem; 110 | } 111 | ``` 112 | 113 | - 盒模型 114 | >1. `inline-table` <br> 115 | 此元素会作为内联表格来显示(一般用于 `<table>` ),表格前后没有换行符。 116 | 117 | ```css 118 | table{ 119 | display: inline-table; 120 | /*由于各浏览器对于此元素的对齐方式不同,所以这里统一指定底部对齐 */ 121 | vertical-align: bottom; 122 | } 123 | ``` 124 | 125 | >2. `list-item` <br> 126 | 此元素会作为列表显示,效果类似于 `<li>` 127 | ```css 128 | div { 129 | display: list-item; 130 | /* 指定列表样式,这里指定小圆点,有的浏览器可能不支持 */ 131 | list-style-type: circle; 132 | } 133 | ``` 134 | 135 | - 渐变 136 | >Internet Explorer 9 及之前的版本不支持渐变。 137 | 138 | ```css 139 | /* 线性渐变 */ 140 | div { 141 | /* 从左到右进行红、绿渐变 */ 142 | background: linear-gradient(to right, red, blue); 143 | /* 可以指定渐变开始的位置 */ 144 | /* 从 div 顶端往下 20% 的位置处开始渐变,之前背景为 red 不渐变 */ 145 | /* 渐变持续到 div 元素 100%-30%=70% 的位置处停止,后面使用 blue 不渐变 */ 146 | background: linear-gradient(to right, red 20%, blue 30%); 147 | /* 使用自定义角度渐变, 180deg 表示从上到下的垂直渐变 */ 148 | background: linear-gradient(180deg, red, blue); 149 | /* 重复渐变 */ 150 | background: repeating-linear-gradient(red, blue); 151 | } 152 | 153 | /* 径向渐变 */ 154 | div { 155 | background: radial-gradient(red, blue); 156 | /* 指定渐变的形状, ellipse 表示圆形渐变 */ 157 | background: radial-gradient(ellipse, red, blue); 158 | /* 使用 at 指定渐变开始的位置 */ 159 | background: radial-gradient(at 130px 50px, red, blue); 160 | /* 渐变尺寸 */ 161 | background: radial-gradient(circle closest-side at 130px 50px, red, blue); 162 | background: radial-gradient(circle 20px at 130px 50px, red, blue); 163 | /* 重复渐变 */ 164 | background: repeating-radial-gradient(red, blue); 165 | } 166 | 167 | ``` 168 | 169 | - 动画<br> 170 | 支持 `IE9` 及以上 171 | 172 | >1. `transition` 与 `transform`<br> 173 | 这种动画方法只能进行起始点与结束点之间的平滑过渡,易于完成简单动画,但是不使用于复杂动画。 174 | ```css 175 | /* 可以单独对需要变化的样式进行指定 */ 176 | div { 177 | transition: background-color 1s linear, color 0.5s ease, width 0.8s linear; 178 | } 179 | ``` 180 | 181 | >2. `animations` 与 `@keyframes`<br> 182 | 适合制作复杂动画 183 | ```css 184 | div { 185 | position: absolute; 186 | background-color:yellow; 187 | top:100px; 188 | width:500px; 189 | } 190 | /* 指定动画名称 : mycolor */ 191 | @keyframes mycolor{ 192 | 0% { 193 | background-color: red; 194 | transform: rotate(0deg); 195 | } 196 | 40% { 197 | background-color: darkblue; 198 | transform: rotate(-30deg); 199 | } 200 | 100% { 201 | background-color: skyblue; 202 | transform: rotate(0deg); 203 | } 204 | } 205 | div:hover { 206 | animation-name: mycolor; 207 | animation-duration: 5s; 208 | animation-timing-function: linear; 209 | animation-iteration-count: infinite; 210 | /* 或者进行缩写 */ 211 | animation: mycolor 5s linear infinite; 212 | } 213 | ``` 214 | 215 | - 布局 216 | 217 | >1. 多栏布局<br> 218 | 支持 `IE9` 及以上 219 | 220 | ```css 221 | div { 222 | /* 指定列数 */ 223 | column-count: 3; 224 | /* 指定列之间的间距 */ 225 | column-width: 20px; 226 | } 227 | ``` 228 | 229 | >2. 盒布局 230 | ```css 231 | /* 这种是 2009 年之前的标准,现在已经过时 */ 232 | div { 233 | display: box; 234 | } 235 | /* 这种是 2012 年的标准,最新写法,又称为弹性盒布局 */ 236 | div { 237 | display: flex; 238 | } 239 | ``` 240 | 241 | >3. `calc()` 计算属性 242 | ```css 243 | div { 244 | width: calc(50% - 100px); 245 | } 246 | ``` 247 | 248 | - 其他属性和样式 249 | 250 | >1. `outline` 251 | 252 | 属性: 253 | ``` 254 | outline-color 255 | outline-style : 轮廓线样式,例如 solid dashed dotted 256 | outline-width : 指定的宽度值,或者 thin medium thick, 默认为 medium 257 | outline-offset : CSS3 新增属性,表示轮廓线与元素边框之间的间距 258 | ``` 259 | 260 | >2. 使用 `initial` 属性值 <br> 261 | 取消对元素样式的指定,让各种属性使用默认值,类似于 删除元素的自定义样式 262 | ```css 263 | div { 264 | color: initial; 265 | } 266 | ``` 267 | 268 | >3. 滤镜 269 | ```css 270 | div { 271 | /* 一个灰度滤镜 */ 272 | filter: grayscale(100%); 273 | } 274 | ``` 275 | 276 | -------------------------------------------------------------------------------- /读书笔记/Javascript权威指南与高级编程(2).md: -------------------------------------------------------------------------------- 1 | # DOM 2 | 3 | ## 访问 `NodeList` 的节点数组 4 | 5 | 方括号和 `item()`方法两种手段,一般都是使用前者 6 | 7 | ```js 8 | someNode.childNodes[0] 9 | someNode.childNodes.item(0) 10 | ``` 11 | 12 | ## cloneNode() 13 | 14 | 此方法不会复制添加到 `DOM`节点中的 `JavaScript`属性,例如事件处理程序,只复制特性、子节点(如果明确指定了的话),其他一切都不会复制,但是某些版本的 `IE`可能也会复制事件处理程序,所以最好在复制节点之前,移除所有的事件处理程序 15 | 16 | ## classList 17 | 18 | `HTML5`新增的 `classList`属性,具备以下方法: 19 | 20 | - add(value):将给定的字符串值添加到列表中,如果值已经存在,就不添加了 21 | - contains(value):表示列表中是否存在给定的值,返回 true/false 22 | - remove(value):从列表中删除给定的字符串 23 | - toggle(value):如果列表中存在 value,则删除;如果不存在 value,则添加 24 | 25 | ## hasFocus 26 | 27 | 文档是否获得了焦点,支持度`IE4+` 28 | ```js 29 | var btn = document.querySelector('.btn') 30 | btn.focus() 31 | console.log(document.hasFocus()) // => true 32 | ``` 33 | 34 | ## insertAdjacentHTML(position, htmlStr) 35 | 36 | 支持直接插入一个 `html`字符串,而不用逐步创建一个元素 37 | 38 | 第一个参数取值如下: 39 | 40 | - beforebegin: 在当前元素之前插入一个紧邻的同辈元素 41 | - afterbegin:在当前元素之下插入一个新的子元素或在第一个子元素之前插入新的子元素 42 | - beforeend: 在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素 43 | - afterend: 在当前元素之后插入一个紧邻的同辈元素 44 | 45 | ```js 46 | element.insertAdjacentHTML('beforebegin', '<p>hello world</p>') 47 | ``` 48 | 49 | ## contains & compareDocumentPosition 50 | 51 | 支持度 `IE9+`,都用于比较元素间的相互关系 52 | 53 | `contains`用于检测某节点是否是另外一个节点的后代,返回 `true/false` 54 | ```js 55 | parentElement.contains(childElement) 56 | ``` 57 | 58 | `compareDocumentPosition`可确定节点间如下关系: 59 | 60 | |掩码|节点关系| 61 | |-|-| 62 | |1|无关(给定的节点不在当前文档中)| 63 | |2|居前(给定的节点在DOM树中的位置位于参考节点之前)| 64 | |4|居后(给定的节点在DOM树中的位置位于参考节点之后)| 65 | |8|包含(给定的节点是参考节点的祖先)| 66 | |16|包含(给定的节点是参考节点的后代)| 67 | 68 | ```js 69 | // 结果 20 表示:居后(4) + 被包含(16) 70 | document.documentELement.compareDocumentPosition(document.body) // => 20 71 | ``` 72 | 73 | ## isSameNode && isEqualNode 74 | 75 | 都用于比较两个节点是否相同 76 | 77 | `isEqualNode`,满足下述条件则返回 `true`: 78 | 79 | - 相同的节点类型 80 | - 相同的节点名、节点值、本地名、命名空间URI和前缀 81 | - 相同的子节点 82 | - 相同的属性和属性值(属性可以没有相同的排序方式) 83 | 84 | `isSameNode`,必须是同一个节点才返回 `true`,`DOM 4`中被废弃,比较是否是同一个元素只需要使用等于表达式即可 `===` 85 | 86 | ## 操作内联 style 87 | 88 | - `cssText`: 元素 style的值 89 | ```js 90 | var box1Style = document.querySelector('.box1').style 91 | console.log(box1.cssText) // => "font-size: 20px; padding: 0px 10px" 92 | ``` 93 | - `length`: 元素的 `css`属性的数量,包括内联 `style`,外联等任何形式的样式设置 94 | ```js 95 | box1Style.length 96 | ``` 97 | - `parentRule`: `CSS` 信息的 `CSSRule`对象 98 | - `getPropertyCSSValue(propertyName)`: 返回包含给定属性值的 `CSSRule`对象 99 | - `getPropertyPriority(propertyName)`: 如果给定的属性使用了 `!important`设置,则返回 `!important`,否则返回空字符串 100 | - `getPropertyValue(propertyName)`: 返回给定属性的字符串值 101 | ```js 102 | box1Style.getPropertyValue('font-size') // => "20px" 103 | ``` 104 | - `item(index)`: 返回给定位置的 CSS属性的名称 105 | - `removeProperty(propertyName)`: 从样式中删除给定的属性,只能删除内联样式,如果属性本来就是不存在则静默失败 106 | - `setProperty(propertyName, value, prioryty)`: 将给定属性设置为相应的值,并加上优先权标志(`!important` 或者 空字符串) -------------------------------------------------------------------------------- /读书笔记/你不知道的JavaScript(1).md: -------------------------------------------------------------------------------- 1 | # 你不知道的JavaScript(上卷) 2 | --- 3 | 4 | ## 遮蔽效应 5 | 6 | ### 遮蔽效应的原理 7 | 8 | ```js 9 | function foo(a) { 10 | var b = a * 2; 11 | function bar(c) { 12 | console.log(a, b, c); 13 | } 14 | bar(b * 3); 15 | } 16 | foo(2); 17 | ``` 18 | 19 | 上述代码存在三个逐级嵌套的作用域,分别为全局作用域、foo所创建的作用域和bar所创建的作用域。 20 | 引擎执行`console.log(a, b, c);`声明,并查找`a,b,c`三个变量的引用,首先从最内部的作用域开始查找, 21 | 如果没有找到对应的变量,例如在`bar`所创建的作用域中,并没有找到`a和b`,则往外部作用域查找, 22 | 如果找到了对应的变量,例如在`bar`所创建的作用域中,可以直接找到`b`变量, 23 | 24 | **作用域查找会在找到第一个匹配的标识符时停止**,因为即使在外部作用域再次声明了变量`b`, 25 | JS引擎也都会选择忽略掉,这叫做 **遮蔽效应** 。 26 | 27 | ### 全局属性绕开遮蔽效应 28 | 29 | 全局变量会自动成为全局对象(比如浏览器中的 `window`对象)的属性,因此可以不直接通过全局对象的词法名称, 30 | 而是间接地通过对全局对象属性的引用来对其进行访问。 31 | 32 | ```js 33 | window.a 34 | ``` 35 | 36 | 通过这种技术可以访问那些被同名变量所遮蔽的全局变量。但非全局的变量,如果被遮蔽了,无论如何都无法被访问到。 37 | 38 | **无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。** 39 | 40 | ## 欺骗词法 41 | 42 | 指得是在程序运行时来“修改”(也可以说欺骗)词法作用域。 43 | 使用欺骗词法并不是什么好主意,并且最重要的是:**欺骗词法作用域会导致性能下降** 。 44 | 所以在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失。 45 | 46 | ### `eval` 47 | 48 | `eval()`函数接收一个字符串作为参数,并将其中的内容视为好像在书写时就存在于 49 | 程序中这个位置的代码,换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。 50 | 在执行 `eval(..)` 之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插 51 | 入进来,并对词法作用域的环境进行修改的。引擎只会如往常地进行词法作用域查找。 52 | 53 | ```js 54 | function foo(str, a) { 55 | eval(str); // 欺骗 56 | console.log(a,b); 57 | } 58 | var b=2; 59 | foo('var b=3;',1); 60 | ``` 61 | 62 | 以上代码将会输出`1,3`,这是因为代码句 ` eval(str) ` 实质上就相当于是`var b = 3`, 63 | JS引擎在寻找`a,b`变量的时候,能够在`foo`的作用域内同时找到,所以就不会再访问全局作用域中的`b`了。 64 | 65 | 在严格模式下(`use strict`),`eval()`在运行时有其自己的作用域,意味着其中的声明无法修改所在的作用域。 66 | 67 | ```js 68 | function foo(str) { 69 | 'use strct'; 70 | eval(str); 71 | console.log(a); // ReferenceError: a is not defined 72 | } 73 | foo('var a=2;'); 74 | ``` 75 | 76 | ### `with` 77 | 78 | `with` 通常被当作重复引用同一个对象的多个属性的快捷方式,可以*不需要重复引用对象本身*。 79 | 在严格模式下,`with` 被完全禁止使用。 80 | 81 | ```js 82 | var obj={ 83 | a:1, 84 | b:2, 85 | c:3 86 | }; 87 | ``` 88 | 89 | 以上代码使用字面量方式定义了一个存在三个属性的对象,下面给这三个属性重新赋值,有两种方法: 90 | ```js 91 | obj.a = 2; 92 | obj.b = 3; 93 | obj.c = 4; 94 | ``` 95 | 和下面代码起到的作用其实是一样的,因为`with`改变下面代码大括号中变量的作用域: 96 | ```js 97 | with(obj) { 98 | a=2; 99 | b=3; 100 | c=4; 101 | } 102 | ``` 103 | 104 | 但 `with` 的作用并不仅仅是改变作用域那么简单,有的时候,可能会导致意料之外的结果: 105 | 106 | ```js 107 | function foo(obj) { 108 | with (obj) { 109 | a = 2; 110 | } 111 | } 112 | var o1 = { 113 | a: 3 114 | }; 115 | var o2 = { 116 | b: 3 117 | }; 118 | 119 | foo(o1); 120 | console.log(o1.a); // 2 121 | foo(o2); 122 | console.log(o2.a); // undefined 123 | console.log(a); // 2——不好,a 被泄漏到全局作用域上了! 124 | ``` 125 | 126 | 尽管 `with` 块可以将一个对象处理为词法作用域,但是这个块内部正常的 `var`声明并不会被限制在这个块的作用域中,而是被添加到 `with` 所处的函数作用域中。 127 | 128 | ```js 129 | var obj={ 130 | a:1, 131 | b:2 132 | } 133 | with(obj){ 134 | var c=3; 135 | var b=5; 136 | a:2 137 | } 138 | console.log( obj.a ); // 2 139 | console.log( obj.b ); // 5 140 | console.log( b ); // undefined 141 | console.log( c ); // 3 142 | ``` 143 | 144 | ## 立即执行函数表达式(IIFE) 145 | 146 | ```js 147 | (function{ 148 | console.log('ok') 149 | })(); 150 | 151 | // 上面代码其实和下面这一段,写法上第二个圆括号的位置发生了变化, 152 | // 但在功能上是一致的,没什么区别,**选择哪个全凭个人喜好**。 153 | 154 | (function{ 155 | console.log('ok') 156 | }()); 157 | 158 | ``` 159 | 160 | ## 变量提升,函数声明优先于变量声明 161 | 162 | ```js 163 | foo(); // 1 164 | var foo; 165 | function foo() { 166 | console.log(1); 167 | } 168 | 169 | foo=function() { 170 | console.log(2); 171 | } 172 | ``` 173 | 174 | 以上代码最终将会输出 `1` ,因为在上述的提升过程中,函数首先被提升,然后才是变量, 175 | 第二句的 `var foo` 其实已经因为重复声明,被JS引擎忽略掉了。 176 | 177 | ## `this` 隐式绑定 178 | 179 | 对象属性引用链中只有最顶层或者说最后一层会影响调用位置。 180 | 181 | ```js 182 | function foo() { 183 | console.log(this.a); 184 | } 185 | 186 | var obj2={ 187 | a:42, 188 | foo:foo 189 | } 190 | 191 | var obj1={ 192 | a:2, 193 | obj2:obj2 194 | } 195 | 196 | obj1.obj2.foo(); // 42 197 | ``` 198 | 上述代码将会输出 `42` 而不是 `2` ,这是因为在引用链上,最后一层是 `obj2` ,而 `obj2` 的 `a` 属性值就是 `42`。 199 | 200 | ## `this` 隐式绑定丢失 201 | 202 | 一个最常见的 `this` 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定, 203 | 从而把 `this` 绑定到全局对象或者 `undefined` 上,取决于是否是严格模式。 204 | 205 | ```js 206 | function foo() { 207 | console.log(this.a); 208 | } 209 | 210 | var obj={ 211 | a:2, 212 | foo:foo 213 | } 214 | 215 | var bar=obj.foo; // 函数别名 216 | var a='oops,global'; 217 | 218 | bar(); // 'oops,global' 219 | ``` 220 | 上述代码中,虽然 `bar` 是 `obj.foo` 的一个引用,但是实际上,它引用的是 `foo` 函数本身,因此此时的 221 | `bar()` 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。 222 | 223 | 一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时: 224 | 225 | ```js 226 | function foo() { 227 | console.log(this.a); 228 | } 229 | function doFoo(fn) { 230 | // fn 其实引用的是 foo 231 | fn(); // <-- 调用位置! 232 | } 233 | var obj = { 234 | a: 2, 235 | foo: foo 236 | }; 237 | var a = "oops, global"; // a 是全局对象的属性 238 | doFoo(obj.foo); // "oops, global" 239 | ``` 240 | 241 | `this`绑定地隐式丢失是非常常见的,一般发生在为函数引用声明一个变量作为别名, 242 | 或者将函数引用作为函数的参数传递,而且调用回调函数的函数可能会修改`this`。 243 | 244 | ## 判断 `this` 245 | 246 | - new 247 | 248 | 函数是否在 `new` 中调用( `new` 绑定)?如果是的话 `this` 绑定的是新创建的对象。 249 | 250 | ```js 251 | var bar = new foo(); 252 | ``` 253 | 254 | - `call` `apply` 255 | 256 | 函数是否通过 `call 、 apply` (显式绑定)或者硬绑定调用?如果是的话, `this` 绑定的是指定的对象。 257 | 258 | ```js 259 | var bar = foo.call(obj2) 260 | ``` 261 | 262 | - 隐式绑定 263 | 264 | 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话, this 绑定的是那个上下文对象。 265 | 266 | ```js 267 | var bar = obj1.foo(); 268 | // 上述代码,foo()在全局的上下文对象中被调用,使用 bar 作为 函数别名, 269 | // 所以this 其实就是被隐式绑定到了全局作用域上。 270 | ``` 271 | 272 | - 默认绑定 273 | 274 | 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 `undefined`,否则绑定到全局对象。 275 | 276 | - 箭头函数 277 | 278 | 箭头函数(`=>`)不使用以上 `this` 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 `this`,(无论 `this` 绑定到什么),这其实和 `ES6` 之前代码中的 `self = this `机制一样。并且 箭头函数的绑定无法被修改。 279 | 280 | - `this`绑定判断的例外情况 281 | 282 | 如果把 `null` 或者 `undefined` 作为 `this` 的绑定对象传入 `call apply` 或者 `bind`,这些值在调用时会被忽略,实际应用的是默认绑定规则 283 | ```js 284 | function foo() { 285 | console.log(this.a); 286 | } 287 | var a=2; 288 | foo.call(null); // 2 289 | ``` 290 | 291 | 需要注意的是,总是使用 `null` 或者 `undefined` 来忽略`this`的绑定可能产生一些副作用。 292 | 一种 **更安全** 的做法是传入一个特殊的对象,把 `this` 绑定到这个对象不会对你的程序产生任何副作用。 293 | 它就是一个空的非委托的对象,而在JS中,创建一个空对象的最简单方法就是`Object.create(null)` 294 | `Object.create(null)` 和 `{}` 很像,但是并不会创建 `Object.prototype` 这个委托,所以它比 `{}` **更空** 295 | 296 | ```js 297 | function foo(a,b) { 298 | console.log( "a:" + a + ", b:" + b ); 299 | } 300 | // 我们的 DMZ 空对象 301 | var ø = Object.create( null ); 302 | // 这里之所以使用一个空对象,其实就是为了能把数组展开成参数传入函数 303 | foo.apply( ø, [2, 3] ); // a:2, b:3 304 | // 使用 bind(..) 进行柯里化 305 | var bar = foo.bind( ø, 2 ); 306 | bar( 3 ); // a:2, b:3 307 | ``` 308 | 309 | ## 复制对象 310 | 311 | 考虑下面代码: 312 | ```js 313 | var obj1={a:'aa',b:'bb'} 314 | var obj2=obj1 315 | obj2===obj1 // true 316 | ``` 317 | 318 | 因为实质上这里`obj2`和`obj1`实质上都是引用内存中同样的一个地址,所以它们是完全相同的,`obj2`的改变,能够同时让`obj1`同时发生相同的变化,这并不是复制,因为没有产生任何副本。 319 | 320 | - `JSON.parse` 321 | 322 | 对于一个能够被`JSON序列化`并且解析的对象来说,有一种巧妙的复制方法: 323 | ```js 324 | var obj1={a:'aa',b:'bb'} 325 | var obj2=JSON.parse(JSON.stringify(obj1)); 326 | obj2==obj1 // false 327 | ``` 328 | 这个时候,`obj2`所指向的内存中的地址,实质上已经与`obj1`不再相同了。 329 | 330 | - `slice` 331 | 332 | 如果是复制数组,那么可以借助`slice()`方法: 333 | ```js 334 | var a=[1,2,3]; 335 | var b=a; 336 | var c=a.slice(); 337 | b === a; // true 338 | c === a; // false 339 | ``` 340 | 341 | ## 属性描述符 342 | 343 | ### `getOwnPropertyDesciptor` 344 | 345 | 从`ES5`开始,可使用 `Object.getOwnPropertyDescriptor(obj, attr)` 来检测属性特性。 346 | ```js 347 | var obj1={ 348 | a:2 349 | }; 350 | 351 | Object.getOwnPropertyDescriptor(obj1, 'a'); 352 | 353 | // 输出: 354 | { 355 | value: 2, 356 | writable: true, 357 | enumerable: true, 358 | configurable: true 359 | } 360 | ``` 361 | 将一个对象属性的 `writable` 和 `configurable` 全部设置为 `false`,就可以创建一个真正的常量属性(不可修改、定义或删除)。 362 | 363 | ### 禁止扩展 364 | 365 | 如果你想禁止一个对象添加新属性并且保留已有属性,可以使用`Object.preventExtensions()`。 366 | 367 | ```js 368 | var obj1={ 369 | a:2 370 | } 371 | 372 | Object.preventExtensions(obj1); 373 | 374 | obj1.b=3; 375 | obj1.b // undefined 376 | ``` 377 | 378 | ### 密封 379 | 380 | `Object.seal()` 方法会创建一个 **密封**对象,这个方法实际上会在一个现有对象上调用 381 | **`Object.preventExtensions()`**,并把所有现存属性标记为 **`configurable:false`** 382 | 383 | 384 | ### 冻结 385 | 386 | `Object.freeze()` 方法会创建一个 **冻结**对象,这个方法实际上会在一个现有对象上调用 387 | **`Object.seal()`**,并把所有数据访问属性标记为 **`writable:false`** 388 | **此方法是可以应用在对象上的级别最高的不可变性** 389 | 390 | 391 | ## 检测对象及其属性的存在性 392 | 393 | - 在不访问属性值的情况下判断对象中是否存在这个属性 394 | ```js 395 | var obj={ 396 | a:2 397 | } 398 | 399 | 'a' in obj // true 400 | 'b' in obj // false 401 | 402 | obj.hasOwnProperty('a') // true 403 | obj.hasOwnProperty('b') // false 404 | ``` 405 | 406 | - 检测对象的枚举属性 407 | 408 | ```js 409 | var myObject = { }; 410 | Object.defineProperty( 411 | myObject, 412 | "a", 413 | // 让 a 像普通属性一样可以枚举 414 | { enumerable: true, value: 2 } 415 | ); 416 | Object.defineProperty( 417 | myObject, 418 | "b", 419 | // 让 b 不可枚举 420 | { enumerable: false, value: 3 } 421 | ); 422 | 423 | myObject.b; // 3 424 | ("b" in myObject); // true 425 | myObject.hasOwnProperty("b"); // true 426 | Object.getOwnPropertyNames(myObject); // ["a", "b"] 427 | 428 | // 第一种检测枚举的方法 429 | for (var k in myObject) { 430 | console.log(k, myObject[k]); 431 | } 432 | // "a" 2 433 | 434 | // 第二种检测枚举的方法 435 | myObject.propertyIsEnumerable("a"); // true 436 | myObject.propertyIsEnumerable("b"); // false 437 | 438 | // 第三种检测枚举的方法 439 | Object.keys(myObject); // ["a"] 440 | ``` 441 | -------------------------------------------------------------------------------- /读书笔记/锋利的JQuery.md: -------------------------------------------------------------------------------- 1 | # 锋利的JQuery(第2版) 2 | --- 3 | 4 | ## JQuery对象和DOM对象的相互转换 5 | 6 | 首先约定编码规范,如果获取的是`jQuery`对象,则在变量前面加上 `$` : 7 | ```js 8 | var $variable = $('#cr') 9 | ``` 10 | 11 | ## jQuery对象转为DOM对象 12 | 13 | - jQuery对象是一个类似数组的对象,可以通过`[index]`的方法得到相应的DOM对象。 14 | 15 | ```js 16 | var $cr = $('#cr'); // jQuery对象 17 | var cr = $cr[0] // DOM对象 18 | ``` 19 | 20 | - 使用jQuery本身提供的 `get(index)` 方法。 21 | 22 | ```js 23 | var $cr = $('#cr'); // jQuery对象 24 | var cr = $cr.get(0) // DOM对象 25 | ``` 26 | 27 | ## DOM对象转为jQuery对象 28 | 29 | 只需要使用 `$()`将DOM对象包装起来即可。 30 | ```js 31 | var cr = document.getElementById('cr); // DOM对象 32 | var $cr = $(cr); // jQuery对象 33 | ``` 34 | 35 | ## `$()`包装选择器 36 | 37 | >使用 `jQuery` 自带的选择器,由于其内部的预防措施,所以即使是选取了网页中并不存在的元素, 38 | 也不会报错。同时,因为 `$()` 选择器选取的一个对象类型,所以想要判断选取的 `jQuery` 对象 39 | 是否存在,不能直接判断,需要将其转换为`DOM`对象 ,或者判断其长度。 40 | 41 | ```js 42 | // 这样判断永远都返回 true,是行不通的 43 | if ($('#tt')) { 44 | // do something 45 | } 46 | ``` 47 | 48 | 需要使用以下两种方法: 49 | ```js 50 | if ($('#tt').length > 0) { 51 | // do something 52 | } 53 | 54 | // 或者 55 | if($('#tt)[0]) { 56 | // do something 57 | } 58 | ``` 59 | 60 | ## 选择器中含有空格的注意事项 61 | 62 | ```js 63 | // .text 和 :hidden 之间存在空格,空格在 jQuery 选择器中就代表后代选择器, 64 | // 所以最后选取到的结果其实是 class 为 text 元素下的隐藏子元素 65 | var $a = $('.test :hidden'); 66 | 67 | // .text 和 :hidden 之间不存在空格,选取到的结果就是 class 为 test 的隐藏元素 68 | var $b = $('.test:hidden'); 69 | ``` 70 | 71 | ## 判断是否含有某个样式 72 | 73 | ```js 74 | $('p').hasClass('tt'); 75 | ``` 76 | 77 | `hasClass()` 这个方法实质上是在 `jQuery` 的内部调用了 `is()` 方法, 78 | 也就是说上面一点代码等于下面这段: 79 | ```js 80 | // 注意方法参数中类名前需要加点号 `.` 81 | $('p').is('.tt') 82 | ``` 83 | 84 | ## `val()` 方法 85 | 86 | `val()` 方法可以返回任何能够带有 `value` 属性的`HTML`标签,如果带有参数,则具备设置值的功能, 87 | 除此之外, `val()` 还有一个功能,就是**它能使 `select checkbox radio` 标签相应的项被选中。** 88 | 89 | ```html 90 | <!-- 单选下拉框: --> 91 | <select id='single'> 92 | <option>first</option> 93 | <option>second</option> 94 | </select> 95 | ``` 96 | ```js 97 | // 选中第二个选项 98 | $('#single').val('second'); 99 | ``` 100 | ```html 101 | <!-- 多选下拉框: --> 102 | <select id='multiple' multiple='multiple' style='height:120px;'> 103 | <option>first</option> 104 | <option>second</option> 105 | <option>third</option> 106 | </select> 107 | ``` 108 | ```js 109 | // 选中第一、二个选项 110 | $('#multiple').val(['first','second']); 111 | ``` 112 | 113 | ```html 114 | <!-- 多选框: --> 115 | <input type="checkbox" value='check1'> 多选框1 116 | <input type="checkbox" value='check2'> 多选框2 117 | <input type="checkbox" value='check3'> 多选框3 118 | ``` 119 | ```js 120 | // 选择第二、三个多选框 121 | $(':check').val(['check1','check2']); 122 | ``` 123 | ```html 124 | <!-- 单选框: --> 125 | <input type="radio" value="radio1">单选1 126 | <input type="radio" value="radio2">单选2 127 | ``` 128 | ```js 129 | // 选择第一个单选按钮 130 | $(':radio').val(['radio1']) 131 | ``` 132 | 133 | ## 取得最近的匹配父元素(包括自身也会检查一遍),逐级向上查找。 134 | 135 | ```js 136 | $(document).bind('click', function(e) { 137 | $(e.target).closest('li').css('color','red'); 138 | }) 139 | ``` 140 | 141 | ## `CSS-DOM` 操作 142 | 143 | ```js 144 | // 单个元素 145 | $('p').css('color', 'red'); 146 | 147 | // 多个元素,注意,属性名如果不加引号可能需要使用驼峰写法, 148 | // 如果加了引号,则是否使用驼峰写法可以任选,规范期间,建议加上引号 149 | $('p').css({'font-size':'30px', 'fontWeight':'bold', backgroundColor:'red', 'color':'red'}) 150 | ``` 151 | 152 | ## `css()` 方法和 `height()` 方法获取元素高度的区别 153 | 154 | ```js 155 | $('div').css('height'); 156 | $('div').height(); 157 | ``` 158 | 159 | `css()` 方法获取的高度值与样式的设置有关,也就是说可能会得到 `auto` 或者 `10px` 之类的字符串, 160 | 而 `height()` 获得的高度则是元素在页面中的实际高度,与样式的设置不存在必然联系,并且不带单位。 161 | 与此类似的还有元素宽度(`width()`)的获取等。 162 | 163 | ## 文档加载简写代码 164 | 165 | 以下三种写法完全相同: 166 | ```js 167 | // 完整写法 168 | $(document).ready(function(){}); 169 | 170 | // 简写1 171 | $().ready(function(){}); 172 | 173 | // 简写2 174 | $(function(){}) 175 | ``` 176 | 177 | ## 事件绑定 178 | 179 | ### 绑定的事件类型 180 | 181 | ``` 182 | blur focus load resize scroll unload click dblclick mousedown mouseup 183 | mousemove mouseover mouseout mouseenter mouseleave change select submit 184 | keydown keypress keyup error 185 | ``` 186 | 187 | 相比于原生 `JavaScript` 事件来说,`jQuery` 的事件绑定类型少了 `on` 188 | 189 | ### 事件绑定简写 190 | 191 | `jQuery` 提供了一套事件绑定的简写方法,简写的唯一作用就是缩减了代码量 192 | ```js 193 | $('.tt').click(function(){ 194 | 195 | }) 196 | 197 | // 等同于: 198 | 199 | $('.tt').bind('click', function(){ 200 | 201 | }) 202 | ``` 203 | 204 | ### 合成事件 205 | 206 | `jQuery`有两个合成事件: `hover()` & `toggle()` 207 | 208 | ```js 209 | hover(enter, leave) 210 | 211 | $('.tt').hover(function(){ 212 | // 光标移入触发的事件 213 | }, function(){ 214 | // 光标移出触发的事件 215 | }) 216 | ``` 217 | ```js 218 | // 用于模拟鼠标连续单击事件,第1次鼠标单击触发指定的第1个函数(fn1),第2次单击触发指定 219 | // 的第2个函数(fn2),依次触发下去,直到最后一个,然后如果继续触发,则从头开始重复触发。 220 | toggle(fn1, fn2,...fnN): 221 | 222 | $('.tt').toggle(function(){ 223 | // 第1次事件触发时执行的函数 224 | }, function(){ 225 | // 第2次事件触发时执行的函数 226 | }) 227 | ``` 228 | 229 | ### 触发自定义事件 230 | 231 | ```js 232 | // 首先定义自定义事件 233 | $('#btn').bind('myClick', function(){ 234 | console.log('ok'); 235 | }) 236 | 237 | // 使用 trigger 触发自定义事件 238 | $('#btn').trigger('myClick'); 239 | ``` 240 | 241 | ### 为事件添加命名空间,便于管理 242 | 243 | 批量卸载事件 244 | ```js 245 | $(function(){ 246 | // 命名空间为 plugin 247 | $('div').bind('click.plugin', function(){ 248 | $('body').append('<p>click事件</p>') 249 | }); 250 | // 命名空间为 plugin 251 | $('div').bind('mouseover.plugin', function(){ 252 | $('body').append('<p>mouseover事件</p>'); 253 | }); 254 | // 没有设置命名空间 255 | $('div').bind('dblclick', function(){ 256 | $('body'.append('<p>dblclick事件</p>')) 257 | }); 258 | 259 | // 点击 button1 时,将卸载掉 click 和 mouseover 事件,因为它们属于 plugin 命名空间 260 | // dblclick事件则不受影响 261 | $('.button1').click(function(){ 262 | $('div').unbind('.plugin'); 263 | }); 264 | }) 265 | ``` 266 | 267 | ### 触发指定命名空间下的事件 268 | 269 | ```js 270 | // 命名空间为 plugin 271 | $('div').bind('click.plugin', function(){ 272 | $('body').append('<p>click事件</p>') 273 | }); 274 | // 没有设置命名空间 275 | $('div').bind('click', function(){ 276 | $('body').append('<p>click2事件</p>'); 277 | }); 278 | 279 | // 点击 button1 时,以上两个绑定事件都将被触发 280 | $('.button1').click(function(){ 281 | $('div').trigger("click"); 282 | }); 283 | // 点击 button12时,只触发 plugin 命名空间内的 click 事件 284 | $('.button2').click(function(){ 285 | $('div').trigger("click.plugin"); 286 | }); 287 | ``` 288 | 289 | ### 属性值获取:`attr()` 与 `prop()` 290 | 291 | `attr()` 能够获取及设置元素的属性值,但是某些时候,比如获取`input`的`disable`属性时, 292 | 可能会出现问题。 在有些浏览器中,只要写了`disable` 属性就可以,而有些则需要写`disable='disable'`。 293 | 所以,从1.6版本开始,`jQuery` 提供新的方法来获取这些属性。 294 | 使用 `prop()` 时,返回值是标准属性:`true/false`,比如`$('#checkbox').prop('disable')`, 295 | 不会像`attr()`那样返回`disable`或者空字符串`""`,而是返回`true`或者`false`,进行属性赋值的时候同样如此。 296 | 297 | >1. 只添加属性名称该属性就会生效时,应该使用`prop()`,例如`checkbox`的`disable`属性。 298 | >2. 只存在`true/false`的属性时应该使用`prop()` 299 | 300 | ### 获取被点击的 `li` 元素在 `ul` 中的索引。 301 | 302 | ```js 303 | var $li = $('li'); 304 | $li.click(function(){ 305 | // 第一种获取方法 306 | var index1 = $(this).index(); 307 | 308 | // 第二种获取方法 309 | var index2 = $li.index(this); 310 | }) 311 | ``` 312 | 313 | ### `jQuery` 插件 314 | 315 | - 表单验证插件:`jquery.validate.js`,一般可以与 `jquery.metadata.js` 配合使用,方便开发。 316 | 317 | (1) 内置验证规则:必填、数字、E-Mail、URL、信用卡等19类 318 | (2) 自定义验证规则 319 | (3) 验证信息提示:内置提示 和 自定义提示 320 | (4) 可以通过 keyup blur 等事件触发 321 | 322 | - `form`插件: `jQuery.form.js`,支持`ajax`表单提交和`ajax`文件上传。 323 | 324 | - 模态窗口插件: `jquery.simplemodal.js` 325 | 326 | - 管理网站`cookie`:`jquery.cookie.min.js` 327 | 328 | ### 性能优化 329 | 330 | - 选择器 331 | 332 | 性能从高到低: 333 | 334 | >id > tagName > class > attribute( 例如:$("[type='button']") ) > 伪选择器(例如:$(':hidden')) 335 | 336 | - 尽量缓存选择器对象 337 | 338 | ```js 339 | // 最好这样: 340 | var $li = $('ul>li'); 341 | $li.css('color','red'); 342 | 343 | // 而不是: 344 | $('ul>li').css('color','red'); 345 | ``` 346 | 347 | - 减少 `DOM` 操作 348 | 349 | ```js 350 | // 最好这样(一次性插入拼接的元素): 351 | var temp_li="", $list=$('ul'); 352 | for(var i=0; i<100; i++){ 353 | temp_li += '<li>' + i + '</li>'; 354 | } 355 | // 注意这里使用了 html() 方法 356 | $list.html(temp_li); 357 | 358 | // 而不是这样(循环多次修改DOM): 359 | var $list=$('ul'); 360 | for(var i=0; i<100; i++){ 361 | $list.append('<li>' + i + '</li>'); 362 | } 363 | // 注意这里使用了 html() 方法 364 | $list.html(temp_li); 365 | ``` 366 | 367 | - 使用简单的 `for` 或 `while` 循环,而不是 `jQuery` 的 `$.each()` 368 | 369 | ```js 370 | // 最好这样: 371 | var array = new Array(); 372 | for(var i=0; i<100; i++) { 373 | array[i] = i; 374 | } 375 | 376 | // 而不是: 377 | var array = new Array(100); 378 | $each(array, function(i){ 379 | array[i] = i; 380 | }) 381 | ``` 382 | 383 | - 使用父级元素委托冒泡事件 384 | 385 | ```js 386 | // 最好这样: 387 | $('ul').click(function(e){ 388 | var $clicked = $(e.target); 389 | $clicked.css('background', 'red'); 390 | }); 391 | 392 | // 或者使用 jQuery 1.7 提供的 on() 方法: 393 | $('ul').on('click', 'td', function(){ 394 | $(this).css('background', 'red'); 395 | }); 396 | 397 | // 而不是: 398 | $('ul li').click(function(){ 399 | $(this).css('background', 'red'); 400 | }); 401 | ``` 402 | 403 | - 将需要重复多次使用的代码片段创建成插件 404 | 405 | - 使用 `join()` 拼接字符串 406 | 407 | ```js 408 | // 最好这样: 409 | var array=[], $list=$('ul'); 410 | for(var i=0; i<20; i++){ 411 | array[i] = '<li>'+i+'</li>'; 412 | // 或者 array.push('<li>'+i+'</li>') 413 | } 414 | $list.html(array.join('')); 415 | 416 | // 而不是: 417 | var temp_li="", $list=$('ul'); 418 | for(var i=0; i<100; i++){ 419 | temp_li += '<li>' + i + '</li>'; 420 | } 421 | $list.html(temp_li); 422 | ``` 423 | 424 | - 合理利用 `HTML5` 的 `Data` 属性 425 | 426 | ```html 427 | <div id="dl" data-role='page' data-options="{'name':'John'}"></div> 428 | ``` 429 | 430 | ```js 431 | $('#dl').data('role'); // page 432 | $('#dl').data('options').name; // John 433 | ``` 434 | 435 | - 尽量使用原生 `JavaScript` 方法,因为在很多时候这可以避免调用许多不需要使用的方法。 436 | 437 | - 使用 `$.proxy()` 改变 `this` 指向 438 | 439 | ```html 440 | <div id="panel"> 441 | <button>1234</button> 442 | </div> 443 | <span>456</span> 444 | ``` 445 | 446 | ```js 447 | // 如果是下面这段代码,点击之后,消失的将是 `button` 元素 448 | $('#panel button').click(function(){ 449 | $(this).fadeOut(); 450 | }) 451 | 452 | // 如果使用 `$.proxy()` ,则消失的将是 `span` 元素 453 | $('#panel button').click($.proxy(function(){ 454 | $(this).fadeOut(); 455 | },$('span'))) 456 | ``` 457 | --------------------------------------------------------------------------------