├── .DS_Store ├── HTTP └── web开发中,必须要了解的HTTP相关知识.md ├── README.md ├── assets └── delete.png ├── designPattern ├── 代理模式.md ├── 单例模式.md ├── 原型模式.md ├── 命令模式.md ├── 工厂模式.md ├── 状态模式.md ├── 策略模式.md ├── 观察者模式.md ├── 迭代器模式.md └── 适配器模式.md ├── html ├── HTML文档中meta标签常用属性及其作用.md ├── 《Webkit技术内幕》之页面渲染过程.md └── 什么是HTML文档.md ├── javaScript ├── JavaScript中Array的sort方法总结.md ├── JavaScript中delete操作符.md ├── JavaScript中的执行栈、执行环境(上下文)、作用域、作用域链、活动对象、变量对象的概念总结.md ├── JavaScript中的正则表达式.md ├── JavaScript中的闭包.md ├── JavaScript中的面向对象、原型、原型链、继承.md └── Object.defineProperty和defineProperties.md ├── javaScript数据结构与算法 ├── 冒泡排序.md ├── 插入排序.md ├── 栈.md ├── 树.md ├── 选择排序.md ├── 链表.md ├── 队列.md └── 集合.md └── notes ├── README.md └── 不同时区与UTC时间的转换关系.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunzhaoye/blog/d118c85f9ca825a3ef1739623b1f27c4472a8688/.DS_Store -------------------------------------------------------------------------------- /HTTP/web开发中,必须要了解的HTTP相关知识.md: -------------------------------------------------------------------------------- 1 | 本文主要记录与HTTP相关的具体概念和知识,关于HTTP协议的诞生和历史发展,不多做介绍,自己但是既然是写HTTP,顺带说两句,上下文也能衔接的上。 2 | 3 | CERN(欧洲核子研究组织)的蒂姆 • 伯纳斯 - 李(Tim BernersLee)博士提出了一种能让远隔两地的研究者们共享知识的设想,于是HTTP慢慢的诞生了。 4 | 5 | 另外,HTTP协议是无状态可以,于是为了保存用户的状态,cookie诞生了。 6 | 7 | HTTP协议是建立在TCP连接之上的,当浏览器输入URL进行访问,浏览器冲URL中解析出主机名和端口,浏览器建立一条与web服务器的连接,然后才进行http请求。 8 | 9 | # TCP连接的建立与终止 10 | 11 | ## 建立TCP连接(三次握手) 12 | 13 | 在客户端与服务端进行http通信之前,需要建立TCP连接,这时需要三次握手 14 | 15 | (1) 请求新的TCP连接,客户端发送一个小的TCP分组,这个分组设置一个特殊的SYN标记,表明是一个客户端请求。 16 | 17 | (2) 如果服务器接受这个连接,就会对一些连接参数进行计算,并向客户端回送一个TCP分组,发送SY和ACK标记,表明连接请求已经被接受 18 | 19 | (3) 最后,客户端向服务器回送一条确认消息,通知服务器连接已经建立。 20 | 21 | ![HTTP-三次握手](http://qiniu.sunzhaoye.com/HTTP-%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.png) 22 | 23 | ## 断开TCP连接(四次断开) 24 | 25 | >建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方 26 | 向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止 27 | 这个方向连接。当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传 28 | 送。发送FIN通常是应用层进行关闭的结果。 29 | 30 | (1) 客户端发送FIN标记到服务器,表明客户端发起关闭连接 31 | 32 | (2) 服务器接收客户端的FIN标记并,向客户端发送FIN的ACK确认标记 33 | 34 | (3) 服务器发送FIN到客户端,服务器关闭连接 35 | 36 | (4) 服务器端发送一个FIN的ACK确认标记,确认连接关闭 37 | 38 | ![HTTP-四次分手](http://qiniu.sunzhaoye.com/HTTP-%E5%9B%9B%E6%AC%A1%E5%88%86%E6%89%8B.png) 39 | 40 | **建立持久连接的请求和响应交互:** 41 | 42 | ![HTTP-持久连接的请求和响应交互](http://qiniu.sunzhaoye.com/HTTP-%E6%8C%81%E4%B9%85%E9%93%BE%E6%8E%A5%E7%9A%84%E8%AF%B7%E6%B1%82%E5%92%8C%E5%93%8D%E5%BA%94%E4%BA%A4%E4%BA%92.png) 43 | 44 | **使用wireshark进行数据抓包:** 45 | 46 | 这里向大家推荐一款抓包软件**Wireshark**,可以用来分析TCP连接的建立和断开过程,以及抓取HTTP请求和相应的信息等,下面是我进行一次客户端和服务端通信的抓包数据截图: 47 | 48 | 49 | ![HTTP-TCP连接的建立与断开](http://qiniu.sunzhaoye.com/HTTP-TCP%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%BB%BA%E7%AB%8B%E4%B8%8E%E6%96%AD%E5%BC%80.png) 50 | 51 | # HTTP报文 52 | 53 | >HTTP协议报文是应用程序之间发送的数据块,也就是客户端和服务端用于交互的信息。客户端的报文叫做请求报文,服务器端的报文叫做响应报文。 54 | 55 | ## HTTP报文组成 56 | 57 | >HTTP报文由起始行、首部和实体的主体(也称报文主体或主体)组成。起始行和首部以一个回车符和换行符作为结束,主体部分可以是二进制数据,也可以为空。 58 | 59 | ![HTTP报文组成](http://qiniu.sunzhaoye.com/HTTP--%E6%8A%A5%E6%96%87%E7%BB%84%E6%88%90.png) 60 | 61 | ### 1. 起始行 62 | 63 | **请求报文起始行:** 64 | 65 | 请求报文起始行说明了要做什么,由请求方法 、请求URI和协议版本构成。 66 | 67 | ``` 68 | GET /index.html HTTP/1.1 69 | ``` 70 | 71 | **响应报文起始行:** 72 | 73 | 响应报文的起始行,由协议版本、状态码和原因短语构成。 74 | ``` 75 | HTTP/1.1 200 OK // OK就是原因短语 76 | ``` 77 | 78 | ![HTTP-请求-响应报文](http://qiniu.sunzhaoye.com/HTTP--%E8%AF%B7%E6%B1%82-%E5%93%8D%E5%BA%94%E6%8A%A5%E6%96%87.png) 79 | 80 | ### 2. 首部 81 | 82 | **首部字段分类** 83 | 84 | * 1.通用首部 85 | 86 | >客户端和服务端都可以使用的首部 87 | 88 | 通用首部字段表: 89 | 90 | ![通用首部字段](http://qiniu.sunzhaoye.com/HTTP-%E9%80%9A%E7%94%A8%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5.png) 91 | 92 | * 2.请求首部 93 | 94 | >请求报文特有的首部,为服务器提供了一些额外的信息,补充了请求的附加内容、客户端信息、响应内容相关的优先级等信息。 95 | 96 | 请求首部字段 97 | ![请求首部字段](http://qiniu.sunzhaoye.com/HTTP-%E8%AF%B7%E6%B1%82%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5.png) 98 | 99 | * 3.响应首部 100 | >响应报文特有的字段 101 | 102 | 响应首部字段表: 103 | 104 | ![响应首部字段](http://qiniu.sunzhaoye.com/HTTP-%E5%93%8D%E5%BA%94%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5.png) 105 | 106 | * 4.实体首部 107 | >用于针对请求报文和响应报文主体部分使用的首部 108 | 109 | ![实体首部字段](http://qiniu.sunzhaoye.com/HTTP-%E5%AE%9E%E4%BD%93%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5.png) 110 | 111 | * 5.扩展首部 112 | 113 | >扩展首部是非标准的首部,由应用程序开发者创建,但还未添加到已批准的HTTP标准中去。 114 | 115 | 116 | # http状态码 117 | 118 | >状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了错误。 119 | 120 | **状态码分类:** 121 | 122 | | 状态码区间 | 类别 | 123 | | ------- | ------- | 124 | | 100~199 | 信息性状态码 | 125 | | 200~299 | 成功状态码 | 126 | | 300~399 | 重定向状态码 | 127 | | 400~499 | 客户端错误状态码 | 128 | | 500~599 | 服务器错误状态码 | 129 | 130 | **常用状态码列表:** 131 | 132 | | 状态码 | 原因短语 |含义 | 133 | | -------| ------- | ------- | 134 | | 200 |OK | 表示从客户端发来的请求在服务器端被正常处理了| 135 | | 204 | No Content | 该状态码代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。另外,也不允许返回任何实体的主体。| 136 | | 301 | Moved Permanently |永久重定向,该状态码表示请求的资源已被分配了新的 URI,以后应使用资源现在所指的 URI| 137 | | 302 | Found | 临时性重定向,该状态码表示请求的资源已被分配了新的 URI,希望用户(本次)能使用新的 URI 访问| 138 | | 303 | See Other | 303 状态码和 302 Found 状态码有着相同的功能,但 303 状态码明确表示客户端应当采用 GET 方法获取资源,这点与 302 状态码有区别| 139 | | 304 | Not Modified|缓存| 140 | | 307 |Temporary Redirect| 临时重定向,和302一样 | 141 | | 400 |Bad Request | 该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码| 142 | | 401 |Unauthorized|该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息| 143 | | 403 |Forbidden| 该状态码表明对请求资源的访问被服务器拒绝了| 144 | | 404 |Not Found| 该状态码表明服务器上无法找到请求的资源| 145 | | 500 |Internal Server Error|该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web应用存在的 bug 或某些临时的故障| 146 | | 502 |Bad Gateway|网关错误| 147 | | 503 |Service Unavailable| 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入RetryAfter 首部字段再返回给客户端| 148 | 149 | # HTTP中不同场景下,首部字段的作用 150 | 151 | ## 1. CORS 跨域资源共享 152 | 153 | > 跨域资源共享([CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS)) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。 --MDN 154 | 155 | 下面使用nodejs来搭建一个简单的服务器,来介绍一个跨域问题的解决方法 156 | 157 | ``` 158 | // index.html 159 | 160 | 161 | 162 | 163 | 164 | CORS 165 | 166 | 167 | Hello World 168 | 171 | 172 | 173 | ``` 174 | 175 | ``` 176 | // server.js 177 | 178 | const http = require('http') 179 | 180 | http.createServer(function(req, res) { 181 | res.writeHead('200', { 182 | 'Access-Control-Allow-Origin': 'http://localhost:8082' 183 | }) 184 | }).listen(8081) 185 | ``` 186 | 在源地址为 http://localhost:8082 下,请求http://localhost:8081,是跨域请求,浏览器会自动在request Header中发送Origin首部字段,并把值设置为来自哪个源,本例为http://localhost:8081。服务器需要在响应头中设置Access-Control-Allow-Origin,来告知浏览器可以处理返回的数据。如果响应头中不设置Access-Control-Allow-Origin则会报错,但是返回状态码为200,跨域实际上是浏览器本身的一个安全机制。 187 | 188 | ![http-跨域-报错](http://qiniu.sunzhaoye.com/http-%E8%B7%A8%E5%9F%9F-%E6%8A%A5%E9%94%99.png) 189 | 190 | ![http-跨域-状态码200](http://qiniu.sunzhaoye.com/http-%E8%B7%A8%E5%9F%9F-%E8%BF%94%E5%9B%9E200%E7%8A%B6%E6%80%81%E7%A0%81.png) 191 | 192 | 193 | ``` 194 | // server2.js 195 | // 启动8082端口服务,在浏览器中访问http://127.0.0.1:8082,会返回index.html内容 196 | 197 | const http = require('http') 198 | const fs = require('fs') 199 | http.createServer(function(req, res) { 200 | var page = fs.readFileSync('index.html', 'utf-8') 201 | res.writeHead(200, { 202 | 'Content-Type': 'text/html' 203 | }) 204 | res.end(page) 205 | }).listen(8082) 206 | ``` 207 | **关于CORS跨域请求的分类:** 208 | 209 | **1.简单请求:** 210 | 211 | 需要同时满足以下的条件就是简单请求 212 | 213 | (1)请求方法: 214 | ``` 215 | GET、POST、HEAD 216 | ``` 217 | (2)请求头不能为以下其他字段之外 218 | 219 | ``` 220 | Accept 221 | Accept-Language 222 | Content-Language 223 | Content-Type的值必须为application/x-www-form-urlencoded、multipart/form-data、text/plain之一 224 | ``` 225 | 226 | **2.非简单请求:** 227 | 228 | 非简单请求是当请求信息不满足简单请求的条件,浏览器就发送方法为OPTIONS的预请求,包含自己请求的方法及需要使用的请求头字段,在得到服务器响应允许之后,浏览器会按照想要使用的请求方法及头信息再发一次请求。 229 | 230 | 现在修改以下上面的例子: 231 | 232 | ``` 233 | // index.html 234 | 235 | 236 | 237 | 238 | 239 | CORS 240 | 241 | 242 | Hello World 243 | 251 | 252 | 253 | ``` 254 | 255 | ``` 256 | // server.js 257 | 258 | const http = require('http') 259 | 260 | http.createServer(function(req, res) { 261 | res.writeHead('200', { 262 | 'Access-Control-Allow-Origin': 'http://localhost:8082' 263 | }) 264 | }).listen(8081) 265 | ``` 266 | 267 | 如果服务端不进行相应的设置告诉浏览器允许跨域访问则会报错 268 | 269 | ![HTTP-cors-methodError](http://qiniu.sunzhaoye.com/HTTP-cors-methodError.png) 270 | 271 | 但是预请求返回状态码为200 272 | ![HTTP-cors-options-状态码200](http://qiniu.sunzhaoye.com/HTTP-cors-options%E7%8A%B6%E6%80%81%E7%A0%81200.png) 273 | 274 | 275 | ``` 276 | // server2.js 277 | // 启动8082端口服务,在浏览器中访问http://127.0.0.1:8082,会返回index.html内容 278 | 279 | const http = require('http') 280 | const fs = require('fs') 281 | http.createServer(function(req, res) { 282 | var page = fs.readFileSync('index.html', 'utf-8') 283 | res.writeHead(200, { 284 | 'Content-Type': 'text/html' 285 | }) 286 | res.end(page) 287 | }).listen(8082) 288 | ``` 289 | 290 | 现在我们修改以下 server.js 291 | 292 | ``` 293 | // server.js 294 | 295 | const http = require('http') 296 | 297 | http.createServer(function(req, res) { 298 | res.writeHead('200', { 299 | 'Access-Control-Allow-Origin': 'http://localhost:8082', 300 | 'Access-Control-Allow-Headers': 'X-Coustom-Head', 301 | 'Access-Control-Allow-Methods': 'PUT' 302 | }) 303 | }).listen(8081) 304 | ``` 305 | 306 | 重新启动node服务,访问http://locaohost:8082,可以看到在发送预请求后,浏览器会继续发送PUT请求 307 | 308 | ![HTTP-cors-options](http://qiniu.sunzhaoye.com/HTTP-cors-options.png) 309 | 310 | ![HTTP-cors-allow.png](http://qiniu.sunzhaoye.com/HTTP-cors-allow.png) 311 | 312 | 关于CORS的其他设置这里就不多做介绍了,这里主要是用一个例子来说明以下http不同字段在跨域场景下的作用。 313 | 314 | ## 2. 缓存 (Cache-Control的作用) 315 | 316 | 本例依旧用node服务来讲解一下Cache-Control的作用,新建三个文件 317 | 318 | ``` 319 | // index.html 320 | 321 | 322 | 323 | 324 | 325 | 326 | Cache-Control 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | ``` 336 | 337 | ``` 338 | // script.js 339 | console.log('script.js') 340 | ``` 341 | 342 | ``` 343 | // server.js 344 | const http = require('http') 345 | const fs = require('fs') 346 | 347 | 348 | http.createServer(function(req, res) { 349 | if (req.url === '/') { 350 | let page = fs.readFileSync('index2.html', 'utf-8') 351 | res.writeHead(200, { 352 | 'Content-Type': 'text/html' 353 | }) 354 | res.end(page) 355 | } 356 | 357 | if (req.url === '/script.js') { 358 | let page = fs.readFileSync('script.js', 'utf-8') 359 | res.writeHead(200, { 360 | 'Content-Type': 'text/javascript', 361 | 'Cache-Control': 'max-age=10' 362 | }) 363 | res.end(page) 364 | } 365 | }).listen(8082) 366 | ``` 367 | 368 | 在第一次请求script.js资源时,向服务器发送请求 369 | 370 | ![HTTP-cache-control-1](http://qiniu.sunzhaoye.com/HTTP-cache-control-1.png) 371 | ![HTTP-cache-control-3](http://qiniu.sunzhaoye.com/HTTP-cache-control-3.png) 372 | 373 | 由于服务器返回响应时,设置Cache-Control: 'max-age=10'时,修改script.js后,在10秒内继续请求script.js资源,则从缓存中读取,而打印信息依旧是'script.js' 374 | 375 | ``` 376 | // script.js 377 | console.log('script-modify.js') 378 | ``` 379 | ![HTTP-cache-control-1](http://qiniu.sunzhaoye.com/HTTP-cache-control-2.png) 380 | ![HTTP-cache-control-3](http://qiniu.sunzhaoye.com/HTTP-cache-control-3.png) 381 | 382 | 更多关于缓存的知识在这里也不多介绍了,贴两张cache-control字段在请求和响应时可以设置的值和其表示含义: 383 | 384 | **1. Cache-Control 缓存请求指令:** 385 | 386 | ![HTTP-cache-control-缓存请求指令](http://qiniu.sunzhaoye.com/HTTP-cache-control-%E7%BC%93%E5%AD%98%E8%AF%B7%E6%B1%82%E6%8C%87%E4%BB%A4.png) 387 | 388 | **2. Cache-Control 缓存响应指令:** 389 | 390 | ![HTTP-cache-control-缓存响应指令](http://qiniu.sunzhaoye.com/HTTP-cache-control-%E7%BC%93%E5%AD%98%E5%93%8D%E5%BA%94%E6%8C%87%E4%BB%A4.png) 391 | 392 | ## 3. cookie 393 | 394 | >指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密),当下次再访问时浏览器会将该网站的cookie发回给服务器端。 395 | 396 | cookie如果不设置过期时间,随浏览器关闭而失效,如果有需要可以设置过期时间,继续上代码例子🌰,新建两个文件如下 397 | 398 | ``` 399 | // index.html 400 | 401 | 402 | 403 | 404 | 405 | Cookie 406 | 407 | 408 | Cookie 409 | 412 | 413 | 414 | ``` 415 | 416 | ``` 417 | // server.js 418 | 419 | const http = require('http') 420 | const fs = require('fs') 421 | 422 | http.createServer(function(req, res) { 423 | if (req.url === '/') { 424 | let page = fs.readFileSync('index.html', 'utf-8') 425 | res.writeHead(200, { 426 | 'Content-Type': 'text/html', 427 | 'Set-Cookie': ['a=1;max-age:5', 'b=2;HTTPOnly'] 428 | }) 429 | res.end(page) 430 | } 431 | }).listen(8082) 432 | ``` 433 | 434 | 启动node服务,访问localhost:8082,可以看到成功设置了cookie 435 | 436 | ![HTTP-cookie-application](http://qiniu.sunzhaoye.com/HTTP-cookie-application.png) 437 | 438 | 并在响应头信息中设置了Set-Cookie字段 439 | 440 | ![HTTP-cookie-init](http://qiniu.sunzhaoye.com/HTTP-cookie-init.png) 441 | 442 | 另外关注以下打印信息,发现只有a=1,因为给b=2设置了HttpOnly属性,不允许JavaScript通过脚本来获取到cookie信息 443 | 444 | ![HTTP-cookie-console](http://qiniu.sunzhaoye.com/HTTP-cookie-console.png) 445 | 446 | 由于当再次请求时,cookie会在请求头中发送到服务器,由于cookie a=1设置了5秒后过期,在5秒后刷新页面,请求头中的cookie只有a=1 447 | 448 | ![HTTP-cookie-againRequest](http://qiniu.sunzhaoye.com/HTTP-cookie-againRequest.png) 449 | 450 | 在5秒内发送二次请求,cookie a=1没有失效,在请求头中cookie a=1;b=2都会发送到服务器 451 | 452 | ![HTTP-cookie-max-age](http://qiniu.sunzhaoye.com/HTTP-cookie-max-age.png) 453 | 454 | 另外对于cookie的其他设置如expires、domain等在这里也不多做介绍了 455 | 456 | ## 4. 重定向 457 | 458 | 当服务端返回301、302、307等状态码都代表资源已经被重定向到其他位置,301表示永久改变URI,302和307表示临时重定向到某个URI 459 | 460 | 本例举一个服务器返回302状态码的例子,直接上代码: 461 | 462 | ``` 463 | // server.js 464 | 465 | const http = require('http'); 466 | const fs = require('fs') 467 | 468 | http.createServer((req, res) => { 469 | if (req.url === '/') { 470 | res.writeHead(302, { 471 | 'Location': '/redirect' 472 | }) 473 | res.end() 474 | } 475 | 476 | if (req.url === '/redirect') { 477 | res.end('redirect') 478 | } 479 | }).listen(8082); 480 | ``` 481 | 访问localhost:8082, 服务器返回302状态码时,在相应头中设置Location首部字段,浏览器会继续发送请求到重定向的地址 482 | 483 | ![HTTP-重定向-302-1](http://qiniu.sunzhaoye.com/HTTP-%E9%87%8D%E5%AE%9A%E5%90%91-302-1.png) 484 | 485 | ![HTTP-重定向-302-2](http://qiniu.sunzhaoye.com/HTTP-%E9%87%8D%E5%AE%9A%E5%90%91-302-2.png) 486 | 487 | # HTTP与HTTPS的区别 488 | 489 | 首先说一下什么是[HTTPS](https://baike.baidu.com/item/https/285356?fr=aladdin) 490 | 491 | > HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 --百度百科 492 | 493 | **HTTPS = HTTP+ 加密 + 认证 + 完整性保护** 494 | 495 | 最主要是在应用层和传输层中间加了一个SSL(安全套阶层),通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信。 496 | 497 | ![HTTP-http与https对比](http://qiniu.sunzhaoye.com/HTTP-http%E4%B8%8Ehttps%E5%AF%B9%E6%AF%94.png) 498 | 499 | **HTTP与HTTPS的区别:** 500 | 501 | * (1) HTTP是明文传输,HTTPS是经过SSL加密后进行传输,只有客户端和服务端根据公钥和私钥进行加密和解密能看到,中间任何传输环节无法获取传输信息,所以HTTPS比HTTP安全 502 | 503 | * (2) HTTPS需要到数字证书认证机构进行购买 504 | 505 | * (3) HTTP服务器默认端口是80,HTTPS服务器默认端口是443 506 | 507 | 本文主要介绍HTTP,关于HTTPS主要就介绍这么多吧。 508 | 509 | # HTTP2 510 | 511 | 本想说点HTTP2的知识,奈何自己是小白,放个百度百科的链接吧 [HTTP2](https://baike.baidu.com/item/HTTP%202.0/12520156?fr=aladdin)。 512 | 513 | 等后续随着不断的学习,再回来更新本文。 514 | 515 | 另外放一个HTTP1.1与HTTP2请求与相应对比的demo的链接[HTTP/2 is the future of the Web, and it is here!](https://http2.akamai.com/demo) 516 | 517 | 最后,本文主要介绍了一些HTTP在web开发中的基础知识,关于概念和图解流程的截图基本上都是来自[《TCP/IP详解 卷1:协议》](https://book.douban.com/subject/1088054/)、[《图解HTTP》](https://book.douban.com/subject/25863515/)、[《HTTP权威指南》](https://book.douban.com/subject/10746113/),可放心参考。笔者功力实在有限,如有问题,请大家多多指出,相互学习和进步,也希望通过我的学习与实践过程,整理出的笔记能对大家有所帮助,谢谢。 518 | 519 | **本文参考链接:** 520 | 521 | [TCP/IP详解 卷1:协议](https://book.douban.com/subject/1088054/) 522 | [图解HTTP](https://book.douban.com/subject/25863515/) 523 | [HTTP权威指南](https://book.douban.com/subject/10746113/) 524 | [跨域资源共享 CORS 详解--阮一峰](http://www.ruanyifeng.com/blog/2016/04/cors.html) 525 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 个人博客 2 | 3 | > 记录自己学习过程, 也方便自己回头查阅, 也与他人分享, 共同探讨, 共同进步! 4 | 5 | 目录 6 | 7 | ## html 8 | * [什么是HTML文档](https://github.com/sunzhaoye/blog/issues/1) 9 | * [HTML文档中meta标签常用属性及其作用](https://github.com/sunzhaoye/blog/issues/2) 10 | * [《Webkit技术内幕》之页面渲染过程](https://github.com/sunzhaoye/blog/issues/13) 11 | 12 | 13 | ## javaScript 14 | 15 | * [你可能知道的 JavaScript数据结构与算法](https://github.com/sunzhaoye/blog/issues/19) 16 | * [JavaScript中常见的设计模式](https://github.com/sunzhaoye/blog/issues/16) 17 | * [JavaScript中的执行上下文和变量对象](https://github.com/sunzhaoye/blog/issues/14) 18 | * [JavaScript中的闭包](https://github.com/sunzhaoye/blog/issues/12) 19 | * [JavaScript中的Object.defineProperty和defineProperties](https://github.com/sunzhaoye/blog/issues/8) 20 | * [JavaScript中的面向对象、原型、原型链、继承](https://github.com/sunzhaoye/blog/issues/10) 21 | * [JavaScript中的正则表达式](https://github.com/sunzhaoye/blog/issues/11) 22 | * [JavaScript中的delete操作符](https://github.com/sunzhaoye/blog/issues/3) 23 | * [JavaScript中Array的sort方法](https://github.com/sunzhaoye/blog/issues/4) 24 | 25 | ## HTTP 26 | 27 | * [web开发中,必须要了解的HTTP相关知识 ](https://github.com/sunzhaoye/blog/issues/18) -------------------------------------------------------------------------------- /assets/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunzhaoye/blog/d118c85f9ca825a3ef1739623b1f27c4472a8688/assets/delete.png -------------------------------------------------------------------------------- /designPattern/代理模式.md: -------------------------------------------------------------------------------- 1 | ## 代理模式 2 | 3 | >![设计模式-代理模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 本文举一个使用代理对象加载图片的例子来理解代理模式,当网络不好的时候,图片的加载需要一段时间,这就会产生空白,影响用户体验,这时候我们可在图片真正加载完之前,使用一张loading占位图片,等图片真正加载完再给图片设置src属性。 6 | 7 | ``` 8 | class MyImage { 9 | constructor() { 10 | this.img = new Image() 11 | document.body.appendChild(this.img) 12 | } 13 | setSrc(src) { 14 | this.img.src = src 15 | } 16 | } 17 | 18 | class ProxyImage { 19 | constructor() { 20 | this.proxyImage = new Image() 21 | } 22 | 23 | setSrc(src) { 24 | let myImageObj = new MyImage() 25 | myImageObj.img.src = 'file://xxx.png' //为本地图片url 26 | this.proxyImage.src = src 27 | this.proxyImage.onload = function() { 28 | myImageObj.img.src = src 29 | } 30 | } 31 | } 32 | 33 | var proxyImage = new ProxyImage() 34 | proxyImage.setSrc('http://xxx.png') //服务器资源url 35 | 36 | ``` 37 | 38 | 本例中,本体类中有自己的setSrc方法,如果有一天网络速度已经不需要预加载了,我们可以直接使用本体对象的setSrc方法,,并且不需要改动本体类的代码,而且可以删除代理类。 39 | 40 | ``` 41 | // 依旧可以满足需求 42 | var myImage = new MyImage() 43 | myImage.setSrc('http://qiniu.sunzhaoye.com/CORS.png') 44 | ``` 45 | 46 | **小结:** 47 | 1. 代理模式符合开放封闭原则 48 | 2. 本体对象和代理对象拥有相同的方法,在用户看来并不知道请求的本体对象还是代理对象。 49 | -------------------------------------------------------------------------------- /designPattern/单例模式.md: -------------------------------------------------------------------------------- 1 | ## 单例模式 2 | 3 | >![设计模式-单例模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 举一个书中登录框的例子,代码如下: 6 | 7 | ``` 8 | 9 | 10 | 11 | 12 | 13 | 14 | 49 | 50 | 51 | 52 | ``` 53 | 54 | **小结:** 55 | 56 | 1. 单例模式的主要思想就是,实例如果已经创建,则直接返回 57 | ``` 58 | function creatSingleton() { 59 | var obj = null 60 | // 实例如已经创建过,直接返回 61 | if (!obj) { 62 | obj = xxx 63 | } 64 | return obj 65 | } 66 | ``` 67 | 68 | 2. 符合开放封闭原则 -------------------------------------------------------------------------------- /designPattern/原型模式.md: -------------------------------------------------------------------------------- 1 | ## 原型模式 2 | 3 | >用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。--[百度百科](https://baike.baidu.com/item/%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/4941014?fr=aladdin) 4 | 5 | 在JavaScript中,实现原型模式是在ECMAScript5中,提出的[Object.create](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create)方法,使用现有的对象来提供新创建的对象的__proto__。 6 | 7 | ``` 8 | var prototype = { 9 | name: 'Jack', 10 | getName: function() { 11 | return this.name 12 | } 13 | } 14 | 15 | var obj = Object.create(prototype, { 16 | job: { 17 | value: 'IT' 18 | } 19 | }) 20 | 21 | console.log(obj.getName()) // Jack 22 | console.log(obj.job) // IT 23 | console.log(obj.__proto__ === prototype) //true 24 | ``` 25 | 26 | 更多关于prototype的知识可以看我之前的[JavaScript中的面向对象、原型、原型链、继承](https://segmentfault.com/a/1190000011363171),下面列一下关于prototype的一些使用方法 27 | 28 | **1. 方法继承** 29 | 30 | ``` 31 | var Parent = function() {} 32 | Parent.prototype.show = function() {} 33 | var Child = function() {} 34 | 35 | // Child继承Parent的所有原型方法 36 | Child.prototype = new Parent() 37 | ``` 38 | 39 | **2. 所有函数默认继承Object** 40 | 41 | ``` 42 | var Foo = function() {} 43 | console.log(Foo.prototype.__proto__ === Object.prototype) // true 44 | ``` 45 | 46 | **3. Object.create** 47 | 48 | ``` 49 | var proto = {a: 1} 50 | var propertiesObject = { 51 | b: { 52 | value: 2 53 | } 54 | } 55 | var obj = Object.create(proto, propertiesObject) 56 | console.log(obj.__proto__ === proto) // true 57 | ``` 58 | 59 | **4. isPrototypeOf** 60 | 61 | prototypeObj是否在obj的原型链上 62 | 63 | ``` 64 | prototypeObj.isPrototypeOf(obj) 65 | ``` 66 | 67 | 68 | **5. instanceof** 69 | 70 | contructor.prototype是否出现在obj的原型链上 71 | 72 | ``` 73 | obj instanceof contructor 74 | ``` 75 | 76 | **6. getPrototypeOf** 77 | 78 | Object.getPrototypeOf(obj) 方法返回指定对象obj的原型(内部[[Prototype]]属性的值) 79 | 80 | ``` 81 | Object.getPrototypeOf(obj) 82 | ``` 83 | 84 | **7. setPrototypeOf** 85 | 86 | 设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null 87 | 88 | ``` 89 | var obj = {} 90 | var prototypeObj = {} 91 | Object.setPrototypeOf(obj, prototypeObj) 92 | console.log(obj.__proto__ === prototypeObj) // true 93 | ``` 94 | -------------------------------------------------------------------------------- /designPattern/命令模式.md: -------------------------------------------------------------------------------- 1 | ## 命令模式 2 | 3 | >![设计模式-命令模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F--%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F.png) [--百度百科](https://baike.baidu.com/item/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/7277118) 4 | 5 | 在命令的发布者和接收者之间,定义一个命令对象,命令对象暴露出一个统一的接口给命令的发布者,而命令的发布者不用去管接收者是如何执行命令的,做到命令发布者和接收者的解耦。 6 | 7 | 8 | 举一个如果页面中有3个按钮,给不同按钮添加不同功能的例子,代码如下: 9 | 10 | ``` 11 | 12 | 13 | 14 | 15 | cmd-demo 16 | 17 | 18 |
19 | 20 | 21 | 22 |
23 | 86 | 87 | 88 | ``` -------------------------------------------------------------------------------- /designPattern/工厂模式.md: -------------------------------------------------------------------------------- 1 | ## 工厂模式 2 | 3 | >工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,用工厂方法代替new操作的一种模式。 4 | 5 | ``` 6 | class Creator { 7 | create(name) { 8 | return new Animal(name) 9 | } 10 | } 11 | 12 | class Animal { 13 | constructor(name) { 14 | this.name = name 15 | } 16 | } 17 | 18 | var creator = new Creator() 19 | 20 | var duck = creator.create('Duck') 21 | console.log(duck.name) // Duck 22 | 23 | var chicken = creator.create('Chicken') 24 | console.log(chicken.name) // Chicken 25 | ``` 26 | 27 | **小结:** 28 | 29 | 1. 构造函数和创建者分离,对new操作进行封装 30 | 2. 符合开放封闭原则 31 | -------------------------------------------------------------------------------- /designPattern/状态模式.md: -------------------------------------------------------------------------------- 1 | ## 状态模式 2 | 3 | ![设计模式-状态模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 举一个关于开关控制电灯的例子,电灯只有一个开关,第一次按下打开弱光,第二次按下打开强光,第三次按下关闭。 6 | 7 | ``` 8 | 9 | 10 | 11 | 12 | 13 | state-demo 14 | 15 | 16 | 17 | 18 | 75 | 76 | 77 | 78 | ``` 79 | 如果这时候需要增加一个超强光,则只需增加一个超强光的类,并添加pressBtn方法,改变强光状态下,点击开关需要把状态更改为超强光,超强光状态下,点击开关把状态改为关闭即可,其他代码都不需要改动。 80 | 81 | ``` 82 | class StrongLightState { 83 | constructor(light) { 84 | this.light = light 85 | } 86 | pressBtn() { 87 | this.light.setState(this.light.superLightState) 88 | console.log('开启超强光') 89 | } 90 | } 91 | 92 | class SuperLightState { 93 | constructor(light) { 94 | this.light = light 95 | } 96 | pressBtn() { 97 | this.light.setState(this.light.offLightState) 98 | console.log('关闭电灯') 99 | } 100 | } 101 | 102 | class Light { 103 | constructor() { 104 | this.offLightState = new OffLightState(this) 105 | this.weekLightState = new WeekLightState(this) 106 | this.strongLightState = new StrongLightState(this) 107 | this.superLightState = new SuperLightState(this) 108 | this.currentState = null 109 | } 110 | setState(newState) { 111 | this.currentState = newState 112 | } 113 | init() { 114 | this.currentState = this.offLightState 115 | } 116 | } 117 | ``` 118 | **小结:** 119 | 1. 通过定义不同的状态类,根据状态的改变而改变对象的行为,二不必把大量的逻辑都写在被操作对象的类中,而且容易增加新的状态 120 | 2. 符合开放封闭原则 -------------------------------------------------------------------------------- /designPattern/策略模式.md: -------------------------------------------------------------------------------- 1 | ## 策略模式 2 | 3 | >定义一系列的算法,把它们一个个封装起来,并使它们可以替换 4 | 5 | 6 | ``` 7 | var fnA = function(val) { 8 | return val * 1 9 | } 10 | 11 | var fnB = function(val) { 12 | return val * 2 13 | } 14 | 15 | var fnC = function (val) { 16 | return val * 3 17 | } 18 | 19 | 20 | var calculate = function(fn, val) { 21 | return fn(val) 22 | } 23 | 24 | console.log(calculate(fnA, 100))// 100 25 | console.log(calculate(fnB, 100))// 200 26 | console.log(calculate(fnC, 100))// 300 27 | 28 | ``` -------------------------------------------------------------------------------- /designPattern/观察者模式.md: -------------------------------------------------------------------------------- 1 | ## 观察者模式(订阅-发布模式) 2 | 3 | >![设计模式-观察者模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 先实现一个简单的发布-订阅模式,代码如下: 6 | 7 | ``` 8 | class Event { 9 | constructor() { 10 | this.eventTypeObj = {} 11 | } 12 | on(eventType, fn) { 13 | if (!this.eventTypeObj[eventType]) { 14 | // 按照不同的订阅事件类型,存储不同的订阅回调 15 | this.eventTypeObj[eventType] = [] 16 | } 17 | this.eventTypeObj[eventType].push(fn) 18 | } 19 | emit() { 20 | // 可以理解为arguments借用shift方法 21 | var eventType = Array.prototype.shift.call(arguments) 22 | var eventList = this.eventTypeObj[eventType] 23 | for (var i = 0; i < eventList.length; i++) { 24 | eventList[i].apply(eventList[i], arguments) 25 | } 26 | } 27 | remove(eventType, fn) { 28 | // 如果使用remove方法,fn为函数名称,不能是匿名函数 29 | var eventTypeList = this.eventTypeObj[eventType] 30 | if (!eventTypeList) { 31 | // 如果没有被人订阅改事件,直接返回 32 | return false 33 | } 34 | if (!fn) { 35 | // 如果没有传入取消订阅的回调函数,则改订阅类型的事件全部取消 36 | eventTypeList && (eventTypeList.length = 0) 37 | } else { 38 | for (var i = 0; i < eventTypeList.length; i++) { 39 | if (eventTypeList[i] === fn) { 40 | eventTypeList.splice(i, 1) 41 | // 删除之后,i--保证下轮循环不会漏掉没有被遍历到的函数名 42 | i--; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | var handleFn = function(data) { 49 | console.log(data) 50 | } 51 | var event = new Event() 52 | event.on('click', handleFn) 53 | event.emit('click', '1') // 1 54 | event.remove('click', handleFn) 55 | event.emit('click', '2') // 不打印 56 | ``` 57 | 58 | 59 | 以上代码可以满足先订阅后发布,但是如果先发布消息,后订阅就不满足了。这时候我们可以稍微修改一下即可满足先发布后订阅,在发布消息时,把事件缓存起来,等有订阅者时再执行。代码如下: 60 | 61 | ``` 62 | class Event { 63 | constructor() { 64 | this.eventTypeObj = {} 65 | this.cacheObj = {} 66 | } 67 | on(eventType, fn) { 68 | if (!this.eventTypeObj[eventType]) { 69 | // 按照不同的订阅事件类型,存储不同的订阅回调 70 | this.eventTypeObj[eventType] = [] 71 | } 72 | this.eventTypeObj[eventType].push(fn) 73 | 74 | // 如果是先发布,则在订阅者订阅后,则根据发布后缓存的事件类型和参数,执行订阅者的回调 75 | if (this.cacheObj[eventType]) { 76 | var cacheList = this.cacheObj[eventType] 77 | for (var i = 0; i < cacheList.length; i++) { 78 | cacheList[i]() 79 | } 80 | } 81 | } 82 | emit() { 83 | // 可以理解为arguments借用shift方法 84 | var eventType = Array.prototype.shift.call(arguments) 85 | var args = arguments 86 | var that = this 87 | 88 | function cache() { 89 | if (that.eventTypeObj[eventType]) { 90 | var eventList = that.eventTypeObj[eventType] 91 | for (var i = 0; i < eventList.length; i++) { 92 | eventList[i].apply(eventList[i], args) 93 | } 94 | } 95 | } 96 | if (!this.cacheObj[eventType]) { 97 | this.cacheObj[eventType] = [] 98 | } 99 | 100 | // 如果先订阅,则直接订阅后发布 101 | cache(args) 102 | 103 | // 如果先发布后订阅,则把发布的事件类型与参数保存起来,等到有订阅后执行订阅 104 | this.cacheObj[eventType].push(cache) 105 | } 106 | } 107 | ``` 108 | 109 | **小结:** 110 | 111 | 1. 发布订阅模式可以使代码解耦,满足开放封闭原则 112 | 2. 当过多的使用发布订阅模式,如果订阅消息始终都没有触发,则订阅者一直保存在内存中。 -------------------------------------------------------------------------------- /designPattern/迭代器模式.md: -------------------------------------------------------------------------------- 1 | ## 迭代器模式 2 | 3 | >![设计模式-迭代器模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 直接上代码, 实现一个简单的迭代器 6 | 7 | ``` 8 | class Creater { 9 | constructor(list) { 10 | this.list = list 11 | } 12 | 13 | // 创建一个迭代器,也叫遍历器 14 | createIterator() { 15 | return new Iterator(this) 16 | } 17 | } 18 | 19 | class Iterator { 20 | constructor(creater) { 21 | this.list = creater.list 22 | this.index = 0 23 | } 24 | 25 | // 判断是否遍历完数据 26 | isDone() { 27 | if (this.index >= this.list.length) { 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | next() { 34 | return this.list[this.index++] 35 | } 36 | 37 | } 38 | 39 | var arr = [1, 2, 3, 4] 40 | 41 | var creater = new Creater(arr) 42 | var iterator = creater.createIterator() 43 | console.log(iterator.list) // [1, 2, 3, 4] 44 | while (!iterator.isDone()) { 45 | console.log(iterator.next()) 46 | // 1 47 | // 2 48 | // 3 49 | // 4 50 | } 51 | ``` 52 | 53 | **ES6中的迭代器:** 54 | 55 | **JavaScript中的有序数据集合包括:** 56 | 57 | * Array 58 | * Map 59 | * Set 60 | * String 61 | * typeArray 62 | * arguments 63 | * NodeList 64 | 65 | **注意:** Object不是有序数据集合 66 | 67 | 以上有序数据集合都部署了Symbol.iterator属性,属性值为一个函数,执行这个函数,返回一个迭代器,迭代器部署了next方法,调用迭代器的next方法可以按顺序访问子元素 68 | 69 | 以数组为例测试一下,在浏览器控制台中打印测试如下: 70 | ![Symbol-iterator](http://qiniu.sunzhaoye.com/Symbol-iterator.png) 71 | 72 | ``` 73 | var arr = [1, 2, 3, 4] 74 | 75 | var iterator = arr[Symbol.iterator]() 76 | 77 | console.log(iterator.next()) // {value: 1, done: false} 78 | console.log(iterator.next()) // {value: 2, done: false} 79 | console.log(iterator.next()) // {value: 3, done: false} 80 | console.log(iterator.next()) // {value: 4, done: false} 81 | console.log(iterator.next()) // {value: undefined, done: true} 82 | ``` 83 | 84 | **小结:** 85 | 86 | 1. JavaScript中的有序数据集合有Array,Map,Set,String,typeArray,arguments,NodeList,不包括Object 87 | 88 | 2. 任何部署了[Symbol.iterator]接口的数据都可以使用for...of循环遍历 89 | 3. 迭代器模式使目标对象和迭代器对象分离,符合开放封闭原则 90 | -------------------------------------------------------------------------------- /designPattern/适配器模式.md: -------------------------------------------------------------------------------- 1 | ## 适配器模式 2 | 3 | >![设计模式-适配器模式](http://qiniu.sunzhaoye.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F.png) 4 | 5 | 举一个书中渲染地图的例子 6 | 7 | ``` 8 | class GooleMap { 9 | show() { 10 | console.log('渲染谷歌地图') 11 | } 12 | } 13 | 14 | class BaiduMap { 15 | show() { 16 | console.log('渲染百度地图') 17 | } 18 | } 19 | 20 | function render(map) { 21 | if (map.show instanceof Function) { 22 | map.show() 23 | } 24 | } 25 | 26 | render(new GooleMap()) // 渲染谷歌地图 27 | render(new BaiduMap()) // 渲染百度地图 28 | ``` 29 | 但是假如BaiduMap类的原型方法不叫show,而是叫display,这时候就可以使用适配器模式了,因为我们不能轻易的改变第三方的内容。在BaiduMap的基础上封装一层,对外暴露show方法。 30 | 31 | ``` 32 | class GooleMap { 33 | show() { 34 | console.log('渲染谷歌地图') 35 | } 36 | } 37 | 38 | class BaiduMap { 39 | display() { 40 | console.log('渲染百度地图') 41 | } 42 | } 43 | 44 | 45 | // 定义适配器类, 对BaiduMap类进行封装 46 | class BaiduMapAdapter { 47 | show() { 48 | var baiduMap = new BaiduMap() 49 | return baiduMap.display() 50 | } 51 | } 52 | 53 | function render(map) { 54 | if (map.show instanceof Function) { 55 | map.show() 56 | } 57 | } 58 | 59 | render(new GooleMap()) // 渲染谷歌地图 60 | render(new BaiduMapAdapter()) // 渲染百度地图 61 | ``` 62 | 63 | **小结:** 64 | 65 | 1. 适配器模式主要解决两个接口之间不匹配的问题,不会改变原有的接口,而是由一个对象对另一个对象的包装。 66 | 2. 适配器模式符合开放封闭原则 -------------------------------------------------------------------------------- /html/HTML文档中meta标签常用属性及其作用.md: -------------------------------------------------------------------------------- 1 | 以前没怎么太注意过meta标签的作用,只是简单了解一些常用属性,现在结合个人了解的进行记录与总结: 2 | 3 | # 元数据 4 | 5 | >首先需要了解一下**元数据(metadata)元素**的概念,用来构建HTML文档的基本结构,以及就如何处理文档向浏览器提供信息和指示,它们本身不是文档内容,但提供了关于后面文档内容的信息。——出自《html5权威指南》 6 | 7 | 如title、base、meta都是元数据元素。本文主要介绍。 8 | 9 | ## 元素 10 | [](http://www.w3school.com.cn/tags/tag_meta.asp) 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。--W3school 11 | 12 | 元素可以定义文档的各种元数据,提供各种文档信息,通俗点说就是可以理解为提供了关于网站的各种信息。html文档中可以包含多个元素,每个元素只能用于一种用途,如果想定义多个文档信息,则需要在head标签中添加多个meta元素。 13 | 14 | | 元素 | meta | 15 | | -------- | --------| 16 | | 父元素 | head | 17 | | 属性 | http-equiv、name、content、charset | 18 | | HTML5中的变化 |1. charset为HTML5中新增的,用来声明字符编码;2. http-equiv属性在HTML4中有很多值,在HTML5中只有refresh、default-style、content-type可用| 19 | 20 | ### 具体用途 21 | 元素出去charset属性外,都是http-equiv属性或name属性结合content来使用 22 | 23 | #### 1. 指定名/值对定义元数据 24 | name属性与content属性结合使用, name用来表示元数据的类型,表示当前标签的具体作用;content属性用来提供值。 25 | ``` 26 | 27 | ``` 28 | 例如: 29 | ``` 30 | 31 | 32 | 33 | demo 34 | 35 | 36 | 37 | 38 | 39 |
welcome
40 | 41 | 42 | ``` 43 | 44 | | 元数据名称(name的值)| 说明 | 45 | | - | --| 46 | | application name |当前页所属Web应用系统的名称 | 47 | | keywords | 描述网站内容的关键词,以逗号隔开,用于SEO搜索 | 48 | | description | 当前页的说明 | 49 | | author | 当前页的作者名 | 50 | | copyright | 版权信息 | 51 | | renderer | renderer是为双核浏览器准备的,用于指定双核浏览器默认以何种方式渲染页面| 52 | 53 | **renderer** 54 | ``` 55 | //默认webkit内核 56 | //默认IE兼容模式 57 | //默认IE标准模式 58 | 59 | 60 | ``` 61 | 62 | 63 | 64 | #### 2. 声明字符编码 65 | charset属性为HTML5新增的属性,用于声明字符编码,以下两种写法效果一样 66 | ``` 67 | //HTML5 68 | ``` 69 | ``` 70 | //旧的HTML 71 | ``` 72 | #### 3. 模拟http标头字段 73 | **http-equiv**属性与**content**属性结合使用, **http-equiv**属性为指定所要模拟的标头字段的名称,**content**属性用来提供值。 74 | ``` 75 | 76 | ``` 77 | **content-Type** 声明网页字符编码: 78 | ``` 79 | 80 | ``` 81 | **refresh** 指定一个时间间隔(以秒为单位),在此时间过去之后从服务器重新载入当前页面,也可以另外指定一个页面. 82 | ``` 83 | //2秒后在当前页跳转到百度 84 | ``` 85 | **X-UA-Compatible** 浏览器采取何种版本渲染当前页面 86 | ``` 87 | //指定IE和Chrome使用最新版本渲染当前页面 88 | 89 | ``` 90 | **expires** 用于设定网页的到期时间,过期后网页必须到服务器上重新传输 91 | ``` 92 | 93 | ``` 94 | **catch-control** 用于指定所有缓存机制在整个请求/响应链中必须服从的指令 95 | ``` 96 | // 97 | ``` 98 | content有以下值(百度百科): 99 | 100 | |content的值|说明| 101 | | -- | -- | 102 | | public |所有内容都将被缓存(客户端和代理服务器都可缓存)| 103 | | private |内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)| 104 | | no-cache |必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。| 105 | | no-store |所有内容都不会被缓存到缓存或 Internet 临时文件中| 106 | | must-revalidation/proxy-revalidation |如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证| 107 | | max-age=xxx (xxx is numeric) |缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高| 108 | 109 | 由于能力有限,结合demo总结以上元素的常用属性及其功能,可能有些纰漏,还望大家多多包含和指正,文章参考了《HTML权威指南》、[W3school](http://www.w3school.com.cn/tags/tag_meta.asp) 及相关博客[HTML meta标签总结与属性的使用介绍](http://www.imooc.com/article/4475)、[常用meta整理](https://segmentfault.com/a/1190000002407912),在我能力外没有总结的可参考以上博客。后续有时间还会继续学习和更新此文章,希望能对大家有所帮助 110 | -------------------------------------------------------------------------------- /html/《Webkit技术内幕》之页面渲染过程.md: -------------------------------------------------------------------------------- 1 | > 最近拜读了传说中的《Webkit技术内幕》一书,有很大收获,尤其是对页面渲染有了较深的认识。由于功力有限,而且书中设计到较多的底层也无法理解,所以本文主要整理和分享一下自己理解的基于Webkit内核浏览器的页面渲染的整体过程,不做深入介绍(也做不了深入介绍)。 2 | 3 | # 浏览器内核 4 | 5 | > 浏览器之所以能呈现出更重页面,正是由于浏览器内核的存在,也被称为渲染引擎,主要作用就是,也就是能把HTML/CSS/JavaScript文本其相关的资源转化转化成可视化可交互的图像页面,这个过程就是渲染。 6 | 7 | # 四大主流内核 8 | 9 | 内核| 浏览器 | 内核识别码 10 | --------- | -------------| ------------- 11 | Trident | IE、360 | -ms 12 | --------- | -------------| ------------- 13 | Gecko | Firefox(火狐) | -moz 14 | --------- | -------------| ------------- 15 | presto | Opera(欧朋) | -o 16 | --------- | -------------| ------------- 17 | Webkit | Chrome(谷歌) safari(苹果)| -webkit 18 | 19 | 20 | # Webkit架构 21 | 22 | 本文主要是基于Webkit内核,所以先放一张整理Webkit架构图(并非全部模块, 主要包含主要模块),大致介绍一下Webkit的架构 23 | 24 | ![webkit架构](http://ovqwwz784.bkt.clouddn.com/Webkit%E6%9E%B6%E6%9E%84.png) 25 | 26 | 自下而上主要分为三层: 27 | 28 | * 1.操作系统层 29 | * 2.Webkit渲染过程中依赖的很多第三方模块 30 | * 3.Webkit层,主要包含webCore、渲染引擎层、嵌入式接口层,绑定层 31 | 32 | ### 1.操作系统层 33 | 34 | 由Webkit可以运行在不同的操作系统上,浏览器也可以运行在不同的操作系统上,另外与对磁盘和内存的操作都需要操作系统来操作,所以需要操作系统支持。说白了就是一切需要对硬件操作的程序都需要操作系统的支持。 35 | 36 | ### 2.第三方模块层 37 | 38 | 资源的获取、页面的渲染等需要依赖第三方库来完成,网络库、图形库、视频库等都是Webkit之所以能工作的基础,Webkit来根据需要来使用相应的库。 39 | 40 | ### 3. WebKitCore 41 | 42 | WebKitCore部分是所有浏览器共享的部分,是渲染网页的基础,包括HTML解释器、CSS解释器、SVG、DOM、渲染树等 43 | 44 | * 1.HTML解释器 45 | 46 | > 解释HTML文本的解释器,把html文档解析成DOM(文档对象模型)树,表示整个html文档 47 | 48 | * 2.CSS解释器 49 | 50 | > 问DOM树中各个元素对象计算出样式信息,后计算后面的网页布局做基础 51 | 52 | * 3.布局 53 | 54 | > 把DOM树和解析后的CSS树的信息结合起来,形成一个包含页面所有元素和样式的信息的一个内部表示模型 55 | 56 | * 4.绘图 57 | 58 | >使用第三方依赖的库如2D/3D图形库等将布局计算后的节点绘制成图像 59 | 60 | ### 4. JavaScript引擎 61 | 62 | 对于JavaScript引擎有必要说一下,JavaScript引擎就是将JavaScript代码解析处理并运行的环境,负责整个JavaScript程序的编译及执行过程。 63 | 64 | 1.webkit默认的引擎是JavaScriptCore引擎,对于不同浏览器对于引擎的实现JavaScript引擎的实现也不一样,比如Chrome浏览器是基于V8引擎等。 65 | 66 | 2.另外在解析/语法分析,构建出"抽象语法树"之后,会将"抽象语法树"进行编译,转化为一组机器指令,拿一个例子来说 67 | 68 | ``` 69 | var a = 1 70 | ``` 71 | JavaScript会创建一个变量a,并分配内存,将1这个值存储在a的变量中。 72 | 73 | 74 | 3.JavaScript可以修改网页内容,也就是修改DOM和和CSS样式,事实上,也正是javaScript代码通过DOM和CSSOM暴露出的接口来进行修改,从而改变页面渲染的效果 75 | 76 | ### 5. 绑定层和嵌入式接口层 77 | 78 | 绑定层和嵌入式接口层最主要与Webkit项目的移植有关,就比如基于linux内核的CentOS、Ubuntu等系统一样。嵌入式接口层主要提供给浏览器调用,不同浏览器厂商会基Webkit以及对外暴露的接口实现自己的功能。 79 | 80 | # 页面渲染过程 81 | 82 | **从输入url开始整体介绍一下页面的渲染流程,然后具体步骤再详细加以说明** 83 | 84 | * 1.用户输入url按下回车后,浏览器开启一个进程对url进行分析,如果是http协议,按照web方式处理 85 | * 2.根据域名进行DNS解析,解析之后发送第二次get请求,进行HTTP协议会话,以获取资源 86 | * 3.此时进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等web服务器; 87 | * 4.继续进入后端应用程序,如PHP、Java等找到对应请求处理,最后由web服务器传送给浏览器资源 88 | * 5.因为资源的位置以URL(统一资源定位符)来区分,发送请求时,如果本地有缓存,发送请求的同时会带上缓存的相关信息,与服务器资源作比较,比如更新时间等,服务器如果没有更新则返回304,直接使用缓存,否则向服务器请求资源。 89 | * 5.浏览器开始下载HTML文档 90 | * 6.浏览器内核开始解析文档(整个html文档就是一大串字符串),构建DOM树,解析成DOM树的过程中,如果遇到JavaScript代码,则交给JavaScript引擎来执行,等到DOM树构建完成后触发DOMContentLoaded事件 91 | * 7.解析CSS,构建CSS树,构建CSSOM(在浏览器控制台输入document.styleSheets按下回车就可以看到styleSheetList的集合了) 92 | * 8.CSS解析完成之后,在DOM树的基础上附加解释后的样式信息,形成RenderObject树,在RenderObject节点创建的同时,webkit会根据网页层次构建出RenderLayer树,同时构建出一个虚拟的绘图上下文(与平台无关的抽象类),最后根据绘图上下文(需要依赖2D/3D库)进行绘制,最终也就是用户看到的可交互的页面。 93 | 94 | # 页面渲染细节 95 | 96 | 上面介绍了页面从输入url发送请求后到页面渲染的整体流程,下面补充一下在这整个过程中的一些细节知识点,方面更好的理解页面的渲染过程 97 | 98 | **1. 首先理解页面是分层次的,比如说如下代码片段代码** 99 | 100 | ``` 101 | 102 | 103 | 104 | 105 | Document 106 | 107 | 108 |
109 |

