├── .gitattributes ├── .gitignore ├── CNAME ├── README.md ├── base ├── README.md ├── fs.js ├── http.js ├── index.html ├── package.json ├── process.js ├── test.html ├── test.zip └── 压缩与解压.js ├── core ├── buffer.js ├── child.js ├── copy.html ├── copy.js ├── fs.js ├── http_.js ├── index.html ├── path.js ├── process.js ├── server.js ├── stream.js ├── test.zip └── zip.js ├── doc ├── cli.md ├── com.md ├── es6 │ ├── arr.md │ ├── class.md │ ├── esModule.md │ ├── func.md │ ├── promise.md │ ├── proxy.md │ ├── set-map.md │ ├── str.md │ ├── symbol.md │ ├── var.md │ ├── 扩展.md │ ├── 解构.md │ └── 迭代器.md ├── git.md ├── high │ ├── async.md │ ├── call.md │ ├── evloop.md │ ├── http.md │ ├── monitor.md │ ├── promise.md │ ├── safe.md │ ├── 优化.md │ ├── 浏览器url到页面渲染流程.md │ ├── 浏览器渲染原理.md │ └── 浏览器缓存.md ├── mes.md ├── mod.md ├── mysql │ ├── mysql事务.md │ ├── sql语句.md │ ├── 填坑记录.md │ └── 理论篇.md ├── r-v.md ├── react.md ├── ser.md └── vue.md ├── index.html ├── moduleDemo ├── day1 │ ├── 1.jpg │ ├── 1.txt │ ├── fs.js │ ├── hello.js │ ├── http.js │ ├── index.html │ ├── route.js │ └── user.html ├── day2 │ ├── form.js │ ├── index.html │ └── post.js └── package.json └── static ├── JS.gif ├── Jietu20180406-151247.gif ├── NODE.gif ├── UI.gif ├── arrow.css ├── blog-1.gif ├── blog-css3.gif ├── blog-h5.gif ├── blog-mvc.gif ├── blog-node.gif ├── blog-ui.gif ├── blog_css3.css ├── canvas-mouse.js ├── canvas.js ├── f2.gif ├── f3.gif ├── f4.gif ├── f5.gif ├── f6.gif ├── f7.gif ├── f8.gif ├── f9.gif ├── fengjing1.gif ├── head.gif ├── main.js ├── radialIndicator.js ├── sroll-blog.js └── webpack.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-language= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dev/ -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | www.qiuchenglei.top -------------------------------------------------------------------------------- /base/README.md: -------------------------------------------------------------------------------- 1 | # node-notes 2 | node 学习笔记 参考:https://github.com/chyingp/nodejs-learning-guide 3 | 4 | ### 目录 5 | 6 | * 1.node学习 7 | * 2.[express框架学习](../doc/express.js) 8 | * 3.[数据库操作封装](../doc/db.js) 9 | 10 | * 解压与压缩 11 | 12 | ```js 13 | 14 | 15 | // 1. 资源压缩 16 | 17 | const fs = require('fs'); 18 | const zlib = require('zlib'); 19 | 20 | 21 | const gzip = zlib.createGzip(); 22 | 23 | const in_ = fs.createReadStream('./test.jpg'); 24 | const out = fs.createWriteStream('./test.zip'); 25 | 26 | in_.pipe(gzip).pipe(out); 27 | 28 | 29 | // 中间件使用 30 | var ARCHIVER = require('archiver'); 31 | var FS = require('fs'); 32 | 33 | var presentDate = new Date(); 34 | var myDate = presentDate.toLocaleDateString();//获取当前日期,eg:2017-02-08,以此日期为压缩包文件名 35 | var path1 = './test.jpg';//图片的绝对路径 36 | var path2 = './第一张图片.jpg'; 37 | var files = [path1,path2];//将图片路径组合成数组形式,用for循环遍历 38 | //压缩后文件输出地址:/ARCHIVER/appData/files/,压缩包名:eg:2017-02-08.zip 39 | var output = FS.createWriteStream('my1.zip'); 40 | //archiver可压缩为zip或tar格式,这里选择zip格式,注意这里新定义了一个变量archive,而不是原有的archiver包引用 41 | var archive = ARCHIVER('zip', { 42 | store: true 43 | }); 44 | //将压缩路径、包名与压缩格式连接 45 | archive.pipe(output); 46 | //nameInZIP指压缩包内的文件名 47 | var nameInZIP = ['1.jpg','2.jpg']; 48 | for (var i = 0; i < files.length; i++) { 49 | console.log(files[i]); 50 | //FS读取文件流并命名,将读取的文件流append到压缩包中 51 | archive.append(FS.createReadStream(files[i]), {'name': nameInZIP[i]}); 52 | } 53 | //压缩结束 54 | archive.finalize(); 55 | 56 | 57 | // 有问题 能压缩 不能解压 加压是空的 58 | //2. 解压 59 | 60 | // const gunzip = zlib.createGunzip(); 61 | 62 | // const in_zip = fs.createReadStream('./my1.zip'); 63 | // const out_ = fs.createWriteStream(__dirname ); 64 | 65 | // in_zip.pipe(gunzip).pipe(out_); 66 | 67 | //中间件使用 68 | var AdmZip = require('adm-zip'); 69 | var zip = new AdmZip('./my1.zip'); 70 | zip.extractAllTo( "./test/"); 71 | 72 | 73 | 74 | // http 服务端压缩 75 | 76 | const http = require('http'); 77 | 78 | const zlib = require('zlib'); 79 | 80 | const fs = require('fs'); 81 | 82 | const filePath = './html/index.html'; 83 | const h = 'hello'; 84 | 85 | 86 | const server = http.createServer((req,res) => { 87 | const acceptEncoding = req.headers['accept-encoding']; 88 | 89 | var gzip ; 90 | 91 | if(acceptEncoding.indexOf('gzip') != '-1') { 92 | gzip = zlib.createGzip(); 93 | 94 | res.writeHead(200,{ 95 | 'Content-Encoding': 'zip' 96 | }); 97 | 98 | 99 | res.end(zlib.gzipSync(h) ); 100 | 101 | //const data = fs.createReadStream(filePath).pipe(gzip).pipe(res); 102 | }else { 103 | //fs.createReadStream(filePath).pipe(res); 104 | res.end(h); 105 | } 106 | }).listen(8080); 107 | 108 | ``` 109 | 110 | * http模块 111 | 112 | ```js 113 | 114 | // dns 解析 115 | 116 | const dns = require('dns'); 117 | dns.lookup('www.baidu.com',function(err,address,family) { 118 | if(err) throw err; 119 | console.log(address); 120 | }) 121 | 122 | dns.resolve4('localhost',(err,address) => { 123 | console.log(JSON.stringify(address)) 124 | }) 125 | 126 | 127 | //http 128 | 129 | const http = require('http') 130 | const url = require('url') 131 | const querystring = require('querystring'); 132 | 133 | const server = http.createServer((req,res) => { 134 | const url_ = req.url; 135 | const urlp = url.parse(url_); 136 | const query = urlp.query 137 | const urlobj = querystring.parse(query); 138 | 139 | console.log(url_,req.method); 140 | //header 141 | const headers = req.headers; 142 | res.end(JSON.stringify(urlobj)); 143 | 144 | 头部 145 | 146 | // 增 147 | res.setHeader('Content-Type', 'text/plain'); 148 | 149 | // 删 150 | res.removeHeader('Content-Type'); 151 | 152 | // 改 153 | res.setHeader('Content-Type', 'text/plain'); 154 | res.setHeader('Content-Type', 'text/html'); // 覆盖 155 | 156 | // 查 157 | res.getHeader('content-type'); 158 | 159 | res.writeHead(200, { 160 | 'Content-Type': 'text/plain; charset=utf-8', 161 | 'X-Content-Type-Options': 'nosniff' 162 | }); 163 | 164 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 165 | 166 | 167 | if(req.url == '/home' && req.method == 'POST'){ //这里要大写 168 | 169 | req.on('aborted',function() { 170 | console.log('发起请求') 171 | }) 172 | 173 | req.on('close',function() { 174 | console.log('请求关闭') 175 | }) 176 | 177 | 178 | 179 | var body = ''; 180 | 181 | req.on('data',(chunk) => { 182 | body+=chunk; 183 | }) 184 | 185 | req.on('end',() => { 186 | 187 | //res.end(body_); 188 | //console.log(querystring.parse(body)) 189 | const posts = querystring.parse(body) 190 | const obj = JSON.parse(JSON.stringify(posts)); 191 | console.log(obj); 192 | res.write(obj); 193 | //res.send(); 194 | res.end('ok'); 195 | }) 196 | 197 | } 198 | //res.end('ok') 199 | 200 | }).listen(3000); 201 | 202 | 203 | 204 | //client 205 | 206 | const client = http.get('http://127.0.0.1:8003/home',(res) => { 207 | //console.log(res); 208 | res.pipe(process.stdout); 209 | console.log(res.statusCode) 210 | //返回给后台 211 | }) 212 | 213 | var net = require('net'); 214 | 215 | var PORT = 8989; 216 | var HOST = '127.0.0.1'; 217 | 218 | var servers = net.createServer(function(socket){ 219 | console.log('Connected: ' + socket.remoteAddress + ':' + socket.remotePort); 220 | 221 | socket.on('data', function(data){ 222 | console.log('DATA ' + socket.remoteAddress + ': ' + data); 223 | console.log('Data is: ' + data); 224 | 225 | socket.write('Data from you is "' + data + '"'); 226 | }); 227 | 228 | socket.on('close', function(){ 229 | console.log('CLOSED: ' + 230 | socket.remoteAddress + ' ' + socket.remotePort); 231 | }); 232 | }); 233 | servers.listen(PORT, HOST); 234 | 235 | console.log(servers instanceof net.Server); // true 236 | 237 | ``` 238 | 239 | * fs文件系统 240 | 241 | ```js 242 | const fs = require('fs'); 243 | 244 | fs.readFile('./index.html','utf-8',function(data,err) { 245 | err? console.log(err) : console.log(data); 246 | }) 247 | 248 | 249 | //通过流来读取 250 | 251 | const stream = fs.createReadStream('./index.html','utf-8'); 252 | 253 | stream.on('data',(chunk) => { 254 | console.log(chunk); 255 | }) 256 | 257 | stream.on('err',(err) => { 258 | console.log(err.message) 259 | }).on('end',() => { 260 | console.log('没有数据了') 261 | }).on('close',() => { 262 | console.log('关闭') 263 | }) 264 | 265 | // 这里写入 会将整个文件重写 266 | fs.writeFile('./index.html','

写入

','utf-8',(err) => { 267 | if(err) { 268 | throw err; 269 | }else { 270 | fs.readFile('./index.html','utf-8',(data,err) => { 271 | console.log(data); 272 | }) 273 | } 274 | }) 275 | 276 | 277 | const writeStream = fs.createWriteStream('./test.html','utf-8'); 278 | 279 | writeStream.on('close',() => { 280 | console.log('....close') 281 | }) 282 | 283 | // 追加进去的 但是还是把源文件全部改掉了 284 | writeStream.write('

test

'); 285 | writeStream.write('

h4 ...

'); 286 | writeStream.write('

h4 hahah

'); 287 | writeStream.on('end',() => { 288 | console.log('....') 289 | }) 290 | 291 | 292 | //判断文件是否存在 293 | fs.access('./index.html',(err) => { 294 | if(err) { 295 | throw err; 296 | } 297 | 298 | console.log('文件存在') 299 | }) 300 | 301 | //创建目录 302 | fs.mkdir('./mkdir',(err) => { 303 | if(err) { 304 | console.log(err.message) 305 | } 306 | 307 | }) 308 | 309 | //删除文件 310 | fs.unlink('./第一张图片.jpg',(err) => { 311 | //if(err) throw err; 312 | }) 313 | 314 | 315 | //删除目录 316 | fs.rmdir('./test1',(err) => { 317 | console.log(err); 318 | }) 319 | 320 | 321 | //读取目录 322 | 323 | const dirs = fs.readdirSync('./doc','utf-8'); 324 | console.log(dirs); 325 | 326 | const path = require('path'); 327 | 328 | const files = path.resolve('./doc','test'); 329 | 330 | console.log(files); // /Users/qiuchenglei/file-web/git/node-notes/doc/test 331 | 332 | var statsm = fs.statSync(files); 333 | //console.log(statsm); 334 | 335 | //遍历目录 336 | 337 | const forFile = function(dir) { 338 | let results = [ path.resolve(dir) ]; 339 | const files_ = fs.readdirSync(dir,'utf-8') 340 | 341 | files_.forEach(function(e) { 342 | var file = path.resolve(dir,e); 343 | 344 | const stats = fs.statSync(file); 345 | 346 | if(stats.isFile()) { 347 | results.push(file); 348 | console.log(file); 349 | }else if(stats.isDirectory()) { 350 | results = results.concat(forFile(file)); 351 | console.log(file); 352 | } 353 | }) 354 | 355 | return results; 356 | } 357 | 358 | 359 | 360 | console.log(forFile('./doc')) 361 | /*['/Users/qiuchenglei/file-web/git/node-notes/doc', 362 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test', 363 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test/test.jpg', 364 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test.html' ] 365 | */ 366 | 367 | 368 | 369 | 370 | 371 | // 文件重命名 可以是目录也可以是文件 372 | 373 | fs.rename('./my1.zip','test.zip',(err) => { 374 | console.log('yes') 375 | }) 376 | 377 | 378 | //监听文件修改 379 | 380 | const options = { 381 | persistent: true, 382 | interval: 2000 383 | } 384 | 385 | fs.watchFile('./test.html',options,(curr,prev) => { 386 | console.log('time' + curr.mtime) 387 | console.log(prev); 388 | }) 389 | 390 | 391 | ``` 392 | 393 | * 进程 394 | 395 | ```js 396 | 397 | const exec = require('child_process').exec; 398 | 399 | // 成功的例子 400 | exec('ls -al', function(error, stdout, stderr){ 401 | if(error) { 402 | console.error('error: ' + error); 403 | return; 404 | } 405 | console.log('stdout: ' + stdout); 406 | console.log('stderr: ' + typeof stderr); 407 | }); 408 | 409 | 410 | // 失败的例子 411 | exec('ls hello.txt', function(error, stdout, stderr){ 412 | if(error) { 413 | console.error('error: ' + error); 414 | return; 415 | } 416 | console.log('stdout: ' + stdout); 417 | console.log('stderr: ' + stderr); 418 | }); 419 | 420 | 421 | var child_process = require('child_process'); 422 | 423 | child_process.execFile('node', ['http.js'], function(error, stdout, stderr){ 424 | if(error){ 425 | throw error; 426 | } 427 | console.log(stdout); 428 | }); 429 | 430 | ``` 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | -------------------------------------------------------------------------------- /base/fs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | fs.readFile('./index.html','utf-8',function(data,err) { 4 | err? console.log(err) : console.log(data); 5 | }) 6 | 7 | 8 | //通过流来读取 9 | 10 | const stream = fs.createReadStream('./index.html','utf-8'); 11 | 12 | stream.on('data',(chunk) => { 13 | console.log(chunk); 14 | }) 15 | 16 | stream.on('err',(err) => { 17 | console.log(err.message) 18 | }).on('end',() => { 19 | console.log('没有数据了') 20 | }).on('close',() => { 21 | console.log('关闭') 22 | }) 23 | 24 | // 这里写入 会将整个文件重写 25 | fs.writeFile('./index.html','

写入

','utf-8',(err) => { 26 | if(err) { 27 | throw err; 28 | }else { 29 | fs.readFile('./index.html','utf-8',(data,err) => { 30 | console.log(data); 31 | }) 32 | } 33 | }) 34 | 35 | 36 | const writeStream = fs.createWriteStream('./test.html','utf-8'); 37 | 38 | writeStream.on('close',() => { 39 | console.log('....close') 40 | }) 41 | 42 | // 追加进去的 但是还是把源文件全部改掉了 43 | writeStream.write('

test

'); 44 | writeStream.write('

h4 ...

'); 45 | writeStream.write('

h4 hahah

'); 46 | writeStream.on('end',() => { 47 | console.log('....') 48 | }) 49 | 50 | 51 | //判断文件是否存在 52 | fs.access('./index.html',(err) => { 53 | if(err) { 54 | throw err; 55 | } 56 | 57 | console.log('文件存在') 58 | }) 59 | 60 | //创建目录 61 | fs.mkdir('./mkdir',(err) => { 62 | if(err) { 63 | console.log(err.message) 64 | } 65 | 66 | }) 67 | 68 | //删除文件 69 | fs.unlink('./第一张图片.jpg',(err) => { 70 | //if(err) throw err; 71 | }) 72 | 73 | 74 | //删除目录 75 | fs.rmdir('./test1',(err) => { 76 | console.log(err); 77 | }) 78 | 79 | 80 | //读取目录 81 | 82 | const dirs = fs.readdirSync('./doc','utf-8'); 83 | console.log(dirs); 84 | 85 | const path = require('path'); 86 | 87 | const files = path.resolve('./doc','test'); 88 | 89 | console.log(files); // /Users/qiuchenglei/file-web/git/node-notes/doc/test 90 | 91 | var statsm = fs.statSync(files); 92 | //console.log(statsm); 93 | 94 | //遍历目录 95 | 96 | const forFile = function(dir) { 97 | let results = [ path.resolve(dir) ]; 98 | const files_ = fs.readdirSync(dir,'utf-8') 99 | 100 | files_.forEach(function(e) { 101 | var file = path.resolve(dir,e); 102 | 103 | const stats = fs.statSync(file); 104 | 105 | if(stats.isFile()) { 106 | results.push(file); 107 | console.log(file); 108 | }else if(stats.isDirectory()) { 109 | results = results.concat(forFile(file)); 110 | console.log(file); 111 | } 112 | }) 113 | 114 | return results; 115 | } 116 | 117 | 118 | 119 | console.log(forFile('./doc')) 120 | /*['/Users/qiuchenglei/file-web/git/node-notes/doc', 121 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test', 122 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test/test.jpg', 123 | '/Users/qiuchenglei/file-web/git/node-notes/doc/test.html' ] 124 | */ 125 | 126 | 127 | 128 | 129 | 130 | // 文件重命名 可以是目录也可以是文件 131 | 132 | fs.rename('./my1.zip','test.zip',(err) => { 133 | console.log('yes') 134 | }) 135 | 136 | 137 | //监听文件修改 138 | 139 | const options = { 140 | persistent: true, 141 | interval: 2000 142 | } 143 | 144 | fs.watchFile('./test.html',options,(curr,prev) => { 145 | console.log('time' + curr.mtime) 146 | console.log(prev); 147 | }) 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /base/http.js: -------------------------------------------------------------------------------- 1 | // dns 解析 2 | 3 | const dns = require('dns'); 4 | dns.lookup('www.baidu.com',function(err,address,family) { 5 | if(err) throw err; 6 | console.log(address); 7 | }) 8 | 9 | dns.resolve4('localhost',(err,address) => { 10 | console.log(JSON.stringify(address)) 11 | }) 12 | 13 | 14 | //http 15 | 16 | const http = require('http') 17 | const url = require('url') 18 | const querystring = require('querystring'); 19 | 20 | const server = http.createServer((req,res) => { 21 | const url_ = req.url; 22 | const urlp = url.parse(url_); 23 | const query = urlp.query 24 | const urlobj = querystring.parse(query); 25 | 26 | console.log(url_,req.method); 27 | //header 28 | const headers = req.headers; 29 | res.end(JSON.stringify(urlobj)); 30 | 31 | 头部 32 | 33 | // 增 34 | res.setHeader('Content-Type', 'text/plain'); 35 | 36 | // 删 37 | res.removeHeader('Content-Type'); 38 | 39 | // 改 40 | res.setHeader('Content-Type', 'text/plain'); 41 | res.setHeader('Content-Type', 'text/html'); // 覆盖 42 | 43 | // 查 44 | res.getHeader('content-type'); 45 | 46 | res.writeHead(200, { 47 | 'Content-Type': 'text/plain; charset=utf-8', 48 | 'X-Content-Type-Options': 'nosniff' 49 | }); 50 | 51 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 52 | 53 | 54 | if(req.url == '/home' && req.method == 'POST'){ //这里要大写 55 | 56 | req.on('aborted',function() { 57 | console.log('发起请求') 58 | }) 59 | 60 | req.on('close',function() { 61 | console.log('请求关闭') 62 | }) 63 | 64 | 65 | 66 | var body = ''; 67 | 68 | req.on('data',(chunk) => { 69 | body+=chunk; 70 | }) 71 | 72 | req.on('end',() => { 73 | 74 | //res.end(body_); 75 | //console.log(querystring.parse(body)) 76 | const posts = querystring.parse(body) 77 | const obj = JSON.parse(JSON.stringify(posts)); 78 | console.log(obj); 79 | res.write(obj); 80 | //res.send(); 81 | res.end('ok'); 82 | }) 83 | 84 | } 85 | //res.end('ok') 86 | 87 | }).listen(3000); 88 | 89 | 90 | 91 | //client 92 | 93 | const client = http.get('http://127.0.0.1:8003/home',(res) => { 94 | //console.log(res); 95 | res.pipe(process.stdout); 96 | console.log(res.statusCode) 97 | //返回给后台 98 | }) 99 | 100 | var net = require('net'); 101 | 102 | var PORT = 8989; 103 | var HOST = '127.0.0.1'; 104 | 105 | var servers = net.createServer(function(socket){ 106 | console.log('Connected: ' + socket.remoteAddress + ':' + socket.remotePort); 107 | 108 | socket.on('data', function(data){ 109 | console.log('DATA ' + socket.remoteAddress + ': ' + data); 110 | console.log('Data is: ' + data); 111 | 112 | socket.write('Data from you is "' + data + '"'); 113 | }); 114 | 115 | socket.on('close', function(){ 116 | console.log('CLOSED: ' + 117 | socket.remoteAddress + ' ' + socket.remotePort); 118 | }); 119 | }); 120 | servers.listen(PORT, HOST); 121 | 122 | console.log(servers instanceof net.Server); // true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 1 11 | 2 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-notes", 3 | "version": "1.0.0", 4 | "description": "a nodejs notes", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "doc" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "hot": "./node_modules/.bin/supervisor process.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/qiuChengleiy/node-notes.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/qiuChengleiy/node-notes/issues" 21 | }, 22 | "homepage": "https://github.com/qiuChengleiy/node-notes#readme", 23 | "devDependencies": { 24 | "adm-zip": "^0.4.11", 25 | "archiver": "^2.1.1", 26 | "node-minizip": "^0.3.0", 27 | "supervisor": "^0.12.0", 28 | "unzip": "^0.1.11" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /base/process.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | 3 | // 成功的例子 4 | exec('ls -al', function(error, stdout, stderr){ 5 | if(error) { 6 | console.error('error: ' + error); 7 | return; 8 | } 9 | console.log('stdout: ' + stdout); 10 | console.log('stderr: ' + typeof stderr); 11 | }); 12 | 13 | 14 | // 失败的例子 15 | exec('ls hello.txt', function(error, stdout, stderr){ 16 | if(error) { 17 | console.error('error: ' + error); 18 | return; 19 | } 20 | console.log('stdout: ' + stdout); 21 | console.log('stderr: ' + stderr); 22 | }); 23 | 24 | 25 | var child_process = require('child_process'); 26 | 27 | child_process.execFile('node', ['http.js'], function(error, stdout, stderr){ 28 | if(error){ 29 | throw error; 30 | } 31 | console.log(stdout); 32 | }); -------------------------------------------------------------------------------- /base/test.html: -------------------------------------------------------------------------------- 1 |

test

h4 ...

h4 hahah

