-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 123 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 123 KB
1
[{"title":"JavaScript浮点数运算的精度问题","date":"2018-03-22T07:34:00.000Z","path":"2018/03/22/accurateOperation/","text":"在 JavaScript 中整数和浮点数都属于 Number 数据类型,所有数字都是以 64 位浮点数形式储存。当做浮点数运算时,会发现一些问题 12345678910111213141516171819202122// 加法 =====================// 0.1 + 0.2 = 0.30000000000000004// 0.7 + 0.1 = 0.7999999999999999// 0.2 + 0.4 = 0.6000000000000001// 2.22 + 0.1 = 2.3200000000000003 // 减法 =====================// 1.5 - 1.2 = 0.30000000000000004// 0.3 - 0.2 = 0.09999999999999998 // 乘法 =====================// 19.9 * 100 = 1989.9999999999998// 19.9 * 10 * 10 = 1990// 1306377.64 * 100 = 130637763.99999999// 1306377.64 * 10 * 10 = 130637763.99999999// 0.7 * 180 = 125.99999999999999// 9.7 * 100 = 969.9999999999999// 39.7 * 100 = 3970.0000000000005 // 除法 =====================// 0.3 / 0.1 = 2.9999999999999996// 0.69 / 10 = 0.06899999999999999 简单的总结一下: JavaScript 里的数字是采用 IEEE 754 标准的 64 位双精度浮点数。对于64位的浮点数在内存中的表示,最高的1位是符号位,接着的11位是指数,剩下的52位为有效数字,具体: 第0位:符号位, s 表示 ,0表示正数,1表示负数,决定了一个数的正负;第1位到第11位:储存指数部分, e 表示,决定了数值的大小;第12位到第63位:储存小数部分(即有效数字),f 表示,决定了数值的精度; IEEE 754规定,有效数字第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字总是1.xx…xx的形式,其中xx..xx的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript提供的有效数字最长为53个二进制位(64位浮点的后52位+有效数字第一位的1) 12345678910111213//0.1+0.2=0.300000000000000040.1 和 0.2 都转化成二进制后再进行运算0.00011001100110011001100110011001100110011001100110011010 +0.0011001100110011001100110011001100110011001100110011010 =0.0100110011001100110011001100110011001100110011001100111IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到二进制为:0.0100110011001100110011001100110011001100110011001100 //转成十进制正好是 0.30000000000000004 整数精度同样存在问题,先来看看问题:12console.log(19571992547450991); //=> 19571992547450990console.log(19571992547450991===19571992547450992); //=> true 同样的原因,在 JavaScript 中 Number类型统一按浮点数处理,整数是按最大54位来算最大(253 - 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(253 - 1),Number.MIN_SAFE_INTEGER,-9007199254740991) 安全整数范围的。所以只要超过这个范围,就会存在被舍去的精度问题。 解决办法数据展示类 当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:1parseFloat(1.4000000000000001.toPrecision(12)) === 1.4 // True 封装成方法就是:123function strip(num, precision = 12) { return +parseFloat(num.toPrecision(precision));} 为什么选择 12 做为默认精度?这是一个经验的选择,一般选12就能解决掉大部分0001和0009问题,而且大部分情况下也够用了,如果你需要更精确可以调高。 数据运算类对于运算类操作,如 +-*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。以加法为例:123456789/** * 精确加法 */function add(num1, num2) { const num1Digits = (num1.toString().split('.')[1] || '').length; const num2Digits = (num2.toString().split('.')[1] || '').length; const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits)); return (num1 * baseNum + num2 * baseNum) / baseNum;} 以上方法能适用于大部分场景。遇到科学计数法如 2.3e+1(当数字精度大于21时,数字会强制转为科学计数法形式显示)时还需要特别处理一下。 以下是实现精确加减乘除的函数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129/** ** 加法函数,用来得到精确的加法结果 ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 ** 调用:accAdd(arg1,arg2) ** 返回值:arg1加上arg2的精确结果 **/function accAdd(arg1, arg2) { var r1, r2, m, c; try { r1 = arg1.toString().split(\".\")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(\".\")[1].length; } catch (e) { r2 = 0; } c = Math.abs(r1 - r2); m = Math.pow(10, Math.max(r1, r2)); if (c > 0) { var cm = Math.pow(10, c); if (r1 > r2) { arg1 = Number(arg1.toString().replace(\".\", \"\")); arg2 = Number(arg2.toString().replace(\".\", \"\")) * cm; } else { arg1 = Number(arg1.toString().replace(\".\", \"\")) * cm; arg2 = Number(arg2.toString().replace(\".\", \"\")); } } else { arg1 = Number(arg1.toString().replace(\".\", \"\")); arg2 = Number(arg2.toString().replace(\".\", \"\")); } return (arg1 + arg2) / m;} //给Number类型增加一个add方法,调用起来更加方便。Number.prototype.add = function (arg) { return accAdd(arg, this);};/** ** 减法函数,用来得到精确的减法结果 ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。 ** 调用:accSub(arg1,arg2) ** 返回值:arg1加上arg2的精确结果 **/function accSub(arg1, arg2) { var r1, r2, m, n; try { r1 = arg1.toString().split(\".\")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(\".\")[1].length; } catch (e) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n);} // 给Number类型增加一个mul方法,调用起来更加方便。Number.prototype.sub = function (arg) { return accMul(arg, this);};/** ** 乘法函数,用来得到精确的乘法结果 ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 ** 调用:accMul(arg1,arg2) ** 返回值:arg1乘以 arg2的精确结果 **/function accMul(arg1, arg2) { var m = 0, s1 = arg1.toString(), s2 = arg2.toString(); try { m += s1.split(\".\")[1].length; } catch (e) { } try { m += s2.split(\".\")[1].length; } catch (e) { } return Number(s1.replace(\".\", \"\")) * Number(s2.replace(\".\", \"\")) / Math.pow(10, m);} // 给Number类型增加一个mul方法,调用起来更加方便。Number.prototype.mul = function (arg) { return accMul(arg, this);};/** ** 除法函数,用来得到精确的除法结果 ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 ** 调用:accDiv(arg1,arg2) ** 返回值:arg1除以arg2的精确结果 **/function accDiv(arg1, arg2) { var t1 = 0, t2 = 0, r1, r2; try { t1 = arg1.toString().split(\".\")[1].length; } catch (e) { } try { t2 = arg2.toString().split(\".\")[1].length; } catch (e) { } with (Math) { r1 = Number(arg1.toString().replace(\".\", \"\")); r2 = Number(arg2.toString().replace(\".\", \"\")); return (r1 / r2) * pow(10, t2 - t1); }} //给Number类型增加一个div方法,调用起来更加方便。Number.prototype.div = function (arg) { return accDiv(this, arg);}; 参考文章 http://www.css88.com/archives/7340https://github.com/camsong/blog/issues/9 Math.jsMath.js 是专门为 JavaScript 和 Node.js 提供的一个广泛的数学库。它具有灵活的表达式解析器,支持符号计算,配有大量内置函数和常量,并提供集成解决方案来处理不同的数据类型像数字,大数字(超出安全数的数字),复数,分数,单位和矩阵。 功能强大,易于使用。 官网:http://mathjs.org/GitHub:https://github.com/josdejong/mathjs","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"}]},{"title":"使用gitbook编写api文档","date":"2018-02-26T08:57:54.000Z","path":"2018/02/26/gitbook/","text":"1、安装gitbook使用npm来安装,在命令行中输入: 1npm install gitbook-cli -g 安装完成之后,你可以使用下面的命令来检验是否安装成功 1gitbook -V 2、创建gitbook目录结构README.md 这个文件相对于是一本Gitbook的简介,比如我们这本书的README.md : 123# Gitbook 使用入门> GitBook 是一个基于 Node.js 的命令行工具,可使用 Github/Git 和 Markdown 来制作精美的电子书。本书将简单介绍如何安装、编写、生成、发布一本在线图书。 SUMMARY.md 这个文件相对于是一本书的目录结构。比如我们这本书的SUMMARY.md : 123456789101112131415> # Summary* [Introduction](README.md)* [基本安装](howtouse/README.md) * [Node.js安装](howtouse/nodejsinstall.md) * [Gitbook安装](howtouse/gitbookinstall.md)* [目录结构](book/README.md) * [README.md 与 SUMMARY编写](book/file.md) * [目录初始化](book/prjinit.md)* [输出](output/README.md) * [输出为静态网站](output/outfile.md) * [输出PDF](output/pdfandebook.md)* [发布](publish/README.md) * [发布到Github Pages](publish/gitpages.md)* [结束](end/README.md) 2.1 使用Gitbook的命令行工具将这个目目录结构生成相应地目录及文件1$ gitbook init 如果一开始时,您的目录下未添加 README.md 和 SUMMARY.md 这两个文件时,命令会自动生成这两个文件,然后在编辑这两个文件即可 3、输出静态网站和PDF3.1 本地预览当你编辑好gitbook文档之后,你可以使用gitbook的命令来进行本地预览。 1gitbook serve gitbook会启动一个4000端口用于预览。 3.2 生成静态网页1gitbook build 生成的静态页面会在更目录的 _book目录下 3.3 输出pdfgitbook编写完成后,为了方便打开查看,我们将其转化成pdf, 需要先安装gitbook pdf: 1npm install gitbook-pdf -g 然后,用下面的命令就可以生成PDF文件了。1gitbook pdf {book_name} 如果,你已经在编写的gitbook当前目录,也可以使用相对路径。1gitbook pdf . 正常情况的话,你就会发现,你的目录中多了一个名为book.pdf的文件。 但是在执行 gitbook pdf . 的时候,报错了: 1EbookError: Error during ebook generation: 'ebook-convert' ..... 网上查了一下,大概是说 ‘ebook-convert’ 应该只是nodejs适配包,并不包含真正的执行程序,需要添加到环境变量中安装calibre http://www.calibre-ebook.com/download_windows安装成功后,打开calibre文件位置 可以看到 ebook-convert.exe 和 ebook-device.exe,点击执行就可以了 4、参考文章Gitbook的安装教程1Gitbook的安装教程2Mac环境安装Gitbook,并导出PDF教程http://ju.outofmemory.cn/entry/212632","tags":[{"name":"gitbook","slug":"gitbook","permalink":"http://yoursite.com/tags/gitbook/"},{"name":"pdf","slug":"pdf","permalink":"http://yoursite.com/tags/pdf/"}]},{"title":"js实现url参数加解密","date":"2018-01-25T02:37:45.000Z","path":"2018/01/25/js实现url参数加解密/","text":"js实现url参数加密解密 加密函数 12345678910111213141516171819202122232425262728293031323334353637383940414243function Encrypt(str, pwd) { if (str == \"\") return \"\"; str = escape(str); if (!pwd || pwd == \"\") { var pwd = \"1234\"; } pwd = escape(pwd); if (pwd == null || pwd.length <= 0) { alert(\"Please enter a password with which to encrypt the message.\"); return null; } var prand = \"\"; for (var I = 0; I < pwd.length; I++) { prand += pwd.charCodeAt(I).toString(); } var sPos = Math.floor(prand.length / 5); var mult = parseInt(prand.charAt(sPos) + prand.charAt(sPos * 2) + prand.charAt(sPos * 3) + prand.charAt(sPos * 4) + prand.charAt(sPos * 5)); var incr = Math.ceil(pwd.length / 2); var modu = Math.pow(2, 31) - 1; if (mult < 2) { alert(\"Algorithm cannot find a suitable hash. Please choose a different password. /nPossible considerations are to choose a more complex or longer password.\"); return null; } var salt = Math.round(Math.random() * 1000000000) % 100000000; prand += salt; while (prand.length > 10) { prand = (parseInt(prand.substring(0, 10)) + parseInt(prand.substring(10, prand.length))).toString(); } prand = (mult * prand + incr) % modu; var enc_chr = \"\"; var enc_str = \"\"; for (var I = 0; I < str.length; I++) { enc_chr = parseInt(str.charCodeAt(I) ^ Math.floor((prand / modu) * 255)); if (enc_chr < 16) { enc_str += \"0\" + enc_chr.toString(16); } else enc_str += enc_chr.toString(16); prand = (mult * prand + incr) % modu; } salt = salt.toString(16); while (salt.length < 8) salt = \"0\" + salt; enc_str += salt; return enc_str;} 解密函数 123456789101112131415161718192021222324252627282930313233343536function Decrypt(str, pwd) { if (str == \"\") return \"\"; if (!pwd || pwd == \"\") { var pwd = \"1234\"; } pwd = escape(pwd); if (str == null || str.length < 8) { alert(\"A salt value could not be extracted from the encrypted message because it's length is too short. The message cannot be decrypted.\"); return; } if (pwd == null || pwd.length <= 0) { alert(\"Please enter a password with which to decrypt the message.\"); return; } var prand = \"\"; for (var I = 0; I < pwd.length; I++) { prand += pwd.charCodeAt(I).toString(); } var sPos = Math.floor(prand.length / 5); var mult = parseInt(prand.charAt(sPos) + prand.charAt(sPos * 2) + prand.charAt(sPos * 3) + prand.charAt(sPos * 4) + prand.charAt(sPos * 5)); var incr = Math.round(pwd.length / 2); var modu = Math.pow(2, 31) - 1; var salt = parseInt(str.substring(str.length - 8, str.length), 16); str = str.substring(0, str.length - 8); prand += salt; while (prand.length > 10) { prand = (parseInt(prand.substring(0, 10)) + parseInt(prand.substring(10, prand.length))).toString(); } prand = (mult * prand + incr) % modu; var enc_chr = \"\"; var enc_str = \"\"; for (var I = 0; I < str.length; I += 2) { enc_chr = parseInt(parseInt(str.substring(I, I + 2), 16) ^ Math.floor((prand / modu) * 255)); enc_str += String.fromCharCode(enc_chr); prand = (mult * prand + incr) % modu; } return unescape(enc_str);}","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"},{"name":"url","slug":"url","permalink":"http://yoursite.com/tags/url/"}]},{"title":"NodeJs 加密解密","date":"2018-01-17T08:18:53.000Z","path":"2018/01/17/NodeJs-加密解密/","text":"常见的加密算法基本分为这几类,1 :线性散列算法、2:对称性加密算法、3、非对称性加密算法。nodejs是通集成在内核中的crypto模块来完成加密解密。 crypto的基本使用 12345678910111213141516171819var crypto = require('crypto'); //加密exports.cipher = function(algorithm, key, buf) { var encrypted = \"\"; var cip = crypto.createCipher(algorithm, key); encrypted += cip.update(buf, 'binary', 'hex'); encrypted += cip.final('hex'); return encrypted}; //解密exports.decipher = function(algorithm, key, encrypted) { var decrypted = \"\"; var decipher = crypto.createDecipher(algorithm, key); decrypted += decipher.update(encrypted, 'hex', 'binary'); decrypted += decipher.final('binary'); return decrypted}; 1、对称式加密就是加密和解密使用同一个密钥,通常称之为“Session Key ”这种加密技术在当今被广泛采用,如美国政府所采用的DES加密标准就是一种典型的“对称式”加密法,它的Session Key长度为56bits。 栗子: 1、加密模块的引用: 123456789var crypto=require('crypto');var $=require('underscore'); //underscore中的reduce、reduceRight方法,进行加密和解密的算法执行var DEFAULTS = { encoding: { input: 'utf8', output: 'hex' }, algorithms: ['bf', 'blowfish', 'aes-128-cbc']}; 默认加密算法配置项: 数据输入格式为utf8,输出格式为hex; 算法使用bf,blowfish,aes-128-abc三种加密算法; 2、配置项初始化: 12345678910function MixCrypto(options) { if (typeof options == 'string') options = { key: options }; options = $.extend({}, DEFAULTS, options); this.key = options.key; this.inputEncoding = options.encoding.input; this.outputEncoding = options.encoding.output; this.algorithms = options.algorithms;} 加密算法可以进行配置,通过配置option进行不同加密算法及编码的使用。 3、加密方法代码如下: 1234567MixCrypto.prototype.encrypt = function (plaintext) { return $.reduce(this.algorithms, function (memo, a) { var cipher = crypto.createCipher(a, this.key); return cipher.update(memo, this.inputEncoding, this.outputEncoding) + cipher.final(this.outputEncoding) }, plaintext, this);}; 使用crypto进行数据的加密处理。 4、解密方法代码如下: 1234567891011MixCrypto.prototype.decrypt = function (crypted) { try { return $.reduceRight(this.algorithms, function (memo, a) { var decipher = crypto.createDecipher(a, this.key); return decipher.update(memo, this.outputEncoding, this.inputEncoding) + decipher.final(this.inputEncoding); }, crypted, this); } catch (e) { return; }}; 使用crypto进行数据的解密处理。 通过underscore中的reduce、reduceRight方法,进行加密和解密的算法执行 2、非对称加密非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密,因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法 为私钥创建公钥 1$ openssl req -key key.pem -new -x509 -out cert.pem 12345678910111213var crypto = require('crypto');var fs = require('fs');var buffer = new Buffer('hello');//使用公钥对数据进行加密var secret = crypto.publicEncrypt(fs.readFileSync('cert.pem').toString(), buffer);//使用私钥对数据进行解密var result = crypto.privateDecrypt(fs.readFileSync('key.pem').toString(), secret);console.log(result.toString()); 参考文章","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"crypto","slug":"crypto","permalink":"http://yoursite.com/tags/crypto/"}]},{"title":"pm2管理koa2","date":"2017-12-20T08:39:48.000Z","path":"2017/12/20/pm2/","text":"1、部署package.json 1234567\"scripts\": { \"release\": \"gulp release\", \"reduce\": \"gulp reduce\", \"dev\": \"gulp dev\", \"test\": \"pm2 start ./config/ecosystem.config.js --env test\", \"server\": \"pm2 start ./config/ecosystem.config.js --env production\" } ecosystem.config.js12345678910111213141516171819202122232425262728module.exports = { apps: [{ name: \"yjl\", script: \"./app.js\", watch: true, env: { NODE_ENV: \"development\", PORT: 443, API_URL:\"https://backend.xxx\", SOURCE_PATH:\"./src\" }, env_test: { NODE_ENV: \"test\", PORT: 443, API_URL:\"https://backend.xxx\", SOURCE_PATH:\"./dist\" }, env_production: { NODE_ENV: \"production\", PORT: 4000, API_URL:\"http://backend.xxx\", SOURCE_PATH:\"./dist\" }, autorestart: true, log_date_format: \"YYYY-MM-DD HH:mm Z\" }]} app.js 123456789101112131415161718192021const port = process.env.PORT || 443;const env = process.env.NODE_ENV || 'development';const src = process.env.SOURCE_PATH || './src';const apiDomain = process.env.API_URL || 'https://backend.xxx';const app = new Koa();// 配置session中间件app.use(session({ key: \"SESSIONID\", //default \"koa:sess\" maxAge: 2 * 60 * 60 * 1000 //设置session超时时间,2小时}))//设置全局变量app.use(async(ctx, next) => { ctx.state.session = ctx.session; ctx.state.apiDomain = apiDomain; await next();}) 2、使用pm21、保存脚本 1pm2 save 2、创建开机启动脚本 1pm2 startup 3、设置开机自动启动(centos 7+)1systemctl enable pm2-root.service 4、重启koa21pm2 restart yjl 5、重新搭建启动koa212pm2 delete yjlnpm run server 6、查看错误日志123pm2 show yjl #查看log的输出目录cd /root/.pm2/logs/ && cat yjl-error-0.log","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"koa2","slug":"koa2","permalink":"http://yoursite.com/tags/koa2/"},{"name":"pm2","slug":"pm2","permalink":"http://yoursite.com/tags/pm2/"}]},{"title":"gulp对seajs的模块做合并压缩","date":"2017-12-14T12:53:28.000Z","path":"2017/12/14/gulp-seajs/","text":"基于gulp对seajs的模块做合并压缩的方式gulpfile.js 1234567891011//seajs合并模式gulp.task("seajs", function () { return merge( gulp.src('dist/js/!(cmdlib|ckeditor|plugin|app)/**/*.js',{base:'dist/js/'}) .pipe(transport()) .pipe(concat({ base: "dist/js/", })) .pipe(gulp.dest('dist/js')) );}); 这个是seajs合并工作中比较关键的一点,它不像requirejs,直接做concat即可;它必须先经过一个transport的任务处理,将匿名模块变成具名模块,同时用define的第二个参数来描述这个模块的所有依赖,就像requirejs那样。只有做完了transport,才能利用gulp-seajs-concat做合并。原因请参考:https://github.com/seajs/seajs/issues/426。gulp-seajs-concat做合并的时候,就很简单了,只要告诉它一个 base选项 即可,这个base选项跟js/common.js中base选项保持一致。因为gulp-seajs-concat根据base和transport之后的模块,就能找到它所依赖的其它模块文件。 参考文章具体例子","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"koa2","slug":"koa2","permalink":"http://yoursite.com/tags/koa2/"},{"name":"seajs","slug":"seajs","permalink":"http://yoursite.com/tags/seajs/"},{"name":"gulp","slug":"gulp","permalink":"http://yoursite.com/tags/gulp/"}]},{"title":"koa2-生成环境部署注意事项","date":"2017-12-13T06:39:23.000Z","path":"2017/12/13/koa2-practice/","text":"koa2应用部署到生产环境,需要做一些安全配置。 1、安全性相关的HTTP头以下是一些安全性相关的HTTP头,你的站点应该设置它们: Strict-Transport-Security:强制使用安全连接(SSL/TLS之上的HTTPS)来连接到服务器。 X-Frame-Options:提供对于“点击劫持”的保护。 X-XSS-Protection:开启大多现代浏览器内建的对于跨站脚本攻击(XSS)的过滤功能。 X-Content-Type-Options: 防止浏览器使用MIME-sniffing来确定响应的类型,转而使用明确的content-type来确定。 Content-Security-Policy:防止受到跨站脚本攻击以及其他跨站注入攻击。 在Node.js中,这些都可以通过使用Helmet模块轻松设置完毕: 123456var koa = require('koa'); var helmet = require('koa-helmet');var app = express();app.use(helmet()); 2、设置 cookie 安全性选项设置以下 cookie 选项来增强安全性: secure - 确保浏览器只通过 HTTPS 发送 cookie。 httpOnly - 确保 cookie 只通过 HTTP(S)(而不是客户机 JavaScript)发送,这有助于防御跨站点脚本编制攻击。 domain - 表示 cookie 的域;用于和请求 URL 的服务器的域进行比较。如果匹配,那么接下来检查路径属性。 path - 表示 cookie 的路径;用于和请求路径进行比较。如果路径和域都匹配,那么在请求中发送 cookie。 expires - 用于为持久性 cookie 设置到期日期。 3、命令注入攻击者使用命令注入来在远程web服务器中运行系统命令。通过命令注入,攻击者甚至可以取得系统的密码。 实践中,如果你有一个URL: https://example.com/downloads?file=user1.txt 它可以变成: https://example.com/downloads?file=%3Bcat%20/etc/passwd 在这个例子中,%3B会变成一个分号。所以将会运行多条系统命令。 为了预防这类攻击,请确保总是检查过滤了用户的输入内容。 我们也可以以Node.js的角度来说:123child_process.exec('ls', function (err, data) { console.log(data);}); 在child_process.exec的底层,它调用了/bin/sh,所以它是一个bash解释器,而不仅仅是只能执行用户程序。 当用户的输入是一个反引号或$()时,将它们传入这个方法就很危险了。 可以通过使用child_process.execFile来解决上面这个问题。 4、跨站请求伪造(CSRF)跨站请求伪造(CSRF)是一种迫使用户在他们已登录的web应用中,执行一个并非他们原意的操作的攻击手段。这种攻击常常用于那些会改变用户的状态的请求,通常它们并不窃取数据,因为攻击者并不能看到响应的内容。 在Node.js中,你可以使用csrf模块来缓和这种攻击。它同样是非常底层的,你可能更喜欢使用如csurf这样的koa-csrf中间件。 在路由层,可以会有如下代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243import Koa from 'koa';import bodyParser from 'koa-bodyparser';import session from 'koa-generic-session';import convert from 'koa-convert'; const app = new Koa(); // set the session keys app.keys = [ 'a', 'b' ]; // add session support app.use(convert(session())); // add body parsing app.use(bodyParser()); // add the CSRF middleware app.use(new CSRF({ invalidSessionSecretMessage: 'Invalid session secret', invalidSessionSecretStatusCode: 403, invalidTokenMessage: 'Invalid CSRF token', invalidTokenStatusCode: 403, excludedMethods: [ 'GET', 'HEAD', 'OPTIONS' ], disableQuery: false})); // your middleware here (e.g. parse a form submit) app.use((ctx, next) => { if (![ 'GET', 'POST' ].includes(ctx.method)) return next(); if (ctx.method === 'GET') { ctx.body = ctx.csrf; return; } ctx.body = 'OK'; }); app.listen(); 在展示层,EJS Template: 123456<form action=\"/register\" method=\"POST\"> <input type=\"hidden\" name=\"_csrf\" value=\"<%= csrf %>\" /> <input type=\"email\" name=\"email\" placeholder=\"Email\" /> <input type=\"password\" name=\"password\" placeholder=\"Password\" /> <button type=\"submit\">Register</button></form> 5、正则表达式这类攻击主要是由于一些正则表达式,在极端情况下,会变得性能及其糟糕。这些正则被称为恶魔正则(Evil Regexes): 对于重复文本进行分组在重复的分组内又有重复内容 ([a-zA-Z]+)*, (a+)+ 或 (a|a?)+在如aaaaaaaaaaaaaaaaaaaaaaaa! 这样的输入面前,都是脆弱的。这会引起大量的计算。更多详情可以参考ReDos。 可以使用Node.js工具 safe-regex 这检测你的正则: 1234$ node safe.js '(beep|boop)*'true $ node safe.js '(a+){10}'false 6、确保依赖项的安全在管理应用程序的依赖项方面,npm 功能非常强大,而且使用方便。但是,您使用的软件包可能包含严重的安全漏洞,也可能会对应用程序产生影响。应用程序的安全取决于依赖项中“最弱”的一环。 可以使用以下的任一或全部两种工具,帮助确保所使用的第三方软件包的安全性:nsp 和 requireSafe。这两种工具的功能大体相同。 nsp 是一种命令行工具,用于检查 Node 安全项目漏洞数据库,确定应用程序是否使用具有已知漏洞的软件包。可通过以下命令安装该工具: 1$ npm i nsp -g 使用以下命令将 npm-shrinkwrap.json 文件提交至 nodesecurity.io 以进行验证: 1$ nsp audit-shrinkwrap 使用以下命令将 package.json 文件提交至 nodesecurity.io 以进行验证: 1$ nsp audit-package 以下示例说明如何使用 requireSafe 来审计 Node 模块: 123$ npm install -g requiresafe$ cd your-app$ requiresafe check","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"koa2","slug":"koa2","permalink":"http://yoursite.com/tags/koa2/"},{"name":"http","slug":"http","permalink":"http://yoursite.com/tags/http/"}]},{"title":"koa2--通过koa2上传图片到php后台","date":"2017-09-18T07:18:59.000Z","path":"2017/09/18/koa2-uploadfile/","text":"项目有个这样的需求,浏览器页面需要上传图片到后台服务器,前端的页面是通过koa2框架搭建起来的中间层,一开始是通过浏览器直接上传文件到后台,需要携带接口token值,token值一开始登陆之后就保存在页面的cookie上面,因此也暴露了token值,有安全隐患。再者因为页面要兼容ie7,找了很久的,最后选定了 ajaxfileupload,但是前端页面和后台页面是不同子域名,需要配置跨域,设置采用设置domain,本地调试起来各种坑… 因此需要寻找一种 通过koa2上传文件(调用上传文件接口)到后台服务器的可行办法。 前端页面 js123456789101112131415161718192021222324252627//document.domain = \"xxx.com\";$.ajaxFileUpload({ url:'/api/upload', //apiDomain + '/api/v1.user/photoupload' secureuri:false, //一般设置为false fileElementId: 'fileupload', //文件上传空间的id属性<input type=\"file\" id=\"file\" name=\"file\" /> dataType: 'json', //返回值类型 一般设置为json success: function (res,status) //服务器成功响应处理函数 { if(res.data.code==0){ $(\"#fileuploadImg\").attr(\"src\", \"http://\" + res.data.imgurl).css({'display': 'inline-block'}); $(\"#fileuploadImg\").attr(\"src2\", res.data.savename); $(\"#text1\").hide(); $(\".field-companyLicense .tips\").removeClass('error').addClass('success'); page.fort(); }else{ alert(res.data.message); } }, error: function (data,status, e)//服务器响应失败处理函数 { console.log(data, status, e); }}); koa2的处理js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137const path = require('path')const fs = require('fs')const Busboy = require('busboy')const request = require('request'); function getSuffix (filename) { return filename.split('.').pop()} // 重命名function Rename (filename) { return Math.random().toString(16).substr(2) + '.' + getSuffix(filename)}// 删除文件function removeTemImage (path) { fs.unlink(path, (err) => { if (err) { throw err } })} module.exports.post = async(ctx) => { let session = ctx.session; let apiDomain = ctx.state.apiDomain; let jump = ctx.request.query.jump; let busboy = new Busboy({ headers: ctx.request.headers }); let req = ctx.req; let imgPath = null; let fileName=null; let contentType=null; // 上传到本地服务器 function uploadFile (ctx) { return new Promise((resolve, reject) => { // 监听文件解析事件 busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { fileName = Rename(filename); imgPath = './uploadImages/' + fileName; contentType=mimetype; // 文件保存到制定路径 file.pipe(fs.createWriteStream(imgPath)); // 解析文件结束 file.on('end', function() { }) }); // 监听请求中的字段 //busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) { //console.log('fieldname:' + fieldname + ',value:' + inspect(val)) //token = ctx.session.user.token; //}) // 监听结束事件 busboy.on('finish', function(val) { resolve({ imgPath:imgPath, options:{ filenafilename: fileName, contentType: contentType, } }); }) req.pipe(busboy) }) } // 上传到后台 function upToPhp (result) { return new Promise(function(resolve, reject) { var formData = { token: ctx.session.user.token, image:{ value: fs.createReadStream(result.imgPath), options:result.options, } }; request.post({ url: apiDomain + '/api/v1.user/photoupload', formData: formData }, function optionalCallback(err, httpResponse, body) { if (err) { return console.error('Upload failed:', err); } console.log('Upload successful! ', body); //删除临时文件 fs.unlink(imgPath,(err) => { if (err) { throw err } }); resolve(); }); }) } // 获取上存图片 const result = await uploadFile(ctx); // 发送到后台 const result2 = await upToPhp(result); ctx.body = { data: { code: result2.data.code, message: result2.data.message, } }; } 页面已经可以成功上传图片到后台了,但是发现了一个问题,就是如果上传的图片是png24格式的时候,upToPhp这个方法没有执行成功,一直提示 result.imgPath 路径不存在,具体也不知道是什么原因,png8和jpg格式的图片可以正常上传。","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"koa2","slug":"koa2","permalink":"http://yoursite.com/tags/koa2/"}]},{"title":"koa2 项目框架","date":"2017-09-18T07:12:54.000Z","path":"2017/09/18/koa2-frame/","text":"如今nodejs变得越来越火热,采用nodejs实现前后端分离架构已被多数大公司所采用,之前没有接触过express, 所以选框架没有太多的束缚,Koa是继Express之后,Node的又一主流Web开发框架。相比于Express,Koa只保留了核心的中间件处理逻辑,去掉了路由,模板,以及其他一些功能。如今版本已更新到了koa2,不仅性能优异,它还支持async/await,堪称回调地狱的终结者 快速撸一遍文档 koa2学习文档","tags":[{"name":"node","slug":"node","permalink":"http://yoursite.com/tags/node/"},{"name":"koa2","slug":"koa2","permalink":"http://yoursite.com/tags/koa2/"}]},{"title":"重新认识定时器","date":"2017-04-20T02:06:33.000Z","path":"2017/04/20/timer/","text":"重新认识一 一般,setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码(引擎内部使用eval函数,将字符串转为代码),第二个参数delay是推迟执行的毫秒数。但是,setTimeout 还可以添加更多参数。第二个之后的参数都将作为 推迟执行函数的 参数 传入。 1234567891011// 传入4个参数setTimeout(function(a,b){ // a=1,b=2 console.log(a+b);},1000,1,2);// 3 重新认识二 IE 9.0及以下版本,只允许setTimeout有两个参数,不支持更多的参数。这时有三种解决方法。 第一种是在一个匿名函数里面,让回调函数带参数运行,再把匿名函数输入setTimeout。123setTimeout(function() { myFunc(\"one\", \"two\", \"three\");}, 1000); 上面代码中,myFunc是真正要推迟执行的函数,有三个参数。如果直接放入setTimeout,低版本的IE不能带参数,所以可以放在一个匿名函数。 第二种解决方法是使用bind方法,把多余的参数绑定在回调函数上面,生成一个新的函数输入setTimeout。1setTimeout(function(arg1){}.bind(undefined, 10), 1000); 上面代码中,bind方法第一个参数是undefined,表示将原函数的this绑定全局作用域,第二个参数是要传入原函数的参数。它运行后会返回一个新函数,该函数不带参数。 重新认识三 如果被setTimeout推迟执行的回调函数是某个对象的方法,那么该方法中的this关键字将指向全局环境,而不是定义时所在的那个对象。 举例1 12345678910111213var x = 1;var o = { x: 2, y: function(){ console.log(this.x); }};setTimeout(o.y,1000);// 1// 上面代码输出的是1,而不是2,这表示o.y的this所指向的已经不是o,而是全局环境了。 举例2 1234567891011121314function User(login) { this.login = login; this.sayHi = function() { console.log(this.login); }}var user = new User('John');setTimeout(user.sayHi, 1000);// undefined// 上面代码只会显示undefined,因为等到user.sayHi执行时,它是在全局对象中执行,所以this.login取不到值。 解决办法: 方法一 将user.sayHi放在函数中执行,sayHi是在user作用域内执行,而不是在全局作用域内执行,所以能够显示正确的值 123setTimeout(function() { user.sayHi();}, 1000); 方法二 使用bind方法,将绑定sayHi绑定在user上面1setTimeout(user.sayHi.bind(user), 1000); 重新认识四 HTML 5标准规定,setTimeout的最短时间间隔是4毫秒。为了节电,对于那些不处于当前窗口的页面,浏览器会将时间间隔扩大到1000毫秒。另外,如果笔记本电脑处于电池供电状态,Chrome和IE 9以上的版本,会将时间间隔切换到系统定时器,大约是15.6毫秒。setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒。 重新认识五 setInterval函数的用法与setTimeout完全一致。 setInterval指定的是“开始执行”之间的间隔,并不考虑每次任务执行本身所消耗的时间。因此实际上,两次执行之间的间隔会小于指定的时间。比如,setInterval指定每100ms执行一次,每次执行需要5ms,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始。 为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。 写个demo,确保 下一个对话框总是在关闭上一个对话框之后2000毫秒弹出: 12345678var i=1;var timer=setTimeout(function(){ alert(i); timer =setTimeout(arguments.callee,2000);},2000) 用setTimeout模拟了setInterval 1234567891011function interval(func,wait){ var interv=function(){ func.call(); setTimeout(interv,wait); } setTimeout(interv,wait);}interval(function(){console.log(1)},1000) 重新认识六 setTimeout和setInterval返回的整数值是连续的,也就是说,第二个setTimeout方法返回的整数值,将比第一个的整数值大1。利用这一点,可以写一个函数,取消当前所有的setTimeout。 clearTimeout实际应用的例子。有些网站会实时将用户在文本框的输入,通过Ajax方法传回服务器,jQuery的写法如下。 12$('textarea').on('keydown', ajaxAction); 这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown事件,造成大量的Ajax通信。这是不必要的,而且很可能会发生性能问题。正确的做法应该是,设置一个门槛值,表示两次Ajax通信的最小间隔时间。如果在设定的时间内,发生新的keydown事件,则不触发Ajax通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,将进行Ajax通信将数据发送出去。 debounce 防抖动方法 1234567891011121314151617181920212223function debounce(fn, delay){ var timer = null; // 声明计时器 return function(){ //保存当前作用域的 this和 arguments var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, delay); };}// 用法示例$('textarea').on('keydown', debounce(ajaxAction, 2500)) 重新认识七 setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。 这意味着,setTimeout和setInterval指定的代码,必须等到本轮Event Loop的所有同步任务都执行完,再等到本轮Event Loop的“任务队列”的所有任务执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。 setIntervel具有累积效应,如果某个操作特别耗时,超过了setInterval的时间间隔,排在后面的操作会被累积起来,然后在很短的时间内连续触发,这可能或造成性能问题(比如集中发出Ajax请求)。 1234567891011121314setInterval(function () { console.log(2);}, 1000);(function () { sleeping(3000);})();// 2,2,2// 2// ...结果就是等到第二行语句运行完成以后,立刻连续输出三个2,然后开始每隔1000毫秒,输出一个2。 重新认识八 等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行setTimeout指定的任务。 也就是说,setTimeout的真正作用是,在“消息队列”的现有消息的后面再添加一个消息,规定在指定时间执行某段代码。setTimeout添加的事件,会在下一次Event Loop执行。 setTimeout(f, 0)将第二个参数设为0,作用是让f在现有的任务(脚本的同步任务和“消息队列”指定的任务)一结束就立刻执行。 也就是说,setTimeout(f, 0)的作用是,尽可能早地执行指定的任务。而并不是会立刻就执行这个任务。 setTimeout(f, 0)指定的任务,最早也要到下一次Event Loop才会执行 1234567891011121314151617181920212223242526272829setTimeout(function() { console.log(\"Timeout\");}, 0);function a(x) { console.log(\"a() 开始运行\"); b(x); console.log(\"a() 结束运行\");}function b(y) { console.log(\"b() 开始运行\"); console.log(\"传入的值为\" + y); console.log(\"b() 结束运行\");}console.log(\"当前任务开始\");a(42);console.log(\"当前任务结束\");// 当前任务开始// a() 开始运行// b() 开始运行// 传入的值为42// b() 结束运行// a() 结束运行// 当前任务结束// Timeout 重新认识九 可以调整事件的发生顺序。 例子1 网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)。 1234567891011121314var input = document.getElementsByTagName('input[type=button]')[0];input.onclick = function A() { setTimeout(function B() { input.value +=' input'; }, 0)};document.body.onclick = function C() { input.value += ' body'};上面代码在点击按钮后,先触发回调函数A,然后触发函数C。在函数A中,setTimeout将函数B推迟到下一轮Loop执行,这样就起到了,先触发父元素的回调函数C的目的了。 例子2 用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。想在用户输入文本后,立即将字符转为大写。但是实际上,它只能将上一个字符转为大写,因为浏览器此时还没接收到文本。 1234567document.getElementById('my-ok').onkeypress = function() { var self = this; setTimeout(function() { self.value = self.value.toUpperCase(); }, 0);} 例子3 1234567891011121314151617181920212223var div = document.getElementsByTagName('div')[0];// 写法一 造成浏览器“堵塞”,因为JavaScript执行速度远高于DOM,会造成大量DOM操作“堆积”for (var i = 0xA00000; i < 0xFFFFFF; i++) { div.style.backgroundColor = '#' + i.toString(16);}// 写法二var timer;var i=0x100000;function func() { timer = setTimeout(func, 0); div.style.backgroundColor = '#' + i.toString(16); if (i++ == 0xFFFFFF) clearTimeout(timer);}timer = setTimeout(func, 0); 重新认识十正常任务(task)与微任务(microtask)。它们的区别在于,“正常任务”在下一轮Event Loop执行,“微任务”在本轮Event Loop的所有任务结束后执行。 正常任务: setTimeoutsetIntervalsetImmediateI/O各种事件(比如鼠标单击事件)的回调函数 微任务: process.nextTickPromise 1234567891011121314151617181920console.log(1);setTimeout(function() { console.log(2);}, 0);Promise.resolve().then(function() { console.log(3);}).then(function() { console.log(4);});console.log(5);// 1// 5// 3// 4// 2","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"},{"name":"setTimeou","slug":"setTimeou","permalink":"http://yoursite.com/tags/setTimeou/"},{"name":"setInterval","slug":"setInterval","permalink":"http://yoursite.com/tags/setInterval/"}]},{"title":"对象的拓展","date":"2017-04-18T02:55:33.000Z","path":"2017/04/18/object/","text":"一、属性的简洁表示法ES6允许直接写入变量和函数 作为对象的属性和方法。 直接写变量时,属性名为变量名,属性值为变量值。 12345678910111213141516171819202122232425262728293031323334353637//例子1,属性简写var foo = 'bar';var baz = {foo};baz // {foo: \"bar\"}// 等同于var baz = {foo: foo};//例子2,属性简写function f(x, y) { return {x, y};}// 等同于function f(x, y) { return {x: x, y: y};}f(1, 2) // Object {x: 1, y: 2}//例子3,方法简写var o = { method() { return \"Hello!\"; }};// 等同于var o = { method: function() { return \"Hello!\"; }}; 二、属性名表达式js定义对象的属性有两种方法,一种是 直接用标识符作为属性,另一种是 用表达式作为属性名,这时要将表达式放在方括号之内。 123456// 方法一obj.foo = true;// 方法二obj['a' + 'bc'] = 123; 使用字面量方式定义对象时(使用大括号),在ES5中只能使用第一种方法(标识符)定义属性。ES6允许字面量定义对象时,用第二种方法(表达式)作为对象的属性名,即把表达式放在方括号内。 1234567891011121314151617181920212223242526272829303132333435363738394041//ES5var ojb={ foo:true, abc:123}//ES6let proKey =\"foo\";let obj={ [proKey]:true, ['a'+'bc']:123};//另一个例子var lastWord = 'last word';var a = { 'first word': 'hello', [lastWord]: 'world'};a['first word'] // \"hello\"a[lastWord] // \"world\"a['last word'] // \"world\"//表达式还可以用于定义方法名。let obj = { ['h' + 'ello']() { return 'hi'; }};obj.hello() // hi 三、Object.assign()Object.assign方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象(target)。其第一个参数是目标对象,后面的参数都是源对象。如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 12345678var target = { a: 1, b: 1 };var source1 = { b: 2, c: 2 };var source2 = { c: 3 };Object.assign(target, source1, source2);target // {a:1, b:2, c:3} 如果只有一个参数,Object.assign会直接返回该参数。 123var obj = {a: 1};Object.assign(obj) === obj // true 如果该参数不是对象,则会先转成对象,然后返回。 12typeof Object.assign(2) // \"object\" 由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。 123Object.assign(undefined) // 报错Object.assign(null) // 报错 如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。 1234let obj = {a: 1};Object.assign(obj, undefined) === obj // trueObject.assign(obj, null) === obj // true 特别注意 Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。12345var obj1 = {a: {b: 1}};var obj2 = Object.assign({}, obj1);obj1.a.b = 2;obj2.a.b // 2 上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。 对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。1234var target = { a: { b: 'c', d: 'e' } }var source = { a: { b: 'hello' } }Object.assign(target, source)// { a: { b: 'hello' } } Object.assign()的多用途1、为对象添加属性12345class Point { constructor(x, y) { Object.assign(this, {x, y}); }} 上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。 2、为对象添加方法12345678910111213141516Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· }});// 等同于下面的写法SomeClass.prototype.someMethod = function (arg1, arg2) { ···};SomeClass.prototype.anotherMethod = function () { ···}; 上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。 3、克隆对象123function clone(origin) { return Object.assign({}, origin);} 上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。 不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。 1234function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin);} 4、合并多个对象将多个对象合并到某个对象。 12const merge =(target, ...sources) => Object.assign(target, ...sources); 如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。 1const merge =(...sources) => Object.assign({}, ...sources); 5、为属性指定默认值12345678910const DEFAULTS = { logLevel: 0, outputFormat: 'html'};function processContent(options) { options = Object.assign({}, DEFAULTS, options); console.log(options); // ...} 上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。 四、属性遍历遍历对象的属性,都遵守同样的属性遍历的次序规则。 首先遍历所有属性名为数值的属性,按照数字排序。 其次遍历所有属性名为字符串的属性,按照生成时间排序。 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。 常用的五种方法: (1)for…in for…in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。 (2)Object.keys(obj) Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。 (3)Object.getOwnPropertyNames(obj) Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。 (4)Object.getOwnPropertySymbols(obj) Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性。 (5)Reflect.ownKeys(obj) Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。 五、Object.keys()ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。 1234var obj = { foo: 'bar', baz: 42 };Object.keys(obj)// [\"foo\", \"baz\"] 六、Object.values()Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。 基本用途: 返回一个包含所有属性值的数组,返回数组的成员顺序,跟Object.keys()排列规则一致一个字符串,会返回各个字符组成的一个数组 1234567891011var obj = { foo: 'bar', baz: 42 };Object.values(obj)// [\"bar\", 42]var obj = { 100: 'a', 2: 'b', 7: 'c' };Object.values(obj)// [\"b\", \"c\", \"a\"]//上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。 如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。 12Object.values('foo')// ['f', 'o', 'o'] 七、Object.entriesObject.entries方法返回一个数组,成员是参数对象本身的(不含继承)的所有可以遍历的属性的键值对数组。 基本用途: 1、遍历对象的属性2、将对象转为真正的Map结构 遍历对象的属性123456let obj={one:1,two:2}for(let [k,v] of Object.entries(obj)){ console.log(`${JSON.stringify(k)}:${JSON.stringify(v)}`)}// \"one\":1// \"two\":2 将对象转为真正的Map结构123var obj={foo:'bar',baz:'1'}var map=new Map(Object.entries(obj));map // {foo:'bar',baz:'1'}","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"object","slug":"object","permalink":"http://yoursite.com/tags/object/"}]},{"title":"函数拓展之:length属性和作用域","date":"2017-04-17T11:58:41.000Z","path":"2017/04/17/function-context/","text":"函数length属性指定了默认值之后,函数的length属性只返回没有指定默认值的参数个数,也就是 指定默认值之后,length属性失真了。 lenght属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也是不计入这个length属性的。如果设置默认值的参数不是尾参数,那么length属性也不再计入后面的参数了 123456789101112(function(a){}).length //1(function(a=5){}).length //0(function(a,b,c=5){}).length //2(function(...args){}).length //0(function(a,b=5,c){}).length //1(function(a=5,b,c){}).length //0 作用域一旦设置了参数的默认值,函数将进行声明初始化,参数将会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。 12345678910var x=1;function f(x,y=x){ console.log(y);}f(2) //2 上面代码中,参数 y的默认值等于变量x。调用函数f时,参数形成了一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,因此 输出 2。 123456789let x = 1;function f(y = x) { let x = 2; console.log(y);}f() // 1 上面代码中,函数f调用时,参数y=x形成一个单独的作用域。这个作用域里面,变量x本身是没有定义的,所以指向了外面的全局变量x。函数调用的时候,函数体内部的局部变量x是影响不到默认值变量x的。 123456789101112131415161718192021222324252627282930//例子1var x=1;function foo(x,y=function(){x=2;}){ var x=3; //重新声明的内部变量,和参数的单独作用域不同 y(); //y里面的x指向第一个参数x console.log(x);}foo() // 3x // 1//例子2var x=1;function foo(x,y=function(){x=2;}){ x=3; // 此时指向了第一个参数 y(); //y里面的x指向第一个参数x console.log(x);}foo() // 2x // 1 上面代码中,例子1的函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。 函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。 如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"length","slug":"length","permalink":"http://yoursite.com/tags/length/"},{"name":"context","slug":"context","permalink":"http://yoursite.com/tags/context/"}]},{"title":"函数拓展之:拓展运算符(...)","date":"2017-04-09T09:05:27.000Z","path":"2017/04/09/function-spread/","text":"扩展运算符(spread)是三个点(…)。 它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。 123456console.log(...[1, 2, 3])// 1 2 3console.log(1, ...[2, 3, 4], 5)// 1 2 3 4 5 主要作用于函数调用,引用对象主要是数组。 12345678function add(x, y) { return x + y;}var numbers = [4, 38];add(...numbers) // 42 应用1、替代数组的apply方法 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。 1234567891011121314151617181920212223// ES5的写法function f(x, y, z) { // ...}var args = [0, 1, 2];f.apply(null, args);// ES6的写法function f(x, y, z) { // ...}var args = [0, 1, 2];f(...args);// ES5的写法var arr1 = [0, 1, 2];var arr2 = [3, 4, 5];Array.prototype.push.apply(arr1, arr2);// ES6的写法var arr1 = [0, 1, 2];var arr2 = [3, 4, 5];arr1.push(...arr2); ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。 12345678910// ES5的写法var arr1 = [0, 1, 2];var arr2 = [3, 4, 5];Array.prototype.push.apply(arr1, arr2);// ES6的写法var arr1 = [0, 1, 2];var arr2 = [3, 4, 5];arr1.push(...arr2); // arr1.push(3,4,5); 2、合并数组1234567891011121314151617// ES5[1, 2].concat(more)// ES6[1, 2, ...more]var arr1 = ['a', 'b'];var arr2 = ['c'];var arr3 = ['d', 'e'];// ES5的合并数组arr1.concat(arr2, arr3);// [ 'a', 'b', 'c', 'd', 'e' ]// ES6的合并数组[...arr1, ...arr2, ...arr3]// [ 'a', 'b', 'c', 'd', 'e' ] 3、与结构赋值相结合1234567891011121314151617181920const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest // [2, 3, 4, 5]const [first, ...rest] = [];first // undefinedrest // []:const [first, ...rest] = [\"foo\"];first // \"foo\"rest // []/*如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。*/const [...butLast, last] = [1, 2, 3, 4, 5];// 报错const [first, ...middle, last] = [1, 2, 3, 4, 5];// 报错 4、字符串,字符串转为真正的数组。12[...'hello']// [ \"h\", \"e\", \"l\", \"l\", \"o\" ] 5、实现了Iterator接口的对象 任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。12var nodeList = document.querySelectorAll('div');var array = [...nodeList]; 上面代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口。 对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。123456789let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3};// TypeError: Cannot spread non-iterable object.let arr = [...arrayLike]; 上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。 6、Map和Set结构,Generator函数扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。 1234567let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'],]);let arr = [...map.keys()]; // [1, 2, 3]","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"...","slug":"","permalink":"http://yoursite.com/tags//"}]},{"title":"函数拓展之:rest参数(...变量名)","date":"2017-04-07T09:04:54.000Z","path":"2017/04/07/function-rest/","text":"ES6 引入rest参数(…变量名)用于获取函数多余的参数。rest参数是一个数组,将多余的参数放入到数组中。平时可以使用rest参数来代替arguments变量。rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 123456789101112function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum;}add(2, 5, 3) // 10 rest 参数代替arguments变量的例子 12345678// arguments变量的写法function sortNumbers() { return Array.prototype.slice.call(arguments).sort();}// rest参数的写法const sortNumbers = (...numbers) => numbers.sort(); rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。利用 rest 参数改写数组push方法的例子: 12345678910function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); });}var a = [];push(a, 1, 2, 3) rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。 1234// 报错function f(a, ...b, c) { // ...} 函数的length属性,不包括 rest 参数。 1234(function(a) {}).length // 1(function(...a) {}).length // 0(function(a, ...b) {}).length // 1","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"rest","slug":"rest","permalink":"http://yoursite.com/tags/rest/"}]},{"title":"变量解构赋值","date":"2017-04-03T03:10:46.000Z","path":"2017/04/03/destructuring/","text":"ES6 允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这个被称为解构。 解构的用途 交换变量的值 从函数返回多个值 函数参数的定义 提取JSON数据 函数参数的默认值 遍历Map结构 输入模块的指定方法 1、数组解构赋值(1) 完全解构,这种写法属于 “模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如: 1234567891011121314151617181920let [foo, [[bar], baz]] = [1, [[2], 3]];foo // 1bar // 2baz // 3let [ , , third] = [\"foo\", \"bar\", \"baz\"];third // \"baz\"let [x, , y] = [1, 2, 3];x // 1y // 3let [head, ...tail] = [1, 2, 3, 4];head // 1tail // [2, 3, 4]let [x, y, ...z] = ['a'];x // \"a\"y // undefinedz // [] (2) 不完全解构,等号左边的模式只匹配到一部分等号右边的数组。这种情况下依然可以解构成功。 12345678let [x, y] = [1, 2, 3];x // 1y // 2let [a, [b], d] = [1, [2, 3], 4];a // 1b // 2d // 4 如果等号的右边不是数组的话,就会报错。1234567// 报错let [foo] = 1;let [foo] = false;let [foo] = NaN;let [foo] = undefined;let [foo] = null;let [foo] = {}; 事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。1234567891011function* fibs() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; }}let [first, second, third, fourth, fifth, sixth] = fibs();sixth // 5 上面代码中,fibs是一个 Generator 函数,原生具有 Iterator 接口。解构赋值会依次从这个接口获取值。 2、对象的解构赋值对象解构赋值和数组的解构赋值不一样。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。 1234567891011//具体版let { foo: foo, bar: bar } = { foo: \"aaa\", bar: \"bbb\" };//简写版let { bar, foo } = { foo: \"aaa\", bar: \"bbb\" };foo // \"aaa\"bar // \"bbb\"let { baz } = { foo: \"aaa\", bar: \"bbb\" };baz // undefined 如果变量名与属性名不一致,必须写下面这样。 12345678var { foo: baz } = { foo: 'aaa', bar: 'bbb' };baz // \"aaa\"let obj = { first: 'hello', last: 'world' };let { first: f, last: l } = obj;f // 'hello'l // 'world' 对象的解构赋值机制是 先找到同名属性,然后再赋值给对应的变量。前者是匹配的模式,后者才是真正的变量。真正被赋值的应该是变量。 12345678910111213141516171819202122let { foo: baz } = { foo: \"aaa\", bar: \"bbb\" };baz // \"aaa\"foo // error: foo is not definedvar node = { loc: { start: { line: 1, column: 5 } }};var { loc: { start: { line }} } = node;line // 1loc // error: loc is undefinedstart // error: start is undefined//这由于只有line是变量,loc和start都是模式,所以不会被赋值啦。 如果解构失败,变量的值等于undefined。12let {foo} = {bar: 'baz'};foo // undefined 如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码。如: 12let {foo: {bar}} = {baz: 'baz'};// 报错 3、字符串的解构赋值字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。 12345678910const [a, b, c, d, e] = 'hello';a // \"h\"b // \"e\"c // \"l\"d // \"l\"e // \"o\"let {length : len} = 'hello';len // 5 4、函数参数解构赋值函数参数的解构也可以使用默认值 123456789101112131415function add([x, y]){ return x + y;}add([1, 2]); // 3function move({x = 0, y = 0} = {}) { return [x, y];}move({x: 3, y: 8}); // [3, 8]move({x: 3}); // [3, 0]move({}); // [0, 0]move(); // [0, 0] 5、默认值 (1)解构赋值允许指定默认值。因为ES6内部使用严格的相等运算符(===),如果一个数组成员不严格等于undefined,默认值不会生效的。如: 123456let [x = 1] = [undefined];x // 1let [x = 1] = [null];x // null, 因为null不严格等于 undefined (2)如果默认值是一个表达式的话,那么表达式是惰性求值的,即只有在用到的时候,才会求值。 123456789101112131415function f() { console.log('aaa');}let [x = f()] = [1];//等价于let x;if ([1][0] === undefined) { x = f();} else { x = [1][0];} 因为x能取到值,所以函数f根本不会执行。 6、具体用途(1)交换变量的值 12345let x = 1;let y = 2;[x, y] = [y, x]; (2)从函数返回多个值 12345678910111213141516171819202122232425// 返回一个数组function example() { return [1, 2, 3];}let [a, b, c] = example();a // 1b // 2c // 3// 返回一个对象function example() { return { foo: 1, bar: 2 };}let { foo, bar } = example();foo // 1bar // 2 (3)函数参数的定义 1234567// 参数是一组有次序的值function f([x, y, z]) { ... }f([1, 2, 3]);// 参数是一组无次序的值function f({x, y, z}) { ... }f({z: 3, y: 2, x: 1}); (4)提取JSON数据 ,快速提取 JSON 数据的值。解构赋值对提取JSON对象中的数据,尤其有用。 12345678910let jsonData = { id: 42, status: \"OK\", data: [867, 5309]};let { id, status, data: number } = jsonData;console.log(id, status, number);// 42, \"OK\", [867, 5309] (5)函数参数的默认值。 指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。 123456789101112jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config}) { // ... do stuff}; (6)遍历Map结构, 任何部署了Iterator接口的对象,都可以用for…of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。 12345678910111213141516171819var map = new Map();map.set('first', 'hello');map.set('second', 'world');for (let [key, value] of map) { console.log(key + \" is \" + value);}// first is hello// second is world// 获取键名for (let [key] of map) { // ...}// 获取键值for (let [,value] of map) { // ...} (7)输入模块的指定方法加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。 1const { SourceMapConsumer, SourceNode } = require(\"source-map\");","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"解构赋值","slug":"解构赋值","permalink":"http://yoursite.com/tags/解构赋值/"}]},{"title":"Promise对象的基本理解","date":"2017-04-01T06:51:49.000Z","path":"2017/04/01/promise/","text":"什么是 PromisePromise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。有如下特点: (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 基本用法Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。 resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。 简单实例1:123456789101112131415var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});promise.then(function(value) { // success}, function(error) { // failure}); 实例2: 123456789function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); });}timeout(100).then((value) => { console.log(value);}); 上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。 用Promise对象实现异步操作1234567891011121314151617181920212223242526272829var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open(\"GET\", url); client.onreadystatechange = handler; client.responseType = \"json\"; client.setRequestHeader(\"Accept\", \"application/json\"); client.send(); function handler() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; }); return promise;};getJSON(\"/posts.json\").then(function(json) { console.log('Contents: ' + json);}, function(error) { console.error('出错了', error);}); 如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。 123456789101112var p1 = new Promise(function (resolve, reject) { //... resolve();});var p2 = new Promise(function (resolve, reject) { // ... resolve(p1);});p2.then(result => console.log(result)) .catch(error => console.log(error)) Promise.prototype.then()Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。 then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。 例子如下: 12345getJSON(\"/posts.json\").then(function(json) { return json.post;}).then(function(post) { // ...}); 采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。 1234567getJSON(\"/post/1.json\").then(function(post) { return getJSON(post.commentURL);}).then(function funcA(comments) { console.log(\"Resolved: \", comments);}, function funcB(err){ console.log(\"Rejected: \", err);}); 上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用funcA,如果状态变为Rejected,就调用funcB。 如果采用箭头函数,上面的代码可以写得更简洁。 123456getJSON(\"/post/1.json\").then( post => getJSON(post.commentURL)).then( comments => console.log(\"Resolved: \", comments), err => console.log(\"Rejected: \", err)); Promise.all()Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。 1var p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。) p的状态由p1、p2、p3决定,分成两种情况。 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。 下面是一个具体的例子。12345678910// 生成一个Promise对象的数组var promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON(\"/post/\" + id + \".json\");});Promise.all(promises).then(function (posts) { // ...}).catch(function(reason){ // ...}); 上面代码中,promises是包含6个Promise实例的数组,只有这6个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。 Promise.race()Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。1var p = Promise.race([p1, p2, p3]); 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。 下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve。12345678const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) })]);p.then(response => console.log(response));p.catch(error => console.log(error)); 上面代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。 Promise.resolve()有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。 Promise.resolve等价于下面的写法。123Promise.resolve('foo')// 等价于new Promise(resolve => resolve('foo')) Promise.resolve方法的参数分成四种情况。 (1)参数是一个Promise实例 如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。 (2)参数是一个thenable对象 thenable对象指的是具有then方法的对象,比如下面这个对象。1234567891011121314151617let thenable = { then: function(resolve, reject) { resolve(42); }};Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。let thenable = { then: function(resolve, reject) { resolve(42); }};let p1 = Promise.resolve(thenable);p1.then(function(value) { console.log(value); // 42}); 上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出42。 (3)参数不是具有then方法的对象,或根本就不是对象 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为Resolved。 var p = Promise.resolve(‘Hello’); p.then(function (s){ console.log(s)});// Hello上面代码生成一个新的Promise对象的实例p。由于字符串Hello不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise实例的状态从一生成就是Resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。 (4)不带有任何参数 Promise.resolve方法允许调用时不带参数,直接返回一个Resolved状态的Promise对象。 所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。 12345var p = Promise.resolve();p.then(function () { // ...}); 上面代码的变量p就是一个Promise对象。 需要注意的是,立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。 12345678910111213setTimeout(function () { console.log('three');}, 0);Promise.resolve().then(function () { console.log('two');});console.log('one');// one// two// three 上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(’one‘)则是立即执行,因此最先输出。","tags":[{"name":"es6","slug":"es6","permalink":"http://yoursite.com/tags/es6/"},{"name":"Promise","slug":"Promise","permalink":"http://yoursite.com/tags/Promise/"}]},{"title":"基于vue2 + vue-router2 + vuex + axios 的一个webapp项目","date":"2017-03-15T01:44:49.000Z","path":"2017/03/15/vue-project1/","text":"这是基于vue2 + vue-router2 + vuex + axios 的一个webapp项目!主要想借此项目来学习vue,觉得光是看api和其他文章总结对vue理解感觉是一知半解,不够深刻。所以找网上找了相关的资料,自己动手写了这个demo,页面的数据是通过调用接口和页面爬取回来的,页面icon和数据保持和线上的一致,如有冒犯请告知,项目仅供参考学习。 参考资料列表 https://cn.vuejs.org/v2/api/ https://github.com/huang303513/Weather_Vue http://www.cnblogs.com/wisewrong/p/6558001.html http://www.cnblogs.com/coco1s/p/4954063.html http://www.jianshu.com/p/dc9a79f6ceb7# https://github.com/jokermonn/-Api/blob/master/CenterWeather.md 部分截图 项目地址:https://github.com/moedong/vue-wfcast1git clone https://github.com/moedong/vue-wfcast.git 安装1npm install 运行1npm run dev 发布1npm run build 技术栈在此DEMO中使用了一下技术 vue2 vue-router2 vuex webpack2 es6 axios cheerio echarts 目录结构 . ├── README.md ├── build // 构建服务和webpack配置 ├── config // 项目不同环境的配置 ├── dist // 项目build目录 ├── index.html // 项目入口文件 ├── package.json // 项目配置文件 ├── src │ ├── assets // css js 和图片资源 │ ├── components // 各种组件 │ ├── libs // 组件的公用模块 │ ├── router // 存放路由的文件夹 │ ├── vuex // 状态管理文件夹 │ ├── App.Vue // 模板文件入口 │ └── main.js // Webpack 预编译入口 │","tags":[{"name":"vue2","slug":"vue2","permalink":"http://yoursite.com/tags/vue2/"},{"name":"vuex","slug":"vuex","permalink":"http://yoursite.com/tags/vuex/"},{"name":"vue-router2","slug":"vue-router2","permalink":"http://yoursite.com/tags/vue-router2/"},{"name":"axios","slug":"axios","permalink":"http://yoursite.com/tags/axios/"}]},{"title":"http","date":"2017-03-02T01:24:40.000Z","path":"2017/03/02/http/","text":"一、HTTP method GET 是最常用的方法,用于请求服务器发送某个资源 HEAD和GET类似,但服务器在响应中只返回首部,不返回实体的主体部分 POST 主要是用来向服务器提交表单数据 PUT 让服务器用请求的主体部分来创建一个由所请求的URL命名的新文档,如果那个URL已经存在的话,就用这个主体代替他。(服务器创建文档) TRACE 用于验证请求是否如愿穿过了请求/响应链。目的服务器端发起一个环回请求诊断,最后一站的服务器会弹回一个TRACE响应并在响应体中携带它收到的原始请求报文。 OPTIONS 请求服务器告知其支持的各种功能。查询服务器支持哪些方法。 DELETE 请求服务器删除请求URL指定的资源。 二、HTTP状态码及其含义 1XX:信息状态码 > * 100 Continue:客户端应当继续发送请求。这个临时相应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求万仇向客户端发送一个最终响应 > * 101 Switching Protocols:服务器已经理解力客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到Upgrade消息头中定义的那些协议。 2XX:成功状态码 * 200 OK:请求成功,请求所希望的响应头或数据体将随此响应返回 * 201 Created: * 202 Accepted: * 203 Non-Authoritative Information: * 204 No Content: * 205 Reset Content: * 206 Partial Content: 3XX:重定向 * 300 Multiple Choices: * 301 Moved Permanently: * 302 Found: * 303 See Other: * 304 Not Modified: * 305 Use Proxy: * 306 (unused): * 307 Temporary Redirect: 4XX:客户端错误 * 400 Bad Request: * 401 Unauthorized: * 402 Payment Required: * 403 Forbidden: * 404 Not Found: * 405 Method Not Allowed: * 406 Not Acceptable: * 407 Proxy Authentication Required: * 408 Request Timeout: * 409 Conflict: * 410 Gone: * 411 Length Required: * 412 Precondition Failed: * 413 Request Entity Too Large: * 414 Request-URI Too Long: * 415 Unsupported Media Type: * 416 Requested Range Not Satisfiable: * 417 Expectation Failed: 5XX: 服务器错误 * 500 Internal Server Error: * 501 Not Implemented: * 502 Bad Gateway: * 503 Service Unavailable: * 504 Gateway Timeout: * 505 HTTP Version Not Supported: 三、HTTP request报文结构 1.首行是Request-Line包括:请求方法,请求URI,协议版本,CRLF2.首行之后是若干行请求头,包括general-header,request-header或者entity-header,每个一行以CRLF结束3.请求头和消息实体之间有一个CRLF分隔4.根据实际请求需要可能包含一个消息实体 一个请求报文例子如下: 123456789101112131415GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1Host: www.w3.orgConnection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36Referer: https://www.google.com.hk/Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8,en;q=0.6Cookie: authorstyle=yesIf-None-Match: \"2cc8-3e3073913b100\"If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMTname=qiu&age=25 四、HTTP response报文结构 1.首行是状态行包括:HTTP版本,状态码,状态描述,后面跟一个CRLF2.首行之后是若干行响应头,包括:通用头部,响应头部,实体头部3.响应头部和响应实体之间用一个CRLF空行分隔最后是一个可能的消息实体 响应报文例子如下: 1234567891011121314HTTP/1.1 200 OKDate: Tue, 08 Jul 2014 05:28:43 GMTServer: Apache/2Last-Modified: Wed, 01 Sep 2004 13:24:52 GMTETag: \"40d7-3e3073913b100\"Accept-Ranges: bytesContent-Length: 16599Cache-Control: max-age=21600Expires: Tue, 08 Jul 2014 11:28:43 GMTP3P: policyref=\"http://www.w3.org/2001/05/P3P/p3p.xml\"Content-Type: text/html; charset=iso-8859-1{\"name\": \"qiu\", \"age\": 25} 五、从浏览器地址栏输入url到显示页面的步骤1.在浏览器地址栏输入URL 2.浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤  i.如果资源未缓存,发起新请求  ii.如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。  iii.检验新鲜通常有两个HTTP头进行控制Expires和Cache-Control:    HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期    HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间 3.浏览器解析URL获取协议,主机,端口,path 4.浏览器组装一个HTTP(GET)请求报文 5.浏览器获取主机ip地址,过程如下:  i.浏览器缓存  ii.本机缓存  iii.hosts文件  iv.路由器缓存  v.ISP DNS缓存  vi.DNS递归查询(可能存在负载均衡导致每次IP不一样) 6.打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:   i.客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口  ii.服务器发回SYN=1, ACK=X+1, Seq=Y的响应包  iii.客户端发送ACK=Y+1, Seq=Z 7.TCP链接建立后发送HTTP请求 8.服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序 9.服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码 10.处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作 11.服务器将响应报文通过TCP连接发送回浏览器 12.浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:  i.主动方发送Fin=1, Ack=Z, Seq= X报文  ii.被动方发送ACK=X+1, Seq=Z报文  iii.被动方发送Fin=1, ACK=X, Seq=Y报文  iv.主动方发送ACK=Y, Seq=X报文 13.浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同 14.如果资源可缓存,进行缓存 15.对响应进行解码(例如gzip压缩) 16.根据资源类型决定如何处理(假设资源为HTML文档) 17.解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释 18.构建DOM树:  i.Tokenizing:根据HTML规范将字符流解析为标记  ii.Lexing:词法分析将标记转换为对象并定义属性和规则  iii.DOM construction:根据HTML标记关系将对象组成DOM树 19.解析过程中遇到图片、样式表、js文件,启动下载 20.构建CSSOM树:  i.Tokenizing:字符流转换为标记流  ii.Node:根据标记创建节点  iii.CSSOM:节点创建CSSOM树 21.根据DOM树和CSSOM树构建渲染树:   i.从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none  ii.对每一个可见节点,找到恰当的CSSOM规则并应用  iii.发布可视节点的内容和计算样式 22.js解析如下:   i.浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading   ii.HTML解析器遇到没有 async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write() 把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容   iii.当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素   iv.当文档完成解析,document.readState变成interactive   vi.浏览器在Document对象上触发DOMContentLoaded事件   vii.此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件 23.显示页面(HTML解析过程中会逐步显示页面) 六、如何使用缓存1、降低浏览器向服务器发出请求的次数如果页面已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。 足够新鲜的Headers General: Status Code:200 OK (from memory cache) 123456Request URL:http://www1.pconline.com.cn/images/blank.gifRequest Method:GETStatus Code:200 OK (from memory cache) // Status:200,Size:from memory cache,Time:0Remote Address:61.145.113.41:80Referrer Policy:no-referrer-when-downgrade 检验新鲜通常有两个HTTP头进行控制Expires和Cache-Control: HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期、(缓存的载止时间,允许客户端在这个时间之前不去检查)HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间(资源在本地缓存多少秒) 如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖。 2、向服务器进行验证请求2.1、Last-Modified在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记(HttpReponse Header)此文件在服务期端最后被修改的时间。 Response Header格式如下: Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT 客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头(HttpRequest Header),询问该时间之后文件是否有被修改过。 Request Header格式如下: If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT 如果服务器端的资源没有变化,则自动返回HTTP304(NotChanged.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。 2.2、ETagHTTP协议规格说明定义ETag为“被请求变量的实体标记”(参见14.19)。简单点即服务器响应时给请求URL标记,并在HTTP响应头中将其传送到客户端。 服务器端 response 返回的格式: Etag:“5d8c72a5edda8d6a:3239″ 浏览器的 request 格式是这样的: If-None-Match:“5d8c72a5edda8d6a:3239″ 如果ETag没改变,则返回状态304。 即:在客户端发出请求后,HttpReponse Header中包含Etag:“5d8c72a5edda8d6a:3239″标识,等于告诉Client端,你拿到的这个的资源有表示ID:5d8c72a5edda8d6a:3239。 当下次需要发Request索要同一个URI的时候,浏览器同时发出一个If-None-Match报头(Http RequestHeader)此时包头中信息包含上次访问得到的Etag:“5d8c72a5edda8d6a:3239″标识。If-None-Match:“5d8c72a5edda8d6a:3239“ 服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。304是HTTP状态码,服务器用来标识这个文件没修改,不返回内容,浏览器在接收到个状态码后,会使用浏览器已缓存的文件。 2.3、Last-Modified 和 EtagRFC 规定,如果 ETag 和 Last-Modified 都有,则必须一次性都发给服务器,没有优先级。如果服务器输出了 ETag,没有必要再输出 Last-Modified。 七、创建ajax过程(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象. (2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息. (3)设置响应HTTP请求状态变化的函数. (4)发送HTTP请求. (5)获取异步调用返回的数据. (6)使用JavaScript和DOM实现局部刷新. 123456789101112131415var xmlHttp = new XMLHttpRequest();xmlHttp.open('GET','demo.php','true');//第三个参数设置请求是否为异步模式。如果是TRUE,JavaScript函数将继续执行,而不等待服务器响应。当状态改变时会调用onreadystatechange属性指定的回调函数xmlHttp.send()xmlHttp.onreadystatechange = function(){ if(xmlHttp.readyState === 4 & xmlHttp.status === 200){ }} 八、JS脚本动态加载12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364function GetHttpRequest(){ if ( window.XMLHttpRequest ) // Gecko return new XMLHttpRequest() ; else if ( window.ActiveXObject ) // IE return new ActiveXObject(\"MsXml2.XmlHttp\") ; } function AjaxPage(sId,url,type){ var oXmlHttp = GetHttpRequest() ; oXmlHttp.OnReadyStateChange = function() { if ( oXmlHttp.readyState == 4 ) { if ( oXmlHttp.status == 200 || oXmlHttp.status == 304 ) { console.log(2222); type===\"js\" && IncludeJS( sId, url, oXmlHttp.responseText ); }else{ alert( 'XML request error: ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')' ) ; } } } oXmlHttp.open('GET', url, true); oXmlHttp.send(null); } function IncludeJS(sId, fileUrl, source) { if ( ( source != null ) && ( !document.getElementById( sId ) ) ){ var oHead = document.getElementsByTagName('HEAD').item(0); var oScript = document.createElement( \"script\" ); oScript.language = \"javascript\"; oScript.type = \"text/javascript\"; oScript.id = sId; oScript.defer = true; oScript.text = source; oHead.appendChild( oScript ); } }AjaxPage( \"scrA\", \"b.js\" ,\"js\");","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"},{"name":"http","slug":"http","permalink":"http://yoursite.com/tags/http/"}]},{"title":"分组操作符","date":"2017-02-05T09:51:40.000Z","path":"2017/02/05/packetOperator/","text":"括号() 是一个分组操作符,它的内部只能包含表达式(由 运算元 和 运算符(可选) 构成,并产生运算结果的语法结构),不能包含语句 函数表达式和函数声明,两者如何区别: 函数声明:   function 函数名称 (参数:可选){ 函数体 } 函数表达式:   function (参数:可选){ 函数体 } 如果不声明函数名称,它肯定是表达式。 如果声明了函数名称的话,ECMAScript是通过上下文来区分的:   如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,  如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。 123456789101112131415function foo(){} // 声明,因为它是程序的一部分var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分new function bar(){}; // 表达式,因为它是new表达式(function(){ function bar(){} // 声明,因为它是函数体的一部分})();(function foo(){}); // 函数表达式:包含在分组操作符内(var x = 5); // Unexpected token var。 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句 在一个表达式后面加上括号(),该表达式会立即执行,但是在一个语句后面加上括号(),是完全不一样的意思,他的只是分组操作符 1234567891011121314// 下面这个function在语法上是没问题的,但是依然只是一个语句// 加上括号()以后依然会报错,因为分组操作符需要包含表达式 function foo(){ /* code */ }(); // SyntaxError: Unexpected token ) // 但是如果你在括弧()里传入一个表达式,将不会有异常抛出// 但是foo函数依然不会执行function foo(){ /* code */ }( 1 ); // 因为它完全等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: function foo(){ /* code */ } ( 1 ); JavaScript里括弧()里面不能包含语句,所以在这一点上,解析器在解析function关键字的时候,会将相应的代码解析成function表达式,而不是function声明 12345678910111213141516// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了// 不过,请注意下一章节的内容解释var i = function () { return 10; } ();true && function () { /* code */ } ();0, function () { /* code */ } ();// 如果你不在意返回值,或者不怕难以阅读// 你甚至可以在function前面加一元操作符号!function () { /* code */ } ();~function () { /* code */ } ();-function () { /* code */ } ();+function () { /* code */ } ();","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"},{"name":"()","slug":"","permalink":"http://yoursite.com/tags//"}]},{"title":"关于H5平时工作中的一些知识点总结","date":"2017-01-01T16:27:25.000Z","path":"2017/01/02/html5/","text":"1、flex 弹性布局 flex-directionflex-wrapflex-flowjustify-content (水平)align-items (竖直 单轴)align-content (竖直 多轴) 1.1、flex-direction row(默认值):主轴为水平方向,起点在左端。row-reverse:主轴为水平方向,起点在右端。column:主轴为垂直方向,起点在上沿。column-reverse:主轴为垂直方向,起点在下沿。 1.2、flex-wrap nowrap(默认):不换行。 wrap:换行,第一行在上方。 wrap-reverse:换行,第一行在下方。 1.3、flex-flow flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。 123.box { flex-flow: <flex-direction> || <flex-wrap>;} 1.4、justify-content flex-start(默认值):左对齐flex-end:右对齐center: 居中space-between:两端对齐,项目之间的间隔都相等。space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。 1.5、align-items flex-start:交叉轴的起点对齐。flex-end:交叉轴的终点对齐。center:交叉轴的中点对齐。baseline: 项目的第一行文字的基线对齐。stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。 1.6、align-content flex-start:与交叉轴的起点对齐。flex-end:与交叉轴的终点对齐。center:与交叉轴的中点对齐。space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。stretch(默认值):轴线占满整个交叉轴。 2、文本垂直居中1<div class=\"box box1\"><span>我是垂直居中元素</span><i></i></div> 123456789101112131415161718192021/* 方法1:dispaly:table-cell */.box1{ text-align:center; display:table-cell; vertical-align:middle; }/* 方法2:display:flex */.box2{ display:flex; justify-content:center; align-items:center; text-align:center; }/* 方法3:translate(-50%,-50%) */.box3{ position:relative;}.box3 span{ position:absolute; left:50%; top:50%; -webkit-transform:translate(-50%,-50%); width:100%; text-align:center; }/* 方法4:兼容 ie ,添加一个 空标签 i */.box1{height:300px;width: 400px;text-align: center;}.box1 span{vertical-align: middle; display: inline-block; *display: inline; *zoom: 1;}.box1 i{ width: 0; height: 100%; vertical-align: middle;display:inline-block;} 3、伪元素实现换行,替代换行标签 《CSS SECRET》 中对 br 标签的描述是,这种方法不仅在可维护性方面是一种糟糕的实践,而且污染了结构层的代码,运用 after 伪元素,可以有一种非常优雅的解决方案。 通过给元素的 after 伪元素添加 content 为 “\\A” 的值。这里 \\A 表示的是什么呢?有一个 Unicode 字符是专门代表换行符的:0x000A 。 在 CSS 中,这个字符可以写作 “\\000A”, 或简化为 “\\A”。这里我们用它来作为 ::after 伪元素的内容。也就是在元素末尾添加了一个换行符的意思。而 white-space: pre; 的作用是保留元素后面的空白符和换行符,结合两者,就可以轻松实现在行内级元素末尾实现换行。 12345inline-element ::after{ content:\"\\A\"; white-space: pre;} 4、will-change提高页面滚动、动画等渲染性能12345678/* 关键字值 */will-change: auto;will-change: scroll-position;will-change: contents;will-change: transform; /* <custom-ident>示例 */will-change: opacity; /* <custom-ident>示例 */will-change: left, top; /* 两个<animateable-feature>示例 */ will-change的使用也要谨慎,遵循最小化影响原则,不要这样直接写在默认状态中: 12345678.will-change { will-change: transform; transition: transform 0.3s;}.will-change:hover { transform: scale(1.5);} 可以让父元素hover的时候,声明will-change,这样,移出的时候就会自动remove,触发的范围基本上是有效元素范围。 12345678910.will-change-parent:hover .will-change { will-change: transform;}.will-change { transition: transform 0.3s;}.will-change:hover { transform: scale(1.5);} 5、pc和wap通用的性能优化 利用缓存 1234567缓存Ajax使用CDN (CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求)服务端配置Etags (实体标签,HTTP协议的一部分,用一个特殊的字符串来标识某个资源的“版本”,客户端(浏览器)来请求的时候,可以比较,如果ETag一致,则表示该资源并没有修改过,客户端(浏览器)可以使用自己缓存的版本)减少DNS查找 减少HTTP请求(雪碧图,文件合并等),初始首屏之外的图片资源按需加载。 代码层面的优化:少用全局变量、减少DOM操作次数、缓存DOM节点查找的结果、避免图片和iFrame等的空Src(空Src会重新加载当前页面,影响速度和效率)、避免使用CSS Expression 6、移动页面性能优化 加载中的优化: 预加载: 显式预加载(loading提示) 、隐性加载(slide滚动图的后面图片的加载) 按需加载: 首屏加载 、 响应式加载 压缩图片 尽量避免重定向 使用其他方式加载图片 :css3绘制图片 、使用iconfont代替图片。 脚本的执行的优化: 尽量避免DataURI: 生成的代码文件相对图片文件体积没有减少反而增大,而且浏览器在对这种base64解码过程中需要消耗内存和cpu,这个在移动端坏处特别明显。 点击事件优化:适当使用touchstart,touchend,touch等事件代替延迟比较大的click事件。 渲染阶段的优化: 动画优化: 尽量使用css3动画(不占js主线程,可开启硬件加速,浏览器可以对动画做优化,不支持中间状态的监听)、适当使用canvas动画(可规避渲染树的计算,渲染更快,开发成本高)、合理使用RAF–requestAnimationFrame(能解决脚本问题引起的丢帧,卡顿问题,支持中间状态监听) 高频事件优化: 类似touchmove,scroll这类的事件可导致多次渲染,增加响应变化的时间间隔,减少重绘次数。 合成/绘制优化: GPU加速,触发GPU加速的方式:CSS3 transitions、 CSS3 3D transforms(transform: translateZ(0) )、will-change 总结","tags":[{"name":"html5","slug":"html5","permalink":"http://yoursite.com/tags/html5/"},{"name":"css3","slug":"css3","permalink":"http://yoursite.com/tags/css3/"}]},{"title":"逻辑思维","date":"2016-12-24T16:34:46.000Z","path":"2016/12/25/logic/","text":"逻辑1 实现判断传入的两个数组是否相似。具体需求: 数组中的成员类型相同,顺序可以不同。例如[1, true] 与 [false, 2]是相似的。 数组的长度一致。 类型的判断范围,需要区分:String, Boolean, Number, undefined, null, 函数,日期, window. 12345678思路:1、判断传入的参数是否为数组 (使用 instanceof 方法)2、检查两个数组长度是否一致3、分别判断数组内元素的基本数据类型 (使用 typeof 方法)4、因为 typeof 只能检查基本数据类型,对于 null, Date, window 返回的都是 object,所以使用 Object.prototype.toString.apply() 来检查这些对象类型,其返回值为:'[object Null]', '[object Date]', '[object global]'5、分别比较每个数组内元素的各种类型的个数,如果都相等,那么这两个数组是相似的。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 /* * param1 Array * param2 Array * return true or false */function arraysSimilar(arr1, arr2){ if(arr1 instanceof Array && arr2 instanceof Array){ var key1 = [],key2 = [],len = arr1.length,len2=arr2.length; // 数组的长度相等判断 if(len!=len2){return false;} // 类型相同判断 if(len){ // 获取类型列表 for(var i= 0;i<len;i++){ // 数组1的类型列表字串 var item1 = arr1[i], typeFirst = typeOf(item1); if(key1.join().indexOf(typeFirst)<0){ key1.push(typeFirst); } // 数组2的类型列表字串 var item2 = arr2[i],typeSecond = typeOf(item2); if(key2.join().indexOf(typeSecond)<0){ key2.push(typeSecond); } } key1 = key1.sort(); key2 = key2.sort(); // 类型字串比较 if(key1.join() == key2.join()){ return true; }else{ return false; } }else{ // 空数组相等 return true; } }else{ // 非数组 return false; }}/** * 类型判断方法 * param item * return type(string,function,boolean,number,undefined,null,window,Date,Array,object) */function typeOf(item){ var type = typeof item; if(type != \"object\"){ // 判断基本类型string,function,boolean,number,undefine }else if(item === null){ // check null type = \"null\"; }else if(item === window){ // check window type =\"window\"; }else{ // 判断object类型object,date,array if(item instanceof Date){ type = \"date\"; }else if(item instanceof Array){ type = 'array'; }else{ type = 'object'; } } return type;} 逻辑2 把一个数组arr按照指定的数组大小size分割成若干个数组块。例如: chunk([1,2,3,4],2)=[[1,2],[3,4]]; chunk([1,2,3,4,5],2)=[[1,2],[3,4],[5]]; 1234567891011function chunk(arr, size) { var arr1 = []; for (var i = 0; i < arr.length; i = i + size) { var arr2 = arr; arr1.push(arr2.slice(i, i + size)); } return arr1;}chunk([\"a\", \"b\", \"c\", \"d\"], 2); 逻辑3 实现一个摧毁(destroyer)函数,第一个参数是待摧毁的数组,其余的参数是待摧毁的值。如:destroyer([1, 2, 3, 1, 2, 3], 2, 3) 应该返回 [1, 1].destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3) 应该返回 [1, 5, 1]. 123456789101112131415function destroyer(arr) { // Remove all the values var tempArguments = arguments; return arr.filter(function(entry) { for(var i = 1; i< tempArguments.length; i++) { if (entry == tempArguments[i]) { return false; } } return true; });}destroyer([1, 2, 3, 1, 2, 3], 2, 3); 逻辑4 先给数组排序,然后找到指定的值在数组的位置,最后返回位置对应的索引。举例:where([1,2,3,4], 1.5) 应该返回 1。因为1.5插入到数组[1,2,3,4]后变成[1,1.5,2,3,4],而1.5对应的索引值就是1。 12345678910111213141516171819function where(arr, num) { // Find my place in this sorted array. //注意sort() 排序规则 arr.sort(function(a,b){ return a- b; }); for(var i =0;i<arr.length;i++){ if(arr[i]>num | arr[i] == num){ return i; } } return arr.length;}where([40, 60], 50); 逻辑5 移位密码也就是密码中的字母会按照指定的数量来做移位。 一个常见的案例就是ROT13密码,字母会移位13个位置。由’A’ ↔ ‘N’, ‘B’ ↔’O’,以此类推。 写一个ROT13函数,实现输入加密字符串,输出解密字符串。 所有的字母都是大写,不要转化任何非字母形式的字符(例如:空格,标点符号),遇到这些特殊字符,跳过它们。如:rot13(“SERR PBQR PNZC”) 应该解码为 “FREE CODE CAMP”rot13(“SERR CVMMN!”) 应该解码为 “FREE PIZZA!” 1234567891011121314151617181920function rot13(str) { // LBH QVQ VG! var arr = str.toUpperCase().split(\" \"); var str1 = []; for (var i = 0; i < arr.length; i++) { var arr1 = arr[i].split(\"\"); for (var j = 0; j < arr1.length; j++) { var num = arr1[j].charCodeAt(); if (num >= 65 && num <= 90) { arr1[j] = num + 13 > 90 ? String.fromCharCode(64 + (num + 13 - 90)):String.fromCharCode(num + 13); //64 + (num + 13 - 90) 要明白为什么是64 , } } str1.push(arr1.join(\"\")); } return str1.join(\" \");}// Change the inputs below to testrot13(\"SERR PBQR PNZC\"); 逻辑6 数组随机排序 12345678910111213141516171819/* Fisher–Yates shuffle */Array.prototype.shuffle = function() { var input = this; for (var i = input.length-1; i >=0; i--) { var randomIndex = Math.floor(Math.random()*(i+1)); var itemAtIndex = input[randomIndex]; input[randomIndex] = input[i]; input[i] = itemAtIndex; } return input;}[1,2,3,4,5,6,7,8].shuffle()//[4, 6, 3, 2, 5, 1, 7, 8] // 每次结果都是随机的 逻辑7 数组去重 1234567891011121314Array.prototype.unique = function(){ var res = []; var json = {}; for(var i = 0; i < this.length; i++){ if(!json[this[i]]){ res.push(this[i]); json[this[i]] = 1; } } return res;}var arr = [112,112,34,'你好',112,112,34,'你好','str','str1']; 逻辑8 旋转字符串 12345678910//先把字符串转化成数组,再借助数组的reverse方法翻转数组顺序,最后把数组转化成字符串。function reverseString(str) { str = str.split('').reverse().join(''); return str;}reverseString(\"hello\"); 逻辑9 确保字符串的每个单词首字母都大写,其余部分小写。(eg:titleCase(“I’m a little tea pot”) 应该返回 “I’m A Little Tea Pot”. titleCase(“sHoRt AnD sToUt”) 应该返回 “Short And Stout”.) 1234567891011function titleCase(str) { str = str.split(\" \");//按照空格把字符串分割成数组 for (var i = 0; i < str.length; i++) { str[i] = str[i].toLowerCase(); str[i] = str[i].substring(0, 1).toUpperCase() + str[i].substring(1); } return str.join(\" \");//通过空格把数组连接成字符串}titleCase(\"I'm a little tea pot\"); 逻辑10 找出最长的单词 12345678910111213141516171819202122232425262728293031323334353637383940//基本答案functionfindLongestWord(str){ // 按照空格分割字符串,生成数组 var strArr = str.split(\" \"); // 初始化 length 为 0 var length = 0; for (var i = 0; i < strArr.length; i++) { // 遍历过程中,若当前字符串长度比 length 大,就更新 length if (strArr[i].length > length) { length = strArr[i].length; } // 不需要 else,因为如果比 length 小,继续执行遍历就可以了 } // 循环结束,返回 length 作为结果 return length;}// 优化,数组内置方法 .reduce() 来实现functionfindLongestWord(str){ var stringArr = str.split(\" \"); return stringArr.reduce(function(prev, next){ // 返回值为参数与当前字符串中较大的数 // 返回值会作为下次计算的 prev 传入 return Math.max(prev, next.length); }, 0)}// 再优化,functionfindLongestWord(str){ return Math.max.apply(null, str.split(\" \").map(function(e){ return e.length; }))} 逻辑11 如果数组第一个字符串元素包含了第二个字符串元素的所有字符,函数返回true。举例: [“hello”, “Hello”] 应该返回true,因为在忽略大小写的情况下,第二个字符串的所有字符都可以在第一个字符串找到。 [“hello”, “hey”] 应该返回false,因为字符串”hello”并不包含字符”y”。 [“Alien”, “line”] 应该返回true,因为”line”中所有字符都可以在”Alien”找到。 1234567891011function mutation(arr) { var a = arr[0].toLowerCase(); var b = arr[1].toLowerCase(); for(var i = 0; i < b.length; i++){ if(a.indexOf(b[i]) < 0){ return false; } } return true;} 逻辑12 生成a-b之间的随机数 123456function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min;}getRandomInt(a,b) // 5 逻辑13 生成n个随机字符 1234567891011121314function random_str(length) { var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; ALPHABET += 'abcdefghijklmnopqrstuvwxyz'; ALPHABET += '0123456789-_'; var str = ''; for (var i=0; i < length; ++i) { var rand = Math.floor(Math.random() * ALPHABET.length); str += ALPHABET.substring(rand, rand + 1); } return str;}random_str(6) // \"NdQKOr\" 逻辑14 冒泡排序 1234567891011121314function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len; i++) { for (var j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j+1]) { //相邻元素两两对比 var temp = arr[j+1]; //元素交换 arr[j+1] = arr[j]; arr[j] = temp; } } } return arr;} 逻辑15 选择排序 1234567891011121314151617function selectionSort(arr) { var len = arr.length; var minIndex, temp; for (var i = 0; i < len - 1; i++) { minIndex = i; for (var j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { //寻找最小的数 minIndex = j; //将最小数的索引保存 } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr;} 逻辑16 插入排序 123456789101112131415function insertionSort(arr) { var len = arr.length; var preIndex, current; for (var i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while(preIndex >= 0 && arr[preIndex] > current) { arr[preIndex+1] = arr[preIndex]; preIndex--; } arr[preIndex+1] = current; } return arr;} 二分法排序1234567891011121314151617function quickSort(arr){ if(arr.length<=1){ return arr; } var nowNode=arr.splice(Math.floor(arr.length/2),1); //获取数组中间的值 var leftArr=[]; var rightArr=[]; for(var i=0;i<arr.length;i++){ if(parseInt(arr[i])<=nowNode){ leftArr.push(arr[i]); }else{ rightArr.push(arr[i]); } } return quickSort(leftArr).concact(nowNode,quickSort(rightArr));} 逻辑17 实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制 123456789Object.prototype.clone=function(){ var o=this.constructor===Array?[]:{}; for(var e in this){ o[e]=typeOf this[e]===\"object\"?this[e].clone():this[e]; } return o;} js作用域1234567(function(){ return typeof arguments; })();//问自动执行函数会返回什么值// 就是考Arguments对象的typeof// 看平时用firebug多不多了…… 12345var f = function g(){ return 23; }; typeof g();//问最后一行的执行结果//根据标准,命名函数表达式的函数名只对函数体内可见//因此报错 1234567 (function(x){ delete x; return x; })(1);//问自动执行函数会返回什么值// 参数不可删除//1 12345 var y = 1, x = y = typeof x; x;//问最后一行的执行结果//声明两个变量x与y,y最初赋为1,x没有赋值,默认赋给window的一个属性undefined,//因此typeof undefined为\"undefined\",最后x= y= \"undefined\" 123456789 (function f(f){ return typeof f(); })(function(){ return 1; });//问自动执行函数会返回什么值//函数名被优先级更高的参数名覆盖了 --->// (function (f){// return typeof f();// })(function(){ return 1; });//typeof 1 ---> \"number\" 12345678910111213141516 var foo = { bar: function() { return this.baz; }, baz: 1 }; (function(){ return typeof arguments[0](); })(foo.bar);//问自动执行函数会返回什么值//我们把下面那个自动执行函数分解一下//var a = function(){// return typeof arguments[0]();//};//a(foo.bar)//执行完arguments[0](),即得到this.baz//由于this变量在此绑定失效,它指向window,window有bax变量吗?//没有,返回\"undefined\" 123456789101112 var foo = { bar: function(){ return this.baz; }, baz: 1 } typeof (f = foo.bar)();//问最后一行的执行结果//我们把最后一行分解一下//window.f//f= foo.bar// f()// typeof f()//返回\"undefined\" 12345678 var f = (function f(){ return \"1\"; }, function g(){ return 2; })(); typeof f;//问最后一行的执行结果//首先要理解分组选择符,最后a会赋给什么呢?// var a = (1,2,3);// alert(a) ---> 3//那么这就简单了,f = function(){return 2};//typeof f() ---> number 12345678910111213var x = 1; if (function f(){}) { x += typeof f; } x;//问x的值//函数声明只能裸露于全局作用域下或位于函数体中。 //从句法上讲,它们不能出现在块中,例如不能出现在 // if、while 或 for 语句中。因为块只能包含语句, //因此if()中的f函数不能当做函数声明,当成表达式使用 //可能在预编译阶段做了如下处理 //if((XXX = function(){})) //因此我们是找不到f的 // 1undefined 12345 var x = [typeof x, typeof y][1]; typeof typeof x;//问最后一行的执行结果//数组其实就是这个样子[\"undefined\",\"undefined\"]//\"string\" 123456789101112 (function(foo){ return typeof foo.bar; })({ foo: { bar: 1 } });//问自动执行函数会返回什么值//分解一下// var bb = { foo: { bar: 1 } }// (function(j){// return typeof j.bar// })(bb)// \"undefined\"//注意那个对象只有foo属性,没有bar属性 123456789 (function f(){ function f(){ return 1; } return f(); function f(){ return 2; } })();//问自动执行函数会返回什么值//函数声明会在预编译阶段被提前//2 12345function f(){ return f; } new f() instanceof f;//问这一行的值//由于函数f会返回自身,这个new 就形同虚设//如果f的形式为 function f(){return this}或function f(){}就不一样//false 12345with (function(x, undefined){}) length;//问length的值为多少//with就是一个读写器,题意是取出函数的length属性//而函数的length就是指它形参的长度 //2","tags":[{"name":"js","slug":"js","permalink":"http://yoursite.com/tags/js/"}]},{"title":"Hexo(yilia)+Github构建个人博客错误收集","date":"2016-12-18T11:58:41.000Z","path":"2016/12/18/hello-world/","text":"1. 报错: ERROR Plugin load failed: hexo-generator-json-content按照这篇教程 一步步走,然后执行 hexo s,打开 http://localhost:4000/ 可以看到主页了,但是 点击左下角的“所有文章”的时候,发现 文章列表里面出现提示 缺少模块, 需要在根目录下导入该包hexo-generator-json-content。 配置根目录下的 _config.xml文件,配置代码有提示。 当导入完包后,重新执行 hexo g ,cmd上面报错了 ERROR Plugin load failed: hexo-generator-json-content,查找相关的文章资料,是由于node版过低造成的,解决办法是 把node升级到6.0版本及以上。 2. 报错:ERROR Deployer not found: github解决办法:在根目录下 run npm install hexo-deployer-git --save,将 _config.xml 中的 deploy 的type的值 改为 git 1234deploy: type: git repository: git@github.com:xxx/xxx.github.io.git branch: master 3. 如何在多台电脑上更新博客解决办法: 1、git clone https://github.com/xxx/xxx.github.io 建立本地仓库2、 4. 报错:Host key verification failed. fatal: Could not read from remote repository解决办法: 1、在当前目录下打开 git bash, 然后输入 :ssh git@github.com ,回复 “yes”2、界面出现 “Connection to github.com closed” 成功。","tags":[{"name":"hexo","slug":"hexo","permalink":"http://yoursite.com/tags/hexo/"},{"name":"github","slug":"github","permalink":"http://yoursite.com/tags/github/"}]}]