aaa

110 | 111 |
112 | 113 | 114 | ``` 115 | 116 | 当网页构建层次的时候,html元素为根层,此时创建一个层,html元素的所有子节点、子节点的子节点,依次类推,如dody,div,p为普通元素,并不会创建新的层次接口,video元素需要进行创建新层来进行视频处理和渲染。 117 | 118 | 如下情况,都需要建立新的RenderLayer节点 119 | 120 | ![建立新的RenderLayer节点](http://ovqwwz784.bkt.clouddn.com/%E9%9C%80%E8%A6%81%E5%BB%BA%E7%AB%8BReanderLayer%E8%8A%82%E7%82%B9%E7%9A%84%E6%83%85%E5%86%B5.png) 121 | 122 | **备注**: 图片直接截取于《Webkit技术内幕》 123 | 124 | **2.从资源字节流到构建成DOM树** 125 | 126 | ![资源字节流到构建成DOM树](http://ovqwwz784.bkt.clouddn.com/%E8%B5%84%E6%BA%90%E5%AD%97%E8%8A%82%E6%B5%81%E5%88%B0DOM%E6%A0%91.png) 127 | 128 | **备注:** 图片直接截取于《Webkit技术内幕》 129 | 130 | * 1.分词/词法分析,从字节流到字符流流(串),是分段的词法解释器会将字符串解释成标记(相当于字典中的词语) 131 | 132 | 如 var a = 1; var、a、=、1 、;。空格是否会被当作词法单元,取决于空格在 133 | 这门语言中是否具有意义。 134 | 135 | * 2.解析/语法分析,基于词法解释器生成的新标记,构建成“抽象语法树”,解析器尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记 136 | 137 | **3.HTML文档对应的DOM树的对应关系** 138 | 139 | ![HTML文档对应的DOM树的对应关系](http://ovqwwz784.bkt.clouddn.com/HTML%E7%BD%91%E9%A1%B5%E5%88%B0DOM%E6%A0%91.png) 140 | 141 | 142 | **4.DOM树和RenderObject树之间的对应关系** 143 | 144 | ![DOM树和RenderObject树](http://ovqwwz784.bkt.clouddn.com/DOM%E6%A0%91%E8%8A%82%E7%82%B9%E5%92%8CRenderObject%E6%A0%91%E8%8A%82%E7%82%B9%E7%9A%84%E5%AF%B9%E5%BA%94%E5%85%B3%E7%B3%BB.png) 145 | 146 | **备注:** 图片直接截取于《Webkit技术内幕》 147 | 148 | **5. RenderObject树与RenderLayer树之间的对应关系** 149 | 150 | 多对一的关系,RenderObject多个节点可以对应RenderLayer的一个层次结构 151 | 152 | ![RenderObject树和RenderLayer](http://ovqwwz784.bkt.clouddn.com/RenderObject%E6%A0%91%E8%8A%82%E7%82%B9%E5%92%8CRenderLayer%E6%A0%91%E8%8A%82%E7%82%B9%E7%9A%84%E5%AF%B9%E5%BA%94%E5%85%B3%E7%B3%BB.png) 153 | 154 | **备注:** 图片直接截取于《Webkit技术内幕》 155 | 156 | 以上文章就是整理的页面渲染整体流程,很多细节没有具体讲,比如解析html文档时,利用栈来处理、绘制3D图形的GPU加速等、由于能力有限不敢多讲,也难讲清楚,这些还是去看大神们具体的书比较,感兴趣的小伙伴,《WebKit技术内幕》了解一下 157 | 158 | 本文主要参考资料: 159 | 160 | [WebKit技术内幕](https://book.douban.com/subject/25910556/) 161 | [Chrome 渲染优化 - 层模型](http://blog.jobbole.com/42967/) 162 | [前端文摘:深入解析浏览器的幕后工作原理](http://www.cnblogs.com/lhb25/p/how-browsers-work.html) -------------------------------------------------------------------------------- /html/什么是HTML文档.md: -------------------------------------------------------------------------------- 1 | # HTML文档 2 | >HTML指的是超文本标记语言 (Hyper Text Markup Language),HTML文档就是我们常说的网页,一个标准的HTML文档由**文档元素**和**元数据元素**组成,二者用来创建HTML文档以及其内容。 3 | 4 | 顺便说一下什么是元数据元素: 用来构建HTML文档的基本结构,以及就如何处理文档向浏览器提供信息和指示,它们本身不是文档内容,但提供了关于后面文档内容的信息。包含在head内,如title、base、meta等都是元数据元素。本文不做重点介绍。 5 | 6 | ## 文档元素 7 | 文档元素一共有四个**DOCTYPE**、**html**、**head**、**body** 8 | 9 | ### DOCTYPE 10 | 11 | 每一个HTML文档都必须由DOCTYPE元素开头,告诉浏览器要处理的是HTML文档,在HTML5中DOCTYPE 声明变得非常简单而且唯一,不用写版本号浏览器也能识别这是HTML5文档,因为和之前的HTML版本有所差异 12 | //HTML5声明 13 | 14 | 在 HTML 4.01 中, 声明引用 DTD,因为 HTML 4.01 基于 SGML。DTD 规定了标记语言的规则,这样浏览器才能正确地呈现内容。HTML5 不基于 SGML,所以不需要引用 DTD。--[W3school](http://www.w3school.com.cn/tags/tag_doctype.asp) 15 | 16 | **HTML4中声明如下:** 17 | 18 | HTML 4.01 Strict(不允许使用框架集): 19 | 20 | 21 | HTML 4.01 Frameset(允许使用框架集): 22 | 23 | 24 | ### html 25 | html表示根元素,表示HTML文档的开始,必须需要的元素,html具有如下属性: 26 | 27 | |属性|值|功能| 28 | | -- | -- | -- | 29 | |manifest|url|定义一个url,描述文档的缓存信息| 30 | | xmlns | "http://www.w3.org/1999/xhtml" |定义 XML namespace 属性(其实这个我也没用过不懂啊),感兴趣的大神自己研究下吧| 31 | 32 | ### head 33 | >包含文档的元数据,向浏览器提供文档内容和标记的信息,还包括脚本和对外资源的引用,如引入外联.css文件、js文件等。 34 | 35 | |包含元素|是否必须|功能| 36 | | -- | -- | -- | 37 | |title|是|必须有一个title元素,定义网站的标题| 38 | |base |否|设置一个基准URL,让HTML文档中的相对链接在此基础上解析| 39 | |meta |否|定义各种文档元数据,可见我的上一篇文章[常用属性总结](https://segmentfault.com/a/1190000010342600)| 40 | |style |否|1. 指定样式类型; 2. 指定样式作用范围; 3. 指定样式使用的媒体类型| 41 | |link |否|1. 载入外部样式表; 2. 为页面定义网站标志; 3. 预先获取关联资源 | 42 | |script |否|1. 定义文档内嵌脚本; 2. 载入外部脚本| 43 | |noscript |否|可以向禁用或不知JavaScript的浏览器展示一些内容| 44 | 45 | - **介绍:** 46 | 如果在浏览器地址为"https://segmentfault.com/demo/index.html"中载入一个文档,代码如下: 47 | ``` 48 | 49 | 50 | 51 | 52 | 53 | 54 | 跳转 55 | 56 | 57 | ``` 58 | 正常点击a链接,浏览器将从"https://segmentfault.com/page2.html"中加载文档 59 | **如果不包含元素**,浏览器将从"https://segmentfault.com/demo/index.html"中加载文档地址 60 | 61 | - **style** 62 | 63 | style元素拥有局部属性:type、scoped、media,其对应作用如下: 64 | 65 | **1.指定内嵌样式,type属性是高速浏览器文档所定义的类型、这个值只有一种text/css** 66 | ``` 67 | 74 | ``` 75 | **2.指定样式范围,scoped属性的作用为style元素内定义的样式只作用于该元素的父级及其所有兄弟元素** 76 | 77 | 78 | **3.media属性表明文档在指定的设备下显示其定义的样式** 79 | ``` 80 | 87 | ``` 88 | media属性所有的设备值如下表(--HTML5权威指南),很多在工作中几乎都用不上,列出来了解一下及以备查阅: 89 | 90 | |设备(media的值)|说明| 91 | |--|--| 92 | |all|将样式用于所有设备(默认值)| 93 | |aural|将样式用于语音合成器| 94 | |braille|将样式用于盲文设备| 95 | |handheld|将样式用于手持设备| 96 | |projection|讲样式用于投影机| 97 | |print|将样式用于打印预览和打印页面时| 98 | |screen|将样式用于计算机显示屏幕| 99 | |tty|将样式用于电传打印机之类的等宽设备| 100 | |tv|将样式用于电视机| 101 | 102 | media不只能规定设备,还能定义更具体的使用条件,举例如下: 103 | ``` 104 | 112 | ``` 113 | ``` 114 | 122 | ``` 123 | 除了用AND来定义具体条件,还可以使用NOT和OR(','表示),另外还有其他供media使用的特性就不列举了,大家可以自己去百度了,再补充一个吧,device-width,这些特性都可以结合max-/mix-来使用 124 | 125 | - **link** 126 | 127 | 拥有的局部属性如下表: 128 | 129 | |属性|说明| 130 | |--|--| 131 | |href|指定引入资源的URL| 132 | |hreflang|说明关联资源使用的语言| 133 | |media|说明关联内容用于哪种设备| 134 | |rel|说明文档与关联资源的关系类型,属性值决定了浏览器如何解析link元素| 135 | |sizes|HTML5中新增,指定网站图标大小| 136 | |type|指定所关联资源的[MIME](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types)类型,如text/css| 137 | 138 | rel属性常用值[rel值列表](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Link_types)及其表示的link元素功能 139 | 140 | |属性|说明| 141 | |--|--| 142 | |stylesheet|载入外部样式表| 143 | |icon|指定图标资源| 144 | |prefetch|预先获取一个资源| 145 | 146 | **1.引入外联样式表** 147 | 148 | 149 | **2.添加网站图标** 150 | 151 | //href属性值为图片路径 152 | **3.预先获取关联的资源** 153 | 154 | rel属性值设置为prefetch,可以预先加载demo.html,如果页面存在链接如下,为需要demo.html页面的操作做好准备 155 | ``` 156 | demo 157 | ``` 158 | - **script** 159 | 160 | >script元素可以定义页面内嵌脚本、引入外部文件脚本并通过script本身局部属性值设定加载脚本的各种方式 161 | 162 | script的常用局部属性及其说明如下表: 163 | 164 | |属性|说明| 165 | |--|--| 166 | |type|表示引用或定义的脚本类型,如果是javascript脚本,此属性可以省略,省略type属性时浏览器默认为使用JavaScript脚本 167 | |src|指定加载外部脚本资源的URL| 168 | |defer|设定脚本执行的方式为延迟执行,告诉浏览器等页面载入和解析完毕才能执行此脚本,只能与src属性一起使用| 169 | |async|设定脚本执行的方式为异步执行,只能与src属性一起使用| 170 | |charset|说明外部脚本资源的字符编码,只能与src属性一起使用| 171 | 172 | **1.定义文档内嵌脚本** 173 | ``` 174 | 179 | ``` 180 | 一般情况下script元素应该放在文档最后,等页面全部加载完成后才去执行,保证脚本文件内可以获取到当前页面的全部内容。 181 | 182 | 如果script元素在head内,如果是当前内嵌脚本,则可以添加window.onload来告诉浏览器当所有页面全部加载完成才去执行。 183 | 184 | **2.载入外部脚本** 185 | 186 | 187 | **3.使用defer属性延迟加载外部脚本** 188 | 189 | 190 | 如果在head中使用script元素,defer属性将会在HTML文档所有元素都解析完毕之后才加载和执行。 191 | 192 | *注意*:defer属性只能处理外部脚本,对内嵌脚本无效。 193 | 194 | **4.使用async属性异步执行脚本** 195 | 196 | 197 | 浏览器在解析script元素时的默认行为是加载和执行脚本的时候暂停处理页面,各script元素按顺序同步执行。 198 | 199 | 使用async属性可以使外部脚本在加载HTML时异步执行,如何使用需结合具体产品功能需求,不过带来的影响是,页面中的脚本不能再按次序同步执行,所以如果当前脚本中与其他脚本有关联,则不适合使用async属性 200 | 201 | ### body 202 | 203 | HTML文档的元数据和文档信息都包含在head内,文档内容包含在body内,body紧跟在head后面,具体不在赘述! 204 | 205 | 本文很多概念和属性表格结合《HTML5权威指南》、W3school、MDN,由于博主能力有限,很多概念直接拿过来引用,并附上相关链接,以保证其正确性,对HTML文档的相关知识作一个简单的总结,希望能帮助到需要的人,同时也方便自己后续查阅。 -------------------------------------------------------------------------------- /javaScript/JavaScript中Array的sort方法总结.md: -------------------------------------------------------------------------------- 1 | ## 使用方式 2 | 3 | | 参数 | 描述 | 返回值 4 | | :--------| :--------| :--------| 5 | | sortby | 可选,规定排序顺序,必须是函数 |对数组的引用,数组在原数组上进行排序,不生成副本| 6 | 7 | ## 说明: 8 | - sort方法内如果不传参数, 则是比较数组内元素的[ASCII](https://baike.baidu.com/item/ASCII/309296?fr=aladdin)字符编码的值,即每次都会调用元素的toString()转换成字符串,按ASCII字符编码值进行比较 9 | - 若想按照其他方式进行排序,则需要传入比较函数(sort内的参数),比较函数需要返回值,***当函数返回值为1的时候就交换两个数组项的顺序,否则就不交换*** 10 | 11 | ### 按照ASCII编码值进行比较 12 | ``` 13 | var arr = ['apple', 'banana', 'pear', 'apricot', 'grape', 'JJJ']; 14 | arr.sort(); 15 | 16 | // arr => ["JJJ", "apple", "apricot", "banana", "grape", "pear"] 17 | 18 | 因为J的ASCII值比a的小,所以J排在apple的前面 19 | ``` 20 | 21 | 22 | 23 | ### 升序排列: 24 | ``` 25 | var arr = [10, 2, 9, 3, 24, 6]; 26 | arr.sort(function(a, b) { 27 | return a-b; 28 | }); 29 | //arr => [2, 3, 6, 9, 10, 24] 30 | ``` 31 | 升序排列可以另写成: 32 | ``` 33 | arr.sort(function(a, b) { 34 | if (ab) { 37 | return 1;// a>b, 1表示需要调换位置 38 | } else { 39 | return 0; // 不调换位置 40 | } 41 | }); 42 | ``` 43 | ### 降序排列: 44 | ``` 45 | var arr = [10, 2, 9, 3, 24, 6]; 46 | arr.sort(function(a, b) { 47 | return b-a; 48 | }); 49 | //arr => [24, 10, 9, 6, 3, 2] 50 | ``` 51 | 降序排列可以另写成: 52 | ``` 53 | arr.sort(function(a, b) { 54 | if (ab) { 57 | return -1 // 表示不需要调换a、 b位置 58 | } else { 59 | return 0 // 表示不需要调换a、 b位置 60 | } 61 | }); 62 | ``` -------------------------------------------------------------------------------- /javaScript/JavaScript中delete操作符.md: -------------------------------------------------------------------------------- 1 | >在JavaScript中,delete操作符用的比较少,但是还是比较重要的,我本人面试的时候就遇到过关于delete的问题,下面总结一下delete的具体用法。 2 | 3 | # 作用: 4 | 5 | delete 操作符用于删除对象的某个属性。 6 | 7 | # 语法: 8 | 直接使用delete操作符 9 | 10 | delete object.property 或 delete object['property'] 11 | 12 | 例如: 13 | ``` 14 | var person = { 15 | name: 'abc' 16 | age: 18 17 | } 18 | 19 | delete person.name 20 | 21 | console.log(person) // {age: 18} 22 | ``` 23 | # 返回值: 24 | 25 | delete操作符具有返回值,返回值为布尔值,对于所有情况都是true,即使是删除不存在的属性也会返回true,还是如上代码,不防打印一下返回值看看 26 | 27 | console.log(delete person.name) //true 28 | console.log(delete person.job) //即使删除对象不存在的属性依然返回true 29 | 30 | 31 | 但是也有例外的情况(返回false),如果属性是**不可配置属性**(对于不可配置属性的概念,可以参考[Object. defineProperty](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty),我第一次听说这个概念的时候也有点蒙圈), 在非严格模式下,返回false,在严格模式下则会抛出语法错误的异常。 32 | 33 | # 具体使用 34 | ## 1. 对象属性不存在 35 | 如上所述,如果删除对象不存在的属性,delete无效,但是返回值仍然为true 36 | 37 | ## 2. 原型链上存在该同名属性 38 | 如果delete操作符删除属性成功,则该属性将永远不存在,但是该对象原型链上存在该同名属性,则该对象会从原型链上继承该同名属性。**但和内存并无关联,内存管理并不是delete操作符可以操作的,而且一点关系也没有。[内存管理](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management)推荐MDN这篇文章** 39 | ``` 40 | // 构造函数 41 | function Person() { 42 | this.name = "张三", 43 | this.job = "工程师" 44 | } 45 | 46 | Person.prototype.name = "李四" 47 | // 创建实例对象 48 | var p = new Person(); 49 | // 只删除p实例的name属性 50 | delete p.name; 51 | console.log(p) => // 通过打印如下图,name属性成功删除 52 | ``` 53 | ![打印字段图片](https://github.com/sunzhaoye/blog/blob/master/assets/delete.png) 54 | 55 | ``` 56 | 接下来看: 57 | console.log(p.name) => // '张三' 依然可以访问到 58 | ``` 59 | 所以可以看出delete操作只会在自身的属性上起作用,这里能console出来'张三',是作用域链的原因,当实例本身无该属性的时候,就会去找它的protype身上有无该同名属性。 60 | ## 3. 使用var声明 61 | 62 | 使用var声明的属性(包括函数),不能从全局作用域或函数作用域中删除 63 | 64 | 在全局作用域中声明属性: 65 | ``` 66 | // 声明属性 67 | var a = 1; // 等同于window.a 68 | delete a // 严格模式下抛出语法异常 SyntaxError 69 | console.log(a); // 1 非严格模式下 70 | console.log(delete a); // 非严格模式下false 71 | ``` 72 | ``` 73 | // 声明函数 74 | var fn = function () { 75 | console.log(1); 76 | } 77 | delete fn // 严格模式下抛出语法异常 SyntaxError 78 | fn() // 1 非严格模式下delete失效, 函数依然存在 79 | 80 | // 另外, 除字面量定义外,匿名函数定义函数效果也是一样 81 | ``` 82 | 在函数作用域中声明属性(效果和在全局作用域中一样): 83 | 84 | ``` 85 | // 局部作用域声明属性 86 | funtion fn() { 87 | var a = 1; 88 | delete a; // 严格模式下抛出语法异常 SyntaxError 89 | console.log(a); // 1 90 | console.log(delete a); // 非严格模式下false 91 | } 92 | 93 | fn(); 94 | ``` 95 | ``` 96 | // 局部作用域声明函数 97 | var fn = function() { 98 | var fn2 = function() { 99 | console.log(1); 100 | }; 101 | delete fn2 // 严格模式下抛出语法异常 SyntaxError 102 | console.log(delete fn2); // false 非严格模式下 103 | fn2(); // 1 104 | } 105 | fn(); 106 | ``` 107 | 另外, 需要注意的是,在对象中定义的函数是可以删除的,和属性一样,比如 108 | 109 | ``` 110 | var person = { 111 | name: '张三', 112 | showName: function () { 113 | console.log(this.name); 114 | } 115 | } 116 | delete person.showName 117 | console.log(person.showName) // undefined 118 | ``` 119 | ## 4. let和const声明的属性 120 | 121 | 任何用let或const声明的属性不能够从它被声明的作用域中删除,我试了下,和var的效果是一样的,目前只能理解到这,如果知道的大神请指点下 122 | ## 5. 不可设置的属性 123 | ### Math, Array, Object等内置对象的属性不可删除 124 | ``` 125 | console.log(Array.length); // 1 126 | delete Array.length 127 | console.log(Array.from); 0 128 | ``` 129 | ``` 130 | delete Array.prototype //严格模式下抛出异常 131 | console.log(Array.prototype) // 非严格模式下,prototype依然存在, 可以自己试试了,自己动手,丰衣足食 132 | console.log(Array.prototype.join); // 非严格模式下,join方法依然存在 133 | ``` 134 | 需要注意的是,只是这些内置对象的属性不可删除,内置对象的方法是可以删除的,比如: 135 | 136 | ``` 137 | console.log(Array.forEach); // 内置函数 138 | delete Array.forEach // 不用区分严格模式与否 139 | console.log(Array.forEach); // undefined 140 | ``` 141 | ### Object.defineProperty()设置为不可设置的属性,不可删除 142 | 143 | ``` 144 | var person = {}; 145 | Object.defineProperty(person, 'name', { 146 | value: '张三', 147 | configurable: false 148 | }) 149 | delete person.name // 严格模式下,抛出异常 150 | console.log(person.name); // 张三 151 | console.log(delete person.name); // 非严格模式false 152 | ``` 153 | 154 | **var, let以及const创建的不可设置的属性不能被delete操作删除** 155 | ``` 156 | var a = 'abc'; // 属于window 等同于window.a 157 | var aVal = Object.getOwnPropertyDescriptor(window, 'a'); 158 | console.log(aVal); 159 | // aVal输入如下 160 | // { 161 | // value: 2, 162 | // writable: true, 163 | // enumerable: true, 164 | // configurable: false // 由于是var声明的属性,所以为false 165 | // } 166 | ``` 167 | ``` 168 | var a = 'abc'; // 属于window 等同于window.a 169 | delete a // 严格模式下抛出异常 170 | var aVal = Object.getOwnPropertyDescriptor(window, 'a'); 171 | console.log(aVal); 172 | console.log(delete a); //false 173 | // 非严格模式下,aVal输入如下 174 | // { 175 | // value: 2, 176 | // writable: true, 177 | // enumerable: true, 178 | // configurable: false // 由于是var声明的属性,所以为false 179 | // } 180 | ``` 181 | 如果开始没有阅读,再去看看吧[Object. defineProperty](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)。如果了解,可以直接略过。 182 | ## 6. 删除数组 183 | 使用delete操作符删除数组总某项元素时,被删除的元素会从该数组中删除,但是数组的length并不会改变 184 | 185 | ``` 186 | var arr = [1, 2, 3]; 187 | delete arr[1] 188 | console.log(arr); // [1, undefined × 1, 2] 189 | console.log(delete arr[1]) // true 190 | console.log(arr[1]); // undefined 191 | ``` 192 | 但是这里存在一个问题 193 | console.log(1 in arr) // false 194 | 195 | 所以如果想把数组中某一项赋值成undefined时,不应该使用delete操作符,而是直接使用下边赋值 196 | arr[1] = undefined; 197 | // 这样就可以解决上面的问题 198 | console.log(1 in arr) // true 199 | 200 | 今天花了点时间,把关于delete的问题总结了一下, 方便自己查阅, 也希望能帮助需要的人, 欢迎大神指点与补充,如果你阅读完,感觉也还有收藏价值,那还等什么,赶快收藏吧! 201 | 202 | -------------------------------------------------------------------------------- /javaScript/JavaScript中的执行栈、执行环境(上下文)、作用域、作用域链、活动对象、变量对象的概念总结.md: -------------------------------------------------------------------------------- 1 | 最近去梳理了一下这些相关概念、对js执行机制也有了更深的认识、为此翻阅了《javaScript高级教程》、《javaScript权威指南》、《深入理解JavaScript系列》等书籍及相关博客,下面我会结合官方概念保证权威准确性、并结合自己的理解去讲解,如果大家发现哪地方不对,希望能多多指出,我自己也在反复理解这些概念。 2 | 3 | 我先列出这基本经典书籍中的一些概念描述、让大家都有一个自己的最初认识,也方便后面讲解。然后接着往下看,会结合具体例子讲解概念,会更加清晰,看完之后保证你有一个新的认识。 4 | 5 | # 官方概念 6 | ## 执行环境(执行上下文Execution Contexts): 7 | 8 | >执行环境定义了变量或函数有权访问的其他数据、决定了它们各自的行为。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,而函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境 --《javaScript高级教程》 9 | 10 | ## 作用域(Scope): 11 | 12 | >javaScript中没有块级作用域、取而代之的使用了函数作用域、即变量在声明它们的函数以及这个函数体嵌套的任意函数体内都是有定义的 --《javaScript权威指南》 13 | 14 | ## 变量对象(Variable Object): 15 | 16 | >每一个执行环境都有一个与之关联的变量对象,是一个抽象的概念,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,单解析器在处理数据时会在后台使用它们。 17 | 18 | ------- 19 | # 补充个人理解的概念 20 | 下面我补充几个自己理解的概念: 21 | 22 | ## [执行环境栈](https://baike.baidu.com/item/%E6%89%A7%E8%A1%8C%E6%A0%88/22105693?fr=aladdin): 23 | 24 | > 存放正在运行子程序(函数即是一个子程序)消息的一个区域,涉及到很多计算机系统知识,如CPU指令,读取内存等,暂时我简单理解为提供存储正在运行的子程序消息的一个区域,不同于对于数据结构中的栈内存。 25 | 26 | ## 作用域链(Scope Chain): 27 | 28 | > 当函数创建时会创建一个包含其父函数变量、父函数的父函数的变量对象、直至全局变量对象的一个作用域链,这个作用域被保存在函数内部的[[scope]]属性中,由于函数本身即是对象,可以理解[[scope]]是后台可以访问的一个属性。当函数调用时,会创建一个自己的活动对象、作为变量对象,被推入到执行环境作用域链的最前端,此时这个[[scope]]属性相当于一个变量对象的集合,并有访问的优先级。作用域链并不保存实际的变量对象,它是一个指针,指向内存中的变量对象列表。 29 | 30 | # js事件循环机制 31 | 32 | **首先需要了解几个概念:** 33 | 34 | 当程序启动时, 一个[进程](https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B)被创建,同时也运行一个线程, 即为[主线程](https://baike.baidu.com/item/%E4%B8%BB%E7%BA%BF%E7%A8%8B/9600138?fr=aladdin),js的运行机制为[单线程](https://baike.baidu.com/item/%E5%8D%95%E7%BA%BF%E7%A8%8B/6972534?fr=aladdin) 35 | 36 | 先贴一张流程图: 37 | 38 | ![js事件循环流程图](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-09-08%20%E4%B8%8B%E5%8D%883.04.05.png) 39 | 40 | **流程如下:** 41 | 42 | 1. 主线程读取js代码, 形成相应的堆和执行栈, 执行同步任务 43 | 44 | 2. 当主线程遇到异步任务,,指定给异步进程处理, 同时继续执行同步任务 45 | 3. 当异步进程处理完毕后, 将相应的异步任务推入到任务队列首部 46 | 4. 主线程任务处理完毕后,,查询任务队列,则取出一个任务队列推入到主线程的执行栈 47 | 5. 重复执行第2、3、4步,这就称为事件循环 48 | 49 | **其中的异步进程有:** 50 | 1. 类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中; 51 | 2. setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中; 52 | 2. Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。 53 | 54 | 讲述js事件循环机制主要是想阐述一下执行栈、执行环境(也叫执行上下文)的概念,帮助我们后面更好的理解当调用一个函数时具体发生了什么。 55 | 56 | **需要注意的是只有同步任务执行完毕后, 才会去任务队列中去读取任务,而不是异步程序返回结果即执行此异步任务** 57 | 58 | # 变量对象(Variable Object, 缩写为VO) 59 | 60 | 当浏览器第一次加载js脚本程序的时候, 默认进入全局执行环境, 此次的全局环境变量对象为window, 在代码中可以访问。 61 | 62 | 如果环境是函数, 则将此活动对象做为当前上下文的变量对象(VO = AO), 此时变量对象是不可通过代码来访问的,下面主要对活动对象进行讲解。 63 | 64 | # 活动对象(Activation Object ,缩写为AO) 65 | 66 | 当函数一调用,立刻创建当前上下文的活动对象, 通过arguments属性初始化,值为arguments对象(传入的实参集合,与形参无关,形参做为局部环境的局部变量被定义) 67 | 68 | ``` 69 | AO = { 70 | arguments: 71 | }; 72 | ``` 73 | arguments对象有以下属性: 74 | 75 | - **length:** 真正传递参数的个数; 76 | - **callee:** 指向当前函数的引用,也就是被调用的函数; 77 | - **'类index':** 字符串类型的整数, 值就是arguments对象中对象下标的值,arguments对象应和数组加以区别, 它就是arguments对象,只是能和数组具有相同的length属性,和可以通过下标来访问值 78 | 79 | 80 | ``` 81 | function show (a, b, c) { 82 | // 通过Object.prototype.toString.call()精准判断类型, 证明arguments不同于数组类型 83 | var arr = [1, 2, 3]; 84 | console.log(Object.prototype.toString.call(arr)); // [object Array] 85 | 86 | console.log(Object.prototype.toString.call(arguments)); // [object Arguments] 87 | 88 | console.log(arguments.length) // 2 传递进来实参的个数 89 | 90 | console.log(arguments.callee === show) // true 就是被调用的函数show自身 91 | 92 | //参数共享 93 | 94 | console.log(a === arguments[0]) // true 95 | 96 | a = 15; 97 | 98 | console.log(arguments[0]) // 15 99 | 100 | arguments[0] = 25; 101 | 102 | console.log(a) // 25; 103 | 104 | 但是,对于没有传进来的参数c, 和arguments的第三个索引是不共享的 105 | 106 | c = 25; 107 | 108 | console.log(arguments[2]) // undefined 109 | 110 | argument[2] = 35; 111 | 112 | console.log(c) // 25 113 | 114 | } 115 | 116 | show(10, 20); 117 | ``` 118 | 119 | 接着往下走,这才是关键的地方,执行环境的代码被分成两个阶段来处理: 120 | 121 | 1. 进入执行环境 122 | 2. 执行函数的代码 123 | 124 | ## 进入执行环境 125 | 126 | 前面说过,函数如果被调用, 进入执行环境(上下文),并立即创建活动对象, 通过arguments属性初始化, 与此同时会扫描执行环境中的所有形参、所有函数声明、所有变量声明, 添加到活动对象(AO)中, 并确定this的值,然后会开始执行代码。 127 | 128 | **在进入执行环境这个阶段:** 129 | 130 | **所有形参声明:** 131 | 132 | >形参名称作为活动对象属性被创建, 如果传递实参, 值就为实参值, 如果没有传递参数, 值就为undefined 133 | 134 | **所有函数声明:** 135 | 136 | >函数名称作为活动对象的属性被创建,值是一个指针在内存中, 指向这个函数,如果变量对象已经存在相同名称的属性, 则完全替换。 137 | 138 | **所有变量声明:** 139 | 140 | >所有变量名称作为活动对象的属性被创建, 值为undefined,但是和函数声明不同的是, 如果变量名称跟已经存在的属性(形式参数和函数)相同、则不会覆盖 141 | 142 | 143 | ``` 144 | function foo(a, b) { 145 | var c = 10; 146 | function d() { 147 | console.log('d'); 148 | } 149 | var e = function () { 150 | console.log('e'); 151 | }; 152 | (function f() {}) 153 | if (true) { 154 | var g = 20; 155 | } else { 156 | var h = 30; 157 | } 158 | } 159 | 160 | foo(10); 161 | ``` 162 | 163 | 此时在进入foo函数执行上下文时,AO为: 164 | 165 | ``` 166 | AO(foo) = { 167 | arguments: { 168 | 0: 10, 169 | length: 1 170 | }, 171 | a: 10, 172 | b: undefined, 173 | c: fundefined, 174 | d: '指向d函数的指针', 175 | e: undefined, 176 | g: undefined, 177 | h: undefined // 虽然else中的代码永远不会执行,但是h仍然是活动对象中的属性 178 | } 179 | 180 | ``` 181 | 182 | **这个例子做如下几点说明:** 183 | 184 | 1. 关于函数,只会创建函数声明作为活动对象的属性, 而f函数作为函数表达式并不会出现在活动对象(AO)中 185 | 186 | 2. e虽然值是一个函数, 但是作为变量属性被活动对象创建 187 | 188 | ## 代码执行阶段 189 | 190 | 在进入执行上下文阶段,活动对象拥有了属性,但是很多属性值为undefined, 到代码直接阶段就开始为这些属性赋值了 191 | 192 | **还是上面的代码例子, 此时活动对象如下:** 193 | 194 | AO(foo) = { 195 | arguments: { 196 | 0: 10, 197 | length: 1 198 | }, 199 | a: 10, 200 | b: undefined, 201 | c: 10, // 赋值为undefined 202 | d: '指向d函数的指针', 203 | e: '指向e函数的指针' // 作为变量被保存在内存中 204 | g: 20, 205 | h: undefined // 声明h变量,但是没有赋值 206 | } 207 | 208 | ``` 209 | 变量对象包括:{arguments对象+函数形参+内部变量+函数声明(但不包含表达式)} 210 | ``` 211 | 212 | 这时这个活动对象, 即作为当前执行环境的变量对象会被推到此执行环境作用域链的最前端, 另外别忘记了当前上线问指针,假定执行环境为一个对象,则整个执行环境可以访问到的属性如下: 213 | 214 | ``` 215 | fooExecutionContext = { 216 | scopeChain(作用域链): AO(foo的活动对象)+所有父执行环境的活动对象, 217 | AO(foo的变量对象): { 218 | arguments: { 219 | 0: 10, 220 | length: 1 221 | }, 222 | a: 10, 223 | b: undefined, 224 | c: 10, // 赋值为undefined 225 | d: '指向d函数的指针', 226 | e: '指向e函数的指针', 227 | g: 20, 228 | h: undefined 229 | }, 230 | this: 当前执行环境的上下文指针 231 | } 232 | ``` 233 | 234 | 下面的例子为了说明一下变量声明的顺序及变量同名不会影响函数声明 235 | 236 | ``` 237 | console.log(foo); // foo的函数体 238 | var foo = 10; 239 | console.log(foo) // 10 240 | function foo() {}; 241 | foo = 20; 242 | console.log(foo); // 20 243 | 244 | ``` 245 | 246 | 在代码执行之前, 就会读取函数声明,变量声明的顺序在函数声明和形参声明之后, 整个流程如下: 247 | 248 | 进入执行环境阶段: 249 | 250 | ``` 251 | 1. var VO = {} 252 | 2. VO[foo] = 'foo函数指针' 253 | 3. 扫描到var foo = 10,但是foo做为function已经声明,所以变量声明不会影响同名的函数声明,如果代码中没有foo函数声明的话,则foo为undefined 254 | ``` 255 | 256 | 代码执行阶段: 257 | 258 | ``` 259 | 1. VO[foo] = 10; 260 | 2. VO[foo] = 20; 261 | ``` 262 | 263 | 解析代码完成。 264 | 265 | # 总结个人理解的概念 266 | 267 | 写了这么多,终于写完了,最后,想说一下我对这几个概念的理解: 268 | 269 | ## 执行环境(执行上下文): 270 | 271 | >作为一段可执行的程序,我抽象把它理解成一个任务,当函数被调用,立刻创建当前执行环境,并被推入到执行栈中 272 | 273 | ## 作用域: 274 | 275 | > 一个抽象的概念, 对当前执行环境所有数据的访问权限 276 | 277 | ## 变量对象: 278 | 279 | > 一个抽象的概念,与执行环境相对象,全局执行环境的变量对象为window,可访问。在函数环境中,函数调用后创建的活动对象即作为当前执行环境的变量对象, 此时从引用的角度看,二者是一个东东。 280 | 281 | ## 活动对象: 282 | 283 | > 作为具体概念存在,在全局执行环境不存在这个概念,全局变量对象的变量对象指向window,而且并不存在arguments属性 284 | 285 | 整篇文章基本上这就是我能理解到的程度了, 很多概念查找很多书籍并加入自己的理解,在后续理解逐渐加深后,也会更新本文, 希望能帮到对此也迷惑的人,如果有理解不对的地方,希望大家多多给出指导意见,谢谢。 -------------------------------------------------------------------------------- /javaScript/JavaScript中的正则表达式.md: -------------------------------------------------------------------------------- 1 | 正则在平时工作中用的非常多, 最开始接触正则的时候感觉这个东东好难记啊,最近把正则的内容整理了一下,写成以下文章。 2 | 3 | 先给大家介绍一个在线解析正则的网站,来帮助我们理解正则,特别是复杂的正则表达式,非常好用 4 | 5 | http://www.regexper.com 6 | 7 | 比如/^@[a-zA-Z]\d+@$/,解析之后图形帮助理解如下: 8 | 9 | ![reg解析](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8B%E5%8D%882.41.14.png) 10 | 11 | # 什么是正则 12 | 13 | 正则表达式是用于匹配字符串中字符组合的模式。主要应用于正则对象的test和esec方法,以及字符串的search、split、match、replace中。 14 | 15 | # 创建正则 16 | 17 | ## 字面量创建 18 | ``` 19 | var reg = /pattern/flag; 20 | ``` 21 | 22 | 在正则表达式保持不变,也就是不需要每次都创建一个新的正则对象的时候,使用字面量创建的正则性能更好 23 | 24 | 每个正则表达式都可以带有一个或多个(也可以不带)表明正则表达式行为的标志 25 | 26 | ![正则flag](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8A%E5%8D%889.19.47.png) 27 | 28 | ## 构造函数创建 29 | 30 | 在js中提供了一个内置构造函数RegExp来创建一个正则对象 31 | ``` 32 | var reg = new RegExp(pattern [, flags]); 33 | ``` 34 | 35 | ``` 36 | var reg = /[ab]c/g; 37 | // 等价于 38 | var reg = new RegExp("[ab]c", g); 39 | ``` 40 | 41 | # 正则表达式中的特殊字符 42 | 43 | ## 元字符 44 | 45 | > 元字符是在正则表达式中有特殊含义的非字母字符,js中正则表达式元字符包括: 46 | 47 | ? + * ^ $ . | \ ( ) [ ] { } 48 | 49 | 因为这些字符在正则表达式中具有特殊含义,所以如果要想在字符串中匹配这些字符,就必须对它们进行转义. 50 | ``` 51 | // 匹配字符串中的ac或者bc 52 | var reg = /[ab]c/; 53 | 54 | // 如果要匹配字符串的"[ab]c", 需要对[]进行转义 55 | var reg = /\[ab\]c/; 56 | 57 | ``` 58 | 59 | **注意:** 60 | 另外需要注意的是,由于使用构造函数创建正则,pattern参数必须为字符串,所有元字符如果需要在字符串中匹配这个字符,需要进行双重转义 61 | 62 | ``` 63 | var reg = /\[ab\]c/g; 64 | 65 | 如果使用构造函数创建正则表达式,应该写成: 66 | 67 | var reg = new RegExp("\\[ab\\]c", g); 68 | ``` 69 | 70 | ## 字符集合 71 | 72 | 我们可以使用[]来构建一个简单的类[xyz],类指符合某些特性的对象,是一个泛指,并不是指某个字符,表示匹配方括号的中任意字符,对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。 73 | ``` 74 | var reg = /[abc]/g; 75 | var reg2 = /[abc.]/g; // 字符集合中的. 76 | var reg3 = /[abc\.]/g; // 字符集合中转义的. 77 | 78 | var str = 'a1b2c3'; 79 | var str2 = 'a1b2c3d.'; 80 | 81 | var res = str.replace(reg, 'X'); 82 | var res2 = str2.replace(reg2, 'X'); 83 | var res3 = str3.replace(reg3, 'X'); 84 | 85 | console.log(res); // X1X2X3 86 | console.log(res2); // X1X2X3dX 87 | console.log(res3); // X1X2X3dX 88 | 89 | ``` 90 | 91 | ## 字符集合取反 92 | 93 | 一个反向字符集[\^xyz], 它匹配任何没有包含在方括号中的字符 94 | ``` 95 | var reg = /[^abc]/g; 96 | var str = 'abcdefg'; 97 | var res = str.replace(reg, 'X'); 98 | console.log(res); // abcXXXX 99 | ``` 100 | ## 范围类 101 | 102 | 在字符集合中可以使用(-)来指定一个字符范围, 如[a-z],表示匹配从a到z的任意字符 103 | ``` 104 | var reg = /[a-z]/g; 105 | var str = 'a1b2c3d4e5F6'; 106 | var res = str.replace(reg, 'X'); 107 | console.log(res); // X1X2X3X4X5F6 108 | ``` 109 | 在范围类[]中可以连写,如同时匹配大小写,[a-zA-Z] 110 | 111 | ``` 112 | var reg = /[a-zA-Z]/g; 113 | var str = 'a1b2c3d4e5F6'; 114 | var res = str.replace(reg, 'X'); 115 | console.log(res); // X1X2X3X4X5X6 116 | ``` 117 | ## 预定义类 118 | 119 | 正则表达式提供了预定义类,来匹配常见的字符类,不需要都通过字符集合去定义正则表达式 120 | 121 | | 字符 | 等价类 | 含义 | 122 | | -------- | --------| ---- | 123 | |.|[\^\\n\\r]|匹配除回车符合换行符之外的任何单个字符| 124 | |\\d|[0-9]|数字字符| 125 | |\\D|[\^0-9]|非数字字符| 126 | |\\s|[\\t\\n\\x0B\\f\\r]|空白符| 127 | |\\S|[\^\\t\\n\\x0B\\f\\r]|非空白符| 128 | |\\w|[a-zA-Z_]|单词字符(字母、数字、下划线)| 129 | |\\W|[\^a-zA-Z_]|非单词字符| 130 | 131 | ## 边界 132 | 正则还提供了边界匹配符 133 | | 字符 | 含义 | 134 | | -------- | ---- | 135 | |^|匹配输入的开始| 136 | |$|匹配输入的结束| 137 | |\\b|单词边界| 138 | |\\B|非单词边界| 139 | 140 | ``` 141 | // ^的应用 142 | var reg = /^@./g 143 | var str = '@123abc@'; 144 | var res = str.replace(reg, 'X'); 145 | console.log(res); // X23abc 146 | 147 | // $的应用 148 | var reg2 = /^.@$/g; 149 | var str2 = '@123abc@'; 150 | var res2 = str2.replace(reg2, 'X'); 151 | console.log(res2); // 123abX 152 | 153 | // \b的应用 154 | var reg3 = /\bis\b/g; 155 | var str3 = 'this is javaScript'; 156 | var res3 = str3.replace(reg3, 'X'); 157 | console.log(res3); // this X javaScript 158 | ``` 159 | 160 | ### 正则的m标志应用 161 | ``` 162 | var reg = /^@./g; 163 | var str = `@abc 164 | @123 165 | @XYZ 166 | ` 167 | var res = str.replace(reg, 'X'); 168 | // 因为即使字符串看上去换行,本质上还是一些换行符,只有结尾和结束 169 | console.log(res); // Xbc 170 | @123 171 | @XYZ 172 | ``` 173 | 当正则表达式使用m标志的时候,在一行文本末尾结束的时候,还会去匹配下一行是否存在与模式匹配的项,例子如下: 174 | ``` 175 | var reg = /^@./gm; 176 | var str = `@abc 177 | @123 178 | @XYZ 179 | ` 180 | var res = str.replace(reg, 'X'); 181 | console.log(res); // Xbc 182 | X23 183 | XYZ 184 | ``` 185 | ## 量词 186 | 187 | |字符|含义| 188 | | -- | -- | 189 | |?|匹配前面一个表达式0次或者1次(至多出现一次)| 190 | |+|匹配前面一个表达式1次或者多次(至少出现一次)| 191 | |*|匹配前一个表达式0次或多次(任意次)| 192 | |{n}|n是一个正整数,匹配了前面一个字符刚好发生了n次| 193 | |{n,m}|n 和 m 都是整数。匹配前面的字符至少n次,最多m次。如果 n 或者 m 的值是0, 这个值被忽略。| 194 | |{n,}|匹配前面字符n此或者更多次(至少出现n次| 195 | 196 | ### 贪婪模式 197 | 贪婪模式是让正则表达式尽可能多的匹配 198 | ``` 199 | var reg = /\d{2,5}/; 200 | var str = '12345678'; 201 | var res = str.replace(reg, 'X'); 202 | console.log(res); // X678 203 | ``` 204 | ### 非贪婪模 205 | 206 | 非贪婪模式是让正则表达式尽可能少的匹配,一旦匹配成功不在继续匹配,做法是在量词后面加上?即可 207 | 208 | ``` 209 | var reg = /\d{2,5}?/ 210 | var str = '12345678'; 211 | var res = str.replace(reg, 'X'); 212 | console.log(res); // X345678 213 | ``` 214 | 215 | ## 分组 216 | 217 | ### 使用括号()进行分组 218 | 量词不作用于紧挨着的某个字符,使用/(x)/匹配 'x',并且记住匹配项,括号被称为补货括号 219 | 220 | ``` 221 | var reg = /([a-zA-Z]\d)/g; 222 | var str = 'a1bbc3D4efg'; 223 | var res = str.replace(reg, 'X'); 224 | console.log(res); // XbbXXefg 225 | ``` 226 | 使用在线解析正则工具如下图: 227 | ![res正则解析](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8B%E5%8D%883.01.07.png) 228 | 229 | 另外可以添加量词 230 | 231 | ``` 232 | var reg = /(abc){3}/g; 233 | var str = 'abcabcabcabc'; 234 | var res = str.replace(reg, 'X'); 235 | console.log(res); // Xabc 236 | ``` 237 | ### 使用或' | '进行分组 238 | 239 | ``` 240 | var reg = /apple|pear/g 241 | var str = 'appleappleHpearpear'; 242 | var res = str.replace(reg, 'X'); 243 | console.log(res); // XXHXX 244 | 245 | var reg2 = /appl(e|p)ear/g; 246 | var str2 = 'appleearHapplpear' 247 | var res2 = str2.replace(reg2, 'X'); 248 | console.log(res2); // XHX 249 | ``` 250 | 在线解析上面代码的reg和reg2如下: 251 | reg: 252 | ![reg](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8B%E5%8D%883.18.46.png) 253 | 254 | reg2: 255 | ![reg2](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8B%E5%8D%883.19.05.png) 256 | 257 | ### 捕获匹配到的分组内容 258 | 259 | 在replace替换环节,可以使用$1、$2、$3...$n等捕获分匹配到的分组 260 | 261 | 比如想把'25/12/2016'转换成'2016-12-25': 262 | 263 | ``` 264 | var reg = /(\d{2})\/(\d{2})\/(\d{4})/; 265 | var str = '25/12/2016'; 266 | var res = str.replace(reg, '$3-$2-$1'); 267 | console.log(res); // 2016-12-25 268 | ``` 269 | ### 忽略分组 270 | 271 | 如果不希望捕获分组,只需要在分组内加上(?:)就可以 272 | 273 | ``` 274 | var reg = /(?:\d{2})\/(\d{2})\/(\d{4})/; 275 | var str = '25/12/2016'; 276 | var res = str.replace(reg, '$3-$2-$1'); 277 | 278 | 此时$2为2016,$1为12,而$3捕获不到,按普通字符串显示 279 | console.log(res); // $3-2016-12 280 | ``` 281 | ## 正向肯定查找 282 | x(?=y) 283 | 匹配x,并且x后必须跟着y,这就是正向肯定查找 284 | 285 | ``` 286 | var reg = /\w(?=\d)/g; 287 | var str = 'a1b2ccd4'; 288 | var res = str.replace(reg, 'X'); 289 | console.log(res); // X1X2ccX4 290 | ``` 291 | 292 | ## 正向否定查找 293 | 匹配x,并且x后必须不跟着y,这就是正向否定查找 294 | 295 | ``` 296 | var reg = /[a-z](?!\d)/g; 297 | var str = 'a1b2ccd4'; 298 | var res = str.replace(reg, 'X'); 299 | console.log(res); // a1b2XXd4 300 | ``` 301 | # 正则对象属性和方法 302 | 303 | ## 属性 304 | 305 | ![正则对象属性](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-14%20%E4%B8%8B%E5%8D%884.03.10.png) 306 | 307 | 属性皆为只读,不可修改 308 | 309 | ``` 310 | var reg = /\[abc\]/; 311 | console.log(reg.ignoreCase) // false; 312 | console.log(reg.global) // false; 313 | console.log(reg.multiline) // false; 314 | 315 | reg.ignoreCase = true; 316 | reg.global = true; 317 | reg.multiline = true; 318 | 319 | console.log(reg.ignoreCase) // false; 320 | console.log(reg.global) // false; 321 | console.log(reg.multiline) // false; 322 | ``` 323 | 还是以上代码看一下source属性 324 | ``` 325 | console.log(reg.source) // \[abc\] 326 | ``` 327 | 如果使用构造函数创建正则对象,再来看一下source属性 328 | ``` 329 | var reg = new RegExp("\\[abc\\]"); //需要对元字符进行双重转义 330 | console.log(reg.source); // \[abc\] 331 | ``` 332 | 通过以上对比可知,source属性是字面量形式创建正则对象所有的字符串 333 | ## 方法 334 | 335 | ### RegExp.prototype.test() 336 | 337 | test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。匹配到返回 true,否则返回false。 338 | 339 | **语法** 340 | ``` 341 | regexObj.test(str) 342 | ``` 343 | ``` 344 | var reg = /\w/; 345 | var str = 'ab'; 346 | console.log(reg.test(str)); // true 347 | console.log(reg.test(str)); // true 348 | console.log(reg.test(str)); // true 349 | ``` 350 | **注意** 351 | 当正则表达式使用全局模式时,lastIndex属性会影响test()方法的返回值,看下面例子 352 | ``` 353 | var reg = /\w/g; 354 | var str = 'ab'; 355 | console.log(reg.test(str)); // true 356 | console.log(reg.test(str)); // true 357 | console.log(reg.test(str)); // false 358 | console.log(reg.test(str)); // true 359 | ``` 360 | 为什么会出现这种现象呢,是因为正则表达式执行test方法时,每次都会把结果作用到操作的正则实例上,由于是全局匹配,第一次匹配到之后reg的lastIndex属性为1,继续匹配,此时从lastIndex的位置开始匹配,即从b开始,结果又匹配到,此时lastIndex属性为2,当继续匹配时,从2开始匹配,没有匹配到,此时返回false,lastIndex被重置为0,所以第4次执行console.log(reg.test(str))就会从新从0开始,所以返回值为true。结合while循环来说明一下: 361 | 362 | ``` 363 | var reg = /\w/g; 364 | var str = 'ab'; 365 | while(reg.test(str)){ 366 | console.log(reg.lastIndex); // 循环执行两次,分别打印出1, 2 367 | } 368 | ``` 369 | ### RegExp.prototype.exec() 370 | 371 | exec() 方法在一个指定字符串中执行一个搜索匹配. 372 | 373 | **语法** 374 | ``` 375 | regexObj.exec(str) 376 | ``` 377 | **返回值:** 378 | 379 | 1.如果匹配失败,返回 null。 380 | 2.如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性,一般来说主要是lastIndex属性值的更新。返回的数组将完全匹配成功的文本作为第一项,将正则括号里匹配成功的作为数组填充到后面,返回值虽然是Array实例,但是包含了index和input属性 381 | 382 | index: 表示匹配项在字符串中的位置,也就是匹配项第一个字符的位置 383 | input: 表示应用正则表达式的字符串 384 | 385 | #### 非全局调用 386 | 387 | 返回数组内容包括: 388 | 1. 第一个元素是与正则表达式相匹配的文本 389 | 2. 第二个元素是与正则对象第一个子表达式相匹配的文本,也就是第一个分组(如果有的话) 390 | 2. 第三个元素是与正则对象第二个子表达式相匹配的文本,也就是第而个分组(如果有的话),以此类推 391 | 392 | ``` 393 | var reg = /\d(\w)(\w)\d/; 394 | var str = '@1bb2c3dd4f'; 395 | var res = reg.exec(str); 396 | console.log(reg.lastIndex); // 0 非全局模式忽略lastIndex属性 397 | console.log(res.index); // 1 398 | console.log(res.input); // @1ab2c3dd4f 399 | console.log(res); // ["1ab2", "a", "b"] 400 | ``` 401 | 402 | #### 全局调用 403 | 404 | ``` 405 | var reg = /\d(\w)(\w)\d/g; 406 | var str = '@1bb2c3dd4f'; 407 | var res = reg.exec(str); 408 | console.log(reg.lastIndex); // 5 非全局模式忽略lastIndex属性 409 | console.log(res.index); // 1 410 | console.log(res.input); // @1ab2c3dd4f 411 | console.log(res); // ["1ab2", "a", "b"] 412 | console.log(reg.lastIndex); 413 | ``` 414 | 使用while循环加深一下理解 415 | ``` 416 | var reg = /\d(\w)(\w)\d/g; 417 | var str = '@1bb2c3dd4f'; 418 | while(reg.exec(str)) { 419 | console.log(reg.lastIndex, res.index, res); 420 | // 打印两次结果分别为 421 | // 5, 1, ["1bb2", "b", "b"] 422 | // 10, 6, ["3dd4", "d", "d"] 423 | } 424 | ``` 425 | 426 | # 字符串对象方法 427 | 428 | ## String.prototype.search() 429 | 方法执行正则表达式和 String对象之间的一个搜索匹配,如果匹配成功,返回正则表达式在字符串中首次匹配项的索引,否则返回-1。 430 | 431 | **语法:** 432 | ``` 433 | str.search(regexp) 434 | ``` 435 | 436 | 如果传入一个非正则表达式对象,则会使用 new RegExp(obj) 隐式地将其转换为正则表达式对象。 437 | 438 | ## String.prototype.match() 439 | 440 | 用于搜索字符串,找到一个或多个与regexp匹配的文本 441 | **语法:** 442 | ``` 443 | str.match(regexp); 444 | ``` 445 | 446 | **返回值:** 447 | 448 | 一个包含了整个匹配结果以及任何括号捕获的匹配结果的 Array ;如果没有匹配项,则返回 null。regexp是否有g标志对返回值有很大影响。 449 | 450 | ### 非全局调用(不包含g标志) 451 | 452 | 返回值和RegExp.prototype.exec()方法一样,就不细说了。 453 | 454 | ### 全局调用(包含g标志) 455 | 456 | 1. 没有找到任何匹配的字符串,返回null 457 | 2. 如果找到了一个或多个匹配的字符串,则返回一个数组,存放字符串中所有匹配的字符串,不包含捕获内容,也不具有index和input属性。 458 | 459 | ``` 460 | var reg = /\d(\w)\d/g; 461 | var str = '1a2b3c4d'; 462 | var res = str.match(reg); 463 | console.log(res); // ["1a2", "3c4"] 464 | console.log(res.index); // undefined 465 | console.log(res.input); // undefined 466 | ``` 467 | 468 | ## String.prototype.split() 469 | 470 | split() 方法使用指定的分隔符字符串将一个String对象分割成字符串数组 471 | 472 | **语法:** 473 | ``` 474 | str.split([separator[, limit]]); 475 | ``` 476 | ![split](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-16%20%E4%B8%8A%E5%8D%8810.17.52.png) 477 | 478 | ### separator 参数 479 | 当separator为字符串时,其实也是默认转成正则去执行 480 | 481 | ``` 482 | var str = 'a, b, c, d'; 483 | var arr = str.split(','); 484 | var arr2 = str.split(/,/); 485 | console.log(arr); // ["a", " b", " c", " d"] 486 | console.log(arr2); // ["a", " b", " c", " d"] 487 | ``` 488 | ### separator带捕获括号 489 | 如果 separator 包含捕获括号(capturing parentheses),则其匹配结果将会包含在返回的数组中。 490 | ``` 491 | var str = 'a1b2c3d'; 492 | var arr = str.split(/(\d)/); 493 | console.log(arr); // ["a", "1", "b", "2", "c", "3", "d"] 494 | ``` 495 | 496 | ### limit参数 497 | 限制返回值中分割元素数量 498 | ``` 499 | var str = 'a b c d e'; 500 | var arr = str.split(' ', 3); 501 | console.log(arr); // ["a", "b", "c"] 502 | ``` 503 | 504 | ## String.prototype.replace() 505 | 506 | replace() 方法返回一个由替换值替换一些或所有匹配的模式后的新字符串。模式可以是一个字符串或者一个正则表达式, 替换值可以是一个字符串或者一个每次匹配都要调用的函数。 507 | 508 | **注意:** 原字符串不会改变。 509 | 510 | **语法:** 511 | 512 | ``` 513 | str.replace(regexp|substr, newSubStr|function) 514 | ``` 515 | ### String.prototype.replace(substr, newSubStr) 516 | 517 | ``` 518 | var str = 'a1b2c3d'; 519 | var resStr = str.replace('1', 'X'); 520 | console.log(resStr); // 521 | ``` 522 | ### String.prototype.replace(regexp, newSubStr) 523 | ``` 524 | var str = 'a1b2c3d'; 525 | var resStr = str.replace(/\d/g, 'X'); 526 | console.log(resStr); // aXbXcXd 527 | ``` 528 | ### String.prototype.replace(regexp, function) 529 | 530 | function会在每次匹配替换的时候调用,包含四个可选参数 531 | 532 | 1. 匹配到的字符串 533 | 2. 正则表达式分组内容,没有分组就没有该参数 534 | 3. 匹配项在字符串中的index 535 | 4. 原字符串 536 | 537 | **例子:** 538 | 539 | 比如要把'a1b2c3'替换后的结果为'a2b3c4' 540 | 541 | ``` 542 | var str = 'a1b2c3'; 543 | var resStr = str.replace(/\d/g, function(matchStr, index, originStr) { 544 | // 此时正则表达式中无捕获,function中则无分组参数 545 | return parseInt(matchStr) + 1; 546 | }); 547 | console.log(resStr); // a2b3c4 548 | ``` 549 | 550 | 当正则表达式中有捕获时,再看一下另外一个例子: 551 | ``` 552 | var str = 'a1b2c3d4e'; 553 | var resStr = str.replace(/(\d)(\w)(\d)/g, function(matchStr, group1, group2, group3, index, originStr) { 554 | // 会执行两次回调,打印结果分别如下 555 | console.log(matchStr) // 1b2 3d4 556 | console.log(group1); // 1 3 557 | console.log(group2); // b d 558 | console.log(group3); // 2 4 559 | return group1 + group3; //把匹配到的文本替换成group1 + group3字符串拼接后的值 560 | }); 561 | // 把匹配到的1b2替换成group1 + group3(12), 3d4替换成(34) 562 | console.log(resStr); // a12c34e 把匹配到的1b2替换成group1 + group3(12) 563 | ``` 564 | 565 | 以上就是我总结的正则表达式相关知识, 感觉把正则搞清楚还是很爽滴, 如发现有问题请多多指教。 566 | -------------------------------------------------------------------------------- /javaScript/JavaScript中的闭包.md: -------------------------------------------------------------------------------- 1 | 文章同步到[github](https://github.com/sunzhaoye/blog/issues/12) 2 | 3 | js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 4 | 5 | # 什么是闭包 6 | 7 | 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然表达的不一样,但是都在对闭包做了最正确的定义和翻译,也帮助大家更好的理解闭包,这阅读起来可能比较模糊,大家往后看,本文通过对多个经典书籍中的例子讲解,相信会让大家能很好的理解js中的闭包。文章开始,我会先铺垫一下闭包的概念和为什么要引入闭包的概念,然后结合例子来说明讲解,并讲解如何使用闭包。 8 | 9 | ## 百度百科中的定义: 10 | 11 | [闭包](https://baike.baidu.com/item/%E9%97%AD%E5%8C%85/10908873?fr=aladdin)包含自由(未绑定到特定对象)变量;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域) -- 百度百科 12 | 13 | ## 《javaScript权威指南》中的概念: 14 | 函数对象可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学中成为**闭包** 15 | 16 | ## 《javaScript高级教程》中概念: 17 | 闭包是指有权访问另一个函数作用域中的变量的函数。 18 | 19 | ## MDN中的概念 20 | 21 | ![MDN闭包](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-18%20%E4%B8%8B%E5%8D%884.59.09.png) 22 | 23 | ## 个人总结的闭包概念: 24 | 1. 闭包就是子函数可以有权访问父函数的变量、父函数的父函数的变量、一直到全局变量。归根结底,就是利用js得词法(静态)作用域,即作用域链在函数创建的时候就确定了。 25 | 2. 子函数如果不被销毁,整条作用域链上的变量仍然保存在内存中。 26 | 27 | # 为什么引入闭包的概念 28 | 29 | 我引入[《深入理解JavaScript系列:闭包(Closures)》](http://www.lai18.com/content/425670.html)文章中的例子来说明,也可以直接去看那篇文章,我结合其他书籍反复读了很多遍此文章才理解清楚。如下: 30 | 31 | ``` 32 | function testFn() { 33 | 34 | var localVar = 10; // 自由变量 35 | 36 | function innerFn(innerParam) { 37 | alert(innerParam + localVar); 38 | } 39 | 40 | return innerFn; 41 | } 42 | 43 | var someFn = testFn(); 44 | someFn(20); // 30 45 | ``` 46 | **一般来说,在函数执行完毕之后,局部变量对象即被销毁,所以innerFn是不可能以返回值形式返回的,innerFn函数作为局部变量应该被销毁才对。** 47 | 48 | 这是当函数以返回值时的问题,另外再看一个当函数以参数形式使用时的问题,还是直接引用《深入理解JavaScript系列》中的例子,也方便大家有兴趣可以直接去阅读那篇文章 49 | 50 | ``` 51 | var z = 10; 52 | 53 | function foo() { 54 | alert(z); 55 | } 56 | 57 | foo(); // 10 – 使用静态和动态作用域的时候 58 | 59 | (function () { 60 | 61 | var z = 20; 62 | foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域 63 | 64 | })(); 65 | 66 | // 将foo作为参数的时候是一样的 67 | (function (funArg) { 68 | 69 | var z = 30; 70 | funArg(); // 10 – 静态作用域, 30 – 动态作用域 71 | 72 | })(foo); 73 | ``` 74 | 当函数foo在不同的函数中调用,z该取哪个上下文中的值呢,这就又是一个问题,所以就引入了闭包的概念,也可以理解为定义了一种规则。 75 | 76 | # 理解闭包 77 | 78 | ## 函数以返回值返回 79 | 80 | 看一个《javsScript权威指南》中的一个例子,我稍微做一下修改如下: 81 | ``` 82 | var scope = 'global scope'; 83 | function checkScope() { 84 | var scope = 'local scope'; 85 | return function() { 86 | console.log(scope); 87 | } 88 | } 89 | 90 | var result = checkScope(); 91 | result(); // local scope checkScope变量对象中的scope,非全局变量scope 92 | ``` 93 | **分析:** 94 | 95 | 即使匿名函数是在checkScope函数外调用,也没有使用全局变量scope,即是利用了js的静态作用域,当匿名函数初始化时,就创建了自己的作用域链(作用域链的概念这里不做解释,可以参考我的另一篇文章[js中的执行栈、执行环境(上下文)、作用域、作用域链、活动对象、变量对象的概念总结](https://segmentfault.com/a/1190000011082342),其实当把作用域链理解好了之后,闭包也就理解了), 此匿名函数的作用域链包括checkScope的活动对象和全局变量对象, 当checkScope函数执行完毕后,checkScope的活动对象并不会被销毁,因为匿名函数的作用域链还在引用checkScope的活动对象,也就是checkScope的执行环境被销毁,但是其活动对象没有被销毁,留存在堆内存中,直到匿名函数销毁后,checkScope的活动对象才会销毁,解除对匿名函数的引用将其设置为null即可,垃圾回收将会将其清除,另外当外部对checkScope的自由变量存在引用的时候,其活动对象也不会被销毁 96 | 97 | ``` 98 | result = null; //解除对匿名函数的引用 99 | ``` 100 | **注释: ** 101 | 102 | 自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量 103 | 104 | **补充: ** 105 | 引用一下《javsScript权威指南》中的补充,帮助大家进一步理解 106 | ![闭包](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-18%20%E4%B8%8B%E5%8D%883.40.00.png) 107 | 108 | ## 函数以参数形式使用 109 | 110 | 当函数以参数形式使用时一般用于利用闭包特性解决实际问题,比如浏览器中内置的方法等,下面我直接引用《深入理解JavaScript系列:闭包(Closures)》中关于闭包实战部分的例子如下: 111 | 112 | ### sort 113 | 114 | 在sort的内置方法中,函数以参数形式传入回调函数,在sort的实现中调用: 115 | ``` 116 | [1, 2, 3].sort(function (a, b) { 117 | ... // 排序条件 118 | }); 119 | ``` 120 | ### map 121 | 和sort的实现一样 122 | ``` 123 | [1, 2, 3].map(function (element) { 124 | return element * 2; 125 | }); // [2, 4, 6] 126 | ``` 127 | 128 | ### 另外利用自执行匿名函数创建的闭包 129 | 130 | ``` 131 | var foo = {}; 132 | 133 | // 初始化 134 | (function (object) { 135 | 136 | var x = 10; 137 | 138 | object.getX = function() { 139 | return x; 140 | }; 141 | 142 | })(foo); 143 | 144 | alert(foo.getX()); // 获得闭包 "x" – 10 145 | ``` 146 | 147 | # 利用闭包实现私有属性的存取 148 | 149 | 先来看一个例子 150 | 151 | ``` 152 | var fnBox = []; 153 | function foo() { 154 | for(var i = 0; i < 3; i++) { 155 | fnBox[i] = function() { 156 | return i; 157 | } 158 | } 159 | } 160 | 161 | foo(); 162 | var fn0 = fnBox[0]; 163 | var fn1 = fnBox[1]; 164 | var fn2 = fnBox[2]; 165 | console.log(fn0()); // 3 166 | console.log(fn1()); // 3 167 | console.log(fn2()); // 3 168 | ``` 169 | 用伪代码来说明如下: 170 | ``` 171 | fn0.[[scope]]= { 172 | // 其他变量对象,一直到全局变量对象 173 | 父级上下文中的活动对象AO: [data: [...], i: 3] 174 | } 175 | 176 | fn1.[[scope]]= { 177 | // 其他变量对象,一直到全局变量对象 178 | 父级上下文中的活动对象AO: [data: [...], i: 3] 179 | } 180 | 181 | fn2.[[scope]]= { 182 | // 其他变量对象,一直到全局变量对象 183 | 父级上下文中的活动对象AO: [data: [...], i: 3], 184 | } 185 | ``` 186 | **分析:** 187 | 188 | 这是因为fn0、fn1、fn2的作用域链共享foo的活动对象, 而且js没有块级作用域,当函数foo执行完毕的时候foo的活动对象中i的值已经变为3,当fn0、fn1、fn2执行的时候,其最顶层的作用域没有i变量,就沿着作用域链查找foo的活动对象中的i,所以i都为3。 189 | 190 | 但是这种结果往往不是我们想要的,这时就可以利用认为创建一个闭包来解决这个问题,如下: 191 | ``` 192 | var fnBox = []; 193 | function foo() { 194 | for(var i = 0; i < 3; i++) { 195 | fnBox[i] = (function(num) { 196 | return function() { 197 | return num; 198 | } 199 | })(i); 200 | } 201 | } 202 | foo(); 203 | var fn0 = fnBox[0]; 204 | var fn1 = fnBox[1]; 205 | var fn2 = fnBox[2]; 206 | console.log(fn0()); // 0 207 | console.log(fn1()); // 1 208 | console.log(fn2()); // 2 209 | ``` 210 | 用伪代码来说明如下: 211 | ``` 212 | fn0.[[scope]]= { 213 | // 其他变量对象,一直到全局变量对象 214 | 父级上下文中的活动对象AO: [data: [...], k: 3], 215 | fn0本身的活动对象AO: {num: 0} 216 | } 217 | 218 | fn1.[[scope]]= { 219 | // 其他变量对象,一直到全局变量对象 220 | 父级上下文中的活动对象AO: [data: [...], k: 3], 221 | fn1本身的活动对象AO: {num: 1} 222 | } 223 | 224 | fn2.[[scope]]= { 225 | // 其他变量对象,一直到全局变量对象 226 | 父级上下文中的活动对象AO: [data: [...], k: 3], 227 | fn2本身的活动对象AO: {num: 2} 228 | } 229 | ``` 230 | **分析:** 231 | 232 | 当使用自执行匿名函数创建闭包, 传入i的值赋值给num,由于作用域链是在函数初始化时创建的,所以当每次循环时,函数fn10、fn1、fn2的作用域链中保存了当次循环是num的值, 当fn10、fn1、fn2调用时,是按照本身的作用域链进行查找。 233 | 234 | # 闭包引起的内存泄漏 235 | 236 | ![闭包内存泄漏](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-10-18%20%E4%B8%8B%E5%8D%884.38.30.png) 237 | 238 | # 总结 239 | 240 | 从理论的角度将,由于js作用域链的特性,js中所有函数都是闭包,但是从应用的角度来说,只有当函数以返回值返回、或者当函数以参数形式使用、或者当函数中自由变量在函数外被引用时,才能成为明确意义上的闭包。 241 | 242 | 最后,我想表达的式,本篇大量引用和罗列了经典的犀牛书《javaScript权威指南》、红宝书《javaScript高级教程》、以及《深入理解JavaScript系列:闭包(Closures)》系列文章中的概念和例子,不为能形成自己的独特见解,只为了能把闭包清晰的讲解出来。笔者是个小菜鸟,能力实在有限,也在学习中,希望大家多多指点,如发现错误,请多多指正。也希望看过此文的朋友能对闭包多一些理解,那我写这篇文章也就值得了。下次面试时就可以告诉面试官什么是闭包了。谢谢。 -------------------------------------------------------------------------------- /javaScript/JavaScript中的面向对象、原型、原型链、继承.md: -------------------------------------------------------------------------------- 1 | 文章同步到[github](https://github.com/sunzhaoye/blog/issues/10) 2 | #本文主要内容 3 | **(本文较长,请耐心阅读,必有收获):** 4 | 5 | * 什么是对象 6 | * 创建对象的几种方式 7 | * 使用构造函数创建 8 | * 字面量创建 9 | * 工厂模式 10 | * 构造模式 11 | * 原型模式 12 | * 原型 13 | * 组合使用构造函数模式和原型模式 14 | * 继承 15 | * 原型链 16 | * 属性查找机制 17 | * 经典继承 18 | * 个人扩展补充 19 | * hasOwnProperty() 20 | * 重写原型对象 21 | * 显式prototype和隐式[[Prototype]]属性 22 | 23 | # 什么是对象 24 | 25 | 直接上《JavaScript高级教程》的截图 26 | 27 | ![对象](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-09-23%20%E4%B8%8A%E5%8D%8810.03.29.png) 28 | 29 | **补充:** 30 | js中说一切都是对象,是不完全的,在js中6种数据类型(Undefined,Null,Number,Boolean,String,Object)中,前五种是基本数据类型,是原始值类型,这些值是在底层实现的,他们不是object,所以没有原型,没有构造函数,所以并不是像创建对象那样通过构造函数创建的实例。关于对象属性类型的介绍就不介绍了,可以看我上一篇文章[Object.defineProperty()和defineProperties()](https://segmentfault.com/a/1190000011294519) 31 | 32 | # 创建对象 33 | 34 | ## 1.使用构造函数创建 35 | ``` 36 | var obj = new Object(); 37 | ``` 38 | ## 2.字面量创建 39 | ``` 40 | var obj = {}; 41 | ``` 42 | ## 3.工厂模式 43 | 44 | 如果使用构造函数和字面量创建很多对象,每个对象本身又有很多相同的属性和方法的话,就会产生大量重复代码,每个对象添加属性都需要重新写一次。如两个对象都需要添加name、age属性及showName方法: 45 | ``` 46 | var p1 = new Object(); 47 | p1.name = '张三' 48 | p1.age = '16', 49 | p1.showName = function() { 50 | return this.name 51 | } 52 | 53 | var p2 = new Object(); 54 | p2.name = '李四' 55 | p2.age = '18', 56 | p2.showName = function() { 57 | return this.name 58 | } 59 | ``` 60 | 为了解决这个问题,人们采用了工厂模式,抽象了创建对象的过程,采用函数封装以特定接口(相同的属性和方法)创建对象的过程。 61 | 62 | ``` 63 | function createPerson(name, age) { 64 | var obj = new Object(); 65 | obj.name = name; 66 | obj.age = age; 67 | obj.showName = function () { 68 | return this.name; 69 | }; 70 | return obj; 71 | } 72 | 73 | var p1 = createPerson('张三', 16); 74 | var p2 = createPerson('李四', 18); 75 | ``` 76 | ## 4.构造模式 77 | 虽然工厂模式解决了创建多个对象的多个相同属性问题,却无法判定对象的具体类型,因为都是Object,无法识别是Array、或是Function等类型,这个时候构造函数模式出现了。 78 | 79 | js中提供了像Object,Array,Function等这样的原生的构造函数,同时也可以创建自定义的构造函数,构造函数是一个函数,用来创建并初始化新创建的对象。将工厂模式的例子用构造函数可以重写为: 80 | 81 | ``` 82 | function Person(name, age) { 83 | this.name = name; 84 | this.age = age; 85 | this.showName = function() { 86 | console.log(this.name); 87 | } 88 | } 89 | 90 | var p1 = new Person('张三', '16'); 91 | var p2 = new Person('李四', '18'); 92 | ``` 93 | 用Person代替了工厂模式的createPerson函数,而且函数名首字母P大写,这是因为按照惯例,构造函数首字母应该大写,而作为非构造函数的函数首字母小写。另外可以注意到构造函数内部的特点: 94 | 1. 没有显示创建对象 95 | 2. 直接在this上添加属性和方法 96 | 3. 没有return 97 | 98 | 另外,还使用了new操作, 要创建一个实例,必须使用new操作符,使用new操作符调用构造函数,在调用构造函数的时候经历了如下几个阶段: 99 | 100 | 1. 创建一个对象 101 | 2. 把创建的对象赋值给this 102 | 3. 执行函数中的代码, 即把属性和方法添加到赋值之后的this 103 | 4. 返回新对象 104 | 105 | 用***伪代码***来说明上述new Person()的过程如下: 106 | 107 | ``` 108 | // 使用new操作符时,会激活函数本身的内部属性[[Construct]],负责分配内存 109 | Person.[[Construct]](initialParameters): 110 | 111 | // 使用原生构造函数创建实例 112 | var Obj = new NativeObject() //NativeObject为原生构造函数,如Object、Array、Function等 113 | 114 | // 给创建的实例添加[[Class]]内部属性,字符串对象的一种表示, 如[Object Array] 115 | // Object.prototype.toString.call(obj)返回值指向的就是[[Class]]这个内部属性 116 | Obj.[[Class]] = Object/Array/Function; 117 | 118 | // 给创建的实例添加[[Prototype]]内部属性,指向构造函数的prototype 119 | O.[[Prototype]] = Person.prototype; 120 | 121 | // 调用构造函数内部属性[Call],将Person执行上下文中this设置为内部创建的对象Obj, 122 | this = Obj; 123 | result = Person.[[Call]](initialParameters); 124 | // result是如果构造函数内部如果存在返回值的话,调用[[call]]时作为返回值,一般为Object类型 125 | 126 | // 调用Person.[[call]]时,执行Person中的代码,给this对象添加属性和方法 127 | this.name = name; 128 | this.age = age; 129 | this.showName = function() { 130 | console.log(this.name); 131 | }; 132 | 133 | //如果Person.[[call]]的返回值result为Object类型 134 | return result 135 | // 否则 136 | return Obj; 137 | ``` 138 | 补充,贴出ECMAScript 5.1版本标准中[[Construct]]的规范,我本人对[[Call]]的返回值问题理解的也不好,希望哪位大神可以指点指点。 139 | 140 | ![construct](http://ovqwwz784.bkt.clouddn.com/Construct.png) 141 | 142 | 143 | 构造函数虽然解决了实例多个同名属性重复添加的问题,但是也存在每个实例的方法都需要重新创建一遍,因为每个方法都是Function的不同实例,看下面这段代码就明白了: 144 | ``` 145 | function Person(name, age) { 146 | this.name = name; 147 | this.age = age; 148 | this.showName = new Function("console.log(this.name);"); 149 | } 150 | 151 | var p1 = new Person('张三', '16'); 152 | var p2 = new Person('李四', '18'); 153 | console.log(p1.showName === p2.showName); //false 154 | ``` 155 | 这个问题可以用以下办法来解决,把showName变成全局函数 156 | ``` 157 | function Person(name, age) { 158 | this.name = name; 159 | this.age = age; 160 | this.showName = showName; 161 | } 162 | function showName() { 163 | console.log(this.name) 164 | } 165 | ``` 166 | 但是这样如果对象需要添加很多方法就会产生很多全局函数,这些问题可以通过原型模式来解决 167 | 168 | ## 5.原型模式 169 | 170 | ### 什么是原型 171 | 172 | 当每一个函数创建时,都会给函数设置一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包含所有实例共享的属性和方法,在默认情况下,都会为prototype对象添加一个constructor属性,指向该函数。 173 | 174 | ``` 175 | Person.prototype.constructor = Person; 176 | ``` 177 | 178 | 原型模式就是不必在构造函数中定义实例的属性和方法,而是将属性和方法都添加到原型对象中。创建自定义构造函数,其原型对象只会默认取得constructor属性,其他的属性和方法都是从Object继承来的。当使用构造函数创建一个实例之后,会给实例添加内部属性**[[prototype]]**,这个属性是一个指针,指向构造函数的prototype(原型)对象,由于是内部属性,无法通过脚本获取,但是在一些Chrome、Firefox、Safari等浏览器中在每个对象身上支持一个__proto__属性,指向的就是构造函数的原型对象。另外可以通过**isProtoTypeOf()**来判断创建的实例是否有指向某构造函数的指针,如果存在,返回true,如果不存在,返回false。 179 | 180 | ``` 181 | function Person() { 182 | 183 | } 184 | Person.prototype.name = '张三'; 185 | Person.prototype.friends = ['张三', '李四']; 186 | Person.prototype.showName = function() { 187 | console.log(this.name); 188 | } 189 | var p1 = new Person(); 190 | var p2 = new Person() 191 | console.log(p1.__proto__ === Person.prototype) // true 192 | console.log(Person.prototype.isPrototypeOf(p1)) // true 193 | ``` 194 | 195 | 在ECMA5中增加了一个方法**Object.getPrototypeOf(params)**,返回值就是创建对象的原型对象 196 | 197 | ``` 198 | console.log(Object.getPrototypeOf(p1) === Person.prototype); // true 199 | console.log(Object.getPrototypeOf(p1).name); //张三 200 | ``` 201 | 202 | 原型模式虽然解决了方法共享的问题,但是对于实例共享来说是个比较大的问题,因为每个实例都需要有描述自己本身特性的专有属性,还是上面的代码: 203 | 204 | ``` 205 | console.log(p1.name) // '张三' 206 | console.log(p2.name) // '张三' 207 | ``` 208 | 209 | 另外对于属性是引用类型的值来说缺点就更明显了,如果执行下面这段代码: 210 | 211 | ``` 212 | p1.friends.push('王五'); 213 | console.log(p1.priends); //['张三', '李四', '王五'] 214 | console.log(p2.priends); //['张三', '李四', '王五'] 215 | ``` 216 | 为了解决原型模式的问题,人们采用了原型和构造组合模式,使用构造函数定义实例,使用原型模式共享方法。 217 | 218 | ## 6组合使用构造函数模式和原型模式 219 | 直接上代码: 220 | 221 | ``` 222 | function Person(name, age) { 223 | this.name = name; 224 | this.age = age; 225 | this.friends = ['张三', '李四']; // this.friends = new Array('张三', '李四') 226 | } 227 | 228 | Person.prototype.showName = function() { 229 | console.log(this.name); 230 | }; 231 | 232 | var p1 = new Person('John'); 233 | var p2 = new Person('Alice'); 234 | p1.friends.push('王五'); 235 | console.log(p1.friends); // ['张三', '李四', '王五']; 236 | console.log(p2.friends); // ['张三', '李四']; 237 | // 因为这时候每个实例创建的时候的friends属性的指针地址不同,所以操作p1的friends属性并不会对p2的friends属性有影响 238 | 239 | console.log(p1.showName === p2.showName) // true 都指向了Person.prototype中的showName 240 | 241 | ``` 242 | 243 | 这种构造函数模式和原型模式组合使用,基本上可以说是js中面向对象开发的一种默认模式,介绍了以上这几种常用创建对象的方式, 还有其他不常用的模式就不介绍了,接下来想说的是js中比较重要的继承。 244 | 245 | # 继承 246 | 247 | ## 什么是原型链 248 | ECMA中继承的主要方法就是通过原型链,主要是一个原型对象等于另一个类型的实例,由于实例内部含有一个指向构造函数的指针,这时候相当于重写了该原型对象,此时该原型对象就包含了一个指向另一个原型的指针,假如另一个原型又是另一个类型的实例,这样就形成了原型链的概念,原型链最底层为Object.prototype.__proto__,为null。 249 | 250 | ## 属性查找机制 251 | 252 | js中实例属性的查找,是按照原型链进行查找,先找实例本身有没有这个属性,如果没有就去查找查找实例的原型对象,也就是[[prototype]]属性指向的原型对象,一直查到Object.prototype,如果还是没有该属性,返回undefined。所有函数的默认原型都是Object实例。 253 | 254 | ``` 255 | function Parent() { 256 | this.surname = '张'; 257 | this.name = '张三'; 258 | this.like = ['apple', 'banana']; 259 | } 260 | var par = new Parent() 261 | function Child() { 262 | this.name = '张小三'; 263 | } 264 | Parent.prototype.showSurname = function() { 265 | return this.surname 266 | } 267 | // 继承实现 268 | 269 | Child.prototype = new Parent(); 270 | 271 | var chi = new Child(); 272 | console.log(chi.showSurname()) // 张 273 | 274 | ``` 275 | 276 | 以上代码证明,此时Child实例已经可以访问到showSurname方法,这就是通过原型链继承Parent原型方法,剖析一下其过程: 277 | ``` 278 | Child.prototype = new Parent(); 279 | ``` 280 | 相当于重写了Child.prototype,指向了父实例par,同时也包含了父实例的[[prototype]]属性,此时 281 | 282 | ``` 283 | console.log(Child.prototype.__proto__ === par.__proto__); // true 284 | console.log(Child.prototype.__proto__ === Parent.prototype); // true 285 | ``` 286 | 执行chi.showSurname()时,根据属性查找机制: 287 | 288 | 1. 先从实例chi本身查找,有没有showSurname,没有 289 | 2. 继续查找chi的原型对象Child.prototype有没有showSurname,没有 290 | 3. 继续查找Child.prototype的原型指针__proto__有没有showSurname,此时Child.prototype.__proto__的指针地址指向Parent.prototype,找到了,所以 291 | ``` 292 | console.log(chi.showSurname()) // 张 293 | ``` 294 | 295 | **补充:** 296 | 所有函数默认继承Object: 297 | 298 | ``` 299 | function Person() { 300 | 301 | } 302 | console.log(Person.prototype.__proto__ === Object.prototype); // true 303 | ``` 304 | 305 | ## 构造函数模式和原型模式组合继承 306 | 只通过原型来实现继承,还存在一定问题,所以js中一般通过借用构造函数和原型组合的方式来实现继承,也称**经典继承**,还是继承那段代码,再贴过来把,方便阅读 307 | 308 | ``` 309 | function Parent() { 310 | this.surname = '张'; 311 | this.name = '张三'; 312 | this.like = ['apple', 'banana']; 313 | } 314 | var par = new Parent() 315 | function Child() { 316 | this.name = '张小三'; 317 | } 318 | Parent.prototype.showSurname = function() { 319 | return this.surname 320 | } 321 | // 继承实现 322 | 323 | Child.prototype = new Parent(); 324 | 325 | var chi1 = new Child(); 326 | var chi2 = new Child(); 327 | console.log(chi.showSurname()) // 张 328 | 329 | // 主要看继承的属性 330 | 331 | console.log(chi.like) // ['apple', 'banana'] 332 | 这是因为Child.prototype指向父实例,当查找实例chi本身没有like属性,就去查找chi的原型对象Child.prototype,所以找到了 333 | 334 | ``` 335 | 那么还存在什么问题呢,主要就是涉及到引用类型的属性时,引用类型数据的原始属性会被实例所共享,而实例本身的属性应该有实例自己的特性,还是以上代码 336 | ``` 337 | chi.like.push('orange'); 338 | console.log(chi1.like); // ['apple', 'banana', 'orange'] 339 | console.log(chi2.like); // ['apple', 'banana', 'orange'] 340 | ``` 341 | 所以构造函数和原型组合的经典继承出现了,也是本篇最重要的内容: 342 | 343 | **1.属性继承** 344 | 345 | 在子构造函数内,使用apply()或call()方法调用父构造函数,并传递子构造函数的this 346 | 347 | **2.方法继承** 348 | 349 | 使用上文提到的原型链继承,继承父构造器的方法 350 | 351 | 上代码: 352 | ``` 353 | function Parent(name) { 354 | this.name = name; 355 | this.like = ['apple', 'banana']; 356 | } 357 | Parent.prototype.showName = function() { 358 | 359 | console.log(this.name); 360 | }; 361 | 362 | function Child(name, age) { 363 | // 继承属性 364 | Parent.call(this, name); 365 | // 添加自己的属性 366 | this.age = age; 367 | } 368 | Child.prototype = new Parent(); 369 | // 子构造函数添加自己的方法 370 | Child.prototype.showAge = function() { 371 | console.log(this.age); 372 | }; 373 | 374 | var chi1 = new Child('张三', 16); 375 | var chi2 = new Child('李四', 18); 376 | 377 | chi1.showName(); //张三 378 | chi1.showAge(); //16 379 | chi1.like.push('orange'); 380 | console.log(chi1.like); // ['apple', 'banana', 'orange'] 381 | console.log(chi2.like); // ['apple', 'banana'] 382 | ``` 383 | 在子构造函数Child中是用call()调用Parent(),在new Child()创建实例的时候,执行Parent中的代码,而此时的this已经被call()指向Child中的this,所以新建的子实例,就拥有了父实例的全部属性,这就是继承属性的原理。对chi1和chi2的like属性,是每个实例自己的属性,二者间不存在引用依赖关系,所以操作chi.like并不会对chi.like造成影响。方法继承,就是上文讲的到的原型链机制继承,另外可以给子构造函数添加自己的属性和方法。 384 | 这就是经典继承,避免了但是使用构造函数或者单独使用原型链的缺陷,成为js中最常用的继承方式。 385 | 386 | # 个人扩展补充 387 | 388 | ## hasOwnProperty() 389 | 390 | **用法: obj.hasOwnProperty(prop)** 391 | 392 | 使用hasOwnProperty()方法可以判断访问的属性是原型属性还是实例属性,如果是实例属性返回true,否则返回false 393 | 394 | ``` 395 | function Person() { 396 | 397 | } 398 | Person.prototype.name = '张三' 399 | var p1 = new Person(); 400 | var p2 = new Person(); 401 | p1.name = '张三'; 402 | console.log(p1.hasOwnProperty('name')) //true 403 | console.log(p2.hasOwnProperty('name')) //false 404 | ``` 405 | ## 重写原型对象 406 | 407 | 在实际开发中,如果原型对象有很多方法,往往我们可以使用字面量的形式,重写原型,但是需要手工指定constructor属性 408 | 409 | ``` 410 | function Person(name, age) { 411 | this.name = name; 412 | this.age = age; 413 | } 414 | var p1 = new Person('张三', 16); 415 | Person.prototype.showName = function() { 416 | return this.name; 417 | } 418 | Person.prototype.showAge = function() { 419 | return this.age; 420 | } 421 | ``` 422 | 如果构造函数的prototype方法很多,可以采用字面量方式定义 423 | 424 | ``` 425 | Person.prototype = { 426 | constructor: Person, 427 | showName: function() { 428 | return this.name; 429 | }, 430 | showAge: function() { 431 | return this.age; 432 | } 433 | } 434 | ``` 435 | 注意这里面手动加了一个constructor属性指向Person构造函数,这是因为使用字面量重写原型对象,这个原型对象变成了一个Object的实例,原型对象本身已经不存在最初函数创建时初始化的constructor属性,这是原型对象的[[prototype]]指针指向了Object.prototype 436 | 437 | ## 显式prototype和隐式[[Prototype]]属性 438 | 439 | ``` 440 | function Person() { 441 | 442 | } 443 | 444 | Person.prototype.a = 10; 445 | var p = new Person(); 446 | 447 | console.log(p.a) //10 448 | 449 | Person.prototype = { 450 | constructor: Person, 451 | a: 20, 452 | b: 30 453 | } 454 | 455 | console.log(p.a) // 10 456 | console.log(p.b) // undefined 457 | 458 | 459 | var p2 = new Person(); 460 | console.log(p2.a) // 20 461 | console.log(p2.b) // 30 462 | ``` 463 | 464 | 因此,有的文章说“动态修改原型将影响所有的对象都会拥有新的原型”是错误的,新原型仅仅在原型修改以后的新创建对象上生效。 465 | 466 | 这里的主要规则是:对象的原型是对象的创建的时候创建的,并且在此之后不能修改为新的对象,如果依然引用到同一个对象,可以通过构造函数的显式prototype引用,对象创建以后,只能对原型的属性进行添加或修改。 467 | 468 | 以上就是我梳理出来的js中面向对象部分的相关概念和理解,依旧主要参考《JavaScript高教程》和[《深入理解JavaScript系列》](http://www.lai18.com/content/425668.html)文章,另外翻看了[ECMAScript5.1中文版](http://lzw.me/pages/ecmascript/#240)。本人对引用书中的概念和相关知识,为保证文章不误导大家,并不是拿来主义,希望本文能对大家有帮助,也希望大家多多指教。 469 | 470 | 471 | -------------------------------------------------------------------------------- /javaScript/Object.defineProperty和defineProperties.md: -------------------------------------------------------------------------------- 1 | >ECMAS-262第5版在定义只有内部采用的特性时,提供了描述了属性特征的几种属性。ECMAScript对象中目前存在的属性描述符主要有两种,数据描述符(数据属性)和存取描述符(访问器属性),数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。 2 | 3 | Object的**defineProperty**和**defineProperties**这两个方法在js中的重要性十分重要,主要功能就是用来**定义或修改这些内部属性**,与之相对应的**getOwnPropertyDescriptor**和**getOwnPropertyDescriptors**就是获取这行内部属性的描述。 4 | 5 | 下面文章我先介绍数据描述符和存取描述符的属性代表的含义,然后简单介绍以上四个方法的基本功能,这些如果了解可直接跳过,最后我会举例扩展及说明各内部属性在各种场景下产生的实际效果,那才是这篇文章的核心内容。本文章关于概念性的描述还是会尽量使用《javaScript高级教程》、MDN网站等概念,保证准确和易于大家理解,讲解部分则结合个人理解和举例说明。 6 | 7 | # 数据(数据描述符)属性 8 | 9 | 数据属性有4个描述内部属性的特性 10 | 11 | ## [[Configurable]] 12 | 表示能否通过[delete](https://segmentfault.com/a/1190000010574280)删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true 13 | 14 | ## [[Enumerable]] 15 | 表示该属性是否可枚举,即是否通过for-in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true 16 | ## [[Writable]] 17 | 能否修改属性的值,如果直接使用字面量定义对象,默认值为true 18 | ## [[Value]] 19 | 20 | 该属性对应的值,默认为undefined 21 | 22 | #访问器(存取描述符)属性 23 | 访问器属性也有4个描述内部属性的特性 24 | ## [[Configurable]] 25 | 和数据属性的[[Configurable]]一样,表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true 26 | 27 | ## [[Enumerable]] 28 | 和数据属性的[[Configurable]]一样,表示该属性是否可枚举,即是否通过for-in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true 29 | 30 | ## [[Get]] 31 | 一个给属性提供 getter 的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined 32 | 33 | ## [[Set]] 34 | 35 | 一个给属性提供 setter 的方法(给对象属性设置值时调用的函数),如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined 36 | 37 | # 创建/修改/获取属性的方法 38 | ## Object.defineProperty() 39 | **功能:** 40 | 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。如果不指定configurable, writable, enumerable ,则这些属性默认值为false,如果不指定value, get, set,则这些属性默认值为undefined 41 | ``` 42 | 语法: Object.defineProperty(obj, prop, descriptor) 43 | ``` 44 | **obj:** 需要被操作的目标对象 45 | **prop:** 目标对象需要定义或修改的属性的名称 46 | **descriptor:** 将被定义或修改的属性的描述符 47 | ``` 48 | var obj = new Object(); 49 | 50 | Object.defineProperty(obj, 'name', { 51 | configurable: false, 52 | writable: true, 53 | enumerable: true, 54 | value: '张三' 55 | }) 56 | 57 | console.log(obj.name) //张三 58 | ``` 59 | ## Object.defineProperties() 60 | **功能:** 61 | 方法直接在一个对象上定义一个或多个新的属性或修改现有属性,并返回该对象。 62 | ``` 63 | 语法: Object.defineProperties(obj, props) 64 | ``` 65 | **obj:** 将要被添加属性或修改属性的对象 66 | **props:** 该对象的一个或多个键值对定义了将要为对象添加或修改的属性的具体配置 67 | 68 | ``` 69 | var obj = new Object(); 70 | Object.defineProperties(obj, { 71 | name: { 72 | value: '张三', 73 | configurable: false, 74 | writable: true, 75 | enumerable: true 76 | }, 77 | age: { 78 | value: 18, 79 | configurable: true 80 | } 81 | }) 82 | 83 | console.log(obj.name, obj.age) // 张三, 18 84 | ``` 85 | 86 | ## Object.getOwnPropertyDescriptor() 87 | **功能:** 88 | 该方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性) 89 | ``` 90 | 语法: Object.getOwnPropertyDescriptor(obj, prop) 91 | ``` 92 | 93 | **obj:** 需要查找的目标对象 94 | **prop:** 目标对象内属性名称 95 | 96 | ``` 97 | var person = { 98 | name: '张三', 99 | age: 18 100 | } 101 | 102 | var desc = Object.getOwnPropertyDescriptor(person, 'name'); 103 | console.log(desc) 结果如下 104 | // { 105 | // configurable: true, 106 | // enumerable: true, 107 | // writable: true, 108 | // value: "张三" 109 | // } 110 | 111 | ``` 112 | 113 | ## Object. getOwnPropertyDescriptors() 114 | **功能:** 115 | 所指定对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。 116 | ``` 117 | 语法: Object.getOwnPropertyDescriptors(obj) 118 | ``` 119 | **obj:** 需要查找的目标对象 120 | 121 | ``` 122 | var person = { 123 | name: '张三', 124 | age: 18 125 | } 126 | var desc = Object.getOwnPropertyDescriptors(person, 'name'); 127 | console.log(desc) // 结果如下图 128 | 129 | ``` 130 | 131 | ![console结果](http://ovqwwz784.bkt.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-09-21%20%E4%B8%8A%E5%8D%8810.40.34.png) 132 | 133 | #各种场景下描述符属性的的扩展示例讲解 134 | 135 | ## configrubale 136 | 137 | 如果设置configrubale属性为false,则不可使用delete操作符(在严格模式下抛出错误), 修改所有内部属性值会抛出错误,在《javaScript高级教程中》说只可以改变writable的值,现在改变writable的值也会抛出错误 138 | ### 在对象中添加一个数据描述符属性 139 | ``` 140 | var person = {}; 141 | 142 | Object.defineProperty(person, 'name', { 143 | configurable: false, 144 | value: 'John' 145 | }) ; 146 | 147 | delete person.name // 严格模式下抛出错误 148 | 149 | console.log(person.name) // 'John' 没有删除 150 | 151 | Object.defineProperty(person, 'name', { 152 | configurable: true //报错 153 | }); 154 | 155 | Object.defineProperty(person, 'name', { 156 | enumerable: 2 //报错 157 | }); 158 | 159 | Object.defineProperty(person, 'name', { 160 | writable: true //报错 161 | }); 162 | 163 | Object.defineProperty(person, 'name', { 164 | value: 2 //报错 165 | }); 166 | ``` 167 | **注意:** 168 | 以上是最开始定义属性描述符时,writabl默认为false,才会出现上述效果,如果writable定义为true, 则可以修改[[writable]]和[[value]]属性值,修改另外两个属性值报错 169 | 170 | ``` 171 | var obj = {}; 172 | 173 | Object.defineProperty(obj, 'a', { 174 | configurable: false, 175 | writable: true, 176 | value: 1 177 | }); 178 | 179 | Object.defineProperty(obj, 'a', { 180 | // configurable: true, //报错 181 | // enumerable: true, //报错 182 | writable: false, 183 | value: 2 184 | }); 185 | var d = Object.getOwnPropertyDescriptor(obj, 'a') 186 | console.log(d); 187 | // { 188 | // value: 2, 189 | // writable: false, 190 | // } 191 | ``` 192 | 193 | ### 在对象中添加存取描述符属性 194 | 195 | ``` 196 | var obj = {}; 197 | var aValue; //如果不初始化变量, 不给下面的a属性设置值,直接读取会报错aValue is not defined 198 | var b; 199 | Object.defineProperty(obj, 'a', { 200 | configurable : true, 201 | enumerable : true, 202 | get: function() { 203 | return aValue 204 | }, 205 | set: function(newValue) { 206 | aValue = newValue; 207 | b = newValue + 1 208 | } 209 | }) 210 | console.log(b) // undefined 211 | console.log(obj.a) // undefined, 当读取属性值时,调用get方法,返回undefined 212 | obj.a = 2; // 当设置属性值时,调用set方法,aValue为2 213 | 214 | console.log(obj.a) // 2 读取属性值,调用get方法,此时aValue为2 215 | console.log(b) // 3 再给obj.a赋值时,执行set方法,b的值被修改为2,额外说一句,vue中的计算属性就是利用setter来实现的 216 | ``` 217 | **注意:** 218 | ***1.getter和setter可以不同时使用,但在严格模式下只其中一个,会抛出错误*** 219 | ***2.数据描述符与存取描述符不可混用,会抛出错误*** 220 | ``` 221 | var obj = {}; 222 | Object.defineProperty(obj, 'a', { 223 | value: 'a1', 224 | get: function() { 225 | return 'a2' 226 | } 227 | }); 228 | ``` 229 | 230 | ***3.全局环境下:*** 231 | ``` 232 | var a = 1; // a属于window, 相当于window.a 233 | ``` 234 | 让我们来看看a的描述符属性 235 | ``` 236 | var d = Object.getOwnPropertyDescriptor(window, 'a'); 237 | console.log(d) 238 | // { 239 | // configurable: false, 240 | // value: 1, 241 | // writable: true, 242 | // enumerable: true 243 | // } 244 | ``` 245 | 在来看一下另一种不适用var声明的方式初始化a变量 246 | 247 | ``` 248 | a = 1; //a相当于window的一个属性, window.a 249 | ``` 250 | 再来看看此时a的描述符属性 251 | ``` 252 | var d = Object.getOwnPropertyDescriptor(window, 'a'); 253 | console.log(d) 254 | // { 255 | // configurable: true, // 此时configurable属性值为true 256 | // value: 1, 257 | // writable: true, 258 | // enumerable: true 259 | // } 260 | ``` 261 | **注意:** 262 | 263 | 只有使用var, let等操作符才是定义变量,而不使用var,直接a=1;,这样a的含义为window的一个属性,并不是我们所说的变量的概念。使用 var定义的任何变量,其configurable属性值都为false,定义对象也是一样 264 | 265 | ``` 266 | var b = { 267 | name: 'bbb' 268 | } 269 | var d = Object.getOwnPropertyDescriptor(window, 'b'); 270 | console.log(d) 271 | // { 272 | // configurable: false 273 | // value: 1, 274 | // writable: true, 275 | // enumerable: true 276 | // } 277 | ``` 278 | 但是这里需要说明的一点是,使用字面量定义的对象,该对象内部的属性的数据描述符属性都为true 279 | ``` 280 | var b = { 281 | name: 'bbb' 282 | } 283 | var d = Object.getOwnPropertyDescriptor(b, 'name'); 284 | console.log(d) 285 | // { 286 | // configurable: true 287 | // writable: true, 288 | // enumerable: true 289 | // value: 'bbb' 290 | // } 291 | ``` 292 | ## Writable 293 | 294 | 当writable为false(并且configrubale为true),[[value]]可以通过defineeProperty修改, 但不能直接赋值修改 295 | ``` 296 | var obj = {}; 297 | 298 | Object.defineProperty(obj, 'a', { 299 | configurable: true, 300 | enumerable: false, 301 | writable: false, 302 | value: 1 303 | }); 304 | 305 | Object.defineProperty(obj, 'a', { 306 | configurable: false, 307 | enumerable: true, 308 | writable: false , 309 | value: 2 310 | }); 311 | var d = Object.getOwnPropertyDescriptor(obj, 'a') 312 | 313 | console.log(d); // 结果如下 314 | // { 315 | // value: 2, 316 | // writable: false, 317 | // enumerable: true, 318 | // configurable: false 319 | // } 320 | 321 | 322 | 但是如果直接复制修改 323 | var obj = {} 324 | 325 | Object.defineProperty(obj, 'a', { 326 | configurable: true, 327 | enumerable: false, 328 | writable: false, 329 | value: 1 330 | }); 331 | o.a=2; 332 | var d = Object.getOwnPropertyDescriptor(obj, 'a') 333 | 334 | console.log(d); // 结果如下 335 | 336 | // { 337 | // value: 1, // 没有做出修改 338 | // writable: false, 339 | // enumerable: true, 340 | // configurable: false 341 | // } 342 | ``` 343 | 344 | ## Enumerable 345 | 346 | 直接上例子 347 | 348 | ``` 349 | var obj = {}; 350 | Object.defineProperties(obj, { 351 | a: { 352 | value: 1, 353 | enumerable: false 354 | }, 355 | b: { 356 | value: 2, 357 | enumerable: true 358 | }, 359 | c: { 360 | value: 3, 361 | enumerable: false 362 | } 363 | }) 364 | 365 | obj.d = 4; 366 | 367 | //等同于 368 | 369 | //Object.defineProperty(obj, 'd', { 370 | // configurable: true, 371 | // enumerable: true, 372 | // writable: true, 373 | // value: 4 374 | //}) 375 | 376 | for(var key in obj) { 377 | console.log(key); 378 | // 打印一次b, 一次d, a和c属性enumerable为false,不可被枚举 379 | } 380 | 381 | var arr = Object.keys(obj); 382 | console.log(arr); // ['b', 'd'] 383 | 384 | ``` 385 | ## get和set 386 | 387 | ### 简易的数据双向绑定 388 | 389 | 在线demo地址: http://www.sunzhaoye.com/demo/index.html 390 | 391 | html代码: 392 | ``` 393 | 394 |

395 | input1=> 396 |

397 |

398 | input2=> 399 | 400 |

401 |
402 | 我每次比input1的值加1=> 403 | 404 |
405 | 406 | ``` 407 | js代码: 408 | ``` 409 | var oInput1 = document.getElementById('input1'); 410 | var oInput2 = document.getElementById('input2'); 411 | var oSpan = document.getElementById('span'); 412 | var obj = {}; 413 | Object.defineProperties(obj, { 414 | val1: { 415 | configurable: true, 416 | get: function() { 417 | oInput1.value = 0; 418 | oInput2.value = 0; 419 | oSpan.innerHTML = 0; 420 | return 0 421 | }, 422 | set: function(newValue) { 423 | oInput2.value = newValue; 424 | oSpan.innerHTML = Number(newValue) ? Number(newValue) : 0 425 | } 426 | }, 427 | val2: { 428 | configurable: true, 429 | get: function() { 430 | oInput1.value = 0; 431 | oInput2.value = 0; 432 | oSpan.innerHTML = 0; 433 | return 0 434 | }, 435 | set: function(newValue) { 436 | oInput1.value = newValue; 437 | oSpan.innerHTML = Number(newValue)+1; 438 | } 439 | } 440 | }) 441 | oInput1.value = obj.val1; 442 | oInput1.addEventListener('keyup', function() { 443 | obj.val1 = oInput1.value; 444 | }, false) 445 | oInput2.addEventListener('keyup', function() { 446 | obj.val2 = oInput2.value; 447 | }, false) 448 | ``` 449 | # 总结 450 | 终于到了最后了,就不具体梳理总结了。虽然我们在开过过程中不怎么使用几种方法,但理解之后对于我们理解js中对象有很大帮助,对后续进步也很有帮助,比如vue的实现原理等。个人能力有限,还希望大家发现问题后能多多指点,共同进步。 -------------------------------------------------------------------------------- /javaScript数据结构与算法/冒泡排序.md: -------------------------------------------------------------------------------- 1 | ### 1.冒泡排序 2 | 3 | >![算法-冒泡排序概念](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E6%A6%82%E5%BF%B5.png) 4 | 5 | **冒泡排序的执行过程** 6 | 7 | ![算法-冒泡排序执行过程](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.png) 8 | 9 | 10 | **代码如下:** 11 | 12 | ``` 13 | let arr = [5, 4, 3, 2, 1] 14 | 15 | // 交换元素的位置 16 | function swap(arr, index1, index2) { 17 | var temp = arr[index1] 18 | arr[index1] = arr[index2] 19 | arr[index2] = temp 20 | } 21 | 22 | function bubbleSort(arr) { 23 | for (let i = 0; i < arr.length - 1; i++) { 24 | for (let j = 0; j < arr.length - i - 1; j++) { 25 | if (arr[j] > arr[j + 1]) { 26 | swap(arr, j, j + 1) 27 | } 28 | } 29 | } 30 | } 31 | 32 | bubbleSort(arr) 33 | console.log(arr) // [1, 2, 3, 4, 5] 34 | ``` -------------------------------------------------------------------------------- /javaScript数据结构与算法/插入排序.md: -------------------------------------------------------------------------------- 1 | ### 3.插入排序 2 | 3 | >![算法-插入排序概念](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%A6%82%E5%BF%B5.png) 4 | 5 | **插入排序的执行过程** 6 | 7 | ![算法-插入排序执行过程](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.png) 8 | 9 | **代码如下:** 10 | 11 | 12 | ``` 13 | let arr = [5, 4, 3, 2, 1] 14 | 15 | function swap(arr, index1, index2) { 16 | var temp = arr[index1] 17 | arr[index1] = arr[index2] 18 | arr[index2] = temp 19 | } 20 | 21 | function insertSort(arr) { 22 | for (let i = 1; i < arr.length; i++) { 23 | let j = i 24 | let temp = arr[i] 25 | while (j > 0 && arr[j - 1] > temp) { 26 | arr[j] = arr[j - 1] 27 | j-- 28 | } 29 | arr[j] = temp 30 | } 31 | } 32 | 33 | insertSort(arr) 34 | console.log(arr) // [1, 2, 3, 4, 5] 35 | ``` -------------------------------------------------------------------------------- /javaScript数据结构与算法/栈.md: -------------------------------------------------------------------------------- 1 | ## 栈 2 | 3 | >栈是一种遵从后进先出LIFO(Last In First Out,后进先出)原则的有序集合。新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。 4 | 5 | 定义一个栈的类,并为该栈声明一些方法,存储数据的底层数据结构使用数组 6 | 7 | ``` 8 | class Stack { 9 | constructor() { 10 | this.dataStore = [] 11 | } 12 | 13 | // 向栈中添加一个或多个元素到栈顶 14 | push() { 15 | for (let i = 0; i < arguments.length; i++) { 16 | this.dataStore.push(arguments[i]) 17 | } 18 | } 19 | 20 | // 移出栈顶元素,并返回被移出的元素 21 | pop() { 22 | return this.dataStore.pop() 23 | } 24 | 25 | // 返回栈顶元素,不对栈做修改 26 | peek() { 27 | return this.dataStore[this.dataStore.length - 1] 28 | } 29 | 30 | // 判断栈是否为空,如果为空返回true,否则返回false 31 | isEmpty() { 32 | return this.dataStore.length === 0 33 | } 34 | 35 | // 清空栈 36 | clear() { 37 | this.dataStore = [] 38 | } 39 | 40 | // 返回栈中元素的个数 41 | 42 | size() { 43 | return this.dataStore.length 44 | } 45 | } 46 | 47 | // 栈的操作 48 | 49 | let stack = new Stack() 50 | 51 | stack.push(1, 2, 3) 52 | console.log(stack.dataStore) // [1, 2, 3] 53 | console.log(stack.pop()) // 3 54 | console.log(stack.dataStore) // [1, 2] 55 | console.log(stack.peek()) // 2 56 | console.log(stack.dataStore) // [1, 2] 57 | console.log(stack.size()) // 2 58 | console.log(stack.isEmpty()) // false 59 | stack.clear() 60 | console.log(stack.dataStore) // [] 61 | console.log(stack.isEmpty()) // true 62 | console.log(stack.size()) // 0 63 | 64 | ``` 65 | 66 | **栈的应用** 67 | 68 | 本文举书中一个进制转换的例子并稍作修改,栈的类还是使用上面定义的Stack 69 | 70 | ``` 71 | function transformBase(target, base) { 72 | let quotient; // 商 73 | let remainder; // 余数 74 | let binaryStr = ''; // 转换后的值 75 | let digits = '0123456789ABCDEF' // 对转换为16进制数做处理 76 | let stack = new Stack() 77 | while(target > 0) { 78 | remainder = target % base 79 | stack.dataStore.push(remainder) 80 | target = Math.floor(target / base) 81 | } 82 | 83 | while(!stack.isEmpty()) { 84 | binaryStr += digits[stack.dataStore.pop()].toString() 85 | } 86 | 87 | return binaryStr 88 | } 89 | 90 | console.log(transformBase(10, 2)) // 1010 91 | console.log(transformBase(10, 8)) // 12 92 | console.log(transformBase(10, 16)) // A 93 | ``` -------------------------------------------------------------------------------- /javaScript数据结构与算法/树.md: -------------------------------------------------------------------------------- 1 | ## 树 2 | 3 | > 一个树结构包含一系列存在父子关系的节点。每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点 4 | ![数据结构-树的相关术语](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%A0%91%E7%9A%84%E7%9B%B8%E5%85%B3%E6%9C%AF%E8%AF%AD.png) 5 | 6 | 7 | ### 二叉树 8 | 9 | > ![数据结构-二叉树概念](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E6%A6%82%E5%BF%B5.png) 10 | 11 | ``` 12 | 13 | // 创建一个键 14 | function createNode(key) { 15 | this.key = key 16 | this.left = null 17 | this.right = null 18 | } 19 | 20 | // 向树中插入键 21 | function insertNode(node, newNode) { 22 | if (newNode.key < node.key) { 23 | if (node.left === null) { 24 | node.left = newNode 25 | } else { 26 | insertNode(node.left, newNode) 27 | } 28 | } else { 29 | if (node.right === null) { 30 | node.right = newNode 31 | } else { 32 | insertNode(node.right, newNode) 33 | } 34 | } 35 | } 36 | 37 | // 遍历回调 38 | function printNode(value) { 39 | console.log(value) 40 | } 41 | 42 | // 中序遍历 43 | function inOrderTraverseNode(node, callback) { 44 | if (node !== null) { 45 | inOrderTraverseNode(node.left, callback) 46 | callback(node.key) 47 | // debugger 可以加入debugger,用浏览器控制观察Call Stack(执行环境栈)来分析程序执行过程 48 | inOrderTraverseNode(node.right, callback) 49 | } 50 | } 51 | 52 | // 先序遍历 53 | function prevOrderTraverseNode(node, callback) { 54 | if (node !== null) { 55 | // 先访问节点本身 56 | callback(node.key) 57 | // 再访问左侧节点 58 | prevOrderTraverseNode(node.left, callback) 59 | // 然后再访问右侧节点 60 | prevOrderTraverseNode(node.right, callback) 61 | } 62 | } 63 | 64 | // 后序遍历 65 | function postOrderTraverseNode(node, callback) { 66 | if (node !== null) { 67 | // 先访问左侧节点 68 | postOrderTraverseNode(node.left, callback) 69 | // 再访问右侧节点 70 | postOrderTraverseNode(node.right, callback) 71 | // 然后再访问节点本身 72 | callback(node.key) 73 | } 74 | } 75 | 76 | class BinarySearchTree { 77 | constructor() { 78 | this.key = null 79 | } 80 | insert(key) { 81 | let newNode = new createNode(key) 82 | if (this.key === null) { 83 | this.key = newNode 84 | } else { 85 | insertNode(this.key, newNode) 86 | } 87 | } 88 | // 中序遍历访问节点(结果为按值由小到大访问) 89 | inOrderTraverse(callback) { 90 | inOrderTraverseNode(this.key, callback) 91 | } 92 | // 先序遍历访问节点(结果为先访问节点本身,再左侧节点,然后再访问右侧节点) 93 | prevOrderTraverse(callback) { 94 | prevOrderTraverseNode(this.key, callback) 95 | } 96 | // 后序遍历访问节点(结果为先访问左侧节点,再访问右侧节点,然后再访问节点本身) 97 | postOrderTraverse(callback) { 98 | postOrderTraverseNode(this.key, callback) 99 | } 100 | // 查找树中的最小值 101 | findMin(node) { 102 | if (node) { 103 | while(node && node.left !== null) { 104 | node = node.left 105 | } 106 | return node.key 107 | } 108 | return null 109 | } 110 | // 查找树中的最小值对应的节点 111 | findMinNode(node) { 112 | if (node) { 113 | while(node && node.left !== null) { 114 | node = node.left 115 | } 116 | return node 117 | } 118 | return null 119 | } 120 | 121 | // 查找树中的最大值 122 | findMax(node) { 123 | if (node) { 124 | while(node && node.right !== null) { 125 | node = node.right 126 | } 127 | return node.key 128 | } 129 | return null 130 | } 131 | 132 | // 查找树中的特定值,如果存在返回true,否则返回false 133 | search(node, key) { 134 | if (node === null) { 135 | return false 136 | } 137 | 138 | if (key < node.key) { 139 | // 如果被查找的key小于节点值,从节点的左侧节点继续递归查找 140 | return this.search(node.left, key) 141 | } else if (key > node.key) { 142 | // 如果被查找的key大于节点值,从节点的左侧节点继续递归查找 143 | return this.search(node.right, key) 144 | } else { 145 | // 被查找的key等于node.key 146 | return true 147 | } 148 | } 149 | 150 | // 移除树中的特定节点 151 | removeNode(node, key) { 152 | if (node === null) { 153 | return null 154 | } 155 | 156 | if (key < node.key) { 157 | node.left = this.removeNode(node.left, key) 158 | } else if (key > node.key) { 159 | node.right = this.removeNode(node.right, key) 160 | } else { 161 | // console.log(node) 162 | // 移除叶子节点(无左右节点的节点) 163 | if (node.left === null && node.right === null) { 164 | node = null 165 | return node 166 | } 167 | 168 | // 移除只有一个节点的节点(只有左节点或只有右节点) 169 | if (node.left === null) { 170 | node = node.right 171 | return node 172 | } else if (node.right === null) { 173 | node = node.left 174 | return node 175 | } 176 | 177 | // 移除有两个节点(既有左节点又有右节点) 178 | if (node.left && node.right) { 179 | // 1. 找到被移除节点的右节点下的最小节点,替换被移除的节点 180 | let minRightNode = this.findMinNode(node.right) 181 | // 2. 把被移除节点的key设置为 被移除节点的右节点下的最小节点的key 182 | node.key = minRightNode.key 183 | // 3. 移除找到的那个最小节点 184 | this.removeNode(node.right, node.key) 185 | // 4. 向被移除节点的父节点返回更新后节点的引用 186 | return node 187 | } 188 | } 189 | } 190 | } 191 | 192 | ``` 193 | 194 | **测试如下:** 195 | ``` 196 | 197 | let tree = new BinarySearchTree() 198 | tree.insert(11) 199 | tree.insert(7) 200 | tree.insert(15) 201 | tree.insert(5) 202 | tree.insert(6) 203 | tree.insert(3) 204 | tree.insert(9) 205 | tree.insert(8) 206 | tree.insert(10) 207 | tree.insert(13) 208 | tree.insert(20) 209 | tree.insert(12) 210 | tree.insert(14) 211 | tree.insert(18) 212 | tree.insert(25) 213 | 214 | tree.inOrderTraverse(printNode) // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25 215 | tree.prevOrderTraverse(printNode) // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25 216 | tree.postOrderTraverse(printNode) // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11 217 | 218 | // tree.key为根节点,为了保持树不同层的结构一致,没有使用root为属性,使用了key 219 | let minNodeVal = tree.findMin(tree.key) 220 | console.log('minNodeVal', minNodeVal) 221 | 222 | let maxNodeVal = tree.findMax(tree.key) 223 | console.log('maxNodeVal', maxNodeVal) 224 | 225 | let isHasNodeVal = tree.search(tree.key, 7) 226 | console.log(isHasNodeVal) // true 227 | 228 | tree.removeNode(tree.key, 15) 229 | console.log(tree) // 可以查看树的结构,15的这个节点的key已经被替换为18,并且key为18的节点已经被删除 230 | ``` 231 | 232 | #### 树的遍历 233 | 234 | **1. 中序遍历** 235 | 236 | ![数据结构-二叉树中序遍历图](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E5%9B%BE.png) 237 | 238 | **2. 先序遍历** 239 | 240 | ![数据结构-二叉树先序遍历图](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%85%88%E5%BA%8F%E9%81%8D%E5%8E%86%E5%9B%BE.png) 241 | 242 | **3. 后序遍历** 243 | 244 | ![数据结构-二叉树后序遍历图](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%9B%BE.png) 245 | 246 | 247 | #### 移除节点的过程 248 | 249 | **1. 移除以一个叶节点** 250 | ![数据结构-二叉树移除节点(叶子节点)](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%A7%BB%E9%99%A4%E8%8A%82%E7%82%B9%28%E5%8F%B6%E5%AD%90%E8%8A%82%E7%82%B9%29.png) 251 | 252 | **2. 移除只有一个左侧子节点或右侧子节点的节点** 253 | ![数据结构-二叉树移除节点(只有一个左侧子节点或右侧子节点)](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%A7%BB%E9%99%A4%E8%8A%82%E7%82%B9%28%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%B7%A6%E4%BE%A7%E5%AD%90%E8%8A%82%E7%82%B9%E6%88%96%E5%8F%B3%E4%BE%A7%E5%AD%90%E8%8A%82%E7%82%B9%29.png) 254 | 255 | **3. 移除有两个子节点的节点** 256 | ![http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%A7%BB%E9%99%A4%E8%8A%82%E7%82%B9%28%E6%9C%89%E4%B8%A4%E4%B8%AA%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E8%8A%82%E7%82%B9%29.png](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%A7%BB%E9%99%A4%E8%8A%82%E7%82%B9%28%E6%9C%89%E4%B8%A4%E4%B8%AA%E5%AD%90%E8%8A%82%E7%82%B9%E7%9A%84%E8%8A%82%E7%82%B9%29.png) 257 | -------------------------------------------------------------------------------- /javaScript数据结构与算法/选择排序.md: -------------------------------------------------------------------------------- 1 | ### 2.选择排序 2 | 3 | >![算法-选择排序概念](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E6%A6%82%E5%BF%B5.png) 4 | 5 | **选择排序的执行过程** 6 | 7 | ![算法-选择排序执行过程](http://qiniu.sunzhaoye.com/%E7%AE%97%E6%B3%95-%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.png) 8 | 9 | 10 | **代码如下:** 11 | 12 | ``` 13 | let arr = [5, 4, 3, 2, 1] 14 | 15 | function swap(arr, index1, index2) { 16 | var temp = arr[index1] 17 | arr[index1] = arr[index2] 18 | arr[index2] = temp 19 | } 20 | 21 | function changeSort(arr) { 22 | for (let i = 0; i < arr.length - 1; i++) { 23 | let minIndex = i 24 | for (let j = i; j < arr.length; j++) { 25 | if (arr[minIndex] > arr[j]) { 26 | minIndex = j 27 | } 28 | } 29 | 30 | if (i !== minIndex) { 31 | swap(arr, i, minIndex) 32 | } 33 | } 34 | } 35 | 36 | changeSort(arr) 37 | console.log(arr) // [1, 2, 3, 4, 5] 38 | 39 | ``` -------------------------------------------------------------------------------- /javaScript数据结构与算法/链表.md: -------------------------------------------------------------------------------- 1 | ## 链表 2 | 3 | > ![数据结构-链表](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8.png) 4 | 5 | 直接上代码: 6 | 7 | ``` 8 | class LinkedList { 9 | constructor() { 10 | this.head = null // 链表的第一个元素 11 | this.length = 0 12 | } 13 | // 向链表尾部添加一个新元素 14 | append(element) { 15 | let Node = function(element) { 16 | this.element = element 17 | this.next = null 18 | } 19 | let node = new Node(element) 20 | let currentNode; 21 | if (this.head == null) { 22 | // 如果链表head为null,表示链表无元素,直接把node赋值给head即可 23 | this.head = node 24 | } else { 25 | currentNode = this.head 26 | while (currentNode.next) { 27 | // 每次循环会进行到链表的倒数第一个元素,把currentNode设置为倒数第一个元素 28 | currentNode = currentNode.next 29 | } 30 | // 把新增的node赋值给currentNode的next属性,最后一个元素的next永远为null 31 | currentNode.next = node 32 | } 33 | // 链表的元素个数每次append后 +1 34 | this.length++ 35 | } 36 | // 从链表中按位置删除元素 37 | removeAt(position) { 38 | // position表示要移除元素的位置 39 | let index = 0 40 | let previous = null 41 | let currentNode = this.head 42 | if (position >= 0 && position < this.length) { 43 | while (index < position) { 44 | // 主要是找出position位置的元素,设置为currentNode 45 | previous = currentNode 46 | currentNode = currentNode.next 47 | index++ 48 | } 49 | // 把currentNode的上一个元素的next指向currentNode的下一个元素,就对应删除了currentNode 50 | previous.next = currentNode.next 51 | } else { 52 | // 表示链表中不存在这个元素,直接return null 53 | return null 54 | } 55 | // 删除后链表的元素个数每次删除减1 56 | this.length--; 57 | // 返回删除的元素 58 | return currentNode 59 | } 60 | // 按元素值删除元素 61 | remove(element) { 62 | let index = this.indexOf(element) 63 | return this.removeAt(index) 64 | } 65 | // 向链表中插入新元素 66 | insert(element, position) { 67 | // element表示被插入元素的具体值 68 | // position表示被插入元素的位置 69 | if (position >= 0 && position < this.length) { 70 | let index = 0 71 | let previous = null 72 | let currentNode = this.head 73 | let Node = function(element) { 74 | this.element = element 75 | this.next = null 76 | } 77 | let node = new Node(element) 78 | while (index < position) { 79 | previous = currentNode 80 | currentNode = currentNode.next 81 | index++ 82 | } 83 | // 把当前元素的上一个元素的next设置为被插入的元素 84 | previous.next = node 85 | // 把被插入元素的next设置为当前元素 86 | node.next = currentNode 87 | // 链表元素个数加1 88 | this.length++; 89 | // 如果插入元素成功,返回true 90 | return true 91 | } else { 92 | // 如果找不到插入元素位置,返回false 93 | return false 94 | } 95 | } 96 | // 查找元素在链表中的位置 97 | indexOf(element) { 98 | let currentNode = this.head 99 | let index = 0 100 | // 如果currentNode也就是head为空,则链表为空不会进入while循环,直接返回 -1 101 | while (currentNode) { 102 | if (element === currentNode.element) { 103 | // 如果被找到,返回当前index 104 | return index 105 | } 106 | // 每一轮循环如果被查找元素还没有被找到,index后移一位,currentNode指向后一位元素,继续循环 107 | index++ 108 | currentNode = currentNode.next 109 | } 110 | // 如果一直while循环结束都没找到返回 -1 111 | return -1 112 | } 113 | // 链表是否为空 114 | isEmpty() { 115 | return this.length === 0 116 | } 117 | // 链表元素个数 118 | size() { 119 | return this.length 120 | } 121 | } 122 | 123 | let linkedList = new LinkedList() 124 | 125 | linkedList.append('a') 126 | linkedList.append('b') 127 | linkedList.append('c') 128 | linkedList.append('d') 129 | linkedList.removeAt(2) 130 | linkedList.insert('e', 2) 131 | console.log('bIndex', linkedList.indexOf('b')) 132 | console.log('fIndex', linkedList.indexOf('f')) 133 | linkedList.remove('d') 134 | console.log(linkedList) 135 | 136 | ``` 137 | 138 | 上述代码测试结果如下图所示: 139 | 140 | ![数据结构-链表测试](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8%E6%B5%8B%E8%AF%95.png) -------------------------------------------------------------------------------- /javaScript数据结构与算法/队列.md: -------------------------------------------------------------------------------- 1 | ## 队列 2 | 3 | >队列是遵循FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。 4 | 5 | 其实队列和栈类似,只是原则不同,队列是先进先出,用代码来实现一个队列及操作队列的一些方法,以下用代码实现一个队列的类, 测试和栈类似,就不做具体测试了。 6 | 7 | ``` 8 | class Queue { 9 | constructor() { 10 | this.dataStore = [] 11 | } 12 | 13 | // 入队 14 | enqueue() { 15 | for (let i = 0; i < arguments.length; i++) { 16 | this.dataStore.push(arguments[i]) 17 | } 18 | } 19 | 20 | // 出队 21 | dequeue() { 22 | return this.dataStore.shift() 23 | } 24 | 25 | // 返回队列第一个元素,不改变队列 26 | front() { 27 | return this.dataStore[0] 28 | } 29 | 30 | // 队列是否为空 31 | isEmpty() { 32 | return this.dataStore.length === 0 33 | } 34 | 35 | // 返回队列的的元素个数 36 | size() { 37 | return this.dataStore.length 38 | } 39 | } 40 | ``` 41 | 42 | ### 优先队列 43 | 44 | 队列中在生活中有着大量应用,如登机时,商务舱要优于经济舱,这时候可以给队列中的元素设置优先级,下面用代码来实现一个优先队列的类 45 | 46 | ``` 47 | class PriorityQueue { 48 | 49 | constructor() { 50 | this.dataStore = [] 51 | } 52 | 53 | isEmpty() { 54 | return this.dataStore.length === 0 55 | } 56 | 57 | enqueue(element, priority) { 58 | 59 | function QueueElement(element, priority) { 60 | this.element = element 61 | this.priority = priority 62 | } 63 | // 定义每次往队列里添加的元素 64 | let queueElement = new QueueElement(element, priority) 65 | 66 | if (this.isEmpty()) { 67 | // 如果每次队列为空直接添加到队列中 68 | this.dataStore.push(queueElement) 69 | } else { 70 | // 定一个是否被添加到队列的标志 71 | let isAdded = false 72 | for (let i = 0; i < this.dataStore.length; i++) { 73 | if (queueElement.priority < this.dataStore[i].priority) { 74 | // 优先级数值越小,代表优先级越高 75 | this.dataStore.splice(i, 0, queueElement) 76 | isAdded = true 77 | break; 78 | } 79 | } 80 | 81 | if (!isAdded) { 82 | // 如果被添加的新元素优先级最低,添加到队尾 83 | this.dataStore.push(queueElement) 84 | } 85 | } 86 | } 87 | // 88 | } 89 | 90 | let priorityQueue = new PriorityQueue() 91 | 92 | priorityQueue.enqueue('a', 5) 93 | priorityQueue.enqueue('b', 2) 94 | priorityQueue.enqueue('c', 3) 95 | 96 | console.log(priorityQueue.dataStore) 97 | ``` 98 | 最后的队列如下图: 99 | 100 | ![数据结构-优先队列测试](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97%E6%B5%8B%E8%AF%95.png) 101 | -------------------------------------------------------------------------------- /javaScript数据结构与算法/集合.md: -------------------------------------------------------------------------------- 1 | ## 集合 2 | 3 | > ![数据结构-集合](http://qiniu.sunzhaoye.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%9B%86%E5%90%88.png) 4 | 5 | ``` 6 | class Set { 7 | constructor() { 8 | this.items = {} 9 | } 10 | 11 | has(val) { 12 | return val in this.items 13 | } 14 | 15 | // 向集合中添加一个新的项 16 | add(val) { 17 | this.items[val] = val 18 | } 19 | 20 | // 从集合中移除指定项 21 | remove(val) { 22 | if (val in this.items) { 23 | delete this.items[val] 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | // 清空集合 30 | clear() { 31 | this.items = {} 32 | } 33 | 34 | // 返回集合中有多少项 35 | size() { 36 | return Object.keys(this.items).length 37 | } 38 | 39 | // 提取items对象的所有属性,以数组的形式返回 40 | values() { 41 | return Object.keys(this.items) 42 | } 43 | 44 | // 取当前集合与其他元素的并集 45 | union(otherSet) { 46 | let unionSet = new Set() 47 | let values = this.values() 48 | for (let i = 0; i < values.length; i++) { 49 | unionSet.add(values[i]) 50 | } 51 | 52 | let valuesOther = otherSet.values() 53 | for (let i = 0; i < valuesOther.length; i++) { 54 | unionSet.add(valuesOther[i]) 55 | } 56 | return unionSet 57 | } 58 | 59 | // 取当前集合与其他元素的交集 60 | intersection(otherSet) { 61 | let intersectionSet = new Set() 62 | let values = this.values() 63 | for (let i = 0; i < values.length; i++) { 64 | if (otherSet.has(values[i])) { 65 | intersectionSet.add(values[i]) 66 | } 67 | } 68 | return intersectionSet 69 | } 70 | 71 | // 取当前集合与其他元素的差集 72 | diff(otherSet) { 73 | let intersectionSet = new Set() 74 | let values = this.values() 75 | for (let i = 0; i < values.length; i++) { 76 | if (!otherSet.has(values[i])) { 77 | intersectionSet.add(values[i]) 78 | } 79 | } 80 | return intersectionSet 81 | } 82 | 83 | // 判断当前集合是否是其他集合的子集 84 | isSubSet(otherSet) { 85 | // 如果当前集合项的个数大于被比较的otherSet的项的个数,则可判断当前集合不是被比较的otherSet的子集 86 | if (this.size() > otherSet.size()) { 87 | return false 88 | } else { 89 | let values = this.values() 90 | for (let i = 0; i < values.length; i++) { 91 | // 只要当前集合有一项不在otherSet中,则返回false 92 | if (!otherSet.has(values[i])) { 93 | return false 94 | } 95 | } 96 | // 循环判断之后,当前集合每一项都在otherSet中,则返回true 97 | return true 98 | } 99 | } 100 | } 101 | ``` 102 | ``` 103 | // 测试 104 | let setA = new Set() 105 | setA.add('a') 106 | setA.add('b') 107 | setA.add('c') 108 | setA.remove('b') 109 | console.log(setA.values()) // ['a', 'c'] 110 | console.log(setA.size()) // 2 111 | 112 | let setB = new Set() 113 | setB.add('c') 114 | setB.add('d') 115 | setB.add('e') 116 | 117 | let unionAB = setA.union(setB) 118 | console.log(unionAB.values()) // ['a', 'c', 'd', 'e'] 119 | let intersectionAB = setA.intersection(setB) 120 | console.log(intersectionAB.values()) // ['c'] 121 | 122 | let diffAB = setA.diff(setB) 123 | console.log(diffAB.values()) // ['a'] 124 | 125 | let setC = new Set() 126 | setC.add('d') 127 | setC.add('e') 128 | 129 | let isSubSetCB = setC.isSubSet(setB) 130 | console.log(isSubSetCB) // true 131 | 132 | let isSubSetAB = setA.isSubSet(setB) 133 | console.log(isSubSetAB) // false 134 | ``` -------------------------------------------------------------------------------- /notes/README.md: -------------------------------------------------------------------------------- 1 | # 笔记 2 | 3 | 目录 4 | 5 | * [不同时区与UTC时间的转换关系](https://github.com/sunzhaoye/blog/blob/master/notes/不同时区与UTC时间的转换关系.md) -------------------------------------------------------------------------------- /notes/不同时区与UTC时间的转换关系.md: -------------------------------------------------------------------------------- 1 | >UTC: 世界协调时,又称世界统一时间,世界标准时间,国际协调时间,以GMT时间为标准修正而来,非常精确 2 | 3 | >GMT: 格林威治标准时间, 格林威治标准时间的正午是指当太阳横穿格林尼治子午线时的时间。 4 | 5 | 整个地球分为二十四时区,每个时区都有自己的本地时间。 UTC与GMT都为英国伦敦时间。因此若以「世界标准时间」的角度来说,UTC比GMT来得更加精准。其误差值必须保持在0.9秒以内,若大于0.9秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使UTC与地球自转周期一致。所以基本上UTC的本质强调的是比GMT更为精确的世界时间标准,不过对于现行表款来说,GMT与UTC的功能与精确度是没有差别的。 6 | 7 | **不同时区与UTC时间的转换关系:** 8 | ``` 9 | UTC + 时区差 = 本地时间 10 | ``` 11 | **注意: 时区差,东为正,西为负,比如北京为东八时区,即+0800,也就是领先UTC时间8个小时** 12 | ``` 13 | UTC + (+0800) = 北京本地时间 14 | ``` 15 | --------------------------------------------------------------------------------