-------------------------------------------------------------------------------- /base/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/base/test.zip -------------------------------------------------------------------------------- /base/压缩与解压.js: -------------------------------------------------------------------------------- 1 | 2 | // 1. 资源压缩 3 | 4 | const fs = require('fs'); 5 | const zlib = require('zlib'); 6 | 7 | 8 | const gzip = zlib.createGzip(); 9 | 10 | const in_ = fs.createReadStream('./test.jpg'); 11 | const out = fs.createWriteStream('./test.zip'); 12 | 13 | in_.pipe(gzip).pipe(out); 14 | 15 | 16 | // 中间件使用 17 | var ARCHIVER = require('archiver'); 18 | var FS = require('fs'); 19 | 20 | var presentDate = new Date(); 21 | var myDate = presentDate.toLocaleDateString();//获取当前日期,eg:2017-02-08,以此日期为压缩包文件名 22 | var path1 = './test.jpg';//图片的绝对路径 23 | var path2 = './第一张图片.jpg'; 24 | var files = [path1,path2];//将图片路径组合成数组形式,用for循环遍历 25 | //压缩后文件输出地址:/ARCHIVER/appData/files/,压缩包名:eg:2017-02-08.zip 26 | var output = FS.createWriteStream('my1.zip'); 27 | //archiver可压缩为zip或tar格式,这里选择zip格式,注意这里新定义了一个变量archive,而不是原有的archiver包引用 28 | var archive = ARCHIVER('zip', { 29 | store: true 30 | }); 31 | //将压缩路径、包名与压缩格式连接 32 | archive.pipe(output); 33 | //nameInZIP指压缩包内的文件名 34 | var nameInZIP = ['1.jpg','2.jpg']; 35 | for (var i = 0; i < files.length; i++) { 36 | console.log(files[i]); 37 | //FS读取文件流并命名,将读取的文件流append到压缩包中 38 | archive.append(FS.createReadStream(files[i]), {'name': nameInZIP[i]}); 39 | } 40 | //压缩结束 41 | archive.finalize(); 42 | 43 | 44 | // 有问题 能压缩 不能解压 加压是空的 45 | //2. 解压 46 | 47 | // const gunzip = zlib.createGunzip(); 48 | 49 | // const in_zip = fs.createReadStream('./my1.zip'); 50 | // const out_ = fs.createWriteStream(__dirname ); 51 | 52 | // in_zip.pipe(gunzip).pipe(out_); 53 | 54 | //中间件使用 55 | var AdmZip = require('adm-zip'); 56 | var zip = new AdmZip('./my1.zip'); 57 | zip.extractAllTo( "./test/"); 58 | 59 | 60 | 61 | // http 服务端压缩 62 | 63 | const http = require('http'); 64 | 65 | const zlib = require('zlib'); 66 | 67 | const fs = require('fs'); 68 | 69 | const filePath = './html/index.html'; 70 | const h = 'hello'; 71 | 72 | 73 | const server = http.createServer((req,res) => { 74 | const acceptEncoding = req.headers['accept-encoding']; 75 | 76 | var gzip ; 77 | 78 | if(acceptEncoding.indexOf('gzip') != '-1') { 79 | gzip = zlib.createGzip(); 80 | 81 | res.writeHead(200,{ 82 | 'Content-Encoding': 'zip' 83 | }); 84 | 85 | 86 | res.end(zlib.gzipSync(h) ); 87 | 88 | //const data = fs.createReadStream(filePath).pipe(gzip).pipe(res); 89 | }else { 90 | //fs.createReadStream(filePath).pipe(res); 91 | res.end(h); 92 | } 93 | }).listen(8080); 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /core/buffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * buffer 处理二进制数据 3 | */ 4 | 5 | 6 | // 好像是废弃了 7 | { 8 | const str = 'hello word' 9 | 10 | const buf = new Buffer(str,'utf-8') 11 | 12 | console.log(buf) // 13 | } 14 | 15 | 16 | // 带有汉字 数字 字母 不用考虑范围 17 | { 18 | const str = '今天是礼拜二 学习nodejs的 1 天' 19 | 20 | const buf = Buffer.from(str) 21 | 22 | const buf_ = Buffer.allocUnsafe(buf.length) 23 | 24 | buf_.fill(str, 0) 25 | 26 | console.log(buf_.toString()) // 可以互转 --- 今天是礼拜二 学习nodejs的 天 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /core/child.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 子进程 3 | */ 4 | 5 | process.on('message', msg => { 6 | console.log('from father: -----> ' , msg) 7 | }) 8 | 9 | 10 | let num = 0; 11 | 12 | // 隔一秒发一次信息 13 | setInterval(() => { 14 | process.send(`i love you ${num} times`) 15 | num++ 16 | },1000) 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /core/copy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | createReadStream here ! 11 | 12 | -------------------------------------------------------------------------------- /core/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fs模块 3 | */ 4 | 5 | const fs = require('fs') 6 | 7 | // 小文件拷贝 8 | { 9 | // 获取命令行参数 10 | console.log(process.argv) 11 | // [ '/usr/local/bin/node', 12 | // '/Users/qiuchenglei/web/git/node-notes/core/fs.js', 13 | // './fs.js', 14 | // './copy.js' ] 15 | 16 | // 文件拷贝功能 --- 小文件 17 | const copy = (arg) => { 18 | fs.writeFileSync(arg[1], fs.readFileSync(arg[0])) // 第一个参数是目标文件 第二个源文件 19 | } 20 | 21 | const main = (argv) => { 22 | copy(argv) 23 | } 24 | 25 | // main(process.argv.slice(2)) --- 执行 26 | 27 | // process是一个全局变量,可通过process.argv获得命令行参数。由于argv[0]固定等于NodeJS执行程序的绝对路径,argv[1]固定等于主模块的绝对路径,因此第一个命令行参数从argv[2]这个位置开始。 28 | } 29 | 30 | 31 | 32 | // 大文件拷贝 33 | { 34 | // 对于大文件 我们只能一点一点读 --- 通过创建数据流 35 | const copy = (arg) => { 36 | const wr = fs.createWriteStream(arg[1]) 37 | fs.createReadStream(arg[0]).pipe(wr) 38 | 39 | let i = 0; 40 | const timer = setInterval(() => i++ ,1000) 41 | 42 | wr.on('finish',() => { 43 | clearInterval(timer) 44 | console.log('文件拷贝完成 ----> 耗时'+ `${i}s`) 45 | }) 46 | } 47 | 48 | const main = (argv) => { 49 | copy(argv) 50 | } 51 | 52 | copy(process.argv.slice(2)) 53 | } 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /core/fs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fs模块 3 | */ 4 | 5 | const fs = require('fs') 6 | const _ = process.argv[1] 7 | 8 | // 小文件拷贝 9 | { 10 | // 获取命令行参数 11 | console.log(process.argv) 12 | // [ '/usr/local/bin/node', 13 | // '/Users/qiuchenglei/web/git/node-notes/core/fs.js', 14 | // './fs.js', 15 | // './copy.js' ] 16 | 17 | // 文件拷贝功能 --- 小文件 18 | const copy = (arg) => { 19 | fs.writeFileSync(arg[1], fs.readFileSync(arg[0])) // 第一个参数是目标文件 第二个源文件 20 | } 21 | 22 | const main = (argv) => { 23 | copy(argv) 24 | } 25 | 26 | // main(process.argv.slice(2)) --- 执行 27 | 28 | // process是一个全局变量,可通过process.argv获得命令行参数。由于argv[0]固定等于NodeJS执行程序的绝对路径,argv[1]固定等于主模块的绝对路径,因此第一个命令行参数从argv[2]这个位置开始。 29 | } 30 | 31 | 32 | 33 | // 大文件拷贝 34 | { 35 | // 对于大文件 我们只能一点一点读 --- 通过创建数据流 36 | const copy = (arg) => { 37 | const wr = fs.createWriteStream(arg[1]) 38 | fs.createReadStream(arg[0]).pipe(wr) 39 | 40 | let i = 0; 41 | const timer = setInterval(() => i++ ,1000) 42 | 43 | wr.on('finish',() => { 44 | clearInterval(timer) 45 | console.log('文件拷贝完成 ----> 耗时'+ `${i}s`) 46 | fs.close() // 关闭 47 | }) 48 | } 49 | 50 | const main = (argv) => { 51 | copy(argv) 52 | } 53 | 54 | // main(process.argv.slice(2)) 55 | } 56 | 57 | 58 | 59 | // 常用的文件属性 60 | { 61 | // 返回一个文件描述对象 62 | const stat = fs.statSync(_) 63 | // console.log(stat,'\n') 64 | } 65 | 66 | 67 | // 异步IO 68 | { 69 | fs.readFile(_,(err,data) => { 70 | if(err) { 71 | throw err 72 | }else { 73 | // console.log(data.toString()) // data 是一个Buffer类 74 | } 75 | }) 76 | 77 | // 异步操作 78 | try{ 79 | const data = fs.readFileSync(_) 80 | // console.log(data.toString()) 81 | } catch(err) { 82 | throw err 83 | } 84 | } 85 | 86 | // 文件追加 87 | { 88 | () => { 89 | const data = fs.readFileSync(process.argv[2]) 90 | const _n = data.toString('utf-8').split('\n') 91 | const script = '' 92 | _n.splice(_n.length-2,0,script) 93 | fs.writeFileSync(process.argv[2], _n.join('\n')) 94 | } 95 | } 96 | 97 | // 文件常规操作 98 | { 99 | () => { 100 | // 删除 101 | fs.unlinkSync('./1.txt') 102 | 103 | // 重新命名 104 | fs.renameSync('./test.html', './index.html') 105 | } 106 | } 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /core/http_.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http 网络编程 3 | * 两种使用方式: 1. 服务端 2.客户端 4 | */ 5 | 6 | const http = require('http') 7 | 8 | // 创建http服务器 9 | // HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成 10 | // http模块在接受到请求头后 会调用回调函数 11 | { 12 | const app = http.createServer((req,res) => { 13 | // console.log(req.headers) 14 | // 完整的请求头 15 | // { host: 'localhost:8001', 16 | // connection: 'keep-alive', 17 | // 'cache-control': 'max-age=0', 18 | // 'upgrade-insecure-requests': '1', 19 | // 'user-agent': 20 | // 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', 21 | // accept: 22 | // 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 23 | // 'accept-encoding': 'gzip, deflate, br', 24 | // 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', 25 | // cookie: 'Hm_lvt_080836300300be57b7f34f4b3e97d911=1529056422' } 26 | 27 | const headers = { 28 | 'Content-Type': 'text-plain' 29 | } 30 | 31 | // let body = []; 32 | // req.on('data', (chunk) => { 33 | // chunk.toString() 34 | // body.push(chunk) 35 | // }) 36 | 37 | // req.on('end', () => { 38 | // body = Buffer.concat(body) 39 | // console.log(body.toString()) 40 | // }) 41 | 42 | res.writeHead(200,headers) 43 | res.end(process.argv[2]) 44 | 45 | }) 46 | 47 | // app.listen(8000) 48 | } 49 | 50 | // client ---- https | http解析 51 | const https = require('https') 52 | const fs = require('fs') 53 | 54 | { 55 | () => { 56 | // http 57 | https.get('https://www.bilibili.com',(res) => { 58 | console.log(res.headers) 59 | let data = '' 60 | res.on('data', (chunk) => { 61 | data+=chunk.toString() 62 | }) 63 | 64 | res.on('end',() => { 65 | console.log(data) // 返回html 字符串 66 | }) 67 | }) 68 | 69 | // 文件下载 70 | const url = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1560310882335&di=3c11ef2b81970197eac6c607305ddb79&imgtype=0&src=http%3A%2F%2Fpic9.nipic.com%2F20100923%2F2531170_140325352643_2.jpg' 71 | const ws = fs.createWriteStream('./test.jpg') 72 | 73 | let receivedBytes = 0; 74 | let totalBytes = 0; 75 | 76 | https.get(url,(res) => { 77 | res.pipe(ws) 78 | 79 | totalBytes = parseInt(res.headers['content-length'], 10); 80 | 81 | res.on('data', (chunk) => { 82 | // 更新下载的文件块字节大小 83 | receivedBytes += chunk.length; 84 | }) 85 | 86 | const timer = setInterval(() => console.log('当前进度---->'+((receivedBytes/totalBytes)*100).toFixed(2) + '%'),0) 87 | 88 | ws.on('finish',() => { 89 | console.log('写入完成') 90 | clearInterval(timer) 91 | }) 92 | 93 | res.on('end',() => { 94 | console.log('写入结束') // 返回html 字符串 95 | ws.close() 96 | }) 97 | })} 98 | 99 | } 100 | 101 | 102 | // 创建https 服务器 103 | // https模块与http模块极为类似,区别在于https模块需要额外处理SSL证书 104 | { 105 | () => { 106 | const options = { 107 | key: fs.readFileSync('./ssl/default.key'), 108 | cert: fs.readFileSync('./ssl/default.cer') 109 | }; 110 | 111 | const server = https.createServer(options, function (request, response) { 112 | // ... 113 | }); 114 | 115 | // NodeJS支持SNI技术,可以根据HTTPS客户端请求使用的域名动态使用不同的证书,因此同一个HTTPS服务器可以使用多个域名提供服务。接着上例,可以使用以下方法为HTTPS服务器添加多组证书。 116 | server.addContext('foo.com', { 117 | key: fs.readFileSync('./ssl/foo.com.key'), 118 | cert: fs.readFileSync('./ssl/foo.com.cer'), 119 | rejectUnauthorized: false 120 | // 如果目标服务器使用的SSL证书是自制的,不是从颁发机构购买的,默认情况下https模块会拒绝连接,提示说有证书安全问题。 121 | //在options里加入rejectUnauthorized: false字段可以禁用对证书有效性的检查,从而允许https模块请求开发环境下使用自制证书的HTTPS服务器 122 | }); 123 | 124 | server.addContext('bar.com', { 125 | key: fs.readFileSync('./ssl/bar.com.key'), 126 | cert: fs.readFileSync('./ssl/bar.com.cer') 127 | }); 128 | 129 | 130 | // client 131 | const options_ = { 132 | hostname: 'www.example.com', 133 | port: 443, 134 | path: '/', 135 | method: 'GET' 136 | }; 137 | 138 | const request = https.request(options_, function (response) {}); 139 | 140 | request.end()} 141 | } 142 | 143 | 144 | // url 解析 145 | const url = require('url') 146 | const queryString = require('querystring') 147 | { 148 | 149 | http.createServer((req,res) => { 150 | const reqUrl = req.url 151 | const _ = url.parse(reqUrl) // url解析 --- 有用的字段为 *** query pathname 152 | 153 | res.writeHead(200,{ 'Content-Type': 'text/plain' }) 154 | res.end(JSON.stringify(_)) 155 | //{"protocol":null, 156 | // "slashes":null, 157 | // "auth":null, 158 | // "host":null, 159 | // "port":null, 160 | // "hostname":null, 161 | // "hash":null, 162 | // "search":"?a=1", 163 | // "query":"a=1", 164 | // "pathname":"/test/user", 165 | // "path":"/test/user?a=1", 166 | // "href":"/test/user?a=1"} 167 | 168 | // 转成url 169 | const url_ = url.format({protocol: 'http',host: 'www.baidu.com',pathname: '/search',search: 'a=1'}) 170 | console.log(url_) 171 | // http://www.baidu.com/search?a=1 172 | 173 | // query参数解析 174 | const query = queryString.parse('a=1&a=100&a=50&b=2') 175 | console.log(query) // { a: [ '1', '100', '50' ], b: '2' } 176 | 177 | }).listen(8000) 178 | } 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /core/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | createReadStream here ! 11 | 12 | 13 | -------------------------------------------------------------------------------- /core/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * path 文件路径 3 | */ 4 | 5 | const path = require('path') 6 | const fs = require('fs') 7 | 8 | { 9 | console.log(path.normalize('.....//////test/a./asadd/')) // ...../test/a./asadd/ 去除 多余的 / window下 \ 10 | console.log(process.cwd()) // /Users/qiuchenglei/web/git/node-notes/core 获取绝对路径 11 | 12 | // 获取扩展名 13 | console.log(path.extname(process.argv[1])) // .js 14 | } 15 | 16 | 17 | 18 | // 目录遍历 --- 指令 node path.js ~/node 会遍历node文件夹 19 | { 20 | function forFiles(dir, callback) { 21 | fs.readdirSync(dir).map(item => { 22 | const _ = path.join(dir,item) 23 | if(fs.statSync(_).isDirectory()) { // 如果是文件夹继续遍历 ---- 深度优先 24 | return forFiles(_,callback) 25 | }else { 26 | callback(_) 27 | } 28 | }) 29 | } 30 | 31 | () => { 32 | forFiles(process.argv[2],(pathname) => { 33 | console.log(pathname) 34 | 35 | // ../moduleDemo/.DS_Store 36 | // ../moduleDemo/day1/1.jpg 37 | // ../moduleDemo/day1/1.txt 38 | // ../moduleDemo/day1/fs.js 39 | // ../moduleDemo/day1/hello.js 40 | // ../moduleDemo/day1/http.js 41 | // ../moduleDemo/day1/index.html 42 | // ../moduleDemo/day1/route.js 43 | // ../moduleDemo/day1/user.html 44 | // ../moduleDemo/day2/.DS_Store 45 | // ../moduleDemo/day2/form.js 46 | // ../moduleDemo/day2/index.html 47 | // ../moduleDemo/day2/post.js 48 | // ../moduleDemo/day2/upload/.DS_Store 49 | // ../moduleDemo/package.json 50 | })} 51 | } 52 | 53 | 54 | // 当读取文本文件时 --- 需要去除 utf8 BOM 否则浏览器会解析错误 55 | { 56 | function readText(pathname) { 57 | var bin = fs.readFileSync(pathname); 58 | 59 | if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) { 60 | bin = bin.slice(3); 61 | } 62 | 63 | return bin.toString('utf-8'); 64 | } 65 | } 66 | 67 | // GBK 转 UTF8 68 | { 69 | //const iconv = require('iconv-lite'); 70 | 71 | function readGBKText(pathname) { 72 | const bin = fs.readFileSync(pathname); 73 | 74 | return iconv.decode(bin, 'gbk'); 75 | } 76 | } 77 | 78 | // 防止乱码 ---- binary编码 79 | { 80 | function replace(pathname) { 81 | var str = fs.readFileSync(pathname, 'binary'); 82 | str = str.replace('foo', 'bar'); 83 | fs.writeFileSync(pathname, str, 'binary'); 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /core/process.js: -------------------------------------------------------------------------------- 1 | /** 2 | * process 进程管理模块 3 | */ 4 | 5 | const child_process_ = require('child_process') 6 | { 7 | () => { 8 | const spawn = child_process_.spawn 9 | console.log(process.execPath) // /usr/local/bin/node 10 | 11 | let server_process = spawn(process.execPath,['./server.js']) // spawn(command[, args][, options]) 12 | 13 | // 递归重启 14 | const restart = () => { 15 | spawn(process.execPath,['./server.js']).on('close',() => { 16 | return restart() 17 | }) 18 | } 19 | 20 | server_process.on('close', (code) => { 21 | console.log(`子进程退出码: ${code}`) 22 | return restart() 23 | }) 24 | 25 | const { stdin, stdout, stderr } = server_process // 输入 输出 错误 --- 流 26 | 27 | stdout.on('data', (data) => { 28 | console.log(`stdout: ${data}`); 29 | // 拿到的是 --- log 30 | //stdout: 子进程 server启动 31 | // stdout: 输出log 32 | }); 33 | 34 | stderr.on('data', (data) => { 35 | console.log(`stderr: ${data}`); 36 | }); 37 | } 38 | } 39 | 40 | // exec会将spawn的输入输出流转换成String,默认使用UTF-8的编码,然后传递给回调函数,使用回调方式在node中较为熟悉,比流更容易操作,所以我们能使用exec方法执行一些shell命令, 41 | // 然后在回调中获取返回值。有点需要注意,这里的buffer是有最大缓存区的,如果超出会直接被kill掉,可用通过maxBuffer属性进行配置(默认: 200*1024)。 42 | { 43 | () => { 44 | child_process_.exec('ls', (err, stdout, stderr) => { 45 | console.log(`\nstdout: ${stdout}`); // 会返回当前目录下的文件名 46 | console.log(`\nstderr: ${stderr}`); 47 | }) 48 | } 49 | } 50 | 51 | 52 | // fork 进程通信 --- 传入的是可执行文件 53 | { 54 | () => { 55 | const fork = child_process_.fork 56 | const child = fork('./child.js') 57 | 58 | child.on('message', msg => { 59 | console.log('message from child ---> ' , msg ) 60 | }) 61 | 62 | child.send('hello child , I am your father ') 63 | } 64 | } 65 | 66 | 67 | /*** 68 | * 高级用法 69 | * 1. 多个进程监听同一端口 ---- node默认会抛出 EADDRINUSE 错误 70 | * 2. 主进程 --- 可以做负载均衡 71 | */ 72 | 73 | /** 74 | * 异常 75 | * 开启两个子进程监听一个端口 76 | */ 77 | 78 | // +--------------+ 79 | // | | 80 | // | master | 81 | // | | 82 | // +--------+--------------+- -- -- - 83 | // | | 84 | // | Error: listen EADDRINUSE 85 | // | | 86 | // | 87 | // +----v----+ +-----v---+ 88 | // | | | | 89 | // | worker1 | | worker2 | 90 | // | | | | 91 | // +---------+ +---------+ 92 | // :8000 :8000 93 | 94 | 95 | /** 96 | * 分发服务 -- 这种做法 : 97 | * 但是这么做又会带来另一个问题,代理模式中十分消耗文件描述符(linux系统默认的最大文件描述符限制是1024),文件描述符在windows系统中称为句柄(handle), 98 | * 习惯性的我们也可以称linux中的文件描述符为句柄。当用户进行访问,首先连接到master进程,会消耗一个句柄,然后master进程再代理到worker进程又会消耗掉一个句柄, 99 | * 所以这种做法十分浪费系统资源。为了解决这个问题,Node的进程间通信可以发送句柄,节省系统资源。 100 | * 101 | * 句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。 102 | */ 103 | 104 | // +--------------+ 105 | // | | 106 | // | master | 107 | // | :80 | 108 | // +--------+--------------+---------+ 109 | // | | 110 | // | | 111 | // | | 112 | // | | 113 | // +----v----+ +-----v---+ 114 | // | | | | 115 | // | worker1 | | worker2 | 116 | // | | | | 117 | // +---------+ +---------+ 118 | // :8000 :8001 119 | 120 | 121 | /** 122 | * tcp 服务发送句柄 123 | * 可以在master进程启动一个tcp服务,然后通过IPC将服务的句柄发送给子进程,子进程再对服务的连接事件进行监听 124 | */ 125 | 126 | { 127 | // master.js 128 | () => { 129 | var { fork } = require('child_process') 130 | var server = require('net').createServer() 131 | server.on('connection', function(socket) { 132 | socket.end('handled by master') // 响应来自master 133 | }) 134 | server.listen(3000, function() { 135 | console.log('master listening on: ', 3000) 136 | }) 137 | for (var i = 0; i < 2; i++) { 138 | var child = fork('./child.js') 139 | child.send('server', server) // 发送句柄给worker 140 | console.log('worker create, pid is ', child.pid) 141 | } 142 | 143 | // child.js 144 | process.on('message', function (msg, handler) { 145 | if (msg !== 'server') { 146 | return 147 | } 148 | // 获取到句柄后,进行请求的监听 149 | handler.on('connection', function(socket) { 150 | socket.end('handled by worker, pid is ' + process.pid) 151 | }) 152 | }) 153 | } 154 | } 155 | 156 | 157 | 158 | /** 159 | * cluster模块 160 | * 用于多核CPU环境下多进程的负载均衡。cluster模块创建子进程本质上是通过child_procee.fork, 161 | * 利用该模块可以很容易的创建共享同一端口的子进程服务器。 162 | */ 163 | 164 | const numCPUs = require('os').cpus().length // 我的是四核 165 | const cluster = require('cluster') 166 | const http = require('http') 167 | 168 | { 169 | if(cluster.isMaster) { 170 | console.log(`主进程 ---- > pid: ${process.pid}`) 171 | 172 | for(let i=0;i { 178 | console.log(`工作进程 ${worker.process.pid }`) 179 | }) 180 | 181 | // 监听消息 182 | cluster.on('message', (worker, message , handle) => { 183 | console.log(`工作进程 ${worker.process.pid}`) 184 | console.log(`工作进程 ${message}`) 185 | }) 186 | 187 | // 监听事件 188 | cluster.on('listening', (worker, address) => { 189 | console.log( 190 | `A worker is now connected to ${address.address}:${address.port}`); 191 | }); 192 | }else { 193 | /** 194 | * 创建 http 服务 195 | * 不同用户下访问的进程不同 196 | */ 197 | http.createServer((req,res) => { 198 | const child_ = process.pid 199 | res.writeHead(200,{ 'Content-Type': 'text/plain;charset=utf-8'}) 200 | res.end(`进程 pid ---> ${child_}`) // 进程 pid ---> 12655 进程 pid ---> 12656 开启两个页面访问 201 | }).listen(3000) 202 | 203 | process.on('message', msg => { 204 | console.log(`from master messages ---> ${msg}`) 205 | }) 206 | 207 | process.send('i am child process ') 208 | } 209 | } 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /core/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 子进程 http服务 3 | */ 4 | 5 | const http = require('http') 6 | http.createServer((req,res) => { 7 | const query = require('url').parse(req.url).query 8 | 9 | if(query == 'a=1') { 10 | throw '服务器出错' 11 | } 12 | 13 | res.writeHead(200) 14 | res.end(query) 15 | 16 | }).listen(8001) 17 | 18 | console.log('子进程 server启动') 19 | 20 | console.log('输出log') 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /core/stream.js: -------------------------------------------------------------------------------- 1 | /** 2 | * stream 数据流 3 | */ 4 | 5 | const fs = require('fs') 6 | 7 | { 8 | const rs = fs.createReadStream(process.argv[2]) 9 | const ws = fs.createWriteStream(process.argv[3]) 10 | let html = '' 11 | rs.on('data', (chunk) => { 12 | // console.log(chunk) 13 | // 返回的是二进制的一个数据 14 | // 15 | html+=chunk.toString('utf-8') 16 | 17 | // 代码改进 --- 加入写入流 18 | { 19 | if(!ws.write(chunk)) { // 写入流跟不上读入流时 停掉 -- 此处判断是否写入目标 20 | rs.pause() 21 | } 22 | } 23 | 24 | }) 25 | 26 | rs.on('end', () => { 27 | ws.end() // 读取结束时 28 | }) 29 | 30 | ws.on('drain', () => { 31 | rs.resume() 32 | }) 33 | 34 | rs.on('close', () => { 35 | console.log('读取完成 ---- > 解析文件: \n') 36 | console.log(html) 37 | 38 | //解析得到的文件 39 | // 40 | // 41 | // 42 | // 43 | // 44 | // 45 | // Document 46 | // 47 | // 48 | // createReadStream here ! 49 | // 50 | // 51 | }) 52 | 53 | } 54 | 55 | 56 | // 另一种是使用 提供的pipe 57 | { 58 | // 参考fs.js 59 | } -------------------------------------------------------------------------------- /core/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/core/test.zip -------------------------------------------------------------------------------- /core/zip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 压缩与解压 3 | */ 4 | 5 | // 使用第三方模块 6 | const archiver = require('archiver') 7 | const fs = require('fs') 8 | 9 | // 压缩 10 | { 11 | () => { 12 | const ws = fs.createWriteStream('./test.zip') 13 | const inpu = './test.jpg' 14 | const archive = archiver('zip', { 15 | zlib: { level: 9 } // Sets the compression level. 16 | }) 17 | 18 | // listen for all archive data to be written 19 | // 'close' event is fired only when a file descriptor is involved 20 | ws.on('close', function() { 21 | console.log(archive.pointer() + ' total bytes'); 22 | console.log('archiver has been finalized and the output file descriptor has closed.'); 23 | console.log('压缩结束') 24 | }); 25 | 26 | archive.on('err', (err) => { 27 | if (err) throw err ; 28 | }) 29 | 30 | archive.on('warning', function(err) { 31 | if (err.code === 'ENOENT') { 32 | // log warning 33 | } else { 34 | // throw error 35 | throw err; 36 | } 37 | }); 38 | 39 | 40 | // append a file from stream 41 | // var file1 = __dirname + '/file1.txt'; 42 | // archive.append(fs.createReadStream(file1), { name: 'file1.txt' }); 43 | 44 | // // append a file from string 45 | // archive.append('string cheese!', { name: 'file2.txt' }); 46 | 47 | // // append a file from buffer 48 | // var buffer3 = Buffer.from('buff it!'); 49 | // archive.append(buffer3, { name: 'file3.txt' }); 50 | 51 | 52 | 53 | // append a file 54 | // archive.file('file1.txt', { name: 'file4.txt' }); 55 | 56 | // // append files from a sub-directory and naming it `new-subdir` within the archive 57 | // archive.directory('subdir/', 'new-subdir'); 58 | 59 | // // append files from a sub-directory, putting its contents at the root of archive 60 | // archive.directory('subdir/', false); 61 | 62 | // // append files from a glob pattern 63 | // archive.glob('subdir/*.txt'); 64 | 65 | 66 | archive.pipe(ws) 67 | 68 | archive.file(inpu, { name: 'zip.jpg' }) 69 | 70 | archive.finalize() 71 | 72 | } 73 | } 74 | 75 | 76 | // 解压 ---- 77 | const unzip = require('unzip') 78 | { 79 | fs.createReadStream('./test.zip').pipe(unzip.Extract({ path: 'zip'})) 80 | } -------------------------------------------------------------------------------- /doc/cli.md: -------------------------------------------------------------------------------- 1 | * 以下是官方贴出的angular-cli的命令行工具(字面意思应该都看得懂) 2 | 3 | * 安装: npm install -g @angular/cli 推荐使用淘宝镜像源 4 | 5 | * 新建项目: ng new Project 6 | 7 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.8. 8 | 9 | ## Development server 10 | 11 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 12 | 13 | ## Code scaffolding 14 | 15 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 16 | 17 | ## Build 18 | 19 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 20 | 21 | ## Running unit tests 22 | 23 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 24 | 25 | ## Running end-to-end tests 26 | 27 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 28 | 29 | ## Further help 30 | 31 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 32 | 33 | 34 | * 快速创建组件 35 | 36 | ng g c Name 37 | 38 | * 快速创建service 39 | 40 | ng g s Name 41 | 42 | * 启动服务 43 | 44 | ng serve 也可以加参数: ng serve --prod --aot -------------------------------------------------------------------------------- /doc/com.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 3 | import { Component } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | title = 'app'; 12 | } 13 | 14 | 15 | ``` 16 | 17 | ```html 18 | 19 | 20 |
21 |

22 | Welcome to {{ title }}! 你好 世界 !正式学习 angular 开发 23 |

24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | 36 | ```css 37 | h1{ 38 | color:green; 39 | } 40 | 41 | 42 | ``` -------------------------------------------------------------------------------- /doc/es6/arr.md: -------------------------------------------------------------------------------- 1 | ES5提供的数组已经很强大,但是ES6中继续改进了一些,主要是增加了新的数组方法,所以这章的知识非常少。 2 | 3 | ### 创建数组 4 | **ES5中创建数组的方式:数组字面量、new一个数组。** 5 | 6 | ```javascript 7 | const arr1 = [] //数组字面量 8 | const arr2 = new Array() //new构建 9 | ``` 10 | 11 | **ES6创建数组:Array.of()、Array.from()** 12 | 13 | #### Array.of() 14 | 15 | ES5中new一个人数组的时候,会存在一个令人困惑的情况。当new一个数字的时候,生成的是一个长度为该数字的数组,当new一个字符串的时候,生成的是该字符串为元素的数组。 16 | 17 | ```javascript 18 | const a = new Array(2) 19 | const b = new Array("2") 20 | console.log(a, b) //[undefined, undefined] ["2"] 21 | ``` 22 | 23 | 这样一来,导致new Array的行为是不可预测的,Array.of()出现为的就是解决这个情况。 24 | 25 | ```javascript 26 | const c = Array.of(2) 27 | const d = Array.of("2") 28 | console.log(c, d) // [2] ["2"] 29 | ``` 30 | 31 | 使用Array.of()创建的数组传入的参数都是作为数组的元素,而不在是数组长度,这样就避免了使用上的歧义。 32 | 33 | #### Array.from() 34 | 35 | 如果说Array.of()是创建一个新数组,而**Array.from()是将类数组转换成数组**。 36 | 37 | 下面的例子讲的是将arguments转换成数组。arguments是类数组对象,他表示的是当前函数的所有参数,如果函数没有参数,那么arguments就为空。 38 | 39 | ```javascript 40 | function test(a, b) { 41 | let arr = Array.from(arguments) 42 | console.log(arr) 43 | } 44 | test(1, 2) //[1, 2] 45 | ``` 46 | 47 | **映射转换:**Array.from(arg1, arg2),我们可以给该方法提供2个参数,第二个参数作为第一个参数的转换。看个简单例子你就懂了。 48 | 49 | ```javascript 50 | function test(a, b) { 51 | let arr = Array.from(arguments, value => value + 2) 52 | console.log(arr) 53 | } 54 | test(1, 2) //[3, 4] 55 | ``` 56 | 57 | Array.from还可以设置第三个参数,指定this。 58 | 59 | **Array.from()转换可迭代对象:**这个用法只需要一个例子,数组去重。 60 | 61 | ```javascript 62 | function test() { 63 | return Array.from(new Set(...arguments)) 64 | } 65 | const s = test([1, "2", 3, 3, "2"]) 66 | console.log(s) // [1,"2",3] 67 | ``` 68 | 69 | ### 给数组添加新方法 70 | 71 | **ES6给数组添加了几个新方法:find()、findIndex()、fill()、copyWithin()。** 72 | 73 | 1、find():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。 74 | 75 | ```javascript 76 | const arr = [1, "2", 3, 3, "2"] 77 | console.log(arr.find(n => typeof n === "number")) // 1 78 | ``` 79 | 80 | 2、findIndex():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。 81 | 82 | ```javascript 83 | const arr = [1, "2", 3, 3, "2"] 84 | console.log(arr.findIndex(n => typeof n === "number")) // 0 85 | ``` 86 | 87 | 3、fill():用新元素替换掉数组内的元素,可以指定替换下标范围。 88 | 89 | ```javascript 90 | arr.fill(value, start, end) 91 | ``` 92 | 93 | 测试一下 94 | 95 | ```javascript 96 | const arr = [1, 2, 3] 97 | console.log(arr.fill(4)) // [4, 4, 4] 不指定开始和结束,全部替换 98 | 99 | const arr1 = [1, 2, 3] 100 | console.log(arr1.fill(4, 1)) // [1, 4, 4] 指定开始位置,从开始位置全部替换 101 | 102 | const arr2 = [1, 2, 3] 103 | console.log(arr2.fill(4, 0, 2)) // [4, 4, 3] 指定开始和结束位置,替换当前范围的元素 104 | ``` 105 | 106 | 4、copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。 107 | 108 | ```javascript 109 | arr.copyWithin(target, start, end) 110 | ``` 111 | 112 | 测试一下 113 | ```javascript 114 | const arr = [1, 2, 3, 4, 5] 115 | console.log(arr.copyWithin(3)) // [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2 116 | 117 | const arr1 = [1, 2, 3, 4, 5] 118 | console.log(arr1.copyWithin(3, 1)) // [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3 119 | 120 | const arr2 = [1, 2, 3, 4, 5] 121 | console.log(arr2.copyWithin(3, 1, 2)) // [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2 122 | ``` 123 | 124 | #### 其他新增方法 125 | 126 | 其他还有定型数组、数组缓冲区的概念,你可以详细查看书上教程。 127 | 128 | ### 总结 129 | 130 | 掌握新的创建数组的方式,以及数组新增的几个方法,就够你使用了。定型数组和数组缓冲区一般人可以不用了解的太详细。 131 | 132 | [=> 返回文章目录][1] 133 | 134 | [1]: https://segmentfault.com/a/1190000010199272 135 | 136 | 上一节:[9.JavaScript中的类](https://github.com/hyy1115/ES6-learning/blob/master/doc/9%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%20JavaScript%E4%B8%AD%E7%9A%84%E7%B1%BBclass.md) 137 | 138 | 下一节:[11.Promise与异步编程](https://github.com/hyy1115/ES6-learning/blob/master/doc/11%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%20Promise%E4%B8%8E%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B.md) -------------------------------------------------------------------------------- /doc/es6/class.md: -------------------------------------------------------------------------------- 1 | ### ES5中的近类结构 2 | ES5以及之前的版本,没有类的概念,但是聪明的JavaScript开发者,为了实现面向对象,创建了特殊的近类结构。 3 | 4 | **ES5中创建类的方法:新建一个构造函数,定义一个方法并且赋值给构造函数的原型。** 5 | 6 | ```javascript 7 | 'use strict'; 8 | //新建构造函数,默认大写字母开头 9 | function Person(name) { 10 | this.name = name; 11 | } 12 | //定义一个方法并且赋值给构造函数的原型 13 | Person.prototype.sayName = function () { 14 | return this.name; 15 | }; 16 | 17 | var p = new Person('eryue'); 18 | console.log(p.sayName() // eryue 19 | ); 20 | ``` 21 | 22 | ### ES6 class类 23 | ES6实现类非常简单,只需要类声明。推荐 [babel在线测试ES6][1] 测试下面的代码。 24 | 25 | #### 类声明 26 | 27 | 如果你学过java,那么一定会非常熟悉这种声明类的方式。 28 | 29 | ```javascript 30 | class Person { 31 | //新建构造函数 32 | constructor(name) { 33 | this.name = name //私有属性 34 | } 35 | //定义一个方法并且赋值给构造函数的原型 36 | sayName() { 37 | return this.name 38 | } 39 | } 40 | let p = new Person('eryue') 41 | console.log(p.sayName()) // eryue 42 | ``` 43 | 44 | 和ES5中使用构造函数不同的是,在ES6中,我们将原型的实现写在了类中,但本质上还是一样的,都是需要新建一个类名,然后实现构造函数,再实现原型方法。 45 | 46 | **私有属性:**在class中实现私有属性,只需要在构造方法中定义this.xx = xx。 47 | 48 | #### 类声明和函数声明的区别和特点 49 | 50 | 1、函数声明可以被提升,类声明不能提升。 51 | 52 | 2、类声明中的代码自动强行运行在严格模式下。 53 | 54 | 3、类中的所有方法都是不可枚举的,而自定义类型中,可以通过Object.defineProperty()手工指定不可枚举属性。 55 | 56 | 4、每个类都有一个[[construct]]的方法。 57 | 58 | 5、只能使用new来调用类的构造函数。 59 | 60 | 6、不能在类中修改类名。 61 | 62 | #### 类表达式 63 | 64 | 类有2种表现形式:声明式和表达式。 65 | 66 | ```javascript 67 | //声明式 68 | class B { 69 | constructor() {} 70 | } 71 | 72 | //匿名表达式 73 | let A = class { 74 | constructor() {} 75 | } 76 | 77 | //命名表达式,B可以在外部使用,而B1只能在内部使用 78 | let B = class B1 { 79 | constructor() {} 80 | } 81 | ``` 82 | 83 | #### 类是一等公民 84 | 85 | JavaScript函数是一等公民,类也设计成一等公民。 86 | 87 | 1、可以将类作为参数传入函数。 88 | 89 | ```javascript 90 | //新建一个类 91 | let A = class { 92 | sayName() { 93 | return 'eryue' 94 | } 95 | } 96 | //该函数返回一个类的实例 97 | function test(classA) { 98 | return new classA() 99 | } 100 | //给test函数传入A 101 | let t = test(A) 102 | console.log(t.sayName()) // eryue 103 | ``` 104 | 105 | 2、通过立即调用类构造函数可以创建单例。 106 | 107 | ```javascript 108 | let a = new class { 109 | constructor(name) { 110 | this.name = name 111 | } 112 | sayName() { 113 | return this.name 114 | } 115 | }('eryue') 116 | console.log(a.sayName()) // eryue 117 | ``` 118 | 119 | #### 访问器属性 120 | 121 | 类支持在原型上定义访问器属性。 122 | ```javascript 123 | class A { 124 | constructor(state) { 125 | this.state = state 126 | } 127 | // 创建getter 128 | get myName() { 129 | return this.state.name 130 | } 131 | // 创建setter 132 | set myName(name) { 133 | this.state.name = name 134 | } 135 | } 136 | // 获取指定对象的自身属性描述符。自身属性描述符是指直接在对象上定义(而非从对象的原型继承)的描述符。 137 | let desriptor = Object.getOwnPropertyDescriptor(A.prototype, "myName") 138 | console.log("get" in desriptor) // true 139 | console.log(desriptor.enumerable) // false 不可枚举 140 | ``` 141 | 142 | #### 可计算成员名称 143 | 144 | 可计算成员是指使用方括号包裹一个表达式,如下面定义了一个变量m,然后使用[m]设置为类A的原型方法。 145 | ```javascript 146 | let m = "sayName" 147 | class A { 148 | constructor(name) { 149 | this.name = name 150 | } 151 | [m]() { 152 | return this.name 153 | } 154 | } 155 | let a = new A("eryue") 156 | console.log(a.sayName()) // eryue 157 | ``` 158 | 159 | #### 生成器方法 160 | 161 | 回顾一下上一章讲的生成器,生成器是一个返回迭代器的函数。在类中,我们也可以使用生成器方法。 162 | 163 | ```javascript 164 | class A { 165 | *printId() { 166 | yield 1; 167 | yield 2; 168 | yield 3; 169 | } 170 | } 171 | let a = new A() 172 | let x = a.printId() 173 | 174 | x.next() // {done: false, value: 1} 175 | x.next() // {done: false, value: 2} 176 | x.next() // {done: false, value: 3} 177 | ``` 178 | 179 | 这个写法很有趣,我们新增一个原型方法稍微改动一下。 180 | 181 | ```javascript 182 | class A { 183 | *printId() { 184 | yield 1; 185 | yield 2; 186 | yield 3; 187 | } 188 | render() { 189 | //从render方法访问printId,很熟悉吧,这就是react中经常用到的写法。 190 | return this.printId() 191 | } 192 | } 193 | let a = new A() 194 | console.log(a.render().next()) // {done: false, value: 1} 195 | ``` 196 | 197 | #### 静态成员 198 | 199 | 静态成员是指在方法名或属性名前面加上static关键字,和普通方法不一样的是,static修饰的方法不能在实例中访问,只能在类中直接访问。 200 | ```javascript 201 | class A { 202 | constructor(name) { 203 | this.name = name 204 | } 205 | static create(name) { 206 | return new A(name) 207 | } 208 | } 209 | let a = A.create("eryue") 210 | console.log(a.name) // eryue 211 | 212 | let t = new A() 213 | console.log(t.create("eryue")) // t.create is not a function 214 | ``` 215 | 216 | #### 继承与派生类 217 | 218 | 我们在写react的时候,自定义的组件会继承React.Component。 219 | 220 | ```javascript 221 | class A extends Component { 222 | constructor(props){ 223 | super(props) 224 | } 225 | } 226 | ``` 227 | 228 | **A叫做派生类**,在派生类中,如果使用了构造方法,就必须使用super()。 229 | 230 | ```javascript 231 | class Component { 232 | constructor([a, b] = props) { 233 | this.a = a 234 | this.b = b 235 | } 236 | add() { 237 | return this.a + this.b 238 | } 239 | } 240 | 241 | class T extends Component { 242 | constructor(props) { 243 | super(props) 244 | } 245 | } 246 | 247 | let t = new T([2, 3]) 248 | console.log(t.add()) // 5 249 | ``` 250 | 251 | **关于super使用的几点要求:** 252 | 253 | 1、只可以在派生类中使用super。派生类是指继承自其它类的新类。 254 | 255 | 2、在构造函数中访问this之前要调用super(),负责初始化this。 256 | 257 | ```javascript 258 | class T extends Component { 259 | constructor(props) { 260 | this.name = 1 // 错误,必须先写super() 261 | super(props) 262 | } 263 | } 264 | ``` 265 | 266 | 3、如果不想调用super,可以让类的构造函数返回一个对象。 267 | 268 | #### 类方法遮蔽 269 | 270 | 我们可以在继承的类中重写父类的方法。 271 | ```javascript 272 | class Component { 273 | constructor([a, b] = props) { 274 | this.a = a 275 | this.b = b 276 | } 277 | //父类的add方法,求和 278 | add() { 279 | return this.a + this.b 280 | } 281 | } 282 | 283 | class T extends Component { 284 | constructor(props) { 285 | super(props) 286 | } 287 | //重写add方法,求积 288 | add() { 289 | return this.a * this.b 290 | } 291 | } 292 | let t = new T([2, 3]) 293 | console.log(t.add()) // 6 294 | ``` 295 | 296 | #### 静态成员继承 297 | 298 | **父类中的静态成员,也可以继承到派生类中。**静态成员继承只能通过派生类访问,不能通过派生类的实例访问。 299 | 300 | ```javascript 301 | class Component { 302 | constructor([a, b] = props) { 303 | this.a = a 304 | this.b = b 305 | } 306 | static printSum([a, b] = props) { 307 | return a + b 308 | } 309 | } 310 | class T extends Component { 311 | constructor(props) { 312 | super(props) 313 | } 314 | } 315 | console.log(T.printSum([2, 3])) // 5 316 | ``` 317 | 318 | #### 派生自表达式的类 319 | 320 | 很好理解,就是指父类可以是一个表达式。 321 | 322 | #### 内建对象的继承 323 | 324 | 有些牛逼的人觉得使用内建的Array不够爽,就希望ECMA提供一种继承内建对象的方法,然后那帮大神们就把这个功能添加到class中了。 325 | ```javascript 326 | class MyArray extends Array { } 327 | let colors = new MyArray() 328 | colors[0] = "1" 329 | console.log(colors) // [1] 330 | ``` 331 | 332 | #### Symbol.species 333 | 334 | 该用法我还没有接触过,目前只知道在内建对象中使用了该方法,如果在类中调用this.constructor,使用Symbol.species可以让派生类重写返回类型。 335 | 336 | #### 在构造函数中使用new.target 337 | 338 | new.target通常表示当前的构造函数名。通常我们使用new.target来阻止直接实例化基类,下面是这个例子的实现。 339 | 340 | ```javascript 341 | class A { 342 | constructor() { 343 | //如果当前的new.target为A类,就抛出异常 344 | if (new.target === A) { 345 | throw new Error("error haha") 346 | } 347 | } 348 | } 349 | let a = new A() 350 | console.log(a) // error haha 351 | ``` 352 | 353 | ### 总结 354 | 355 | 本章只有一个知识点,那就是class的使用,最开始的声明class,到后面的继承派生类,都是非常常用的写法,还有静态成员的使用。 356 | 357 | 如果上面的那些例子你练习的不够爽,或许你该找个react基础demo简单的使用class来练练手了。 358 | 359 | [=> 返回文章目录][2] 360 | 361 | 362 | [1]: https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=true&presets=env,es2015,es2015-loose,es2016,es2017,stage-0,stage-1,stage-2,stage-3&targets=&browsers=%3E%202%25,%20ie%209,&builtIns=false&debug=false&code_lz=MYGwhgzhAECC0G8BQ1rAPYDsIBcBOArsDungBS5g4CmAlIiqtDgBYCWEAdJTdALzQe1RgF9GAc2o5oAWwCeAOTAzqZesibQ8UgnkzN2XIZ0zLhqMaghTZis2VMr1jVKw7ccVaibP9oj82gxMRAbABNqCDw2AAcSPD8AeQAjACtqYk5JHESAd0wABTx0GOo8HDkAEUjgaLjSMlhOGOKSCtKAGmgAInklFW7aJAxsdFDOEHRxMgio2PjOakwCFTwwZNDaIA 363 | [2]: https://segmentfault.com/a/1190000010199272 364 | 365 | 上一节:[8.迭代器(Iterator)和生成器(Generator)](https://github.com/hyy1115/ES6-learning/blob/master/doc/8%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E8%BF%AD%E4%BB%A3%E5%99%A8%EF%BC%88Iterator%EF%BC%89%E5%92%8C%E7%94%9F%E6%88%90%E5%99%A8%EF%BC%88Generator%EF%BC%89.md) 366 | 367 | 下一节:[10.改进数组的功能](https://github.com/hyy1115/ES6-learning/blob/master/doc/10%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%20%E6%94%B9%E8%BF%9B%E6%95%B0%E7%BB%84%E7%9A%84%E5%8A%9F%E8%83%BD.md) -------------------------------------------------------------------------------- /doc/es6/esModule.md: -------------------------------------------------------------------------------- 1 | ### 模块的定义 2 | 3 | 模块是自动运行在严格模式下并且没有办法退出运行的JavaScript代码。 4 | 5 | 模块可以是函数、数据、类,需要指定导出的模块名,才能被其他模块访问。 6 | ```javascript 7 | //数据模块 8 | const obj = {a: 1} 9 | //函数模块 10 | const sum = (a, b) => { 11 | return a + b 12 | } 13 | //类模块 14 | class My extends React.Components { 15 | 16 | } 17 | ``` 18 | ### 模块的导出 19 | 20 | 给数据、函数、类添加一个export,就能导出模块。一个配置型的JavaScript文件中,你可能会封装多种函数,然后给每个函数加上一个export关键字,就能在其他文件访问到。 21 | ```javascript 22 | //数据模块 23 | export const obj = {a: 1} 24 | //函数模块 25 | export const sum = (a, b) => { 26 | return a + b 27 | } 28 | //类模块 29 | export class My extends React.Components { 30 | 31 | } 32 | ``` 33 | ### 模块的引用 34 | 35 | 在另外的js文件中,我们可以引用上面定义的模块。使用import关键字,导入分2种情况,一种是导入指定的模块,另外一种是导入全部模块。 36 | 37 | 1、导入指定的模块。 38 | ```javascript 39 | //导入obj数据,My类 40 | import {obj, My} from './xx.js' 41 | 42 | //使用 43 | console.log(obj, My) 44 | ``` 45 | 2、导入全部模块 46 | ```javascript 47 | //导入全部模块 48 | import * as all from './xx.js' 49 | 50 | //使用 51 | console.log(all.obj, all.sun(1, 2), all.My) 52 | ``` 53 | ### 默认模块的使用 54 | 55 | 如果给我们的模块加上default关键字,那么该js文件默认只导出该模块,你还需要把大括号去掉。 56 | ```javascript 57 | //默认模块的定义 58 | function sum(a, b) { 59 | return a + b 60 | } 61 | export default sum 62 | 63 | //导入默认模块 64 | import sum from './xx.js' 65 | ``` 66 | ### 模块的使用限制 67 | 68 | 不能在语句和函数之内使用export关键字,只能在模块顶部使用,作为react和vue开发者的你,这个限制你应该很熟悉了。 69 | 70 | **在react中,模块顶部导入其他模块。** 71 | ```javascript 72 | import react from 'react' 73 | ``` 74 | **在vue中,模块顶部导入其他模块。** 75 | ```javascript 76 | 79 | ``` 80 | 81 | ### 修改模块导入和导出名 82 | 83 | 有2种修改方式,一种是模块导出时修改,一种是导入模块时修改。 84 | 85 | 1、导出时修改: 86 | ```javascript 87 | function sum(a, b) { 88 | return a + b 89 | } 90 | export {sum as add} 91 | 92 | import { add } from './xx.js' 93 | add(1, 2) 94 | ``` 95 | 2、导入时修改: 96 | ```javascript 97 | function sum(a, b) { 98 | return a + b 99 | } 100 | export sum 101 | 102 | import { sum as add } from './xx.js' 103 | add(1, 2) 104 | ``` 105 | ### 无绑定导入 106 | 107 | 当你的模块没有可导出模块,全都是定义的全局变量的时候,你可以使用无绑定导入。 108 | 109 | 模块: 110 | ```javascript 111 | let a = 1 112 | const PI = 3.1314 113 | ``` 114 | 无绑定导入: 115 | ```javascript 116 | import './xx.js' 117 | console.log(a, PI) 118 | ``` 119 | ### 浏览器加载模块 120 | 121 | 有用过webpack打包js模块的同学可能有经验,使用webpack打包了多个js文件,然后放到HTML使用script加载时,如果加载顺序不对,就会出现找不到模块的错误。 122 | 123 | 这是因为模块之间是有依赖关系的,就像你使用jQuery的时候,必须先加载jQuery的代码,才能使用jQuery提供的方法。 124 | 125 | **加载模块的方法,总是先加载模块1,再加载模块2,因为module类型默认使用defer属性。** 126 | ```javascript 127 | 128 | 129 | ``` 130 | ### 总结 131 | 132 | 模块还有很多有意思的特性,对react和vue开发有一定经验的人对这些基本知识应该了如指掌,新手不了解也不用太心急,写几个module.js做一下尝试。如果浏览器报错,不能识别export模块,你可能需要先加载babel的js插件来编译它。 133 | 134 | [1]: https://segmentfault.com/a/1190000010199272 135 | 136 | 上一节:[12.代理(Proxy)和反射(Reflection)API](https://github.com/hyy1115/ES6-learning/blob/master/doc/12%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E4%BB%A3%E7%90%86%EF%BC%88Proxy%EF%BC%89%E5%92%8C%E5%8F%8D%E5%B0%84%EF%BC%88Reflection%EF%BC%89API.md) 137 | 138 | [==>返回目录](https://github.com/hyy1115/ES6-learning/tree/master/doc) 139 | -------------------------------------------------------------------------------- /doc/es6/func.md: -------------------------------------------------------------------------------- 1 | 作为前端切图仔,越发觉得自己离不开函数了。 2 | 3 | 说到JavaScript函数,脑子里都是匿名函数、普通函数、闭包函数、构造函数......然后还能说出一大堆函数的概念。如果你达到这个水平,那么函数对你来说没有难度,是一个前端老鸟了。 4 | 5 | 当我闭上眼睛,不看键盘,手指在键盘上敲击出一个排序函数的时候,我在想,还是回顾一下函数的基本知识才有意思。 6 | 7 | ### 函数的默认参数 8 | 在ES5中,我们给函数传参数,然后在函数体内设置默认值,如下面这种方式。 9 | 10 | ```javascript 11 | function a(num, callback) { 12 | num = num || 6 13 | callback = callback || function (data) {console.log('ES5: ', data)} 14 | callback(num * num) 15 | } 16 | a() //ES5: 36,不传参输出默认值 17 | 18 | //你还可以这样使用callback 19 | a(10, function(data) { 20 | console.log(data * 10) // 1000, 传参输出新数值 21 | }) 22 | ``` 23 | 24 | **而在ES6中,我们使用新的默认值写法。** 25 | 26 | ```javascript 27 | function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) { 28 | callback(num * num) 29 | } 30 | 31 | a() //ES6: 36, 不传参输出默认值 32 | 33 | a(10, function(data) { 34 | console.log(data * 10) // 1000,传参输出新数值 35 | }) 36 | ``` 37 | 38 | **使用ES6的默认值写法可以让函数体内部的代码更加简洁优雅** 39 | 40 | **默认值对arguments对象的影响** 41 | 42 | 我们先要了解arguments对象是什么?准确一点来说它是一个类数组对象,它存在函数内部,它将当前函数的所有参数组成了一个类数组对象。 43 | 44 | ```javascript 45 | function a(num, b){ 46 | console.log(arguments) // {"0": 6, "1": 10} 47 | console.log(arguments.length) // 2 48 | } 49 | 50 | a(6, 10) 51 | ``` 52 | 53 | 上面的输出结果看起来很正常,那么,如果我们加上参数默认值会怎样呢? 54 | 55 | ```javascript 56 | function a(num = 1, b = 1){ 57 | console.log(arguments) 58 | } 59 | a() // {} 默认值不能被arguments识别。 60 | a(6, 10) // {"0":6,"1":10} 61 | ``` 62 | 63 | 下面我们看一下修改参数默认值对arguments的影响。 64 | 65 | 1、在ES5的非严格模式下,一开始输入的参数是1,那么可以获取到arguments[0](表示第一个参数)全等于num,修改num = 2之后,arguments[0]也能更新到2。 66 | 67 | ```javascript 68 | function a(num){ 69 | console.log(num === arguments[0]) //true 70 | num = 2 //修改参数默认值 71 | console.log(num === arguments[0]) //true 72 | } 73 | a(1) 74 | ``` 75 | 76 | 2、在ES5的严格模式下,arguments就不能在函数内修改默认值后跟随着跟新了。 77 | 78 | ```javascript 79 | "use strict"; //严格模式 80 | function a(num) { 81 | console.log(num === arguments[0]); // true 82 | num = 2; 83 | console.log(num === arguments[0]); // false 84 | } 85 | a(1); 86 | ``` 87 | **在ES6环境下,默认值对arguments的影响和ES5严格模式是同样的标准。** 88 | 89 | **默认参数表达式** 90 | 91 | 参数不仅可以设置默认值为字符串,数字,数组或者对象,还可以是一个函数。 92 | 93 | ```javascript 94 | function add() { 95 | return 10 96 | } 97 | function a(num = add()){ 98 | console.log(num) 99 | } 100 | a() // 10 101 | ``` 102 | **默认参数的临时死区** 103 | 104 | 第一章我们提到了let和const什么变量的临时死区(TDZ),默认参数既然是参数,那么也同样有临时死区,函数的作用域是独立的,a函数不能共享b函数的作用域参数。 105 | 106 | ```javascript 107 | //这是个默认参数临时死区的例子,当初始化a时,b还没有声明,所以第一个参数对b来说就是临时死区。 108 | function add(a = b, b){ 109 | console.log(a + b) 110 | } 111 | add(undefined, 2) // b is not define 112 | ``` 113 | 114 | ### 无命名参数 115 | 116 | 上面说的参数都是命名参数,而无命名参数也是函数传参时经常用到的。当传入的参数是一个对象,不是一个具体的参数名,则是无命名参数。 117 | 118 | ```javascript 119 | function add(object){ 120 | console.log(object.a + object.b) 121 | } 122 | let obj = { 123 | a: 1, 124 | b: 2 125 | } 126 | add(obj) // 3 127 | ``` 128 | 129 | **不定参数的使用:**使用...(展开运算符)的参数就是不定参数,它表示一个数组。 130 | 131 | ```javascript 132 | function add(...arr){ 133 | console.log(a + b) 134 | } 135 | let a = 1,b = 2 136 | add(a, b) // 3 137 | ``` 138 | 139 | **不定参数的使用限制:**必须放在所有参数的末尾,不能用于对象字面量setter中。 140 | 141 | ```javascript 142 | //错误的写法1 143 | function add(...arr, c){ 144 | console.log(a + b) 145 | } 146 | let a = 1,b = 2,c = 3 147 | add(a, b, c) 148 | 149 | //错误的写法2 150 | let obj = { 151 | set add(...arr) { 152 | 153 | } 154 | } 155 | ``` 156 | 157 | **ES6中的构造函数Function新增了支持默认参数和不定参数。** 158 | 159 | ### 展开运算符(...) 160 | 161 | 展开运算符的作用是解构数组,然后将每个数组元素作为函数参数。 162 | 163 | 有了展开运算符,我们操作数组的时候,就可以不再使用apply来指定上下文环境了。 164 | 165 | ```javascript 166 | //ES5的写法 167 | let arr = [10, 20, 50, 40, 30] 168 | let a = Math.max.apply(null, arr) 169 | console.log(a) // 50 170 | 171 | //ES6的写法 172 | let arr = [10, 20, 50, 40, 30] 173 | let a = Math.max(...arr) 174 | console.log(a) // 50 175 | ``` 176 | 177 | ### 块级函数 178 | 179 | **严格模式下:**在ES6中,你可以在块级作用域内声明函数,该函数的作用域只限于当前块,不能在块的外部访问。 180 | ```javascript 181 | "use strict"; 182 | if(true) { 183 | const a = function(){ 184 | 185 | } 186 | } 187 | ``` 188 | **非严格模式:**即使在ES6中,非严格模式下的块级函数,他的作用域也会被提升到父级函数的顶部。所以大家写代码尽量使用严格模式,避免这些奇葩情况。 189 | 190 | ### 箭头函数(=>) 191 | 192 | 如果看到你这里,你发现你还没有在项目中使用过箭头函数,没关系,你并不low,而是学习不够努力。 193 | 194 | ```javascript 195 | const arr = [5, 10] 196 | const s = arr.reduce((sum, item) => sum + item) 197 | console.log(s) // 15 198 | ``` 199 | 200 | **箭头函数和普通函数的区别是:** 201 | 202 | 1、箭头函数没有this,函数内部的this来自于父级最近的非箭头函数,并且不能改变this的指向。 203 | 204 | 2、箭头函数没有super 205 | 206 | 3、箭头函数没有arguments 207 | 208 | 4、箭头函数没有new.target绑定。 209 | 210 | 5、不能使用new 211 | 212 | 6、没有原型 213 | 214 | 7、不支持重复的命名参数。 215 | 216 | **箭头函数的简单理解** 217 | 218 | 1、箭头函数的左边表示输入的参数,右边表示输出的结果。 219 | 220 | ```javascript 221 | const s = a => a 222 | console.log(s(2)) // 2 223 | ``` 224 | 225 | 2、箭头函数中,最重要的this报错将不再成为你每天都担心的bug。 226 | 227 | 3、箭头函数还可以输出对象,在react的action中就推荐这种写法。 228 | 229 | ```javascript 230 | const action = (type, a) => ({ 231 | type: "TYPE", 232 | a 233 | }) 234 | ``` 235 | 236 | 4、支持立即执行函数表达式写法 237 | 238 | ```javascript 239 | const test = ((id) => { 240 | return { 241 | getId() { 242 | console.log(id) 243 | } 244 | } 245 | })(18) 246 | test.getId() // 18 247 | ``` 248 | 249 | 5、箭头函数给数组排序 250 | 251 | ```javascript 252 | const arr = [10, 50, 30, 40, 20] 253 | const s = arr.sort((a, b) => a - b) 254 | console.log(s) // [10,20,30,40,50] 255 | ``` 256 | 257 | ### 尾调用优化 258 | 259 | 尾调用是什么鬼? 260 | 261 | 尾调用是指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。 262 | 263 | ES6中,引擎会帮你做好尾调用的优化工作,你不需要自己优化,但需要满足下面3个要求: 264 | 265 | 1、函数不是闭包 266 | 267 | 2、尾调用是函数最后一条语句 268 | 269 | 3、尾调用结果作为函数返回 270 | 271 | **一个满足以上要求的函数如下所示:** 272 | 273 | ```javascript 274 | "use strict"; 275 | function a() { 276 | return b(); 277 | } 278 | ``` 279 | 280 | **下面的都是不满足的写法:** 281 | 282 | ```javascript 283 | //没有return不优化 284 | "use strict"; 285 | function a() { 286 | b(); 287 | } 288 | 289 | //不是直接返回函数不优化 290 | "use strict"; 291 | function a() { 292 | return 1 + b(); 293 | } 294 | 295 | //尾调用是函数不是最后一条语句不优化 296 | "use strict"; 297 | function a() { 298 | const s = b(); 299 | return s 300 | } 301 | 302 | //闭包不优化 303 | "use strict"; 304 | function a() { 305 | const num = 1 306 | function b() { 307 | return num 308 | } 309 | return b 310 | } 311 | ``` 312 | 313 | **尾调用实际用途——递归函数优化** 314 | 315 | 在ES5时代,我们不推荐使用递归,因为递归会影响性能。 316 | 317 | 但是有了尾调用优化之后,递归函数的性能有了提升。 318 | 319 | ```javascript 320 | //新型尾优化写法 321 | "use strict"; 322 | function a(n, p = 1) { 323 | if(n <= 1) { 324 | return 1 * p 325 | } 326 | let s = n * p 327 | return a(n - 1, s) 328 | } 329 | //求 1 x 2 x 3的阶乘 330 | let sum = a(3) 331 | console.log(sum) // 6 332 | ``` 333 | 334 | ### 总结 335 | 336 | 函数这一章涉及到的知识点比较多,默认参数,命名参数,不定参数,展开运算符,箭头函数,尾调用优化。 337 | 338 | 第一次学习这些知识的人可以关注箭头函数和展开运算符的使用,这是最重要也最常用的知识,如果你已经在项目中使用过这些知识,那么作为巩固也是有帮助的,俗话说温故知新。 339 | 340 | 341 | 342 | [1]: https://segmentfault.com/a/1190000010199272 343 | 344 | 上一节:[2.字符串和正则表达式](https://github.com/hyy1115/ES6-learning/blob/master/doc/2%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%92%8C%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md) 345 | 346 | 下一节:[4.扩展对象的功能性](https://github.com/hyy1115/ES6-learning/blob/master/doc/4%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E6%89%A9%E5%B1%95%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%8A%9F%E8%83%BD%E6%80%A7.md) -------------------------------------------------------------------------------- /doc/es6/promise.md: -------------------------------------------------------------------------------- 1 | #### 为什么要异步编程 2 | 3 | 我们在写前端代码时,经常会对dom做事件处理操作,比如点击、激活焦点、失去焦点等;再比如我们用ajax请求数据,使用回调函数获取返回值。这些都属于异步编程。 4 | 5 | 也许你已经大概知道JavaScript引擎单线程的概念,那么这种单线程模式和异步编程有什么关系呢? 6 | 7 | **JavaScript引擎中,只有一个主线程,当执行JavaScript代码块时,不允许其他代码块执行,而事件机制和回调机制的代码块会被添加到任务队列中,当符合某个触发回调或者事件的时候,就会执行该事件或者回调函数。** 8 | 9 | 上面这段话的意思可以这样理解,假设你是一个修仙者,你去闯一个秘境,这个秘境就是主线程,你只能一直深入下去,直到找到宝物和出口,而你还有一个自身的储物空间,这个空间就类似存放任务队列的东西,你在储物空间放了很多你可能用到的法宝或者丹药,这些东西就是回调函数和事件函数,当你遇到危险或者满足某个条件时,就可以从储物空间拿出你当前需要的东西。 10 | 11 | 好吧,不扯这么远,下面看正题。 12 | 13 | **事件模型:** 14 | 浏览器初次渲染DOM的时候,我们会给一些DOM绑定事件函数,只有当触发了这些DOM事件函数,才会执行他们。 15 | 16 | ```javascript 17 | const btn = document.querySelector('.button') 18 | btn.onclick = function(event) { 19 | console.log(event) 20 | } 21 | ``` 22 | 23 | **回调模式:** 24 | nodejs中可能非常常见这种回调模式,但是对于前端来说,ajax的回调是最熟悉不过了。ajax回调有多个状态,当响应成功和失败都有不同的回调函数。 25 | 26 | ```javascript 27 | $.post('/router', function(data) { 28 | console.log(data) 29 | }) 30 | ``` 31 | 32 | 回调也可能带来一个问题,那就是地狱回调,不过幸运的是,我从进入前端界开始,就使用react,跳过了很多坑,特别是地狱回调,一直没有机会在工作中遇见到,真是遗憾。 33 | 34 | ### Promise 35 | 36 | 事件函数没有问题,我们用的很爽,问题出在回调函数,尤其是指地狱回调,Promise的出现正是为了避免地狱回调带来的困扰。 37 | 38 | 推荐你看[JavaScript MDN Promise教程][1],然后再结合本文看,你就能学会使用Promise了。 39 | 40 | #### Promise是什么 41 | 42 | Promise的中文意思是承诺,也就是说,JavaScript对你许下一个承诺,会在未来某个时刻兑现承诺。 43 | 44 | #### Promise生命周期 45 | 46 | react有生命周期,vue也有生命周期,就连Promise也有生命周期,现在生命周期咋这么流行了。 47 | 48 | **Promise的生命周期:进行中(pending),已经完成(fulfilled),拒绝(rejected)** 49 | 50 | Promise被称作异步结果的占位符,它不能直接返回异步函数的执行结果,需要使用then(),当获取异常回调的时候,使用catch()。 51 | 52 | 这次我们使用axios插件的代码做例子。axios是前端比较热门的http请求插件之一。 53 | 54 | 1、创建axios实例instance。 55 | ```javascript 56 | import axios from 'axios' 57 | export const instance = axios.create() 58 | ``` 59 | 60 | 2、使用axios实例 + Promise获取返回值。 61 | 62 | ```javascript 63 | const promise = instance.get('url') 64 | 65 | promise.then(result => console.log(result)).catch(err => console.log(err)) 66 | ``` 67 | 68 | #### 使用Promise构建函数创建新的Promise 69 | 70 | Promise构造函数只有一个参数,该参数是一个函数,被称作执行器,执行器有2个参数,分别是resolve()和reject(),一个表示成功的回调,一个表示失败的回调。 71 | 72 | ```javascript 73 | new Promise(function(resolve, reject) { 74 | setTimeout(() => resolve(5), 0) 75 | }).then(v => console.log(v)) // 5 76 | ``` 77 | 78 | **记住,Promise实例只能通过resolve或者reject函数来返回,并且使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的。** 79 | 80 | 1、我们也可以使用Promise直接resolve(value)。 81 | 82 | ```javascript 83 | Promise.resolve(5).then(v => console.log(v)) // 5 84 | ``` 85 | 86 | 2、也可以使用reject(value) 87 | 88 | ```javascript 89 | Promise.reject(5).catch(v => console.log(v)) // 5 90 | ``` 91 | 92 | 3、执行器错误通过catch捕捉。 93 | 94 | ```javascript 95 | new Promise(function(resolve, reject) { 96 | if(true) { 97 | throw new Error('error!!') 98 | } 99 | }).catch(v => console.log(v.message)) // error!! 100 | ``` 101 | 102 | #### 全局的Promise拒绝处理 103 | 104 | 不重要的内容,不用细看。 105 | 106 | 这里涉及到nodejs环境和浏览器环境的全局,主要说的是如果执行了Promise.reject(),浏览器或者node环境并不会强制报错,只有在你调用catch的时候,才能知道Promise被拒绝了。 107 | 108 | 这种行为就像是,你写了一个函数,函数内部有true和false两种状态,而我们希望false的时候抛出错误,但是在Promise中,并不能直接抛出错误,**无论Promise是成功还是拒绝状态,你获取Promise生命周期的方法只能通过then()和catch()。** 109 | 110 | **nodejs环境:** 111 | 112 | node环境下有个对象叫做process,即使你没写过后端node,如果写过前端node服务器,也应该知道可以使用process.ENV_NODE获取环境变量。为了监听Promise的reject(拒绝)情况,NodeJS提供了一个process.on(),类似jQuery的on方法,事件绑定函数。 113 | 114 | process.on()有2个事件 115 | 116 | unhandledRjection:在一个事件循环中,当Promise执行reject(),并且没有提供catch()时被调用。 117 | 118 | 正常情况下,你可以使用catch捕捉reject。 119 | ```javascript 120 | Promise.reject("It was my wrong!").catch(v => console.log(v)) 121 | ``` 122 | 123 | 但是,有时候你不总是记得使用catch。你就需要使用process.on() 124 | 125 | ```javascript 126 | let rejected 127 | rejected = Promise.reject("It was my wrong!") 128 | 129 | process.on("unhandledRjection", function(reason, promise) { 130 | console.log(reason.message) // It was my wrong! 131 | console.log(rejected === promise) // true 132 | }) 133 | ``` 134 | 135 | 136 | rejectionHandled:在一个事件循环后,当Promise执行reject,并且没有提供catch()时被调用。 137 | 138 | ```javascript 139 | let rejected 140 | rejected = Promise.reject(new Error("It was my wrong!")) 141 | 142 | process.on("rejectionHandled", function(promise) { 143 | console.log(rejected === promise) // true 144 | }) 145 | ``` 146 | 147 | **异同:** 148 | 149 | 事件循环中、事件循环后,你可能很难理解这2个的区别,但是这不重要,重要的是,如果你通过了catch()方法来捕捉reject操作,那么,这2个事件就不会生效。 150 | 151 | **浏览器环境:** 152 | 153 | 和node环境一样,都提供了unhandledRjection、rejectionHandled事件,不同的是浏览器环境是通过window对象来定义事件函数。 154 | 155 | ```javascript 156 | let rejected 157 | rejected = Promise.reject(new Error("It was my wrong!")) 158 | 159 | window.rejectionHandled = function(event) { 160 | console.log(event) // true 161 | } 162 | rejectionHandled() 163 | ``` 164 | 165 | 将代码在浏览器控制台执行一遍,你就会发现报错了:Uncaught (in promise) Error: It was my wrong! 166 | 167 | 耶,你成功了!报错内容正是你写的reject()方法里面的错误提示。 168 | 169 | #### Promise链式调用 170 | 171 | 这个例子中,使用了3个then,第一个then返回 s * s,第二个then捕获到上一个then的返回值,最后一个then直接输出end。这就叫链式调用,很好理解的。我只使用了then(),实际开发中,你还应该加上catch()。 172 | 173 | ```javascript 174 | new Promise(function(resolve, reject) { 175 | try { 176 | resolve(5) 177 | } catch (error) { 178 | reject('It was my wrong!!!') 179 | } 180 | }).then(s => s * s).then(s2 => console.log(s2)).then(() => console.log('end')) 181 | // 25 "end" 182 | ``` 183 | 184 | #### Promise的其他方法 185 | 在Promise的构造函数中,除了reject()和resolve()之外,还有2个方法,Promise.all()、Promise.race()。 186 | 187 | **Promise.all():** 188 | 189 | 前面我们的例子都是只有一个Promise,现在我们使用all()方法包装多个Promise实例。 190 | 191 | 语法很简单:参数只有一个,可迭代对象,可以是数组,或者Symbol类型等。 192 | 193 | ```javascript 194 | Promise.all(iterable).then().catch() 195 | ``` 196 | 197 | 示例:传入3个Promise实例 198 | ```javascript 199 | Promise.all([ 200 | new Promise(function(resolve, reject) { 201 | resolve(1) 202 | }), 203 | new Promise(function(resolve, reject) { 204 | resolve(2) 205 | }), 206 | new Promise(function(resolve, reject) { 207 | resolve(3) 208 | }) 209 | ]).then(arr => { 210 | console.log(arr) // [1, 2, 3] 211 | }) 212 | ``` 213 | 214 | **Promise.race():**语法和all()一样,但是返回值有所不同,race根据传入的多个Promise实例,只要有一个实例resolve或者reject,就只返回该结果,其他实例不再执行。 215 | 216 | 还是使用上面的例子,只是我给每个resolve加了一个定时器,最终结果返回的是3,因为第三个Promise最快执行。 217 | 218 | ```javascript 219 | Promise.race([ 220 | new Promise(function(resolve, reject) { 221 | setTimeout(() => resolve(1), 1000) 222 | }), 223 | new Promise(function(resolve, reject) { 224 | setTimeout(() => resolve(2), 100) 225 | }), 226 | new Promise(function(resolve, reject) { 227 | setTimeout(() => resolve(3), 10) 228 | }) 229 | ]).then(value => { 230 | console.log(value) // 3 231 | }) 232 | ``` 233 | 234 | #### Promise派生 235 | 236 | 派生的意思是定义一个新的Promise对象,继承Promise方法和属性。 237 | 238 | ```javascript 239 | class MyPromise extends Promise { 240 | 241 | //重新封装then() 242 | success(resolve, reject) { 243 | return this.then(resolve, reject) 244 | } 245 | //重新封装catch() 246 | failer(reject) { 247 | return this.catch(reject) 248 | } 249 | } 250 | ``` 251 | 252 | 接着我们来使用一下这个派生类。 253 | ​ 254 | ```javascript 255 | new MyPromise(function(resolve, reject) { 256 | resolve(10) 257 | }).success(v => console.log(v)) // 10 258 | ``` 259 | 260 | 如果只是派生出来和then、catch一样的方法,我想,你不会干这么无聊的事情。 261 | 262 | #### Promise和异步的联系 263 | 264 | Promise本身不是异步的,只有他的then()或者catch()方法才是异步,也可以说Promise的返回值是异步的。通常Promise被使用在node,或者是前端的ajax请求、前端DOM渲染顺序等地方。 265 | 266 | ###比Promise更牛逼的异步方案 267 | 268 | 在本章你只需要了解有async这个未来的方案,推荐不会的赶紧去网上找资料学,反正我是已经在实际项目中全面开展async了。 269 | ```javascript 270 | async function a() { 271 | await function() {} 272 | } 273 | ``` 274 | 275 | ### 总结 276 | 277 | Promise是什么、怎么用、怎么获取返回值?是本章的中心内容,多看几遍,你会发现使用Promise是非常简单的事情。 278 | 279 | [=> 返回文章目录][2] 280 | 281 | 282 | [1]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise 283 | [2]: https://segmentfault.com/a/1190000010199272 284 | 285 | 上一节:[10.改进数组的功能](https://github.com/hyy1115/ES6-learning/blob/master/doc/10%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%20%E6%94%B9%E8%BF%9B%E6%95%B0%E7%BB%84%E7%9A%84%E5%8A%9F%E8%83%BD.md) 286 | 287 | 下一节:[12.代理(Proxy)和反射(Reflection)API](https://github.com/hyy1115/ES6-learning/blob/master/doc/12%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E4%BB%A3%E7%90%86%EF%BC%88Proxy%EF%BC%89%E5%92%8C%E5%8F%8D%E5%B0%84%EF%BC%88Reflection%EF%BC%89API.md) 288 | -------------------------------------------------------------------------------- /doc/es6/proxy.md: -------------------------------------------------------------------------------- 1 | ### 反射 Reflect 2 | 3 | 当你见到一个新的API,不明白的时候,就在浏览器打印出来看看它的样子。 4 | 5 | ![clipboard.png](https://segmentfault.com/img/bVR5HF?w=1722&h=560) 6 | 7 | #### 反射的概念 8 | 9 | Reflect 是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与代理处理程序的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。 10 | 11 | ```javascript 12 | new Reflect() //错误的写法 13 | ``` 14 | 15 | #### 反射的使用 16 | 17 | Reflect提供了一些静态方法,静态方法是指只能通过对象自身访问的的方法,这个知识在前面几章讲解过。**所有方法的详细解析,前往 [Reflect详解][1] 查看。** 18 | 19 | **静态方法列表:**这么多静态方法,**你需要学会的是如何使用它们**。 20 | 21 | 1、Reflect.apply() 22 | 2、Reflect.construct() 23 | 3、Reflect.defineProperty() 24 | 4、Reflect.deleteProperty() 25 | 5、Reflect.enumerate() 26 | 6、Reflect.get() 27 | 7、Reflect.getOwnPropertyDescriptor() 28 | 8、Reflect.getPrototypeOf() 29 | 9、Reflect.has() 30 | 10、Reflect.isExtensible() 31 | 11、Reflect.ownKeys() 32 | 12、Reflect.preventExtensions() 33 | 13、Reflect.set() 34 | 14、Reflect.setPrototypeOf() 35 | 36 | **静态方法的使用:** 37 | 38 | demo1:使用Reflect.get()获取目标对象指定key的value。 39 | ```javascript 40 | let obj = { 41 | a: 1 42 | }; 43 | 44 | let s1 = Reflect.get(obj, "a") 45 | console.log(s1) // 1 46 | ``` 47 | demo2:使用Reflect.apply给目标函数floor传入指定的参数。 48 | ```javascript 49 | const s2 = Reflect.apply(Math.floor, undefined, [1.75]); 50 | console.log(s2) // 1 51 | ``` 52 | #### 进一步理解Reflect 53 | 54 | 看了上面的例子和方法,我们知道Reflect可以拦截JavaScript代码,包括拦截对象,拦截函数等,然后对拦截到的对象或者函数进行读写等操作。 55 | 56 | 比如demo1的get()方法,拦截obj对象,然后读取key为a的值。当然,不用反射也可以读取a的值。 57 | 58 | 再看demo2的apply()方法,这个方法你应该比较了解了,和数组中使用apply不同的是,Reflect.apply()提供了3个参数,第一个参数是反射的函数,后面2个参数才是和数组的apply一致。demo2的例子我们可以理解成是拦截了Math.floor方法,然后传入参数,将返回值赋值给s2,这样我们就能在需要读取这个返回值的时候调用s2。 59 | ```javascript 60 | //数组使用apply 61 | const arr = [1, 2, 3] 62 | function a() { 63 | return Array.concat.apply(null, arguments) 64 | } 65 | const s = a(arr) 66 | console.log(s) // [1, 2 ,3] 67 | ``` 68 | 69 | 其实Reflect的作用和我们下面要讲的Proxy是差不多的。 70 | 71 | ### 代理 Proxy 72 | 73 | Proxy这个词相信你已经听过无数遍了,我曾经写过一篇webpack使用代理来拦截指定域的API请求,转发到新的目标URL的文章 [webpack中使用proxy][2]。但是注意Proxy和proxy,大小写字母之间是不同的。本章讲的是大写字母开头的Proxy。 74 | 75 | ![clipboard.png](https://segmentfault.com/img/bVR5W5?w=710&h=182) 76 | 77 | #### 语法 78 | ```javascript 79 | let p = new Proxy(target, handler); 80 | ``` 81 | target:一个目标对象(可以是任何类型的对象,包括本机数组,函数,甚至另一个代理)用Proxy来包装。 82 | handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。 83 | 84 | #### 代理的使用 85 | 86 | **基础demo:**Proxy的demo有很多,我们只分析基础demo,主要看new Proxy({}, handler)的操作,指定目标obj对象,然后handler对象执行get()操作,get()返回值的判断是,如果name是target目标对象的属性,则返回target[name]的值,否则返回37,最后测试的时候,p.a是对象p的key,所以返回a的value,而p.b不存在,返回37。 87 | ```javascript 88 | const obj = { 89 | a: 10 90 | } 91 | let handler = { 92 | get: function(target, name){ 93 | console.log('test: ', target, name) 94 | // test: {"a":10} a 95 | // test: {"a":10} b 96 | return name in target ? target[name] : 37 97 | } 98 | } 99 | let p = new Proxy(obj, handler) 100 | console.log(p.a, p.b) // 10 37 101 | ``` 102 | 这个例子的作用是拦截目标对象obj,当执行obj的读写操作时,进入handler函数进行判断,如果读取的key不存在,则返回默认值。 103 | 104 | 我们使用一些http-proxy插件或者webpack的时候,有时候需要访问某个api时,跳转到指定的url,这种方式也能解决跨域访问。这种代理模式和Proxy的代理有异曲同工之妙。但是,别混为一体了。 105 | ```javascript 106 | module.exports = { 107 | devServer: { 108 | proxy: [ 109 | { 110 | context: "/api/*", //代理API 111 | target: 'https://www.hyy.com', //目标URL 112 | secure: false 113 | } 114 | ] 115 | } 116 | } 117 | ``` 118 | ### 总结 119 | 120 | 无论是反射还是代理,除了他们使用方法不同之外,他们所作的事情非常相似,都可以理解成拦截某个东西,然后执行某个函数操作,再返回函数操作的结果。 121 | 122 | 大部分前端在日常业务需求中,几乎很少使用到这2个API,实际使用场景还得在以后的开发中慢慢挖掘。 123 | 124 | 125 | [1]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect 126 | [2]: https://segmentfault.com/a/1190000008635891 127 | [3]: https://segmentfault.com/a/1190000010199272 128 | 129 | 上一节:[11.Promise与异步编程](https://github.com/hyy1115/ES6-learning/blob/master/doc/11%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%20Promise%E4%B8%8E%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B.md) 130 | 131 | 下一节:[13.用模块封装代码](https://github.com/hyy1115/ES6-learning/blob/master/doc/13%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E7%94%A8%E6%A8%A1%E5%9D%97%E5%B0%81%E8%A3%85%E4%BB%A3%E7%A0%81.md) -------------------------------------------------------------------------------- /doc/es6/set-map.md: -------------------------------------------------------------------------------- 1 | Map和Set都叫做集合,但是他们也有所不同。Set常被用来检查对象中是否存在某个键名,Map集合常被用来获取已存的信息。 2 | 3 | ### Set 4 | 5 | #### Set是有序列表,含有相互独立的非重复值。 6 | 7 | #### 创建Set 8 | 9 | 既然我们现在不知道Set长什么样,有什么价值,那么何不创建一个Set集合看看呢? 10 | 11 | **创建一个Set集合,你可以这样做:** 12 | 13 | ```javascript 14 | let set = new Set(); 15 | console.log(set); 16 | 17 | //在浏览器控制台的输出结果 18 | Set(0) {} 19 | size:(...) 20 | __proto__:Set 21 | [[Entries]]:Array(0) 22 | length:0 23 | ``` 24 | 25 | 看起来像个对象,那么现在我们在控制台打印一个对象,对比一下两者有什么不同。 26 | 27 | ```javascript 28 | let obj = new Object() 29 | console.log(obj) 30 | 31 | //在控制台输出对象 32 | Object {} 33 | __proto__: 34 | ``` 35 | 36 | 从输出结果看,Set和Object有明显的区别,反正他们就不是一个东西。 37 | 38 | **接着,我们看一下Set的原型有哪些:** 39 | 40 | ![clipboard.png](https://segmentfault.com/img/bVRqlN?w=858&h=652) 41 | 42 | 这里主要介绍几个基础原型的作用,想要了解全部请前往 [Set集合之家][1] 查看: 43 | 44 | Set.prototype.size 45 | 返回Set对象的值的个数。 46 | 47 | Set.prototype.add(value) 48 | 在Set对象尾部添加一个元素。返回该Set对象。 49 | 50 | Set.prototype.entries() 51 | 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。 52 | 53 | Set.prototype.forEach(callbackFn[, thisArg]) 54 | 按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。 55 | 56 | Set.prototype.has(value) 57 | 返回一个布尔值,表示该值在Set中存在与否。 58 | 59 | **在例子中使用这几个方法测试一下:** 60 | 61 | ```javascript 62 | let set = new Set(); 63 | set.add('haha'); 64 | set.add(Symbol('haha')); 65 | 66 | console.log(set.size); //2 67 | 68 | console.log(set); 69 | Set(2) {"haha", Symbol(haha)} 70 | size:(...) 71 | __proto__:Set 72 | [[Entries]]:Array(2) 73 | 0:"haha" 74 | 1:Symbol(haha) 75 | length:2 76 | 77 | console.log(set.has('haha')) // true 78 | ``` 79 | 80 | 到这里,你会发现Set像数组,又像一个对象,但又不完全是。 81 | 82 | #### 迭代Set 83 | 84 | Set既然提供了entries和forEach方法,那么他就是可迭代的。 85 | 86 | 但如果你使用for in来迭代Set,你不能这样做: 87 | 88 | ```javascript 89 | for(let i in sets) { 90 | console.log(i); //不存在 91 | } 92 | ``` 93 | for in迭代的是对象的key,而在Set中的元素没有key,**使用for of来遍历**: 94 | 95 | ```javascript 96 | for(let value of sets) { 97 | console.log(value); 98 | } 99 | //"haha" 100 | //Symbol(haha) 101 | 102 | //如果你需要key,则使用下面这种方法 103 | for(let [key, value] of sets.entries()) { 104 | console.log(value, key); 105 | } 106 | //"haha" "haha" 107 | //Symbol(haha) Symbol(haha) 108 | ``` 109 | 110 | 111 | **forEach操作Set:**Set本身没有key,而forEach方法中的key被设置成了元素本身。 112 | 113 | ```javascript 114 | sets.forEach((value, key) => { 115 | console.log(value, key); 116 | }); 117 | //"haha" "haha" 118 | //Symbol(haha) Symbol(haha) 119 | 120 | sets.forEach((value, key) => { 121 | console.log(Object.is(value, key)); 122 | }); 123 | //true true 124 | ``` 125 | 126 | #### Set和Array的转换 127 | 128 | Set和数组太像了,Set集合的特点是没有key,没有下标,只有size和原型以及一个可迭代的不重复元素的类数组。既然这样,我们就可以把一个Set集合转换成数组,也可以把数组转换成Set。 129 | 130 | ```javascript 131 | //数组转换成Set 132 | const arr = [1, 2, 2, '3', '3'] 133 | let set = new Set(arr); 134 | console.log(set) // Set(3) {1, 2, "3"} 135 | 136 | //Set转换成数组 137 | let set = new Set(); 138 | set.add(1); 139 | set.add('2'); 140 | console.log(Array.from(set)) // (2) [1, "2"] 141 | ``` 142 | 143 | js面试中,经常会考的一道数组去重题目,就可以使用Set集合的不可重复性来处理。经测试只能去重下面3种类型的数据。 144 | 145 | ```javascript 146 | const arr = [1, 1, 'haha', 'haha', null, null] 147 | let set = new Set(arr); 148 | console.log(Array.from(set)) // [1, 'haha', null] 149 | console.log([...set]) // [1, 'haha', null] 150 | ``` 151 | 152 | ### Weak Set集合 153 | 154 | Set集合本身是强引用,只要new Set()实例化的引用存在,就不释放内存,这样一刀切肯定不好啊,比如你定义了一个DOM元素的Set集合,然后在某个js中引用了该实例,但是当页面关闭或者跳转时,你希望该引用应立即释放内存,Set不听话,那好,你还可以使用 [Weak Set][2] 155 | 156 | **语法:** 157 | 158 | ```javascript 159 | new WeakSet([iterable]); 160 | ``` 161 | 162 | **和Set的区别:** 163 | 164 | 1、**WeakSet 对象中只能存放对象值**, 不能存放原始值, 而 Set 对象都可以. 165 | 166 | 2、WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, **WeakSet 对象是无法被枚举的**, 没有办法拿到它包含的所有元素. 167 | 168 | **使用:** 169 | 170 | ```javascript 171 | let set = new WeakSet(); 172 | const class_1 = {}, class_2 = {}; 173 | set.add(class_1); 174 | set.add(class_2); 175 | console.log(set) // WeakSet {Object {}, Object {}} 176 | console.log(set.has(class_1)) // true 177 | console.log(set.has(class_2)) // true 178 | ``` 179 | 180 | ### Map 181 | 182 | #### Map是存储许多键值对的有序列表,key和value支持所有数据类型。 183 | 184 | #### 创建Map 185 | 186 | 如果说Set像数组,那么Map更像对象。而对象中的key只支持字符串,Map更加强大,支持所有数据类型,不管是数字、字符串、Symbol等。 187 | 188 | ```javascript 189 | // 一个空Map集合 190 | let map = new Map() 191 | console.log(map) 192 | ``` 193 | 194 | ![clipboard.png](https://segmentfault.com/img/bVRqxA?w=548&h=188) 195 | 196 | **Map的所有原型方法:** 197 | ![clipboard.png](https://segmentfault.com/img/bVRqxE?w=874&h=654) 198 | 199 | 对比Set集合的原型,**Map集合的原型多了set()和get()方法**,注意set()和Set集合不是一个东西。Map没有add,使用set()添加key,value,在Set集合中,使用add()添加value,没有key。 200 | 201 | ```javascript 202 | let map = new Map(); 203 | map.set('name', 'haha'); 204 | map.set('id', 10); 205 | console.log(map) 206 | // 输出结果 207 | Map(2) {"name" => "haha", "id" => 10} 208 | size:(...) 209 | __proto__:Map 210 | [[Entries]]:Array(2) 211 | 0:{"name" => "haha"} 212 | 1:{"id" => 10} 213 | length:2 214 | 215 | console.log(map.get('id')) // 10 216 | console.log(map.get('name')) // "haha" 217 | ``` 218 | 219 | **使用对象做key** 220 | 221 | ```javascript 222 | let map = new Map(); 223 | const key = {}; 224 | map.set(key, '谁知道这是个什么玩意'); 225 | console.log(map.get(key)) // 谁知道这是个什么玩意 226 | ``` 227 | 228 | **Map同样可以使用forEach遍历key、value** 229 | 230 | ```javascript 231 | let map = new Map(); 232 | const key = {}; 233 | map.set(key, '这是个什么玩意'); 234 | map.set('name', 'haha'); 235 | map.set('id', 1); 236 | map.forEach((value, key) => { 237 | console.log(key, value) 238 | }) 239 | 240 | //Object {} "这是个什么玩意" 241 | //"name" "haha" 242 | //"id" 1 243 | ``` 244 | 245 | 其他Map的使用方法可以前往 [Map之家][3] 学习。 246 | 247 | ### Weak Map 248 | 249 | 有强Map,就有弱鸡Map。 250 | 251 | 和Set要解决的问题一样,希望不再引用Map的时候自动触发垃圾回收机制。那么,你就需要Weak Map。 252 | 253 | ```javascript 254 | let map = new WeakMap(); 255 | const key = document.querySelector('.header'); 256 | map.set(key, '这是个什么玩意'); 257 | 258 | map.get(key) // "这是个什么玩意" 259 | 260 | //移除该元素 261 | key.parentNode.removeChild(key); 262 | key = null; 263 | ``` 264 | 265 | 266 | ### 总结 267 | 268 | Set集合可以用来过滤数组中重复的元素,只能通过has方法检测指定的值是否存在,或者是通过forEach处理每个值。 269 | 270 | Weak Set集合存放对象的弱引用,当该对象的其他强引用被清除时,集合中的弱引用也会自动被垃圾回收机制回收,追踪成组的对象是该集合最好的使用方式。 271 | 272 | Map集合通过set()添加键值对,通过get()获取键值,各种方法的使用查看文章教程,你可以把它看成是比Object更加强大的对象。 273 | 274 | Weak Map集合只支持对象类型的key,所有key都是弱引用,当该对象的其他强引用被清除时,集合中的弱引用也会自动被垃圾回收机制回收,为那些实际使用与生命周期管理分离的对象添加额外信息是非常适合的使用方式。 275 | 276 | 记得刚开始学习JavaScript的时候,不知道各种数据类型有什么用,如果你现在刚学习Map和Set也是这种不知道能用来干什么的想法,那么,恭喜,他们已经开始走入你的编程生涯,慢慢的你就会熟悉他们。 277 | 278 | 279 | 280 | [1]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set 281 | [2]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakSet 282 | [3]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map 283 | [4]: https://segmentfault.com/a/1190000010199272 284 | 285 | 上一节:[6.Symbol和Symbol属性](https://github.com/hyy1115/ES6-learning/blob/master/doc/6%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Symbol%E5%92%8CSymbol%E5%B1%9E%E6%80%A7.md) 286 | 287 | 下一节:[8.迭代器(Iterator)和生成器(Generator)](https://github.com/hyy1115/ES6-learning/blob/master/doc/8%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E8%BF%AD%E4%BB%A3%E5%99%A8%EF%BC%88Iterator%EF%BC%89%E5%92%8C%E7%94%9F%E6%88%90%E5%99%A8%EF%BC%88Generator%EF%BC%89.md) -------------------------------------------------------------------------------- /doc/es6/str.md: -------------------------------------------------------------------------------- 1 | 相比较于第一章变量的声明,这一章的内容有了一点深度提升,但还不至于很难理解。本章主要讲2个知识点,**字符串**、**正则表达式** 2 | 3 | ### 字符串 4 | 字符串(String)是JavaScript6大原始数据类型。其他几个分别是Boolean、Null、Undefined、Number、Symbol(es6新增)。 5 | 6 | 字符串类型在前端开发者,是使用最频繁的类型之一,网站上可见的各种文案,几乎都是字符串类型的数据。我们经常需要使用的操作无非是这么几点:读取字符串、转换字符串、清空字符串、拼接字符串、截取字符串。 7 | 8 | 在ES5中,字符串类型已经有了非常丰富的应用能力,那么,在ES6中,ECMA的专家们对字符串做了什么更新呢? 9 | 10 | 当Unicode引入扩展字符集之后,16位的字符已经不足以满足字符串的发展,所以才在ES6中更新了Unicode的支持。 11 | 12 | 我们看看ES6字符串新增的方法 13 | 14 | **UTF-16码位:**ES6强制使用UTF-16字符串编码。关于UTF-16的解释请自行百度了解。 15 | 16 | **codePointAt():** 17 | 该方法支持UTF-16,接受编码单元的位置而非字符串位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。 18 | 19 | **String.fromCodePoiont():**作用与codePointAt相反,检索字符串中某个字符的码位,也可以根据指定的码位生成一个字符。 20 | 21 | **normalize()**:提供Unicode的标准形式,接受一个可选的字符串参数,指明应用某种Unicode标准形式。 22 | 23 | 24 | ### 正则表达式 25 | 26 | **正则表达式u修饰符:** 27 | 当给正则表达式添加u字符时,它就从编码单元操作模式切换为字符模式。 28 | 29 | ### 其他新增的方法 30 | 31 | 上面提到的字符串和正则的新增方法只有在国际化的时候才用的到,我想,国内的很多网站还是不需要考虑国际化的问题,看不懂就先丢掉。下面讲到的新增的方法是实际开发中需求比较频繁的方法。 32 | 33 | **字符串中的子串识别**: 34 | 35 | 以前我们经常使用indexOf()来检测字符串中是否包含另外一段字符串。 36 | 37 | ```javascript 38 | let t = 'abcdefg' 39 | if(t.indexOf('cde') > -1) { 40 | console.log(2) 41 | } 42 | //输出2,因为t字符串中包含cde字符串。 43 | ``` 44 | 45 | 在ES6中,新增了3个新方法。每个方法都接收2个参数,需要检测的子字符串,以及开始匹配的索引位置。 46 | 47 | **includes(str, index):**如果在字符串中检测到指定文本,返回true,否则false。 48 | 49 | ```javascript 50 | let t = 'abcdefg' 51 | if(t.includes('cde')) { 52 | console.log(2) 53 | } 54 | //true 55 | ``` 56 | 57 | **startsWith(str, index)**:如果在字符串起始部分检测到指定文本,返回true,否则返回false。 58 | 59 | ```javascript 60 | let t = 'abcdefg' 61 | if(t.startsWith('ab')) { 62 | console.log(2) 63 | } 64 | //true 65 | ``` 66 | 67 | **endsWith(str, index)**:如果在字符串的结束部分检测到指定文本,返回true,否则返回false。 68 | 69 | ```javascript 70 | let t = 'abcdefg' 71 | if(t.endsWith('fg')) { 72 | console.log(2) 73 | } 74 | //true 75 | ``` 76 | 77 | **如果你只是需要匹配字符串中是否包含某子字符串,那么推荐使用新增的方法,如果需要找到匹配字符串的位置,使用indexOf()。** 78 | 79 | **repeat(number)** 80 | 81 | 这个方法挺有意思的,接收一个Number类型的数据,返回一个重复N次的新字符串。即使这个字符串是空字符,也你能返回N个空字符的新字符串。 82 | 83 | ```javascript 84 | console.log('ba'.repeat(3)) //bababa 85 | ``` 86 | 87 | ### 正则表达式的其他更新 88 | 89 | 正则表达式y修饰符、正则表达式的复制、flags属性...... 90 | 91 | 由于这一块知识没用过,就不打算去研究实际用途。 92 | 93 | ### 模板字面量 94 | 95 | 以前,我们用单引号或双引号表示字符串。 96 | ```javascript 97 | let a = '123' //单引号 98 | let b = "123" //双引号 99 | ``` 100 | 现在,使用模板字面量反撇号``。在实际开发中,这是经常都要用到的方法。 101 | ```javascript 102 | let c = `123` //反撇号 103 | ``` 104 | 在字符串中使用反撇号,只需要加上转义符。 105 | ```javascript 106 | let d = `12\`3` //字符串内插入反撇号的方式。 107 | ``` 108 | 109 | **在多行字符串的使用价值:** 110 | 111 | 模板字面量为解决多行字符串的一系列问题提供了一个非常好的机制。 112 | 113 | 如果不使用模板字面量,实现多行字符串,你可能会使用换行符。 114 | 115 | ```javascript 116 | let a = '123\n456' 117 | console.log(a) 118 | // 123 119 | // 456 120 | ``` 121 | 122 | 使用模板字面量,就可以非常简单的实现需求。 123 | 124 | ```javascript 125 | let a = `123 126 | 456 127 | ` 128 | console.log(a) 129 | // 123 130 | // 456 131 | ``` 132 | 133 | **在模板字面量插入变量的方法。** 134 | 135 | 我们不再需要使用 +(加号)来向字符串插入变量,而是使用${params}直接插入你需要添加到字符串的位置。 136 | 137 | ```javascript 138 | let t = 'haha' 139 | let a = `123${t}456` 140 | console.log(a) //123haha456 141 | ``` 142 | 143 | 这种方式也叫作字符串占位符。占位符支持互相嵌套模板字面量,强大吧。有了它,我们终于可以抛弃 + 拼接字符串的恶心做法了。 144 | 145 | **模板字面量的终极用法** 146 | tag是一个方法,方法名你可以任意命名,这种写法被称作标签模板。 147 | 148 | ```javascript 149 | function tag(literals, ...substitutions) { 150 | //literals是数组,第一个位置是"",第二个位置是占位符之间的字符串,在本例中是haha 151 | //substitutions是字符串中的模板字面量,可能多个 152 | 153 | //函数最终返回字符串 154 | } 155 | let a = 4 156 | let t = tag`${a} haha` 157 | console.log(t) //4 haha 158 | ``` 159 | 160 | ### 总结 161 | 162 | 本章讲到了ES6中新增的Unicode方法,但是对于不需要做国际化处理的同学,这个功能就不会用到,需要**关注的新增的字符串匹配的3个方法,以及模板字面量的使用。**这2个知识点非常非常常用! 163 | 164 | [1]: https://segmentfault.com/a/1190000010199272 165 | 166 | 上一节:[1.块级作用域绑定](https://github.com/hyy1115/ES6-learning/blob/master/doc/1%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F%E7%BB%91%E5%AE%9A.md) 167 | 168 | 下一节:[3.函数](https://github.com/hyy1115/ES6-learning/blob/master/doc/3%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%87%BD%E6%95%B0.md) -------------------------------------------------------------------------------- /doc/es6/symbol.md: -------------------------------------------------------------------------------- 1 | 还记得对象Object吗? 2 | 3 | ```javascript 4 | let obj = { 5 | a: 1 6 | } 7 | ``` 8 | 对象的格式: 9 | 10 | ```javascript 11 | Object { 12 | key: value 13 | } 14 | ``` 15 | 在ES5的时代,对象的key只能是字符串String类型。有人就想搞事,把key改成其他数据类型,这不是瞎折腾吗?ES组织的大神们为了对付这类搞事的人,就指定了一个新的数据类型:Symbol。 16 | 17 | ### 原始数据类型 18 | 19 | 学习Symbol之前,让我们回忆一下你曾经用过的原始数据类型,只有5个,别搞错了。 20 | 21 | **null、undefined** 22 | 23 | 是不是面试的时候有人问过你这两者的区别?问这种问题的人很无聊,你要是和他当同事,真是受罪。 24 | 25 | **Number 数字类型** 26 | ```javascript 27 | const a = 10 28 | typeof a // number 29 | ``` 30 | 31 | **String 字符串** 32 | ```javascript 33 | const a = 'haha' 34 | typeof a // string 35 | ``` 36 | 37 | **boolean 布尔型** 38 | 39 | ```javascript 40 | const a = true, b = false 41 | ``` 42 | 43 | ### Symbol 44 | 45 | Symbol到底长啥样?又该怎么用呢?我们一起来探索一下。 46 | 47 | **在MDN文档中,关于Symbol的说明是这样的:** 48 | 49 | Symbol 是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用。Symbol 对象是一个 symbol primitive data type 的隐式对象包装器。 50 | 51 | symbol 数据类型是一个原始数据类型。 52 | 53 | **Symbol的语法格式:** 54 | 55 | Symbol([description]) //description是可选的 56 | 57 | **创建一个Symbol:** 58 | 59 | 看了Symbol的描述,不知道是什么鬼?长得像个函数。 60 | 61 | 我们开始按照语法创建一个Symbol来研究一下 62 | 63 | ```javascript 64 | const name = Symbol(); 65 | const name1 = Symbol('sym1'); 66 | console.log(name, name1) // Symbol() Symbol(sym1) 67 | ``` 68 | 69 | Symbol不能使用new 70 | 71 | ```javascript 72 | const name = new Symbol(); //不可以这样做。 73 | //Symbol is not a constructor 74 | ``` 75 | 76 | **使用Symbol:** 77 | 78 | 使用Number的时候,我们可以这样写: 79 | 80 | ```javascript 81 | const b = Number(10) // 10 82 | //简写 83 | const b = 10 84 | ``` 85 | 86 | 同理,使用Symbol,我们可以这样: 87 | 88 | ```javascript 89 | const name1 = Symbol('sym1'); // Symbol(sym1) 90 | ``` 91 | 92 | 在所有使用可计算属性名的地方,都能使用Symbol类型。比如在对象中的key。 93 | 94 | ```javascript 95 | const name = Symbol('name'); 96 | const obj = { 97 | [name]: "haha" 98 | } 99 | console.log(obj[name]) // haha 100 | ``` 101 | 102 | 你还可以使用Object.defineProperty()和Object.defineProperties()方法。这2个方法是对象的方法,但是作为Symbol类型key,也不影响使用。 103 | 104 | ```javascript 105 | // 设置对象属性只读。 106 | Object.defineProperty(obj, name, {writable: false}) 107 | ``` 108 | 109 | 这2个方法非常有用,在react源码中,使用了大量的只读属性的对象。以下是从react源码截取的一段代码,设置了props对象只读。但是react仍旧使用字符串作为key,并不用Symbol。 110 | 111 | ```javascript 112 | Object.defineProperty(props, 'key', { 113 | get: warnAboutAccessingKey, 114 | configurable: true 115 | }); 116 | ``` 117 | 118 | #### Symbol全局共享 119 | 120 | Symbol有点特殊,在js文件中定义的Symbol,并不能在其他文件直接共享。 121 | 122 | ES6提供了一个注册机制,当你注册Symbol之后,就能在全局共享注册表里面的Symbol。Symbol的注册表和对象表很像,都是key、value结构,只不过这个value是Symbol值。 123 | (key, Symbol) 124 | 语法: 125 | 126 | ```javascript 127 | Symbol.for() //只有一个参数 128 | ``` 129 | 130 | 131 | 还有一个方法是获取注册表的Symbol。 132 | 133 | 语法: 134 | 135 | ```javascript 136 | Symbol.keyFor() //只有一个参数,返回的是key 137 | ``` 138 | 139 | 从注册表获取全局共享的Symbol 140 | ```javascript 141 | let name = Symbol.for('name'); 142 | let name1 = Symbol.for('name1'); 143 | let name2 = Symbol.for('name2'); 144 | 145 | console.log(Symbol.keyFor(name)) // name 146 | console.log(Symbol.keyFor(name1)) // name1 147 | console.log(Symbol.keyFor(name2)) // name2 148 | ``` 149 | 150 | 注意:如果要防止Symbol命名重复问题,可以加上前缀。如:hyy.name 151 | 152 | 153 | #### Symbol与类型强制转换 154 | 155 | JavaScript中的类型可以自动转换。比如Number转换成字符串。 156 | 157 | ```javascript 158 | let a = 1; 159 | console.log(typeof a); // number 160 | console.log(a + ' haha') // '1haha' 161 | ``` 162 | 163 | 但是注意了,Symbol不支持这种转换。Symbol就是这么拽啊! 164 | 165 | ```javascript 166 | let a = Symbol('a'); 167 | console.log(typeof a); 168 | console.log(a + ' haha') // Cannot convert a Symbol value to a string 169 | ``` 170 | 171 | #### Symbol检索 172 | 173 | 在对象中获取字符串的key时,可以使用Object.keys()或Object.getOwnPropertyNames()方法获取key,但是使用Symbol做key是,你就只能使用ES6新增的方法来获取了。 174 | 175 | ```javascript 176 | let a = Symbol('a'); 177 | let b = Symbol('b'); 178 | 179 | let obj = { 180 | [a]: "123", 181 | [b]: 45 182 | } 183 | 184 | const symbolsKey = Object.getOwnPropertySymbols(obj); 185 | 186 | for(let value of symbolsKey) { 187 | console.log(obj[value]) 188 | } 189 | //"123" 190 | //45 191 | ``` 192 | 193 | ### 总结 194 | 195 | Symbol还提供了多个方法给开发者使用,我们不再一一研究每个方法的用途,你想要了解全面可以查看 [Symbol MDN文档][1] 196 | 197 | 我们只需要知道Symbol如何定义,如何在全局共享,如果在对象中替代key即可应付基本的开发需求了。 198 | 199 | 最后再回顾一下Symbol是什么:Symbol是JavaScript的原始数据类型,一个全新的数据类型,和对象、数字、字符串等完全不一样,它必须通过Symbol()创建。它的使用看上面的详细介绍。 200 | 201 | 202 | 203 | [1]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol 204 | [2]: https://segmentfault.com/a/1190000010199272 205 | 206 | 上一节:[5.解构:使数据访问更便捷](https://github.com/hyy1115/ES6-learning/blob/master/doc/5%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E8%A7%A3%E6%9E%84%EF%BC%9A%E4%BD%BF%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E6%9B%B4%E4%BE%BF%E6%8D%B7.md) 207 | 208 | 下一节:[7.Set集合与Map集合](https://github.com/hyy1115/ES6-learning/blob/master/doc/7%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Set%E9%9B%86%E5%90%88%E4%B8%8EMap%E9%9B%86%E5%90%88.md) -------------------------------------------------------------------------------- /doc/es6/var.md: -------------------------------------------------------------------------------- 1 | **本章涉及3个知识点,var、let、const,现在让我们了解3个关键字的特性和使用方法。** 2 | 3 | ### var 4 | JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当前作用域的最顶部,这种行为叫做**变量提升(Hoisting)** 5 | 6 | 也就是说,如果在函数内部声明的变量,都会被提升到该函数开头,而在全局声明的变量,就会提升到全局作用域的顶部。 7 | 8 | ```javascript 9 | function test() { 10 | console.log('1: ', a) //undefined 11 | if (false) { 12 | var a = 1 13 | } 14 | console.log('3: ', a) //undefined 15 | } 16 | 17 | test() 18 | ``` 19 | 20 | 实际执行时,上面的代码中的变量a会提升到函数顶部声明,即使if语句的条件是false,也一样不影响a变量提升。 21 | 22 | ```javascript 23 | function test() { 24 | var a 25 | //a声明没有赋值 26 | console.log('1: ', a) //undefined 27 | if (false) { 28 | a = 1 29 | } 30 | //a声明没有赋值 31 | console.log('3: ', a) //undefined 32 | } 33 | ``` 34 | 35 | 在函数嵌套函数的场景下,变量只会提升到最近的一个函数顶部,而不会提升到外部函数。 36 | 37 | ```javascript 38 | //b提升到函数a顶部,但不会提升到函数test。 39 | function test() { 40 | function a() { 41 | if (false) { 42 | var b = 2 43 | } 44 | } 45 | console.log('b: ', b) 46 | } 47 | 48 | test() //b is not defined 49 | ``` 50 | 51 | 如果a没有声明,那么就会报错,**没有声明和声明后没有赋值是不一样的**,这点一定要区分开,有助于我们找bug。 52 | 53 | ```javascript 54 | //a没有声明的情况 55 | a is not defined 56 | ``` 57 | 58 | ### let 59 | let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。 60 | 61 | 一个非常简单的例子: 62 | ```javascript 63 | 64 | function test() { 65 | if(true) { 66 | console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象 67 | let a = 1 68 | } 69 | } 70 | test() // a is not defined 71 | 72 | function test() { 73 | if(true) { 74 | let a = 1 75 | } 76 | console.log(a) 77 | } 78 | test() // a is not defined 79 | ``` 80 | 81 | 唯一正确的使用方法:**先声明,再访问。** 82 | 83 | ```javascript 84 | function test() { 85 | if(true) { 86 | let a = 1 87 | console.log(a) 88 | } 89 | } 90 | test() // 1 91 | ``` 92 | 93 | ### const 94 | 声明常量,一旦声明,不可更改,而且常量必须初始化赋值。 95 | 96 | ```javascript 97 | const type = "ACTION" 98 | ``` 99 | 100 | 我们试试重新声明type,看看会报什么错: 101 | ​ 102 | ```javascript 103 | const type = "ACTION" 104 | type = 1 105 | console.log(type) //"type" is read-only 106 | 107 | const type = "ACTION" 108 | let type = 1 109 | console.log(type) //Duplicate declaration "type" 110 | ``` 111 | 112 | const虽然是常量,不允许修改默认赋值,但如果定义的是对象Object,那么可以修改对象内部的属性值包括新增删除键值对也是可以的。 113 | 114 | ```javascript 115 | const type = { 116 | a: 1 117 | } 118 | type.a = 2 //没有直接修改type的值,而是修改type.a的属性值,这是允许的。 119 | console.log(type) // {a: 2} 120 | 121 |    type.b = 3 //拓展Object也是没有问题的 122 |    console.log(type) // {a: 2 , b: 3} 123 | 124 |    delete type.b=3 //删除整个键值对也OK的 125 | console.log(type) // {a: 2} 126 | 127 |    //如果重新定义数据结构~常量的内存地址值发生改变,这个是不可行的。 128 |    type={}; //Assignment to constant variable. 129 |    type=[]; //Assignment to constant variable. 130 | ``` 131 | 132 | ### const和let的异同点 133 | 134 | **相同点:**const和let都是在当前块内有效,执行到块外会被销毁,也不存在变量提升(TDZ),不能重复声明。 135 | 136 | **不同点:**const不能再赋值,let声明的变量可以重复赋值。 137 | 138 | ### 临时死区(TDZ) 139 | 上面我们也提到了TDZ的场景,那么,有什么用呢?答案就是没什么用。 140 | 141 | 临时死区的意思是在当前作用域的块内,在声明变量前的区域叫做临时死区。 142 | ​ 143 | ```javascript 144 | if (true) { 145 | //这块区域是TDZ 146 | let a = 1 147 | } 148 | ``` 149 | 150 | ### 块级作用域的使用场景 151 | 除了上面提到的常用声明方式,我们还可以在循环中使用,最出名的一道面试题:循环中定时器闭包的考题 152 | 153 | 在for循环中使用var声明的循环变量,会跳出循环体污染当前的函数。 154 | 155 | ```javascript 156 | for(var i = 0; i < 5; i++) { 157 | setTimeout(() => { 158 | console.log(i) //5, 5, 5, 5, 5 159 | }, 0) 160 | } 161 | console.log(i) //5 i跳出循环体污染外部函数 162 | 163 | //将var改成let之后 164 | for(let i = 0; i < 5; i++) { 165 | setTimeout(() => { 166 | console.log(i) // 0,1,2,3,4 167 | }, 0) 168 | } 169 | console.log(i)//i is not defined i无法污染外部函数 170 | ``` 171 | 172 | 关于这个使用场景的具体分析可以查看我写的另外一篇文章:[JavaScript同步、异步、回调执行顺序之经典闭包setTimeout面试题分析][1] 173 | 174 | ### 在全局作用域声明 175 | 如果在全局作用域使用let或者const声明,当声明的变量本身就是全局属性,比如closed。只会覆盖该全局变量,而不会替换它。 176 | 177 | ```javascript 178 | window.closed = false 179 | let closed = true 180 | 181 | closed // true 182 | window.closed // false 183 | ``` 184 | 185 | ### 最佳实践 186 | 在实际开发中,我们选择使用var、let还是const,取决于我们的变量是不是需要更新,通常我们希望变量保证不被恶意修改,而使用大量的const,在react中,props传递的对象是不可更改的,所以使用const声明,声明一个对象的时候,也推荐使用const,当你需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。 187 | 188 | 189 | [1]: https://segmentfault.com/a/1190000008922457 190 | [2]: https://segmentfault.com/a/1190000010199272 191 | 192 | 下一节:[2.字符串和正则表达式](https://github.com/hyy1115/ES6-learning/blob/master/doc/2%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%92%8C%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md) 193 | -------------------------------------------------------------------------------- /doc/es6/扩展.md: -------------------------------------------------------------------------------- 1 | 变量功能被加强了、函数功能被加强了,那么作为JavaScript中最普遍的对象,不加强对得起观众吗? 2 | 3 | ### 对象类别 4 | 5 | 在ES6中,对象分为下面几种叫法。(不需要知道概念) 6 | 7 | 1、普通对象 8 | 9 | 2、特异对象 10 | 11 | 3、标准对象 12 | 13 | 4、内建对象 14 | 15 | ### 对象字面量语法拓展 16 | 17 | 随便打开一个js文件,对象都无处不在,看一个简单的对象。 18 | 19 | ```javascript 20 | { 21 | a: 2 22 | } 23 | ``` 24 | 25 | **ES6针对对象的语法扩展了一下功能** 26 | 27 | 1、属性初始值简写 28 | ```javascript 29 | //ES5 30 | function a(id) { 31 | return { 32 | id: id 33 | }; 34 | }; 35 | 36 | //ES6 37 | const a = (id) => ({ 38 | id 39 | }) 40 | ``` 41 | 42 | 2、对象方法简写 43 | 44 | ```javascript 45 | // ES5 46 | const obj = { 47 | id: 1, 48 | printId: function() { 49 | console.log(this.id) 50 | } 51 | } 52 | 53 | // ES6 54 | const obj = { 55 | id: 1, 56 | printId() { 57 | console.log(this.id) 58 | } 59 | } 60 | ``` 61 | 62 | 3、属性名可计算 63 | 64 | 属性名可以传入变量或者常量,而不只是一个固定的字符串。 65 | 66 | ```javascript 67 | const id = 5 68 | const obj = { 69 | [`my-${id}`]: id 70 | } 71 | console.log(obj['my-5']) // 5 72 | ``` 73 | 74 | ### ES6对象新增方法 75 | 76 | 在Object原始对象上新增方法,原则上来说不可取,但是为了解决全世界各地提交的issue,在ES6中的全局Object对象上新增了一些方法。 77 | 78 | **1、Object.is()** 79 | 80 | 用来解决JavaScript中特殊类型 == 或者 === 异常的情况。 81 | 82 | 下面是一些异常情况 83 | 84 | ```javascript 85 | //实际出现了异常(错误输出) 86 | console.log(NaN === NaN) // false 87 | console.log(+0 === -0) // true 88 | console.log(5 == "5") //true 89 | 90 | //我们期望的目标输出(正确输出) 91 | console.log(NaN === NaN) // true 92 | console.log(+0 === -0) // false 93 | console.log(5 == "5") //false 94 | ``` 95 | 96 | 为了解决历遗留问题,**新增了Object.is()来处理2个值的比较。** 97 | 98 | ```javascript 99 | console.log(Object.is(NaN, NaN)) // true 100 | console.log(Object.is(+0, -0)) // false 101 | console.log(Object.is(5, "5")) //false 102 | ``` 103 | 104 | **2、Object.assign()** 105 | 106 | 也许你已经见过或者使用过这个方法了,那这个新增的方法解决了什么问题呢? 107 | 108 | 答:混合(Mixin)。 109 | 110 | mixin是一个方法,实现了拷贝一个对象给另外一个对象,返回一个新的对象。 111 | 112 | 下面是一个mixin方法的实现,这个方法实现的是浅拷贝。将b对象的属性拷贝到了a对象,合并成一个新的对象。 113 | 114 | ```javascript 115 | //mixin不只有这一种实现方法。 116 | function mixin(receiver, supplier) { 117 | Object.keys(supplier).forEach((key) => { 118 | receiver[key] = supplier[key] 119 | }) 120 | return receiver 121 | } 122 | 123 | let a = {name: 'sb'}; 124 | let b = { 125 | c: { 126 | d: 5 127 | } 128 | } 129 | console.log(mixin(a, b)) // {"name":"sb","c":{"d":5}} 130 | ``` 131 | 132 | 写这样一个mixin方法是不是很烦,而且每个项目都得引入这个方法,现在,ES6给我们提供了一个现成的方法Object.assign()来做mixin的事情。 133 | 134 | 假设要实现上面的mixin方法,你只需要给Object.assign()传入参数即可。 135 | 136 | ```javascript 137 | console.log(Object.assign(a, b))// {"name":"sb","c":{"d":5}} 138 | ``` 139 | 140 | **使用Object.assign(),你就可以不是有继承就能获得另一个对象的所有属性,快捷好用。** 141 | Object.assign 方法只复制源对象中可枚举的属性和对象自身的属性。 142 | **看一个实现Component的例子。** 143 | 144 | ```javascript 145 | //声明一个对象Component 146 | let Component = {} 147 | //给对象添加原型方法 148 | Component.prototype = { 149 | componentWillMount() {}, 150 | componentDidMount() {}, 151 | render() {console.log('render')} 152 | } 153 | //定义一个新的对象 154 | let MyComponent = {} 155 | //拷贝Component的方法和属性。 156 | Object.assign(MyComponent, Component.prototype) 157 | 158 | console.log(MyComponent.render()) // render 159 | ``` 160 | 161 | **在react的reducer中,每次传入新的参数返回新的state,你都可能用到Object.assign()方法。** 162 | 163 | ### 重复的对象字面量属性 164 | 165 | ES5的严格模式下,如果你的对象中出现了key相同的情况,那么就会抛出错误。而在ES6的严格模式下,不会报错,后面的key会覆盖掉前面相同的key。 166 | 167 | ```javascript 168 | const state = { 169 | id: 1, 170 | id: 2 171 | } 172 | console.log(state.id) // 2 173 | ``` 174 | 175 | ### 自有属性枚举顺序 176 | 177 | 这个概念看起来比较模糊,如果你看了下面的例子,你可能就会明白在说什么了。 178 | 179 | ```javascript 180 | const state = { 181 | id: 1, 182 | 5: 5, 183 | name: "eryue", 184 | 3: 3 185 | } 186 | 187 | Object.getOwnPropertyNames(state) 188 | //["3","5","id","name"] 枚举key 189 | 190 | Object.assign(state, null) 191 | //{"3":3,"5":5,"id":1,"name":"eryue"} 192 | ``` 193 | 194 | 上面的例子的输出结果都有个规律,就是数字提前,按顺序排序,接着是字母排序。而这种行为也是ES6新增的标准。你还可以自己测试一下其他方法是不是也支持枚举自动排序。比如Object.keys(), for in 等。 195 | 196 | ### 增强对象原型 197 | 198 | 如果你想定义一个对象,你会想到很多方法。 199 | 200 | ```javascript 201 | let a = {} 202 | 203 | let b = Object.create(a) 204 | 205 | function C() {} 206 | 207 | class D {} 208 | ``` 209 | 210 | 那么,ES6是如何在这么强大的对象上面继续增强功能呢? 211 | 212 | 1、允许改变对象原型 213 | 214 | 改变对象原型,是指在对象实例化之后,可以改变对象原型。我们使用 [Object.setPrototypeOf()][1] 来改变实例化后的对象原型。 215 | 216 | ```javascript 217 | let a = { 218 | name() { 219 | return 'eryue' 220 | } 221 | } 222 | let b = Object.create(a) 223 | console.log(b.name()) // eryue 224 | 225 | //使用setPrototypeOf改变b的原型 226 | let c = { 227 | name() { 228 | return "sb" 229 | } 230 | } 231 | Object.setPrototypeOf(b, c) 232 | console.log(b.name()) //sb 233 | ``` 234 | 235 | 2、简化原型访问的super引用 236 | 237 | 这一个知识你可以看书籍原文,我目前想不到实际业务代码来分析。 238 | 239 | ### 方法的定义 240 | 241 | ES6明确了方法的定义。 242 | 243 | ```javascript 244 | let a = { 245 | //方法 246 | name() { 247 | return 'eryue' 248 | } 249 | } 250 | //函数 251 | function name() {} 252 | ``` 253 | 254 | 估计习惯了函数和方法切换的我们,还是不用太在意这些具体的叫法。 255 | 256 | ### 总结 257 | 258 | 本章讲解了对象字面量语法拓展,ES6新增方法,允许重复的对象字面量属性,自有枚举属性排序,增强对象原型,明确了方法的定义。 259 | 260 | 我们平时开发中比较常用的是前面4种新增的功能,尤其是Object.assign()的使用。但是,就算把全部新增的功能记住也不是难事。所以,全都记住吧! 261 | 262 | 263 | [1]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf 264 | [2]: https://segmentfault.com/a/1190000010199272 265 | 266 | 上一节:[3.函数](https://github.com/hyy1115/ES6-learning/blob/master/doc/3%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%87%BD%E6%95%B0.md) 267 | 268 | 下一节:[5.解构:使数据访问更便捷](https://github.com/hyy1115/ES6-learning/blob/master/doc/5%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E8%A7%A3%E6%9E%84%EF%BC%9A%E4%BD%BF%E6%95%B0%E6%8D%AE%E8%AE%BF%E9%97%AE%E6%9B%B4%E4%BE%BF%E6%8D%B7.md) -------------------------------------------------------------------------------- /doc/es6/解构.md: -------------------------------------------------------------------------------- 1 | 解构,一种黑魔法 2 | 3 | **解构是从对象中提取出更小元素的过程。赋值是对解构出来的元素进行重新赋值。** 4 | 5 | **下面的代码你可能无法在浏览器上实时测试,推荐在babel官网在线测试代码:[在线测试ES6代码网址][1]** 6 | 7 | ### 解构的分类 8 | 9 | 1、对象解构 10 | 11 | 2、数组解构 12 | 13 | 3、混合解构 14 | 15 | 4、解构参数 16 | 17 | ### 对象解构 18 | 19 | **对象解构简单的例子** 20 | ```javascript 21 | let obj = { 22 | a: 1, 23 | b: [1, 2] 24 | } 25 | // 对象解构 26 | const { a, b } = obj 27 | console.log(a, b) //1 [1, 2] 28 | ``` 29 | 30 | **在函数中使用解构赋值** 31 | 32 | 解构是将对象或者数组的元素一个个提取出来,而赋值是给元素赋值,解构赋值的作用就是给对象或者数组的元素赋值。 33 | 34 | 在调用test()函数的时候,我们给参数设置了默认值3,如果不重新赋值,则打印出3,3,但是进行解构赋值后,将props对象的参数解构赋值给a和b,所以打印结果是{a: 1, b: 2} 35 | ```javascript 36 | let props = { 37 | a: 1, 38 | b: 2 39 | } 40 | function test(value) { 41 | console.log(value) 42 | } 43 | test({a=3, b=3} = props) // {a: 1, b: 2} 44 | ``` 45 | 46 | 下面这个例子定义了a = 3,b = 3两个变量,现在我们想修改这2个变量的值,采用解构赋值的方式可以这样做:定义一个props对象,该对象包含2个属性a和b,然后进行解构赋值,这时就能更新变量a和b的value。 47 | ```javascript 48 | let props = { 49 | a: 1, 50 | b: 2 51 | }, 52 | a = 3, 53 | b = 3; 54 | //解构赋值 55 | ({ a, b } = props) 56 | console.log(a, b) // 1, 2 57 | ``` 58 | 59 | **在react的父子组件传递参数过程中,也使用到了解构赋值。**[react demo在线测试][2] 60 | 61 | ```jsx harmony 62 | class Parent extends React.Component { 63 | render() { 64 | const {a = 3, b = 3} = this.props 65 | return

{a}-{b}

66 | } 67 | } 68 | 69 | ReactDOM.render( 70 | , 71 | document.getElementById('root') 72 | ); 73 | //在浏览器渲染 1-2,默认值是 3-3,但是因为传递了新的props进来,执行了解构赋值之后a和b更新了。 74 | 75 | ``` 76 | **嵌套对象解构** 77 | 78 | 当对象层次较深时,你也可以解构出来。 79 | 80 | ```javascript 81 | let obj = { 82 | a: { 83 | b: { 84 | c: 5 85 | } 86 | } 87 | } 88 | const {a: {b}} = obj 89 | console.log(b.c) // 5 90 | ``` 91 | 92 | ### 数组解构 93 | 94 | 数组解构比对象解构简单,因为数组只有数组字面量,不需要像对象一个使用key属性。 95 | 96 | **数组解构** 97 | 你可以选择性的解构元素,不需要解构的元素就使用逗号代替。 98 | 99 | ```jsx harmony 100 | let arr = [1, 2, 3] 101 | 102 | //解构前2个元素 103 | const [a, b] = arr 104 | console.log(a,b) //1 2 105 | 106 | //解构中间的元素 107 | const [, b,] = arr 108 | console.log(b) // 2 109 | ``` 110 | 111 | **解构赋值** 112 | 如果你没有看明白上面说到的对象解构赋值的含义,那么看完下面的数组解构赋值,或许你会有比较清晰的理解。 113 | 114 | 这个例子中,正常情况下打印a的值是haha,但是将数组arr的第一个元素解构赋值给a,a的值就变成了1。 115 | 116 | ```javascript 117 | //初始化一个变量a 118 | let a = "haha"; 119 | //定义一个数组 120 | let arr = [1, 2, 3]; 121 | //解构赋值a,将arr数组的第一个元素解构赋值给a, 122 | [a] = arr; 123 | console.log(a); // 1 124 | ``` 125 | 126 | 使用解构赋值,还可以调换2个变量的值。 127 | 128 | ```jsx harmony 129 | let a = 1, b = 2; 130 | [a, b] = [b, a]; 131 | console.log(a, b); // 2 1 132 | ``` 133 | 134 | **嵌套数组解构** 135 | 136 | ```javascript 137 | let arr = [1, [2, 3], 4]; 138 | let [a, [,b]] = arr; 139 | console.log(a, b) // 1 3 140 | 141 | //实际解构过程,左边的变量和右边的数组元素一一对应下标。 142 | var a = arr[0], 143 | _arr$ = arr[1], 144 | b = _arr$[1]; 145 | ``` 146 | 147 | **不定元素解构** 148 | 三个点的解构赋值必须放在所有解构元素的最末尾,否则报错。 149 | 150 | ```javascript 151 | let arr = [1, 2, 3, 4]; 152 | let [...a] = arr; 153 | console.log(a) //[1,2,3,4] 这种做法就是克隆arr数组。 154 | ``` 155 | 156 | ### 混合解构 157 | 混合解构指的是对象和数组混合起来,执行解构操作,没什么难度。 158 | 159 | ```javascript 160 | let obj = { 161 | a: { 162 | id: 1 163 | }, 164 | b: [2, 3] 165 | } 166 | 167 | const { 168 | a: {id}, 169 | b:[...arr] 170 | } = obj; 171 | console.log(id, arr) //id = 1, arr = [2, 3] 172 | ``` 173 | 174 | ### 解构参数 175 | 当给函数传递参数时,我们可以对每个参数进行解构,我给option的参数设置了默认值,这样可以防止没有给option传参导致的报错情况。 176 | 177 | ```javascript 178 | function Ajax(url, options) { 179 | const {timeout = 0, jsonp = true} = options 180 | console.log(url, timeout, jsonp) 181 | }; 182 | Ajax('baidu.com', { 183 | timeout: 1000, 184 | jsonp: false 185 | }) // "baidu.com" 1000 false 186 | ``` 187 | 188 | ### 总结 189 | 190 | 本章讲解了对象解构赋值和数组解构赋值,以及对象和数组混合情况下的解构赋值操作,最后一个知识点是解构函数的参数。每一个都是重点,特别是最后一个,解构参数恐怕你经常在用了,只是通常你没发现。 191 | 192 | 193 | [1]: https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=true&presets=env,es2015,es2015-loose,es2016,es2017,stage-0,stage-1,stage-2,stage-3&targets=&browsers=%3E%202%25,%20ie%209,&builtIns=false&debug=false&code_lz=DYUwLgBADgTg9lAzhAvBA3gKAhAhgLggEYAabCAI0ICZMBfM3VCAZjIuZYG5MAKdPCUoQ6zWAkQBKTAGM4AO0RxQAOmBwA5r1xCKkoA 194 | [2]: https://codepen.io/gaearon/pen/ZpvBNJ?editors=0010 195 | [3]: https://segmentfault.com/a/1190000010199272 196 | 197 | 上一节:[4.扩展对象的功能性](https://github.com/hyy1115/ES6-learning/blob/master/doc/4%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E6%89%A9%E5%B1%95%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%8A%9F%E8%83%BD%E6%80%A7.md) 198 | 199 | 下一节:[6.Symbol和Symbol属性](https://github.com/hyy1115/ES6-learning/blob/master/doc/6%E3%80%81%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3ES6%E3%80%8B%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94Symbol%E5%92%8CSymbol%E5%B1%9E%E6%80%A7.md) -------------------------------------------------------------------------------- /doc/git.md: -------------------------------------------------------------------------------- 1 | ## Git 学习笔记 2 | 3 | ### 基本操作 4 | * 1.git init 初始化本地仓库 5 | * 2.git add filename 添加文件到仓库 6 | * 3.git commit -m "描述说明" 提交到仓库 7 | * 4.git status 查看版本状态 (innsertios 插入 提示) 8 | * 5.git diff 查看上次修改 9 | * 6.git add filename 再次添加到版本库 我就记住啦~ 10 | * 7.git log 命令显示从最近到最远的提交日志 [head 表示当前版本] 11 | 12 | 13 | ### 版本切换 14 | 15 | * 8.git reset --hard HEAD^ 回退到上一个版本 可以用git log 查看下日志 16 | 17 | * 9.git reset --hard 提交名(前7位) 可以返回当前版本 注意: 在命令行窗口没关的情况下 18 | 19 | * Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL 20 | 21 | * 关键在于 commit id 22 | 23 | 不小心把命令行窗口关了怎么办? 24 | 25 | git还是可以补救的: 26 | 27 | * 10.git reflog 记录每一次的命令 它会告诉你 版本的变化 就是提交对应的ID 28 | 29 | ### 工作区和版本库 30 | 31 | * 工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库 32 | 33 | * Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD 34 | 35 | * 我们写文件的地方叫工作区====== 提交的地方叫版本库(.git它是被隐藏的) || 版本库: 1.暂存区 2.master分支 36 | 37 | 38 | * git add 操作是将我们所有的修改提交到暂存区 然后git commit一次性提交到master 每次提交都有一个commit id 39 | 40 | * 提交后工作区如果没有做修改 那么它会是干净的 41 | 42 | ##### 暂存区 一定要理解 !!!!!! Git做的是版本管理的修改 而非文件 43 | 44 | * git add filename 是把文件修改提交到 暂存区 如果一个文件修改了 并没有git add 直接commit是没法把修改提交上的 45 | 46 | * git commit 是一次性将 暂存区的修改 提交到 master 47 | 48 | 49 | 50 | * 用git diff HEAD -- filename命令可以查看工作区和版本库里面最新版本的区别 51 | 52 | 53 | 54 | 55 | ### 撤销修改 56 | 57 | * git checkout -- .file 撤销工作区的修改 58 | 59 | * 1.一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态; 60 | 61 | * 2.一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。 62 | 63 | * 当然 git reset 也可以帮我们回到之前的修改 64 | 65 | * git reset HEAD 可以把暂存区的修改撤销掉(unstage)重新放回工作区: 就是丢弃暂存区的修改 66 | 67 | * 注意不要和git reset --hard commit_id 混淆 它是用来做版本替换 68 | 69 | 70 | ### 删除操作 71 | 72 | * 1.rm 直接删掉工作区的文件 73 | * 2.git rm filename 删掉版本库的文件 74 | 75 | 当工作区的文件被删除后 可以用git reset --hard 从版本库中恢复该文件 76 | 77 | 当然我们可以撤销工作区的修改 也可以一键恢复我们的文件 当然在版本库没有被删除的情况下 也就是没有commit的情况下 78 | 79 | 80 | 如果执行git rm 则工作区和版本库都会被删除掉 注意:做了修改后要 commit 提交到master分支 81 | 82 | 83 | 84 | 85 | ### 远程仓库 86 | 87 | 88 | #### 创建本地仓库与远程关联 89 | 90 | 注意 : 在这之前需要先创建好ssh key 这样可以判断出 是否是本人关联而不是其他人 91 | 92 | * 添加ssh key方法:命令行输入 ssh-keygen -t rsa -C "youremail@example.com" 替换成自己的邮箱 93 | * 设置: 首先找到自己的github账号-> settings -> sshkey -> title(这里可以自己随便写) -> Key 94 | * key: 输入上面的命令之后 默认在/Users/用户名/.ssh 下的 id_rsa.pub,将这个文件的内容复制到github的Key内容里 点击ADD添加 即可 95 | 96 | 97 | * 1. 关联远程仓库 git remote add origin git@github.com:yourname/git-note.git 98 | 99 | * 2. 首次推送 git push -u origin master 100 | 101 | * 3. 以后推送本地 git push origin master 102 | 103 | * 4. 更新远程仓库: git pull 104 | 105 | * 5. 推送: git push 106 | 107 | 108 | #### 创建远程仓库克隆 109 | 110 | * git clone git@github.com:yourname/git-note.git 111 | 112 | * cd git-note && ls 113 | 114 | * 可以看到远程仓库克隆下来 并且带了一个README.md的文件 115 | 116 | 117 | #### 合并分支 118 | 119 | ```git merge branch_name``` 120 | 121 | 建议: 先合并不提交:git merge --no-ff branch_name 122 | 123 | 124 | 125 | #### 查看分支 126 | 127 | ```Git branch -a``` 128 | 129 | 删除远程分支: 130 | 131 | - 使用命令 ```git push origin --delete Chapater6```   可以删除远程分支Chapater6    132 |        133 | 134 | 删除本地分支: 135 | - 使用命令 ```git branch -d Chapater8``` 可以删除本地分支(在主分支中) 136 | 137 | 138 | 清空远程分支 : 139 | - 先清空本地在清除远程 140 | 141 | 142 | #### 远程同步本地 143 | 144 | ##### 分支相关操作: 145 | 146 | * `git branch` # 列出本地分支 147 | * `git branch -r` # 列出远端分支 148 | * `git branch -a` # 列出所有分支 149 | * `git branch -d test` # 删除test分支 150 | * `git branch -D test` # 强制删除test分支 151 | 152 | 153 | ##### 拉取远程分支并创建本地分支 154 | 155 | 方法一 156 | 157 | * git checkout -b develop origin/develop # 本地分支名x origin/远程分支名x 158 | 159 | 使用该方式会在本地新建分支x,并自动切换到该本地分支x。 160 | 161 | 采用此种方法建立的本地分支会和远程分支建立映射关系。 162 | 163 | 方式二 164 | 165 | 166 | * git fetch origin develop:develop # 远程分支名x:本地分支名x 167 | 168 | 使用该方式会在本地新建分支x,但是不会自动切换到该本地分支x,需要手动checkout。 169 | 170 | 采用此种方法建立的本地分支不会和远程分支建立映射关系。 171 | 172 | 173 | ##### Git pull 174 | 175 | 176 | git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。 177 | 178 | ```$ git pull <远程主机名> <远程分支名>:<本地分支名>``` 179 | 比如,取回origin主机的next分支,与本地的master分支合并,需要写成下面这样。 180 | 181 | ```$ git pull origin next:master``` 182 | 如果远程分支是与当前分支合并,则冒号后面的部分可以省略。 183 | 184 | ```$ git pull origin next``` 185 | 上面命令表示,取回origin/next分支,再与当前分支合并。实质上,这等同于先做git fetch,再做git merge。 186 | 187 | ```shell 188 | $ git fetch origin 189 | $ git merge origin/next 190 | ``` 191 | 在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。 192 | 193 | Git也允许手动建立追踪关系。 194 | 195 | ```git branch --set-upstream master origin/next``` 196 | 上面命令指定master分支追踪origin/next分支。 197 | 198 | 如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。 199 | 200 | ```$ git pull origin``` 201 | 上面命令表示,本地的当前分支自动与对应的origin主机”追踪分支”(remote-tracking branch)进行合并。 202 | 203 | 如果当前分支只有一个追踪分支,连远程主机名都可以省略 204 | 205 | 206 | #### commit规范 207 | 208 | - [参考](https://juejin.im/post/5bd2debfe51d457abc710b57) 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /doc/high/async.md: -------------------------------------------------------------------------------- 1 | ### JS异步编程 2 | 3 | #### 并发与并行 4 | 5 | > ###### 涉及面试题:并发与并行的区别? 6 | 7 | 并发:是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务 8 | 9 | 并行:是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。 10 | 11 | 12 | #### 回调函数 13 | 14 | > ###### 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题? 15 | 16 | 回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个请求存在依赖性,你可能就会写出如下代码: 17 | 18 | ```js 19 | ajax(url, () => { 20 | // 处理逻辑 21 | ajax(url1, () => { 22 | // 处理逻辑 23 | ajax(url2, () => { 24 | // 处理逻辑 25 | }) 26 | }) 27 | }) 28 | ``` 29 | 30 | 回调地狱的根本问题就是: 31 | 32 | - 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身 33 | - 嵌套函数一多,就很难处理错误 34 | 35 | 回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return 36 | 37 | #### Generator 38 | 39 | > ###### 你理解的 Generator 是什么? 40 | 41 | Generator 最大的特点就是可以控制函数的执行 42 | (迭代器函数) 43 | 44 | ```js 45 | function *foo(x) { 46 | let y = 2 * (yield (x + 1)) 47 | let z = yield (y / 3) 48 | return (x + y + z) 49 | } 50 | let it = foo(5) 51 | console.log(it.next()) // => {value: 6, done: false} 52 | console.log(it.next(12)) // => {value: 8, done: false} 53 | console.log(it.next(13)) // => {value: 42, done: true} 54 | ``` 55 | 56 | - generator函数调用会返回一个迭代器 57 | - 第一次执行next时,传参会被忽略,且函数会被暂停在yeild (x+1),返回 5+1 58 | - 第二次执行,传入的参数等与上一个yeild的返回值,如果不传则永远返回undefined,所以第二个yield等于2*12/8=3 59 | - 第三次执行的时候会把参数给到z, 60 | 61 | 改造上个代码: 62 | ```js 63 | function *fetch() { 64 | yield ajax(url, () => {}) 65 | yield ajax(url1, () => {}) 66 | yield ajax(url2, () => {}) 67 | } 68 | let it = fetch() 69 | let result1 = it.next() 70 | let result2 = it.next() 71 | let result3 = it.next() 72 | 73 | ``` 74 | 75 | #### Promise 76 | 77 | > ###### Promise 的特点是什么,分别有什么优缺点?什么是 Promise 链?Promise 构造函数执行和 then 函数执行有什么区别? 78 | 79 | 三种状态: 80 | - 等待中(pending) 81 | - 完成了 (resolved) 82 | - 拒绝了(rejected) 83 | 84 | 状态一旦改变就无法改变,变为resloved后就不能再改变 85 | 86 | ```js 87 | new Promise((resolve, reject) => { 88 | resolve('success') 89 | // 无效 90 | reject('reject') 91 | }) 92 | ``` 93 | 当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的 94 | 95 | ```js 96 | new Promise((resolve, reject) => { 97 | console.log('new Promise') 98 | resolve('success') 99 | }) 100 | console.log('finifsh') 101 | // new Promise -> finifsh 102 | ``` 103 | 104 | Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装 105 | 106 | ```js 107 | Promise.resolve(1) 108 | .then(res => { 109 | console.log(res) // => 1 110 | return 2 // 包装成 Promise.resolve(2) 111 | }) 112 | .then(res => { 113 | console.log(res) // => 2 114 | }) 115 | ``` 116 | 117 | promise也可以解决之前回调地域的问题 118 | 119 | ```js 120 | ajax(url) 121 | .then(res => { 122 | console.log(res) 123 | return ajax(url1) 124 | }).then(res => { 125 | console.log(res) 126 | return ajax(url2) 127 | }).then(res => console.log(res)) 128 | ``` 129 | 130 | #### async 和 await 131 | 132 | > ###### async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么? 133 | 134 | 一个函数如果加上 async ,那么该函数就会返回一个 Promise 135 | 136 | ```js 137 | async function test() { 138 | return "1" 139 | } 140 | console.log(test()) // -> Promise {: "1"} 141 | ``` 142 | 143 | async 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样,并且 await 只能配套 async 使用 144 | 145 | ```js 146 | async function test() { 147 | let value = await sleep() 148 | } 149 | ``` 150 | async 和 await相比直接使用 Promise 来说,优势在于处理 then 的调用链,并且也能优雅地解决回调地狱问题。 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。 151 | 152 | ```js 153 | async function test() { 154 | // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式 155 | // 如果有依赖性的话,其实就是解决回调地狱的例子了 156 | await fetch(url) 157 | await fetch(url1) 158 | await fetch(url2) 159 | } 160 | ``` 161 | 162 | - 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来 163 | - 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码 164 | - 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10 165 | 166 | 上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。 167 | 168 | 169 | #### 定时器函数 170 | 171 | > ###### setTimeout、setInterval、requestAnimationFrame 各有什么特点? 172 | 173 | JS 是单线程执行的,如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行 174 | 175 | ```js 176 | let period = 60 * 1000 * 60 * 2 177 | let startTime = new Date().getTime() 178 | let count = 0 179 | let end = new Date().getTime() + period 180 | let interval = 1000 181 | let currentInterval = interval 182 | 183 | function loop() { 184 | count++ 185 | // 代码执行所消耗的时间 186 | let offset = new Date().getTime() - (startTime + count * interval); 187 | let diff = end - new Date().getTime() 188 | let h = Math.floor(diff / (60 * 1000 * 60)) 189 | let hdiff = diff % (60 * 1000 * 60) 190 | let m = Math.floor(hdiff / (60 * 1000)) 191 | let mdiff = hdiff % (60 * 1000) 192 | let s = mdiff / (1000) 193 | let sCeil = Math.ceil(s) 194 | let sFloor = Math.floor(s) 195 | // 得到下一次循环所消耗的时间 196 | currentInterval = interval - offset 197 | console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) 198 | 199 | setTimeout(loop, currentInterval) 200 | } 201 | 202 | setTimeout(loop, currentInterval) 203 | ``` 204 | 205 | 通常来说不建议使用 setInterval。第一,它和 setTimeout 一样,不能保证在预期的时间执行任务。第二,它存在执行累积的问题,不好修正,请看以下伪代码 206 | 207 | ```js 208 | function demo() { 209 | setInterval(function(){ 210 | console.log(2) 211 | },1000) 212 | sleep(2000) 213 | } 214 | demo() 215 | ``` 216 | 217 | 如果你有循环定时器的需求,其实完全可以通过 requestAnimationFrame 来实现 218 | 219 | 首先 requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 setTimeout。 220 | 221 | 222 | -------------------------------------------------------------------------------- /doc/high/call.md: -------------------------------------------------------------------------------- 1 | #### 手写 call、apply 及 bind 函数 2 | 3 | > ###### 涉及面试题:call、apply 及 bind 函数内部实现是怎么样的? 4 | 5 | 特点: 6 | 7 | - 第一个参数默认是window 8 | - 改变this指向,并且新对象可以执行该函数并能接受参数 9 | 10 | 11 | ##### call 12 | 13 | - 首先是Function的原型上添加方法 14 | - 接着给context上下文添加fn属性 15 | - call传入多余的参数作为函数执行的参数 16 | - 最后将fn从目标对象移除掉 17 | ```js 18 | Function.prototype.call_ = () => { 19 | console.log('call_') 20 | } 21 | 22 | function MyTest(e) { 23 | console.log(this) // { name: 'lili', fn: [Function: MyTest] } 24 | console.log('mytest ----> ' + e) // mytest ----> lili 25 | } 26 | 27 | // MyTest.call_() // test 28 | 29 | // 需: MyTest.call_(obj, 'call_') 30 | 31 | Function.prototype.call_ = function (context) { // 这里要是 function 不然捕捉不到this 32 | console.log(this, context) // [Function: MyTest] { name: 'lili' } 33 | // 判断调用者是否是函数 34 | if(typeof this !== 'function') { 35 | throw new TypeError('type error') 36 | } 37 | 38 | // // context 上下文 39 | context = context || window 40 | context.fn = this // 赋予要执行函数 41 | const args = [...arguments].slice(1) 42 | const result = context.fn(args) 43 | delete context.fn // 移除属性 44 | return result // 返回执行结果 45 | } 46 | 47 | const obj = { 48 | name: 'lili' 49 | } 50 | 51 | MyTest.call_(obj, obj.name) 52 | ``` 53 | 54 | ##### apply 55 | 56 | apply和call很像,传参形式有点不一样 57 | 58 | ```js 59 | Function.prototype.call_ = function (context) { // 这里要是 function 不然捕捉不到this 60 | console.log(this, context) // [Function: MyTest] { name: 'lili' } 61 | // 判断调用者是否是函数 62 | if(typeof this !== 'function') { 63 | throw new TypeError('type error') 64 | } 65 | 66 | // // context 上下文 67 | context = context || window 68 | context.fn = this // 赋予要执行函数 69 | 70 | // 对参数进行处理 71 | let result 72 | // 传的是数组 73 | if(arguments[1]) { 74 | // 将数组扩展到函数参数上 75 | result = context.fn(...arguments[1]) 76 | }else { 77 | result = context.fn() 78 | } 79 | delete context.fn // 移除属性 80 | return result // 返回执行结果 81 | } 82 | ``` 83 | 84 | ##### bind 85 | 86 | bind实现的是绑定上下文并且返回的是一个函数 87 | 88 | ```js 89 | function test (e,v) { 90 | console.log(e) // 12 91 | console.log(v) // 10 92 | console.log(this) // { name: 1 } 93 | } 94 | 95 | const obj = { name: 1 } 96 | 97 | test.bind(obj, 12)(10) 98 | 99 | test.bind(obj)(10) 100 | // 10 101 | // undefined 102 | ``` 103 | 104 | 实现 105 | 106 | ```js 107 | Function.prototype.myBind = function (context) { 108 | if (typeof this !== 'function') { 109 | throw new TypeError('Error') 110 | } 111 | const _this = this 112 | const args = [...arguments].slice(1) 113 | // 返回一个函数 114 | return function F() { 115 | // 因为返回了一个函数,我们可以 new F(),所以需要判断 116 | if (this instanceof F) { 117 | // 当this不被改变时 忽略传入的this 118 | return new _this(...args, ...arguments) 119 | } 120 | return _this.apply(context, args.concat(...arguments)) 121 | } 122 | } 123 | ``` 124 | 125 | 126 | #### new 127 | 128 | > ###### 涉及面试题:new 的原理是什么?通过 new 的方式创建对象和通过字面量创建有什么区别? 129 | 130 | 调用 new 的过程中会发生: 131 | 132 | - 新生成了一个对象 133 | - 链接到原型 134 | - 绑定 this 135 | - 返回新对象 136 | 137 | new的实现: 138 | ```js 139 | function create() { 140 | let obj = {} 141 | let Con = [].shift.call(arguments) // 获取传入的第一个参数 142 | obj.__proto__ = Con.prototype 143 | let result = Con.apply(obj, arguments) 144 | return result instanceof Object ? result : obj 145 | } 146 | ``` 147 | 148 | - 创建一个空对象 149 | - 获取构造函数 150 | - 设置空对象的原型 151 | - 绑定 this 并执行构造函数 152 | - 确保返回值为对象 153 | 154 | 对于对象来说,其实都是通过 new 产生的,无论是 function Foo() 还是 let a = { b : 1 } 。 155 | 156 | 对于创建一个对象来说,更推荐使用字面量的方式创建对象(无论性能上还是可读性)。因为你使用 new Object() 的方式创建对象需要通过作用域链一层层找到 Object,但是你使用字面量的方式就没这个问题。 157 | 158 | ```js 159 | function Foo() {} 160 | // function 就是个语法糖 161 | // 内部等同于 new Function() 162 | let a = { b: 1 } 163 | // 这个字面量内部也是使用了 new Object() 164 | ``` 165 | 166 | 167 | 168 | 169 | 170 | #### instanceof 的原理 171 | 172 | instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。 173 | 174 | 实现: 175 | ```js 176 | function myInstanceof(left, right) { 177 | let prototype = right.prototype 178 | left = left.__proto__ 179 | while (true) { 180 | if (left === null || left === undefined) 181 | return false 182 | if (prototype === left) 183 | return true 184 | left = left.__proto__ 185 | } 186 | } 187 | ``` 188 | 189 | - 首先获取类型的原型 190 | - 然后获得对象的原型 191 | - 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null 192 | 193 | 194 | #### 为什么 0.1 + 0.2 != 0.3 195 | > ###### 为什么 0.1 + 0.2 != 0.3?如何解决这个问题? 196 | 197 | 计算机是通过 198 | 二进制来存储东西的,0.1 在二进制中是无限循环的一些数字,其实不只是 0.1,其实很多十进制小数用二进制表示都是无限循环的 199 | 那么这些循环的数字被裁剪了,就会出现精度丢失的问题,也就造成了 0.1 不再是 0.1 了,而是变成了 0.100000000000000002 200 | 201 | ###### console.log(0.1)为什么是0.1? 202 | 203 | 输入内容的时候,二进制被转换为了十进制,十进制又被转换为了字符串,在这个转换的过程中发生了取近似值的过程,所以打印出来的其实是一个近似值,你也可以通过以下代码来验证 204 | 205 | ```js 206 | console.log(0.100000000000000002) // 0.1 207 | ``` 208 | 209 | 如何解决这个问题? 210 | 211 | ```js 212 | parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true 213 | ``` 214 | 215 | 216 | #### 垃圾回收机制 217 | 218 | > ###### V8 下的垃圾回收机制是怎么样的? 219 | 220 | V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)分为新生代和老生代两部分。 221 | 222 | 新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。 223 | 224 | 老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法和标记压缩算法。 225 | -------------------------------------------------------------------------------- /doc/high/evloop.md: -------------------------------------------------------------------------------- 1 | ### Envent Loop 2 | 3 | #### 进程和线程 4 | 5 | > ###### 涉及面试题:进程与线程区别?JS 单线程带来的好处? 6 | 7 | 本质上来说,两个名词都是 CPU 工作时间片的一个描述。 8 | 9 | ##### 进程 10 | 11 | CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序 12 | 13 | ##### 线程 14 | 线程是进程中的更小单位,描述了执行一段指令所需的时间 15 | 16 | ##### 在浏览器中 17 | 18 | 打开一个tab相当于创建了一个进程,一个进程中包含多个线程(比如渲染线程、JS引擎线程、HTTP请求线程等) 发起一个请求相当于创建一个线程,结束后,该线程可能被销毁 19 | 20 | 21 | JS引擎线程可能会阻止UI渲染,这两个线程互斥,原因是JS可以修改DOM,如果JS执行并且UI还在工作,这样创建元素可能不安全。单线程也有好处,比如它更节省内存,节约上下文切换时间,没有锁的问题的好处。在服务端中,锁的问题比如读取数据枷锁,直到读取完毕后才可以进行写入操作 22 | 23 | 24 | ##### 执行栈 25 | 26 | > ###### 涉及面试题:什么是执行栈? 27 | 28 | 可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则 29 | 30 | 平时开发中也可以查看报错情况,可以定位到准确的执行位置 31 | 32 | 当我们使用递归的时候,因为栈可存放的函数是有限制的,一旦存放了过多的函数且没有得到释放的话,就会出现爆栈的问题 33 | 34 | ```js 35 | function bar() { 36 | bar() 37 | } 38 | bar() // Maximum call stack 39 | ``` 40 | 41 | #### 浏览器中的Event Loop 42 | 43 | > ###### 涉及面试题:异步代码执行顺序?解释一下什么是 Event Loop ? 44 | 45 | ##### 异步代码执行 46 | 47 | JS在执行的时候往执行栈中存放函数,遇到异步的代码,会挂起,等待需要执行的时候加入task(task有多种)队列,当执行栈为空时,就会从task队列中拿出需要执行的代码放入执行栈中执行,JS 中的异步还是同步行为(只不过顺序不一样) 48 | 49 | 50 | 51 | ##### 事件循环队列 52 | 53 | 不同的任务源会被分配到不同的 Task 队列 54 | 55 | 任务源可分为: 56 | - 微任务(microtask) 57 | - 宏任务(macrotask) 58 | 59 | > 在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task 60 | 61 | 如下代码执行顺序: 62 | 63 | ```js 64 | 65 | console.log('script start') // 1 66 | 67 | async function async1() { 68 | await async2() // 3. 69 | console.log('async1 end') // 9 70 | } 71 | async function async2() { 72 | console.log('async2 end') // 4 73 | } 74 | async1() // 2 调用 75 | 76 | setTimeout(function() { 77 | console.log('setTimeout') // 10 78 | }, 0) 79 | 80 | new Promise(resolve => { 81 | console.log('Promise') // 5 82 | resolve() 83 | }) 84 | .then(function() { 85 | console.log('promise1') // 7 86 | }) 87 | .then(function() { 88 | console.log('promise2') // 8 89 | }) 90 | 91 | console.log('script end') // 6 92 | 93 | ``` 94 | 95 | 新的浏览器下输出: 96 | 97 | ```js 98 | script start 99 | async2 end 100 | Promise 101 | script end 102 | async1 end 103 | promise1 104 | promise2 105 | setTimeout 106 | ``` 107 | 108 | - await 看成是让出线程的标志 109 | - await 后面跟着 Promise 的话,async1 end 需要等待三个 tick 才能执行到 110 | 111 | 112 | Event Loop 执行顺序 113 | 114 | - 先执同步代码-宏任务 115 | - 执行完同步,执行栈为空,查看是否有异步代码要执行 116 | - 执行所有微任务 117 | - 当执行完所有微任务,如有必要会渲染页面 118 | - 开始下一轮Event Loop, 执行宏任务中的异步代码,也就是setTimeout的回调 119 | 120 | 121 | 虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务而 setTimeout 属于宏任务 122 | 123 | 124 | 微任务包括: 125 | 126 | - process.nextTick 127 | - promise 128 | - MutationObserver,其中 process.nextTick 为 Node 独有。 129 | 130 | 131 | 宏任务包括: 132 | 133 | - script 134 | - setTimeout 135 | - setInterval 136 | - setImmediate 137 | - I/O 138 | - UI rendering 139 | 140 | 141 | #### Node中的Event Loop 142 | 143 | > ###### 涉及面试题:Node 中的 Event Loop 和浏览器中的有什么区别?process.nexttick 执行顺序? 144 | 145 | Node 中的 Event Loop 和浏览器中的是完全不相同的东西 146 | 147 | Node 的 Event Loop 分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。 148 | 149 | ##### timers 150 | 151 | timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。 152 | 153 | 同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。 154 | 155 | 156 | ##### I/O 157 | 158 | I/O 阶段会处理一些上一轮循环中的少数未执行的 I/O 回调 159 | 160 | 161 | ##### idle, prepare 162 | 163 | idle, prepare 阶段内部实现 164 | 165 | ##### poll 166 | 167 | poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情 168 | 169 | - 回到 timer 阶段执行回调 170 | - 执行 I/O 回调 171 | 172 | 173 | 并且在进入该阶段时如果没有设定了 timer 的话: 174 | 175 | - 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制 176 | - 如果 poll 队列为空时,会有两件事发生 177 | - -1. 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调 178 | - -2. 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去 179 | 180 | 当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。 181 | 182 | ##### check 183 | check 阶段执行 setImmediate 184 | 185 | ##### close callbacks 186 | 187 | close callbacks 阶段执行 close 事件 188 | 189 | 对于以上代码来说,setTimeout 可能执行在前,也可能执行在后 190 | 191 | 192 | ```js 193 | const fs = require('fs') 194 | 195 | fs.readFile(__filename, () => { 196 | setTimeout(() => { 197 | console.log('timeout'); 198 | }, 0) 199 | setImmediate(() => { 200 | console.log('immediate') 201 | }) 202 | }) 203 | ``` 204 | 205 | 首先在有些情况下,定时器的执行顺序其实是随机的 206 | 207 | ```js 208 | setTimeout(() => { 209 | console.log('setTimeout') 210 | }, 0) 211 | setImmediate(() => { 212 | console.log('setImmediate') 213 | }) 214 | ``` 215 | 216 | - setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的 217 | - 事件循环也是需要成本的,如果在准备时候花费了大于 1ms 的时间,那么在 timer 阶段就会直接执行 setTimeout 回调 218 | - 如果准备时间花费小于 1ms,那么就是 setImmediate 回调先执行了 219 | 220 | 221 | --------- 222 | 223 | 待续.... 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /doc/high/http.md: -------------------------------------------------------------------------------- 1 | #### HTTP 请求中的内容 2 | 3 | 三次握手和四次挥手: 4 | 5 | > http的三次握手和四次挥手: 6 | > 7 | > 浏览器在给服,务器传输数据之前,有三次握手,握手成功之后,才可以传输数据 8 | > 9 | > 1、浏览器需要先发送SYN码,客户端请求和服务器建立连接; 10 | > 11 | > 2、服务器接收到SYN码,再发送给客户端SYN+ACK码,我可以建立连接; 12 | > 13 | > 3、客户端接收到ACK码,验证这个ACK是否正确,如果正确则客户端和服务端则建立起数据连接;双方的数据发送通道都将开启; 14 | > 15 | > 16 | > 17 | > 四次挥手: 18 | > 19 | > 1、当客户端无数据要传输了,会发送FIN码告诉服务器,我发送完毕了; 20 | > 21 | > 2、当服务端接收完毕后,告诉客户端ACK码,告诉客户端你可以把数据通道关闭了; 22 | > 23 | > 3、当服务器发送完毕之后,也会发送FIN码,告诉浏览器,数据发送完毕; 24 | > 25 | > 4、当客户端接收完毕 之后,同样发送ACK码,告诉服务器,数据接收完毕,你可以关闭; 26 | > 27 | > 三次握手和四次挥手的好处:确保数据的安全和完整 28 | > 29 | > 30 | > 31 | > 响应头:服务器会告诉浏览器数据的长度,浏览器数据长度和响应头数据长度相同,说明数据已经接收完毕了。 32 | 33 | 网络传输七层协议: 34 | 35 | > 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 36 | 37 | HTTP 请求由三部分构成,分别为: 38 | - 请求行 39 | - 首部 40 | - 实体 41 | 42 | 请求行大概长这样 GET /images/logo.gif HTTP/1.1,基本由请求方法、URL、协议版本组成,这其中值得一说的就是请求方法了。 43 | 44 | 请求方法分为很多种,最常用的也就是 Get 和 Post 了。虽然请求方法有很多,但是更多的是传达一个语义,而不是说 Post 能做的事情 Get 就不能做了。如果你愿意,都使用 Get 请求或者 Post 请求都是可以的。更多请求方法的语义描述可以阅读 文档。 45 | 46 | 47 | > Post 和 Get 的区别? 48 | 49 | 首先先引入副作用和幂等的概念。 50 | 51 | 副作用指对服务器上的资源做改变,搜索是无副作用的,注册是副作用的。 52 | 53 | 幂等指发送 M 和 N 次请求(两者不相同且都大于 1),服务器上资源的状态一致,比如注册 10 个和 11 个帐号是不幂等的,对文章进行更改 10 次和 11 次是幂等的。因为前者是多了一个账号(资源),后者只是更新同一个资源。 54 | 55 | 在规范的应用场景上说,Get 多用于无副作用,幂等的场景,例如搜索关键字。Post 多用于副作用,不幂等的场景,例如注册。 56 | 57 | 在技术上说: 58 | 59 | - Get 请求能缓存,Post 不能 60 | - Post 相对 Get 安全一点点,因为Get 请求都包含在 URL 里(当然你想写到 body 里也是可以的),且会被浏览器保存历史纪录。Post 不会,但是在抓包的情况下都是一样的。 61 | - URL有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的 62 | - Post 支持更多的编码类型且不对数据类型限制 63 | 64 | 65 | #### 首部 66 | 67 | ##### 通用首部 68 | 69 | Cache-Control 控制缓存的行为 70 | 71 | Connection 浏览器想要优先使用的连接类型,比如 keep-alive 72 | 73 | Date 创建报文时间 74 | 75 | Pragma 报文指令 76 | 77 | Via 代理服务器相关信息 78 | 79 | Transfer-Encoding 传输编码方式 80 | 81 | Upgrade 要求客户端升级协议 82 | 83 | Warning 在内容中可能存在错误 84 | 85 | 86 | ##### 请求首部 87 | 88 | Accept 能正确接收的媒体类型 89 | 90 | Accept-Charset 能正确接收的字符集 91 | 92 | Accept-Encoding 能正确接收的编码格式列表 93 | 94 | Accept-Language 能正确接收的语言列表 95 | 96 | Expect 期待服务端的指定行为 97 | 98 | From 请求方邮箱地址 99 | 100 | Host 服务器的域名 101 | 102 | If-Match 两端资源标记比较 103 | 104 | If-Modified-Since 本地资源未修改返回 304(比较时间) 105 | 106 | If-None-Match 本地资源未修改返回 304(比较标记) 107 | 108 | User-Agent 客户端信息 109 | 110 | Max-Forwards 限制可被代理及网关转发的次数 111 | 112 | Proxy-Authorization 向代理服务器发送验证信息 113 | 114 | Range 请求某个内容的一部分 115 | 116 | Referer 表示浏览器所访问的前一个页面 117 | 118 | TE 传输编码方式 119 | 120 | 121 | ##### 响应首部 122 | 123 | - Accept-Ranges 是否支持某些种类的范围 124 | - Age 资源在代理缓存中存在的时间 125 | - ETag 资源标识 126 | - Location 客户端重定向到某个 URL 127 | - Proxy-Authenticate 向代理服务器发送验证信息 128 | - Server 服务器名字 129 | - WWW-Authenticate 获取资源需要的验证信息 130 | 131 | ##### 实体首部 132 | 133 | - Allow 资源的正确请求方式 134 | - Content-Encoding 内容的编码格式 135 | - Content-Language 内容使用的语言 136 | - Content-Length request body 长度 137 | - Content-Location 返回数据的备用地址 138 | - Content-MD5 Base64加密格式的内容 MD5检验值 139 | - Content-Range 内容的位置范围 140 | - Content-Type 内容的媒体类型 141 | - Expires 内容的过期时间 142 | - Last_modified 内容的最后修改时间 143 | 144 | #### 常见状态码 145 | 146 | ##### 2XX 成功 147 | 148 | - 200 OK,表示从客户端发来的请求在服务器端被正确处理 149 | - 204 No content,表示请求成功,但响应报文不含实体的主体部分 150 | - 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容 151 | - 206 Partial Content,进行范围请求 152 | 153 | 154 | ##### 3XX 重定向 155 | 156 | - 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL 157 | - 302 found,临时性重定向,表示资源临时被分配了新的 URL 158 | - 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源 159 | - 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况 160 | - 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 161 | 162 | ##### 4XX 客户端错误 163 | 164 | - 400 bad request,请求报文存在语法错误 165 | - 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 166 | - 403 forbidden,表示对请求资源的访问被服务器拒绝 167 | - 404 not found,表示在服务器上没有找到请求的资源 168 | 169 | ##### 5XX 服务器错误 170 | 171 | - 500 internal sever error,表示服务器端在执行请求时发生了错误 172 | - 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能 173 | - 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 174 | 175 | 176 | #### TLS 177 | 178 | HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议进行了加密。 179 | 180 | TLS 协议位于传输层之上,应用层之下。首次进行 TLS 协议传输需要两个 RTT ,接下来可以通过 Session Resumption 减少到一个 RTT。 181 | 182 | 在 TLS 中使用了两种加密技术,分别为:对称加密和非对称加密。 183 | 184 | ___ 185 | 186 | #### HTTP/2 187 | 188 | HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能。 189 | 190 | 在 HTTP/1 中,为了性能考虑,我们会引入雪碧图、将小图内联、使用多个域名等等的方式。这一切都是因为浏览器限制了同一个域名下的请求数量(Chrome 下一般是限制六个连接),当页面中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 191 | 192 | 在 HTTP/2 中引入了多路复用的技术,这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也间接更容易实现全速传输,毕竟新开一个 TCP 连接都需要慢慢提升传输速度。 193 | 194 | 195 | 196 | 197 | #### 二进制传输 198 | 199 | HTTP/2 中所有加强性能的核心点在于此。在之前的 HTTP 版本中,我们是通过文本的方式传输数据。在 HTTP/2 中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。 200 | 201 | #### 多路复用 202 | 203 | 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 204 | 205 | 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 206 | 207 | 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。 208 | 209 | 210 | #### Header 压缩 211 | 212 | 在 HTTP/1 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。 213 | 214 | 在 HTTP /2 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。 215 | 216 | ##### 服务端 Push 217 | 218 | 在 HTTP/2 中,服务端可以在客户端某个请求后,主动推送其他资源。 219 | 220 | 可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch 。 221 | 222 | ___ 223 | 224 | #### HTTP/3 225 | 226 | 虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,虽然这个问题并不是它本身造成的,而是底层支撑的 TCP 协议的问题。 227 | 228 | 因为 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。当这个连接中出现了丢包的情况,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。 229 | 230 | 因为在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。 231 | 232 | 那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。 233 | 234 | 基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,当然 HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC,接下来我们就来学习关于这个协议的内容。 235 | 236 | 237 | ##### QUIC 238 | 239 | 之前我们学习过 UDP 协议的内容,知道这个协议虽然效率很高,但是并不是那么的可靠。QUIC 虽然基于 UDP,但是在原本的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。这里我们就挑选几个重要的功能学习下这个协议的内容。 240 | 241 | 多路复用 242 | 243 | 虽然 HTTP/2 支持了多路复用,但是 TCP 协议终究是没有这个功能的。QUIC 原生就实现了这个功能,并且传输的单个数据流可以保证有序交付且不会影响其他的数据流,这样的技术就解决了之前 TCP 存在的问题。 244 | 245 | 并且 QUIC 在移动端的表现也会比 TCP 好。因为 TCP 是基于 IP 和端口去识别连接的,这种方式在多变的移动端网络环境下是很脆弱的。但是 QUIC 是通过 ID 的方式去识别一个连接,不管你网络环境如何变化,只要 ID 不变,就能迅速重连上。 246 | 247 | 0-RTT 248 | 249 | 通过使用类似 TCP 快速打开的技术,缓存当前会话的上下文,在下次恢复会话的时候,只需要将之前的缓存传递给服务端验证通过就可以进行传输了。 250 | 251 | 纠错机制 252 | 253 | 假如说这次我要发送三个包,那么协议会算出这三个包的异或值并单独发出一个校验包,也就是总共发出了四个包。 254 | 255 | 当出现其中的非校验包丢包的情况时,可以通过另外三个包计算出丢失的数据包的内容。 256 | 257 | 当然这种技术只能使用在丢失一个包的情况下,如果出现丢失多个包就不能使用纠错机制了,只能使用重传的方式了。 258 | 259 | -------------------------------------------------------------------------------- /doc/high/monitor.md: -------------------------------------------------------------------------------- 1 | ### 监控 2 | 前端监控一般分为三种,分别为页面埋点、性能监控以及异常监控。 3 | 4 | #### 页面埋点 5 | 6 | 可以参考: 7 | > https://segmentfault.com/a/1190000014922668 8 | 9 | 页面埋点应该是大家最常写的监控了,一般起码会监控以下几个数据: 10 | 11 | - PV / UV 12 | - 停留时长 13 | - 流量来源 14 | - 用户交互 15 | 16 | 对于这几类统计,一般的实现思路大致可以分为两种,分别为手写埋点和无埋点的方式。 17 | 18 | 相信第一种方式也是大家最常用的方式,可以自主选择需要监控的数据然后在相应的地方写入代码。这种方式的灵活性很大,但是唯一的缺点就是工作量较大,每个需要监控的地方都得插入代码。 19 | 20 | 另一种无埋点的方式基本不需要开发者手写埋点了,而是统计所有的事件并且定时上报。这种方式虽然没有前一种方式繁琐了,但是因为统计的是所有事件,所以还需要后期过滤出需要的数据。 21 | 22 | 23 | #### 性能监控 24 | 25 | 性能监控可以很好的帮助开发者了解在各种真实环境下,页面的性能情况是如何的。 26 | 27 | 对于性能监控来说,我们可以直接使用浏览器自带的 Performance API 来实现这个功能。 28 | 29 | 对于性能监控来说,其实我们只需要调用 performance.getEntriesByType('navigation') 这行代码就行了。对,你没看错,一行代码我们就可以获得页面中各种详细的性能相关信息。 30 | 31 | 我们可以发现这行代码返回了一个数组,内部包含了相当多的信息,从数据开始在网络中传输到页面加载完成都提供了相应的数据。 32 | 33 | 34 | #### 异常监控 35 | 36 | 对于异常监控来说,以下两种监控是必不可少的,分别是代码报错以及接口异常上报。 37 | 38 | 对于代码运行错误,通常的办法是使用 window.onerror 拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外 39 | 40 | - 对于跨域的代码运行错误会显示 Script error. 对于这种情况我们需要给 script 标签添加 crossorigin 属性 41 | - 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过 arguments.callee.caller 来做栈递归 42 | 43 | 对于异步代码来说,可以使用 catch 的方式捕获错误。比如 Promise 可以直接使用 catch 函数,async await 可以使用 try catch。 44 | 45 | 但是要注意线上运行的代码都是压缩过的,需要在打包时生成 sourceMap 文件便于 debug。 46 | 47 | 对于捕获的错误需要上传给服务器,通常可以通过 img 标签的 src 发起一个请求。 48 | 49 | 另外接口异常就相对来说简单了,可以列举出出错的状态码。一旦出现此类的状态码就可以立即上报出错。接口异常上报可以让开发人员迅速知道有哪些接口出现了大面积的报错,以便迅速修复问题。 50 | 51 | 52 | -------------------------------------------------------------------------------- /doc/high/promise.md: -------------------------------------------------------------------------------- 1 | ### 手写Promise 2 | 3 | #### 简易版的promise 4 | 5 | ##### 大体框架 6 | 7 | ```js 8 | const PENDING = 'pending' 9 | const RESOLVED = 'resolved' 10 | const REJECTED = 'rejected' 11 | 12 | function MyPromise(fn) { 13 | const that = this 14 | // 代码可能会异步执行,用于获取正确的 this 对象 15 | 16 | that.state = PENDING 17 | that.value = null 18 | that.resolvedCallbacks = [] 19 | that.rejectedCallbacks = [] 20 | // 待完善 resolve 和 reject 函数 21 | // 待完善执行 fn 函数 22 | } 23 | ``` 24 | - 一开始 Promise 的状态应该是 pending 25 | - value 变量用于保存 resolve 或者 reject 中传入的值 26 | - resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,因为当执行完 27 | Promise 时状态可能还是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用 28 | 29 | 完善 resolve 和 reject 函数,添加在 MyPromise 函数体内部 30 | 31 | ```js 32 | function resolve(value) { 33 | if (that.state === PENDING) { 34 | that.state = RESOLVED 35 | that.value = value 36 | that.resolvedCallbacks.map(cb => cb(that.value)) 37 | } 38 | } 39 | 40 | function reject(value) { 41 | if (that.state === PENDING) { 42 | that.state = REJECTED 43 | that.value = value 44 | that.rejectedCallbacks.map(cb => cb(that.value)) 45 | } 46 | } 47 | ``` 48 | 49 | - 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态 50 | - 将当前状态更改为对应状态,并且将传入的值赋值给 value 51 | - 遍历回调数组并执行 52 | - 53 | 54 | 实现如何执行 Promise 中传入的函数 55 | 56 | ```js 57 | try { 58 | fn(resolve, reject) 59 | } catch (e) { 60 | reject(e) 61 | } 62 | ``` 63 | 64 | - 实现很简单,执行传入的参数并且将之前两个函数当做参数传进去 65 | - 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行 reject 函数 66 | 67 | 最后我们来实现较为复杂的 then 函数 68 | 69 | ```js 70 | MyPromise.prototype.then = function(onFulfilled, onRejected) { 71 | const that = this 72 | onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v 73 | onRejected = 74 | typeof onRejected === 'function' 75 | ? onRejected 76 | : r => { 77 | throw r 78 | } 79 | if (that.state === PENDING) { 80 | that.resolvedCallbacks.push(onFulfilled) 81 | that.rejectedCallbacks.push(onRejected) 82 | } 83 | if (that.state === RESOLVED) { 84 | onFulfilled(that.value) 85 | } 86 | if (that.state === REJECTED) { 87 | onRejected(that.value) 88 | } 89 | } 90 | ``` 91 | 92 | - 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 93 | 94 | - 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传,比如如下代码 95 | 96 | ```js 97 | // 该代码目前在简单版中会报错 98 | // 只是作为一个透传的例子 99 | Promise.resolve(4).then().then((value) => console.log(value)) 100 | ``` 101 | 102 | 接下来就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数,比如如下代码就会进入等待态的逻辑 103 | 104 | 105 | ```js 106 | new MyPromise((resolve, reject) => { 107 | setTimeout(() => { 108 | resolve(1) 109 | }, 0) 110 | }).then(value => { 111 | console.log(value) 112 | }) 113 | ``` 114 | 115 | 116 | #### Promise A+ 117 | 118 | 等厉害了再补充...... -------------------------------------------------------------------------------- /doc/high/safe.md: -------------------------------------------------------------------------------- 1 | #### XSS攻击 2 | > ###### 什么是 XSS 攻击?如何防范 XSS 攻击?什么是 CSP? 3 | 4 | XSS 简单点来说,就是攻击者想尽一切办法将可以执行的代码注入到网页中。 5 | 6 | XSS 可以分为多种类型,但是总体上我认为分为两类:持久型和非持久型。 7 | 8 | 持久型也就是攻击的代码被服务端写入进数据库中,这种攻击危害性很大,因为如果网站访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。 9 | 10 | 这种情况如果前后端没有做好防御的话,这段评论就会被存储到数据库中,这样每个打开该页面的用户都会被攻击到。 11 | 12 | 非持久型相比于前者危害就小的多了,一般通过修改 URL 参数的方式加入攻击代码,诱导用户访问链接从而进行攻击。 13 | 14 | ```html 15 | 16 |
{{name}}
17 | ``` 18 | 19 | 但是对于这种攻击方式来说,如果用户使用 Chrome 这类浏览器的话,浏览器就能自动帮助用户防御攻击。但是我们不能因此就不防御此类攻击了,因为我不能确保用户都使用了该类浏览器。 20 | 21 | 对于 XSS 攻击来说,通常有两种方式可以用来防御。 22 | 23 | 首先,对于用户的输入应该是永远不信任的。最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义 24 | 25 | ```js 26 | function escape(str) { 27 | str = str.replace(/&/g, '&') 28 | str = str.replace(//g, '>') 30 | str = str.replace(/"/g, '&quto;') 31 | str = str.replace(/'/g, ''') 32 | str = str.replace(/`/g, '`') 33 | str = str.replace(/\//g, '/') 34 | return str 35 | } 36 | ``` 37 | 38 | 通过转义可以将攻击代码 变成 39 | 40 | ```js 41 | // -> <script>alert(1)</script> 42 | escape('') 43 | ``` 44 | 45 | 但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。 46 | 47 | ```js 48 | const xss = require('xss') 49 | let html = xss('

XSS Demo

') 50 | // ->

XSS Demo

<script>alert("xss");</script> 51 | console.log(html) 52 | ``` 53 | 54 | 以上示例使用了 js-xss 来实现,可以看到在输出中保留了 h1 标签且过滤了 script 标签。 55 | 56 | 57 | ##### CSP 58 | CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击 59 | 60 | 通常可以通过两种方式来开启 CSP: 61 | 62 | - 设置 HTTP Header 中的 Content-Security-Policy 63 | - 设置 meta 标签的方式 64 | 65 | 这里以设置 HTTP Header 来举例 66 | 67 | - 只允许加载本站资源 68 | 69 | ``` 70 | Content-Security-Policy: default-src ‘self’ 71 | ``` 72 | 73 | - 只允许加载 HTTPS 协议图片 74 | 75 | ``` 76 | Content-Security-Policy: img-src https://* 77 | ``` 78 | 79 | - 允许加载任何来源框架 80 | 81 | ``` 82 | Content-Security-Policy: child-src 'none' 83 | ``` 84 | 85 | #### CSRF 86 | 87 | > ###### 涉及面试题:什么是 CSRF 攻击?如何防范 CSRF 攻击? 88 | 89 | CSRF 中文名为跨站请求伪造。原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。 90 | 91 | 举个例子,假设网站中有一个通过 GET 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口 92 | 93 | ``` 94 | 95 | ``` 96 | 97 | 那么你是否会想到使用 POST 方式提交请求是不是就没有这个问题了呢?其实并不是,使用这种方式也不是百分百安全的,攻击者同样可以诱导用户进入某个页面,在页面中通过表单提交 POST 请求。 98 | 99 | 100 | #### 如何防御 101 | 102 | 防范 CSRF 攻击可以遵循以下几种规则: 103 | 104 | - Get 请求不对数据进行修改 105 | - 不让第三方网站访问到用户 Cookie 106 | - 阻止第三方网站请求接口 107 | - 请求时附带验证信息,比如验证码或者 Token 108 | 109 | ##### SameSite 110 | 111 | 可以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。 112 | 113 | ##### 验证 Referer 114 | 对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。 115 | 116 | ##### Token 117 | 服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。 118 | 119 | 120 | #### 点击劫持 121 | > ###### 什么是点击劫持?如何防范点击劫持? 122 | 123 | 点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。 124 | 125 | ##### X-FRAME-OPTIONS 126 | X-FRAME-OPTIONS 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用 iframe 嵌套的点击劫持攻击。 127 | 128 | 该响应头有三个值可选,分别是 129 | 130 | - DENY,表示页面不允许通过 iframe 的方式展示 131 | - SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示 132 | - ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示 133 | 134 | 135 | ##### JS 防御 136 | 对于某些远古浏览器来说,并不能支持上面的这种方式,那我们只有通过 JS 的方式来防御点击劫持了。 137 | 138 | ``` 139 | 140 | 145 | 146 | 147 | 155 | 156 | ``` 157 | 158 | 以上代码的作用就是当通过 iframe 的方式加载页面时,攻击者的网页直接不显示所有内容了 159 | 160 | #### 中间人攻击 161 | 162 | > ###### 什么是中间人攻击?如何防范中间人攻击? 163 | 164 | 中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。 165 | 166 | 通常来说不建议使用公共的 Wi-Fi,因为很可能就会发生中间人攻击的情况。如果你在通信的过程中涉及到了某些敏感信息,就完全暴露给攻击方了。 167 | 168 | 当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。HTTPS 就可以用来防御中间人攻击,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。 -------------------------------------------------------------------------------- /doc/high/优化.md: -------------------------------------------------------------------------------- 1 | #### 图片优化 2 | 3 | 4 | 图片加载优化: 5 | 6 | - 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 7 | - 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 8 | - 小图使用 base64 格式 9 | - 将多个图标文件整合到一张图片中(雪碧图) 10 | 11 | 选择正确的图片格式: 12 | 13 | - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 14 | - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 15 | - 照片使用 JPEG 16 | 17 | #### DNS 预解析 18 | DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。 19 | 20 | ``` 21 | 22 | ``` 23 | 24 | #### 节流 25 | 26 | 考虑一个场景,滚动事件中会发起网络请求,但是我们并不希望用户在滚动过程中一直发起请求,而是隔一段时间发起一次,对于这种情况我们就可以使用节流。 27 | 28 | 理解了节流的用途,我们就来实现下这个函数 29 | 30 | 31 | ```js 32 | // func是用户传入需要防抖的函数 33 | // wait是等待时间 34 | const throttle = (func, wait = 50) => { 35 | // 上一次执行该函数的时间 36 | let lastTime = 0 37 | return function(...args) { 38 | // 当前时间 39 | let now = +new Date() 40 | // 将当前时间和上一次执行函数时间对比 41 | // 如果差值大于设置的等待时间就执行函数 42 | if (now - lastTime > wait) { 43 | lastTime = now 44 | func.apply(this, args) 45 | } 46 | } 47 | } 48 | 49 | setInterval( 50 | throttle(() => { 51 | console.log(1) 52 | }, 500), 53 | 1 54 | ) 55 | ``` 56 | 57 | 58 | #### 防抖 59 | 考虑一个场景,有一个按钮点击会触发网络请求,但是我们并不希望每次点击都发起网络请求,而是当用户点击按钮一段时间后没有再次点击的情况才去发起网络请求,对于这种情况我们就可以使用防抖。 60 | 61 | 理解了防抖的用途,我们就来实现下这个函数 62 | 63 | ```js 64 | // func是用户传入需要防抖的函数 65 | // wait是等待时间 66 | const debounce = (func, wait = 50) => { 67 | // 缓存一个定时器id 68 | let timer = 0 69 | // 这里返回的函数是每次用户实际调用的防抖函数 70 | // 如果已经设定过定时器了就清空上一次的定时器 71 | // 开始一个新的定时器,延迟执行用户传入的方法 72 | return function(...args) { 73 | if (timer) clearTimeout(timer) 74 | timer = setTimeout(() => { 75 | func.apply(this, args) 76 | }, wait) 77 | } 78 | } 79 | ``` 80 | 81 | #### 预加载 82 | 83 | 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。 84 | 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。 85 | 86 | 预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载 87 | 88 | ``` 89 | 90 | ``` 91 | 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。 92 | 93 | 94 | 95 | #### 预渲染 96 | 97 | 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | 预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染 104 | 105 | #### 懒执行 106 | 懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。 107 | 108 | #### 懒加载 109 | 110 | 懒加载就是将不关键的资源延后加载。 111 | 112 | 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。 113 | 114 | 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。 115 | 116 | #### CDN 117 | CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。 118 | 119 | 因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。 120 | -------------------------------------------------------------------------------- /doc/high/浏览器url到页面渲染流程.md: -------------------------------------------------------------------------------- 1 | #### DNS 2 | 3 | 首先是 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来。 4 | 5 | DNS 的作用就是通过域名查询到具体的 IP。 6 | 7 | 8 | 在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 www.google.com 时,会进行一下操作: 9 | 10 | 11 | - 操作系统会首先在本地缓存中查询 IP 12 | - 没有的话会去系统配置的 DNS 服务器中查询 13 | - 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器 14 | - 然后去该服务器查询 google 这个二级域名 15 | - 接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP 16 | 17 | 18 | 以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。 19 | 20 | ##### TCP握手 21 | 22 | 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。 23 | 24 | 当 TCP 握手结束后就会进行 TLS 握手,然后就开始正式的传输数据。 25 | 26 | ##### 响应html 27 | 28 | 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件。 29 | 30 | 首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错。 31 | 32 | 浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。 33 | 34 | 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行。 35 | 36 | 如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP/2 协议的话会极大的提高多图的下载效率。 37 | 38 | CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西 39 | 40 | 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了。 41 | 42 | 这一部分就是渲染原理中讲解到的内容,可以详细的说明下这一过程。并且在下载文件时,也可以说下通过 HTTP/2 协议可以解决队头阻塞的问题。 43 | 44 | 总的来说这一章节就是带着大家从 DNS 查询开始到渲染出画面完整的了解一遍过程,将之前学习到的内容连接起来。 45 | 46 | 当来这一过程远远不止这些内容,但是对于大部分人能答出这些内容已经很不错了,你如果想了解更加详细的过程,可以阅读这篇文章。 47 | 48 | 49 | ___ 50 | 51 | 更多:https://github.com/skyline75489/what-happens-when-zh_CN -------------------------------------------------------------------------------- /doc/high/浏览器渲染原理.md: -------------------------------------------------------------------------------- 1 | 我们知道执行 JS 有一个 JS 引擎,那么执行渲染也有一个渲染引擎。同样,渲染引擎在不同的浏览器中也不是都相同的。比如在 Firefox 中叫做 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 开发的 2 | 3 | #### 浏览器接收到 HTML 文件并转换为 DOM 树 4 | 5 | 当我们打开一个网页时,浏览器都会去请求对应的 HTML 文件。虽然平时我们写代码时都会分为 JS、CSS、HTML 文件,也就是字符串,但是计算机硬件是不理解这些字符串的,所以在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。 6 | 7 | 当数据转换为字符串以后,浏览器会先将这些字符串通过词法分析转换为标记(token),这一过程在词法分析中叫做标记化(tokenization)。 8 | 9 | 10 | 那么什么是标记呢?这其实属于编译原理这一块的内容了。简单来说,标记还是字符串,是构成代码的最小单位。这一过程会将代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思。 11 | 12 | 当结束标记化后,这些标记会紧接着转换为 Node,最后这些 Node 会根据不同 Node 之前的联系构建为一颗 DOM 树。 13 | 14 | 15 | 在解析 HTML 文件的时候,浏览器还会遇到 CSS 和 JS 文件,这时候浏览器也会去下载并解析这些文件 16 | 17 | 18 | #### 将 CSS 文件转换为 CSSOM 树 19 | 20 | 在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。 21 | 22 | 23 | ```html 24 |
25 | 26 |
27 | 35 | ``` 36 | 37 | 对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平 38 | 39 | 40 | 41 | #### 生成渲染树 42 | 43 | 44 | 当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。 45 | 46 | 在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。 47 | 48 | 49 | 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。对于这一部分的内容因为过于底层,还涉及到了硬件相关的知识,这里就不再继续展开内容了。 50 | 51 | 52 | 53 | #### 为什么操作 DOM 慢 54 | 55 | 因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。 56 | 57 | 58 | > ###### 插入几万个 DOM,如何实现页面不卡顿? 59 | 60 | 对于这道题目来说,首先我们肯定不能一次性把几万个 DOM 全部插入,这样肯定会造成卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM。大部分人应该可以想到通过 requestAnimationFrame 的方式去循环的插入 DOM,其实还有种方式去解决这个问题:虚拟滚动 61 | 62 | 这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。 63 | 64 | 65 | #### 什么情况阻塞渲染 66 | 67 | 首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。 68 | 69 | 然后当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。 70 | 71 | 72 | 当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。 73 | 74 | 当 script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 script 标签放在任意位置。 75 | 76 | 对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。 77 | 78 | 79 | 80 | #### 重绘(Repaint)和回流(Reflow) 81 | 82 | 重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能 83 | 84 | - 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘 85 | - 回流是布局或者几何属性需要改变就称为回流 86 | 87 | 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。 88 | 89 | 以下几个动作可能会导致性能问题: 90 | 91 | - 改变 window 大小 92 | - 改变字体 93 | - 添加或删除样式 94 | - 文字改变 95 | - 定位或者浮动 96 | - 盒模型 97 | 98 | 并且很多人不知道的是,重绘和回流其实也和 Eventloop 有关 99 | 100 | 101 | - 当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。 102 | - 然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。 103 | - 判断是否触发了 media query 104 | - 更新动画并且发送事件 105 | - 判断是否有全屏操作事件 106 | - 执行 requestAnimationFrame 回调 107 | - 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 108 | - 更新界面 109 | - 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。 110 | 111 | #### 减少重绘和回流 112 | 113 | - 使用 transform 替代 top 114 | 115 | ```html 116 |
117 | 126 | 132 | ``` 133 | - 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局) 134 | - 不要把节点的属性值放在一个循环里当成循环里的变量 135 | 136 | ```js 137 | for(let i = 0; i < 1000; i++) { 138 | // 获取 offsetTop 会导致回流,因为需要去获取正确的值 139 | console.log(document.querySelector('.test').style.offsetTop) 140 | } 141 | ``` 142 | 143 | - 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局 144 | - 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame 145 | - CSS 选择符从右往左匹配查找,避免节点层级过多 146 | - 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层 147 | 148 | 设置节点为图层的方式有很多,我们可以通过以下几个常用属性可以生成新图层 149 | 150 | - will-change 151 | - video、iframe 标签 152 | 153 | 154 | 155 | > ###### 在不考虑缓存和优化网络协议的前提下,考虑可以通过哪些方式来最快的渲染页面,也就是常说的关键渲染路径,这部分也是性能优化中的一块内容。 156 | 157 | 158 | 当发生 DOMContentLoaded 事件后,就会生成渲染树,生成渲染树就可以进行渲染了,这一过程更大程度上和硬件有关系了 159 | 160 | 提示如何加速: 161 | 162 | - 从文件大小考虑 163 | - 从 script 标签使用上来考虑 164 | - 从 CSS、HTML 的代码书写上来考虑 165 | - 从需要下载的内容是否需要在首屏使用上来考虑 166 | 167 | -------------------------------------------------------------------------------- /doc/mes.md: -------------------------------------------------------------------------------- 1 | ```js 2 | import { Component, OnInit, Input, Output, EventEmitter, ElementRef } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-todo-header', 6 | templateUrl: './todo-header.component.html', 7 | styleUrls: ['./todo-header.component.css'] 8 | }) 9 | export class TodoHeaderComponent implements OnInit { 10 | inputValue = ''; 11 | 12 | // 绑入属性 13 | @Input() placeholder = 'What needs to be done?'; 14 | @Input() delay = 300; 15 | 16 | // 唤起父组件 17 | @Output() onEnterUp = new EventEmitter(); 18 | @Output() textChanges = new EventEmitter(); 19 | 20 | constructor(private elementRef: ElementRef) { } 21 | 22 | ngOnInit() { 23 | } 24 | 25 | enterUp() { 26 | this.onEnterUp.emit('你好 我是子组件'); 27 | this.textChanges.emit(this.inputValue); 28 | this.inputValue = ''; 29 | } 30 | 31 | childs() { 32 | alert('父组件传入子组件'); 33 | } 34 | 35 | } 36 | 37 | 38 | ``` 39 | 40 | ```html 41 | 42 |
43 |

Todos

44 | 50 |
51 | 52 | 53 | ``` 54 | 55 | ```js 56 | 57 | import { Component, OnInit, ViewChild } from '@angular/core'; 58 | import { TodoService } from './todo.service'; 59 | import { Todo } from './todo.model'; 60 | 61 | import { TodoHeaderComponent } from './todo-header/todo-header.component'; 62 | 63 | @Component({ 64 | selector: 'app-todo', 65 | templateUrl: './todo.component.html', 66 | styleUrls: ['./todo.component.css'], 67 | providers: [TodoService], 68 | }) 69 | export class TodoComponent implements OnInit { 70 | todos: Todo[] = []; 71 | desc = ''; 72 | 73 | // 唤起子组件 74 | @ViewChild(TodoHeaderComponent) child: TodoHeaderComponent; 75 | 76 | constructor(private service: TodoService) { } 77 | 78 | // 初始化函数 79 | ngOnInit() { 80 | console.log(this.child.childs()); 81 | this.child.placeholder = '我要改变子组件的placeholder'; 82 | } 83 | 84 | add() { 85 | // this.todos.push({id: '1', desc: this.desc, completed: false}); 86 | this.todos = this.service.add(this.desc); 87 | this.desc = ''; 88 | console.log(this.todos); 89 | } 90 | 91 | addTodo() { 92 | this.service 93 | .addTodo(this.desc) 94 | .then(todo => { 95 | this.todos = [...this.todos, todo]; 96 | this.desc = ''; 97 | console.log(todo); 98 | }); 99 | } 100 | 101 | addTodos(ev) { 102 | console.log(ev); // 子组件发来信息 103 | } 104 | 105 | onTextChanges(ev) { 106 | console.log(ev); 107 | } 108 | 109 | } 110 | 111 | 112 | 113 | ``` 114 | 115 | 116 | 117 | ```html 118 | 119 |

120 | todo works! 121 |

122 | 123 |
124 | 125 |
    126 |
  • {{ todo.desc }}
  • 127 |
128 |
129 | 130 | 135 | 136 | 137 | 138 | 139 | 140 | ``` -------------------------------------------------------------------------------- /doc/mod.md: -------------------------------------------------------------------------------- 1 | ```js 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { HttpModule } from '@angular/http'; 6 | import { RouterModule } from '@angular/router'; 7 | 8 | // 组件加载 9 | import { AppComponent } from './app.component'; 10 | import { TestComponent } from './test/test.component'; 11 | import { TodoComponent } from './todo/todo.component'; 12 | import { TodoHeaderComponent } from './todo/todo-header/todo-header.component'; 13 | 14 | // 业务 15 | import { AuthService } from './core/auth.service'; 16 | 17 | // 路由配置 18 | import { Router } from './app.router'; 19 | 20 | // web内存服务 21 | import { InMemoryWebApiModule, HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; 22 | import { InMemoryTodoDbService } from './todo/todo.server'; 23 | 24 | 25 | 26 | const routeConfig = [ 27 | { 28 | path: 'login', 29 | component: TestComponent, 30 | }, 31 | { 32 | path: '*', // 当路由配置中没有的时候,会跳转以下 33 | redirectTo: '', // 跳转 34 | // pathMatch: 'full', // 完全匹配 35 | } 36 | ]; 37 | 38 | @NgModule({ 39 | declarations: [ 40 | AppComponent, 41 | TestComponent, 42 | TodoComponent, 43 | TodoHeaderComponent, 44 | ], 45 | imports: [ 46 | BrowserModule, 47 | FormsModule, 48 | HttpModule, 49 | // InMemoryWebApiModule.forRoot(InMemoryTodoDbService), 50 | // RouterModule.forRoot(routeConfig), 51 | Router, 52 | ], 53 | providers: [ 54 | {provide: 'auth', useClass: AuthService} 55 | ], 56 | bootstrap: [AppComponent] 57 | }) 58 | export class AppModule { } 59 | 60 | 61 | 62 | ``` -------------------------------------------------------------------------------- /doc/mysql/mysql事务.md: -------------------------------------------------------------------------------- 1 | mysql事务: 2 | 3 | 概念: 事务指逻辑上的一组操作,组成这种操作的各个单元,要不全部成功,要不全部失败。 4 | 5 | 为了在执行sql时部分执行失败而导致数据库乱掉,在执行成功时在提交整个事务,否则就退回重新执行 6 | 7 | 8 | 9 | 开启事务: start transaction; 10 | 11 | update .... 12 | 13 | select .... 14 | 15 | 开启事务之后 数据库的数据不会马上改变 --- 可以打开另一个终端登进去查看 16 | 17 | 18 | rollback; 回滚事务 --- 撤销指定的sql语句 --- 之前执行的都会被撤销 开启事务到回滚 ---- 只能是 ---insert update delete --语句 19 | 20 | 21 | Savepoint savepoint_name ;; ---创建保留点 --- 处理事务中设置的保留点 22 | 23 | rollback to savepoint_name; --- 会退到保留点 24 | 25 | 26 | 27 | commit; ---- 提交事务 -- 提交为存储的事务 ---- 注意提交之后就 存储进去了 没法回滚到以前数据了--- 28 | 29 | 提交完事务如果要继续,需要开启新的事务 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/mysql/填坑记录.md: -------------------------------------------------------------------------------- 1 | ---------填坑记录 2 | 3 | 查看所有进程: ps -A 4 | 5 | 查看指定端口号进程: sudo lsof -i :27017 6 | 7 | 根据进程名称: ps -ef | grep nginx 8 | 9 | 根据PID杀死进程: sudo kill -9859 10 | 11 | 12 | 13 | ## 数据库遇到的坑 14 | 15 | * The server requested authentication method unknown to the client 16 | 17 | ```php 18 | 19 | sudo vim /etc/mysql/my.cnf 在最下面新增如下代码: 20 | 21 | [mysqld] 22 | default_authentication_plugin= mysql_native_password 23 | 24 | 25 | //创建新的用户 使用创建的用户连接 26 | 27 | CREATE USER 'admin'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword'; 28 | GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION; 29 | CREATE USER 'admin'@'%' IDENTIFIED WITH mysql_native_password BY 'yourpass'; 30 | GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION; 31 | # 32 | CREATE DATABASE IF NOT EXISTS `yourdb` COLLATE 'utf8_general_ci' ; 33 | GRANT ALL ON `yourdb`.* TO 'admin'@'%' ; 34 | FLUSH PRIVILEGES ; 35 | 36 | ``` 37 | 38 | ----------------------- 密码忘记 ? 39 | 40 | [root@controller ~]# service mysqld stop 41 | [root@controller ~]# mysqld_safe --skip-grant-table & 42 | [root@controller ~]# mysql 43 | mysql> select user,host,password from mysql.user; 44 | +----------+-----------------------+-------------------------------------------+ 45 | | user | host | password | 46 | +----------+-----------------------+-------------------------------------------+ 47 | | root | localhost | *A4B6157319038724E3560894F7F932C8886EBFCF | 48 | | root | localhost.localdomain | | 49 | | root | 127.0.0.1 | | 50 | | root | ::1 | | 51 | | | localhost | | 52 | | | localhost.localdomain | | 53 | | root | % | *23AE809DDACAF96AF0FD78ED04B6A265E05AA257 | 54 | +----------+-----------------------+-------------------------------------------+ 55 | mysql> update mysql.user set password=password("123") where user="root" and host="localhost"; 56 | mysql> flush privileges; 57 | mysql> exit 58 | [root@controller ~]# service mysqld restart 59 | [root@controller ~]# mysql -uroot -p123 60 | 61 | 62 | 63 | 64 | ------------------- 删库 ? 65 | 66 | 67 | 删除与权限相关的库mysql,所有的授权信息都丢失,主要用于测试数据库或者刚刚建库不久没有授权数据的情况(从删库到跑路) 68 | [root@controller ~]# rm -rf /var/lib/mysql/mysql 69 | [root@controller ~]# service mysqld restart 70 | [root@controller ~]# mysql 71 | 72 | 73 | 74 | 75 | ----------------- group by 不能用? 76 | 77 | 78 | ONLY_FULL_GROUP_BY的意思是:对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中,也就是说查出来的列必须在group by后面出现否则就会报错,或者这个字段出现在聚合函数里面。 79 | 80 | 解决方法: 81 | 命令行输入: 82 | set sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; 83 | 84 | 85 | ------------------- 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /doc/mysql/理论篇.md: -------------------------------------------------------------------------------- 1 | -------mysql 开源 免费 跨平台 --- 关系型数据库 --- 存在磁盘里 (非关系型数据库存在内存里) 2 | 3 | 1.数据库是存储数据的仓库 4 | 5 | 6 | 7 | 8 | DBMS: 数据库管理系统软件 ---- 操作和管理数据库 9 | 10 | DBS: 数据库系统 11 | 12 | 常见的数据库管理软件: 13 | 14 | oracle-- 甲骨文 15 | sql server --- 微软 16 | db2 ---- IBM --- 大型企业、银行 17 | 18 | 19 | 可以创建多个数据库 ,每个数据库可以创建很多表 20 | 21 | 22 | 23 | 常用操作: 24 | 25 | 26 | //1. 服务启动 : mysql.server start 27 | //2. 服务停止 : mysql.server stop 28 | //3. 登录数据库 : mysql -u 用户名 -p -> 输入密码登入shell (我本地数据库: u:admin p:yourpass) 29 | mysql -h 主机地址 -P(大写) 端口号 -u username -p pass 不写默认本机 30 | 31 | //4. 修改密码: show DATABASES; 显示数据库; 32 | // use mysql; 使用mysql数据库 33 | // update user set password=password('新密码') WHERE User='root'; 34 | //5. 退出shell: exit; 35 | 36 | 37 | 38 | // DDL 操作 39 | 40 | //1. 创建**字符的数据库 : CREATE DATABASE 数据库名 character set 字符名; (默认是utf8) 41 | //2. 查看创建的数据库定义信息: show create database 数据库名; 42 | //3. 删除数据库: DROP DATABASE 数据库名称; 43 | //4. 修改数据库: alter database 数据库名 set 字符名 ....; 44 | //5. 查看当前使用的数据库: select database(); 45 | //6. 切换数据库:use 数据库名称; 46 | 47 | 48 | 49 | 50 | //2. 创建表: 51 | 52 | // 语法: create table tablename ( 字段 字段类型 ); 53 | 54 | // 例子: 55 | // create table emp( 56 | // id int, 57 | // name varchar(50), 58 | // gender varchar(10), 59 | // birthday date, 60 | // entry_date date, 61 | // job varchar(100), 62 | // salary double, 63 | // resume varchar(200) 64 | // ); 65 | 66 | 67 | //3. 查看创建的表: 68 | // SHOW TABLES; 69 | 70 | //4. 查看表的字段信息: 71 | // DESC 表名; 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /doc/r-v.md: -------------------------------------------------------------------------------- 1 | #### MVVM 2 | 3 | > ###### 什么是 MVVM?比之 MVC 有什么区别? 4 | 5 | 首先先申明一点,不管是 React 还是 Vue,它们都不是 MVVM 框架,只是有借鉴 MVVM 的思路。文中拿 Vue 举例也是为了更好地理解 MVVM 的概念 6 | 7 | 8 | 但是 MVC 有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。 9 | 10 | 在 MVVM 架构中,引入了 ViewModel 的概念。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。 11 | 12 | 以 Vue 框架来举例,ViewModel 就是组件的实例。View 就是模板,Model 的话在引入 Vuex 的情况下是完全可以和组件分离的。 13 | 14 | 除了以上三个部分,其实在 MVVM 中还引入了一个隐式的 Binder 层,实现了 View 和 ViewModel 的绑定。 15 | 16 | 同样以 Vue 框架来举例,这个隐式的 Binder 层就是 Vue 通过解析模板中的插值和指令从而实现 View 与 ViewModel 的绑定。 17 | 18 | 对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View 与 ViewModel 绑定起来,而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓。 19 | 20 | 21 | 22 | #### Virtual DOM 23 | 24 | > ###### 什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快? 25 | 26 | 那么相较于 DOM 来说,操作 JS 对象会快很多,并且我们也可以通过 JS 来模拟 DOM 27 | 28 | ```js 29 | const ul = { 30 | tag: 'ul', 31 | props: { 32 | class: 'list' 33 | }, 34 | children: { 35 | tag: 'li', 36 | children: '1' 37 | } 38 | } 39 | ``` 40 | 41 | 上述代码对应的 DOM 就是 42 | 43 | ``` 44 |
    45 |
  • 1
  • 46 |
47 | ``` 48 | 那么既然 DOM 可以通过 JS 对象来模拟,反之也可以通过 JS 对象来渲染出对应的 DOM。当然了,通过 JS 来模拟 DOM 并且渲染对应的 DOM 只是第一步,难点在于如何判断新旧两个 JS 对象的最小差异并且实现局部更新 DOM。 49 | 50 | 首先 DOM 是一个多叉树的结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),这个复杂度肯定是不能接受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对比差异。 实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动 DOM 元素。 所以判断差异的算法就分为了两步 51 | 52 | - 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异 53 | - 一旦节点有子元素,就去判断子元素是否有不同 54 | 55 | 在第一步算法中我们需要判断新旧节点的 tagName 是否相同,如果不相同的话就代表节点被替换了。如果没有更改 tagName 的话,就需要判断是否有子元素,有的话就进行第二步算法。 56 | 57 | 在第二步算法中,我们需要判断原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动。 58 | 59 | ```js 60 | // 假设这里模拟一个 ul,其中包含了 5 个 li 61 | [1, 2, 3, 4, 5] 62 | // 这里替换上面的 li 63 | [1, 2, 5, 4] 64 | ``` 65 | 66 | 从上述例子中,我们一眼就可以看出先前的 ul 中的第三个 li 被移除了,四五替换了位置。 67 | 68 | 那么在实际的算法中,我们如何去识别改动的是哪个节点呢?这就引入了 key 这个属性,想必大家在 Vue 或者 React 的列表中都用过这个属性。这个属性是用来给每一个节点打标志的,用于判断是否是同一个节点。 69 | 70 | 当然在判断以上差异的过程中,我们还需要判断节点的属性是否有变化等等。 71 | 72 | 当我们判断出以上的差异后,就可以把这些差异记录下来。当对比完两棵树以后,就可以通过差异去局部更新 DOM,实现性能的最优化。 73 | 74 | 另外再来回答「为什么 Virtual DOM 比原生 DOM 快」这个问题。首先这个问题得分场景来说,如果无脑替换所有的 DOM 这种场景来说,Virtual DOM 的局部更新肯定要来的快。但是如果你可以人肉也同样去局部替换 DOM,那么 Virtual DOM 必然没有你直接操作 DOM 来的快,毕竟还有一层 diff 算法的损耗。 75 | 76 | 当然了 Virtual DOM 提高性能是其中一个优势,其实最大的优势还是在于: 77 | 78 | - 将 Virtual DOM 作为一个兼容层,让我们还能对接非 Web 端的系统,实现跨端开发。 79 | - 同样的,通过 Virtual DOM 我们可以渲染到其他的平台,比如实现 SSR、同构渲染等等。 80 | - 实现组件的高度抽象化 81 | 82 | 83 | #### 路由原理 84 | > 前端路由原理?两种实现方式有什么区别? 85 | 86 | 前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式 87 | 88 | - Hash 模式 89 | - History 模式 90 | 91 | 92 | ##### Hash 模式 93 | 94 | www.test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化时,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com。 95 | 96 | ```js 97 | window.addEventListener('hashchange', () => { 98 | // ... 具体逻辑 99 | }) 100 | ``` 101 | 102 | Hash 模式相对来说更简单,并且兼容性也更好。 103 | 104 | 105 | ##### History 模式 106 | 107 | History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改变 URL。 108 | 109 | 通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。 110 | 111 | ```js 112 | // 新增历史记录 113 | history.pushState(stateObject, title, URL) 114 | // 替换当前历史记录 115 | history.replaceState(stateObject, title, URL) 116 | ``` 117 | 118 | 当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件 119 | 120 | ```js 121 | window.addEventListener('popstate', e => { 122 | // e.state 就是 pushState(stateObject) 中的 stateObject 123 | console.log(e.state) 124 | }) 125 | ``` 126 | 127 | ##### 两种模式对比 128 | 129 | - Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL 130 | - History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串 131 | - Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候 132 | 133 | 134 | #### Vue 和 React 之间的区别 135 | 136 | Vue 的表单可以使用 v-model 支持双向绑定,相比于 React 来说开发上更加方便,当然了 v-model 其实就是个语法糖,本质上和 React 写表单的方式没什么区别。 137 | 138 | 改变数据方式不同,Vue 修改状态相比来说要简单许多,React 需要使用 setState 来改变状态,并且使用这个 API 也有一些坑点。并且 Vue 的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是 React 还是需要用户手动去优化这方面的问题。 139 | 140 | React 16以后,有些钩子函数会执行多次,这是因为引入 Fiber 的原因,这在后续的章节中会讲到。 141 | 142 | React 需要使用 JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过 JS 来控制页面,更加的灵活。Vue 使用了模板语法,相比于 JSX 来说没有那么灵活,但是完全可以脱离工具链,通过直接编写 render 函数就能在浏览器中运行。 143 | 144 | 在生态上来说,两者其实没多大的差距,当然 React 的用户是远远高于 Vue 的。 145 | 146 | 在上手成本上来说,Vue 一开始的定位就是尽可能的降低前端开发的门槛,然而 React 更多的是去改变用户去接受它的概念和思想,相较于 Vue 来说上手成本略高。 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /doc/ser.md: -------------------------------------------------------------------------------- 1 | ```js 2 | 3 | import { Injectable } from '@angular/core'; 4 | import { Http, Headers } from '@angular/http'; 5 | import { UUID } from 'angular2-uuid'; 6 | // import 'rxjs/add/operator/toPromise'; 7 | 8 | import {Todo} from './todo.model'; 9 | import { resolve } from 'url'; 10 | import { reject } from 'q'; 11 | 12 | @Injectable({ 13 | providedIn: 'root' 14 | }) 15 | export class TodoService { 16 | 17 | // 定义你的假WebAPI地址,这个定义成什么都无所谓 18 | // 只要确保是无法访问的地址就好 19 | private api_url = 'api/todos'; 20 | private headers = new Headers({'Content-Type': 'application/json'}); 21 | 22 | todos: Todo[] = []; 23 | 24 | constructor(private http: Http) { } 25 | 26 | test() { 27 | this.http 28 | .get('http://localhost:3000/posts') 29 | .toPromise() 30 | .then(res => { 31 | alert(JSON.stringify(res.json())); 32 | }) 33 | .catch(this.handleError); 34 | } 35 | 36 | // POST /todos 37 | addTodo(desc: string): Promise { 38 | this.test(); 39 | const todo = { 40 | id: UUID.UUID(), 41 | desc: desc, 42 | completed: false 43 | }; 44 | return this.http 45 | .post(this.api_url, JSON.stringify(todo), {headers: this.headers}) 46 | .toPromise() 47 | .then(res => { 48 | return res.json() as Todo; // 以Todo的形式返回 49 | }) 50 | .catch(this.handleError); 51 | } 52 | 53 | private handleError(error: any): Promise { 54 | console.error('An error occurred', error); 55 | return Promise.reject(error.message || error); 56 | } 57 | 58 | add(todoItem: string): Todo[] { 59 | const todo = { 60 | id: UUID.UUID(), 61 | desc: todoItem, 62 | completed: false 63 | }; 64 | this.todos.push(todo); 65 | return this.todos; 66 | } 67 | 68 | } 69 | 70 | 71 | ``` -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
正在改版中, 为了更好的体验, 还请谅解 ~~~
11 | 12 | 13 | -------------------------------------------------------------------------------- /moduleDemo/day1/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/moduleDemo/day1/1.jpg -------------------------------------------------------------------------------- /moduleDemo/day1/1.txt: -------------------------------------------------------------------------------- 1 |

我是txt里的文字

-------------------------------------------------------------------------------- /moduleDemo/day1/fs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 1 3 | * fs : 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); 8 | const fs = require("fs"); 9 | 10 | const global_path = './day1/'; 11 | 12 | http.createServer((req,res) => { 13 | 14 | res.writeHead(200, {"Content-Type": "text/html;charset=UTF8"}); 15 | 16 | // 读取文件 17 | fs.readFile(`${global_path}/1.txt`, {"charset": "utf-8"}, (err,data) =>{ // 异步执行 执行顺序上外部代码先执行 回调是I/O操作完成后调用 18 | if (err) throw err; 19 | res.end(data); 20 | }) 21 | 22 | // 创建文件夹 异步执行 23 | fs.mkdir(`${global_path}upload/image`,(err) => { // 注意: 在创建image之前 upload要存在 不能直接写 /upload/image 24 | if (err) console.log(err); 25 | }); 26 | 27 | //检查文件 可以是文件夹或者可执行文件 28 | fs.stat(`${global_path}/upload/image`,(err,stats) => { 29 | if(err) console.log(err) 30 | 31 | // 获取文件的大小; 32 | console.log(stats.size); 33 | 34 | // 获取文件最后一次访问的时间; 35 | console.log(stats.atime.toLocaleString()); 36 | // 文件创建的时间; 37 | console.log(stats.birthtime.toLocaleString()); 38 | // 文件最后一次修改时间; 39 | console.log(stats.mtime.toLocaleString()); 40 | // 状态发生变化的时间; 41 | console.log(stats.ctime.toLocaleString()) 42 | // 判断是否是目录;是返回true;不是返回false; 43 | console.log(stats.isFile()) 44 | // 判断是否四文件夹 45 | console.log(stats.isDirectory()); // true 46 | 47 | 48 | // res.end("我是先调用的"); // 先调用回调/// 此时已经结束了 程序进行 并不会执行上边读取 txt文件的回调 49 | // 这里最好不要 res.end 结束请求 因为可能有其他已经执行的回调还没有调用 50 | }) 51 | 52 | // 查看文件夹下的所有文件 文件夹只会显示文件夹不会显示内部文件或者说是不会吧嵌套的文件夹或文件输出 53 | fs.readdir('./day1',(err,files) => { 54 | if (err) console.log(err); 55 | console.log(files) 56 | // 返回 57 | // [ '1.jpg', 58 | // '1.txt', 59 | // 'fs.js', 60 | // 'hello.js', 61 | // 'http.js', 62 | // 'index.html', 63 | // 'route.js', 64 | // 'upload', 65 | // 'user.html' ] 66 | 67 | 68 | // 迭代文件夹目录 : 异步变同步 (异步执行会导致某些检测直接跳过) 69 | let dirArr = []; // 存储文件 70 | 71 | (function iterator(i){ 72 | // 迭代结束 73 | if(i == files.length) { 74 | return 75 | } 76 | 77 | fs.stat(`./day1/${files[i]}`,(err,stats) => { 78 | if (err) console.log(err); 79 | if(stats.isDirectory()) { 80 | // dirArr.push(files[i]) 81 | fs.readdir(`./day1/${files[i]}`,(err,file_) => { 82 | if (err) console.log(err); 83 | console.log(file_) 84 | }) 85 | } 86 | iterator(i+1); // 遍历 87 | console.log(dirArr) 88 | }) 89 | 90 | 91 | })(0) 92 | }) 93 | 94 | 95 | }).listen(3000,(err) => { 96 | if (err) throw err; 97 | console.log('serer is starting on port 3000 O(∩_∩)O哈哈~'); 98 | }); 99 | 100 | 101 | 102 | // 补充: 103 | 104 | //遍历目录 (推荐使用) 105 | const path = require('path'); 106 | 107 | const forFile = function(dir) { 108 | let results = [ path.resolve(dir) ]; 109 | const files_ = fs.readdirSync(dir,'utf-8') 110 | 111 | files_.forEach(function(e) { 112 | var file = path.resolve(dir,e); 113 | 114 | const stats = fs.statSync(file); 115 | 116 | if(stats.isFile()) { 117 | results.push(file); 118 | console.log(file); 119 | }else if(stats.isDirectory()) { 120 | results = results.concat(forFile(file)); 121 | console.log(file); 122 | } 123 | }) 124 | 125 | return results; 126 | } 127 | 128 | console.log(forFile('./day1')) 129 | // [ '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1', 130 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/1.jpg', 131 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/1.txt', 132 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/fs.js', 133 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/hello.js', 134 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/http.js', 135 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/index.html', 136 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/route.js', 137 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/upload', 138 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/upload/image', 139 | // '/Users/qiuchenglei/node/NodeJS进阶学习/moduleDemo/day1/user.html' ] 140 | 141 | 142 | // 判断文件是否存在 143 | fs.access(`${global_path}/1.jpg`,(err) => { 144 | if(err) { 145 | throw err; 146 | } 147 | 148 | console.log('文件存在') 149 | }) 150 | 151 | 152 | 153 | 154 | //删除文件 155 | // fs.unlink('./第一张图片.jpg',(err) => { 156 | // //if(err) throw err; 157 | // }) 158 | 159 | 160 | // //删除目录 161 | // fs.rmdir('./test1',(err) => { 162 | // console.log(err); 163 | // }) 164 | 165 | 166 | 167 | // 文件重命名 可以是目录也可以是文件 168 | 169 | // fs.rename('./1.jpg','2.jpg',(err) => { 170 | // console.log('yes') 171 | // }) 172 | 173 | 174 | 175 | // 获取文件拓展名 176 | console.log(path.extname("sdad/sdada/index.html")); 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /moduleDemo/day1/hello.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 1 3 | * 初识Node 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); 8 | const fs = require("fs"); 9 | 10 | let res_ = (res,page) => fs.readFile(page, (err,data) => { 11 | if (err) throw err; 12 | res.writeHead(200, {"Content-Type": "text/html;charset=UFT-8"}); 13 | res.end(data); 14 | }); 15 | 16 | // node中没有web容器的概念,每一次的请求都要通过路由来处理 17 | 18 | http.createServer((req,res) => { 19 | 20 | //比如:读取一张图片 21 | if(req.url == "/1.jpg") { 22 | fs.readFile("1.jpg", (err,data) => { 23 | if (err) throw err; 24 | res.writeHead(200, {"Content-Type": "image/jpg"}); 25 | res.end(data); 26 | }); 27 | }else { 28 | req.url == "/" ? res_.call(this,res,"index.html") : res_.call(this,res,"user.html"); 29 | } 30 | 31 | }).listen(3000,(err) => { 32 | if (err) throw err; 33 | console.log('serer is starting on port 3001 O(∩_∩)O哈哈~'); 34 | }); 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /moduleDemo/day1/http.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 1 3 | * http : 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); // http 模块 8 | const url = require("url"); // url 9 | const qs = require("querystring"); // 字符串查询模块 10 | 11 | 12 | http.createServer((req,res) => { 13 | 14 | // 设置请求头 15 | //res.setHeader({"Content-Type": "text/html;charset=utf-8"}) 16 | 17 | // 返回请求头 18 | res.writeHead(200,{"Content-Type": "text/html;charset=utf-8"}); 19 | // .css => text/css .jpg/.png => image/jpeg&png .js . 20 | 21 | 22 | 23 | // 输出 24 | res.write("

hello nodejs

") 25 | 26 | // 两个是对象 一个是请求 一个是响应 27 | // console.log(req) 28 | // console.log(res) 29 | 30 | //req #代表锚点 它后边的hash不会触发网络请求 所以后边携带的参数都不会发送到服务器 31 | res.write(`${req.url}
`) 32 | 33 | 34 | // 对url的解析 req.url #后边的值不会获取到 35 | 36 | res.write(`${url.parse("https://www.baidu.com/s?wd=%23rsv_20").host}
`); // 输出 host 主机/域名部分 37 | res.write(`${url.parse(req.url).pathname}
`); // 输出url地址部分 不携带参数 38 | res.write(`${url.parse(req.url).query}
`); // 输出参数部分 a=sada&b=sdjal982 39 | res.write(`${url.parse("/adasdada/inde.html#13131").hash}
`); // 输出#后边的参数 40 | res.write(`${url.parse(req.url).search}
`);// 输出?问号后边部分 a=sada&b=sdjal982 41 | // 其他的还有 origin port password protocol协议 42 | 43 | // 参数的解析 ?后边携带的参数 44 | let json = JSON.stringify(url.parse(req.url,true)); 45 | res.write(`${json.query}


`) // 会解析成一个对象 获取参数直接加上 query.name 46 | 47 | //Object 48 | // { 49 | // "protocol": null, 50 | // "slashes": null, 51 | // "auth": null, 52 | // "host": null, 53 | // "port": null, 54 | // "hostname": null, 55 | // "hash": null, 56 | // "search": "?a=1&b=2", 57 | // "query": { 58 | // "a": "1", 59 | // "b": "2" 60 | // }, 61 | // "pathname": "/adasdada/inde.html", 62 | // "path": "/adasdada/inde.html?a=1&b=2", 63 | // "href": "/adasdada/inde.html?a=1&b=2" 64 | // } 65 | 66 | 67 | // 查询字符串模块解析 url参数 68 | let arg = qs.parse('foo=bar&abc=xyz&abc=123'); 69 | console.log(arg) 70 | // { foo: 'bar', abc: [ 'xyz', '123' ] } 71 | // { foo: 'bar', abc: [ 'xyz', '123' ] } 72 | 73 | // 转换 不带参数默认转为下面 74 | //qs.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }); 75 | // 返回 'foo=bar&baz=qux&baz=quux&corge=' 76 | 77 | // 带参数: 1:分隔符 2:之间的引用符号 78 | //qs.stringify({ foo: 'bar', baz: 'qux' }, ';', ':'); 79 | // 返回 'foo:bar;baz:qux' 80 | 81 | 82 | // 解码 默认情况下是utf-8 83 | // let code = qs.parse('w=%D6%D0%CE%C4&foo=bar', null, null,{ decodeURIComponent: gbkDecodeURIComponent }); 84 | // console.log(code); 85 | 86 | 87 | // url转换 .resolve 第一个参数:原来的地址 第二个参数目标地址 88 | res.write(`${url.resolve("127.0.0.1:3000/","/admin")}
`); // 127.0.0.1:/admin 89 | res.write(`${url.resolve("/index.html","username")}
`); // /username 90 | res.write(`${url.resolve("127.0.0.1:3000/index.html","/user")}
`); // 127.0.0.1:/user 91 | 92 | 93 | 94 | // 结束响应 结束响应后边就不能执行代码了 95 | res.end(); 96 | 97 | }).listen(3000,(err) => { 98 | if (err) throw err; 99 | console.log('serer is starting on port 3000 O(∩_∩)O哈哈~'); 100 | }); 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /moduleDemo/day1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hello world 5 | 6 | 7 | 8 |
9 |

hello world

10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /moduleDemo/day1/route.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 1 3 | * 路由初探 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); 8 | 9 | http.createServer((req,res) => { 10 | 11 | res.writeHead(200, {"Content-Type": "text/html;charset=UTF8"}); 12 | 13 | let id = req.url.substr(7) 14 | 15 | if(req.url.substr(0,7) == "/count/") { 16 | if(/^\d{5}$/.test(id)) { // ID 只允许在5位 而且只能是5位数字 17 | res.end(`count is ${id}`); 18 | }else { 19 | res.end('err path') 20 | } 21 | }else if(req.url.substr(0,7) == "/pass_/") { 22 | if(/^\d{5}$/.test(id)) { 23 | res.end(`pass_ is ${id}`); 24 | }else { 25 | res.end('err path') 26 | } 27 | }else { 28 | res.end('err path') 29 | } 30 | 31 | 32 | console.log(id) 33 | 34 | res.end(); 35 | 36 | }).listen(3000,(err) => { 37 | if (err) throw err; 38 | console.log('serer is starting on port 3001 O(∩_∩)O哈哈~'); 39 | }); 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /moduleDemo/day1/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hello world 5 | 6 | 7 | 8 |
9 |

User

10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /moduleDemo/day2/form.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 2 3 | * FORM: npm i -S formidable 中间件 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); 8 | const fs = require("fs"); 9 | const qs = require("querystring"); 10 | const formidable = require('formidable'); 11 | const util = require('util'); 12 | 13 | 14 | let res_ = (res,page) => fs.readFile(page, (err,data) => { 15 | if (err) throw err; 16 | res.writeHead(200, {"Content-Type": "text/html;charset=UFT-8"}); 17 | res.end(data); 18 | }); 19 | 20 | http.createServer((req,res) => { 21 | 22 | if(req.url == "/") { 23 | res_.call(this,res,"./day2/index.html") 24 | }else { 25 | if(req.url == "/admin" && req.method == "POST") { 26 | // parse a file upload 27 | let form = new formidable.IncomingForm(); 28 | form.encoding = 'utf-8'; 29 | 30 | fs.access('./day2/upload',(err) => { 31 | if (err) { 32 | fs.mkdirSync("./day2/upload"); 33 | } 34 | }) 35 | 36 | 37 | form.uploadDir = "./day2/upload"; 38 | 39 | form.parse(req, function(err, fields, files) { 40 | 41 | console.log(files.file.name); 42 | 43 | // 文件改名 新名称必须加上路劲 44 | if(files.file.name) { 45 | fs.rename(`./${files.file.path}`,`./day2/upload/${files.file.name}`,(err) => { 46 | if(err) console.log(err); 47 | }); 48 | } 49 | 50 | res.writeHead(200, {'content-type': 'text/plain'}); 51 | res.write('received upload:\n\n'); 52 | res.end(util.inspect({fields: fields, files: files})); // 工具类 53 | }); 54 | 55 | return; 56 | } 57 | } 58 | 59 | }).listen(3000,(err) => { 60 | if (err) throw err; 61 | console.log('serer is starting on port 3000 O(∩_∩)O哈哈~'); 62 | }); 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /moduleDemo/day2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | post 请求 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /moduleDemo/day2/post.js: -------------------------------------------------------------------------------- 1 | /* 2 | * day 2 3 | * POST: node通过分割post发来的数据 通过事件监听来实现 4 | */ 5 | 'use strict' 6 | 7 | const http = require("http"); 8 | const fs = require("fs"); 9 | const qs = require("querystring"); 10 | 11 | let res_ = (res,page) => fs.readFile(page, (err,data) => { 12 | if (err) throw err; 13 | res.writeHead(200, {"Content-Type": "text/html;charset=UFT-8"}); 14 | res.end(data); 15 | }); 16 | 17 | http.createServer((req,res) => { 18 | 19 | if(req.url == "/") { 20 | res_.call(this,res,"./day2/index.html") 21 | }else { 22 | if(req.url == "/admin" && req.method == "POST") { 23 | let data = ''; 24 | req.on('data',(chunk) => { 25 | data+=chunk; 26 | console.log(data) // 27 | }); 28 | 29 | req.on('end',(err) => { 30 | if(err) console.log(err); 31 | console.log(data.toString()); //{"username":"dadsa","password":"sdada"} 32 | res.writeHead(200, {"Content-Type": "text/html;charset=UFT-8"}); 33 | res.end(data.toString()) 34 | }) 35 | } 36 | } 37 | 38 | }).listen(3000,(err) => { 39 | if (err) throw err; 40 | console.log('serer is starting on port 3000 O(∩_∩)O哈哈~'); 41 | }); 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /moduleDemo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moduledemo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "./node_modules/.bin/supervisor ./day2/form.js" 9 | }, 10 | "author": "qiuchenglei", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "supervisor": "^0.12.0" 14 | }, 15 | "dependencies": { 16 | "formidable": "^1.2.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /static/JS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/JS.gif -------------------------------------------------------------------------------- /static/Jietu20180406-151247.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/Jietu20180406-151247.gif -------------------------------------------------------------------------------- /static/NODE.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/NODE.gif -------------------------------------------------------------------------------- /static/UI.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/UI.gif -------------------------------------------------------------------------------- /static/arrow.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .arrow { 4 | cursor: pointer; 5 | display: block; 6 | height: 5px; 7 | margin: 40px; 8 | position: relative; 9 | width: 27.5px; 10 | } 11 | .arrow.active:before, 12 | .arrow.active:after { 13 | 14 | } 15 | .arrow.active:before { 16 | transform: rotate(0) scaleX(0.5) translateX(0px) translateY(px); 17 | } 18 | .arrow.active:after { 19 | transform: rotate(0) scaleX(0.5) translateX(-0px) translateY(px); 20 | } 21 | .arrow:before, 22 | .arrow:after { 23 | background: none repeat scroll 0 0 #68228B; 24 | border-radius: 0px; 25 | content: " "; 26 | display: block; 27 | height: 2px; 28 | position: absolute; 29 | top: 6px; 30 | transition: all 300ms ease 0s; 31 | width: 20px; 32 | } 33 | .arrow:before { 34 | left: -3px; 35 | transform: rotate(45deg); 36 | } 37 | .arrow:after { 38 | right: -3px; 39 | transform: rotate(-45deg); 40 | } 41 | -------------------------------------------------------------------------------- /static/blog-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-1.gif -------------------------------------------------------------------------------- /static/blog-css3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-css3.gif -------------------------------------------------------------------------------- /static/blog-h5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-h5.gif -------------------------------------------------------------------------------- /static/blog-mvc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-mvc.gif -------------------------------------------------------------------------------- /static/blog-node.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-node.gif -------------------------------------------------------------------------------- /static/blog-ui.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/blog-ui.gif -------------------------------------------------------------------------------- /static/blog_css3.css: -------------------------------------------------------------------------------- 1 | 2 | #apps{ 3 | margin-bottom: 350px; 4 | } 5 | 6 | .heads-css3{ 7 | width: 100%;height: 270px; 8 | margin-bottom: 500px; 9 | } 10 | 11 | @keyframes move_wave { 12 | 0% { 13 | transform: translateX(0) translateZ(0) scaleY(1) 14 | } 15 | 50% { 16 | transform: translateX(-25%) translateZ(0) scaleY(0.55) 17 | } 18 | 100% { 19 | transform: translateX(-50%) translateZ(0) scaleY(1) 20 | } 21 | } 22 | .waveWrapper { 23 | overflow: hidden; 24 | position: absolute; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | top: 0; 29 | } 30 | .waveWrapperInner { 31 | position: absolute; 32 | width: 100%; 33 | overflow: hidden; 34 | height: 100%; 35 | background-image: linear-gradient(to top, #86377b 20%, #27273c 80%); 36 | } 37 | .bgTop { 38 | z-index: 15; 39 | opacity: 0.5; 40 | } 41 | .bgMiddle { 42 | z-index: 10; 43 | opacity: 0.75; 44 | } 45 | .bgBottom { 46 | z-index: 5; 47 | 48 | } 49 | .wave { 50 | position: absolute; 51 | left: 0; 52 | width: 200%; 53 | height: 100%; 54 | background-repeat: repeat no-repeat; 55 | background-position: 0 bottom; 56 | transform-origin: center bottom; 57 | } 58 | .waveTop { 59 | background-size: 50% 100px; 60 | } 61 | .waveAnimation .waveTop { 62 | animation: move-wave 3s; 63 | -webkit-animation: move-wave 3s; 64 | -webkit-animation-delay: 1s; 65 | animation-delay: 1s; 66 | } 67 | .waveMiddle { 68 | background-size: 50% 120px; 69 | } 70 | .waveAnimation .waveMiddle { 71 | animation: move_wave 10s linear infinite; 72 | } 73 | .waveBottom { 74 | background-size: 50% 100px; 75 | } 76 | .waveAnimation .waveBottom { 77 | animation: move_wave 15s linear infinite; 78 | } -------------------------------------------------------------------------------- /static/canvas-mouse.js: -------------------------------------------------------------------------------- 1 | // One of my first experiments, woop! :D 2 | 3 | var SCREEN_WIDTH = window.innerWidth; 4 | var SCREEN_HEIGHT = window.innerHeight; 5 | 6 | var RADIUS = 70; 7 | 8 | var RADIUS_SCALE = 1; 9 | var RADIUS_SCALE_MIN = 1; 10 | var RADIUS_SCALE_MAX = 1.5; 11 | 12 | var QUANTITY = 25; 13 | 14 | var canvas; 15 | var context; 16 | var particles; 17 | 18 | var mouseX = SCREEN_WIDTH * 0.5; 19 | var mouseY = SCREEN_HEIGHT * 0.5; 20 | var mouseIsDown = false; 21 | 22 | function init() { 23 | 24 | canvas = document.getElementById( 'world' ); 25 | 26 | if (canvas && canvas.getContext) { 27 | context = canvas.getContext('2d'); 28 | 29 | // Register event listeners 30 | window.addEventListener('mousemove', documentMouseMoveHandler, false); 31 | window.addEventListener('mousedown', documentMouseDownHandler, false); 32 | window.addEventListener('mouseup', documentMouseUpHandler, false); 33 | document.addEventListener('touchstart', documentTouchStartHandler, false); 34 | document.addEventListener('touchmove', documentTouchMoveHandler, false); 35 | window.addEventListener('resize', windowResizeHandler, false); 36 | 37 | createParticles(); 38 | 39 | windowResizeHandler(); 40 | 41 | setInterval( loop, 1000 / 60 ); 42 | } 43 | } 44 | 45 | function createParticles() { 46 | particles = []; 47 | 48 | for (var i = 0; i < QUANTITY; i++) { 49 | var particle = { 50 | size: 1, 51 | position: { x: mouseX, y: mouseY }, 52 | offset: { x: 0, y: 0 }, 53 | shift: { x: mouseX, y: mouseY }, 54 | speed: 0.01+Math.random()*0.04, 55 | targetSize: 1, 56 | fillColor: '#' + (Math.random() * 0x904040 + 0xaaaaaa | 0).toString(16), 57 | orbit: RADIUS*.5 + (RADIUS * .5 * Math.random()) 58 | }; 59 | 60 | particles.push( particle ); 61 | } 62 | } 63 | 64 | function documentMouseMoveHandler(event) { 65 | mouseX = event.clientX - (window.innerWidth - SCREEN_WIDTH) * .5; 66 | mouseY = event.clientY - (window.innerHeight - SCREEN_HEIGHT) * .5; 67 | } 68 | 69 | function documentMouseDownHandler(event) { 70 | mouseIsDown = true; 71 | } 72 | 73 | function documentMouseUpHandler(event) { 74 | mouseIsDown = false; 75 | } 76 | 77 | function documentTouchStartHandler(event) { 78 | if(event.touches.length == 1) { 79 | event.preventDefault(); 80 | 81 | mouseX = event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * .5;; 82 | mouseY = event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * .5; 83 | } 84 | } 85 | 86 | function documentTouchMoveHandler(event) { 87 | if(event.touches.length == 1) { 88 | event.preventDefault(); 89 | 90 | mouseX = event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * .5;; 91 | mouseY = event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * .5; 92 | } 93 | } 94 | 95 | function windowResizeHandler() { 96 | SCREEN_WIDTH = window.innerWidth; 97 | SCREEN_HEIGHT = window.innerHeight; 98 | 99 | canvas.width = SCREEN_WIDTH; 100 | canvas.height = SCREEN_HEIGHT; 101 | } 102 | 103 | function loop() { 104 | 105 | if( mouseIsDown ) { 106 | RADIUS_SCALE += ( RADIUS_SCALE_MAX - RADIUS_SCALE ) * (0.02); 107 | } 108 | else { 109 | RADIUS_SCALE -= ( RADIUS_SCALE - RADIUS_SCALE_MIN ) * (0.02); 110 | } 111 | 112 | RADIUS_SCALE = Math.min( RADIUS_SCALE, RADIUS_SCALE_MAX ); 113 | 114 | context.fillStyle = 'rgba(255,255,255,0.35)'; 115 | context.fillRect(0, 0, context.canvas.width, context.canvas.height); 116 | 117 | for (i = 0, len = particles.length; i < len; i++) { 118 | var particle = particles[i]; 119 | 120 | var lp = { x: particle.position.x, y: particle.position.y }; 121 | 122 | // Rotation 123 | particle.offset.x += particle.speed; 124 | particle.offset.y += particle.speed; 125 | 126 | // Follow mouse with some lag 127 | particle.shift.x += ( mouseX - particle.shift.x) * (particle.speed); 128 | particle.shift.y += ( mouseY - particle.shift.y) * (particle.speed); 129 | 130 | // Apply position 131 | particle.position.x = particle.shift.x + Math.cos(i + particle.offset.x) * (particle.orbit*RADIUS_SCALE); 132 | particle.position.y = particle.shift.y + Math.sin(i + particle.offset.y) * (particle.orbit*RADIUS_SCALE); 133 | 134 | // Limit to screen bounds 135 | particle.position.x = Math.max( Math.min( particle.position.x, SCREEN_WIDTH ), 0 ); 136 | particle.position.y = Math.max( Math.min( particle.position.y, SCREEN_HEIGHT ), 0 ); 137 | 138 | particle.size += ( particle.targetSize - particle.size ) * 0.01; 139 | 140 | if( Math.round( particle.size ) == Math.round( particle.targetSize ) ) { 141 | particle.targetSize = 1 + Math.random() * 2; 142 | } 143 | 144 | context.beginPath(); 145 | context.fillStyle = particle.fillColor; 146 | context.strokeStyle = particle.fillColor; 147 | context.lineWidth = particle.size; 148 | context.moveTo(lp.x, lp.y); 149 | context.lineTo(particle.position.x, particle.position.y); 150 | context.stroke(); 151 | context.arc(particle.position.x, particle.position.y, particle.size/2, 0, Math.PI*2, true); 152 | context.fill(); 153 | } 154 | } 155 | 156 | window.onload = init; -------------------------------------------------------------------------------- /static/canvas.js: -------------------------------------------------------------------------------- 1 | $(()=>{ 2 | 3 | 4 | //html 5 | $('#can1').radialIndicator({ 6 | radius: 70, 7 | barColor: '#fa00b4', 8 | barWidth: 3, 9 | initValue: 0, 10 | roundCorner : true, 11 | percentage: true 12 | }); 13 | 14 | 15 | 16 | //css 17 | $('#can2').radialIndicator({ 18 | radius: 70, 19 | barColor: '#00FF7F', 20 | barWidth: 3, 21 | initValue: 0, 22 | roundCorner : true, 23 | percentage: true 24 | }); 25 | 26 | //js 27 | $('#can3').radialIndicator({ 28 | radius: 70, 29 | barColor: '#912CEE', 30 | barWidth: 3, 31 | initValue: 0, 32 | roundCorner : true, 33 | percentage: true 34 | }); 35 | 36 | //node 37 | $('#can4').radialIndicator({ 38 | radius: 70, 39 | barColor: '#87CEFA', 40 | barWidth: 3, 41 | initValue: 0, 42 | roundCorner : true, 43 | percentage: true 44 | }); 45 | 46 | //当达到指定高度时 出发圆环动画 47 | 48 | $(window).on('scroll',function() { 49 | 50 | if(document.body.scrollHeight >= 1000 ) { 51 | 52 | var radialObj1 = $('#can1').data('radialIndicator'); 53 | 54 | radialObj1.animate(90); 55 | 56 | 57 | var radialObj2 = $('#can2').data('radialIndicator'); 58 | 59 | radialObj2.animate(90); 60 | 61 | 62 | var radialObj3 = $('#can3').data('radialIndicator'); 63 | 64 | radialObj3.animate(95); 65 | 66 | 67 | var radialObj4 = $('#can4').data('radialIndicator'); 68 | 69 | radialObj4.animate(85); 70 | 71 | 72 | } 73 | }); 74 | 75 | 76 | 77 | 78 | 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /static/f2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f2.gif -------------------------------------------------------------------------------- /static/f3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f3.gif -------------------------------------------------------------------------------- /static/f4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f4.gif -------------------------------------------------------------------------------- /static/f5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f5.gif -------------------------------------------------------------------------------- /static/f6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f6.gif -------------------------------------------------------------------------------- /static/f7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f7.gif -------------------------------------------------------------------------------- /static/f8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f8.gif -------------------------------------------------------------------------------- /static/f9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/f9.gif -------------------------------------------------------------------------------- /static/fengjing1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/fengjing1.gif -------------------------------------------------------------------------------- /static/head.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/head.gif -------------------------------------------------------------------------------- /static/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function rndint(t, n) { 3 | return Math.floor(t + (n - t + 1) * Math.random()) 4 | } 5 | function rndfloat(t, n) { 6 | return t + (n - t) * Math.random() 7 | } 8 | function getGET() { 9 | var t = location.hash.toString(); 10 | if (!t) 11 | return {}; 12 | if (t = t.substring(1), !t) 13 | return {}; 14 | var n = t.split("&"), 15 | e = {}; 16 | for (var i in n) { 17 | var r = n[i].split("="); 18 | e[r[0]] = decodeURI(r[1]) 19 | } 20 | return e 21 | } 22 | function DOM_p() { 23 | var t = document.createElement("p"); 24 | return t 25 | } 26 | function DOM_i(t, n) { 27 | var e = document.createElement("i"); 28 | switch (e.style.display = "inline-block", e.style.opacity = 0, e.style.transform = "\n translate(" + rndint(-500, 500) + "%," + rndint(-500, 500) + "%)\n scale(" + rndfloat(0, 2) + ")\n rotate3d(" + Math.random() + ", " + Math.random() + ", " + Math.random() + ", " + rndint(-180, 180) + "deg)\n ", e.innerText = t, n) { 29 | case "fast": 30 | e.style.transition = rndint(400, 600) + "ms " + rndint(0, 150) + "ms"; 31 | break; 32 | case "normal": 33 | default: 34 | e.style.transition = rndint(1e3, 2e3) + "ms " + rndint(0, 300) + "ms"; 35 | break; 36 | case "slow": 37 | e.style.transition = rndint(2e3, 4e3) + "ms " + rndint(0, 500) + "ms" 38 | } 39 | return e 40 | } 41 | function animate() { 42 | p_now != -1 && ps[p_now].map(function(t) { 43 | t.style.transform = "\n translate(" + rndint(-500, 500) + "%," + rndint(-500, 500) + "%)\n scale(" + rndfloat(0, 2) + ")\n rotate3d(" + Math.random() + ", " + Math.random() + ", " + Math.random() + ", " + rndint(-180, 180) + "deg)\n ", t.style.opacity = 0 44 | }); 45 | do ++p_now >= ps.length && (p_now = 0); 46 | while (!ps[p_now]); 47 | ps[p_now].map(function(t) { 48 | t.style.opacity = 1, t.style.transform = "none" 49 | }), document.body.style.backgroundColor = "hsl(" + rndint(0, 359) + ",40%,40%)"; 50 | var t = settings["time" + p_now] || settings.time; 51 | setTimeout(animate, t) 52 | } 53 | var GET = getGET(), 54 | ps = [], 55 | settings = { 56 | time: 5e3, 57 | size: 4, 58 | speed: "normal" 59 | }; 60 | var dates = new Date(); 61 | var now = dates.toLocaleString();//2018/ 62 | var hello = now.substring(0,10); 63 | 64 | for (var key in GET) 65 | parseInt(key) == key ? ps[key] = GET[key] : settings[key] = GET[key]; 66 | ps.length || (ps = [hello+'我们在此相遇'+'>.<', "很开心 遇见您"], settings.size = 3); 67 | var DOM_stage = document.getElementById("stage"), 68 | body_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, 69 | body_height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; 70 | ps = ps.map(function(t, n) { 71 | var e = DOM_p(), 72 | i = t.split(""); 73 | return i = i.map(function(t) { 74 | var i = DOM_i(t, settings.speed), 75 | r = settings["size" + n] || settings.size; 76 | return i.style.fontSize = r + "rem", e.appendChild(i), i 77 | }), DOM_stage.appendChild(e), i 78 | }); 79 | var p_now = -1; 80 | animate(), document.title = "您好 朋友"; 81 | 82 | -------------------------------------------------------------------------------- /static/sroll-blog.js: -------------------------------------------------------------------------------- 1 | $(()=> { 2 | const width = document.body.clientWidth; 3 | 4 | console.log(width); 5 | 6 | if(width !== 1440 ) { 7 | $('.arrow').remove(); 8 | $('.gos').remove(); 9 | } 10 | 11 | $('.more-me').on('click',function() { 12 | window.open('about-blog.html'); 13 | }); 14 | 15 | 16 | 17 | $('.go').hover(function(){ 18 | $('.go').css({ 19 | 'background':'purple', 20 | 'color':'white' 21 | }); 22 | 23 | $('.gos').animate({ 24 | width:400+'px', 25 | opacity:1 26 | },'slow'); 27 | 28 | },function(){ 29 | $('.go').css({ 30 | 'background':'', 31 | 'color':'purple' 32 | }); 33 | $('.gos').animate({ 34 | width:0, 35 | opacity:0 36 | },'slow'); 37 | }); 38 | 39 | $('.go').click(function() { 40 | 41 | $('.contens').css({ 42 | display:'block', 43 | position:'absolute', 44 | background:'white', 45 | zIndex:1111 46 | }); 47 | $('.contens').animate({ 48 | top: -350+'px' 49 | },'slow'); 50 | 51 | 52 | }) 53 | 54 | $('.back').hover(function() { 55 | $('.back').css({ 56 | opacity:1 57 | }) 58 | },function() { 59 | $('.back').css({ 60 | opacity:.3 61 | }) 62 | }); 63 | $('.back').click(function() { 64 | 65 | $('.contens').animate({ 66 | top: 780+'px' 67 | },'slow'); 68 | 69 | setTimeout(()=>{ 70 | $('.contens').css({ 71 | display:'none', 72 | position:'absolute', 73 | background:'white', 74 | zIndex:'' 75 | }); 76 | },1000); 77 | 78 | }) 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /static/webpack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuChengleiy/blog/5ad5819e55f311d42e8af923b97af75bfe514a70/static/webpack.gif --------------------------------------------------------------------------------