├── css ├── css基础.md └── 多方案实现垂直水平居中.md ├── eslint └── 常见错误.md ├── html ├── html基础.md └── 输入url到页面加载.md ├── http ├── Etag.md ├── http基础.md ├── http状态码.md └── keep-alive.md ├── js ├── es6.md ├── js事件.md ├── js垃圾回收机制.md ├── js基础.md ├── js基础2.md ├── 原生属性 proxy.md ├── 原生属性.md ├── 数组大全.md └── 模块化.md ├── node ├── QQ截图20180828211949.png ├── QQ截图20180828212348.png └── node+IIS配置https.md ├── react ├── react-router.md ├── react取消eslint.md └── react生命周期.md ├── sass └── sass不支持中文.md ├── vue ├── virtual-dom │ ├── README.md │ ├── diff-1.jpg │ ├── diff-2.jpg │ ├── diff-3.jpg │ ├── diff-4.jpg │ ├── diff-5.jpg │ ├── diff-6.jpg │ ├── html-parse.md │ ├── parse.md │ ├── patch.md │ └── vdom.md ├── vue 组件通信五种方式.md ├── vue基础.md ├── 双向绑定原理及实现.md └── 讲解provide和inject.md ├── webpack └── 基本知识.md ├── 其他 ├── 技巧.md ├── 理解手写promise │ └── index.html ├── 问问问....超大问题.md └── 问题的解决.md ├── 安全 └── 前端鉴权.md ├── 性能优化 ├── css优化.md ├── html优化.md └── js优化.md ├── 浏览器 └── 基础1.md └── 移动端 ├── 基础篇.md ├── 移动端资源及常见问题.md └── 高清适配方案.md /css/css基础.md: -------------------------------------------------------------------------------- 1 | ```css 2 | [class ^=val] 属性值以val开头的class节点元素 3 | 4 | [class $=val] 属性值以val结尾的class节点元素 5 | 6 | [class *=val] 属性值包含了val的class节点元素 7 | 8 | p:first-of-type 在父元素的子元素中选择第一个p元素 9 | 10 | p:last-of-type 在父元素的子元素中选择最后一个p元素 11 | 12 | p:only-of-type(n) 在父元素的子元素中选择唯一的p元素 13 | 14 | p:nth-last-of-type(n) 在父元素中的子元素中选择倒数的第n个p元素 15 | 16 | 17 | p:only-child :在p元素中选择只有一个子元素 18 | 19 | p:last-child 在p元素中选择的最后一个子元素 20 | 21 | p:nth-child(n || even || odd) 在父元素中选择第n个p元素 || 偶数 || 基数 22 | 23 | p:nth-last-child(n) 在p元素中选择倒数的第n个子元素 24 | 25 | 26 | :root 选择文本的根元素 27 | 28 | p:empty 选择没有子元素的p元素 29 | 30 | p:enable 选择每个未禁用的p元素 31 | 32 | p:disable 选择每个被禁用的p元素 33 | 34 | p:checked 选择每个被选中的p元素 35 | 36 | p:target 选择当前活动的p元素 37 | 38 | 39 | :not(p) 选择不包括p元素的 每一个元素 40 | 41 | ::selection 用户选取的部分 42 | 43 | :focus 获得焦点的元素器 44 | 45 | 46 | 47 | p:first-line 对p元素的第一行文本进行格式化,只能用于块级元素 48 | 49 | p:first-letter 对p元素的首字母设置特殊样式,只能用于块级元素 50 | 51 | p:before 在p元素的内容前面插入新内容, 52 | 53 | p:after 在p元素的后面插入新内容 54 | ``` 55 | 56 | ### 如何创建块级格式化上下文,BFC有什么用? 57 | 理解:BFC是css布局的一个概念,是一块独立的渲染区域,一个环境,里面的元素不会影响到外部的元素 58 | 59 | 如何创建: 60 | 61 | - 根元素(body) 62 | 63 | - 浮动元素 (float不为`none`) 64 | 65 | - 绝对定位元素 (position取值为`absolute`或`fixed`) 66 | 67 | - 元素display取值为`inline-block`,`inline-flex`,`flex`,`table-caption`,`table-cell`之一 68 | 69 | - overflow 除`visible`以外的元素 70 | 71 | `BFC布局规则:` 72 | 1. 内部的元素会在垂直方向,一个接一个地放置。 73 | 2. 在同一个BFC容器里的两个相邻的元素的margin会发生重叠 74 | 3. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此, 文字环绕效果,设置float 75 | 4. BFC的区域不会与float元素重叠。 76 | 5. 计算BFC的高度,浮动元素也参与计算 77 | 78 | `BFC作用:` 79 | 1. 自适应两栏布局 80 | 2. 可以阻止元素被浮动元素覆盖 81 | 3. 可以包含浮动元素---清除内部浮动 原理::触发父div的BFC属性,使下面的子div都处在父div的同一个BFC区域之内 82 | 4. 创建属于不同的BFC时,可以阻止margin重叠 83 | 84 | 85 | ### 去除inline-block元素间的缝隙 86 | 87 | inline-block水平呈现的元素间,换行显示或空格分隔的情况下会有间距(html中元素间空格,回车,html注释都会造成缝隙) 88 | 89 | - 使用margin负值,不过对于12像素大小的上下文,Arial字体的margin负值为-3像素,Tahoma和Verdana就是-4像素,而Geneva为-6像素 90 | 91 | - 使用无闭合标签,(但是为了兼容ie6/7,最后一个列表的结束标签需要加上) 92 | 93 | - font-size:0 94 | 95 | - letter-spacing为负值,但是有些有浏览器最小间距1像素,在小就会还原了 96 | 97 | - word-spacing为负值 98 | 99 | - 移除空格 100 | 101 | 102 | ### `display` , `float` , `position` 之间的关系 103 | 104 | 当display:none的时候,就会忽略掉float和position的值,因为此时元素已经消失在渲染树中,元素不占位 105 | 106 | 否则,当position为absolute或fixed的时候,元素就是绝对定位,float的值是none 107 | 108 | 否则,看float不是none的时候,此时元素是浮动元素。 109 | 110 | 否则,如果元素是根元素,,其它情况下display的值为指定值。 111 | 112 | 总结来说,绝对定位,浮动,根元素都需要调整display 113 | 114 | ### CSS 权重优先级顺序简单表示为: 115 | 116 | `!important` > `行内样式` > `ID` > `类、伪类、属性` > `标签名` > `继承` > `通配符` 117 | 118 | 最大 > 1000 > 100 > 10 > 1 > 0 119 | 120 | 121 | `@import` 的性能优于`link`,因为`@import`在页面加载完后才执行,所以在页面加载的同时不会去并行加载这个css文件,适合加载未来需要的文件。 122 | 123 | `link` 在页面加载的同时就会同时加载,浏览器对并行下载有限制,适合 马上就需要加载的文件,并且要放置页面头部。 124 | 125 | ### 盒子模型 126 | ie盒子模型: width = content + padding + border 127 | w3c盒子模型: width = content + border 128 | 129 | box-sizing: content-box 是IE盒子模型 130 | box-sizing: border-box 是W3C盒子模型 131 | 132 | 区别在于 : IE盒子模型: 高 或 宽 = content + padding + border 133 | 134 | ### transition和animation的区别 135 | Animation和transition大部分属性是相同的,他们都是随时间改变元素的属性值,他们的主要区别是transition需要触发一个事件才能改变属性, 136 | 而animation不需要触发任何事件的情况下才会随时间改变属性值,并且transition为2帧,从from .... to,而animation可以一帧一帧的。 137 | 138 | ### 超链接访问过后hover样式就不出现了 139 | 被点击访问过的超链接样式不再具有hover和active了。 140 | 解决方法是改变CSS属性的排列顺序:L-V-H-A ( love hate ): a:link {} a:visited {} a:hover {} a:active {} 141 | 142 | ### css3新特性 143 | 选择器 144 | 边框(border-image、border-radius、box-shadow) 145 | 背景(background-clip、background-origin、background-size) 146 | 渐变(linear-gradients、radial-gradents) 147 | 字体(@font-face) 148 | 转换、形变(transform) 149 | 过度(transition) 150 | 动画(animation) 151 | 弹性盒模型(flex-box) 152 | 媒体查询(@media) 153 | 154 | ### 清除div浮动方式 (scss) 155 | 定义zoom:1来解决IE浮动问题 156 | 157 | ##### 1. 浮动元素定义after或before,和zoom 158 | ```css 159 | .clearfloat{ 160 | zoom: 1; 161 | 162 | &:after { 163 | height: 0; content: ""; clear: both; 164 | display: block; visibility: hidden; 165 | } 166 | } 167 | 168 | // 支持after或before的浏览器, zoom解决低版本ie浮动问题 169 | ``` 170 | 171 | ##### 2. 浮动元素结尾处增加空div或br标签为clear:both 172 | ```html 173 | // 添加一个空div或br标签,利用clear:both清除浮动,让父级div能自动获取到高度 174 |
175 |
176 |
177 | ``` 178 | 179 | ##### 3. 设置div固定高度 180 | 181 | ##### 4. 浮动元素定义overflow: hidden 182 | 需要定义width或zoom,但是超出div的元素会被隐藏掉,不建议和position一起使用 183 | 184 | ##### 5. 浮动元素定义overflow: auto 185 | 需要定义width或zoom, 内部内容超出会有滚动条 186 | 187 | ##### 6. 浮动元素的上级元素一起浮动(float:left) 188 | 189 | ##### 7. 浮动元素定义disaply: table (会影响布局,看情况使用) 190 | 191 | 参考: https://www.cnblogs.com/nxl0908/p/7245460.html 192 | 193 | ### zoom在ie的作用 194 | Zoom属性是IE浏览器的专有属性,可以设置或检索对象的缩放比例。设置或更改一个已被呈递的对象的此属性值将导致环绕对象的内容重新流动。 195 | 196 | 当设置了zoom的值之后,所设置的元素就会就会扩大或者缩小,高度宽度就会重新计算了,这里一旦改变zoom值时其实也会发生重新渲染,运用这个原理,也就解决了ie下子元素浮动时候父元素不随着自动扩大的问题。 197 | 198 | 作用: 199 | 1. 检查页面的标签是否闭合 200 | 2. 样式排除法 201 | 3. 模块确认法 202 | 4. 检查是否清除浮动 203 | 5. 检查 IE 下是否触发 haslayout 204 | 6. 边框背景调试法 205 | 7. 解决ie下的bug,如外边距(margin)的重叠,浮动的清除,触发ie的 haslayout属性 206 | 参考: https://www.jb51.net/css/40285.html 207 | https://blog.csdn.net/u010313768/article/details/47067593 208 | 209 | 210 | ### 当margin是百分比时,计算值为父元素的宽度 211 | 212 | 更准确的应该,当书写模式为横向布局的时候,margin就会以父元素的宽度为计算值,如果书写模式为纵向布局时,margin就会以父元素的高度为计算值 213 | 214 | 这是因为根据w3c标准的浏览器来说,书写模式为横向的时候,宽度根据浏览器的宽度或许是正常的,如果一个元素的上下外边距时父元素的height的百分数,就可能导致一个无限循环,所以margin百分比值在计算时会参考父容器尺寸 215 | 216 | ### flex布局常用属性及作用 217 | 当设置`display:flex` 布局后,元素就会变成弹性布局,他的子元素变为弹性列表 218 | 219 | - flex-direction: 设置子元素主轴的方式是行还是列,是顺序还是逆序,有`row`, `row-reverse`, `column`, `column-reverse`选值 220 | 221 | - flex-wrap: 设置子元素沿主轴方向是否需要换行,正序还是逆序,有`nowrap`, `wrap`, `wrap-reverse`选值 222 | 223 | - flex-flow: 可以同时设置flex-derection 和 flex-wrap 值,空格隔开 224 | 225 | - justify-content: 设置元素沿主轴方向的摆放位置,`flex-start`(靠左), `flex-end`(靠右), `center`(居中), `space-between`(子元素之间留有空白,前后没有), `space-around`(每一个都有空白间距), `initiad`(继承) 226 | 227 | - align-items: 设置一行元素沿侧轴方向的摆放位置,`flex-start`(靠左,不拉伸高度), `flex-end`(靠容器的底部,不拉伸高度), `stretch`(默认值,拉伸高度), `baseline`(对齐容器的基线上), `center`(垂直居中,不拉伸), `initiad`(继承) 228 | 229 | - align-self: 与align-items类似,可以单独设置子元素设置沿侧轴方向摆放的位置,`auto`(默认值), `flex-sart`(靠左), `flex-end`(靠右), `stretch`(拉伸), `baseline`(容器基线上), `center`(垂直), `initial`(继承) 230 | 231 | - order: 设置子元素排列顺序,按照order值从左到右,支持正负值, 默认值为0 232 | 233 | - aling-center: 设置元素垂直方向的摆放位置,`flex-sart`(靠左), `flex-end`(容器底部), `stretch`(平分高度,子元素垂直之间留有空白,最前没有),`center`(垂直), `initial`(继承), `space-between`(平分高度,子元素垂直之间留有空白,最前最后没有), `space-around`(平分高度,子元素垂直之间留有空白,最前最后有) -------------------------------------------------------------------------------- /css/多方案实现垂直水平居中.md: -------------------------------------------------------------------------------- 1 | ### CSS 实现水平垂直居中 2 | ```html 3 |
4 |
1
5 |
6 | ``` 7 | 8 | ##### 1.position+auto 9 | ```css 10 | .wrapper{ 11 | width:100px;height:100px; 12 | position:relative;background:#ddd; 13 | } 14 | 15 | .slide{ 16 | width:50px;height:50px; 17 | position:absolute;top:0;left:0;right:0;bottom:0;margin:auto; 18 | background:#ff0000; 19 | } 20 | ``` 21 | 22 | 这个居中方案比较简单常用,IE8及以上支持 23 | 但是有一点需要注意:宽度和高度不能是auto,否则高宽会继承 限制他的元素(relative);下一个方案解决这问题: 24 | 25 | ##### 2. table-cell 26 | ```css 27 | .wrapper{ 28 | width:100px;height:100px; 29 | display:table-cell;/* 让 vertical-align: middle; 生效 */ 30 | background:#ddd;vertical-align: middle; 31 | text-align: center; 32 | } 33 | 34 | .slide{ 35 | display:inline-block; 36 | vertical-align:middle; 37 | background:#ff0000; 38 | } 39 | ``` 40 | 41 | 这个方案可以解决上一个需要设置高宽的问题,IE8及以上支持。这里除了给父父元素一个vertical-align: middle;之外,我们还需要给需要水平垂直居中的元素也添加vertical-align: middle;属性。如果不添加,那么在垂直方向上会有几个像素的误差。 42 | 43 | ##### 3.position+margin/absolute/translate 44 | 45 | ```css 46 | // 形式一 47 | .wrapper{ 48 | width:100px;height:100px; 49 | position:relative;background:#ddd; 50 | } 51 | 52 | .slide{ 53 | width: 50px; 54 | height: 50px; 55 | background: #ff0000; 56 | position: absolute; 57 | top: 50px; 58 | left: 50px; 59 | margin-top:-25px;margin-left:-25px; 60 | } 61 | 62 | 63 | 64 | // 形式二 65 | .wrapper{ 66 | width:100px;height:100px; 67 | position:relative;background:#ddd; 68 | } 69 | 70 | .slide{ 71 | width: 50px; 72 | height: 50px; 73 | background: #ff0000; 74 | position: absolute; 75 | top:25px;left:25px; 76 | } 77 | 78 | 79 | 80 | // 形式三 81 | .wrapper{ 82 | width:100px;height:100px; 83 | position:relative;background:#ddd; 84 | } 85 | 86 | .slide{ 87 | width: 50px; 88 | height: 50px; 89 | background: #ff0000; 90 | position: absolute; 91 | top: 50%; 92 | left: 50%; 93 | transform: translate(-50%,-50%); 94 | } 95 | ``` 96 | 前两个都差不多,需要自己计算数值并且需要设置宽高,IE5及以上支持 97 | 推荐使用第三个方案,利用translate按自身的高宽百分比移动,达到居中效果,IE9及以上支持 98 | 99 | ##### 4.Flex 100 | ```css 101 | .wrapper{ 102 | width:100px;height:100px; 103 | background:#ddd;display:flex; 104 | align-items: center; /* 垂直居中 */ 105 | justify-content: center;/* 水平居中 */ 106 | } 107 | 108 | .slide{ 109 | background: #ff0000; 110 | } 111 | ``` 112 | 不需要设置元素高宽,且简单,就可以达到居中效果,但是….IE10及以上才支持。移动端兼容良好,推荐在移动端使用 113 | 114 | ##### 5.Flex+margin 115 | 116 | ```css 117 | .wrapper{ 118 | width:100px;height:100px; 119 | background:#ddd;display:flex; 120 | } 121 | 122 | .slide{ 123 | background: #ff0000;margin:auto; 124 | } 125 | ``` 126 | 兼容性:IE10及以上 ,这种方法跟第4种方法比较起来代码也更加简洁。 127 | 128 | 129 | ##### 6.fixed+top+transform 130 | 131 | ```js 132 | .demo { 133 | position:fixed; 134 | top: 50%; 135 | left: 50%; 136 | transform: translate(-50%, -50%); 137 | } 138 | ``` 139 | 140 | 141 | ##### 网上终极法 142 | 143 | ```css 144 | .demo-wrap{ 145 | height: 200px; 146 | width: 200px; 147 | display: table; 148 | position: relative; 149 | background: #eee; 150 | } 151 | .demo-center{ 152 | display: table-cell; 153 | vertical-align: middle; 154 | text-align: center; 155 | *position: absolute; 156 | *top: 50%; 157 | *left: 50%; 158 | } 159 | .center{ 160 | background: #ccc; 161 | display: inline-block; 162 | *position:relative; 163 | *top: -50%; 164 | *left: -50%; 165 | } 166 | ``` -------------------------------------------------------------------------------- /eslint/常见错误.md: -------------------------------------------------------------------------------- 1 | #### Unexpected tab character (no-tabs) 2 | 字面意思理解呢就是意想不到的制表符,当时出现的时候就是我习惯的使用Tab键去替代空格,但是eslint默认不认可Tab,所以解决方法就是重新用空格缩进或者以下很简单: 3 | 4 |   在eslint的配置文件中(.eslintrc)rules项中添加一行:"no-tabs":"off"。如下: 5 | ![](https://images2018.cnblogs.com/blog/1186411/201803/1186411-20180318161854264-928644952.png) 6 | 7 | #### Trailing spaces not allowed (no-trailing-spaces) 8 | 后面不能有空格,需要删除 9 | 10 | #### Line 3 exceeds the maximum line length of 100 (max-len) 11 | 一行不能超过100个字符 12 | 13 | #### Trailing spaces not allowed (no-trailing-spaces) 14 | 一般是有多余的空格 -------------------------------------------------------------------------------- /html/html基础.md: -------------------------------------------------------------------------------- 1 | #### XHTML 和 HTML的区别 2 | 主要区别在于功能,书写习惯,XHTML可以兼容各大浏览器,平板,手机,而且也能够快速并且正确编译网页 3 | 4 | 书写习惯上: 5 | 1. 在XHTML中,所有标签都必须是小写,不能既有大写又有小写,也不能都是大写 6 | 7 | 2. 标签必须正确的闭合,开始标签和结束必须成对出现,标签顺序也必须有序,正确 8 | 9 | 3. 在XHTML 1.0中规定标签属性必须是双引号 10 | 11 | 4. XHTML 1.1中不允许使用target="_blank", 如果想要有开新窗口的功能,就必须改写为rel="external",并搭配JavaScript实现此效果。 12 | ```html 13 | MUKI space 14 | ``` 15 | 16 | #### H5新特性: 17 | 新增选择器 document.querySelector、document.querySelectorAll 18 | 拖拽释放(Drag and drop) API 19 | 媒体播放的 video 和 audio 20 | 本地存储 localStorage 和 sessionStorage 21 | 离线应用 manifest 22 | 桌面通知 Notifications 23 | 语意化标签 article、footer、header、nav、section 24 | 增强表单控件 calendar、date、time、email、url、search 25 | 地理位置 Geolocation 26 | 多任务 webworker 27 | 全双工通信协议 websocket 28 | 历史管理 history 29 | 跨域资源共享(CORS) Access-Control-Allow-Origin 30 | 页面可见性改变事件 visibilitychange 31 | 跨窗口通信 PostMessage 可以安全地实现跨源通信 32 | Form Data 对象 33 | 绘画 canvas 34 | 服务器发送事件 ( SSE, server-sent event ) (只要让服务器保持 HTTP 会话不关闭,当有新的更新时,立刻输出) 35 | 36 | #### H5移除的元素: 37 | 纯表现的元素:basefont、big、center、font、s、strike、tt、u 38 | 对可用性产生负面影响的元素:frame、frameset、noframes 39 | 40 | #### DOM文档加载顺序: 41 | 解析HTML结构 42 | 加载外部脚本和样式表文件(loading) 43 | 解析并执行脚本 44 | DOM树构建完成(readyState:interactive) (DOMcontentLoaded 事件执行) 45 | 加载外部资源文件(图片等) 46 | 页面加载完成(readyState:complete) (load 事件执行) 47 | 48 | ### doctype 作用 49 | doctype一般声明与html文档的第一行,用于告诉浏览器以什么样的文档标准解析文档,doctype不存在或者格式错误会以兼容模式呈现 50 | 51 | ### 严格模式和混杂模式的区别 52 | 严格的模式的排版和js的运作方式都会以浏览器的最高标准运行,兼容模式主要是以向低版本浏览器兼容的方式运行,防止老站点无法运行 53 | 54 | ### html5 为什么是? 55 | html5 不基于sgml,因此不需要对dtd进行引用,但是需要让浏览器知道以什么模式运行 56 | html4 还基于sgml,需要引用dtd 57 | 58 | ### 行内元素有哪些?块级元素有哪些? 空(void)元素有那些? 59 | 60 | ***行内元素***: 61 | a - 锚点,em - 强调,strong - 粗体强调,span - 定义文本内区块,i - 斜体,img - 图片,b - 粗体,label - 表格标签,select - 项目选择,textarea - 多行文本输入框,sub - 下标, 62 | sup - 上标,q - 短引用; 63 | ***块元素***: 64 | div - 常用块级,dl - 定义列表,dt,dd,ul- 非排序列表,li,ol-排序表单,p-段落,h1,h2,h3,h4,h5-标题,table-表格,fieldset - form控制组,form - 表单, 65 | ***空元素***: 66 | br-换行,hr-水平分割线 67 | 68 | ### html5 离线存储原理 69 | 在html标签添加`manifest`属性 70 | ```html 71 | 72 | 73 | ... 74 | 75 | ``` 76 | 在cache.mainifest文件内编写需要缓存的文件 77 | ```html 78 | CACHE MANIFEST 79 | #v0.11 80 | 81 | // cache表示需要离线缓存的列表,不需要把自身页面列出来,因为包含manifest 的页面也会自动被缓存 82 | CACHE: 83 | js/app.js 84 | css/index.css 85 | 86 | // 优先级比cache高,一般只有在在线的情况下才能访问,离线时无法使用,需要的话可以在cache中添加 87 | NETWORK: 88 | resourse/logo.png 89 | 90 | // 如果资源访问失败,就会用第二个资源替代他,比如上面的例子就是,访问资源错误就会跳转至offline.html 91 | FALLBACK: 92 | // offline.html 93 | ``` 94 | 如果浏览器遇到有manifest属性,并且在线: 95 | 如果是第一次访问,就会下载并且缓存manifest文件的具体资源,如果是不是第一次访问,会先使用旧的离线存储数据展示页面,然后浏览器会比较本地和服务器之间的manifest文件,如果不是最新的文件,就会重新下载并且缓存文件 96 | 如果是离线的情况: 97 | 直接展示离线缓存的文件 98 | 99 | ### cookie,localstorage, sessionstorage区别 100 | 101 | cookie只能存储4kb的大小,并且数据保存在客户端,每次同源网络请求的时候都会携带,在使用的时候可以设置过期时间,如果没有设置浏览器关闭后就会删除cookie,如果设置了就会存储在硬盘中,就算浏览器关闭后再打开访问,还是可以使用的,比起seesion不是特别安全,一般浏览器限制存储20个cookie,主要用于会话标识 102 | 103 | localstorage数据同样保存在客户端,网络请求的时候不会携带数据,在使用的时候没有设置过期时间,但是可以通过存储时间进行封装来实现,如果不手动清除,就会一直保存,就会浏览器关闭后也还存在,持久化方案之一,一般存储可达5mb或者更大,localstorage在同源的窗口可以互相调用,都是共享的 104 | 105 | session数据保存在服务器端,同源网站的任意网页内都可以访问,session一般在标签页变比或浏览器关闭后就清除,即使标签关闭或刷新网页数据依然可以访问,适合临时存储 106 | 107 | 区别: 108 | 1. cookie和localstorage数据存放在客户端,session数据放在服务器上 109 | 2. cookie和localstorage不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session 110 | 3. session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie 111 | 4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,localstorage和sessionstorage 112 | 5. 建议将登录信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中 113 | 6. session保存在服务器,客户端不知道其中的信心;cookie保存在客户端,服务器能够知道其中的信息 114 | 7. session中保存的是对象,cookie中保存的是字符串 115 | 8. session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的 116 | 117 | 参考: 118 | https://www.cnblogs.com/zr123/p/8086525.html 119 | 120 | 121 | ### href和src的区别 122 | - src是指外部资源,用于替代这个元素,这个属性是将资源嵌入到当前文档中元素所在的位置,,浏览器会暂停加载直到这资源加载完成。这也是为什么要将js文件的加载放在body最后的原因(在前面) 123 | - href是指超链接,用于建立这个标签与外部资源之间的关系,如果资源是一个css链接,html的解析和渲染不会暂停,css文件的加载是同时进行的,这不同于在style标签里面的内置样式,用@import添加的样式是在页面载入之后再加载,这可能会导致页面因重新渲染而闪烁。所以我们建议使用link而不是@import。 124 | 125 | ### defer和async的区别 126 | 127 | - defer 会在之后,DOMContentLoaded事件触发之前执行,尽管script标签在head内,也是如此,defer会按照加载顺序执行脚本 128 | 129 | - async 在html解析script标签的时候,会异步请求script标签的链接,不会造成阻塞进程 130 | 131 | 区别: 132 | - async加载的顺序不一定会按照执行的先后顺序执行,defer会按照顺序执行 133 | - 两者都是异步请求 134 | - defer 是立即下载延迟执行脚本,async下载后立即执行 135 | - defer 适合对一些脚本有依赖,需要操作dom节点的脚本使用, async适合对无依赖,优先级低的脚本执行,例如:预加载 -------------------------------------------------------------------------------- /html/输入url到页面加载.md: -------------------------------------------------------------------------------- 1 | 2 | 可以分为六个过程: 3 | `1、DNS解析` -> `2、TCP连接` -> `3、发送HTTP请求` -> `4、服务器处理请求并返回HTTP报文` -> `5、浏览器解析并渲染页面` -> `6、连接结束` 4 | 5 | 6 | 7 | #### dns解析 8 | 9 | 每一个计算机实际上都有一个ip地址,但是用户不太方便记忆ip地址,所有就有了网址替换ip地址,dns解析就充当了一个翻译官,当用户在浏览器输入网址并打开的时候,实际上就是网址到ip地址的转换。 10 | 11 | ##### dns解析过程 12 | 13 | dns解析实际上是递归查询ip地址的过程 14 | 15 | 输入地址打开后告诉本地dns服务器,如果没找到,则会进入下一级域名服务器查看,如此重复,比如:www.baidu.com,首先在本地域名服务器中查找ip地址,如果没有找到,本地域名服务器会向根域名服务器发送一个请求,如果跟域名服务器也不存在该域名的ip地址时,本地域名会向com顶级域名服务器发送一个请求,依次类推循环下去,直到最后本地域名服务器得到baidu的ip地址并缓存到本地,供下次使用,所有网址的解析过程为: 16 | `.` -> `.com` -> `baidu.com.` -> `www.baidu.com.` 17 | 18 | 最后面的.对应根域名服务器 19 | 20 | `根域名服务器` -> `com顶级域名服务器` -> `baidu.comy域名服务器` -> `www.baidu.com` 21 | 22 | 23 | ##### dns优化 24 | 25 | `dns缓存` 26 | 27 | dns有着多个缓存,从离浏览器的距离排序的话,`浏览器缓存` -> `系统缓存` -> `路由器缓存` -> `ips缓存` -> `根域名服务器缓存` -> `顶级域名服务器缓存` -> `主域名服务器缓存` 28 | 29 | 其主要优化的就是缓存这部分。 30 | 31 | 不同浏览器的缓存机制不同: IE对DNS记录默认的缓存时间为30分钟,Firefox对DNS记录默认的缓存时间为1分钟,Chrome对DNS记录默认的缓存时间为1分钟。 32 | 33 | 缓存时间长:减少DNS的重复查找,节省时间。 34 | 缓存时间短:及时检测服务器的IP变化,保证访问的正确性。 35 | 36 | `减少dns查询次数` 37 | 38 | DNS查询也消耗响应时间,若网页内容来自各个不同的domain,则客户端首次解析这些domain需要消耗一定的时间,但由于DNS查询结果会缓存在本地系统和浏览器中一段时间,所以DNS查询一般只是对首次访问时的速度有影响。 39 | 40 | 减少DNS查询次数需要减少来自不同domain的请求的数量,如尽量将外部域的对象下载到本地服务器上等。 41 | 42 | ##### dns负载均衡 43 | 44 | dns负载均衡又叫dns重定向,其主要作用是将用户的http请求,让最接近用户地理位置的dns服务器接收并返回请求,还有许多优化的手段 45 | 46 | 47 | #### tcp链接 48 | 49 | `建立连接` 50 | 51 | 建立连接之前,服务器一直会打开端口并对其监听,当客户端主动和服务器端建立连接的时候,发起一个打开端口的请求(该端口一般为临时端口),然后进入三次握手的过程: 52 | 53 | `TCP连接的三次握手过程` 54 | 55 | **图解:** 56 | 57 | ![](http://blog.chinaunix.net/attachment/201304/8/22312037_1365405910EROI.png) 58 | 59 | TCP报文字段格式 60 | (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。 61 | (2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。 62 | (3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下: 63 | - (A)URG:紧急指针(urgent pointer)有效。 64 | - (B)ACK:确认序号有效。 65 | - (C)PSH:接收方应该尽快将这个报文交给应用层。 66 | - (D)RST:重置连接。 67 | - (E)SYN:发起一个新连接。 68 | - (F)FIN:释放一个连接。 69 | 70 | 需要注意的是: 71 | - (A)不要将确认序号Ack与标志位中的ACK搞混了。 72 | - (B)确认方Ack=发起方Req+1,两端配对。 73 | 74 | 75 | - 第一次握手:client将标志位SYN置为1,随机产生一个值为seq=J;并将数据包发送给Server,Client进入SYN_SENT状态,等待server确认 76 | 77 | - 第二次握手:Server收到数据包后由标志位SYN=1知道client请求建立连接,server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将数据包发送给client以确认连接请求,server进入SYN_RCVD状态。 78 | 79 | - 第三次握手:client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则连接简历成功,Client和server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。 80 | 81 | 82 | `TCP的四次挥手过程` 83 | 84 | **图解:** 85 | 86 | ![](http://blog.chinaunix.net/attachment/201304/9/22312037_1365503104wDR0.png) 87 | 88 | - 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Cient进入FIN_WAIT_1 89 | 90 | - 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态 91 | 92 | - 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态 93 | 94 | - 第四次挥手:Client收到FIN后,Client进入TIME_WAIT 状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 95 | 96 | 97 | ##### HTTP请求 98 | 99 | 主要是发生在客户端,发送HTTP请求的过程就是构建HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口(HTTP协议80/8080,hTTPS协议443)。HTTP请求报文是三部分组成:请求行,请求报头和请求正文。 100 | 101 | 102 | ##### 请求行 103 | 104 | 格式如下:`Method Request-URL HTTP-Version CRLF` 105 | 106 | ``` 107 | eg: GET index.html HTTP/1.1 108 | ``` 109 | 110 | 常用的方法有GET,POST,PUT,DELETE,OPTIONS,HEAD. 111 | 112 | 113 | 114 | ##### 请求报头 115 | 116 | 请求报头允许能向服务器传递请求的工具附加信息和客户端(客户端不一定特指浏览器,有时候也可使用Linux下的CURL命令以及HTTP客户端测试工具等。) 117 | 118 | 119 | ##### 请求正文 120 | 121 | 当使用POST,PUT等方法时,通常需要客户端向服务端传递数据。这些数据就储存在请求正文中 122 | 123 | 124 | 125 | ##### 服务器处理请求并返回HTTP报文 126 | 127 | HTTP响应报文也是由三部分组成:状态码,响应报头和响应报文 128 | 129 | 130 | 131 | 详细参考:https://segmentfault.com/a/1190000006879700, 132 | https://www.cnblogs.com/chengyunshen/p/7196348.html, 133 | https://www.cnblogs.com/xsilence/p/6034361.html, 134 | https://blog.csdn.net/sssnmnmjmf/article/details/68486261 -------------------------------------------------------------------------------- /http/Etag.md: -------------------------------------------------------------------------------- 1 | HTTP协议规格说明定义ETag为“被请求变量的实体值”。另一种说法是,ETag是一个可以与Web资源关联的记号(token)。 2 | Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match。 3 | 4 | Etag的作用:主要为了解决 Last-Modified 无法解决的一些问题。 5 | 6 | 请求流程如下: 7 | 8 | 当发送一个服务器请求时,浏览器首先会进行缓存过期判断,浏览器先根据缓存过期时间判断缓存文件是否过期。 9 | 10 | - 情景一:若没有过期,则不向服务器发送请求,直接使用缓存中的结果,此时我们在浏览器控制台中可以看到 200 OK(from cache) ,此时的情况就是完全使用缓存,浏览器和服务器没有任何交互的。 11 | 12 | - 情景二:若已过期,则向服务器发送GET请求,此时请求中会带上文件修改时间和Etag。 13 | 14 | 然后,进行资源更新判断。服务器根据浏览器传过来的文件修改时间,判断自浏览器上一次请求之后,文件有没有被修改过;根据Etag,判断文件内容自上一次请求之后,有没有发生变化。 15 | 16 | - 情形一:若两种判断的结论都是文件没有被修改过,则服务器就不给浏览器发index.html的内容了,直接告诉它,文件没有被修改过,你用你那边的缓存吧—— 304 Not Modified,此时浏览器就会从本地缓存中获取index.html的内容。此时的情况叫协议缓存,浏览器和服务器之间有一次请求交互。 17 | 18 | - 情形二:若修改时间和文件内容判断有任意一个没有通过,则服务器会受理此次请求,之后的操作同情景一。 19 | 20 | 客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。工作过程如下: 21 | ![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537366736208&di=ad46c4d1886cf78d2a66345c8a7d2340&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180824%2Ff1335f8436cf43e28b40f578b8a1beb8.jpeg) 22 | 1. 客户端请求一个页面(A)。 23 | 2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。 24 | 3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。 25 | 4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。 26 | 5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。 27 | 28 | Etag 主要为了解决 Last-Modified 无法解决的一些问题。 29 | 30 | 1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET; 31 | 32 | 2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒) 33 | 34 | 3、某些服务器不能精确的得到文件的最后修改时间; 35 | 36 | 为此,HTTP/1.1 引入了 Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。 -------------------------------------------------------------------------------- /http/http基础.md: -------------------------------------------------------------------------------- 1 | ####ji常见状态码 2 | 1xx 指示信息 - 表示请求已接收,继续处理 3 | 2xx 成功 - 表示请求已成功接收 4 | 3xx 重定向 - 要完成的请求必须进行更进一步的操作 5 | 4xx 客户端错误 - 请求有语法错误或请求无法实现 6 | 5xx 服务器错误 - 服务器内部未能实现合法的请求 7 | 8 | ## `2XX 成功` 9 | 10 | 200 OK,表示从客户端发来的请求在服务器端被正确处理 11 | 204 No content,表示请求成功,但响应报文不含实体的主体部分 12 | 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容 13 | 206 Partial Content,服务器接收Range get请求并且完成了 14 | 15 | ## `3XX 重定向` 16 | 17 | 301 moved permanently,永久性重定向,表示请求的url已经转移到新的url 18 | 302 found,临时性重定向,表示请求的url临时转移到新的url 19 | 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源 20 | 304 not modified,表示服务器告诉客户端,缓存的资源可以继续使用 21 | 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 22 | 23 | ## `4XX 客户端错误` 24 | 25 | 400 bad request,请求报文存在语法错误,服务器不能理解 26 | 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 27 | 403 forbidden,表示对请求资源的访问被服务器拒绝 28 | 404 not found,表示在服务器上没有找到请求的资源 29 | 30 | ## `5XX 服务器错误` 31 | 32 | 500 internal sever error,表示服务器端在执行请求时发生了错误 33 | 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能 34 | 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 35 | 36 | 37 | #### http请求的方式 38 | 39 | HTTP Method的历史: 40 | HTTP 0.9 这个版本只有GET方法 41 | HTTP 1.0 这个版本有GET HEAD POST这三个方法 42 | HTTP 1.1 这个版本是当前版本,包含GET HEAD POST OPTIONS PUT DELETE TRACE CONNECT这8个方法 43 | HTTP/1.1之后增加的方法 44 | 在HTTP/1.1标准制定之后,又陆续扩展了一些方法。其中使用中较多的是PATCH 45 | 46 | `get` 向服务器请求获取指定某一资源,一般用于数据的读取,这不是一个安全的方法,因为请求的数据或者提交数据会附加在url上,也就是幂等 47 | 48 | `post` 将某一资源提交至服务器,进行处理,请求的数据或者提交的数据是在请求体中,不会显示出来,所以这是一个安全的方法,也就是非幂等 49 | 50 | `head` 跟get一样,都是请求服务器的某一资源,根本get不同的是,head返回的数据是只有服务器的响应头信息即时响应主体,而不会返回资源的内容部分,主要用于客户端查看服务器的性能情况 51 | 52 | `put` 将指定资源上传更新至服务器,通过该方法客户端可以将指定资源的最新数据传送给服务器取代指定的资源的内容,也就是幂等 53 | 54 | `delete` 删除服务器上的某一资源,也是幂等方法 55 | 56 | `connect` http1.1协议方法,建立一个到由目标资源标识的服务器的隧道,通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。 57 | 58 | `options` 向服务器请求某一资源支持的所有http请求方法,JavaScript的XMLHttpRequest对象进行CORS跨域资源共享时,就是使用OPTIONS方法发送请求,以判断是否有对指定资源的访问权限。 59 | 60 | `trace` 请求某一资源后服务器回显收到的请求数据(译注:TRACE方法让客户端测试到服务器的网络通路,回路的意思如发送一个请返回一个响应,这就是一个请求响应回路),如果请求是有效的,响应应该在响应实体主体里包含整个请求消息,并且响应应该包含一个Content-Type头域值为”message/http”的头域。TRACE方法的响应不能不缓存。 61 | 62 | `PATCH` 用来对已知资源进行局部更新,在请求中定义了一个描述修改的实体集合,如果被请求修改的资源不存在,服务器可能会创建一个新的资源。也是非幂等方法 63 | 64 | 65 | post和get的区别: 66 | - post在浏览器回退的时候是会再次发送http请求的,而get不会 67 | - get是会被浏览器主动缓存,而post除非手动设置,否则不会被缓存 68 | - get请求只支持url方式,而post支持多种编码格式(二进制,fromData,base64) 69 | - get请求的参数可以完整的保存到浏览器的历史记录中,但是post不会 70 | - 对参数的数据类型,get只支持ASCII字符,post没有限制 71 | - get参数是用过拼接URL传递,post是放在request body中 72 | - 在浏览器的历史记录中会保留请求的地址 73 | - get方法还容易造成Cross-site request forgery(csrf)攻击,因为参数是直接暴露在URL中 74 | 75 | post和put的本质区别即在于是否具有幂等性,2者都可以用于创建和更新,这取决与你的设计,所以更多的是语义和应用服务器那边怎么处理 76 | 需要以更新的形式来修改某一具体资源的时候,如何判断用PUT还是POST呢? 77 | 很简单,如果该更新对应的URI多次调用的结果一致,则用PUT 78 | 在每次更新提交相同的内容,最终的结果不一致的时候,用POST 79 | 80 | PATCH方法和put方法的区别: 81 | - PATCH方法用于资源的部分内容的更新; 82 | - PUT用于更新某个较为完整的资源,并且不能重复做部分更改,否则代理和缓存、甚至服务器或者客户端都会得到有问题的操作结果。 83 | - PATCH方法可能会在资源不存在时去创建它。 84 | 85 | 幂等是一个数学和计算机学概念,在计算机范畴内表示一个操作执行任意次对系统的影响跟一次是相同,对于两个参数,如果传入值相等,结果也等于每个传入值,则称其为幂等的,如min(a,b), 86 | 在HTTP/1.1规范中幂等性的定义是: 87 | > Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. 88 | 89 | 如:POST 方法不是幂等的,若反复执行多次对应的每一次都会创建一个新资源。如果请求超时,则需要回答这一问题:资源是否已经在服务端创建了?能否再重试一次或检查资源列表?而对于幂等方法不存在这一个问题,我们可以放心地多次请求 90 | 91 | #### 何时触发options请求?: 92 | 规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。 93 | 94 | ### 三次握手过程和四次挥手过程 95 | ![](https://camo.githubusercontent.com/36cf7d4e1598683fe72a5e1c3e837b16840f4085/687474703a2f2f6f6f327239726e7a702e626b742e636c6f7564646e2e636f6d2f6a656c6c797468696e6b544350342e6a7067) 96 | 97 | https://github.com/jawil/blog/issues/14 98 | 99 | https://sofish.github.io/restcookbook/http%20methods/idempotency/ 100 | https://www.cnblogs.com/machao/p/5788425.html 101 | 102 | ###关于http2.0的理解 103 | 104 | - http2.0 引入了 '服务端推' 的概念,他允许服务端在客户端需要数据之前主动的将数据发送到客户端缓存中(web sockeet),从而提高性能。 105 | - http2.0 提供更多的加密支持,支持使用多路技术,允许多个消息在一个连接上同时交差 106 | - http2.0 增加了头压缩, 即使非常大的请求,其请求和响应的header都只会占宽带比例的一小部分 107 | 108 | 109 | ###304缓存原理 110 | 111 | 当客户端请求发送一个页面,服务端收到这个请求后返回数据,会标识`ETAG`,连同发送给客户端,浏览器接受这个数据展示页面并且一起缓存`ETAG`,之后再次请求这个页面的时候会把上次服务端返回的`ETAG`发送给服务端,服务端接受这个`ETAG`并且判断距离上次访问服务端文件有没有更新, 如果没有就会返回一个304状态码,空响应体,否则就连同`ETAG`和最新的文件返回客户端 -------------------------------------------------------------------------------- /http/http状态码.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 1**(信息类):表示接收到请求并且继续处理 3 | 100——客户必须继续发出请求 4 | 101——客户要求服务器根据请求转换HTTP协议版本 5 | 6 | 2**(响应成功):表示动作被成功接收、理解和接受 7 | 200——表明该请求被成功地完成,所请求的资源发送回客户端 8 | 201——提示知道新文件的URL 9 | 202——接受和处理、但处理未完成 10 | 203——返回信息不确定或不完整 11 | 204——请求收到,但返回信息为空 12 | 205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件 13 | 206——服务器已经完成了部分用户的GET请求 14 | 15 | 3**(重定向类):为了完成指定的动作,必须接受进一步处理 16 | 300——请求的资源可在多处得到 17 | 301——本网页被永久性转移到另一个URL 18 | 302——请求的网页被转移到一个新的地址,但客户访问仍继续通过原始URL地址,重定向,新的URL会在response中的Location中返回,浏览器将会使用新的URL发出新的Request。 19 | 303——建议客户访问其他URL或访问方式 20 | 304——自从上次请求后,请求的网页未修改过,服务器返回此响应时,不会返回网页内容,代表上次的文档已经被缓存了,还可以继续使用 21 | 305——请求的资源必须从服务器指定的地址得到 22 | 306——前一版本HTTP中使用的代码,现行版本中不再使用 23 | 307——申明请求的资源临时性删除 24 | 25 | 4**(客户端错误类):请求包含错误语法或不能正确执行 26 | 400——客户端请求有语法错误,不能被服务器所理解 27 | 401——请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 28 |   HTTP 401.2 - 未授权:服务器配置问题导致登录失败 29 |   HTTP 401.3 - ACL 禁止访问资源 30 |   HTTP 401.4 - 未授权:授权被筛选器拒绝 31 |   HTTP 401.5 - 未授权:ISAPI 或 CGI 授权失败 32 | 402——保留有效ChargeTo头响应 33 | 403——禁止访问,服务器收到请求,但是拒绝提供服务 34 |   HTTP 403.1 禁止访问:禁止可执行访问 35 |   HTTP 403.2 - 禁止访问:禁止读访问 36 |   HTTP 403.3 - 禁止访问:禁止写访问 37 |   HTTP 403.4 - 禁止访问:要求 SSL 38 |   HTTP 403.5 - 禁止访问:要求 SSL 128 39 |   HTTP 403.6 - 禁止访问:IP 地址被拒绝 40 |   HTTP 403.7 - 禁止访问:要求客户证书 41 |   HTTP 403.8 - 禁止访问:禁止站点访问 42 |   HTTP 403.9 - 禁止访问:连接的用户过多 43 |   HTTP 403.10 - 禁止访问:配置无效 44 |   HTTP 403.11 - 禁止访问:密码更改 45 |   HTTP 403.12 - 禁止访问:映射器拒绝访问 46 |   HTTP 403.13 - 禁止访问:客户证书已被吊销 47 |   HTTP 403.15 - 禁止访问:客户访问许可过多 48 |   HTTP 403.16 - 禁止访问:客户证书不可信或者无效 49 | HTTP 403.17 - 禁止访问:客户证书已经到期或者尚未生效 50 |   HTTP 403.18 – 在当前的应用程序池中不能执行所请求的 URL。这个错误代码为 IIS 6.0 所专用。 51 |   HTTP 403.19 – 不能为这个应用程序池中的客户端执行 CGI。这个错误代码为 IIS 6.0 所专用。 52 |   HTTP 403.20 – Passport 登录失败。这个错误代码为 IIS 6.0 所专用。 53 | 404——一个404错误表明可连接服务器,但服务器无法取得所请求的网页,请求资源不存在。eg:输入了错误的URL 54 |   HTTP 404.1 – 无法在所请求的端口上访问 Web 站点。 55 |   HTTP 404.2 – Web 服务扩展锁定策略阻止本请求。 56 |   HTTP 404.3 – MIME 映射策略阻止本请求。 57 | 405——用户在Request-Line字段定义的方法不允许 58 | 406——根据用户发送的Accept拖,请求资源不可访问 59 | 407——类似401,用户必须首先在代理服务器上得到授权 60 | 408——客户端没有在用户指定的饿时间内完成请求 61 | 409——对当前资源状态,请求不能完成 62 | 410——服务器上不再有此资源且无进一步的参考地址 63 | 411——服务器拒绝用户定义的Content-Length属性请求 64 | 412——一个或多个请求头字段在当前请求中错误 65 | 413——请求的资源大于服务器允许的大小 66 | 414——请求的资源URL长于服务器允许的长度 67 | 415——请求资源不支持请求项目格式 68 | 416——请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段 69 | 417——服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求长。 70 | 423 – 锁定的错误 71 | 72 | 5**(服务端错误类):服务器不能正确执行一个正确的请求 73 | HTTP 500 - 服务器遇到错误,无法完成请求 74 |   HTTP 500.100 - 内部服务器错误 - ASP 错误 75 |   HTTP 500.11 服务器关闭 76 |   HTTP 500.12 应用程序重新启动 77 |   HTTP 500.13 - 服务器太忙 78 |   HTTP 500.14 - 应用程序无效 79 |   HTTP 500.15 - 不允许请求 global.asa 80 |   HTTP 500.16 – UNC 授权凭据不正确。这个错误代码为 IIS 6.0 所专用。 81 |   HTTP 500.18 – URL 授权存储不能打开。这个错误代码为 IIS 6.0 所专用。 82 |   Error 501 - 未实现 83 | HTTP 502 - 网关错误 84 |   HTTP 502.1 – CGI 应用程序超时。 85 |   HTTP 502.2 – CGI 应用程序出错。application. 86 | HTTP 503:由于超载或停机维护,服务器目前无法使用,一段时间后可能恢复正常 87 | 88 | 89 | 200 - 服务器成功返回网页,客户端请求已成功。 90 | 302 - 对象临时移动。服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。 91 | 304 - 属于重定向。自上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。 92 | 401 - 未授权。请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。 93 | 404 - 未找到。服务器找不到请求的网页。 94 | 2xx - 成功。表示服务器成功地接受了客户端请求。 95 | 3xx - 重定向。表示要完成请求,需要进一步操作。客户端浏览器必须采取更多操作来实现请求。例如,浏览器可能不得不请求服务器上的不同的页面,或通过代理服务器重复该请求。 96 | 4xx - 请求错误。这些状态代码表示请求可能出错,妨碍了服务器的处理。 97 | 5xx - 服务器错误。表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错 98 | ``` -------------------------------------------------------------------------------- /http/keep-alive.md: -------------------------------------------------------------------------------- 1 | #### Keep-Alive是什么? 如何工作的 2 | http 1.0中默认是关闭KeepAlive,需要在http头加入"Connection:Keep-Alive",才能启用KeepAlive。 3 | 在http1.1之前,每一个tcp连接都会在请求后返回数据之后关闭连接,如果网站上有大量的图片,css等等资源,就会不断的向服务器简历http请求,这样的效率就会慢很多,之后,http1.1就推出了HTTP keep-alive(http persistent connection)的长连接. 4 | ![](http://www.nowamagic.net/librarys/images/201312/2013_12_20_02.png) 5 | 6 | http长连接的方案之一,如果浏览器支持keep-alive,就会在发起http请求的请求头中添加: 7 | > Connection: Keep-Alive 8 | 9 | 然后服务器响应数据后,也会在返回的请求头中添加: 10 | > Connection: Keep-Alive 11 | 12 | 当需要关闭连接时,HTTP 头中会包含如下内容: 13 | 14 | > Connection: Close 15 | 16 | 这样在一定的时间(keep-alive timeout)里还会保持http的连接,不会中断,当客户端发起另一个请求的时候,会使用同一个连接,因为在发起之前客户端和服务器链接还没断开,直到客户端或者服务器一方觉得会话可以结束后, 17 | 18 | 维基百科的说明: 19 | 20 | > 在 HTTP 1.1 中 所有的连接默认都是持续连接,除非特殊声明不支持。 HTTP 持久连接不使用独立的 keepalive 信息,而是仅仅允许多个请求使用单个连接。然而, Apache 2.0 httpd 的默认连接过期时间 是仅仅15秒 ,对于 Apache 2.2 只有5秒。 短的过期时间的优点是能够快速的传输多个web页组件,而不会绑定多个服务器进程或线程太长时间。 21 | 22 | #### 优点: 23 | - 减少tcp的连接建立次数 24 | - 允许请求和应答的HTTP管线化 25 | - 降低拥塞控制 (TCP连接减少了) 26 | - 减少后续请求的响应时间,因为此时不需要建立TCP,也不需要TCP握手等过程; 27 | - 报告错误无需关闭TCP连接 28 | - 提高性能和提高httpd服务器的吞吐率 29 | 30 | #### 缺点 31 | 32 | - 长时间的tcp连接容易导致资源浪费,而且keep-alive配置不当也会造成反效果 33 | - 可能会损害服务器的整体性能,直接影响到服务器的并发数。 34 | 35 | 36 | 37 | http://www.nowamagic.net/academy/detail/23350305 38 | https://zh.wikipedia.org/wiki/HTTP%E6%8C%81%E4%B9%85%E8%BF%9E%E6%8E%A5 -------------------------------------------------------------------------------- /js/es6.md: -------------------------------------------------------------------------------- 1 | ### import export 2 | ```js 3 | 1.当用export default people导出时,就用 import people 导入(不带大括号) 4 | 5 | 2.一个文件里,有且只能有一个export default。但可以有多个export。 6 | 7 | 3.当用export name 时,就用import { name }导入(记得带上大括号) 8 | 9 | 4.当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age } 10 | 11 | 5.当一个文件里出现n多个 export 导出很多模块,导入时除了一个一个导入,也可以用import * as example 12 | ``` 13 | 14 | 15 | ### es6 箭头函数 16 | 17 | ```js 18 | document.querySelector('p').onclick = () => { 19 | console.log(this) 20 | } 21 | // window 22 | ``` 23 | 24 | 25 | 箭头函数 Arrow functions :箭头函数与现有函数不同,但并不是用来替代现有函数的,他一般是用来作为回调函数使用的,主要目的是为了简化回调函数的写法: 26 | 箭头函数本身是没有this的,函数内的this执行箭头函数 定义时所在的对象,而不是使用时所在的对象, 27 | 28 | 箭头函数内部,不存在arguments对象 29 | 30 | 不可以当作构造函数,不可以使用new指令 31 | 32 | 简化回调函数 33 | ```js 34 | document.querySelector('p').onclick = () => { 35 | console.log(this) 36 | } 37 | // windoiw 使用箭头函数自身是没有this值的 38 | 39 | var x = '1'; 40 | var set = x => { 41 | console.log(x) 42 | console.log(arguments) 43 | } 44 | set(2) 45 | // 2 46 | // ReferenceError: arguments is not defined 47 | // 箭头函数没有arguments值 48 | 49 | function set(x){ 50 | console.log(x) 51 | } 52 | var s = new set(1) 53 | // 1 54 | 55 | set = x => { 56 | console.log(x) 57 | } 58 | var s = new set(1) 59 | // Uncaught TypeError: set is not a constructor 60 | // 一般函数可以用new指令,但是箭头函数不能使用new指令 61 | 62 | ``` 63 | 64 | ### Array.isArray() 用来判断一个变量是否是数组 65 | 66 | ```javascript 67 | var arr = [] 68 | Array.isArray(arr) 69 | ``` 70 | 71 | 除此之外还有五种方式判断一个变量是否是数组 72 | 73 | ```javascript 74 | // 1.基于instanceof 75 | a instanceof Array; 76 | 77 | // 2.基于constructor 78 | a.constructor === Array 79 | 80 | // 3.基于Array.prototype.isProtptypeOf() 81 | Array.prototype.isPrototypeOf(a) 82 | 83 | // 4.基于Object.getPrototypeOf() 84 | Object.getPrototypeOf(a) === Array.prototype 85 | 86 | // 5.Object.prototype.toString.apply() 87 | Object.prototype.toString.apply(a) === '[Object Array]' 88 | 89 | ``` 90 | 91 | 92 | 93 | 一般主流框架都是基于最后一种方式去判断 94 | 95 | 既然用这种方式能够判断是不是数组,那是不是能够判断字符串,数组,对象....?马上实验一波 96 | 97 | ```javascript 98 | Object.prototype.toString.call('') // "[object String]" 99 | 100 | Object.prototype.toString.call(new String) // "[object String]" 101 | 102 | Object.prototype.toString.call(1) // "[object Number]" 103 | 104 | Object.prototype.toString.call(NaN) // "[object Number]" 105 | 106 | Object.prototype.toString.call(new Number) // "[object Number]" 107 | 108 | Object.prototype.toString.call(-'1') // "[object Number]" 109 | 110 | Object.prototype.toString.call(new Object) // "[object Object]" 111 | 112 | Object.prototype.toString.call({}) // "[object Object]" 113 | 114 | Object.prototype.toString.call(new Boolean) // "[object Boolean]" 115 | 116 | Object.prototype.toString.call(false) // "[object Boolean]" 117 | 118 | Object.prototype.toString.call(null) // "[object Null]" 119 | 120 | Object.prototype.toString.call(undefined) // "[object Undefined]" 121 | 122 | Object.prototype.toString.call([]) // "[object Array]" 123 | 124 | Object.prototype.toString.call(new Array) // "[object Array]" 125 | 126 | ``` 127 | -------------------------------------------------------------------------------- /js/js事件.md: -------------------------------------------------------------------------------- 1 | ### jq中 return false 到底做了什么? 2 | 3 | 函数每次调用return false时,其实做了三件事 4 | ```js 5 | event.preventDfefault(); 6 | 7 | event.stopPropagation(); 8 | 9 | 停止回调函数执行并立即返回 10 | ``` 11 | 12 | ### 对添加事件的兼容 13 | ```js 14 | // 添加事件 15 | function addEvent(el, onType, handle, bol){ 16 | if(el.addEventListener){ 17 | el.addEventListener(onType, handle, bol) 18 | }else if(el.attachEvent){ 19 | el.attachEvent('on'+onType, handle) 20 | }else{ 21 | el['on'+onType] = handle 22 | } 23 | } 24 | 25 | // 删除事件 26 | function removeEvent(el, onType, handle, bol){ 27 | if(el.addEventListener){ 28 | el.removeEventListener(onType, handle, bol) 29 | }else if(el.attachEvent){ 30 | el.detachEvent('on'+onType, handle) 31 | }else{ 32 | el['on'+onType] = null 33 | } 34 | } 35 | ``` 36 | 37 | ### Event.eventPhase属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。 38 | Event.eventPhase的返回值有四种可能。 39 | 40 | 0. 事件目前没有发生。 41 | 1. 事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。 42 | 2. 事件到达目标节点,即Event.target属性指向的那个节点。 43 | 3. 事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。 44 | 45 | http://javascript.ruanyifeng.com/dom/event.html#toc13 46 | 47 | ### 以下事件不支持冒泡事件: 48 | ```js 49 | 鼠标事件:mouserleave mouseenter 50 | 焦点事件:blur focus 51 | UI事件:scroll resize 52 | ``` -------------------------------------------------------------------------------- /js/js垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | #### javascript垃圾回收机制原理(待完善) 2 | 3 | JavaScript 在定义变量时就完成了内存分配。,创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。 4 | 5 | 1. 标记清除 6 | 7 | JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep),它的执行性能不是特别高。 8 | 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 9 | 当我们在分配一个大对象的时候,这块空间不连续,我们在进行寻址在找的过程中就非常麻烦,影响性能,另外,如果说,实在找不到了,虚拟机会自动再进行触发一次垃圾回收,那么,这个过程也是非常耗性能的 10 | 11 | 2. 引用计数 12 | 13 | 引用计数(reference counting)的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。 14 | 15 | 16 | ### 初次之外再介绍其他几种 17 | 18 | ### 复制算法 19 | 这个算法就是来解决标记-清除算法的效率问题的 20 | 21 | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management 22 | https://neveryu.github.io/2017/02/18/js-memory-management-and-gc/ 23 | https://segmentfault.com/a/1190000007315908 -------------------------------------------------------------------------------- /js/js基础.md: -------------------------------------------------------------------------------- 1 | ## js作用域链 2 | 3 | 作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,因此我们便能通过作用域链访问到父级里声明的变量或者函数。 4 | 5 | 当js代码执行的时候,都会生成一个作用域链。作用域链的作用是保证有权访问执行环境里的变量和函数是有序访问。访问作用域链的变量只能在包含他的函数中向上访问,直到全局执行环境为止(window对象),整个作用域链是由不同执行位置上的变量对象按照规则所构建一个链表 6 | 7 | 作用域链就是在内部函数中,可以访问外部函数变量的这种机制,用链式查找决定那些数据能被内部函数访问。 8 | 9 | 作用域链由`[[scope]]`指向父级变量对象和作用域链, `AO`: 自身活动对象 10 | 11 | ![图片加载中..](http://files.jb51.net/file_images/article/201605/201655141623615.png?201645141635) 12 | 13 | ## 闭包的理解 14 | 15 | 当**父函数被销毁时**, 返回子函数的`[[scope]]`中保留着父函数变量对象和作用域链 16 | 17 | 闭包是指有权访问另一个函数的作用域中变量的函数,创建闭包最常见的方式就是在函数中创建另一个函数,通过创建的这个闭包函数访问外层的局部变量,利用闭包可以突破作用域链,将变量缓存在内存中,简单来说闭包就是外部想访问一个函数内部参数或变量的桥梁,并且访问结束后将引用的变量保存在内存中供下次使用。 18 | 19 | ##### 闭包的特性: 20 | 21 | - 函数中嵌套函数 22 | 23 | - 闭包函数可以访问外层作用域的参数和变量 (沿着作用域链寻找) 24 | 25 | - 闭包内的参数和变量不会被垃圾回收机制回收 26 | 27 | - 封装变量,类似其他语言的私有变量,来限制变量的作用域 28 | 29 | ##### 闭包的优缺 30 | 31 | - 创建是为了封装和缓存变量,以供外部使用。可以避免全局变量污染 32 | 33 | - 但是闭包会保存在内存中,会增大内存的占有率,使用不当容易造成内存泄漏, 34 | 35 | - 在js中,函数就是闭包,只有函数才能产生作用域的概念 36 | 37 | - 在退出函数之前,将不使用的局部变量全部删除 38 | 39 | - 闭包和循环如果同时使用的话有时会有问题,因为闭包内的变量是保存变化的,如果创建闭包之后再使用函数的话,循环里的 i 可能会一直是最后一个值(比如最大值)。 40 | 41 | ![js闭包图解](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524246978601&di=d940f06269611579008d091b87b1d945&imgtype=0&src=http%3A%2F%2Fs15.sinaimg.cn%2Fmw690%2F0066XBEogy6SH2GHjdQ6e%26690) 42 | 43 | 44 | ## js作用域 45 | 46 | `全局作用域`: 47 | 48 | 在js中,最外层定义的变量拥有全局作用域,对任何内容来说,都是可以访问的。 49 | 50 | ```js 51 | var a = 'js'; 52 | function b(){ 53 | console.log(a) 54 | } 55 | 56 | b(); // js 57 | ``` 58 | 59 | `局部作用域`: 60 | 61 | 局部作用域在函数内部定义的变量,一般只有在当前作用域内下可以访问,而对于函数外部是不可以访问的,但是函数内部定义变量一定要写var命令,不然等同于生成了全局作用域。 62 | 63 | ```js 64 | 65 | function a(){ 66 | var b = 'js' 67 | } 68 | a() 69 | console.log(b) // ReferenceError: b is not defined 70 | 71 | 72 | function a(){ 73 | b = 'js' 74 | } 75 | a() 76 | console.log(b) // js 77 | ``` 78 | 79 | - javascript的作用域是相对函数而言的,可以称为函数作用域 80 | 81 | - 所以并不是用var声明的变量作用范围起止于花括号之间,javascript并没有块级作用域 82 | 83 | 84 | ## javascript原型链(prototype) 85 | 86 | 每一个对象内部都有一个prototype属性,当查找一个对象属性的时候,如果这个属性不存在这个对象中,就会通过这个prototype去查找这个属性,这个prototype又会有自己protoype属性,这样一层一层查找,直到Object内建对象中,如果Object中也不存在,就会返回undefined。 87 | 88 | - javascript对象赋值给新的变量的时候,实际上只是通过引用来传递的,新的变量中没有属于自己的原型副本。当我们修改原型的时候,与之相关的对象也会跟随改变,因为他们都指向同一个内存地址。 89 | 90 | ![原型图解](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524246781051&di=de48a70f756cd6ac7fb1e93d86dda1c2&imgtype=jpg&src=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D4057629261%2C3288707102%26fm%3D214%26gp%3D0.jpg) 91 | 92 | 93 | ## 事件代理 94 | 95 | 事件代理又称事件委托,是把原本需要绑定在元素的事件委托给父元素或包含他的其他元素,让委托的元素监听并处理事件。 96 | 97 | - 事件代理的原理是利用dom元素的时间冒泡机制 98 | 99 | - 使用事件代理能够提高性能 100 | 101 | - 大量节省内存,减少事件注册,(比如在table中代理所有tr元素的click事件) 102 | 103 | - 一般新增元素事件是没有添加进去的,这时就可以在父元素上用事件代理能很好解决这个问题 104 | 105 | 106 | ## this指向的理解 107 | 108 | this的指向在函数定义的时候是不确定的,只有在执行函数的时候才能确定this指向谁,实际上this的最终指向的是哪个调用他的对象内 109 | 110 | ```js 111 | function a(){ 112 | this.name = 'js' 113 | console.log(this.name,this) // undefined , window 114 | } 115 | a() // 相当于是window.a() 116 | 117 | 118 | 119 | var o = { 120 | name: 'js', 121 | fn: function (){ 122 | console.log(this.name,this) // 'js' , fn 123 | } 124 | } 125 | o.fn() // 相当于window.o.fn() 126 | ``` 127 | o.fn()是通过o调用的,this自然指向了o 128 | 129 | 1. 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,严格模式除外。 130 | 131 | 2. 如果这个函数被上一级的对象所调用,那么this指向的就是上一级的对象。尽管这个函数被多个的对象所调用,this指向的还是调用它的上一级的对象 132 | 133 | 3. 在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window 134 | 135 | ```js 136 | var o = { 137 | a:10, 138 | b:{ 139 | a:12, 140 | fn:function(){ 141 | console.log(this.a); // 12 142 | } 143 | } 144 | } 145 | o.b.fn(); 146 | ``` 147 | 148 | 尽管被多个对象调用,但是this依然指向他上一级对象 149 | 150 | 151 | ### 当this遇到return时的问题 152 | 153 | 如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this指向的还是函数实例。 154 | 155 | ```js 156 | function fn() 157 | { 158 | this.name = 'js'; 159 | return {}; 160 | } 161 | var a = new fn; 162 | console.log(a.name); //undefined 163 | // 返回值返回的是对象 164 | 165 | 166 | function fn() 167 | { 168 | this.name = 'js'; 169 | return function(){}; 170 | } 171 | var a = new fn; 172 | console.log(a.name); //undefined 173 | // 返回值返回的是匿名函数 174 | 175 | 176 | function fn() 177 | { 178 | this.name = 'js'; 179 | return undefined; 180 | } 181 | var a = new fn; 182 | console.log(a.name); //js 183 | // 返回值返回的是不是对象 184 | 185 | 186 | 187 | function fn() 188 | { 189 | this.name = 'js'; 190 | return undefined; 191 | } 192 | var a = new fn; 193 | console.log(a); //fn {name: "js"} 194 | // 返回值返回的是不是对象 195 | ``` 196 | 197 | 198 | 还有null比较特殊,虽然null也是对象,但是this指向的还是函数的实例 199 | 200 | ```js 201 | function fn() 202 | { 203 | this.name = 'js'; 204 | return null; 205 | } 206 | var a = new fn; 207 | console.log(a.name); //js 208 | ``` 209 | 210 | 211 | 总结: 212 | 213 | - 在严格模式下,默认的this不是window,而是undefined。在node中,是Global对象 214 | 215 | - new 会改变this的对象,就好像new使用了call或apply方法(但实际上可能并不是) 216 | 217 | - 当函数作为对象的方法被调用时,this就会指向该对象。 218 | 219 | - 作为普通函数,this指向window。 220 | 221 | - 构造器调用,this指向返回的这个对象。 222 | 223 | - 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上 224 | 225 | 226 | 227 | 参考文献:https://www.cnblogs.com/pssp/p/5216085.html 228 | 参考文献:https://www.cnblogs.com/humin/p/4556820.html 229 | 参考文献:https://www.cnblogs.com/liugang-vip/p/5616484.html 230 | 231 | 232 | ## 事件模型 233 | 234 | 235 | 236 | 冒泡事件: 当使用事件冒泡的时候,子元素先触发,然后父元素后触发 237 | 238 | 捕获事件: 当使用时间捕获的时候,父元素先触发,然后子元素后触发 239 | 240 | dom时间流: 同时支持冒泡和捕获事件,先触发捕获机制然后在冒泡机制 241 | 242 | 阻止冒泡: 在w3c中设置使用`stopPropagation()`方法;在IE下设置 `cancelBubble = true`. 243 | 244 | 阻止默认事件: 阻止事件默认的行为,在w3c中使用 `preventDefault()`方法;在IE下设置`window.event.returnValue = false` 245 | 246 | 247 | 248 | ## new操作符内部具体干了什么 249 | 250 | 1. 创建一个空对象 251 | 2. 设置原型链,将赋值变量的__proto__指向新对象的prototype,继承属性和方法 252 | 3. 将新的对象this指向引用的对象,就好像使用了call或apply 253 | 4. 判断函数返回值的类型,如果是对象,就会返回对象的内容,如果不是就返回这个引用类型的对象 254 | 255 | 256 | ## Ajax原理 257 | 258 | Ajax的原理就是服务器和客户端之间加了个中间层(ajax引擎),通过xmlhttpRequest对象对服务器发起异步请求,从服务器获取数据,然后用js操作dom更新页面内容,实现用户操作与服务器响应异步化 259 | 260 | ![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524670684736&di=d4ec4bc8d6c9acc21962c474591a1ee3&imgtype=0&src=http%3A%2F%2Ffilesimg.111cn.net%2F2011%2F05%2F20111119011732771.jpg%3F16) 261 | 262 | 263 | ## 几种跨域方案 264 | 265 | 1. 通过jsonp跨域 266 | 2. document.domain + iframe跨域 267 | 3. location.hash + iframe 268 | 4. window.name + iframe跨域 269 | 5. postMessage跨域 270 | 6. 跨域资源共享(CORS) 271 | 7. nginx代理跨域 272 | 8. nodejs中间件代理跨域 273 | 9. WebSocket协议跨域 274 | 275 | 276 | ## 实现模块化开发 277 | 278 | 模块化就是讲js文件按照功能分离,根据需求引入不同的文件中。 279 | 280 | 优点: 281 | 282 | 1.防止命名冲突 283 | 2.减少文件依赖 284 | 3.异步加载文件 285 | 286 | 287 | 立即执行函数,不暴露私有成员 288 | 289 | ```js 290 | var module1 = (function(){ 291 |     var _count = 0; 292 |     var m1 = function(){ 293 |       console.log(1) 294 |     }; 295 |     var m2 = function(){ 296 |       console.log(2) 297 |     }; 298 |     return { 299 |       m1 : m1, 300 |       m2 : m2 301 |     }; 302 | })(); 303 | ``` 304 | 305 | ## 异步加载js方式 306 | 1.动态生成 322 | defer属性规定是否对脚本执行进行延迟,直到页面加载为止。之前只有IE的hack支持defer属性,现在H5开始全面支持defer属性。 323 | 324 | 3.async(异步,会打乱执行顺序) 325 | 326 | async是HTML5的新属性,该属性规定一旦脚本可用,则会异步执行(一旦下载完毕就会立刻执行)。需要注意的是:async属性仅适用于外部脚本(只有在使用src属性时)。 327 | 328 | 4.XHR注入 329 | 330 | 5.ajax eval 331 | 332 | 6.iframe 333 | 334 | ## XML和JSON的区别 335 | 336 | 1. JSON 比 XML数据体积小,传输速度快,容易解析处理,数据容易交互 337 | 338 | 3. JSON 比 XML 不好理解数据格式 339 | 340 | 4. JSON的速度要远远快于XML 341 | 342 | 343 | ## js 中判断数据类型的方法 344 | ```js 345 | typeof 只能判断基本类型(number, string, Boolean, Object ,null , Symol, function) 346 | 347 | instanceof 判断已知对象类型的方法,([] instanceof Array) 348 | 349 | constructor 根据对象的constructor对象类型判断 350 | 351 | Object.prototype.toString.call([]) 根据Object方法判断 352 | ``` 353 | 354 | #### IE和标准DOM事件模型之间存在的差别 355 | 1. 这里的IE是IE11以下; 356 | 2. 参数的差别: attachEvent()的第一个参数比addEventListener()的事件名多一个"on", 357 | 且没有第三个参数,因为IE事件模型只支持冒泡事件流; 358 | 3. 事件处理函数作用域的区别: IE中事件处理程序处于全局作用域,其内的this会指向window; 359 | 4. 而用DOM(0或2)级事件的事件处理程序的作用域是元素作用域,其内的this指向其所属的元素 360 | ```js 361 | 例: document.addEventListener("click", function(){ 362 | if(this == document){ 363 | alert("此时this指向document"); 364 | } 365 | }, false); 366 | ``` 367 | 5. 事件对象event的属性方法的差别 368 | ```js 369 | IE DOM 370 | cancelBubble = true stopPropagation() //停止冒泡 371 | returnValue = false preventDefault() //阻止元素默认事件 372 | srcEelement target //事件目标 373 | ``` 374 | 375 | #### javascript 中的new做了什么? 376 | 1. 创建一个新对象 377 | 2. 将构造函数的作用域赋给新对象(this就指向这个新的对象) 378 | 3. 将构造函数的属性和方法添加到新对象中, 并且设置__proto__为构造函数的prototype (新对象就继承了构造函数的所有属性) 379 | 4. 返回新对象 380 | 381 | ```js 382 | function New(f) { 383 | return function () { 384 | var o = {"__proto__": f.prototype}; 385 | f.apply(o, arguments);//继承父类的属性 386 | return o; 387 | } 388 | } 389 | ``` 390 | 391 | #### 箭头函数this的原理: 392 | 393 | this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。 394 | 395 | 箭头函数不会绑定那些局部变量,所有涉及它们的引用,都会沿袭向上查找外层作用域链的方案来处理。 396 | 397 | ```js 398 | function foo() { 399 | setTimeout( () => { 400 | console.log("args:", arguments); 401 | },100); 402 | } 403 | 404 | foo( 2, 4, 6, 8 ); 405 | // args: [2, 4, 6, 8] 406 | ``` 407 | 这段代码中,=>箭头函数并没有绑定 arguments,所以它会以 foo() 的 arguments 来取而代之,而 super 和 new.target 也是一样的情况。 408 | -------------------------------------------------------------------------------- /js/js基础2.md: -------------------------------------------------------------------------------- 1 | #### 防抖和节流 2 | - 短时间内多次触发同一个事件,只执行最后一次,或者在开始时执行,中间不执行。比如公交车上车,要等待最后一个乘客上车 3 | - 节流是连续触发事件的过程中以一定时间间隔执行函数。节流会稀释你的执行频率,比如每间隔1秒钟,只会执行一次函数,无论这1秒钟内触发了多少次事件,都为解决高频事件而来 4 | 5 | ```js 6 | /** 7 | fn 执行的函数 8 | wait 延时的时间 9 | immediate 是否立即执行? 10 | */ 11 | function debounce = (fn, wait, immediate) => { 12 | let timeout = null 13 | return function () { 14 | const [_this, arg] = [this, arguments] 15 | timeout && clearTimeout(timeout) 16 | if (!immediate) { 17 | timeout = setTimeout(() => { 18 | fn.apply(_this, arg) 19 | }, wait); 20 | } 21 | } 22 | } 23 | 24 | function throttled = (fn, wait) => { 25 | let startTime = Date.now() 26 | let timeout = null 27 | return function () { 28 | const [ curTime, _this, arg] = [ Date.now(), this, arguments ] 29 | 30 | let diffTime = wait - (curTime - startTime) 31 | clearTimeout(timeout) 32 | if (diffTime <= 0) { 33 | fn.apply(_this, arg) 34 | startTime = Date.now() 35 | } else { 36 | tineout = setTimeout(_ => { 37 | fn.apply(_this, arg) 38 | tineout = null 39 | }, diffTime) 40 | } 41 | 42 | } 43 | } 44 | 45 | // 记录开始时间和当前执行时间,并计算出时间差,如果时间小于等于0说明可以立即执行,否则延时执行函数 46 | ``` 47 | 48 | 49 | #### JS具有自动垃圾收集的机制 50 | - JS的垃圾收集器每隔固定的时间就执行一次释放操作,通用的是通过标记清除的算法 51 | - 在局部作用域中,垃圾回收器很容易做出判断并回收,全局比较难,所以应避免全局变量 52 | - 现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。 53 | 54 | 55 | ### Fcuntion.prototype.bind的实现 56 | 57 | ```js 58 | 59 | Function.prototype.bind = function (){ 60 | var _this = this; // 缓存原函数的this指向 61 | var _thisBind = [].shift.call(arguments); // 获取传入的第一个参数,即this的指向 62 | var bingOptions = [].splice.call(arguments); // 除第一个外之后的所有参数 63 | return function(){ 64 | // 返回新的函数, 并且使用apply, 最后传入两次参数w作为apply的第二个参数 65 | return _this.apply.(_thisBind, [].concat.call(bingOptions, [].splice.call(arguments))) 66 | } 67 | } 68 | 69 | ``` 70 | 71 | ### AOP(面向切面编程)模式 72 | 73 | 在不改变原函数的代码情况下,添加类似生命周期函数,在原函数执行之前或之后执行某函数,例如在ajax请求中,在请求之前需要判断某个表单元素,判断不成功就不触发ajax请求,或者在请求之后处理数据,可以简单的制作一个http拦截器,主要还是对复杂的函数解耦, 74 | ```js 75 | function ajaxData(){ 76 | console.log('准备请求数据'); 77 | } 78 | Function.prototype.before = function(fn){ 79 | var _this = this; // 这个例子的this指向的是ajaxData 80 | if(fn.apply(this, arguments) == false){ 81 | return false; 82 | } 83 | _this.apply(this, arguments) 84 | return _this; 85 | } 86 | Function.prorotype.after = function(fn){ 87 | this.apply(this, arguments) 88 | console.log(this) 89 | } 90 | ajaxData.before(function(){ 91 | console.log('之前') 92 | }).after(function(){ 93 | console.log('之后') 94 | }) 95 | ``` 96 | 97 | 98 | ### Object.defineProperty 99 | 100 | ```js 101 | writable // 是否可修改 102 | Enumerable // 是否可以枚举 103 | Configurable // 是否可删除 104 | value // 值 105 | ``` 106 | 107 | #### slice,substring,substr的区别 108 | 109 | slice() 第一个参数代表开始位置,第二个参数代表结束位置的下一个位置,截取出来的字符串的长度为第二个参数与第一个参数之间的差;若参数值为负数,则将该值加上字符串长度后转为正值;若第一个参数等于大于第二个参数,则返回空字符串. 110 | 111 | substring() 第一个参数代表开始位置,第二个参数代表结束位置的下一个位置;若参数值为负数,则将该值转为0;两个参数中,取较小值作为开始位置,截取出来的字符串的长度为较大值与较小值之间的差. 112 | 113 | substr() 第一个参数代表开始位置,第二个参数是代表从第一个参数的下标开始截取的长度 114 | 115 | ```js 116 | 一个参数的情况 117 | s.slice(2) // 23456 118 | s.substring(2) // 23456 119 | s.substr(2) // 23456 120 | 121 | 两个参数的情况 122 | s.slice(2,4) // 23 123 | s.substring(2,4) // 23 124 | s.substr(2,4) // 2345 125 | 126 | 第二个为负值的情况 127 | s.slice(2,-1) // 2345 128 | s.substring(2,-1) // 01 129 | s.substr(2,-1) // '' 130 | 131 | 第二个值比第一个小的情况下 132 | s.slice(2,1) // '' 133 | s.substring(2,1) // 1 134 | s.substr(2,1) // 2 135 | ``` -------------------------------------------------------------------------------- /js/原生属性 proxy.md: -------------------------------------------------------------------------------- 1 | ### proxy是es6引入Proxies的,他可以改变你操作对象的方式,比如拦截对象的写入和读取属性操作 2 | 3 | ##### 一般对一个对象的赋值,其实也就是set方法里对对象的一次赋值,获取对象的属性其实就是在get里return了 对象的属性,也算是给对象加一个壳(拦截), 4 | 5 | ```js 6 | var observed = { 7 | /* 8 | * @object 正在操作的对象本身 9 | * @prop 被访问的对象属性 10 | * @value 接受的新值 11 | */ 12 | set: (object, prop, value) => { 13 | // object[prop] = value 14 | }, 15 | /* 16 | * @object 正在操作的对象本身 17 | * @prop 被访问的对象属性 18 | */ 19 | get: (object, prop) => { 20 | // return object[prop] 21 | } 22 | } 23 | /* 24 | * 创建proxy对象 25 | * new Proxy 接受两个参数 1. 观察的值 2. handler 定义get或set逻辑 26 | * get在获取对象属性时触发, set在赋值对象属性时触发 27 | * 返回Proxy对象 28 | */ 29 | let obj = new Proxy({}, observed) 30 | obj.name = 'js' 31 | // js 32 | console.log(obj.name) 33 | // undefined 34 | console.log(obj) 35 | // Proxy {} 36 | ``` 37 | 38 | ##### 如果把set和get里的注释去掉,就可以正常的对对象的属性赋值和获取,就是我们生活中正常的使用对象 39 | 40 | #### 还可以对传入的值和获取对象属性的行为做一次验证,例如实现私有对象属性: 41 | #### 不能获取身高的值,但是可以赋值 42 | #### 不能修改体重,但是可以获取 43 | #### 爱好即可以修改也可以获取 44 | ```js 45 | var my = { 46 | height: 190, 47 | weight: 115, 48 | hobby: 'js' 49 | } 50 | var observed = { 51 | set: (object, prop, value) => { 52 | if (prop == 'weight') { 53 | throw new TypeError('不能修改体重') 54 | } else { 55 | object[prop] = value 56 | } 57 | }, 58 | get: (object, prop) => { 59 | if (prop == 'height') { 60 | throw new TypeError('不能获取身高') 61 | } else { 62 | return object[prop] 63 | } 64 | } 65 | } 66 | let obj = new Proxy(my, observed) 67 | obj.weight = 130 // 会报错不能修改体重 68 | consoel.log(obj.weight) // 115 , 上一句赋值没有成功 69 | 70 | console.log(obj.height) // 会报错不能获取身体 71 | obj.height = 170 72 | console.log(obj) 73 | // Proxy {height: 170, weight: 115, hobby: "js"} 虽然不能获取,但是可以修改 74 | 75 | obj.hobby = 'java' 76 | console.log(obj.hobby) 77 | // java 即可以修改,也可以获取 78 | ``` 79 | #### 当然我只是举个小例子,其实实用的功能很强大 80 | 81 | #### 还有时你希望切换两个不同的元素的属性或类名。例如: 82 | ```js 83 | let view = new Proxy({ 84 | selected: null 85 | }, 86 | { 87 | set: function(obj, prop, newval) { 88 | let oldval = obj[prop]; 89 | 90 | if (prop === 'selected') { 91 | if (oldval) { 92 | oldval.setAttribute('aria-selected', 'false'); 93 | } 94 | if (newval) { 95 | newval.setAttribute('aria-selected', 'true'); 96 | } 97 | } 98 | 99 | // The default behavior to store the value 100 | obj[prop] = newval; 101 | } 102 | }); 103 | 104 | let i1 = view.selected = document.getElementById('item-1'); 105 | console.log(i1.getAttribute('aria-selected')); // 'true' 106 | 107 | let i2 = view.selected = document.getElementById('item-2'); 108 | console.log(i1.getAttribute('aria-selected')); // 'false' 109 | console.log(i2.getAttribute('aria-selected')); // 'true' 110 | ``` 111 | 112 | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable -------------------------------------------------------------------------------- /js/原生属性.md: -------------------------------------------------------------------------------- 1 | ### getComputedStyle和currentStyle 2 | 3 | element.style 获取的是内联样式或设置样式 , 如果获取指定的属性名 style中 4 | 5 | 不存在则返回空 6 | 7 | element.currentStyle 这是ie专有的属性,只在ie下支持,在获取未设置的属性 8 | 9 | 值时,currentStyle会读取浏览器的默认值 10 | 11 | document.defaultView.getComputedStyle(element,null) ie6~ie8是不支持的, 12 | 13 | 获取属性和currentStyle类似 14 | 15 | ```js 16 |
17 | 18 | // js 19 | var div = document.querySelector('div') 20 | div.style.height 21 | // "100px" 22 | div.style.width 23 | // "" 24 | 25 | 26 | div.currentStyle['height'] 27 | // "100px" 28 | div.currentStyle['width'] 29 | // "0px" 30 | 31 | document.defaultView.getComputedStyle(div,null).height 32 | // "100px" 33 | document.defaultView.getComputedStyle(div,null).backgroundColor 34 | // "rgba(0, 0, 0, 0)",js中属性名是需要驼峰写法 35 | document.defaultView.getComputedStyle(div,null)['background-color'] 36 | // "rgba(0, 0, 0, 0)",数组中字符串可以不用驼峰写法 37 | 38 | 39 | // 兼容写法 40 | Element.prototype.getComputedStyle = function(x){ 41 | return getComputedStyle(this)[x] || this.currentStyle[x] || this.style[x] 42 | } 43 | div.getComputedStyle('height') 44 | // "100px" 45 | ``` -------------------------------------------------------------------------------- /js/数组大全.md: -------------------------------------------------------------------------------- 1 | ### `pop` `shift` `push` `unshift` `reverse` `sort` `splice` `copyWithin` `fill` 这些都是会改变自身的值的数组方法 2 | 3 | `pop 删除数组的最后一个元素` 4 | 有意思的是,pop的设计是根据长度进行删除的,也就是说如果在对象中声明了length的长度,也可以删除? 5 | 6 | ```javascript 7 | var obj = {1:'javascrip',2:'css',3:'vue',length:4} 8 | Array.prototype.pop.call(obj) 9 | console.log(obj) 10 | 11 | // {1: "javascrip", 2: "css", length: 3} 12 | 13 | 如果length为0,将无法删除 14 | var obj = {1:'javascrip',2:'css',3:'vue',length:0} 15 | Array.prototype.pop.call(obj) 16 | console.log(obj) 17 | 18 | //{1: "javascrip", 2: "css", 3: "vue", length: 0} 19 | ``` 20 | 21 | `shift 删除数组的第一个元素` 22 | 同样和pop类似,根据长度删除 23 | 24 | ```javascript 25 | var obj = {1:'javascrip',2:'css',3:'vue',length:4} 26 | Array.prototype.shift.call(obj) 27 | console.log(obj) 28 | 29 | // {0: "javascrip", 1: "css", 2: "vue", length: 3} 30 | 31 | 如果length为0,也同样无法删除 32 | var obj = {1:'javascrip',2:'css',3:'vue',length:0} 33 | Array.prototype.shift.call(obj) 34 | console.log(obj) 35 | 36 | //{1: "javascrip", 2: "css", 3: "vue", length: 0} 37 | ``` 38 | 39 | 40 | `push 在数组的末尾添加一个或多个元素 并且返回新的数组长度` 41 | 同样是根据长度添加元素,那么可以在指定位置上添加元素? 42 | 43 | ```javascript 44 | var obj = {1:'javascrip',2:'css',3:'vue',length:4} 45 | Array.prototype.push.call(obj,'jq') 46 | console.log(obj) 47 | 48 | // {1: "javascrip", 2: "css", 3: "vue", 4: "jq", length: 5} 49 | 50 | var obj = {1:'javascrip',2:'css',3:'vue',length:3} 51 | Array.prototype.push.call(obj,'jq') 52 | console.log(obj) 53 | 54 | // {1: "javascrip", 2: "css", 3: "jq", length: 4} 55 | 只是在指定位置上替换元素 56 | 57 | 用applay 可实现合并数组 58 | var obj = [1] ; var obj1 = [2] 59 | var obj3 = Array.prototype.push.apply(obj,obj1) 60 | console.log(obj) 61 | ``` 62 | 63 | `unshift 在数组开头添加一个或多个元素,并且返回新的数组长度` 64 | 同样可以指定length位置上添加元素,如果使用Array.prorotype.unshift.call(),那么会替换掉第一个原色 65 | 但是如果length为0,那么就会被解释器认为数组长度为0,会从对象的下标0开始插入,相应的位置属性会被替换掉 66 | 67 | ```javascript 68 | var obj = {1:'javascrip',2:'css',3:'vue',length:4} 69 | Array.prototype.unshift.apply(obj,['w']) 70 | console.log(obj) 71 | 72 | // {0: "w", 2: "javascrip", 3: "css", 4: "vue", length: 5} 73 | // 替换掉第一个元素 74 | 75 | var obj = {1:'javascrip',2:'css',3:'vue',length:0} 76 | Array.prototype.unshift.apply(obj,['w']) 77 | console.log(obj) 78 | 79 | // {0: "w", 1: "javascrip", 2: "css", 3: "vue", length: 1} 80 | // 在开头上添加一个元素,实现正常的unshift 81 | ``` 82 | 83 | `reverse 颠倒数组元素的位置 返回使用该函数的引用` 84 | 同样可以Array.prorotype.unshift.call(),颠倒的顺序只在length的范围内 85 | 86 | ```javascript 87 | var obj = {1:'javascrip',2:'css',3:'vue',length:2} 88 | Array.prototype.reverse.call(obj) 89 | console.log(obj) 90 | 91 | // {0: "javascrip", 2: "css", 3: "vue", length: 2} 92 | 93 | 如果length为0,相当于没有执行 94 | ``` 95 | 96 | `sort 对数组的元素排序 并返回这个数组` 97 | `arr.sort([comparefn])` 98 | sort可以指定一个带返回的comparefn函数,如果省略掉,数组元素就会按照各自转换为Unicode(万国码)位点顺序排序,比如javascript比css靠前,数字排序比较的时候,也会先转换成字符串的Unicode进行排序,比如:'25'比'8'靠前 99 | 100 | ```javascript 101 | var obj = ['javascript','css','vue'] 102 | Array.prototype.sort.call(obj) 103 | console.log(obj) 104 | 105 | // ["css", "javascript", "vue"] 106 | 107 | var obj = ['1','21','13','21'] 108 | Array.prototype.sort.call(obj) 109 | console.log(obj) 110 | 111 | // ["1", "13", "21", "21"] 112 | ``` 113 | 114 | 如果指定了带返回的comparefn函数,数组将按照该函数的返回值来排序,若a和b是两个将要比较的元素 115 | 116 | ```javascript 117 | comparefn(a,b) > 0 // 如果b比a大,那么排在前面 118 | comparefn(a,b) < 0 // 如果a比b大,那么排在前面 119 | comparefn(a,b) = 0 // 相对位置不变 120 | 121 | // 也可以在comparefn内直接返回比较后的值 122 | 123 | comparefn(a,b){ 124 | return a > b // 数字比较可以用: a-b 125 | } 126 | 127 | comparefn(a,b){ 128 | return a < b // 数字比较可以用 b - a 129 | } 130 | 131 | comparefn(a,b){ 132 | return a = b 133 | } 134 | ``` 135 | 136 | 如果数组元素为非ASCII字符的字符串(如包含类似 e、é、è、a、ä 或中文等非英文字符的字符串),则需要使用String.localCompare 137 | 138 | ```javascript 139 | var arr = ['互','联','网','改','变','世','界']; 140 | arr.sort(function (a,b){ 141 | return a.localeCompare(b) 142 | }) 143 | console.log(arr) 144 | 145 | // (7) ["世", "互", "变", "改", "界", "网", "联"] 146 | ``` 147 | 148 | sort 也同样适用Array.prorotype.unshift.call() 149 | 150 | ```javascript 151 | var obj = {0:'互',1:'联',2:'网',3:'改',4:'变',5:'世',6:'界',length:4}; 152 | Array.prototype.sort.call(obj,function(a, b){ 153 | return a.localeCompare(b); 154 | }); 155 | console.log(obj); 156 | 157 | // {0: "互", 1: "改", 2: "网", 3: "联", 4: "变", 5: "世", 6: "界", length: 4} 158 | ``` 159 | 160 | ### Chrome的不同 161 | ECMAscript规范中并未规定具体的sort算法,这就势必导致各个浏览器不尽相同的sort算法,下面是Chrome下使用sort的表现: 162 | 163 | ```javascript 164 | arr = [{s:'1',a:'a'},{s:'1',a:'b'},{s:'1',a:"c"},{s:'1',a:'d'},{s:'1',a:'e'},{s:'1',a:'f'},{s:'1',a:'g'},{s:'1',a:'h'},{s:'q',a:'i'},{s:'1',a:'j'},{s:'1',a:'k'}] 165 | arr.sort(function(v1,v2){ 166 | return v1.s-v2.s 167 | }) 168 | for(let i in arr){ 169 | console.log(arr[i].a) 170 | } 171 | 172 | // f a c d e b g h i j k 173 | ``` 174 | 175 | 由于s值相等,arr数组排序前后应该不变,然而Chrome输出的却不同,而其他浏览器(如IE 或 Firefox) 表现正常。 176 | 当排序的数组长度超过10条时,会调用另一种排序方法(快速排序);而10条及以下采用的是插入排序,所以在使用sort排序时改变排序的返回值即可,应该这么写才能将结果准确输出: 177 | 178 | ```javascript 179 | arr = [{s:'1',a:'a'},{s:'1',a:'b'},{s:'1',a:"c"},{s:'1',a:'d'},{s:'1',a:'e'},{s:'1',a:'f'},{s:'1',a:'g'},{s:'1',a:'h'},{s:'q',a:'i'},{s:'1',a:'j'},{s:'1',a:'k'}] 180 | arr.forEach(function(v,k){ 181 | v.__index = k 182 | }) 183 | arr.sort(function(v1,v2){ 184 | return v1.s-v2.s || v1.__index - v2.__index 185 | }) 186 | for(let i in arr){ 187 | console.log(arr[i].a) 188 | } 189 | 190 | // a b c d e f g h i j k 191 | ``` 192 | 193 | 而且使用数组的sort的排序方法需要注意的是,各浏览器的针对sort方法的内部算法实现不同,排序函数尽量值返回-1,0,1三种不同的值,不要尝试返回true和false等其它数值,因为可能导致不可靠的排序结果 194 | 195 | 196 | ### 不同的浏览器及脚本引擎 197 | |Browser Name |ECMAScript Engine| 198 | |-----|-----| 199 | |Internet Explorer 6 - 8 |JScript| 200 | |Internet Explorer 9 - 10 |Chakra| 201 | |Firefox |SpiderMonkey, IonMonkey, TraceMonkey| 202 | |Chrome |V8| 203 | |Safair |JavaScriptCore(SquirrelFish Extreme)| 204 | |Opera |Carakan| 205 | 206 | 207 | 分析以下代码,预期将数组元素进行升序排序: 208 | 209 | ```javascript 210 | var array = [7, 6, 5, 4, 3, 2, 1, 0, 8, 9]; 211 | var comparefn = function (x, y) { 212 | return x > y; 213 | }; 214 | array.sort(comparefn); 215 | ``` 216 | 217 | 代码中,comparefn 函数返回值为 bool 类型,并非为规范规定的 -1、0、1 值。那么执行此代码,各 JS 脚本引擎实现情况如何? 218 | 219 | | |输出结果| 是否符合预期| 220 | |---|------|----------| 221 | |JScript |[2, 3, 5, 1, 4, 6, 7, 0, 8, 9] |否| 222 | |Carakan |[0, 1, 3, 8, 2, 4, 9, 5, 6, 7] |否| 223 | |Chakra & JavaScriptCore |[7, 6, 5, 4, 3, 2, 1, 0, 8, 9] |否| 224 | |SpiderMonkey |[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |是| 225 | |V8 |[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |是| 226 | 227 | 根据表中数据可见,当数组内元素个数小于等于 10 时,现象如下: 228 | - JScript & Carakan 排序结果有误 229 | - Chakra & JavaScriptCore 看起来没有进行排序 230 | - SpiderMonkey 返回了预期的正确结果 231 | - V8 暂时看起来排序正确 232 | 233 | 将数组元素扩大至 11 位,现象如下: 234 | ```javascript 235 | var array = [7, 6, 5, 4, 3, 2, 1, 0, 10, 9, 8]; 236 | var comparefn = function (x, y) { 237 | return x > y; 238 | }; 239 | array.sort(comparefn); 240 | ``` 241 | 242 | |JavaScript引擎 |输出结果 |是否符合预期| 243 | |----|----|----| 244 | |JScript |[2, 3, 5, 1, 4, 6, 7, 0, 8, 9, 10] |否| 245 | |Carakan |[0, 1, 3, 8, 2, 4, 9, 5, 10, 6, 7] |否| 246 | |Chakra & JavaScriptCore |[7, 6, 5, 4, 3, 2, 1, 0, 10, 8, 9] |否| 247 | |IonMonkey |[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |是| 248 | |V8 |[5, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10] |否| 249 | 250 | 根据表中数据可见,当数组内元素个数大于 10 时: 251 | 252 | - JScript & Carakan 排序结果有误 253 | - Chakra & JavaScriptCore 看起来没有进行排序 254 | - SpiderMonkey 返回了预期的正确结果 255 | - V8 排序结果由正确转为不正确 256 | 257 | (感觉sort稍微复杂了点,主要还是ECMAscript规范中并未规定sort算法,所以导致每个浏览器的有不同的算法,不同的结果) 258 | 259 | 260 | ### splice 新元素替换旧元素的方式修改数组,特别适用于需要维持原数组引用时,就地删除或者新增元素,splice最适合 261 | 262 | `arr.splice(start,deleteCount[, item1[, item2[, …]]])` 263 | `start` 指定从哪一位开始修改内容,如果超过了数组的长度,则从数组的末尾开始修改内容; 264 | 如果是负数,则其指定的索引位置等同于length+start(length为数组的长度),表示从数组的末尾开始的第-start位开始; 265 | 266 | 267 | `deleteCount` 指定要删除的元素个数,若等于0,则不删除。这种情况下,至少应该添加一位新元素,若大于start之后的元素和,则start及之后的元素都被删除。 268 | 269 | `itemN` 指定替换的删除后的元素或新增元素,如果忽略掉,则该方法只会删除元素 270 | 271 | `返回值` 由原数组中被删除元素组成的数组,如果没有删除,则返回一个数组,比如: 272 | ```javascript 273 | var array = ["apple","boy"]; 274 | var splices = array.splice(1,1); 275 | console.log(array); // ["apple"] 276 | console.log(splices); // ["boy"] ,可见是从数组下标为1的元素开始删除,并且删除一个元素,由于itemN缺省,故此时该方法只删除元素 277 | 278 | array = ["apple","boy"]; 279 | splices = array.splice(2,1,"cat"); 280 | console.log(array); // ["apple", "boy", "cat"] 281 | console.log(splices); // [], 可见由于start超过数组长度,此时从数组末尾开始添加元素,并且原数组不会发生删除行为 282 | 283 | array = ["apple","boy"]; 284 | splices = array.splice(-2,1,"cat"); 285 | console.log(array); // ["cat", "boy"] 286 | console.log(splices); // ["apple"], 可见当start为负值时,是从数组末尾开始的第-start位开始删除,删除一个元素,并且从此处插入了一个元素 287 | 288 | array = ["apple","boy"]; 289 | splices = array.splice(-3,1,"cat"); 290 | console.log(array); // ["cat", "boy"] 291 | console.log(splices); // ["apple"], 可见即使-start超出数组长度,数组默认从首位开始删除 292 | 293 | array = ["apple","boy"]; 294 | splices = array.splice(0,3,"cat"); 295 | console.log(array); // ["cat"] 296 | console.log(splices); // ["apple", "boy"], 可见当deleteCount大于数组start之后的元素总和时,start及之后的元素都将被删除 297 | ``` 298 | 299 | 同样,splice可以用在Array.prototype.splice.call()上 300 | 301 | ```javascript 302 | arr = {1:'javascript',2:'css',3:"jq",length:'2'} 303 | Array.prototype.splice.call(arr,1,1) 304 | console.log(arr) 305 | // {2: "css", 3: "jq", length: 1} 306 | ``` 307 | 308 | 如果需要删除数组中一个已存在的元素,可参考如下: 309 | 310 | ```javascript 311 | var array = ['a','b','c']; 312 | array.splice(array.indexOf('b'),1); 313 | ``` 314 | 315 | 316 | ### copyWihtnin 基于ECMAScript 2015 (es6)规范,用于数组内元素之间的替换,即替换的元素和被替换的元素均是数组内的元素。(更简单的来说就是将一个数组内的元素替换到 别的位置及属性) 317 | 318 | 语法:arr.copyWithin(target, start[, end = this.length]) 319 | 320 | ```javascript 321 | arr.copyWithin(target) 322 | 323 | arr.copyWithin(target, start) 324 | 325 | arr.copyWithin(target, start, end) 326 | 327 | arr.copyWithin(目标索引, [源开始索引], [结束源索引]) 328 | ``` 329 | 330 | `target` 331 | 0 为基底的索引,复制序列到该位置。如果是负数,target 将从末尾开始计算。 332 | 如果 target 大于等于 arr.length,将会不发生拷贝。如果 target 在 start 之后,复制的序列将 被修改以符合 arr.length。 333 | 334 | `start` 335 | 0 为基底的索引,开始复制元素的起始位置。如果是负数,start 则其指定的索引位置等同于length+start,将从末尾开始计算。 336 | 如果 start 被忽略,copyWithin 将会从0开始复制。 337 | 338 | `end` 339 | 0 为基底的索引,开始复制元素的结束位置。copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。如果是负数, end 将从末尾开始计算。 340 | 如果 end 被忽略,copyWithin 将会复制到 arr.length。(但是我觉得因为换成:如果 end 被忽略,那么end默认为数组的索引长度,) 341 | 342 | `copyWithin`方法不要求其this值必须是一个数组对象;除此之外,copyWithin是一个可变方法,它可以改变this对象本身,并且返回它,而不仅仅是它的拷贝。 343 | 344 | MDN的例子很好的说明了: 345 | 346 | ```javascript 347 | [1, 2, 3, 4, 5].copyWithin(-2); 348 | // [1, 2, 3, 1, 2] 349 | 350 | [1, 2, 3, 4, 5].copyWithin(0, 3); 351 | // [4, 5, 3, 4, 5] 352 | 353 | [1, 2, 3, 4, 5].copyWithin(0, 3, 4); 354 | // [4, 2, 3, 4, 5] 355 | 356 | [1, 2, 3, 4, 5].copyWithin(-2, -3, -1); 357 | // [1, 2, 3, 3, 4] 358 | 359 | [].copyWithin.call({length: 5, 3: 1}, 0, 3); 360 | // {0: 1, 3: 1, length: 5} 361 | 362 | var i32a = new Int32Array([1, 2, 3, 4, 5]); 363 | 364 | i32a.copyWithin(0, 2); 365 | // Int32Array [3, 4, 5, 4, 5] 366 | 367 | [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); 368 | // Int32Array [4, 2, 3, 4, 5] 369 | ``` 370 | 371 | 不支持copyWithin,可以使用polyfill 372 | ```javascript 373 | if (!Array.prototype.copyWithin) { 374 | Array.prototype.copyWithin = function(target, start/*, end*/) { 375 | // Steps 1-2. 376 | if (this == null) { 377 | throw new TypeError('this is null or not defined'); 378 | } 379 | 380 | var O = Object(this); 381 | 382 | // Steps 3-5. 383 | var len = O.length >>> 0; 384 | 385 | // Steps 6-8. 386 | var relativeTarget = target >> 0; 387 | 388 | var to = relativeTarget < 0 ? 389 | Math.max(len + relativeTarget, 0) : 390 | Math.min(relativeTarget, len); 391 | 392 | // Steps 9-11. 393 | var relativeStart = start >> 0; 394 | 395 | var from = relativeStart < 0 ? 396 | Math.max(len + relativeStart, 0) : 397 | Math.min(relativeStart, len); 398 | 399 | // Steps 12-14. 400 | var end = arguments[2]; 401 | var relativeEnd = end === undefined ? len : end >> 0; 402 | 403 | var final = relativeEnd < 0 ? 404 | Math.max(len + relativeEnd, 0) : 405 | Math.min(relativeEnd, len); 406 | 407 | // Step 15. 408 | var count = Math.min(final - from, len - to); 409 | 410 | // Steps 16-17. 411 | var direction = 1; 412 | 413 | if (from < to && to < (from + count)) { 414 | direction = -1; 415 | from += count - 1; 416 | to += count - 1; 417 | } 418 | 419 | // Step 18. 420 | while (count > 0) { 421 | if (from in O) { 422 | O[to] = O[from]; 423 | } else { 424 | delete O[to]; 425 | } 426 | 427 | from += direction; 428 | to += direction; 429 | count--; 430 | } 431 | 432 | // Step 19. 433 | return O; 434 | }; 435 | } 436 | ``` 437 | 438 | copyWithin也同样可以用Array.prototype.copyWithin.call() 439 | 440 | ```javascript 441 | var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5} 442 | Array.prototype.copyWithin.call(o,0,3); 443 | 444 | // {0: 4, 1: 5, 2: 3, 3: 4, 4: 5, length: 5} 445 | ``` 446 | 447 | ### fill 用一个固定值替换数组内从起始索引到终止索引直接的全部元素 448 | 449 | `arr.fill(value, start, end)` 450 | value替换数组元素的值 451 | start起始索引,默认为0,如果是个负数,则开始索引为length+start 452 | end 终止索引,默认为数组索引的长度,如果是个负数,则终止索引为length+end 453 | 454 | ```javascript 455 | [1, 2, 3].fill(4) // [4, 4, 4] 456 | [1, 2, 3].fill(4, 1) // [1, 4, 4] 457 | [1, 2, 3].fill(4, 1, 2) // [1, 4, 3] 458 | [1, 2, 3].fill(4, 1, 1) // [1, 2, 3] 459 | [1, 2, 3].fill(4, -3, -2) // [4, 2, 3] 460 | [1, 2, 3].fill(4, NaN, NaN) // [1, 2, 3] 461 | Array(3).fill(4); // [4, 4, 4] 462 | [].fill.call({length: 3}, 4) // {0: 4, 1: 4, 2: 4, length: 3} 463 | ``` 464 | 不支持fill可以使用polyfill 465 | ```javascript 466 | if (!Array.prototype.fill) { 467 | Object.defineProperty(Array.prototype, 'fill', { 468 | value: function(value) { 469 | 470 | // Steps 1-2. 471 | if (this == null) { 472 | throw new TypeError('this is null or not defined'); 473 | } 474 | 475 | var O = Object(this); 476 | 477 | // Steps 3-5. 478 | var len = O.length >>> 0; 479 | 480 | // Steps 6-7. 481 | var start = arguments[1]; 482 | var relativeStart = start >> 0; 483 | 484 | // Step 8. 485 | var k = relativeStart < 0 ? 486 | Math.max(len + relativeStart, 0) : 487 | Math.min(relativeStart, len); 488 | 489 | // Steps 9-10. 490 | var end = arguments[2]; 491 | var relativeEnd = end === undefined ? 492 | len : end >> 0; 493 | 494 | // Step 11. 495 | var final = relativeEnd < 0 ? 496 | Math.max(len + relativeEnd, 0) : 497 | Math.min(relativeEnd, len); 498 | 499 | // Step 12. 500 | while (k < final) { 501 | O[k] = value; 502 | k++; 503 | } 504 | 505 | // Step 13. 506 | return O; 507 | } 508 | }); 509 | } 510 | ``` 511 | `在MDN上有一句:fill 方法故意被设计成通用方法, 它需要this值是个对象,类数组对象调用会报错` 512 | 513 | 但是发现 fill 也同样适用于Array.prototype.fill.call() 514 | 515 | ```javascript 516 | var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5} 517 | var o2 = Array.prototype.fill.call(o,10,0,2); 518 | 519 | // {0: 10, 1: 10, 2: 3, 3: 4, 4: 5, length: 5} 520 | ``` 521 | 522 | 与copyWithin不同的是,copyWithin是只能数组之间替换元素 523 | 而fill是指定一个固定值然后替换掉数组相应的数组索引 524 | 525 | 526 | ### `concat` `join` `slice` `toString` `toLocateString` `indexOf` `lastIndexOf` 都是不会改变自身的数组方法 527 | 528 | 529 | ### concat 将传入的数组或元素与原数组合并,组成新的数组并返回 530 | 531 | 语法:arr.concat(value1, value2, …, valueN) 532 | 533 | ```javascript 534 | var arr = [1,2] 535 | var arr2 = arr.concat([3,4],[5,6]) 536 | console.log(arr2) 537 | 538 | // [1, 2, 3, 4, 5, 6] 539 | ``` 540 | 541 | 若concat不传入参数,那么将基于原数组浅复制生成一个一样的新数组(指向新的地址空间) 542 | ```javascript 543 | var arr = [1,2] 544 | var arr2 = arr.concat() 545 | arr2.push(1) 546 | console.log(arr,arr2) 547 | 548 | // [1, 2] [1, 2, 1] 549 | ``` 550 | 551 | 552 | 同样,concat也可以用Array.prototype.concat.call(),但是结果却不是我们想要的 553 | 554 | ```javascript 555 | var arr = {1:'q',2:'w',length:2} 556 | var arr2 = Array.prototype.concat.call(arr,'q',{3:'e',length:1}) 557 | console.log(arr2) 558 | 559 | // 0:{1: "q", 2: "w", length: 2} 560 | // 1:"q" 561 | // 2:{3: "e", length: 1} 562 | ``` 563 | 类数组对象合并后返回的是依然是数组,并不是我们想要的结果 564 | 565 | 566 | ### join 将数组中的所有元素连接成一个字符串 567 | 568 | 语法:arr.join([separator = ‘,’]) separator可选,缺省默认为逗号。 569 | 570 | ```javascript 571 | var arr = ['js','css','jq'] 572 | var arr2 = arr.join() 573 | var arr3 = arr.join('-') 574 | var arr4 = arr.join(' 1 ') 575 | console.log(arr2,arr3,arr4) 576 | 577 | // js,css,jq js-css-jq js 1 css 1 jq 578 | ``` 579 | 580 | 同样,join也可以用Array.prototype.join.call() 581 | 582 | ```javascript 583 | var arr = ['js','css','jq'] 584 | var arr2 = Array.prototype.join.call(arr,'-') 585 | console.log(arr2) 586 | 587 | // js-css-jq 588 | ``` 589 | 590 | 591 | ### slice 将数组的一部分浅复制存入新的数组对象里,并且返回这个数组对象 592 | 593 | 语法:arr.slice([start[, end]]) 594 | 595 | start 从数组的索引开始提取元素,如果传入的参数为负数,则表示从原数组中的末尾倒数第几个元素,如果省略,那么slice的索引为0开始。 596 | end 结束数组的索引位置,并且提取start到end的索引(不包括end)如果被省略或者end大于数组的索引长度,slice会一直提取到原数组的末尾(相当于从start到数组的最后一个元素) 597 | 598 | 按照MDN的对slice的描述: 599 | 600 | ``` 601 | slice 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝: 602 | 603 | 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。 604 | 605 | 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。 606 | 607 | 如果向两个数组任一中添加了新元素,则另一个不会受到影响。 608 | ``` 609 | 610 | ```javascript 611 | var arr = ['js','css','jq'] 612 | var arr2 = arr.slice(0,1) 613 | arr2.push('vue') 614 | console.log(arr,arr2) 615 | // ["js", "css", "jq"] (2) ["js", "vue"] 616 | ``` 617 | 618 | slice 也适用于Array.prototype.slice.call() 619 | 620 | ```javascript 621 | var arr = [1,2,3] 622 | var arr2 = Array.prototype.slice.call(arr,0,1) 623 | console.log(arr2) 624 | // [1] 625 | ``` 626 | 627 | 另外不支持的可以用polyfill 628 | 629 | ```javascript 630 | (function () { 631 | 'use strict'; 632 | var _slice = Array.prototype.slice; 633 | 634 | try { 635 | // Can't be used with DOM elements in IE < 9 636 | _slice.call(document.documentElement); 637 | } catch (e) { // Fails in IE < 9 638 | // This will work for genuine arrays, array-like objects, 639 | // NamedNodeMap (attributes, entities, notations), 640 | // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes), 641 | // and will not fail on other DOM objects (as do DOM elements in IE < 9) 642 | Array.prototype.slice = function (begin, end) { 643 | // IE < 9 gets unhappy with an undefined end argument 644 | end = (typeof end !== 'undefined') ? end : this.length; 645 | 646 | // For native Array objects, we use the native slice function 647 | if (Object.prototype.toString.call(this) === '[object Array]'){ 648 | return _slice.call(this, begin, end); 649 | } 650 | 651 | // For array like object we handle it ourselves. 652 | var i, cloned = [], 653 | size, len = this.length; 654 | 655 | // Handle negative value for "begin" 656 | var start = begin || 0; 657 | start = (start >= 0) ? start: len + start; 658 | 659 | // Handle negative value for "end" 660 | var upTo = (end) ? end : len; 661 | if (end < 0) { 662 | upTo = len + end; 663 | } 664 | 665 | // Actual expected size of the slice 666 | size = upTo - start; 667 | 668 | if (size > 0) { 669 | cloned = new Array(size); 670 | if (this.charAt) { 671 | for (i = 0; i < size; i++) { 672 | cloned[i] = this.charAt(start + i); 673 | } 674 | } else { 675 | for (i = 0; i < size; i++) { 676 | cloned[i] = this[start + i]; 677 | } 678 | } 679 | } 680 | 681 | return cloned; 682 | }; 683 | } 684 | }()); 685 | ``` 686 | 687 | ### toString 返回数组的字符串形式,该字符串由数组中的每个元素的toString()返回值经调用join()方法连接(由逗号隔开)组成。(相当于toString就是用arr.join()实现的) 688 | 689 | ```javascript 690 | var arr = [1,2,3] 691 | var arr2 = arr.toString() 692 | console.log(arr2) 693 | 694 | // 1,2,3 695 | ``` 696 | 697 | 当数组直接和字符串做连接操作时,会自动调用toString方法 698 | ```js 699 | [1,2,3,4] + [,5] 700 | 701 | // 1,2,3,4,5 702 | 703 | var arr = {1:'q',2:'w',3:'e',length:4} 704 | var arr2 = Array.prototype.toString.call(arr) 705 | console.log(arr2) 706 | // [object Object] 707 | ``` 708 | 709 | 根据ES5语义,toString() 方法是通用的,可被用于任何对象。 710 | 如果对象有一个join() 方法,将会被调用,其返回值将被返回,没有则调用Object.prototype.toString(),为此,我们给o对象添加一个join方法。如下: 711 | 712 | ```js 713 | var o = { 714 | 0:'Jan', 715 | 1:'Feb', 716 | 2:'Mar', 717 | length:3, 718 | join:function(){ 719 | return Array.prototype.join.call(this); 720 | } 721 | }; 722 | console.log(Array.prototype.toString.call(o)); 723 | ``` 724 | 725 | ### toLocalString 类似toString的变形,该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。 726 | 727 | 数组不同的元素将调用各自的toLocalString方法 728 | 729 | - Object: Object.prototype.toLocalString 730 | - Number: Number.prototype.toLocalString 731 | - Date : Date.prototype.toLocalString 732 | 733 | 734 | ```js 735 | var o = { 736 | 0:123, 737 | 1:'abc', 738 | 2:new Date(), 739 | length:3, 740 | join:function(){ 741 | return Array.prototype.join.call(this); 742 | } 743 | }; 744 | console.log(Array.prototype.toLocaleString.call(o)) 745 | // 123,abc,2018/1/24 下午4:13:45 746 | ``` 747 | 748 | ### indexOf 查找元素在数组中第一次出现的索引位置,如果没有,则返回-1 749 | 750 | 语法:arr.indexOf(element, fromIndex=0) 751 | 752 | element 需要查找的元素 753 | 754 | fromIndex 为开始查找的位置,缺省默认为0,如果超出数组的长度则返回-1。 755 | 如果为负值,假设数组长度为length,则从数组的第length+fromIndex项开始往末尾查找,如果length+fromIndex<0则整个数组都会被查找。 756 | 757 | indexOf使用严格相等(即===去匹配数组中的元素) 758 | 759 | 760 | ```js 761 | var array = ['abc', 'def', 'ghi','123']; 762 | console.log(array.indexOf('def')); // 1 763 | console.log(array.indexOf('def',-1)); // -1 此时表示从最后一个元素往后查找,因此查找失败返回-1 764 | console.log(array.indexOf('def',-4)); // 1 由于4大于数组长度,此时将查找整个数组,因此返回1 765 | console.log(array.indexOf(123)); // -1, 由于是严格匹配,因此并不会匹配到字符串'123' 766 | ``` 767 | 768 | 同样可以用Array.prototype.indexOf.call() 769 | 770 | ```js 771 | var o = {0:'abc', 1:'def', 2:'ghi', length:3}; 772 | console.log(Array.prototype.indexOf.call(o,'ghi',-4)); 773 | 774 | // 2 775 | ``` 776 | 777 | 如果不支持,可以用polyfill 778 | ```js 779 | if (!Array.prototype.indexOf) { 780 | Array.prototype.indexOf = function(searchElement, fromIndex) { 781 | var k; 782 | 783 | // 1. Let O be the result of calling ToObject passing 784 | // the this value as the argument. 785 | if (this == null) { 786 | throw new TypeError('"this" is null or not defined'); 787 | } 788 | 789 | var O = Object(this); 790 | 791 | // 2. Let lenValue be the result of calling the Get 792 | // internal method of O with the argument "length". 793 | // 3. Let len be ToUint32(lenValue). 794 | var len = O.length >>> 0; 795 | 796 | // 4. If len is 0, return -1. 797 | if (len === 0) { 798 | return -1; 799 | } 800 | 801 | // 5. If argument fromIndex was passed let n be 802 | // ToInteger(fromIndex); else let n be 0. 803 | var n = +fromIndex || 0; 804 | 805 | if (Math.abs(n) === Infinity) { 806 | n = 0; 807 | } 808 | 809 | // 6. If n >= len, return -1. 810 | if (n >= len) { 811 | return -1; 812 | } 813 | 814 | // 7. If n >= 0, then Let k be n. 815 | // 8. Else, n<0, Let k be len - abs(n). 816 | // If k is less than 0, then let k be 0. 817 | k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 818 | 819 | // 9. Repeat, while k < len 820 | while (k < len) { 821 | if (k in O && O[k] === searchElement) { 822 | return k; 823 | } 824 | k++; 825 | } 826 | return -1; 827 | }; 828 | } 829 | ``` 830 | 831 | 832 | 833 | ### lastIndexOf indexOf的逆向查找,即从数组的末尾处一直到数组的开头开始查找,查找元素最后一次出现时的索引位置,如果没有则返回-1. 834 | 835 | 语法:arr.lastIndexOf(element, fromIndex=length-1) 836 | 837 | element 需要查找的元素 838 | 839 | fromIndex 开始查找的位置,缺省默认为length-1,如果超过数组长度,犹豫是逆向查找,则查找整个数组,如果为负值,则从数组的第length+fromIndex项往数组开头开始查找,如果length|fromIndex<0数组则不会被查找。(与indexOf相反) 840 | 841 | 同indexOf一样,lastIndexOf也是严格匹配数组元素 842 | 843 | 844 | ```js 845 | var arr = [1,2,3,4,5,6,7,8] 846 | var arr2 = arr.lastIndexOf(6) 847 | console.log(arr2) 848 | // 5 849 | 850 | var arr = [1,2,3,6,6,5,7,8] 851 | var arr2 = arr.lastIndexOf(6) 852 | console.log(arr2) 853 | // 4 854 | 855 | var arr = [1,2,3,6,6,6,7,8] 856 | var arr2 = arr.lastIndexOf(6) 857 | console.log(arr2) 858 | // 5 859 | 860 | var o = {0:'abc', 1:'def', 2:'ghi', length:3}; 861 | console.log(Array.prototype.lastIndexOf.call(o,'ghi')); 862 | 863 | // 2 864 | ``` 865 | 866 | 如果不支持,可以用polyfill 867 | 868 | ```js 869 | if (!Array.prototype.lastIndexOf) { 870 | Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) { 871 | 'use strict'; 872 | 873 | if (this === void 0 || this === null) { 874 | throw new TypeError(); 875 | } 876 | 877 | var n, k, 878 | t = Object(this), 879 | len = t.length >>> 0; 880 | if (len === 0) { 881 | return -1; 882 | } 883 | 884 | n = len - 1; 885 | if (arguments.length > 1) { 886 | n = Number(arguments[1]); 887 | if (n != n) { 888 | n = 0; 889 | } 890 | else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) { 891 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); 892 | } 893 | } 894 | 895 | for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) { 896 | if (k in t && t[k] === searchElement) { 897 | return k; 898 | } 899 | } 900 | return -1; 901 | }; 902 | } 903 | ``` 904 | 905 | 906 | 907 | ### includes(ES7) 基于ECMAScript 2016(es7)规范,他用于查找数组中是否包含某个指定的值,如果是则返回true,否则返回false 908 | 909 | 语法:arr.includes(element, fromIndex=0) 910 | 911 | element 查找的元素 912 | 913 | fromIndex 数组索引开始的位置,缺省为0,他是正向查找,即从索引处往数组末尾开始查找。 914 | 915 | ```js 916 | var arr = [-0,1,2,3,4,5] 917 | var arr2 = arr.includes(4) 918 | var arr3 = arr.includes(-4) 919 | var arr4 = arr.includes(2,1) 920 | var arr5 = arr.includes(+0) 921 | console.log(arr2,arr3,arr4,arr5) 922 | 923 | // true,false,true,true 924 | ``` 925 | 926 | 以上includes 忽略了-0和+0的区别,因为JavaScript一直以来都是不区分 -0 和 +0 的。但是只有0才不区分而已。 927 | 928 | includes 与 indexOf 相似,但是又有一些区别。 929 | 一、index 返回的是数值型,而include返回的是布尔值,在某些时候include更好使用 930 | ```js 931 | var ary = [1]; 932 | 933 | if (ary.indexOf(1) !== -1) { 934 | 935 | console.log("数组存在1") 936 | 937 | } 938 | 939 | if (ary.includes(1)) { 940 | 941 | console.log("数组存在1") 942 | 943 | } 944 | ``` 945 | 946 | 二、如果数组中有NaN,你又正好需要判断数组中是否有NaN的存在,indexOf是无法判断的,而includes却是可以的 947 | ```js 948 | var ary1 = [NaN]; 949 | 950 | console.log(ary1.indexOf(NaN))//-1 951 | 952 | console.log(ary1.includes(NaN))//true 953 | ``` 954 | 955 | 三、当数组中有空的值时候,includeOf会认为空的值是undefined,而indexOf认为是稀疏数组,省略掉的值是不存在的 956 | ```js 957 | var ary1 = new Array(3); 958 | 959 | console.log(ary1.indexOf(undefined));//-1 960 | 961 | console.log(ary1.includes(undefined))//true 962 | ``` 963 | 964 | 965 | 同样适用Array.prototype.includes.call() 966 | ```js 967 | var o = {0:'a', 1:'b', 2:'c', length:3}; 968 | var bool = Array.prototype.includes.call(o, 'a'); 969 | console.log(bool); 970 | 971 | // true 972 | ``` 973 | 974 | polyfill 975 | 976 | ```js 977 | if (!Array.prototype.includes) { 978 | Object.defineProperty(Array.prototype, 'includes', { 979 | value: function(searchElement, fromIndex) { 980 | 981 | if (this == null) { 982 | throw new TypeError('"this" is null or not defined'); 983 | } 984 | 985 | // 1. Let O be ? ToObject(this value). 986 | var o = Object(this); 987 | 988 | // 2. Let len be ? ToLength(? Get(O, "length")). 989 | var len = o.length >>> 0; 990 | 991 | // 3. If len is 0, return false. 992 | if (len === 0) { 993 | return false; 994 | } 995 | 996 | // 4. Let n be ? ToInteger(fromIndex). 997 | // (If fromIndex is undefined, this step produces the value 0.) 998 | var n = fromIndex | 0; 999 | 1000 | // 5. If n ≥ 0, then 1001 | // a. Let k be n. 1002 | // 6. Else n < 0, 1003 | // a. Let k be len + n. 1004 | // b. If k < 0, let k be 0. 1005 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 1006 | 1007 | function sameValueZero(x, y) { 1008 | return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); 1009 | } 1010 | 1011 | // 7. Repeat, while k < len 1012 | while (k < len) { 1013 | // a. Let elementK be the result of ? Get(O, ! ToString(k)). 1014 | // b. If SameValueZero(searchElement, elementK) is true, return true. 1015 | if (sameValueZero(o[k], searchElement)) { 1016 | return true; 1017 | } 1018 | // c. Increase k by 1. 1019 | k++; 1020 | } 1021 | 1022 | // 8. Return false 1023 | return false; 1024 | } 1025 | }); 1026 | } 1027 | ``` 1028 | 1029 | 1030 | ### toSource(非标准) 返回数组的源代码 1031 | 1032 | ```js 1033 | var array = ['a', 'b', 'c']; 1034 | console.log(array.toSource()); // ["a", "b", "c"] 1035 | 1036 | // Array.prototype.toSource.call() 1037 | var o = {0:'a', 1:'b', 2:'c', length:3}; 1038 | console.log(Array.prototype.toSource.call(o)); 1039 | 1040 | // // ["a","b","c"] 1041 | ``` 1042 | 1043 | ### forEach every some filter map reduce reduceRight以及es6新增的entries find findIndex keys values 都是基于es6,不会改变自身的办法一共有12个。 1044 | 1045 | ### forEach 方法指定数组的每项元素都执行一次传入函数,返回值为undefined. 1046 | 语法:arr.forEach(fn, thisArg) 1047 | 1048 | fn 标识在数组每一项上执行的函数,接收三个参数。 1049 | - value 当前正在被处理的元素的值 1050 | - index 当前正在被处理元素的索引 1051 | - array 当前数组的本身 1052 | 1053 | thisArg 可选,用来当作fn函数内的this对象 1054 | 1055 | forEach 将为数组中的每一项执行一次fn函数,那些已删除,新增或者从未赋值的项将被跳过(但不包括值为undefined的项) 1056 | 1057 | 遍历过程中,fn会被传入上述三个参数 1058 | 1059 | 1060 | ```js 1061 | var arr =[1,2,3,4] 1062 | var obj = {name:'cc'} 1063 | arr.forEach(function(value,index,array){ 1064 | arr[index] = value * 2 1065 | console.log(this.name) 1066 | console.log(value) 1067 | },obj) 1068 | // cc 1069 | // 1 1070 | // cc 1071 | // 2 1072 | // cc 1073 | // 3 1074 | // cc 1075 | // 4 1076 | // obj.name执行了四次 1077 | ``` 1078 | 1079 | 同样也可以用Array.prototype.forEach.call() 1080 | ```js 1081 | var o = {0:1, 1:3, 2:5, length:3}; 1082 | Array.prototype.forEach.call(o,function(value, index, obj){ 1083 | console.log(value,index,obj); 1084 | obj[index] = value * value; 1085 | },o); 1086 | ``` 1087 | 1088 | 如果不支持可以用polyfill 1089 | 1090 | ```js 1091 | if (!Array.prototype.forEach) { 1092 | 1093 | Array.prototype.forEach = function(callback, thisArg) { 1094 | 1095 | var T, k; 1096 | 1097 | if (this == null) { 1098 | throw new TypeError(' this is null or not defined'); 1099 | } 1100 | 1101 | // 1. Let O be the result of calling toObject() passing the 1102 | // |this| value as the argument. 1103 | var O = Object(this); 1104 | 1105 | // 2. Let lenValue be the result of calling the Get() internal 1106 | // method of O with the argument "length". 1107 | // 3. Let len be toUint32(lenValue). 1108 | var len = O.length >>> 0; 1109 | 1110 | // 4. If isCallable(callback) is false, throw a TypeError exception. 1111 | // See: http://es5.github.com/#x9.11 1112 | if (typeof callback !== "function") { 1113 | throw new TypeError(callback + ' is not a function'); 1114 | } 1115 | 1116 | // 5. If thisArg was supplied, let T be thisArg; else let 1117 | // T be undefined. 1118 | if (arguments.length > 1) { 1119 | T = thisArg; 1120 | } 1121 | 1122 | // 6. Let k be 0 1123 | k = 0; 1124 | 1125 | // 7. Repeat, while k < len 1126 | while (k < len) { 1127 | 1128 | var kValue; 1129 | 1130 | // a. Let Pk be ToString(k). 1131 | // This is implicit for LHS operands of the in operator 1132 | // b. Let kPresent be the result of calling the HasProperty 1133 | // internal method of O with argument Pk. 1134 | // This step can be combined with c 1135 | // c. If kPresent is true, then 1136 | if (k in O) { 1137 | 1138 | // i. Let kValue be the result of calling the Get internal 1139 | // method of O with argument Pk. 1140 | kValue = O[k]; 1141 | 1142 | // ii. Call the Call internal method of callback with T as 1143 | // the this value and argument list containing kValue, k, and O. 1144 | callback.call(T, kValue, k, O); 1145 | } 1146 | // d. Increase k by 1. 1147 | k++; 1148 | } 1149 | // 8. return undefined 1150 | }; 1151 | } 1152 | ``` 1153 | 1154 | ### every 传入的函数测试所有的元素,只要其中有一个函数返回值为false,那么该办法的结果为false;如果全部返回true,那么该办法的结果才返回true,因为every存在如下规律: 1155 | 1156 | - 若需检测数组中所有存在元素是否大于10(即value > 10),那么需要在传入的函数中return 'true'返回值,同时整个方法结果为true才表示数组存在的元素满足条件 (即数组中的value 都大于10,那么就算满足条件,返回true) 1157 | - 反之 若需要检测数组中存在的元素是否有不满足的大于10的,返回 'false' 1158 | 1159 | 语法同forEach 相同, 1160 | 1161 | ```js 1162 | var arr = [11,12,13,14,15,16] 1163 | var arr2 = [1,11,12,13,14,15] 1164 | arr.every(function(value){ 1165 | console.log(value > 10) 1166 | }) 1167 | arr2.every(function(value){ 1168 | console.log(value > 10) 1169 | }) 1170 | 1171 | // true false 1172 | // arr2 因为数组第一个元素不满足 条件,所以返回false 1173 | ``` 1174 | 1175 | 同样也适用Array.prototype.every.call() 1176 | 1177 | ```js 1178 | var o = {0:10, 1:8, 2:25, length:3}; 1179 | var bool = Array.prototype.every.call(o,function(value, index, obj){ 1180 | return value >= 8; 1181 | },o); 1182 | console.log(bool); 1183 | ``` 1184 | 1185 | 如果不支持,可以用ployfill 1186 | 1187 | ```js 1188 | if (!Array.prototype.every) 1189 | { 1190 | Array.prototype.every = function(fun /*, thisArg */) 1191 | { 1192 | 'use strict'; 1193 | 1194 | if (this === void 0 || this === null) 1195 | throw new TypeError(); 1196 | 1197 | var t = Object(this); 1198 | var len = t.length >>> 0; 1199 | if (typeof fun !== 'function') 1200 | throw new TypeError(); 1201 | 1202 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 1203 | for (var i = 0; i < len; i++) 1204 | { 1205 | if (i in t && !fun.call(thisArg, t[i], i, t)) 1206 | return false; 1207 | } 1208 | 1209 | return true; 1210 | }; 1211 | } 1212 | ``` 1213 | 1214 | ### some 跟every相反,使用some检测数组元素时,只要有一个函数返回值为true,则该方法返回true,如果全部都返回false,则该方法才返回false, 1215 | 1216 | - 若需检测数组中存在的元素是否有一个元素或多个元素大于10(即value > 10),那么我们需要在传入的函数中 return 'true',则some返回true,反之所有函数的返回false,则some返回false 1217 | 1218 | ```js 1219 | var arr = [10,1,2,3,4] 1220 | var arr2 = [1,2,3,4,5] 1221 | var arr3 = arr.some(function(value){ 1222 | return value >= 10 1223 | }) 1224 | var arr4 = arr2.some(function(value){ 1225 | return value >= 10 1226 | }) 1227 | console.log(arr3,arr4) 1228 | 1229 | // true false 1230 | ``` 1231 | 1232 | Array.prototype.come.call()同every一样,参照every的写法。 1233 | 1234 | 如果不支持的话可以用polyfill: 1235 | 1236 | ```js 1237 | if (!Array.prototype.some) 1238 | { 1239 | Array.prototype.some = function(fun /*, thisArg */) 1240 | { 1241 | 'use strict'; 1242 | 1243 | if (this === void 0 || this === null) 1244 | throw new TypeError(); 1245 | 1246 | var t = Object(this); 1247 | var len = t.length >>> 0; 1248 | if (typeof fun !== 'function') 1249 | throw new TypeError(); 1250 | 1251 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 1252 | for (var i = 0; i < len; i++) 1253 | { 1254 | if (i in t && fun.call(thisArg, t[i], i, t)) 1255 | return true; 1256 | } 1257 | 1258 | return false; 1259 | }; 1260 | } 1261 | ``` 1262 | 1263 | ### filter 使用传入的函数测试所有元素,并返回所有通过测试的元素组成新的数组,他就好比一个过滤器,筛选掉不符合条件的元素。 1264 | 1265 | 语法:arr.filter(fn, thisArg) 1266 | 1267 | ```js 1268 | var arr = [10,3,20,12,13,40] 1269 | var arr2 = arr.filter(function(value){ 1270 | return value >= 10 && value <= 20 1271 | }) 1272 | console.log(arr2) 1273 | // [10, 20, 12, 13] 1274 | // 满足大于等于10且小于等于20,并输出 1275 | ``` 1276 | Array.prototype.filter.call()写法同every一样,polyfill: 1277 | 1278 | ```js 1279 | if (!Array.prototype.filter) 1280 | { 1281 | Array.prototype.filter = function(fun /* , thisArg*/) 1282 | { 1283 | "use strict"; 1284 | 1285 | if (this === void 0 || this === null) 1286 | throw new TypeError(); 1287 | 1288 | var t = Object(this); 1289 | var len = t.length >>> 0; 1290 | if (typeof fun !== "function") 1291 | throw new TypeError(); 1292 | 1293 | var res = []; 1294 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 1295 | for (var i = 0; i < len; i++) 1296 | { 1297 | if (i in t) 1298 | { 1299 | var val = t[i]; 1300 | 1301 | // NOTE: Technically this should Object.defineProperty at 1302 | // the next index, as push can be affected by 1303 | // properties on Object.prototype and Array.prototype. 1304 | // But that method's new, and collisions should be 1305 | // rare, so use the more-compatible alternative. 1306 | if (fun.call(thisArg, val, i, t)) 1307 | res.push(val); 1308 | } 1309 | } 1310 | 1311 | return res; 1312 | }; 1313 | } 1314 | ``` 1315 | 1316 | 1317 | ### map 遍历数组元素,使用传入函数处理每一个元素,并返回函数的返回值组成新的数组。 1318 | 1319 | 语法: 1320 | let new_array = arr.map(function callback(currentValue, index, array) { 1321 | // Return element for new_array 1322 | }[, thisArg]) 1323 | 1324 | - map方法会给数组中的每个元素都按顺序调用一次callback函数,callback每次执行后返回的返回值(包括undefined)组合成新的数组,callback函数只会在有有值的索引上被调用,那些从来没被赋过值或者使用delete删除的索引则不会使用。 1325 | - callback 函数会被自动传入三个参数,数组元素,数组索引,原数组本身 1326 | - thisArg 参数有值,则每次callback函数被调用的时候,this都会指向这个thisArg参数上的这个对象,如果省略了thisArg参数,或者赋值为null或undefined,则this指向全局对象。 1327 | - map在执行的的时候不会修改原数组本身 (也可以在callback执行时改变原数组元素) 1328 | - 使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。 1329 | 1330 | ```js 1331 | var obj = [{key:1,value:10},{key:2,value:20},{key:3,value:30}] 1332 | var obj2 = obj.map(function(value){ 1333 | var robj = {}; 1334 | robj[value.key] = value.value; 1335 | return robj 1336 | }) 1337 | console.log(obj2) 1338 | 1339 | // 0:{1: 10} 1340 | // 1:{2: 20} 1341 | // 2:{3: 30} 1342 | 1343 | 1344 | // console.log(obj) 1345 | // 0:{key: 1, value: 10} 1346 | // 1:{key: 2, value: 20} 1347 | // 2:{key: 3, value: 30} 1348 | // 原远足未被修改 1349 | ``` 1350 | 1351 | Array.prototype.map.cal()使用: 1352 | ```js 1353 | var a = Array.prototype.map.call('hello word',function(value){ 1354 | console.log(value) 1355 | }) 1356 | 1357 | 1358 | h 1359 | e 1360 | l 1361 | l 1362 | o 1363 | 1364 | w 1365 | o 1366 | r 1367 | d 1368 | ``` 1369 | 1370 | 遍历querySelectorAll()所有集合 1371 | ```js 1372 | var el = document.querySelectorAll('div') 1373 | Array.prototype.map.call(el,function(value){ 1374 | console.log(value) 1375 | }) 1376 | ``` 1377 | 1378 | 返回字符串 1379 | ```js 1380 | var str = '12345'; 1381 | Array.prototype.map.call(str, function(x) { 1382 | return x; 1383 | }).reverse().join(''); 1384 | 1385 | // 输出: '54321' 1386 | ``` 1387 | 1388 | MDN上有这么描述map的话: 1389 | 1390 | 通常情况下,map 方法中的 callback 函数只需要接受一个参数,就是正在被遍历的数组元素本身。但这并不意味着 map 只给 callback 传了一个参数。这个思维惯性可能会让我们犯一个很容易犯的错误。 1391 | 1392 | ```js 1393 | // 下面的语句返回什么呢: 1394 | ["1", "2", "3"].map(parseInt); 1395 | // 你可能觉的会是[1, 2, 3] 1396 | // 但实际的结果是 [1, NaN, NaN] 1397 | 1398 | // 通常使用parseInt时,只需要传递一个参数. 1399 | // 但实际上,parseInt可以有两个参数.第二个参数是进制数. 1400 | // 可以通过语句"alert(parseInt.length)===2"来验证. 1401 | // map方法在调用callback函数时,会给它传递三个参数:当前正在遍历的元素, 1402 | // 元素索引, 原数组本身. 1403 | // 第三个参数parseInt会忽视, 但第二个参数不会,也就是说, 1404 | // parseInt把传过来的索引值当成进制数来使用.从而返回了NaN. 1405 | 1406 | function returnInt(element) { 1407 | return parseInt(element, 10); 1408 | } 1409 | 1410 | ['1', '2', '3'].map(returnInt); // [1, 2, 3] 1411 | // 意料之中的结果 1412 | 1413 | // 也可以使用简单的箭头函数,结果同上 1414 | ['1', '2', '3'].map( str => parseInt(str) ); // [1, 2, 3] 1415 | 1416 | // 一个更简单的方式: 1417 | ['1', '2', '3'].map(Number); // [1, 2, 3] 1418 | // 与`parseInt` 不同,下面的结果会返回浮点数或指数: 1419 | ['1.1', '2.2e2', '3e300'].map(Number); // [1.1, 220, 3e+300] 1420 | ``` 1421 | 1422 | 1423 | ### reduce 接收一个方法作为累加器,数组中的每个值(从左至右)开始合并,最终一个值。 1424 | 1425 | 语法:arr.reduce(callback, initialValue) 1426 | 1427 | callback 每次执行数组中每个值的函数,包含四个参数: 1428 | 1429 | - previousValue 上一次调用回调时累计的返回的值,或者是提供的初始值 1430 | - value 数组中当前被处理元素的值 1431 | - index 当前元素在数组中的索引 1432 | - array 原数组自身 1433 | 1434 | initialValue 指定第一次调用fn的第一个参数,如果没有提供初始值,则使用数组的第一个元素的值,。 1435 | 1436 | 当callback第一次执行时: 1437 | - 如果initialValue 在调用reduce时被提供,那么第一个previousValue 将等于initialValue,此时item等于数组中的第一个值 1438 | - 如果 initialValue 未被提供,那么 previousVaule 等于数组中的第一个值,item 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。 1439 | - 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么fn不会被执行,数组的唯一值将被返回。 1440 | 1441 | 计算1到900的和 1442 | ```js 1443 | var arr = [] 1444 | for(var i=0;i<=1000;i++){ 1445 | arr.push(i) 1446 | if(i == 900){ 1447 | var arr2 = arr.reduce(function(sum,value){ 1448 | return sum+value 1449 | }) 1450 | } 1451 | } 1452 | console.log(arr2) 1453 | // 405450 1454 | ``` 1455 | 1456 | 把数组的[1,2,3,4,5]转换成整数的12345 1457 | ```js 1458 | var num = [1,2,3,4,5].reduce(function(x,y){ 1459 | return x.toString() + y 1460 | }) 1461 | console.log(num) 1462 | // 12345 1463 | ``` 1464 | 1465 | 数组去除重复 1466 | ```js 1467 | var arr = [1,2,3,2,1,33,3,2,1] 1468 | var arr2 = arr.sort().reduce(function(init,current){ 1469 | if(init.length === 0 || init[init.length-1]!== current){ 1470 | init.push(current); 1471 | } 1472 | return init; 1473 | },[]) 1474 | console.log(arr2) 1475 | // [1, 2, 3, 33] 1476 | ``` 1477 | 1478 | ```js 1479 | var array = [1, 2, 3, 4]; 1480 | var s = array.reduce(function(previousValue, value, index, array){ 1481 | return previousValue * value; 1482 | },1); 1483 | console.log(s); // 24 1484 | // ES6写法更加简洁 1485 | array.reduce((p, v) => p * v); // 24 1486 | ``` 1487 | 1488 | 以上回调被执行了4次,每次的参数和返回: 1489 | |callback |previousValue |currentValue |index |array |return |value| 1490 | |---|---|---|---|---|---|---| 1491 | |第1次 |1 |1 |1 |[1,2,3,4] 1| 1492 | |第2次 |1 |2 |2 |[1,2,3,4] 2| 1493 | |第3次 |2 |3 |3 |[1,2,3,4] 6| 1494 | |第4次 |6 |4 |4 |[1,2,3,4] 24| 1495 | 1496 | Array.prototype.reduce.call()写法参照every,如果不支持reduce,可以用polyfill: 1497 | 1498 | ```js 1499 | if (!Array.prototype.reduce) { 1500 | Object.defineProperty(Array.prototype, 'reduce', { 1501 | value: function(callback /*, initialValue*/) { 1502 | if (this === null) { 1503 | throw new TypeError( 'Array.prototype.reduce ' + 1504 | 'called on null or undefined' ); 1505 | } 1506 | if (typeof callback !== 'function') { 1507 | throw new TypeError( callback + 1508 | ' is not a function'); 1509 | } 1510 | 1511 | // 1. Let O be ? ToObject(this value). 1512 | var o = Object(this); 1513 | 1514 | // 2. Let len be ? ToLength(? Get(O, "length")). 1515 | var len = o.length >>> 0; 1516 | 1517 | // Steps 3, 4, 5, 6, 7 1518 | var k = 0; 1519 | var value; 1520 | 1521 | if (arguments.length >= 2) { 1522 | value = arguments[1]; 1523 | } else { 1524 | while (k < len && !(k in o)) { 1525 | k++; 1526 | } 1527 | 1528 | // 3. If len is 0 and initialValue is not present, 1529 | // throw a TypeError exception. 1530 | if (k >= len) { 1531 | throw new TypeError( 'Reduce of empty array ' + 1532 | 'with no initial value' ); 1533 | } 1534 | value = o[k++]; 1535 | } 1536 | 1537 | // 8. Repeat, while k < len 1538 | while (k < len) { 1539 | // a. Let Pk be ! ToString(k). 1540 | // b. Let kPresent be ? HasProperty(O, Pk). 1541 | // c. If kPresent is true, then 1542 | // i. Let kValue be ? Get(O, Pk). 1543 | // ii. Let accumulator be ? Call( 1544 | // callbackfn, undefined, 1545 | // « accumulator, kValue, k, O »). 1546 | if (k in o) { 1547 | value = callback(value, o[k], k, o); 1548 | } 1549 | 1550 | // d. Increase k by 1. 1551 | k++; 1552 | } 1553 | 1554 | // 9. Return accumulator. 1555 | return value; 1556 | } 1557 | }); 1558 | } 1559 | ``` 1560 | 1561 | 1562 | ### reduceRight 方法与reduce写法一样,与reduce不同的是,reduceRight是从右至左开始合并,最终返回一个值,与reduce的执行方向相反,其它完全一致,写法参考reduce 1563 | 1564 | polyfill: 1565 | ```js 1566 | if ('function' !== typeof Array.prototype.reduceRight) { 1567 | Array.prototype.reduceRight = function(callback /*, initialValue*/) { 1568 | 'use strict'; 1569 | if (null === this || 'undefined' === typeof this) { 1570 | throw new TypeError('Array.prototype.reduce called on null or undefined'); 1571 | } 1572 | if ('function' !== typeof callback) { 1573 | throw new TypeError(callback + ' is not a function'); 1574 | } 1575 | var t = Object(this), len = t.length >>> 0, k = len - 1, value; 1576 | if (arguments.length >= 2) { 1577 | value = arguments[1]; 1578 | } else { 1579 | while (k >= 0 && !(k in t)) { 1580 | k--; 1581 | } 1582 | if (k < 0) { 1583 | throw new TypeError('Reduce of empty array with no initial value'); 1584 | } 1585 | value = t[k--]; 1586 | } 1587 | for (; k >= 0; k--) { 1588 | if (k in t) { 1589 | value = callback(value, t[k], k, t); 1590 | } 1591 | } 1592 | return value; 1593 | }; 1594 | } 1595 | ``` 1596 | 1597 | 1598 | ### entries 基于ECMAScript2015(es6)规范,返回一个数组迭代器对象,该对象包括数组中每个索引的键值对,手动循环对象的属性值可以输出相应的属性位置的值。 1599 | 1600 | ```js 1601 | var arr = [1,2,3,4] 1602 | var arr2 = arr.entries() 1603 | console.log(arr2.next().value) 1604 | console.log(arr2.next().value) 1605 | console.log(arr2.next().value) 1606 | console.log(arr2.next().value) 1607 | console.log(arr2.next().value) 1608 | 1609 | // [0, 1] 1610 | // [1, 2] 1611 | // [2, 3] 1612 | // [3, 4] 1613 | // undefined 数组结尾在迭代就是undefined 1614 | ``` 1615 | 1616 | 也可以使用:Array.prototype.entries.call() 1617 | 1618 | ```js 1619 | var o = {0:"a", 1:"b", 2:"c", length:3}; 1620 | var iterator = Array.prototype.entries.call(o); 1621 | console.log(iterator.next().value); // [0, "a"] 1622 | console.log(iterator.next().value); // [1, "b"] 1623 | console.log(iterator.next().value); // [2, "c"] 1624 | ``` 1625 | 1626 | 1627 | ### find 基于ECMAScript2015(es6)规范,返回数组中第一个满足条件的元素的值,如果数组为空或是稀疏数组 则返回undefined,find不会改变原来的数组。 1628 | 1629 | 语法:arr.find(callback[, thisArg]) 1630 | 1631 | - callback 执行数组的每一个值的函数,接收3个参数 1632 | - element 当前遍历到的元素 1633 | - index 当前遍历到的索引 1634 | - array 数组本身 1635 | - thisArg 可指定callback的this参数 1636 | 1637 | 查找满足条件的数组对象 1638 | ```js 1639 | var inventory = [ 1640 | {value: 'js', name: 0}, 1641 | {value: 'css', name: 1}, 1642 | {value: 'jq', name: 2} 1643 | ]; 1644 | 1645 | function findCherries(fruit) { 1646 | return fruit.value === 'css'; 1647 | } 1648 | 1649 | function findIndexCher(isValue){ 1650 | return isValue === 'vue' 1651 | } 1652 | 1653 | console.log(inventory.find(findCherries)); // {value: "css", name: 1} 1654 | console.log(inventory.find(findIndexCher)); // undefined 1655 | ``` 1656 | 1657 | Array.prototype.find.call()写法与上面类似,不在阐述, 1658 | 1659 | polyfill: 1660 | ```js 1661 | if (!Array.prototype.find) { 1662 | Object.defineProperty(Array.prototype, 'find', { 1663 | value: function(predicate) { 1664 | // 1. Let O be ? ToObject(this value). 1665 | if (this == null) { 1666 | throw new TypeError('"this" is null or not defined'); 1667 | } 1668 | 1669 | var o = Object(this); 1670 | 1671 | // 2. Let len be ? ToLength(? Get(O, "length")). 1672 | var len = o.length >>> 0; 1673 | 1674 | // 3. If IsCallable(predicate) is false, throw a TypeError exception. 1675 | if (typeof predicate !== 'function') { 1676 | throw new TypeError('predicate must be a function'); 1677 | } 1678 | 1679 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. 1680 | var thisArg = arguments[1]; 1681 | 1682 | // 5. Let k be 0. 1683 | var k = 0; 1684 | 1685 | // 6. Repeat, while k < len 1686 | while (k < len) { 1687 | // a. Let Pk be ! ToString(k). 1688 | // b. Let kValue be ? Get(O, Pk). 1689 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). 1690 | // d. If testResult is true, return kValue. 1691 | var kValue = o[k]; 1692 | if (predicate.call(thisArg, kValue, k, o)) { 1693 | return kValue; 1694 | } 1695 | // e. Increase k by 1. 1696 | k++; 1697 | } 1698 | 1699 | // 7. Return undefined. 1700 | return undefined; 1701 | } 1702 | }); 1703 | } 1704 | ``` 1705 | 1706 | 1707 | ### findIndex 基于ECMASscript2015(es6)规范,他返回数组中第一个满足条件的元素的索引,如果没有则返回-1,写法与find相同。 1708 | 1709 | ```js 1710 | var inventory = [ 1711 | {value: 'js', name: 0}, 1712 | {value: 'css', name: 1}, 1713 | {value: 'jq', name: 2} 1714 | ]; 1715 | 1716 | function findCherries(fruit) { 1717 | return fruit.value === 'css'; 1718 | } 1719 | 1720 | function findIndexCher(isValue){ 1721 | return isValue === 'vue' 1722 | } 1723 | 1724 | console.log(inventory.find(findCherries)); // 1 1725 | console.log(inventory.find(findIndexCher)); // -1 1726 | ``` 1727 | 1728 | Array.prototype.findIndex.call()写法与上面类似,不在阐述, 1729 | 1730 | 1731 | polyfil: 1732 | ```js 1733 | if (!Array.prototype.findIndex) { 1734 | Object.defineProperty(Array.prototype, 'findIndex', { 1735 | value: function(predicate) { 1736 | // 1. Let O be ? ToObject(this value). 1737 | if (this == null) { 1738 | throw new TypeError('"this" is null or not defined'); 1739 | } 1740 | 1741 | var o = Object(this); 1742 | 1743 | // 2. Let len be ? ToLength(? Get(O, "length")). 1744 | var len = o.length >>> 0; 1745 | 1746 | // 3. If IsCallable(predicate) is false, throw a TypeError exception. 1747 | if (typeof predicate !== 'function') { 1748 | throw new TypeError('predicate must be a function'); 1749 | } 1750 | 1751 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. 1752 | var thisArg = arguments[1]; 1753 | 1754 | // 5. Let k be 0. 1755 | var k = 0; 1756 | 1757 | // 6. Repeat, while k < len 1758 | while (k < len) { 1759 | // a. Let Pk be ! ToString(k). 1760 | // b. Let kValue be ? Get(O, Pk). 1761 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). 1762 | // d. If testResult is true, return k. 1763 | var kValue = o[k]; 1764 | if (predicate.call(thisArg, kValue, k, o)) { 1765 | return k; 1766 | } 1767 | // e. Increase k by 1. 1768 | k++; 1769 | } 1770 | 1771 | // 7. Return -1. 1772 | return -1; 1773 | } 1774 | }); 1775 | } 1776 | ``` 1777 | 1778 | 1779 | ### keys 基于ECMAScript2015(es6)规范,返回一个数组索引的迭代器 1780 | 1781 | ```js 1782 | var arr = ['js','css','jq'] 1783 | var iterator = arr.keys() 1784 | console.log(iterator.next()) // {value: 0, done: false} 1785 | console.log(iterator.next()) // {value: 1, done: false} 1786 | console.log(iterator.next()) // {value: 2, done: false} 1787 | console.log(iterator.next()) // {value: undefined, done: true} 1788 | ``` 1789 | 1790 | 在稀疏数组中使用keys也会 包含那些没有值的对应索引,如下: 1791 | 1792 | ```js 1793 | var array = ["abc", , "xyz"]; 1794 | var sparseKeys = Object.keys(array); 1795 | var denseKeys = [...array.keys()]; 1796 | console.log(sparseKeys); // ["0", "2"] 1797 | console.log(denseKeys); // [0, 1, 2] 1798 | ``` 1799 | 1800 | 用keys快速生成0到10的新数组 1801 | ```js 1802 | [...Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1803 | [...new Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1804 | ``` 1805 | 1806 | Array.prototype.keys.call()写法与上面类似,不在阐述, 1807 | 1808 | 1809 | ### values 基于ECMAScript2015(es6)规范,返回一个数组迭代器对象,该对象包含数组中每个索引的值。用法与entries相同。 1810 | 1811 | ```js 1812 | var array = ["abc", "xyz"]; 1813 | var iterator = array.values(); 1814 | console.log(iterator.next().value);//abc 1815 | console.log(iterator.next().value);//xyz 1816 | ``` 1817 | 1818 | 1819 | ### Symbol.iterator 该方法基于ECMAScript 2015(ES6)规范,同 values 方法功能相同。 1820 | 1821 | ```js 1822 | var array = ["abc", "xyz"]; 1823 | var iterator = array[Symbol.iterator](); 1824 | console.log(iterator.next().value); // abc 1825 | console.log(iterator.next().value); // xyz 1826 | ``` 1827 | 1828 | 1829 | 参考资料1:http://louiszhai.github.io/2017/04/28/array 1830 | 参考资料2:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array -------------------------------------------------------------------------------- /js/模块化.md: -------------------------------------------------------------------------------- 1 | ### AMD,CMD区别 2 | AMD 是RequireJS在推广过程中对模块定义的规范化产出 3 | CMD 是SeaJS 在推广过程中对模块定义的规范化产出 4 | 5 | AMD的意思是 异步模块定义,他的是`异步模块定义`,他采用异步方式加载模块,模块的加载不影响他后面语句的运行,所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 6 | AMD也采用require()语句加载模块,但是不同于CommonJS,目前有两js库实现了AMD规范,require.js和curl.js 7 | 8 | 区别: 9 | 1. 对于依赖的模块,AMD是提前执行,CMD是延迟执行 10 | 2. CMD推崇依赖就进,AMD推崇依赖前置 11 | ```js 12 | // CMD 13 | define(function(require, exports, module) { 14 | var a = require('./a') 15 | a.doSomething() 16 | var b = require('./b') 17 | b.doSomething() 18 | }) 19 | 20 | // AMD 21 | define(['./a', './b'], function(a, b) { 22 | a.doSomething() 23 | b.doSomething() 24 | }) 25 | ``` 26 | 27 | 28 | ### CommonJS 中的 require/exports 和 ES6 中的 import/export 区别? 29 | CommonJS模块的重要特性是加载时执行,即脚本代码在 require的时候,就会全部执行。 30 | 一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。 31 | 32 | ES6 模块是动态引用,如果使用 import 从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 33 | 34 | CommonJS 规范规定,每个模块内部,module 变量代表当前模块,这个变量是一个对象,它的 exports属性(即module.exports)是对外的接口。 35 | 加载某个模块,其实是加载该模块的module.exports属性,export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。 36 | import/export 最终都是编译为require/exports 来执行的。 37 | 38 | 39 | ### require与import的区别 40 | 41 | **require**支持 动态导入,import不支持,正在提案 (babel 下可支持) 42 | **require**是 同步导入,import属于 异步 导入 43 | **require**是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化 44 | -------------------------------------------------------------------------------- /node/QQ截图20180828211949.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/node/QQ截图20180828211949.png -------------------------------------------------------------------------------- /node/QQ截图20180828212348.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/node/QQ截图20180828212348.png -------------------------------------------------------------------------------- /node/node+IIS配置https.md: -------------------------------------------------------------------------------- 1 | #### 从腾讯云中免费下载ssl证书,一年的有效期 2 | 3 | ![](./QQ截图20180828211949.png) 4 | 5 | 解压后就会有以下文件,腾讯云对其有四种方案的说明,我用的是iis方案,过程顺利,但是却没有效果,可能是因为还缺少其它步骤 6 | ![](./QQ截图20180828212348.png) 7 | 8 | 以下是关键代码 9 | ```js 10 | var fs = require('fs'); 11 | var https = require('https'); 12 | var options = { 13 | pfx: fs.readFileSync('./证书.pfx'), 14 | passphrase: 'iis文件夹keystorePass.text内容' 15 | } 16 | var httpServer = https.createServer(options,app) 17 | httpServer.listen(443, function(){ 18 | console.log('https') 19 | }) 20 | ``` 21 | 22 | 因为http监听的是80端口,但是https监听的是443端口,所以要写两个 23 | 完整代码: 24 | ```js 25 | // 加载express模块 26 | var express = require('express'); 27 | var swig = require('swig'); 28 | var app = express(); 29 | var bp = require('body-parser'); 30 | var cookies = require('cookies'); 31 | var path = require('path') 32 | // 后台上传的模块 33 | var multipart = require('connect-multiparty'); 34 | var multipartMiddleware = multipart(); 35 | app.use(multipart({uploadDir:'./proImg' })); 36 | 37 | app.use('/',require('./routers/getUser')) 38 | app.get('/',function(req,res){ 39 | res.send('正常访问,ojbk') 40 | }) 41 | 42 | //图片访问 43 | app.get('/proImg/*', function (req, res) { 44 | res.sendFile( __dirname + req.url); 45 | }) 46 | 47 | // ssl证明 48 | app.get('/.well-known/pki-validation/fileauth.txt', function (req, res) { 49 | console.log(1) 50 | res.sendFile(__dirname+'/.well-known/pki-validation/fileauth.txt'); 51 | }) 52 | // 开启端口 53 | server = app.listen(80) 54 | 55 | // 开启https 关键 56 | var fs = require('fs'); 57 | var https = require('https'); 58 | var options = { 59 | pfx: fs.readFileSync('./www.zongdusir.top.pfx'), 60 | passphrase: '1f90815m04097m' 61 | } 62 | var httpServer = https.createServer(options,app) 63 | httpServer.listen(443, function(){ 64 | console.log('https') 65 | }) 66 | ``` -------------------------------------------------------------------------------- /react/react-router.md: -------------------------------------------------------------------------------- 1 | #### 不管是HashRouter,还是BrowserRouter ,他们的子元素必须是只有一个,可以用Switch将route包裹起来 2 | 3 | #### react 用js跳转路由时可以用`this.props.history.push('/')`,如果报错push - undefined 的话,往往是props是个空对象,所以需要在`route`中也添加路由 4 | ```js 5 | // app.js 6 | 7 |
8 | 9 | 10 | // 把底部导航按钮以组件形式引入,这样组件内部就可以用 this.props.history.push('/'),但是要注意this的指向 11 | 12 |
13 |
14 | ``` -------------------------------------------------------------------------------- /react/react取消eslint.md: -------------------------------------------------------------------------------- 1 | 在根目录下找到package.json文件修改以下配置: 2 | ```js 3 | "eslintConfig": { 4 | "extends": "react-app", 5 | "rules": { 6 | "no-undef": "off", 7 | "no-restricted-globals": "off", 8 | "no-unused-vars": "off" 9 | } 10 | } 11 | ``` -------------------------------------------------------------------------------- /react/react生命周期.md: -------------------------------------------------------------------------------- 1 | ```js 2 | import React from 'react' 3 | 4 | class App extends React.Component{ 5 | consttructor(props, cont){ 6 | // 只要组件有consttructor就要写super(),不然this会指向错误 7 | // 这里最先执行,可以获取从其他组件传来的数据,可以初始化数据 8 | super(props, cont) 9 | this.state = {...} 10 | } 11 | componentWillMount(){ // 组件准备挂载 12 | // consttructor 刚刚执行完后 才执行 componentWillMount 13 | // 这时组件和dom还没渲染,render还没执行 14 | // 主要是在服务端渲染 15 | // ajax请求数据,最好不要写在这里,因为如果数据返回空,就会造成页面空白,且不利于服务端渲染 16 | } 17 | componentDidMount(){ // 组件渲染完成 18 | // 组件首次渲染完成,此时dom节点已经生成 19 | // ajax请求数据可以写在这里,使用setState后悔重新渲染 20 | } 21 | componentWillReceiveProps(nextProps){ // props改变后执行 22 | // 仅在props有变化的时候才执行 23 | // 接收一个参数,通过对比 新的nextProps和旧的this.props,,用setState重新赋值新的props,从而重新渲染组件 24 | } 25 | shouldComponentUpdate(){ 26 | 27 | // return false会阻止组件的更新 28 | } 29 | } 30 | ``` -------------------------------------------------------------------------------- /sass/sass不支持中文.md: -------------------------------------------------------------------------------- 1 | 打开ruby安装的目录,一般安装在c盘 2 | 3 | 例如: `C:\Ruby23\lib\ruby\gems\2.3.0\gems\sass-3.5.6\lib\sass\engine.rb` 4 | 5 | 找到`engine.rb`文件 6 | 7 | 在module Sass上面添加一句 8 | Encoding.default_external = Encoding.find('utf-8') -------------------------------------------------------------------------------- /vue/virtual-dom/README.md: -------------------------------------------------------------------------------- 1 | # 阅读顺序: [vdom](./vdom.md) -- [parse](./parse.md) -- [patch](./patch.md) -- [html-parse](./html-parse.md) 2 | 3 | ### Virtual Dom 是什么 4 | 是利用js双缓存dom节点经过diff算法比较,只更新需要更新的dom,实现高效的更新页面,Vue和Ember早就开始用虚拟DOM提高页面更新的速度,随后在Vue.js 2.0中也添加了这一技术 5 | 6 | ### 为什么要用Virtual Dom 7 | 8 | 1. 如果页面有成千上万的dom节点时,每更新一次就去创建这么多的节点,不断的对页面造成重绘、重排...,产生的性能和体验都非常差 9 | 10 | 2. Virtual Dom不仅仅提高页面更新速度,还可以扩展添加其他技术,比如Vue里的v-for,v-if,v-else,v-model,自定义指令,绑定事件... 11 | 12 | 13 | 3. 改变了我们写html的方法,摆脱了获取dom节点对象,对其赋值,取值,时间绑定等操作, 14 | 实现了MVVM模式开发,既数据,操作,条件渲染写在html页面上 15 | 16 | 17 | ### Virtual DOM 是怎么实现? 18 | 每次在数据改变之后,就会在UI更新之前创建一个新的Virtual DOM。更新浏览器的DOM分三个步骤: 19 | 1. 首次渲染之后只要数据发生改变,就会重新生成一个完整的Virtual DOM。 20 | 2. 重新diff对比计算比较出新的和之前的Virtual DOM的差异。 21 | 3. 更新真实DOM中真正发生改变的部分,就像是给DOM打了个补丁。 22 | 23 | ### Vue中的Virtual Dom 原理 24 | 25 | ![](https://cythilya.github.io/assets/2017-04-08-vue-rendering-flow.png) 26 | 27 | 说明上图过程: 28 | * template 经由 parser 解析得到 vnode object,对这个object 进行模版语法解析,转为AST node,最后生成一棵完整的AST tree (抽象语法树,abstract syntax tree)。 29 | * 使用AST tree 生成渲染函数(render function),执行渲染函数会得到v-node。 30 | * watcher 搜集依赖、经由observer 对v-node 深度数据绑定和更新。 31 | * v-node 经由patch 后render 为真正的HTML。 32 | 33 | 34 | 数据更新时,渲染得到新的virtual dom,与上次的virtual dom进行diff比较,记录所有有差异的dom节点,然后在patch中更新ui 35 | ![](2017-04-11-vue-rendering-flow.png) 36 | 37 | * 若是已经parse 过的template,则会做更新,例如:比对、重新绑定数据、更新必要的DOM element。 38 | 39 | ### diff 40 | * 当组件状态发生更新或交互、数据变更等因素导致需要试图更新的时候,然后触发Virtual Dom数据的变化,然后通过对缓存中的Virtual Dom数据和真实DOM进行diff算法比对,再对真实DOM更新。可以简单认为Virtual Dom是真实DOM的缓存。 41 | 42 | ![](https://camo.githubusercontent.com/db55af854af44f10b16053687c6c02d3d5ae4b98/68747470733a2f2f692e6c6f6c692e6e65742f323031372f30382f32372f353961323431396133633631372e706e67) 43 | 44 | 45 | 基于 Virtual DOM 的数据更新与UI同步机制: 46 | ![](https://user-gold-cdn.xitu.io/2018/5/24/163904e89b21b515?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 47 | 48 | 初始渲染时,首先将渲染为Virtual dom,然后在转化为dom 49 | ![](https://user-gold-cdn.xitu.io/2017/5/16/39eac671c7fae8f73917ba1e6d06daa8?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 50 | 51 | 52 | * vue的vdom借鉴了开源库[snabbdom](https://github.com/snabbdom/snabbdom)思想,结合vue的特点 53 | 54 | 55 | 56 | - https://zhuanlan.zhihu.com/p/24311601 57 | - http://hcysun.me/vue-design/art/ 58 | - https://johnresig.com/files/htmlparser.js 59 | - https://www.jianshu.com/p/9e9477847ba1 60 | - https://github.com/vuejs/vue/blob/dev/src/core/instance/lifecycle.js#L139-L202 61 | -------------------------------------------------------------------------------- /vue/virtual-dom/diff-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-1.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/diff-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-2.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/diff-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-3.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/diff-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-4.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/diff-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-5.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/diff-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZongDuCha/lyqNotes/4b40a9c4eeaacfa5797b8ec27ebbd18ae2e3edb0/vue/virtual-dom/diff-6.jpg -------------------------------------------------------------------------------- /vue/virtual-dom/html-parse.md: -------------------------------------------------------------------------------- 1 | ### html-parse (解析html模版) 2 | - src/compiler/parser/html-parser 3 | 4 | - 循环解析 `template`,用正则匹配相应元素(开始和结束标签,注释节点,文档类型...),不同的情况不同处理 5 | 6 | - 循环中利用`advance函数`不断的前进字符,直到结束 7 | 8 | - 对于注释节点和文档类型的匹配,只advance前进字符串到结尾 9 | 10 | - 匹配和解析开始标签,先通过正则`startTagOpen `匹配开始 标签名,调用`handleStartTag `解析,正则`match`匹配节点属性(class,id,自定义属性...),将属性添加到match.attrs,判断如果不是一元标签类似``,`
`,`
`,就遍历处理attrs属性, 如果是一元标签,就保存开始标签名压入stack,利用`advance函数`前进至开始标签的闭合符, 11 | 12 | - 匹配和解析闭合标签,先通过正则`endTag`匹配闭合标签,前进至闭合标签末尾,调用`parseEndTag`倒序遍历解析,判断闭合标签名跟handleStartTag保存的开始标签名匹配比较,如果不同,就报错,如果匹配就从stack里弹出开始标签名,并从stack尾部获取`lastTag`,最后调用`option.end`。 13 | 14 | - 接下来就是截取html的text文本,如果textEnd大于或等于0,就说明从当前位置到textEnd的位置都是文本,并且如果有 < 的字符,就继续查找真正文本结束的位置,调用`advance`前进字符, 如果textEnd小于0的情况下,说明整个`tepmlate`解析完了或者没有 文本了,就把剩余的html传给text,清空html,最后调用chars 回调函数 15 | 16 | 17 | ```js 18 | /** 19 | * Not type-checking this file because it's mostly vendor code. 20 | */ 21 | 22 | /*! 23 | * HTML Parser By John Resig (ejohn.org) 24 | * Modified by Juriy "kangax" Zaytsev 25 | * Original code by Erik Arvidsson, Mozilla Public License 26 | * http://erik.eae.net/simpleser/simplehtmlparser.js 27 | */ 28 | 29 | import { makeMap, no } from 'shared/util' 30 | import { isNonPhrasingTag } from 'web/compiler/util' 31 | 32 | // Regular Expressions for parsing tags and attributes 33 | const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ 34 | // could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName 35 | // but for Vue templates we can enforce a simple charset 36 | const ncname = '[a-zA-Z_][\\w\\-\\.]*' 37 | const qnameCapture = `((?:${ncname}\\:)?${ncname})` 38 | const startTagOpen = new RegExp(`^<${qnameCapture}`) 39 | const startTagClose = /^\s*(\/?)>/ 40 | const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) 41 | const doctype = /^]+>/i 42 | // #7298: escape - to avoid being pased as HTML comment when inlined in page 43 | const comment = /^', 58 | '"': '"', 59 | '&': '&', 60 | ' ': '\n', 61 | ' ': '\t' 62 | } 63 | const encodedAttr = /&(?:lt|gt|quot|amp);/g 64 | const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g 65 | 66 | // #5992 67 | const isIgnoreNewlineTag = makeMap('pre,textarea', true) 68 | const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n' 69 | 70 | function decodeAttr (value, shouldDecodeNewlines) { 71 | const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr 72 | return value.replace(re, match => decodingMap[match]) 73 | } 74 | 75 | export function parseHTML (html, options) { 76 | const stack = [] 77 | const expectHTML = options.expectHTML 78 | const isUnaryTag = options.isUnaryTag || no 79 | const canBeLeftOpenTag = options.canBeLeftOpenTag || no 80 | let index = 0 81 | let last, lastTag 82 | while (html) { 83 | last = html 84 | // Make sure we're not in a plaintext content element like script/style 85 | // 如果没有lastTag,并确保不是在一个纯文本内容元素中:script、style、textarea 86 | if (!lastTag || !isPlainTextElement(lastTag)) { 87 | let textEnd = html.indexOf('<') 88 | //判断html字符串是否以<开头 89 | if (textEnd === 0) { 90 | // 判断html是不是以 注释的 91 | // const comment = /^') 94 | 95 | if (commentEnd >= 0) { 96 | // 如果有保留注释,就执行 comment 方法 97 | if (options.shouldKeepComment) { 98 | options.comment(html.substring(4, commentEnd)) 99 | } 100 | advance(commentEnd + 3) 101 | continue 102 | } 103 | } 104 | 105 | // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment 106 | // 判断是否为向下兼容放入注释,如 107 | // const conditionalComment = /^') 110 | if (conditionalEnd >= 0) { 111 | advance(conditionalEnd + 2) 112 | continue 113 | } 114 | } 115 | 116 | // html头部 117 | // 获取doctype 开头的标签内容 118 | // const doctype = /^]+>/i 119 | const doctypeMatch = html.match(doctype) 120 | if (doctypeMatch) { 121 | advance(doctypeMatch[0].length) 122 | continue 123 | } 124 | 125 | // 处理结束标签 126 | // const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) 127 | const endTagMatch = html.match(endTag) 128 | if (endTagMatch) { 129 | const curIndex = index 130 | advance(endTagMatch[0].length) 131 | parseEndTag(endTagMatch[1], curIndex, index) 132 | continue 133 | } 134 | 135 | // 处理起始标签, 获取开始标签的match对象 136 | const startTagMatch = parseStartTag() 137 | if (startTagMatch) { 138 | // 解析头部 节点属性 139 | handleStartTag(startTagMatch) 140 | if (shouldIgnoreFirstNewline(lastTag, html)) { 141 | advance(1) 142 | } 143 | continue 144 | } 145 | } 146 | 147 | let text, rest, next 148 | // 如果<标签位置大于0,表示b标签内有内容 149 | // 解析<,开始标签或结束标签 150 | if (textEnd >= 0) { 151 | // 截取从 0 - textEnd 的字符串 152 | rest = html.slice(textEnd) 153 | // 获取在普通字符串中的<字符,而不是开始标签、结束标签、注释、条件注释 154 | while ( 155 | !endTag.test(rest) && 156 | !startTagOpen.test(rest) && 157 | !comment.test(rest) && 158 | !conditionalComment.test(rest) 159 | ) { 160 | // < in plain text, be forgiving and treat it as text 161 | // 处理文本中的<字符 162 | // 解析 123123,,<前的内容 123123 163 | next = rest.indexOf('<', 1) 164 | if (next < 0) break 165 | textEnd += next 166 | rest = html.slice(textEnd) 167 | } 168 | // 最终截取字符串的内容 169 | text = html.substring(0, textEnd) 170 | advance(textEnd) 171 | } 172 | 173 | if (textEnd < 0) { 174 | text = html 175 | html = '' 176 | } 177 | 178 | if (options.chars && text) { 179 | // 文本解析完之后调用,包括文本自身 180 | options.chars(text) 181 | } 182 | } else { 183 | // 处理stackedTag为 script, style, textarea 安全解析 184 | let endTagLength = 0; 185 | const stackedTag = lastTag.toLowerCase() 186 | const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(]*>)', 'i')) 187 | const rest = html.replace(reStackedTag, function (all, text, endTag) { 188 | endTagLength = endTag.length 189 | if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { 190 | // 解析 ''.replace(//g, '$1') 123 191 | text = text 192 | .replace(//g, '$1') // #7298 193 | .replace(//g, '$1') 194 | } 195 | if (shouldIgnoreFirstNewline(stackedTag, text)) { 196 | text = text.slice(1) 197 | } 198 | if (options.chars) { 199 | // 文本解析完之后调用,包括文本自身 200 | options.chars(text) 201 | } 202 | return '' 203 | }) 204 | index += html.length - rest.length 205 | html = rest 206 | // tag解析结束 207 | parseEndTag(stackedTag, index - endTagLength, index) 208 | } 209 | // 如果html文本解析到最后 210 | if (html === last) { 211 | options.chars && options.chars(html) 212 | if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) { 213 | options.warn(`Mal-formatted tag at end of template: "${html}"`) 214 | } 215 | break 216 | } 217 | } 218 | 219 | // Clean up any remaining tags 220 | // 清除所有的残余标签 221 | parseEndTag() 222 | 223 | // 将index推进n个字符,然后从第n个字符截取html内容字符串 224 | function advance (n) { 225 | index += n 226 | html = html.substring(n) 227 | } 228 | 229 | // 开始匹配开始标签中的内容 230 | // 该方法使用正则匹配获取HTML开始标签,并且将开始标签中的属性都保存到一个数组中。最终返回标签结果:标签名、标签属性和标签起始结束位置 231 | function parseStartTag () { 232 | const start = html.match(startTagOpen) 233 | if (start) { 234 | const match = { 235 | tagName: start[1], 236 | attrs: [], 237 | start: index 238 | } 239 | advance(start[0].length) 240 | let end, attr 241 | while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { 242 | advance(attr[0].length) 243 | // attr 是标签名 244 | match.attrs.push(attr) 245 | } 246 | 247 | // 在第二次while循环后 end匹配到结束标签 => ['>',''] 248 | if (end) { 249 | match.unarySlash = end[1] 250 | advance(end[0].length) // 标记结束位置 251 | match.end = index // 返回结束位置的索引 252 | return match // 返回匹配对象 ----------- (start)左开始标签起始位置 (end)右标签结束位置 (tagName)标签名 (attrs)节点属性集合 253 | } 254 | } 255 | } 256 | 257 | // 处理开始标签,提取节点属性 258 | function handleStartTag (match) { 259 | const tagName = match.tagName 260 | const unarySlash = match.unarySlash 261 | 262 | if (expectHTML) { 263 | if (lastTag === 'p' && isNonPhrasingTag(tagName)) { 264 | parseEndTag(lastTag) 265 | } 266 | if (canBeLeftOpenTag(tagName) && lastTag === tagName) { 267 | parseEndTag(tagName) 268 | } 269 | } 270 | 271 | const unary = isUnaryTag(tagName) || !!unarySlash 272 | 273 | // 解析开始标签的属性名和属性值 274 | const l = match.attrs.length 275 | const attrs = new Array(l) 276 | // {name:'id',value:'test'}的格式 277 | // 循环提取开始标签的节点属性,存入attrs 278 | for (let i = 0; i < l; i++) { 279 | const args = match.attrs[i] 280 | // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 281 | if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) { 282 | if (args[3] === '') { delete args[3] } 283 | if (args[4] === '') { delete args[4] } 284 | if (args[5] === '') { delete args[5] } 285 | } 286 | const value = args[3] || args[4] || args[5] || '' 287 | const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' 288 | ? options.shouldDecodeNewlinesForHref 289 | : options.shouldDecodeNewlines 290 | attrs[i] = { 291 | name: args[1], 292 | // 处理转义字符 293 | value: decodeAttr(value, shouldDecodeNewlines) 294 | } 295 | } 296 | 297 | if (!unary) { 298 | stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }) 299 | //设置结束标签 300 | lastTag = tagName 301 | } 302 | 303 | if (options.start) { 304 | // 调用start回调函数 305 | options.start(tagName, attrs, unary, match.start, match.end) 306 | } 307 | } 308 | 309 | // 处理结束标签 310 | function parseEndTag (tagName, start, end) { 311 | let pos, lowerCasedTagName 312 | if (start == null) start = index 313 | if (end == null) end = index 314 | 315 | if (tagName) { 316 | lowerCasedTagName = tagName.toLowerCase() 317 | } 318 | 319 | // Find the closest opened tag of the same type 320 | // 倒序循环 从堆栈中找到结束标签对应的tag名 321 | if (tagName) { 322 | for (pos = stack.length - 1; pos >= 0; pos--) { 323 | if (stack[pos].lowerCasedTag === lowerCasedTagName) { 324 | break 325 | } 326 | } 327 | } else { 328 | // If no tag name is provided, clean shop 329 | pos = 0 330 | } 331 | 332 | if (pos >= 0) { 333 | // Close all the open elements, up the stack 334 | for (let i = stack.length - 1; i >= pos; i--) { 335 | if (process.env.NODE_ENV !== 'production' && 336 | (i > pos || !tagName) && 337 | options.warn 338 | ) { 339 | options.warn( 340 | `tag <${stack[i].tag}> has no matching end tag.` 341 | ) 342 | } 343 | if (options.end) { 344 | options.end(stack[i].tag, start, end) 345 | } 346 | } 347 | 348 | // Remove the open elements from the stack 349 | // 从栈中移除元素,并标记为 lastTag 350 | stack.length = pos 351 | lastTag = pos && stack[pos - 1].tag 352 | } else if (lowerCasedTagName === 'br') { // 从栈中移除元素,并标记为 lastTag 353 | if (options.start) { 354 | options.start(tagName, [], true, start, end) 355 | } 356 | } else if (lowerCasedTagName === 'p') { // 段落标签 357 | if (options.start) { 358 | options.start(tagName, [], false, start, end) 359 | } 360 | if (options.end) { 361 | options.end(tagName, start, end) 362 | } 363 | } 364 | } 365 | } 366 | /* 367 | 先是获取开始结束位置、小写标签名;然后遍历堆栈找到同类开始 TAG 的位置; 368 | 对找到的 TAG 位置后的所有标签都执行 options.end 方法; 369 | 将 pos 后的所有标签从堆栈中移除,并修改最后标签为当前堆栈最后一个标签的标签名; 370 | 如果是br标签,执行 option.start 方法;如果是 p 标签,执行 options.start 和options.end 方法。 371 | */ 372 | ``` 373 | 374 | ### parseHTML 参数说明 375 | ![](https://segmentfault.com/img/bVbeE3d?w=951&h=524) 376 | 377 | 378 | ### parseHTML函数 While循环处理流程 379 | ![](https://segmentfault.com/img/bVbeFbN?w=1082&h=809) 380 | -------------------------------------------------------------------------------- /vue/virtual-dom/parse.md: -------------------------------------------------------------------------------- 1 | ### parse (生成 AST结构且解析html模版) 2 | ```js 3 | /* @flow */ 4 | 5 | import he from 'he' 6 | // https://github.com/vuejs/vue/tree/dev/src/compiler/parser 7 | import { parseHTML } from './html-parser' 8 | import { parseText } from './text-parser' 9 | import { parseFilters } from './filter-parser' 10 | import { genAssignmentCode } from '../directives/model' 11 | import { extend, cached, no, camelize } from 'shared/util' 12 | import { isIE, isEdge, isServerRendering } from 'core/util/env' 13 | 14 | // https://github.com/vuejs/vue/blob/dev/src/compiler/helpers.js 15 | import { 16 | addProp, 17 | addAttr, 18 | baseWarn, 19 | addHandler, 20 | addDirective, 21 | getBindingAttr, 22 | getAndRemoveAttr, 23 | pluckModuleFunction 24 | } from '../helpers' 25 | 26 | // 匹配@以及v-on,绑定事件 27 | export const onRE = /^@|^v-on:/ 28 | // 匹配v-、@以及: 29 | export const dirRE = /^v-|^@|^:/ 30 | // 匹配v-for中的in以及of 31 | // 比如 for(var items in item) , for(var items of item) 32 | export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/ 33 | // v-for参数中带括号的情况匹配 34 | // 比如 v-for( (items, index) in item)这样的参数 35 | export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ 36 | // 去掉两边括号,比如 (value, key) => value, key 37 | const stripParensRE = /^\(|\)$/g 38 | // 捕获指令参数 39 | const argRE = /:(.*)$/ 40 | // 匹配v-bind以及: 41 | const bindRE = /^:|^v-bind:/ 42 | // 根据点来分开各个级别的正则,比如.stop.native.trim解析后可以得到.stop .native .trim 43 | const modifierRE = /\.[^.]+/g 44 | 45 | // he 为第三方的库,he.decode 函数用于 HTML 字符实体的解码工作 https://github.com/mathiasbynens/he 46 | // he.decode('foo © bar ≠ baz 𝌆 qux') → 'foo © bar ≠ baz 𝌆 qux' 47 | const decodeHTMLCached = cached(he.decode) 48 | 49 | // configurable state 50 | export let warn: any 51 | let delimiters 52 | let transforms 53 | let preTransforms 54 | let postTransforms 55 | let platformIsPreTag 56 | let platformMustUseProp 57 | let platformGetTagNamespace 58 | 59 | type Attr = { name: string; value: string }; 60 | 61 | /** 62 | * Convert HTML string to AST. 63 | */ 64 | /*将HTML字符串转换成AST*/ 65 | export function createASTElement ( 66 | tag: string, 67 | attrs: Array, 68 | parent: ASTElement | void 69 | ): ASTElement { 70 | return { 71 | type: 1, 72 | tag, 73 | attrsList: attrs, 74 | attrsMap: makeAttrsMap(attrs), 75 | parent, 76 | children: [] 77 | } 78 | } 79 | 80 | /** 81 | * Convert HTML string to AST. 82 | */ 83 | export function parse ( 84 | template: string, 85 | options: CompilerOptions 86 | ): ASTElement | void { 87 | /*警告函数,baseWarn是Vue 编译器默认警告*/ 88 | warn = options.warn || baseWarn 89 | /*检测是否是
标签*/
 90 |   platformIsPreTag = options.isPreTag || no
 91 |   platformMustUseProp = options.mustUseProp || no
 92 |   platformGetTagNamespace = options.getTagNamespace || no
 93 | 
 94 |   // 找出options.mudules中每一项中属性含有key方法
 95 |   transforms = pluckModuleFunction(options.modules, 'transformNode')
 96 |   preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
 97 |   postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
 98 | 
 99 |   delimiters = options.delimiters
100 | 
101 |   /* 解析非一元数组就存入元素 */
102 |   const stack = []
103 |   const preserveWhitespace = options.preserveWhitespace !== false
104 |   let root
105 |   let currentParent
106 |   /*标志位,是否有v-pre属性*/
107 |   let inVPre = false
108 |   /*标志位,是否是pre标签*/
109 |   let inPre = false
110 |   let warned = false
111 | 
112 |   /*只发出一次的warning*/
113 |   function warnOnce (msg) {
114 |     if (!warned) {
115 |       warned = true
116 |       warn(msg)
117 |     }
118 |   }
119 | 
120 |   function closeElement (element) {
121 |     // check pre state
122 |     if (element.pre) {
123 |       inVPre = false
124 |     }
125 |     if (platformIsPreTag(element.tag)) {
126 |       inPre = false
127 |     }
128 |     // apply post-transforms
129 |     for (let i = 0; i < postTransforms.length; i++) {
130 |       postTransforms[i](element, options)
131 |     }
132 |   }
133 | 
134 |   /*解析HTML*/
135 |   parseHTML(template, {
136 |     warn,
137 |     // 结束标签
138 |     expectHTML: options.expectHTML,
139 |     isUnaryTag: options.isUnaryTag,
140 |     // 开始标签
141 |     canBeLeftOpenTag: options.canBeLeftOpenTag,
142 |     // 
143 |     shouldDecodeNewlines: options.shouldDecodeNewlines,
144 |     shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
145 |     shouldKeepComment: options.comments,
146 | 
147 |     // 主要是创建,处理,优化AST元素,
148 |     // 通过`createASTElment`函数创建AST元素,tpye是AST元素类型,(一般1是元素节点,2是表达式,3是纯文本),tag是标签名,attrsList是节点属性列表,attrsMap是节点属性映射表,parent是AST元素的父AST元素,children是AST元素的子AST元素,都保存在 `element`对象中.
149 | 
150 |     // 判断 元素是不是服务端渲染的元素 或者 是不是被Vue禁止的标签元素,如: