-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjin-se-meng-xiang.ghost.2016-09-13.json
1 lines (1 loc) · 486 KB
/
jin-se-meng-xiang.ghost.2016-09-13.json
1
{"db":[{"meta":{"exported_on":1473756839184,"version":"004"},"data":{"posts":[{"id":1,"uuid":"7773c41c-7513-46d6-a5eb-a1256d0f73a2","title":"Welcome to Ghost","slug":"welcome-to-ghost","markdown":"You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in a URL, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. Perhaps you've started using a new blogging platform and feel the sudden urge to share their slogan? A quote might be just the way to do it!\n\n> Ghost - Just a blogging platform\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)","html":"<p>You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at <code><your blog URL>/ghost/</code>. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!</p>\n\n<h2 id=\"gettingstarted\">Getting Started</h2>\n\n<p>Ghost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!</p>\n\n<p>Writing in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use <em>shortcuts</em> to <strong>style</strong> your content. For example, a list:</p>\n\n<ul>\n<li>Item number one</li>\n<li>Item number two\n<ul><li>A nested item</li></ul></li>\n<li>A final item</li>\n</ul>\n\n<p>or with numbers!</p>\n\n<ol>\n<li>Remember to buy some milk </li>\n<li>Drink the milk </li>\n<li>Tweet that I remembered to buy the milk, and drank it</li>\n</ol>\n\n<h3 id=\"links\">Links</h3>\n\n<p>Want to link to a source? No problem. If you paste in a URL, like <a href=\"http://ghost.org\">http://ghost.org</a> - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to <a href=\"http://ghost.org\">the Ghost website</a>. Neat.</p>\n\n<h3 id=\"whataboutimages\">What about Images?</h3>\n\n<p>Images work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:</p>\n\n<p><img src=\"https://ghost.org/images/ghost.png\" alt=\"The Ghost Logo\" /></p>\n\n<p>Not sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:</p>\n\n<h3 id=\"quoting\">Quoting</h3>\n\n<p>Sometimes a link isn't enough, you want to quote someone on what they've said. Perhaps you've started using a new blogging platform and feel the sudden urge to share their slogan? A quote might be just the way to do it!</p>\n\n<blockquote>\n <p>Ghost - Just a blogging platform</p>\n</blockquote>\n\n<h3 id=\"workingwithcode\">Working with Code</h3>\n\n<p>Got a streak of geek? We've got you covered there, too. You can write inline <code><code></code> blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.</p>\n\n<pre><code>.awesome-thing {\n display: block;\n width: 100%;\n}\n</code></pre>\n\n<h3 id=\"readyforabreak\">Ready for a Break?</h3>\n\n<p>Throw 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.</p>\n\n<hr />\n\n<h3 id=\"advancedusage\">Advanced Usage</h3>\n\n<p>There's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.</p>\n\n<p><input type=\"text\" placeholder=\"I'm an input field!\" /></p>\n\n<p>That should be enough to get you started. Have fun - and let us know what you think :)</p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463567128012,"created_by":1,"updated_at":1463567128012,"updated_by":1,"published_at":1463567128044,"published_by":1},{"id":2,"uuid":"9a9d44c4-cfa6-45eb-85aa-0ad8e81d4389","title":"redux源码分析系列之compose.js","slug":"compose-js","markdown":"## compose.js\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27566686&auto=1&height=66\"></iframe>\n\n听宝宝的话要早点睡,所以就先写最简单的一个哈😳!废话不多说先贴源码:\n\n```\nexport default function compose(...funcs) {\n if (funcs.length === 0) {\n return arg => arg\n } else {\n const last = funcs[funcs.length - 1]\n const rest = funcs.slice(0, -1)\n return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))\n }\n}\n```\n\n先来看看代码的意思,如果没有传入函数,则返回了一个接受参数并原样返回的函数;如果传入的函数数组长度大于0,则取出右边的函数为`last`,其余的为`rest`,然后返回一个函数。这个函数用`last(...args)`为高阶函数`reduceRight`迭代的初始值进行迭代,每次迭代的结果将被传入下一个函数充当参数。\n> 注:`reduceRight`为es5引入的方法,使用形式如下Array.reduceRight((prev,current,index,array) => (...), initialValue),它会为数组中的元素从右向左执行回调方法。\n\n举例来说,有三个函数`a,b,c`,参数为args,`f=compose(a,b,c)`,`f(...args)`执行的效果就和`a(b(c(...args)))`一样,目的就是将多个函数组合起来。在redux中像`middleware`、`reducer`等多处使用到了compose。不早了,今天先到这哈😀!\n\n","html":"<h2 id=\"composejs\">compose.js</h2>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27566686&auto=1&height=66\"></iframe>\n\n<p>听宝宝的话要早点睡,所以就先写最简单的一个哈😳!废话不多说先贴源码:</p>\n\n<pre><code>export default function compose(...funcs) { \n if (funcs.length === 0) {\n return arg => arg\n } else {\n const last = funcs[funcs.length - 1]\n const rest = funcs.slice(0, -1)\n return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))\n }\n}\n</code></pre>\n\n<p>先来看看代码的意思,如果没有传入函数,则返回了一个接受参数并原样返回的函数;如果传入的函数数组长度大于0,则取出右边的函数为<code>last</code>,其余的为<code>rest</code>,然后返回一个函数。这个函数用<code>last(...args)</code>为高阶函数<code>reduceRight</code>迭代的初始值进行迭代,每次迭代的结果将被传入下一个函数充当参数。</p>\n\n<blockquote>\n <p>注:<code>reduceRight</code>为es5引入的方法,使用形式如下Array.reduceRight((prev,current,index,array) => (...), initialValue),它会为数组中的元素从右向左执行回调方法。</p>\n</blockquote>\n\n<p>举例来说,有三个函数<code>a,b,c</code>,参数为args,<code>f=compose(a,b,c)</code>,<code>f(...args)</code>执行的效果就和<code>a(b(c(...args)))</code>一样,目的就是将多个函数组合起来。在redux中像<code>middleware</code>、<code>reducer</code>等多处使用到了compose。不早了,今天先到这哈😀!</p>","image":"/content/images/2016/05/687474703a2f2f692e696d6775722e636f6d2f4a65567164514d2e706e67-2.png","featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463567445619,"created_by":1,"updated_at":1463802780875,"updated_by":1,"published_at":1463755965662,"published_by":1},{"id":3,"uuid":"4cfddac0-e27b-478d-a53e-21edf2583e73","title":"mac木有随意行的解决方案——实践篇","slug":"macmu-you-sui-yi-xing-de-jie-jue-fang-an-shi-jian-pian","markdown":"由于把旧的电脑带回家了,宝宝再也不能够在床上玩电脑了,可恶的是还要配置mac的网络。万恶的移动没有提供mac版的随意行,因此需要自己设置vpn,可怕!宝宝慌了,还好凭借宝宝的聪明脑瓜,把网络配置好了,先拿出来分享。至于其中原理,宝宝还要研究研究,下次分享哈!\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26590186&auto=1&height=66\"></iframe>\n\n#### 第一步 买一个端口转换器\n\n> 注:附上我的购买链接[点我点我。](https://detail.tmall.com/item.htm?_u=l22095huca16&id=524667361946)不过这个上网网速还不如实验室2M小水管,建议大家上京东买正版,可恶的奎君周不早说😒!\n\n#### 第二步 连接网线、端口转换器、usb端口\n\n> 此时会自动产生如下图选中的Apple USB Ethernet Adapter。\n\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f776b73grwj31140vadl6.jpg)\n\n#### 第三步 选择此处的创建PPPoE服务\n\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f776e04d0qj31140vatf6.jpg)\n\n#### 第四步 设置PPPoE服务相关参数,也就是宽带连接的用户名密码\n\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f776bw1r5qj31140va44m.jpg)\n\n#### 第五步 创建vpn,选择IPSec上的L2TP\n\n![](https://ws3.sinaimg.cn/large/006bH5BKgw1f776eeuu5qj31140va44u.jpg)\n\n### 第六步 设置vpn相关参数,服务器地址和账户名,密码,并把高级设置里的通过VPN发送所有流量钩上\n\n![](https://ws4.sinaimg.cn/large/006bH5BKgw1f776eulgsgj31140va0yw.jpg)\n\n### 第七步 增加 /etc/ppp/options 配置文件,内容如下图\n\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f776fek55yj30vo0p2mz7.jpg)\n\n![](https://ws3.sinaimg.cn/large/006bH5BKgw1f776fqp7x6j30vo0p275d.jpg)\n","html":"<p>由于把旧的电脑带回家了,宝宝再也不能够在床上玩电脑了,可恶的是还要配置mac的网络。万恶的移动没有提供mac版的随意行,因此需要自己设置vpn,可怕!宝宝慌了,还好凭借宝宝的聪明脑瓜,把网络配置好了,先拿出来分享。至于其中原理,宝宝还要研究研究,下次分享哈!</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26590186&auto=1&height=66\"></iframe>\n\n<h4 id=\"\">第一步 买一个端口转换器</h4>\n\n<blockquote>\n <p>注:附上我的购买链接<a href=\"https://detail.tmall.com/item.htm?_u=l22095huca16&id=524667361946\">点我点我。</a>不过这个上网网速还不如实验室2M小水管,建议大家上京东买正版,可恶的奎君周不早说😒!</p>\n</blockquote>\n\n<h4 id=\"usb\">第二步 连接网线、端口转换器、usb端口</h4>\n\n<blockquote>\n <p>此时会自动产生如下图选中的Apple USB Ethernet Adapter。</p>\n</blockquote>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f776b73grwj31140vadl6.jpg\" alt=\"\" /></p>\n\n<h4 id=\"pppoe\">第三步 选择此处的创建PPPoE服务</h4>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f776e04d0qj31140vatf6.jpg\" alt=\"\" /></p>\n\n<h4 id=\"pppoe\">第四步 设置PPPoE服务相关参数,也就是宽带连接的用户名密码</h4>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f776bw1r5qj31140va44m.jpg\" alt=\"\" /></p>\n\n<h4 id=\"vpnipsecl2tp\">第五步 创建vpn,选择IPSec上的L2TP</h4>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/006bH5BKgw1f776eeuu5qj31140va44u.jpg\" alt=\"\" /></p>\n\n<h3 id=\"vpnvpn\">第六步 设置vpn相关参数,服务器地址和账户名,密码,并把高级设置里的通过VPN发送所有流量钩上</h3>\n\n<p><img src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f776eulgsgj31140va0yw.jpg\" alt=\"\" /></p>\n\n<h3 id=\"etcpppoptions\">第七步 增加 /etc/ppp/options 配置文件,内容如下图</h3>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f776fek55yj30vo0p2mz7.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/006bH5BKgw1f776fqp7x6j30vo0p275d.jpg\" alt=\"\" /></p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463834942922,"created_by":1,"updated_at":1472196279679,"updated_by":1,"published_at":1463837040160,"published_by":1},{"id":4,"uuid":"52d5cb6e-e34b-46a8-a91f-adda39d7e944","title":"mac安装mongodb","slug":"macan-zhuang-mongodb","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=41665696&auto=1&height=66\"></iframe>\n\n#### 安装Homebrew\n Homebrew是Mac OSX下一个包依赖管理工具,用它来安装软件非常的方便只需要brew install 软件名这一条命令就可以将你所需要的软件安装好,不用再操心安装过程中软件的依赖问题,这些问题Homebrew统统帮你搞定。Homebrew安装方法也很简单:打开终端,然后输入命令\n \n<!--bash-->\n\truby -e \"$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)\"\n\n#### 安装mongodb\n 如果是之前就已经安装好的Homebrew,那先要更新一下库:\n \n<!--bash-->\n\tbrew update\n 然后执行下面命令安装mongodb:\n \n<!--bash-->\n\tbrew install mongodb\n --with-openssl参数表示通过ssl源安装,--devel表示安装最新开发版。此时通过mongod --config /usr/local/etc/mongodb.conf命令已经可以启动。(3.0.7版本是如此。)\n\n#### 遇到的几个问题\n1.\t我第一次安装完以后,装了Robomongo,但是在连接时发现了bug,一直Authorization skip by you的错误。然后改dbpath、logpath等等操作,都不奏效,就把mongodb玩坏了。最后google得到Robomongo还不支持mongodb3,解决方案在此[http://http://liyanjie918.blog.163.com/blog/static/2022729020156261410274/](http://http://liyanjie918.blog.163.com/blog/static/2022729020156261410274/ \"使用Robomongo 连接MongoDB 3.x 报 Authorization failed 解决办法\")\n2.\t可能还会遇到找不到/data/db路径,这是mongdb在之前版本时默认的dbpath,如果安装的是2.*的版本可能就会遇到这个问题,就需要:mkdir -p /data/db\n另外,最后还会有一个权限问题,用sudo chmod R 当前用户名 /data 命令能够解决。\n3.\t大致问题如上,出现这些问题主要还是对mac系统文件和权限等不熟,已经太久没有动mongo,所以也有点生疏了。","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=41665696&auto=1&height=66\"></iframe>\n\n<h4 id=\"homebrew\">安装Homebrew</h4>\n\n Homebrew是Mac OSX下一个包依赖管理工具,用它来安装软件非常的方便只需要brew install 软件名这一条命令就可以将你所需要的软件安装好,不用再操心安装过程中软件的依赖问题,这些问题Homebrew统统帮你搞定。Homebrew安装方法也很简单:打开终端,然后输入命令\n \n<!--bash--> \n\n<pre><code>ruby -e \"$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)\"\n</code></pre>\n\n<h4 id=\"mongodb\">安装mongodb</h4>\n\n 如果是之前就已经安装好的Homebrew,那先要更新一下库:\n \n<!--bash--> \n\n<pre><code>brew update\n</code></pre>\n\n 然后执行下面命令安装mongodb:\n \n<!--bash--> \n\n<pre><code>brew install mongodb\n</code></pre>\n\n<p> --with-openssl参数表示通过ssl源安装,--devel表示安装最新开发版。此时通过mongod --config /usr/local/etc/mongodb.conf命令已经可以启动。(3.0.7版本是如此。)</p>\n\n<h4 id=\"\">遇到的几个问题</h4>\n\n<ol>\n<li>我第一次安装完以后,装了Robomongo,但是在连接时发现了bug,一直Authorization skip by you的错误。然后改dbpath、logpath等等操作,都不奏效,就把mongodb玩坏了。最后google得到Robomongo还不支持mongodb3,解决方案在此<a href=\"http://http://liyanjie918.blog.163.com/blog/static/2022729020156261410274/\" title=\"使用Robomongo 连接MongoDB 3.x 报 Authorization failed 解决办法\">http://http://liyanjie918.blog.163.com/blog/static/2022729020156261410274/</a> </li>\n<li>可能还会遇到找不到/data/db路径,这是mongdb在之前版本时默认的dbpath,如果安装的是2.*的版本可能就会遇到这个问题,就需要:mkdir -p /data/db <br />\n另外,最后还会有一个权限问题,用sudo chmod R 当前用户名 /data 命令能够解决。</li>\n<li>大致问题如上,出现这些问题主要还是对mac系统文件和权限等不熟,已经太久没有动mongo,所以也有点生疏了。</li>\n</ol>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463838003388,"created_by":1,"updated_at":1463838618095,"updated_by":1,"published_at":1463838208563,"published_by":1},{"id":5,"uuid":"2b51ebd5-cda4-4792-b84e-5874f72d42a2","title":"koa1源码分析之Application","slug":"koa1yuan-ma-fen-xi-zhi-application","markdown":"##Application\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=32507038&auto=1&height=66\"></iframe>\n\n原来写的文章,迁移过来,还是koa1的,koa2也出了,待更新。\n\n###依赖\n\n- debug:debug源码分析;\n- compose_es7:与co.wrap相似,不过能够接受 async/await 函数;\n- onFinished:捕捉finish或error事件,并根据第二个参数执行回调;\n- compose:compose_es7的简化版,只接受generator;\n- isJSON:顾名思义,判断传入参数是否为JSON;\n- statuses:将http码分为三类,redirect、empty以及retry,然后根据传入的http来判断属于哪一类;\n- accepts:根据req的内容判断(content negotiation)字段,如accept,Accept-Encoding,Accept-Language等字段对这些字段进行格式化,方便后续处理;\n- only:只返回在传入参数里所包含的字段;\n- co:co源码解析\n\n###构造器源码\n\n<!--javascript-->\n\t/**\n\t * Initialize a new `Application`.\n\t *\n\t * @api public\n\t */\n\tfunction Application() {\n\t if (!(this instanceof Application)) return new Application;\n\t this.env = process.env.NODE_ENV || 'development';\n\t this.subdomainOffset = 2;\n\t this.middleware = [];\n\t this.proxy = false;\n\t this.context = Object.create(context);\n\t this.request = Object.create(request);\n\t this.response = Object.create(response);\n\t}\n\n上面koa的Application的构造函数,可以看到在创建Application时有初始化如下属性:\n\n1. env表示是开发环境还是生产环境,通过process.env.NODE_ENV读取环境变量中的参数设置,如果没有设置默认为开发环境\n2. subdomainOffset,表示子域名的偏移,默认为2\n3. proxy如果为true,则解析 \"Host\" 的 header 域,并支持X-Forwarded-Host,默认为false。\n4. context上下文\n5. request请求对象\n6. response响应对象\n\n#### 有哪些方法\n\n#### app.listen\n\n<!--javascript-->\n\t/**\n\t * Shorthand for:\n\t *\n\t * http.createServer(app.callback()).listen(...)\n\t *\n\t * @param {Mixed} ...\n\t * @return {Server}\n\t * @api public\n\t */\n\tapp.listen = function(){\n\t debug('listen');\n\t var server = http.createServer(this.callback());\n\t return server.listen.apply(server, arguments);\n\t};\n\n 这个方法调用了node原生的http模块创建了一个server,要关注的是传入的这个callback。\n\n\n\n#### app.callback\n<!--javascript-->\n\n\t/**\t\n\t * Return a request handler callback\n\t * for node's native http server.\n\t *\n\t * @return {Function}\n\t * @api public\n\t */\n\tapp.callback = function(){\n\t var fn = this.experimental\n\t ? compose_es7(this.middleware)\n\t : co.wrap(compose(this.middleware));\n\t var self = this;\n\t if (!this.listeners('error').length) this.on('error', this.onerror);\n\t return function(req, res){\n\t res.statusCode = 404;\n\t var ctx = self.createContext(req, res);\n\t onFinished(res, ctx.onerror);\n\t fn.call(ctx).then(function () {\n\t respond.call(ctx);\n\t }).catch(ctx.onerror);\n\t }\n\t};\n\n 返回一个适合 http.createServer() 方法的回调函数用来处理请求。 您也可以使用这个回调函数将您的app挂载在 Connect/Express 应用上。一点点看下来,this.middleware是中间件的一个数组,用co.wrap或者compose_es7进行包装,co是tj大神的一个用来包装执行generator函数的库,而compose_es7应该是es7可能会引入的相似功能的特性,不同的是后者支持es7的async/await。后面一句处理错误,最后是返回一个请求处理函数。在函数中会调用createContext创建上下文,用onFinished模块捕捉finish或error事件,并根据第二个参数执行回调。然后调用co包装返回的promise对象进行中间件的调用,调用就由co来进行。调用成功后执行调用respond函数。\n\n#### 最后就来看看respond函数\n\n<!--javascript-->\n\n\t/**\t\n\t * Response helper.\n\t */\n\tfunction respond() {\n\t // allow bypassing koa\n\t if (false === this.respond) return;\n\t var res = this.res;\n\t if (res.headersSent || !this.writable) return;\n\t var body = this.body;\n\t var code = this.status;\n\t // ignore body\n\t if (statuses.empty[code]) {\n\t // strip headers\n\t this.body = null;\n\t return res.end();\n\t }\n\t if ('HEAD' == this.method) {\n\t if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));\n\t return res.end();\n\t }\n\t // status body\n\t if (null == body) {\n\t this.type = 'text';\n\t body = this.message || String(code);\n\t this.length = Buffer.byteLength(body);\n\t return res.end(body);\n\t }\n\t // responses\n\t if (Buffer.isBuffer(body)) return res.end(body);\n\t if ('string' == typeof body) return res.end(body);\n\t if (body instanceof Stream) return body.pipe(res);\n\t // body: json\n\t body = JSON.stringify(body);\n\t this.length = Buffer.byteLength(body);\n\t res.end(body);\n\t}\n\n 在1.0.0版本中respond函数第一行就是yield *next,也就是一开始不执行,到所有中间件执行完后对res进行处理。但co进入4.*以后,用promise代替了thunk函数的实现,返回一个promise,因此当前1.1.2版本中的respond函数的调用写在了co返回的promise的第一个回调中。\n\n#### 其它\n\n- app.use向中间件数组中添加中间件\n- app.createContext创建请求上下文\n- app.inspect/toJSON返回配置的参数subdomainOffset、proxy和env。","html":"<h2 id=\"application\">Application</h2>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=32507038&auto=1&height=66\"></iframe>\n\n<p>原来写的文章,迁移过来,还是koa1的,koa2也出了,待更新。</p>\n\n<h3 id=\"\">依赖</h3>\n\n<ul>\n<li>debug:debug源码分析;</li>\n<li>compose_es7:与co.wrap相似,不过能够接受 async/await 函数;</li>\n<li>onFinished:捕捉finish或error事件,并根据第二个参数执行回调;</li>\n<li>compose:compose_es7的简化版,只接受generator;</li>\n<li>isJSON:顾名思义,判断传入参数是否为JSON;</li>\n<li>statuses:将http码分为三类,redirect、empty以及retry,然后根据传入的http来判断属于哪一类;</li>\n<li>accepts:根据req的内容判断(content negotiation)字段,如accept,Accept-Encoding,Accept-Language等字段对这些字段进行格式化,方便后续处理;</li>\n<li>only:只返回在传入参数里所包含的字段;</li>\n<li>co:co源码解析</li>\n</ul>\n\n<h3 id=\"\">构造器源码</h3>\n\n<!--javascript--> \n\n<pre><code>/**\n * Initialize a new `Application`.\n *\n * @api public\n */\nfunction Application() {\n if (!(this instanceof Application)) return new Application;\n this.env = process.env.NODE_ENV || 'development';\n this.subdomainOffset = 2;\n this.middleware = [];\n this.proxy = false;\n this.context = Object.create(context);\n this.request = Object.create(request);\n this.response = Object.create(response);\n}\n</code></pre>\n\n<p>上面koa的Application的构造函数,可以看到在创建Application时有初始化如下属性:</p>\n\n<ol>\n<li>env表示是开发环境还是生产环境,通过process.env.NODE_ENV读取环境变量中的参数设置,如果没有设置默认为开发环境 </li>\n<li>subdomainOffset,表示子域名的偏移,默认为2 </li>\n<li>proxy如果为true,则解析 \"Host\" 的 header 域,并支持X-Forwarded-Host,默认为false。 </li>\n<li>context上下文 </li>\n<li>request请求对象 </li>\n<li>response响应对象</li>\n</ol>\n\n<h4 id=\"\">有哪些方法</h4>\n\n<h4 id=\"applisten\">app.listen</h4>\n\n<!--javascript--> \n\n<pre><code>/**\n * Shorthand for:\n *\n * http.createServer(app.callback()).listen(...)\n *\n * @param {Mixed} ...\n * @return {Server}\n * @api public\n */\napp.listen = function(){\n debug('listen');\n var server = http.createServer(this.callback());\n return server.listen.apply(server, arguments);\n};\n</code></pre>\n\n<p> 这个方法调用了node原生的http模块创建了一个server,要关注的是传入的这个callback。</p>\n\n<h4 id=\"appcallback\">app.callback</h4>\n\n<!--javascript-->\n\n<pre><code>/** \n * Return a request handler callback\n * for node's native http server.\n *\n * @return {Function}\n * @api public\n */\napp.callback = function(){\n var fn = this.experimental\n ? compose_es7(this.middleware)\n : co.wrap(compose(this.middleware));\n var self = this;\n if (!this.listeners('error').length) this.on('error', this.onerror);\n return function(req, res){\n res.statusCode = 404;\n var ctx = self.createContext(req, res);\n onFinished(res, ctx.onerror);\n fn.call(ctx).then(function () {\n respond.call(ctx);\n }).catch(ctx.onerror);\n }\n};\n</code></pre>\n\n<p> 返回一个适合 http.createServer() 方法的回调函数用来处理请求。 您也可以使用这个回调函数将您的app挂载在 Connect/Express 应用上。一点点看下来,this.middleware是中间件的一个数组,用co.wrap或者compose<em>es7进行包装,co是tj大神的一个用来包装执行generator函数的库,而compose</em>es7应该是es7可能会引入的相似功能的特性,不同的是后者支持es7的async/await。后面一句处理错误,最后是返回一个请求处理函数。在函数中会调用createContext创建上下文,用onFinished模块捕捉finish或error事件,并根据第二个参数执行回调。然后调用co包装返回的promise对象进行中间件的调用,调用就由co来进行。调用成功后执行调用respond函数。</p>\n\n<h4 id=\"respond\">最后就来看看respond函数</h4>\n\n<!--javascript-->\n\n<pre><code>/** \n * Response helper.\n */\nfunction respond() {\n // allow bypassing koa\n if (false === this.respond) return;\n var res = this.res;\n if (res.headersSent || !this.writable) return;\n var body = this.body;\n var code = this.status;\n // ignore body\n if (statuses.empty[code]) {\n // strip headers\n this.body = null;\n return res.end();\n }\n if ('HEAD' == this.method) {\n if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));\n return res.end();\n }\n // status body\n if (null == body) {\n this.type = 'text';\n body = this.message || String(code);\n this.length = Buffer.byteLength(body);\n return res.end(body);\n }\n // responses\n if (Buffer.isBuffer(body)) return res.end(body);\n if ('string' == typeof body) return res.end(body);\n if (body instanceof Stream) return body.pipe(res);\n // body: json\n body = JSON.stringify(body);\n this.length = Buffer.byteLength(body);\n res.end(body);\n}\n</code></pre>\n\n<p> 在1.0.0版本中respond函数第一行就是yield <em>next,也就是一开始不执行,到所有中间件执行完后对res进行处理。但co进入4.</em>以后,用promise代替了thunk函数的实现,返回一个promise,因此当前1.1.2版本中的respond函数的调用写在了co返回的promise的第一个回调中。</p>\n\n<h4 id=\"\">其它</h4>\n\n<ul>\n<li>app.use向中间件数组中添加中间件</li>\n<li>app.createContext创建请求上下文</li>\n<li>app.inspect/toJSON返回配置的参数subdomainOffset、proxy和env。</li>\n</ul>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463838292023,"created_by":1,"updated_at":1463838903875,"updated_by":1,"published_at":1463838479996,"published_by":1},{"id":6,"uuid":"9a4be2b6-a671-4302-99fe-d0a54af86417","title":"Yesterday's","slug":"yesterday","markdown":"T团20周年的开场曲,晚上写作业时又拿出来循环播放,听完又是元气满满的一天呀!歌词很棒,拿出来分享分享!\n看到网易云音乐的评论里,一个考研党在2015年9月24日23点33发了一句“正在考研的我听着每一句都是泪”,到4月13日他又评论了一句“我考上了”,中间没有任何别的回复,但多了4个赞。我看到这莫名地就释然了,点了个赞,继续写作业去了,很奇妙的感受,我叫它感同身受!\n20周年那一版因为版权无法生成外链,只有下面这个版本了,不过也很不错!\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=22690729&auto=1&height=66\"></iframe>\n\n風の無い夜の公園で 動かないブランコ達は<br>\n在无风的夜晚公园里 不会摇晃的秋千们<br>\n与此刻的自己 有着可怕的相似<br>\n恐い位似てるんだよ 今の僕自身に<br>\n欲しい物は山ほどある<br>\n想要的东西如山般高<br>\n但手中却什么也没有<br>\nだけど欠片(かけら)も手にできず<br>\n無くしてきた 色んな夢を探してる<br>\n只是搜寻着 那些消逝的梦想<br>\n弱さは人の運命(さだめ)だと<br>\n命运两字是人生的无奈<br>\nなんとなく気付いてるけど<br>\n似乎我也注意到了<br>\n「支えられたい、支えてみたい」忙しく思うよ<br>\n却不停的想着「能撑得,在撑撑看」<br>\n強がる事に不器用で<br>\n就算是徒费力气<br>\n空回りしてもいいから<br>\n硬撑也无所谓<br>\n我只是想不断追寻 那变化多端的未来<br>\n追い続けたい 色んな形の未来を<br>\n觉得容易的事情却意外地<br>\n容易(たやす)く思える事が意外に結構<br>\n无法简单得手<br>\n手に入れるのが難しく思える<br>\n这难道不是跟盯着自己的脚步<br>\nそれってもしかして自分の足元を<br>\n見つめてみればきっと転がってんじゃない<br>\n就会摔跤是一样的道理吗<br>\n激しい雨が降った後にはきれいな花が咲く<br>\n在倾盆大雨过后 娇艳的花儿即将绽放<br>\nあきらめたりしない 届くはずさ想いは<br>\n千万不要放弃 因为梦想即将到达<br>\nそしていっそ昨日までの自分を捨て去ろう<br>\n然后索性忘却到昨天为止的自己吧<br>\n静けさを引き裂くように 空き缶を強く蹴り飛ばす<br>\n仿佛是要将寂静撕裂 狠狠的将罐子踢飞<br>\n胸に秘めた 色んな迷いを詰め込んで<br>\n深藏在内心的 是满满的各种迷茫<br>\n結果だけにしがみついていたよずっと<br>\n一直以来总是只紧抓住结果不放<br>\nプロセスなんか馬鹿らしく思えて<br>\n觉得过程什么的都不重要<br>\n残缺的月 带着勇敢的微笑<br>\n満ち欠ける月のけなげな微笑み<br>\n看吧 太阳一定依旧在你眼前升起<br>\n日はまた昇るきっと目の前にほら<br>\n頬を伝ったどんな涙も大きな価値がある<br>\n脸颊上滑落的 是无价眼泪带来的讯息<br>\n二度とない時を負けないように進むよ<br>\n怀着仅此一次绝不认输的心情前进<br>\n激しい雨が降った後にはきれいな花が咲く<br>\n在大雨倾盆过后 娇艳的花儿即将绽放<br>\nあきらめたりしない 届くはずさ願いは<br>\n千万不要放弃 因为梦想即将到达<br>\nそしていっそ昨日までの自分を捨て去ろう<br>\n然后索性忘却到昨天为止的自己吧<br>\n遠くない近くない捉えずらいホントにいつもやっかいもんは自分<br>\n最难琢磨的是那忽远忽近难以搞定的真实的自己<br>\nそれでも向き合って生きていかなきゃダメさ<br>\n但是不好好奋斗下去是不行的<br>\nだから「昨日までの自分を捨て去ろう」って唄おう<br>\n所以 就让我们唱着「忘却到昨天为止的自己吧」<br>\nいつかはどんな部分(こころ)も愛せる気がするよ<br>\n无论何时 无论何地都感受到爱<br>\n何が起こっても構わない荒波に打たれても<br>\n无论发生任何事 就算是再大的风浪<br>\n僕は信じている最終形の自分を<br>\n我始终相信着最真实的自己<br>\nどれ位こうしてたんだろう<br>\n到底这样过了多久<br>\n街は息を吹き返した<br>\n整个街道重新开始呼吸<br>\n不思議なんだ僕の胸に光がともってく<br>\n不可思议的是我的心也渐渐亮了起来<br>\nそよぎはじめていた風が淋しさを全部連れ去り<br>\n徐徐吹起的微风将一切寂寞带走<br>\n踊りだしたブランコ達も笑ってる<br>\n跳起舞来的秋千们也露出笑意<br>","html":"<p>T团20周年的开场曲,晚上写作业时又拿出来循环播放,听完又是元气满满的一天呀!歌词很棒,拿出来分享分享! <br />\n看到网易云音乐的评论里,一个考研党在2015年9月24日23点33发了一句“正在考研的我听着每一句都是泪”,到4月13日他又评论了一句“我考上了”,中间没有任何别的回复,但多了4个赞。我看到这莫名地就释然了,点了个赞,继续写作业去了,很奇妙的感受,我叫它感同身受!\n20周年那一版因为版权无法生成外链,只有下面这个版本了,不过也很不错!</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=22690729&auto=1&height=66\"></iframe>\n\n<p>風の無い夜の公園で 動かないブランコ達は<br>\n在无风的夜晚公园里 不会摇晃的秋千们<br>\n与此刻的自己 有着可怕的相似<br>\n恐い位似てるんだよ 今の僕自身に<br>\n欲しい物は山ほどある<br>\n想要的东西如山般高<br>\n但手中却什么也没有<br>\nだけど欠片(かけら)も手にできず<br>\n無くしてきた 色んな夢を探してる<br>\n只是搜寻着 那些消逝的梦想<br>\n弱さは人の運命(さだめ)だと<br>\n命运两字是人生的无奈<br>\nなんとなく気付いてるけど<br>\n似乎我也注意到了<br>\n「支えられたい、支えてみたい」忙しく思うよ<br>\n却不停的想着「能撑得,在撑撑看」<br>\n強がる事に不器用で<br>\n就算是徒费力气<br>\n空回りしてもいいから<br>\n硬撑也无所谓<br>\n我只是想不断追寻 那变化多端的未来<br>\n追い続けたい 色んな形の未来を<br>\n觉得容易的事情却意外地<br>\n容易(たやす)く思える事が意外に結構<br>\n无法简单得手<br>\n手に入れるのが難しく思える<br>\n这难道不是跟盯着自己的脚步<br>\nそれってもしかして自分の足元を<br>\n見つめてみればきっと転がってんじゃない<br>\n就会摔跤是一样的道理吗<br>\n激しい雨が降った後にはきれいな花が咲く<br>\n在倾盆大雨过后 娇艳的花儿即将绽放<br>\nあきらめたりしない 届くはずさ想いは<br>\n千万不要放弃 因为梦想即将到达<br>\nそしていっそ昨日までの自分を捨て去ろう<br>\n然后索性忘却到昨天为止的自己吧<br>\n静けさを引き裂くように 空き缶を強く蹴り飛ばす<br>\n仿佛是要将寂静撕裂 狠狠的将罐子踢飞<br>\n胸に秘めた 色んな迷いを詰め込んで<br>\n深藏在内心的 是满满的各种迷茫<br>\n結果だけにしがみついていたよずっと<br>\n一直以来总是只紧抓住结果不放<br>\nプロセスなんか馬鹿らしく思えて<br>\n觉得过程什么的都不重要<br>\n残缺的月 带着勇敢的微笑<br>\n満ち欠ける月のけなげな微笑み<br>\n看吧 太阳一定依旧在你眼前升起<br>\n日はまた昇るきっと目の前にほら<br>\n頬を伝ったどんな涙も大きな価値がある<br>\n脸颊上滑落的 是无价眼泪带来的讯息<br>\n二度とない時を負けないように進むよ<br>\n怀着仅此一次绝不认输的心情前进<br>\n激しい雨が降った後にはきれいな花が咲く<br>\n在大雨倾盆过后 娇艳的花儿即将绽放<br>\nあきらめたりしない 届くはずさ願いは<br>\n千万不要放弃 因为梦想即将到达<br>\nそしていっそ昨日までの自分を捨て去ろう<br>\n然后索性忘却到昨天为止的自己吧<br>\n遠くない近くない捉えずらいホントにいつもやっかいもんは自分<br>\n最难琢磨的是那忽远忽近难以搞定的真实的自己<br>\nそれでも向き合って生きていかなきゃダメさ<br>\n但是不好好奋斗下去是不行的<br>\nだから「昨日までの自分を捨て去ろう」って唄おう<br>\n所以 就让我们唱着「忘却到昨天为止的自己吧」<br>\nいつかはどんな部分(こころ)も愛せる気がするよ<br>\n无论何时 无论何地都感受到爱<br>\n何が起こっても構わない荒波に打たれても<br>\n无论发生任何事 就算是再大的风浪<br>\n僕は信じている最終形の自分を<br>\n我始终相信着最真实的自己<br>\nどれ位こうしてたんだろう<br>\n到底这样过了多久<br>\n街は息を吹き返した<br>\n整个街道重新开始呼吸<br>\n不思議なんだ僕の胸に光がともってく<br>\n不可思议的是我的心也渐渐亮了起来<br>\nそよぎはじめていた風が淋しさを全部連れ去り<br>\n徐徐吹起的微风将一切寂寞带走<br>\n踊りだしたブランコ達も笑ってる<br>\n跳起舞来的秋千们也露出笑意<br></p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1463918607786,"created_by":1,"updated_at":1463919901297,"updated_by":1,"published_at":1463919661601,"published_by":1},{"id":7,"uuid":"d4620769-971a-4d03-b3f7-dac29ab33ead","title":"Fetch API 异常处理","slug":"liu-lan-qi-xia-zai-google-playde-apk","markdown":"江边跑了几公里,有点累🙄,不过今天的风儿真喧嚣啊!发现江边有个地方和动漫里还挺像,就在那块草坪上坐了坐🤗。好了不费话了,写完还要洗澡!\n\n继之前服务端 Jersey 的异常处理之后,这篇要谈谈我在 react native 客户端 Fetch API 中进行异常处理遇到的一些问题!总所周知,Fetch 是使用 Promise 进行异步的编写的,所以这个问题其实也就是 Promise 的异常处理了!\n\n`Promise.prototype.catch` 这个方法就是用来捕获调用链上的异常的,但是在使用中还是有比较多的坑的。首先,这个方法是 `.then(null, rejection)` 的别名,或者说语法糖应该也可以吧!\n\n几个注意点:\n\n1. 如果 Promise 状态已经变成 Resolved ,再抛出错误是无效的;\n2. Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。\n3. 跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。\n4. *需要注意的是,catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。*\n\n这是 ES6 入门中,阮一峰老师提到的几个比较重要的点。然后我来讲讲我总结的 Fetch 中使用 catch 捕获异常的最佳实践,也是我目前的做法:\n\n1. 首先建议使用 catch 而不是 Promise 的第二个参数,由 catch 统一处理调用链上的异常;\n2. 第二注意 catch 返回的是一个新的 Promise,因此如果想要在封装的 Rest 类中统一 catch 处理异常是不行的,因为此处处理了异常后,后面的业务逻辑还是会执行,就可能导致一些意外的错误。即使在此处统一抛出一个新的异常,那么在后面调用时还是需要再 catch,这是得不偿失的。所以建议还是统一在每个调用后面单独处理;\n3. 当然,单独处理的逻辑可以提取公共的,减少下一次维护的成本和代码量。\n\n因为探讨的是我所在 app 的场景,而我又自己封装了 Fetch,因此可能上面的说法存在一些难懂的地方。但是原则是一致的,用 catch 不用 reject,单独处理,抽取公共逻辑。","html":"<p>江边跑了几公里,有点累🙄,不过今天的风儿真喧嚣啊!发现江边有个地方和动漫里还挺像,就在那块草坪上坐了坐🤗。好了不费话了,写完还要洗澡!</p>\n\n<p>继之前服务端 Jersey 的异常处理之后,这篇要谈谈我在 react native 客户端 Fetch API 中进行异常处理遇到的一些问题!总所周知,Fetch 是使用 Promise 进行异步的编写的,所以这个问题其实也就是 Promise 的异常处理了!</p>\n\n<p><code>Promise.prototype.catch</code> 这个方法就是用来捕获调用链上的异常的,但是在使用中还是有比较多的坑的。首先,这个方法是 <code>.then(null, rejection)</code> 的别名,或者说语法糖应该也可以吧!</p>\n\n<p>几个注意点:</p>\n\n<ol>\n<li>如果 Promise 状态已经变成 Resolved ,再抛出错误是无效的; </li>\n<li>Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。 </li>\n<li>跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。 </li>\n<li><em>需要注意的是,catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。</em></li>\n</ol>\n\n<p>这是 ES6 入门中,阮一峰老师提到的几个比较重要的点。然后我来讲讲我总结的 Fetch 中使用 catch 捕获异常的最佳实践,也是我目前的做法:</p>\n\n<ol>\n<li>首先建议使用 catch 而不是 Promise 的第二个参数,由 catch 统一处理调用链上的异常; </li>\n<li>第二注意 catch 返回的是一个新的 Promise,因此如果想要在封装的 Rest 类中统一 catch 处理异常是不行的,因为此处处理了异常后,后面的业务逻辑还是会执行,就可能导致一些意外的错误。即使在此处统一抛出一个新的异常,那么在后面调用时还是需要再 catch,这是得不偿失的。所以建议还是统一在每个调用后面单独处理; </li>\n<li>当然,单独处理的逻辑可以提取公共的,减少下一次维护的成本和代码量。</li>\n</ol>\n\n<p>因为探讨的是我所在 app 的场景,而我又自己封装了 Fetch,因此可能上面的说法存在一些难懂的地方。但是原则是一致的,用 catch 不用 reject,单独处理,抽取公共逻辑。</p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464107300210,"created_by":1,"updated_at":1471011841046,"updated_by":1,"published_at":1471011841047,"published_by":1},{"id":8,"uuid":"b8b11fe7-0536-434c-ab84-10bfa539d770","title":"转:Reactjs 的 PropTypes 使用方法","slug":"zhuan-reactjs-de-proptypes-shi-yong-fang-fa","markdown":"### Reactjs 的 PropTypes 使用方法\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=35528482&auto=1&height=66\"></iframe>\n\npropTypes 使用来规范组件Props的类型\n\n```\n var Test = React.createClass({\n propTypes: {\n // required\n requiredFunc: React.PropTypes.func.isRequired,\n requiredAny: React.PropTypes.any.isRequired,\n\n // primitives, optional by default\n bool: React.PropTypes.bool,\n func: React.PropTypes.func,\n number: React.PropTypes.number,\n string: React.PropTypes.string,\n },\n render:function(){\n return <div/>\n }\n});\n\nvar component = React.render(\n <Test requiredFunc=\"bar\" bool=\"true\" requiredAny=\"a\"/>, \n document.body\n);\n```\n\n若没有按照规范,会显示警告。\n\n```\nReact.PropTypes 的种类\nReact.PropTypes.array // 数组\nReact.PropTypes.bool.isRequired // Boolean 且必要。\nReact.PropTypes.func // 函数\nReact.PropTypes.number // 数字\nReact.PropTypes.object // 对象\nReact.PropTypes.string // 字符串\nReact.PropTypes.any // 任何类型的: numbers, strings, elements等\nReact.PropTypes.element // React 元素\nReact.PropTypes.instanceOf(XXX) // 某种XXX类型的实体\nReact.PropTypes.oneOf(['foo', 'bar']) // 其中一个字符串\nReact.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]) // 其中一种类型\nReact.PropTypes.arrayOf(React.PropTypes.string) // 某种类型的数组\nReact.PropTypes.objectOf(React.PropTypes.string) // 某种类型的对象\nReact.PropTypes.shape({ // 是否符合指定的格式\n color: React.PropTypes.string,\n fontSize: React.PropTypes.number\n});\nReact.PropTypes.any.isRequired // 可以是任何格式,且必要。\n\n// 自定义格式(当不符合的时候,会显示Error) \n// 不要用`console.warn` 或者 throw, 因为它在`oneOfType` 的情况下无效。\ncustomPropType: function(props, propName, componentName) {\n if (!/^[0-9]/.test(props[propName])) {\n return new Error('Validation failed!');\n }\n}\n```\n\ngetDefaultProps\n\n当父组件没有提供props的属性时,可以采用getDefaultProps,预设props属性的方式,让元件使用预设的值,确保有props带入。\n\n```\n var ComponentWithDefaultProps = React.createClass({ \n getDefaultProps : function () { \n return {\n value : 'default value' \n }; \n }, \n /* ... */ \n});\n```\n\nPosted by James Yang March 19, 2015 ReactJS","html":"<h3 id=\"reactjsproptypes\">Reactjs 的 PropTypes 使用方法</h3>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=35528482&auto=1&height=66\"></iframe>\n\n<p>propTypes 使用来规范组件Props的类型</p>\n\n<pre><code> var Test = React.createClass({\n propTypes: {\n // required\n requiredFunc: React.PropTypes.func.isRequired,\n requiredAny: React.PropTypes.any.isRequired,\n\n // primitives, optional by default\n bool: React.PropTypes.bool,\n func: React.PropTypes.func,\n number: React.PropTypes.number,\n string: React.PropTypes.string,\n },\n render:function(){\n return <div/>\n }\n});\n\nvar component = React.render( \n <Test requiredFunc=\"bar\" bool=\"true\" requiredAny=\"a\"/>, \n document.body\n);\n</code></pre>\n\n<p>若没有按照规范,会显示警告。</p>\n\n<pre><code>React.PropTypes 的种类 \nReact.PropTypes.array // 数组 \nReact.PropTypes.bool.isRequired // Boolean 且必要。 \nReact.PropTypes.func // 函数 \nReact.PropTypes.number // 数字 \nReact.PropTypes.object // 对象 \nReact.PropTypes.string // 字符串 \nReact.PropTypes.any // 任何类型的: numbers, strings, elements等 \nReact.PropTypes.element // React 元素 \nReact.PropTypes.instanceOf(XXX) // 某种XXX类型的实体 \nReact.PropTypes.oneOf(['foo', 'bar']) // 其中一个字符串 \nReact.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]) // 其中一种类型 \nReact.PropTypes.arrayOf(React.PropTypes.string) // 某种类型的数组 \nReact.PropTypes.objectOf(React.PropTypes.string) // 某种类型的对象 \nReact.PropTypes.shape({ // 是否符合指定的格式 \n color: React.PropTypes.string,\n fontSize: React.PropTypes.number\n});\nReact.PropTypes.any.isRequired // 可以是任何格式,且必要。\n\n// 自定义格式(当不符合的时候,会显示Error) \n// 不要用`console.warn` 或者 throw, 因为它在`oneOfType` 的情况下无效。\ncustomPropType: function(props, propName, componentName) { \n if (!/^[0-9]/.test(props[propName])) {\n return new Error('Validation failed!');\n }\n}\n</code></pre>\n\n<p>getDefaultProps</p>\n\n<p>当父组件没有提供props的属性时,可以采用getDefaultProps,预设props属性的方式,让元件使用预设的值,确保有props带入。</p>\n\n<pre><code> var ComponentWithDefaultProps = React.createClass({ \n getDefaultProps : function () { \n return {\n value : 'default value' \n }; \n }, \n /* ... */ \n});\n</code></pre>\n\n<p>Posted by James Yang March 19, 2015 ReactJS</p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464234823225,"created_by":1,"updated_at":1464247927106,"updated_by":1,"published_at":1464247836046,"published_by":1},{"id":9,"uuid":"727848e2-4e25-4943-bb82-5a6f2bba13b5","title":"什么是视差滚动","slug":"shi-yao-shi-shi-chai-gun-dong","markdown":"下午在研究facebook今年f8大会上开源的基于react native的f8app的时候看到一个ParallaxBackground组件,不明觉厉啊!于是查了一下单词Parallax,意思为视差,所以ParallaxBackground其实是一个视差的背景,最后我把目光落到了视差滚动。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=385757&auto=1&height=66\"></iframe>\n\n视差滚动是近些年国外web design中的一种潮流,它能够提高网页的立体性。它是如何运作的呢!上百度百科。\n> 视差效果,原本是一个天文学术语,当我们观察星空时,离我们远的星星移动速度较慢,离我们近的星星移动速度则较快。当我们坐在车上向车窗外 看时,也会有这样的感觉,远处的群山似乎没有在动,而近处的稻田却在飞速掠过。许多游戏中都使用视差效果来增加场景的立体感。说的简单点就是网页内的元素在滚动屏幕时发生的位置的变化,然而各个不同的元素位置变化的速度不同,导致网页内的元素有层次错落的错觉,这和我们人体的眼球效果很像。我看到多家产品商用视差滚动效果来展示产品,从不同的空间角度和用户体验,起到了非常不错的效果。 目前这种视差滚动效果被越来越多的国外网站所应用, 成为网页设计的热点趋势。 通过一个很长的网页页面,其中利用一些令人惊叹的插图和图形,并使用视差滚动(Parallax Scrolling)效果,让多层背景以不同的速度移动,形成立体的运动效果,带来非常出色的视觉体验。完美的展示了一个复杂的过程,让你犹如置身其中。\n\n百度百科看了还是木有很深刻的认识,那看看下面这个例子你一定会说“噢,来是这样”。[点我点我](http://hotdot.pro/#)\n\n在我看来视差滚动其实是通过页面多层次不同速度的滚动来达到一种视觉上的落差,使人感到页面有一种立体感!","html":"<p>下午在研究facebook今年f8大会上开源的基于react native的f8app的时候看到一个ParallaxBackground组件,不明觉厉啊!于是查了一下单词Parallax,意思为视差,所以ParallaxBackground其实是一个视差的背景,最后我把目光落到了视差滚动。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=385757&auto=1&height=66\"></iframe>\n\n<p>视差滚动是近些年国外web design中的一种潮流,它能够提高网页的立体性。它是如何运作的呢!上百度百科。</p>\n\n<blockquote>\n <p>视差效果,原本是一个天文学术语,当我们观察星空时,离我们远的星星移动速度较慢,离我们近的星星移动速度则较快。当我们坐在车上向车窗外 看时,也会有这样的感觉,远处的群山似乎没有在动,而近处的稻田却在飞速掠过。许多游戏中都使用视差效果来增加场景的立体感。说的简单点就是网页内的元素在滚动屏幕时发生的位置的变化,然而各个不同的元素位置变化的速度不同,导致网页内的元素有层次错落的错觉,这和我们人体的眼球效果很像。我看到多家产品商用视差滚动效果来展示产品,从不同的空间角度和用户体验,起到了非常不错的效果。 目前这种视差滚动效果被越来越多的国外网站所应用, 成为网页设计的热点趋势。 通过一个很长的网页页面,其中利用一些令人惊叹的插图和图形,并使用视差滚动(Parallax Scrolling)效果,让多层背景以不同的速度移动,形成立体的运动效果,带来非常出色的视觉体验。完美的展示了一个复杂的过程,让你犹如置身其中。</p>\n</blockquote>\n\n<p>百度百科看了还是木有很深刻的认识,那看看下面这个例子你一定会说“噢,来是这样”。<a href=\"http://hotdot.pro/#\">点我点我</a></p>\n\n<p>在我看来视差滚动其实是通过页面多层次不同速度的滚动来达到一种视觉上的落差,使人感到页面有一种立体感!</p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464244796501,"created_by":1,"updated_at":1464245506730,"updated_by":1,"published_at":1464245322285,"published_by":1},{"id":10,"uuid":"5995d7ae-ad04-4a41-9710-a30426873cf3","title":"一脸懵逼:git大小写不敏感","slug":"gitda-xiao-xie-bu-min-gan","markdown":"用了这么久git,今天第一次遇到文件大小写不区分的问题,因为node找依赖模块时区分大小写,项目就跑不起来了!!!\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=188647&auto=1&height=66\"></iframe>\n\n只能找办法解决:首先修改git的配置,使其大小写敏感\n```\ngit config core.ignorecase false\n```\n其次需要删除本地文件重新add和commit\n```\ngit rm -f filename\n\ngit add filename\n\ngit commit -m \"message\"\n```","html":"<p>用了这么久git,今天第一次遇到文件大小写不区分的问题,因为node找依赖模块时区分大小写,项目就跑不起来了!!!</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=188647&auto=1&height=66\"></iframe>\n\n<p>只能找办法解决:首先修改git的配置,使其大小写敏感</p>\n\n<pre><code>git config core.ignorecase false \n</code></pre>\n\n<p>其次需要删除本地文件重新add和commit</p>\n\n<pre><code>git rm -f filename\n\ngit add filename\n\ngit commit -m \"message\" \n</code></pre>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464334877736,"created_by":1,"updated_at":1464336310938,"updated_by":1,"published_at":1464335179180,"published_by":1},{"id":11,"uuid":"203b9723-2d0c-4d53-8634-98ad9d8087f4","title":"一口老血:ESLint配置文档翻译","slug":"pei-zhi-eslint-2","markdown":"## 配置ESLint\nESLint 被设计为完全可配置的,这意味着你可以关闭任意规则,仅仅运行基础的句法检查,或混合和匹配捆绑的规则和你习惯的规则来最适合你的项目。这有两种主要的方式来配置 ESLint 。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=308353&auto=1&height=66\"></iframe>\n\n1. 配置注释 - 直接把配置信息通过 JavaScript 注释的方式写入文件\n2. 配置文件 - 使用一个 JavaScript ,JSON or YAML 文件来具体说明整个文件夹和所有它的子文件夹的配置信息。它可以在一个 `.eslintrc.*` 文件或在 `package.json` 文件的 `eslintConfig` 字段,ESLint 将会自动寻找并读取它们,或者你可以通过命令行具体制定配置文件。\n\n有以下几块信息是可以被配置的:\n\n* Environments - 你的脚本被设计运行在什么环境。每个环境将会带有一组预定义的全局变量。\n* Globals - 你的脚本执行期间所需的额外的全局变量。\n* Rules - 将打开的规则,以及它的错误级别。\n\nESLint 所有这些选项使你你有了对你的代码细力度的控制力。\n\n## 制定解析器选项\nESLint 允许你去指定你想要支持的 JavaScript 语言选项。默认,ESLint 支持 ES5 的句法。你可以通过覆盖解析器选项的设置去启用 ES6 和 ES7 甚至JSX的支持。\n\n请注意支持 JSX 句法不等同于支持 React 。React 应用了 ESLint 无法识别的更具体的语法。如果你在使用 React 并想要支持 React 语法,我们推荐你使用 `eslint-plugin-react` 。\n\n解析器选项在你的 `.eslintrc.*` 文件的 `parserOptions` 属性中被指定。这个可用的选项如下:\n\n* ecmaVersion - 设置你想要使用的 ECMAScript 的具体版本,可以为3、5(默认)、6或者7。\n* sourceType - 设置 **\"script\"** (默认),如果你的代码 ECMAScript 模块当中则使用 **\"module\"** 。\n* ecmaFeatures - 一个指示你所想要使用的语言特性的对象:\n * globalReturn - 允许 return 表达式在全局作用域当中\n * impliedStrict - 开启严格模式(如果 ecmaVersion 大于等于5)\n * jsx - 启用 JSX\n * experimentalObjectRestSpread -启用对于实验性的对象剩余/展开参数的支持。(重要:这是一个实验特性,将来可能会出现显著的变化。建议你不要写依赖于这一功能的规则,除非你已经愿意去维护其改变时所引起的问题。)\n\n这有一个例子 `.eslintrc.json` 文件:\n\n```\n{\n \"parserOptions\": {\n \"ecmaVersion\": 6,\n \"sourceType\": \"module\",\n \"ecmaFeatures\": {\n \"jsx\": true\n }\n },\n \"rules\": {\n \"semi\": 2\n }\n}\n```\n\n设置解析器选项帮助 ESLint 决定什么是一个解析错误。所有语言选项默认是 false 。\n\n## 指定解析器\nESLint 默认使用 `Espree` 作为它的解析器。你可以在你的配置文件当中可选的指定一个不同的解析器,只要它满足以下的要求:\n\n1. 它必须是一个本地安装的npm模块。\n2. 它必须有一个 Esprima 兼容接口(它必须导出一个 `parse()` 方法)。\n3. 它必须产出 Esprima 兼容的抽象语法树和表意的对象。\n\n注意甚至有了这些兼容性,我们不保证一个外部的解析器将和 ESLint 正确地工作,ESLint将不会解决这些由其他解析器不兼容造成的问题。\n\n你需要使用 `.eslintrc` 文件中的 `parser` 选项来指定一个npm模块作为你的解析器。例如,下面指定使用 Esprima 来代替 Espree :\n\n```\n{\n \"parser\": \"esprima\",\n \"rules\": {\n \"semi\": \"error\"\n }\n}\n```\n\n下列解析器是兼容 ESLint 的:\n* Esprima\n* Babel-ESLint - 一个 Babel 解析器的包装,使其兼容 ESLint 。\n\n注意当你使用一个习惯的解析器时, `parserOptions` 配置属性仍然是 ESLint 在非默认的ES5环境工作所必须的。解析器通过了所有的 `parserOptions` ,可以也可以不使用它们去决定启用的特性。\n\n## 指定环境\n一个环境预定义了全局变量。可用的环境如下:\n\n* browser - 浏览器全局变量。\n* node - Node.js 全局变量和 Node.js 作用域。\n* commonjs - CommonJS 全局变量和 CommonJS 作用域(只有在使用 Browserify或WebPack产生的浏览器代码中使用)。\n* shared-node-browser - Node and Browser公共的全局变量。\n* es6 - 启用所有的 ES6 module 所需要的特性。\n* worker - web workers 全局变量.\n* amd - defines require() and define() as global variables as per the amd spec.\n* mocha - 加入 Mocha 测试的所有全局变量。\n* jasmine - 加入 Jasmine 测试的所有全局变量,针对1.3和2.0版本。\n* jest - Jest 全局变量。\n* phantomjs - PhantomJS 全局变量。\n* protractor - Protractor 全局变量。\n* qunit - QUnit 全局变量。\n* jquery - jQuery 全局变量。\n* prototypejs - Prototype.js 全局变量。\n* shelljs - ShellJS 全局变量。\n* meteor - Meteor 全局变量。\n* mongo - MongoDB 全局变量。\n* applescript - AppleScript 全局变量。\n* nashorn - Java 8 Nashorn 引擎全局变量。\n* serviceworker - Service Worker 全局变量。\n* atomtest - Atom 测试 helper 变量。\n* embertest - Ember 测试 helper 变量。\n* webextensions - WebExtensions 变量。\n* greasemonkey - GreaseMonkey 变量。\n\n这些环境不相互影响,所以你可以同时定义超过一个环境。\n\nEnvironments 能够在文件当中,在配置文件中或使用命令行的 `--env` 参数指定。\n\n在你的 JavaScript 中使用注释指定环境,使用下面的格式:\n\n```\n/*eslint-env node, mocha */\n```\n\n他启用了 Node.js 和 Mocha 的环境。\n\n在配置文件当中指定环境,使用 `env` 键并把你需要启用的环境的属性设为 `true` 。例如,下面例子启用了 brower 和 Node.js 环境:\n\n```\n{\n \"env\": {\n \"browser\": true,\n \"node\": true\n }\n}\n```\n\n或者在 `package.json` 文件\n\n```\n{\n \"name\": \"mypackage\",\n \"version\": \"0.0.1\",\n \"eslintConfig\": {\n \"env\": {\n \"browser\": true,\n \"node\": true\n }\n }\n}\n```\n\n在 YAML 文件中:\n\n```\n---\n env:\n browser: true\n node: true\n```\n\n如果你想去使用一个来自插件的环境,确保指定了插件名在 `plugins` 数组,然后使用不带前缀的插件名,其次是斜杠,再其次是环境名。举例来说:\n\n```\n{\n \"plugins\": [\"example\"],\n \"env\": {\n \"example/custom\": true\n }\n}\n```\n\n在一个 `package.json` 文件中\n\n```\n{\n \"name\": \"mypackage\",\n \"version\": \"0.0.1\",\n \"eslintConfig\": {\n \"plugins\": [\"example\"],\n \"env\": {\n \"example/custom\": true\n }\n }\n}\n```\n\nYAML 文件:\n\n```\n---\n plugins:\n - example\n env:\n example/custom: true\n```\n\n## 指定全局变量\nno-undef 规则将会警告将要访问的为定义的变量。如果你使用全局变量在一个文件中,它将是值得的去定义 globals 以至于 ESLint 将不会警告它们的使用。你可以定义全局变量使用文件中的注释或者配置文件。\n\n在你的 JavaScript 文件中指定全局变量,使用下面的格式:\n\n```\n/* global var1, var2 */\n```\n\n定义两个全局变量, `var1` 和 `var2` 。如果你想要去可选的制定这些全局变量不应当被修改(只读),你可以设置每个全局变量一个 false 标记:\n\n```\n/* global var1:false, var2:false */\n```\n\n在配置文件中配置全局变量,使用 `globals` 键并指出你想要使用的全局变量。设置全局变量为 `true` 来使其可以被覆盖或制定 `false` 使其只读。举例:\n\n```\n{\n \"globals\": {\n \"var1\": true,\n \"var2\": false\n }\n}\n```\n\nYAML中:\n\n```\n---\n globals:\n var1: true\n var2: false\n```\n\n这个例子允许 `var1` 可以在代码中覆盖,但 `var2` 不允许。\n\n## 配置插件\nESLint 支持使用第三方的插件。在使用插件前,需要用 npm 将它安装。\n\n在配置文件中配置插件,使用 `plugins` 键,它包含一个插件名的列表。 `eslint-plugin-` 的前缀可以从插件名中省略。\n\n```\n{\n \"plugins\": [\n \"plugin1\",\n \"eslint-plugin-plugin2\"\n ]\n}\n```\n\nYAML中:\n```\n---\n plugins:\n - plugin1\n - eslint-plugin-plugin2\n```\n\n注意:一个全局安装的 ESLint 只能改使用全局安装的 ESLint 插件。本地安装的 ESLint 能够使用本地和全局的 ESLint 插件。\n\n## 配置规则\nESlint 自带了大量的规则。你能够通过注释和配置文件的方式修改你项目中的规则。修改一个规则的设置,你必须设置 rule ID 为下列中的一个值:\n\n* `\"off\"` 或 `0` - 关闭规则\n* `\"warn\"` 或 `1` - 打开规则起提醒作用(不退出代码执行)\n* `\"error\"` 或 `2` - 打开规则起报错作用(将触发退出代码)\n\n在文件中使用注释配置,使用下面的格式:\n\n```\n/* eslint eqeqeq: \"off\", curly: \"error\" */\n```\n\n在这个例子中 `eqeqeq` 被关闭, `curly` 被打开 error 级别。你也可以使用等价的数字表示规则的级别:\n\n```\n/* eslint eqeqeq: 0, curly: 2 */\n```\n\n这个例子和上一个例子相同,只是它使用数字编号代替了字符串值。`eqeqeq` 被关闭, `curly` 被打开 error 级别。\n\n如果一个规则有可选的选项,你可以使用数组字面量设置它们,例如:\n\n```\n/* eslint quotes: [\"error\", \"double\"], curly: 2 */\n```\n\n这个注释为 `quotes` 规则指定了两个选项。数组的第一项总是严重的规则(数字或字符串)。\n\n在配置文件配置规则,使用 `rules` 键跟着一个 error 级别和任何选项你想要使用的。例如:\n\n```\n{\n \"rules\": {\n \"eqeqeq\": \"off\",\n \"curly\": \"error\",\n \"quotes\": [\"error\", \"double\"]\n }\n}\n```\n\nYAML 文件中:\n\n```\n---\nrules:\n eqeqeq: off\n curly: error\n quotes:\n - error\n - double\n```\n\n配置一个在插件中定义的规则,你必须为 rule ID 加上插件名作为前缀,以及 `/`。举例来说:\n\n```\n{\n \"plugins\": [\n \"plugin1\"\n ],\n \"rules\": {\n \"eqeqeq\": \"off\",\n \"curly\": \"error\",\n \"quotes\": [\"error\", \"double\"],\n \"plugin1/rule1\": \"error\"\n }\n}\n```\n\nYAML 文件中:\n\n```\n---\nplugins:\n - plugin1\nrules:\n eqeqeq: 0\n curly: error\n quotes:\n - error\n - \"double\"\n plugin1/rule1: error\n```\n\n在这个配置文件中,规则 `plugin1/rule1` 来自插件 `plugin1` 。你也能够使用如下格式配置注释的方式,例如:\n\n```\n/* eslint \"plugin1/rule1\": \"error\" */\n```\n\n注意:当指定来自插件的规则,确保省略 `eslint-plugin-` 。ESLint 只使用无前缀的名字来定位内部规则。\n\n## 使用行内注释来禁用规则\n使用如下的格式来临时地禁用文件中规则的警告:\n\n```\n/* eslint-disable */\n\n// Disables all rules between comments\nalert('foo');\n\n/* eslint-enable */\n```\n\n你也可以禁用或启用指定的规则:\n\n```\n/* eslint-disable no-alert, no-console */\n\n// Disables no-alert and no-console warnings between comments\nalert('foo');\nconsole.log('bar');\n\n/* eslint-enable no-alert, no-console */\n```\n\n在整个文件中禁用规则警告,把 `/* eslint-disable */` 放到文件顶部:\n\n```\n/* eslint-disable */\n\n// Disables all rules for the rest of the file\nalert('foo');\n```\n\n你也可以在整个文件禁用指定的规则:\n\n```\n/* eslint-disable no-alert */\n\n// Disables no-alert for the rest of the file\nalert('foo');\n```\n\n在指定行禁用所有规则:\n\n```\nalert('foo'); // eslint-disable-line\n\n// eslint-disable-next-line\nalert('foo');\n```\n\n在指定行禁用指定的规则:\n\n```\nalert('foo'); // eslint-disable-line no-alert\n\n// eslint-disable-next-line no-alert\nalert('foo');\n```\n\n指定行禁用多个规则:\n\n```\nalert('foo'); // eslint-disable-line no-alert, quotes, semi\n\n// eslint-disable-next-line no-alert, quotes, semi\nalert('foo');\n```\n\n注意:注释仅仅是禁用了一部分警告,告诉 ESlint 不要报告这部分代码违规。 ESlint 解析整个文件,所以禁用的代码仍然需要符合合法的 JavaScript 语法。\n\n## 添加共享的设置\nESlint 支持向配置文件中添加共享的配置。你能够添加 `settings` 对象到 ESLint 配置文件中,它将会提供给每一个将要执行的规则。这也许是有用的如果你添加了习惯的规则并想要它们能够访问相同的信息并轻松配置。\n\n在 JSON 中:\n\n```\n{\n \"settings\": {\n \"sharedData\": \"Hello\"\n }\n}\n```\n\n在 YAML 文件中:\n\n```\n---\n settings:\n sharedData: \"Hello\"\n```\n\n## 使用配置文件\n这有两种方式去使用配置文件。第一种是保存文件到你喜欢的地方,然后传递它的地址到 CLI 使用 `-c` 选项,例如:\n\n```\neslint -c myconfig.json myfiletotest.js\n```\n\n第二种方式是去使用配置文件通过 `.eslintrc.*` 或 `package.json` 文件。ESLint 将会自动在目录中寻找它们,从连续的父目录一路找到文件系统根目录。这个选项时有用的当你想要对项目的不同部分使用不同的配置,或者当你想要别的直接去使用 ESLint 而不需要记住它在配置文件中传递。\n\n两种方式,配置文件的设置都将会覆盖默认的配置。\n\n## 配置文件格式\nESLint 支持各种格式的配置文件:\n\n* **JavaScript** - 使用 `.eslintrc.js` 并导出一个包含你配置信息的对象。\n* **YAML** - 使用 `.eslintrc.yaml` 或 `.eslintrc.yml` 来定义配置结构。\n* **JSON** - 使用 `.eslintrc.json` 来定义配置结构。 ESLint's JSON 文件也支持 JavaScript 风格的注释。\n* **Deprecated** - 使用 `eslintrc`,过时的,可以是 JSON 或 YAML。\n* **package.json** - 创建一个 `eslintConfig` 属性在你的 `package.json`文件并在这里定义你的配置。\n\n如果有多种配置文件在同一个目录,ESLint将会使用一个。它们的优先级如下:\n\n1. `.eslintrc.js`\n2. `.eslintrc.yaml`\n3. `.eslintrc.yml`\n4. `.eslintrc.json`\n5. `.eslintrc`\n6. `package.json`\n\n## 配置及联和层次结构\n当使用 `.eslintrc.*` 和 `package.json` 文件来配置,你可以利用配置级联的优势。举例来说,假设你有下列的目录结构:\n\n```\nyour-project\n├── .eslintrc\n├── lib\n│ └── source.js\n└─┬ tests\n ├── .eslintrc\n └── test.js\n```\n\n配置级联将会使用最近的 `.eslintrc` 文件作为最高优先级,然后是任何父级目录的配置文件等。当你在项目中运行 ESLint ,所有 `lib/` 目录下的文件将会使用根目录下的 `.eslintrc` 文件作为它们的配置。如果 ESLint 进入 `test/` 目录,它将会使用 `your-project/tests/.eslintrc` 而不是 `your-project/.eslintrc` 。所以 `your-project/tests/test.js` 的检测是基于目录层次结构中的两个 `.eslintrc` 文件的组合的,近的拥有更高的优先级。这样的方式,你可以拥有项目级别的 ESLint 设置,并可以用目录层次的配置来覆盖它。\n\n相同的方式,在 `package.json` 文件中也可行。根目录中 `package.json` 的 `eslintConfig` 字段将在其所有子目录生效,但是在 tests 目录的 `.eslintrc` 文件将会覆盖所有冲突的配置。\n\n```\nyour-project\n├── package.json\n├── lib\n│ └── source.js\n└─┬ tests\n ├── .eslintrc\n └── test.js\n```\n\n如果在一个目录中,同时找到了 `.eslintrc` 和 `package.json` 文件,`eslintrc` 文件将有更高的优先级, `package.json` 文件将不被使用。\n\n注意:如果你又一个个人的配置文件在你的用户目录( `~/.eslintrc` ),它将会仅仅在没有别的配置文件没发现的情况下使用。它将会对你所有用户目录下的文件生效,包括第三方的代码,因此在运行 ESLint 很可能会产生问题。\n\n默认地, ESLint 将会在所有父目录直到根目录寻找配置文件。这将是有用的,如果你想要所有你的项目去跟随某个约定,但是有时候会导致意想不到的结果。限制 ESLint 在一个指定的项目,把 `\"root\": true` 加到 `package.json` 的 `eslintConfig` 字段或者 `.eslintrc.*` 文件,在你的根目录。 ESLint 将会停止寻找父目录当它找到了 `\"root\": true` 的配置。\n\n```\n{\n \"root\": true\n}\n```\nYAML 文件中:\n\n```\n---\n root: true\n```\n\n举例来说, 这个例子当中 `main.js` 将会使用 `lib/` 中的配置,而不会使用 `productA` 目录下的 `.eslintrc` 文件的配置,因为在 `lib/` 中的 `.eslintrc` 有 `\"root\": true` 的设置。\n\n```\nhome\n└── user\n ├── .eslintrc <- Always skipped if other configs present\n └── projectA\n ├── .eslintrc <- Not used\n └── lib\n ├── .eslintrc <- { \"root\": true }\n └── main.js\n```\n\n完整的配置层次,从高到低如下:\n\n1. 行内配置\n 1. `/*eslint-disable*/` and `/*eslint-enable*/`\n 2. `/*global*/`\n 3. `/*eslint*/`\n 4. `/*eslint-env*/`\n2. 命令行配置\n 1. `--global`\n 2. `--rule`\n 3. `--env`\n 4. `-c, --config`\n3. 项目级别配置\n 1. `.eslintrc.*` 或 `package.json` 文件\n 2. 祖先目录中寻找,除非有 `\"root\": true` 将不会向上寻找\n 3. 个人某人的配置在 `~/.eslintrc`\n\n## 扩展配置文件\n如果你想要扩展一个指定的配置文件,你可以使用 `extends` 属性,并制定路径。可以是相对的或是绝对的路径。\n\n配置能够被如下文件扩展:\n\n1. YAML 文件\n2. JSON 文件\n3. JS 文件\n4. 共享的配置包\n\n扩展配置提供了基础的规则,并可以覆盖。举例:\n\n```\n{\n \"extends\": \"./node_modules/coding-standard/.eslintrc\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": 1\n }\n}\n```\n\n配置也许也可以以数组的方式提供,后面的会覆盖前面的相同的规则的配置。举例:\n\n```\n{\n \"extends\": [\n \"./node_modules/coding-standard/eslintDefaults.js\",\n // Override eslintDefaults.js\n \"./node_modules/coding-standard/.eslintrc-es6\",\n // Override .eslintrc-es6\n \"./node_modules/coding-standard/.eslintrc-jsx\",\n ],\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n```\n\n扩展配置能够包涵它们自己的 `extends` ,导致循环的引用。\n\n你也可以使用共享的配置包。你需要使用 npm 去安装它们,例如:\n\n```\n{\n \"extends\": \"eslint-config-myrules\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n```\n\n在这个例子, `eslint-config-myrules` 包将会加载为一个对象并作为这个配置的父配置。\n\n注意:你可以省略 `eslint-config-` 前缀, ESLint 将会自动为你添加,和插件工作相似。\n\nESlint 也支持插件提供的扩展配置:\n\n```\n{\n \"extends\": \"plugin:eslint-plugin-myplugin/myConfig\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n```\n\n在这个例子, `eslint-plugin-myplugin` 包包含了名为 `default` 的配置。\n\n**十分重要**:使用插件中的配置时,必须要加上 `plugin:` 前缀来指明哈。但你可以可选地省略 `eslint-plugin` 前缀。\n\n注意:对于根目录或者其它父目录, `extends` 处理路径会用当前工作目录,而不是文件自身。\n\n## 配置文件中的注释\nJSON 和 YAML 配置文件都支持注释, `package.json` 不能包括注释。你可以使用 JAVAScript 风格的注释或 YAML 风格的注释, ESLint 将安全地忽略它们。这可以允许你的配置文件跟友好。举例:\n\n```\n{\n \"env\": {\n \"browser\": true\n },\n \"rules\": {\n // Override our default settings just for this directory\n \"eqeqeq\": \"warn\",\n \"strict\": \"off\"\n }\n}\n```\n\n## 指定需要检测的文件后缀\n当前指定后缀需要在命令行选项 `--ext` 后,添加用空格分开的后缀名列表。\n\n## 忽略文件或目录\n\n\n你可以告诉 ESLint 去忽略指定的文件或目录,通过一个 `.eslintignore` 文件在你的根目录。 `.eslintignore` 文件是一个普通文本文件,每一行定义了要被忽略的文件或目录。举例:下列表达式将会省略所有 JavaScript 文件:\n\n```\n**/*.js\n```\n\n当 ESLint 运行的时候,它会寻找当前工作目录的 `.eslintignore` 文件在它决定监测之前。如果找到了文件,这里面的配置将会生效。 `.eslintignore` 只会被使用一次,因此目录中其他的 `.eslintignore` 将不会被使用。\n\n* #好开头将会被认为是注释\n* 相对于 `.eslintignore` 文件路径或当前工作目录的路径\n* 忽略规则参照 `.gitignore` 规范\n* !用来取消前面的忽略的匹配\n\n`/node_modules/*` 和 `/bower_components/*` 将默认被忽略。\n\n举例,把下面的 `.eslintignore` 文件放倒工作目录下将会忽略`/node_modules/*` 和 `/bower_components/*` 目录,任何扩展名为 `.ts.js` 或 `.coffee.js` 可能被转换,任何在 `build/*` 目录除了 `build/index.js` 将被忽略:\n\n```\n# /node_modules/* and /bower_components/* ignored by default\n\n# Ignore built files except build/index.js\nbuild/*\n!build/index.js\n```\n\n## 使用替换的文件\n如果你喜欢去使用别的文件而不是 `.eslintignore` 来工作,你在命令行中可以指定 `--ignore-path` 选项。举例来说,你可以使用 `.jshintignore` ,因为两者的配置相同:\n\n```\neslint --ignore-path .jshintignore file.js\n```\n\n你也可以使用 `.gitignore` 文件:\n\n```\neslint --ignore-path .gitignore file.js\n```\n\n任何标准的 ignore 文件可以被使用。\n\n## 忽略文件警告\n当你 eslint 一个被忽略的文件或目录时,会发出警告,你可以使用 `--no-ignore` 来进行省略。\n","html":"<h2 id=\"eslint\">配置ESLint</h2>\n\n<p>ESLint 被设计为完全可配置的,这意味着你可以关闭任意规则,仅仅运行基础的句法检查,或混合和匹配捆绑的规则和你习惯的规则来最适合你的项目。这有两种主要的方式来配置 ESLint 。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=308353&auto=1&height=66\"></iframe>\n\n<ol>\n<li>配置注释 - 直接把配置信息通过 JavaScript 注释的方式写入文件 </li>\n<li>配置文件 - 使用一个 JavaScript ,JSON or YAML 文件来具体说明整个文件夹和所有它的子文件夹的配置信息。它可以在一个 <code>.eslintrc.*</code> 文件或在 <code>package.json</code> 文件的 <code>eslintConfig</code> 字段,ESLint 将会自动寻找并读取它们,或者你可以通过命令行具体制定配置文件。</li>\n</ol>\n\n<p>有以下几块信息是可以被配置的:</p>\n\n<ul>\n<li>Environments - 你的脚本被设计运行在什么环境。每个环境将会带有一组预定义的全局变量。</li>\n<li>Globals - 你的脚本执行期间所需的额外的全局变量。</li>\n<li>Rules - 将打开的规则,以及它的错误级别。</li>\n</ul>\n\n<p>ESLint 所有这些选项使你你有了对你的代码细力度的控制力。</p>\n\n<h2 id=\"\">制定解析器选项</h2>\n\n<p>ESLint 允许你去指定你想要支持的 JavaScript 语言选项。默认,ESLint 支持 ES5 的句法。你可以通过覆盖解析器选项的设置去启用 ES6 和 ES7 甚至JSX的支持。</p>\n\n<p>请注意支持 JSX 句法不等同于支持 React 。React 应用了 ESLint 无法识别的更具体的语法。如果你在使用 React 并想要支持 React 语法,我们推荐你使用 <code>eslint-plugin-react</code> 。</p>\n\n<p>解析器选项在你的 <code>.eslintrc.*</code> 文件的 <code>parserOptions</code> 属性中被指定。这个可用的选项如下:</p>\n\n<ul>\n<li>ecmaVersion - 设置你想要使用的 ECMAScript 的具体版本,可以为3、5(默认)、6或者7。</li>\n<li>sourceType - 设置 <strong>\"script\"</strong> (默认),如果你的代码 ECMAScript 模块当中则使用 <strong>\"module\"</strong> 。</li>\n<li>ecmaFeatures - 一个指示你所想要使用的语言特性的对象:\n<ul><li>globalReturn - 允许 return 表达式在全局作用域当中</li>\n<li>impliedStrict - 开启严格模式(如果 ecmaVersion 大于等于5)</li>\n<li>jsx - 启用 JSX</li>\n<li>experimentalObjectRestSpread -启用对于实验性的对象剩余/展开参数的支持。(重要:这是一个实验特性,将来可能会出现显著的变化。建议你不要写依赖于这一功能的规则,除非你已经愿意去维护其改变时所引起的问题。)</li></ul></li>\n</ul>\n\n<p>这有一个例子 <code>.eslintrc.json</code> 文件:</p>\n\n<pre><code>{\n \"parserOptions\": {\n \"ecmaVersion\": 6,\n \"sourceType\": \"module\",\n \"ecmaFeatures\": {\n \"jsx\": true\n }\n },\n \"rules\": {\n \"semi\": 2\n }\n}\n</code></pre>\n\n<p>设置解析器选项帮助 ESLint 决定什么是一个解析错误。所有语言选项默认是 false 。</p>\n\n<h2 id=\"\">指定解析器</h2>\n\n<p>ESLint 默认使用 <code>Espree</code> 作为它的解析器。你可以在你的配置文件当中可选的指定一个不同的解析器,只要它满足以下的要求:</p>\n\n<ol>\n<li>它必须是一个本地安装的npm模块。 </li>\n<li>它必须有一个 Esprima 兼容接口(它必须导出一个 <code>parse()</code> 方法)。 </li>\n<li>它必须产出 Esprima 兼容的抽象语法树和表意的对象。</li>\n</ol>\n\n<p>注意甚至有了这些兼容性,我们不保证一个外部的解析器将和 ESLint 正确地工作,ESLint将不会解决这些由其他解析器不兼容造成的问题。</p>\n\n<p>你需要使用 <code>.eslintrc</code> 文件中的 <code>parser</code> 选项来指定一个npm模块作为你的解析器。例如,下面指定使用 Esprima 来代替 Espree :</p>\n\n<pre><code>{\n \"parser\": \"esprima\",\n \"rules\": {\n \"semi\": \"error\"\n }\n}\n</code></pre>\n\n<p>下列解析器是兼容 ESLint 的:\n* Esprima\n* Babel-ESLint - 一个 Babel 解析器的包装,使其兼容 ESLint 。</p>\n\n<p>注意当你使用一个习惯的解析器时, <code>parserOptions</code> 配置属性仍然是 ESLint 在非默认的ES5环境工作所必须的。解析器通过了所有的 <code>parserOptions</code> ,可以也可以不使用它们去决定启用的特性。</p>\n\n<h2 id=\"\">指定环境</h2>\n\n<p>一个环境预定义了全局变量。可用的环境如下:</p>\n\n<ul>\n<li>browser - 浏览器全局变量。</li>\n<li>node - Node.js 全局变量和 Node.js 作用域。</li>\n<li>commonjs - CommonJS 全局变量和 CommonJS 作用域(只有在使用 Browserify或WebPack产生的浏览器代码中使用)。</li>\n<li>shared-node-browser - Node and Browser公共的全局变量。</li>\n<li>es6 - 启用所有的 ES6 module 所需要的特性。</li>\n<li>worker - web workers 全局变量.</li>\n<li>amd - defines require() and define() as global variables as per the amd spec.</li>\n<li>mocha - 加入 Mocha 测试的所有全局变量。</li>\n<li>jasmine - 加入 Jasmine 测试的所有全局变量,针对1.3和2.0版本。</li>\n<li>jest - Jest 全局变量。</li>\n<li>phantomjs - PhantomJS 全局变量。</li>\n<li>protractor - Protractor 全局变量。</li>\n<li>qunit - QUnit 全局变量。</li>\n<li>jquery - jQuery 全局变量。</li>\n<li>prototypejs - Prototype.js 全局变量。</li>\n<li>shelljs - ShellJS 全局变量。</li>\n<li>meteor - Meteor 全局变量。</li>\n<li>mongo - MongoDB 全局变量。</li>\n<li>applescript - AppleScript 全局变量。</li>\n<li>nashorn - Java 8 Nashorn 引擎全局变量。</li>\n<li>serviceworker - Service Worker 全局变量。</li>\n<li>atomtest - Atom 测试 helper 变量。</li>\n<li>embertest - Ember 测试 helper 变量。</li>\n<li>webextensions - WebExtensions 变量。</li>\n<li>greasemonkey - GreaseMonkey 变量。</li>\n</ul>\n\n<p>这些环境不相互影响,所以你可以同时定义超过一个环境。</p>\n\n<p>Environments 能够在文件当中,在配置文件中或使用命令行的 <code>--env</code> 参数指定。</p>\n\n<p>在你的 JavaScript 中使用注释指定环境,使用下面的格式:</p>\n\n<pre><code>/*eslint-env node, mocha */\n</code></pre>\n\n<p>他启用了 Node.js 和 Mocha 的环境。</p>\n\n<p>在配置文件当中指定环境,使用 <code>env</code> 键并把你需要启用的环境的属性设为 <code>true</code> 。例如,下面例子启用了 brower 和 Node.js 环境:</p>\n\n<pre><code>{\n \"env\": {\n \"browser\": true,\n \"node\": true\n }\n}\n</code></pre>\n\n<p>或者在 <code>package.json</code> 文件</p>\n\n<pre><code>{\n \"name\": \"mypackage\",\n \"version\": \"0.0.1\",\n \"eslintConfig\": {\n \"env\": {\n \"browser\": true,\n \"node\": true\n }\n }\n}\n</code></pre>\n\n<p>在 YAML 文件中:</p>\n\n<pre><code>---\n env:\n browser: true\n node: true\n</code></pre>\n\n<p>如果你想去使用一个来自插件的环境,确保指定了插件名在 <code>plugins</code> 数组,然后使用不带前缀的插件名,其次是斜杠,再其次是环境名。举例来说:</p>\n\n<pre><code>{\n \"plugins\": [\"example\"],\n \"env\": {\n \"example/custom\": true\n }\n}\n</code></pre>\n\n<p>在一个 <code>package.json</code> 文件中</p>\n\n<pre><code>{\n \"name\": \"mypackage\",\n \"version\": \"0.0.1\",\n \"eslintConfig\": {\n \"plugins\": [\"example\"],\n \"env\": {\n \"example/custom\": true\n }\n }\n}\n</code></pre>\n\n<p>YAML 文件:</p>\n\n<pre><code>---\n plugins:\n - example\n env:\n example/custom: true\n</code></pre>\n\n<h2 id=\"\">指定全局变量</h2>\n\n<p>no-undef 规则将会警告将要访问的为定义的变量。如果你使用全局变量在一个文件中,它将是值得的去定义 globals 以至于 ESLint 将不会警告它们的使用。你可以定义全局变量使用文件中的注释或者配置文件。</p>\n\n<p>在你的 JavaScript 文件中指定全局变量,使用下面的格式:</p>\n\n<pre><code>/* global var1, var2 */\n</code></pre>\n\n<p>定义两个全局变量, <code>var1</code> 和 <code>var2</code> 。如果你想要去可选的制定这些全局变量不应当被修改(只读),你可以设置每个全局变量一个 false 标记:</p>\n\n<pre><code>/* global var1:false, var2:false */\n</code></pre>\n\n<p>在配置文件中配置全局变量,使用 <code>globals</code> 键并指出你想要使用的全局变量。设置全局变量为 <code>true</code> 来使其可以被覆盖或制定 <code>false</code> 使其只读。举例:</p>\n\n<pre><code>{\n \"globals\": {\n \"var1\": true,\n \"var2\": false\n }\n}\n</code></pre>\n\n<p>YAML中:</p>\n\n<pre><code>---\n globals:\n var1: true\n var2: false\n</code></pre>\n\n<p>这个例子允许 <code>var1</code> 可以在代码中覆盖,但 <code>var2</code> 不允许。</p>\n\n<h2 id=\"\">配置插件</h2>\n\n<p>ESLint 支持使用第三方的插件。在使用插件前,需要用 npm 将它安装。</p>\n\n<p>在配置文件中配置插件,使用 <code>plugins</code> 键,它包含一个插件名的列表。 <code>eslint-plugin-</code> 的前缀可以从插件名中省略。</p>\n\n<pre><code>{\n \"plugins\": [\n \"plugin1\",\n \"eslint-plugin-plugin2\"\n ]\n}\n</code></pre>\n\n<p>YAML中: </p>\n\n<pre><code>---\n plugins:\n - plugin1\n - eslint-plugin-plugin2\n</code></pre>\n\n<p>注意:一个全局安装的 ESLint 只能改使用全局安装的 ESLint 插件。本地安装的 ESLint 能够使用本地和全局的 ESLint 插件。</p>\n\n<h2 id=\"\">配置规则</h2>\n\n<p>ESlint 自带了大量的规则。你能够通过注释和配置文件的方式修改你项目中的规则。修改一个规则的设置,你必须设置 rule ID 为下列中的一个值:</p>\n\n<ul>\n<li><code>\"off\"</code> 或 <code>0</code> - 关闭规则</li>\n<li><code>\"warn\"</code> 或 <code>1</code> - 打开规则起提醒作用(不退出代码执行)</li>\n<li><code>\"error\"</code> 或 <code>2</code> - 打开规则起报错作用(将触发退出代码)</li>\n</ul>\n\n<p>在文件中使用注释配置,使用下面的格式:</p>\n\n<pre><code>/* eslint eqeqeq: \"off\", curly: \"error\" */\n</code></pre>\n\n<p>在这个例子中 <code>eqeqeq</code> 被关闭, <code>curly</code> 被打开 error 级别。你也可以使用等价的数字表示规则的级别:</p>\n\n<pre><code>/* eslint eqeqeq: 0, curly: 2 */\n</code></pre>\n\n<p>这个例子和上一个例子相同,只是它使用数字编号代替了字符串值。<code>eqeqeq</code> 被关闭, <code>curly</code> 被打开 error 级别。</p>\n\n<p>如果一个规则有可选的选项,你可以使用数组字面量设置它们,例如:</p>\n\n<pre><code>/* eslint quotes: [\"error\", \"double\"], curly: 2 */\n</code></pre>\n\n<p>这个注释为 <code>quotes</code> 规则指定了两个选项。数组的第一项总是严重的规则(数字或字符串)。</p>\n\n<p>在配置文件配置规则,使用 <code>rules</code> 键跟着一个 error 级别和任何选项你想要使用的。例如:</p>\n\n<pre><code>{\n \"rules\": {\n \"eqeqeq\": \"off\",\n \"curly\": \"error\",\n \"quotes\": [\"error\", \"double\"]\n }\n}\n</code></pre>\n\n<p>YAML 文件中:</p>\n\n<pre><code>---\nrules: \n eqeqeq: off\n curly: error\n quotes:\n - error\n - double\n</code></pre>\n\n<p>配置一个在插件中定义的规则,你必须为 rule ID 加上插件名作为前缀,以及 <code>/</code>。举例来说:</p>\n\n<pre><code>{\n \"plugins\": [\n \"plugin1\"\n ],\n \"rules\": {\n \"eqeqeq\": \"off\",\n \"curly\": \"error\",\n \"quotes\": [\"error\", \"double\"],\n \"plugin1/rule1\": \"error\"\n }\n}\n</code></pre>\n\n<p>YAML 文件中:</p>\n\n<pre><code>---\nplugins: \n - plugin1\nrules: \n eqeqeq: 0\n curly: error\n quotes:\n - error\n - \"double\"\n plugin1/rule1: error\n</code></pre>\n\n<p>在这个配置文件中,规则 <code>plugin1/rule1</code> 来自插件 <code>plugin1</code> 。你也能够使用如下格式配置注释的方式,例如:</p>\n\n<pre><code>/* eslint \"plugin1/rule1\": \"error\" */\n</code></pre>\n\n<p>注意:当指定来自插件的规则,确保省略 <code>eslint-plugin-</code> 。ESLint 只使用无前缀的名字来定位内部规则。</p>\n\n<h2 id=\"\">使用行内注释来禁用规则</h2>\n\n<p>使用如下的格式来临时地禁用文件中规则的警告:</p>\n\n<pre><code>/* eslint-disable */\n\n// Disables all rules between comments\nalert('foo');\n\n/* eslint-enable */\n</code></pre>\n\n<p>你也可以禁用或启用指定的规则:</p>\n\n<pre><code>/* eslint-disable no-alert, no-console */\n\n// Disables no-alert and no-console warnings between comments\nalert('foo'); \nconsole.log('bar');\n\n/* eslint-enable no-alert, no-console */\n</code></pre>\n\n<p>在整个文件中禁用规则警告,把 <code>/* eslint-disable */</code> 放到文件顶部:</p>\n\n<pre><code>/* eslint-disable */\n\n// Disables all rules for the rest of the file\nalert('foo'); \n</code></pre>\n\n<p>你也可以在整个文件禁用指定的规则:</p>\n\n<pre><code>/* eslint-disable no-alert */\n\n// Disables no-alert for the rest of the file\nalert('foo'); \n</code></pre>\n\n<p>在指定行禁用所有规则:</p>\n\n<pre><code>alert('foo'); // eslint-disable-line\n\n// eslint-disable-next-line\nalert('foo'); \n</code></pre>\n\n<p>在指定行禁用指定的规则:</p>\n\n<pre><code>alert('foo'); // eslint-disable-line no-alert\n\n// eslint-disable-next-line no-alert\nalert('foo'); \n</code></pre>\n\n<p>指定行禁用多个规则:</p>\n\n<pre><code>alert('foo'); // eslint-disable-line no-alert, quotes, semi\n\n// eslint-disable-next-line no-alert, quotes, semi\nalert('foo'); \n</code></pre>\n\n<p>注意:注释仅仅是禁用了一部分警告,告诉 ESlint 不要报告这部分代码违规。 ESlint 解析整个文件,所以禁用的代码仍然需要符合合法的 JavaScript 语法。</p>\n\n<h2 id=\"\">添加共享的设置</h2>\n\n<p>ESlint 支持向配置文件中添加共享的配置。你能够添加 <code>settings</code> 对象到 ESLint 配置文件中,它将会提供给每一个将要执行的规则。这也许是有用的如果你添加了习惯的规则并想要它们能够访问相同的信息并轻松配置。</p>\n\n<p>在 JSON 中:</p>\n\n<pre><code>{\n \"settings\": {\n \"sharedData\": \"Hello\"\n }\n}\n</code></pre>\n\n<p>在 YAML 文件中:</p>\n\n<pre><code>---\n settings:\n sharedData: \"Hello\"\n</code></pre>\n\n<h2 id=\"\">使用配置文件</h2>\n\n<p>这有两种方式去使用配置文件。第一种是保存文件到你喜欢的地方,然后传递它的地址到 CLI 使用 <code>-c</code> 选项,例如:</p>\n\n<pre><code>eslint -c myconfig.json myfiletotest.js \n</code></pre>\n\n<p>第二种方式是去使用配置文件通过 <code>.eslintrc.*</code> 或 <code>package.json</code> 文件。ESLint 将会自动在目录中寻找它们,从连续的父目录一路找到文件系统根目录。这个选项时有用的当你想要对项目的不同部分使用不同的配置,或者当你想要别的直接去使用 ESLint 而不需要记住它在配置文件中传递。</p>\n\n<p>两种方式,配置文件的设置都将会覆盖默认的配置。</p>\n\n<h2 id=\"\">配置文件格式</h2>\n\n<p>ESLint 支持各种格式的配置文件:</p>\n\n<ul>\n<li><strong>JavaScript</strong> - 使用 <code>.eslintrc.js</code> 并导出一个包含你配置信息的对象。</li>\n<li><strong>YAML</strong> - 使用 <code>.eslintrc.yaml</code> 或 <code>.eslintrc.yml</code> 来定义配置结构。</li>\n<li><strong>JSON</strong> - 使用 <code>.eslintrc.json</code> 来定义配置结构。 ESLint's JSON 文件也支持 JavaScript 风格的注释。</li>\n<li><strong>Deprecated</strong> - 使用 <code>eslintrc</code>,过时的,可以是 JSON 或 YAML。</li>\n<li><strong>package.json</strong> - 创建一个 <code>eslintConfig</code> 属性在你的 <code>package.json</code>文件并在这里定义你的配置。</li>\n</ul>\n\n<p>如果有多种配置文件在同一个目录,ESLint将会使用一个。它们的优先级如下:</p>\n\n<ol>\n<li><code>.eslintrc.js</code> </li>\n<li><code>.eslintrc.yaml</code> </li>\n<li><code>.eslintrc.yml</code> </li>\n<li><code>.eslintrc.json</code> </li>\n<li><code>.eslintrc</code> </li>\n<li><code>package.json</code></li>\n</ol>\n\n<h2 id=\"\">配置及联和层次结构</h2>\n\n<p>当使用 <code>.eslintrc.*</code> 和 <code>package.json</code> 文件来配置,你可以利用配置级联的优势。举例来说,假设你有下列的目录结构:</p>\n\n<pre><code>your-project \n├── .eslintrc\n├── lib\n│ └── source.js\n└─┬ tests\n ├── .eslintrc\n └── test.js\n</code></pre>\n\n<p>配置级联将会使用最近的 <code>.eslintrc</code> 文件作为最高优先级,然后是任何父级目录的配置文件等。当你在项目中运行 ESLint ,所有 <code>lib/</code> 目录下的文件将会使用根目录下的 <code>.eslintrc</code> 文件作为它们的配置。如果 ESLint 进入 <code>test/</code> 目录,它将会使用 <code>your-project/tests/.eslintrc</code> 而不是 <code>your-project/.eslintrc</code> 。所以 <code>your-project/tests/test.js</code> 的检测是基于目录层次结构中的两个 <code>.eslintrc</code> 文件的组合的,近的拥有更高的优先级。这样的方式,你可以拥有项目级别的 ESLint 设置,并可以用目录层次的配置来覆盖它。</p>\n\n<p>相同的方式,在 <code>package.json</code> 文件中也可行。根目录中 <code>package.json</code> 的 <code>eslintConfig</code> 字段将在其所有子目录生效,但是在 tests 目录的 <code>.eslintrc</code> 文件将会覆盖所有冲突的配置。</p>\n\n<pre><code>your-project \n├── package.json\n├── lib\n│ └── source.js\n└─┬ tests\n ├── .eslintrc\n └── test.js\n</code></pre>\n\n<p>如果在一个目录中,同时找到了 <code>.eslintrc</code> 和 <code>package.json</code> 文件,<code>eslintrc</code> 文件将有更高的优先级, <code>package.json</code> 文件将不被使用。</p>\n\n<p>注意:如果你又一个个人的配置文件在你的用户目录( <code>~/.eslintrc</code> ),它将会仅仅在没有别的配置文件没发现的情况下使用。它将会对你所有用户目录下的文件生效,包括第三方的代码,因此在运行 ESLint 很可能会产生问题。</p>\n\n<p>默认地, ESLint 将会在所有父目录直到根目录寻找配置文件。这将是有用的,如果你想要所有你的项目去跟随某个约定,但是有时候会导致意想不到的结果。限制 ESLint 在一个指定的项目,把 <code>\"root\": true</code> 加到 <code>package.json</code> 的 <code>eslintConfig</code> 字段或者 <code>.eslintrc.*</code> 文件,在你的根目录。 ESLint 将会停止寻找父目录当它找到了 <code>\"root\": true</code> 的配置。</p>\n\n<pre><code>{\n \"root\": true\n}\n</code></pre>\n\n<p>YAML 文件中:</p>\n\n<pre><code>---\n root: true\n</code></pre>\n\n<p>举例来说, 这个例子当中 <code>main.js</code> 将会使用 <code>lib/</code> 中的配置,而不会使用 <code>productA</code> 目录下的 <code>.eslintrc</code> 文件的配置,因为在 <code>lib/</code> 中的 <code>.eslintrc</code> 有 <code>\"root\": true</code> 的设置。</p>\n\n<pre><code>home \n└── user\n ├── .eslintrc <- Always skipped if other configs present\n └── projectA\n ├── .eslintrc <- Not used\n └── lib\n ├── .eslintrc <- { \"root\": true }\n └── main.js\n</code></pre>\n\n<p>完整的配置层次,从高到低如下:</p>\n\n<ol>\n<li>行内配置 <br />\n<ol><li><code>/*eslint-disable*/</code> and <code>/*eslint-enable*/</code></li>\n<li><code>/*global*/</code></li>\n<li><code>/*eslint*/</code></li>\n<li><code>/*eslint-env*/</code></li></ol></li>\n<li>命令行配置 <br />\n<ol><li><code>--global</code></li>\n<li><code>--rule</code></li>\n<li><code>--env</code></li>\n<li><code>-c, --config</code></li></ol></li>\n<li>项目级别配置 <br />\n<ol><li><code>.eslintrc.*</code> 或 <code>package.json</code> 文件</li>\n<li>祖先目录中寻找,除非有 <code>\"root\": true</code> 将不会向上寻找</li>\n<li>个人某人的配置在 <code>~/.eslintrc</code></li></ol></li>\n</ol>\n\n<h2 id=\"\">扩展配置文件</h2>\n\n<p>如果你想要扩展一个指定的配置文件,你可以使用 <code>extends</code> 属性,并制定路径。可以是相对的或是绝对的路径。</p>\n\n<p>配置能够被如下文件扩展:</p>\n\n<ol>\n<li>YAML 文件 </li>\n<li>JSON 文件 </li>\n<li>JS 文件 </li>\n<li>共享的配置包</li>\n</ol>\n\n<p>扩展配置提供了基础的规则,并可以覆盖。举例:</p>\n\n<pre><code>{\n \"extends\": \"./node_modules/coding-standard/.eslintrc\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": 1\n }\n}\n</code></pre>\n\n<p>配置也许也可以以数组的方式提供,后面的会覆盖前面的相同的规则的配置。举例:</p>\n\n<pre><code>{\n \"extends\": [\n \"./node_modules/coding-standard/eslintDefaults.js\",\n // Override eslintDefaults.js\n \"./node_modules/coding-standard/.eslintrc-es6\",\n // Override .eslintrc-es6\n \"./node_modules/coding-standard/.eslintrc-jsx\",\n ],\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n</code></pre>\n\n<p>扩展配置能够包涵它们自己的 <code>extends</code> ,导致循环的引用。</p>\n\n<p>你也可以使用共享的配置包。你需要使用 npm 去安装它们,例如:</p>\n\n<pre><code>{\n \"extends\": \"eslint-config-myrules\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n</code></pre>\n\n<p>在这个例子, <code>eslint-config-myrules</code> 包将会加载为一个对象并作为这个配置的父配置。</p>\n\n<p>注意:你可以省略 <code>eslint-config-</code> 前缀, ESLint 将会自动为你添加,和插件工作相似。</p>\n\n<p>ESlint 也支持插件提供的扩展配置:</p>\n\n<pre><code>{\n \"extends\": \"plugin:eslint-plugin-myplugin/myConfig\",\n\n \"rules\": {\n // Override any settings from the \"parent\" configuration\n \"eqeqeq\": \"warn\"\n }\n}\n</code></pre>\n\n<p>在这个例子, <code>eslint-plugin-myplugin</code> 包包含了名为 <code>default</code> 的配置。</p>\n\n<p><strong>十分重要</strong>:使用插件中的配置时,必须要加上 <code>plugin:</code> 前缀来指明哈。但你可以可选地省略 <code>eslint-plugin</code> 前缀。</p>\n\n<p>注意:对于根目录或者其它父目录, <code>extends</code> 处理路径会用当前工作目录,而不是文件自身。</p>\n\n<h2 id=\"\">配置文件中的注释</h2>\n\n<p>JSON 和 YAML 配置文件都支持注释, <code>package.json</code> 不能包括注释。你可以使用 JAVAScript 风格的注释或 YAML 风格的注释, ESLint 将安全地忽略它们。这可以允许你的配置文件跟友好。举例:</p>\n\n<pre><code>{\n \"env\": {\n \"browser\": true\n },\n \"rules\": {\n // Override our default settings just for this directory\n \"eqeqeq\": \"warn\",\n \"strict\": \"off\"\n }\n}\n</code></pre>\n\n<h2 id=\"\">指定需要检测的文件后缀</h2>\n\n<p>当前指定后缀需要在命令行选项 <code>--ext</code> 后,添加用空格分开的后缀名列表。</p>\n\n<h2 id=\"\">忽略文件或目录</h2>\n\n<p>你可以告诉 ESLint 去忽略指定的文件或目录,通过一个 <code>.eslintignore</code> 文件在你的根目录。 <code>.eslintignore</code> 文件是一个普通文本文件,每一行定义了要被忽略的文件或目录。举例:下列表达式将会省略所有 JavaScript 文件:</p>\n\n<pre><code>**/*.js\n</code></pre>\n\n<p>当 ESLint 运行的时候,它会寻找当前工作目录的 <code>.eslintignore</code> 文件在它决定监测之前。如果找到了文件,这里面的配置将会生效。 <code>.eslintignore</code> 只会被使用一次,因此目录中其他的 <code>.eslintignore</code> 将不会被使用。</p>\n\n<ul>\n<li>#好开头将会被认为是注释</li>\n<li>相对于 <code>.eslintignore</code> 文件路径或当前工作目录的路径</li>\n<li>忽略规则参照 <code>.gitignore</code> 规范</li>\n<li>!用来取消前面的忽略的匹配</li>\n</ul>\n\n<p><code>/node_modules/*</code> 和 <code>/bower_components/*</code> 将默认被忽略。</p>\n\n<p>举例,把下面的 <code>.eslintignore</code> 文件放倒工作目录下将会忽略<code>/node_modules/*</code> 和 <code>/bower_components/*</code> 目录,任何扩展名为 <code>.ts.js</code> 或 <code>.coffee.js</code> 可能被转换,任何在 <code>build/*</code> 目录除了 <code>build/index.js</code> 将被忽略:</p>\n\n<pre><code># /node_modules/* and /bower_components/* ignored by default\n\n# Ignore built files except build/index.js\nbuild/* \n!build/index.js\n</code></pre>\n\n<h2 id=\"\">使用替换的文件</h2>\n\n<p>如果你喜欢去使用别的文件而不是 <code>.eslintignore</code> 来工作,你在命令行中可以指定 <code>--ignore-path</code> 选项。举例来说,你可以使用 <code>.jshintignore</code> ,因为两者的配置相同:</p>\n\n<pre><code>eslint --ignore-path .jshintignore file.js \n</code></pre>\n\n<p>你也可以使用 <code>.gitignore</code> 文件:</p>\n\n<pre><code>eslint --ignore-path .gitignore file.js \n</code></pre>\n\n<p>任何标准的 ignore 文件可以被使用。</p>\n\n<h2 id=\"\">忽略文件警告</h2>\n\n<p>当你 eslint 一个被忽略的文件或目录时,会发出警告,你可以使用 <code>--no-ignore</code> 来进行省略。</p>","image":null,"featured":0,"page":0,"status":"published","language":"en_US","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464345663531,"created_by":1,"updated_at":1464770186759,"updated_by":1,"published_at":1464348992506,"published_by":1},{"id":12,"uuid":"d0ae47ae-bcbc-4333-93af-004ae853c2a2","title":"react 的虫洞 context(官方文档翻译)","slug":"react-de-chong-dong-context","markdown":"> 中文社区还停留在 0.14.7,文档上有不少落后了,比如 shadowEqual、context 等的知识,所以只能自己来了。\n\n#### Context\n\nReact 一个强大之处就是很容易去跟踪数据在组件中的流动。当你观察一个组件时,你能够容易地确切地看出什么 props 被传递,这使得你的 apps 能够容易地推导。\n\n偶尔,你想要去传递数据贯穿整个组件树,不想手动地一级级传递下去。React 的 \"context\" 特性帮助你做到这一点。(译外音:形象地描述就是像虫洞那样达到穿越效果,props 能够在自己共享,有点像全局变量)。\n\n> 注意: Context 是一个先进的、实验性的特性。这个 API 可能会在未来的发行版有所改变。\n\n> 大多数应用将不需要去使用 context。尤其如果你仅仅是初学 React ,你应当尽量不用 context。使用 context 将会试你的代码难以去理解,因为它使得数据的流动不清晰。它就像全局变量传递变量到整个应用。\n\n> 如果你不得不使用 context,请谨慎地使用。无论你是在在构建一个应用还是库,尝试去隔离 context 到一个小的区域和避免直接地使用 context API,这样可以在 API 改变时,方便地升级。\n\n#### 在组件树中自动地传递信息\n\n假定你有如下的结构:\n\n```\nclass Button extends React.Component {\n render() {\n return (\n <button style={{background: this.props.color}}>\n {this.props.children}\n </button>\n );\n }\n}\n\nclass Message extends React.Component {\n render() {\n return (\n <div>\n {this.props.text} <Button color={this.props.color}>Delete</Button>\n </div>\n );\n }\n}\n\nclass MessageList extends React.Component {\n render() {\n const color = \"purple\";\n const children = this.props.messages.map((message) =>\n <Message text={message.text} color={color} />\n );\n return <div>{children}</div>;\n }\n}\n```\n\n在这个例子里,我们为了合适地设置 Button 和 Message 的样式,手动传递一个 color prop 。主题是一个好的例子,当你想要让整个子级可以访问一些信息片段(如:一个颜色)。 使用 context ,你可以传递这些自动地。\n\n```\nclass Button extends React.Component {\n render() {\n return (\n <button style={{background: this.context.color}}>\n {this.props.children}\n </button>\n );\n }\n}\n\nButton.contextTypes = {\n color: React.PropTypes.string\n};\n\nclass Message extends React.Component {\n render() {\n return (\n <div>\n {this.props.text} <Button>Delete</Button>\n </div>\n );\n }\n}\n\nclass MessageList extends React.Component {\n getChildContext() {\n return {color: \"purple\"};\n }\n\n render() {\n const children = this.props.messages.map((message) =>\n <Message text={message.text} />\n );\n return <div>{children}</div>;\n }\n}\n\nMessageList.childContextTypes = {\n color: React.PropTypes.string\n};\n```\n\n通过在 MessageList(context的提供者) 添加 ` childContextTypes ` 和 ` getChildContext `, React 将会自动地传递信息下去,在所有的子组件中通过定义 ` contextTypes ` 就可以访问。\n\n如果 ` contextTypes ` 没有定义,context 会是一个空的对象。\n\n#### 父子耦合(这一段优点不明觉厉)\nContext 也能够让你创建一个 API 如下:\n\n```\n<Menu>\n <MenuItem>aubergine</MenuItem>\n <MenuItem>butternut squash</MenuItem>\n <MenuItem>clementine</MenuItem>\n</Menu>\n```\n\n通过在 Menu 组件传递相关的信息,每一个 MenuItem 能够传回 Menu 容器。\n\n在你使用这个 API 构建组件之前,考虑是否有更干净的替代方案。我们盲目的传递 items 作为一个数组,如下:\n\n```\n<Menu items={['aubergine', 'butternut squash', 'clementine']} />\n```\n\n再次提醒,你能够在 props 中传递整个 React 组件如果你喜欢。\n\n#### 在生命周期方法引用 context \n\n如果 ` contextTypes ` 被定义在一个组件中,下列生命周期方法将会收到额外的参数:\n\n```\nvoid componentWillReceiveProps(\n object nextProps, object nextContext\n)\n\nboolean shouldComponentUpdate(\n object nextProps, object nextState, object nextContext\n)\n\nvoid componentWillUpdate(\n object nextProps, object nextState, object nextContext\n)\n\nvoid componentDidUpdate(\n object prevProps, object prevState, object prevContext\n)\n```\n\n#### 在无状态函数组件引用 context \n\n无状态函数组件也能够引用 context ,如果 ` contextTypes ` 被定义为函数的参数。下列代码显示了基于无状态函数的 Button 组件。\n\n```\nconst Button = ({children}, context) =>\n <button style={{background: context.color}}>\n {children}\n </button>;\n\nButton.contextTypes = {color: React.PropTypes.string};\n```\n\n#### 更新 context\n当 state 或 props 改变, ` getChildContext ` 函数将被调用。为了更新 context 中的数据, 用 ` this.setState ` 触发一个本地的 state 的更新。这将会触发一个新的 context 然后子组件将会收到改变的 context。\n\n```\nclass MediaQuery extends React.Component {\n constructor(props) {\n super(props);\n this.state = {type:'desktop'};\n }\n\n getChildContext() {\n return {type: this.state.type};\n }\n\n componentDidMount() {\n const checkMediaQuery = () => {\n const type = window.matchMedia(\"(min-width: 1025px)\").matches ? 'desktop' : 'mobile';\n if (type !== this.state.type) {\n this.setState({type});\n }\n };\n\n window.addEventListener('resize', checkMediaQuery);\n checkMediaQuery();\n }\n\n render() {\n return this.props.children;\n }\n}\n\nMediaQuery.childContextTypes = {\n type: React.PropTypes.string\n};\n```\n\n#### 什么时候不该使用 context \n\n在写干净的代码时,全局变量是最好要避免的,你应当避免使用 context 在那多数情况下。尤其是在使用它去节省代码和代替明确的传递 props 时,要思考再三。\n\n最好的方式对于 context 是绝对地向下传递 登录用户、当前语言或主题信息等。所有这些除了全局变量,context 推荐你把它们规定在一个单一的 React 组件树内。\n\n在组件中不要使用 context 去传递你的模型数据。在整个树上明确地传递你的数据是更加容易去理解的。使用 context 使你的组件更多的耦合和更少的复用性,因为它们的渲染因所处的环境而不同。\n\n#### 已知的限制\n\n如果一个 context 的值被组件的改变提供,当中间组件使用了` shouldComponentUpdate ` 并返回了 `false\n ` 那么后代的值将不会改变。详见 github issue [#2517](https://github.com/facebook/react/issues/2517)\n\n","html":"<blockquote>\n <p>中文社区还停留在 0.14.7,文档上有不少落后了,比如 shadowEqual、context 等的知识,所以只能自己来了。</p>\n</blockquote>\n\n<h4 id=\"context\">Context</h4>\n\n<p>React 一个强大之处就是很容易去跟踪数据在组件中的流动。当你观察一个组件时,你能够容易地确切地看出什么 props 被传递,这使得你的 apps 能够容易地推导。</p>\n\n<p>偶尔,你想要去传递数据贯穿整个组件树,不想手动地一级级传递下去。React 的 \"context\" 特性帮助你做到这一点。(译外音:形象地描述就是像虫洞那样达到穿越效果,props 能够在自己共享,有点像全局变量)。</p>\n\n<blockquote>\n <p>注意: Context 是一个先进的、实验性的特性。这个 API 可能会在未来的发行版有所改变。</p>\n \n <p>大多数应用将不需要去使用 context。尤其如果你仅仅是初学 React ,你应当尽量不用 context。使用 context 将会试你的代码难以去理解,因为它使得数据的流动不清晰。它就像全局变量传递变量到整个应用。</p>\n \n <p>如果你不得不使用 context,请谨慎地使用。无论你是在在构建一个应用还是库,尝试去隔离 context 到一个小的区域和避免直接地使用 context API,这样可以在 API 改变时,方便地升级。</p>\n</blockquote>\n\n<h4 id=\"\">在组件树中自动地传递信息</h4>\n\n<p>假定你有如下的结构:</p>\n\n<pre><code>class Button extends React.Component { \n render() {\n return (\n <button style={{background: this.props.color}}>\n {this.props.children}\n </button>\n );\n }\n}\n\nclass Message extends React.Component { \n render() {\n return (\n <div>\n {this.props.text} <Button color={this.props.color}>Delete</Button>\n </div>\n );\n }\n}\n\nclass MessageList extends React.Component { \n render() {\n const color = \"purple\";\n const children = this.props.messages.map((message) =>\n <Message text={message.text} color={color} />\n );\n return <div>{children}</div>;\n }\n}\n</code></pre>\n\n<p>在这个例子里,我们为了合适地设置 Button 和 Message 的样式,手动传递一个 color prop 。主题是一个好的例子,当你想要让整个子级可以访问一些信息片段(如:一个颜色)。 使用 context ,你可以传递这些自动地。</p>\n\n<pre><code>class Button extends React.Component { \n render() {\n return (\n <button style={{background: this.context.color}}>\n {this.props.children}\n </button>\n );\n }\n}\n\nButton.contextTypes = { \n color: React.PropTypes.string\n};\n\nclass Message extends React.Component { \n render() {\n return (\n <div>\n {this.props.text} <Button>Delete</Button>\n </div>\n );\n }\n}\n\nclass MessageList extends React.Component { \n getChildContext() {\n return {color: \"purple\"};\n }\n\n render() {\n const children = this.props.messages.map((message) =>\n <Message text={message.text} />\n );\n return <div>{children}</div>;\n }\n}\n\nMessageList.childContextTypes = { \n color: React.PropTypes.string\n};\n</code></pre>\n\n<p>通过在 MessageList(context的提供者) 添加 <code>childContextTypes</code> 和 <code>getChildContext</code>, React 将会自动地传递信息下去,在所有的子组件中通过定义 <code>contextTypes</code> 就可以访问。</p>\n\n<p>如果 <code>contextTypes</code> 没有定义,context 会是一个空的对象。</p>\n\n<h4 id=\"\">父子耦合(这一段优点不明觉厉)</h4>\n\n<p>Context 也能够让你创建一个 API 如下:</p>\n\n<pre><code><Menu> \n <MenuItem>aubergine</MenuItem>\n <MenuItem>butternut squash</MenuItem>\n <MenuItem>clementine</MenuItem>\n</Menu> \n</code></pre>\n\n<p>通过在 Menu 组件传递相关的信息,每一个 MenuItem 能够传回 Menu 容器。</p>\n\n<p>在你使用这个 API 构建组件之前,考虑是否有更干净的替代方案。我们盲目的传递 items 作为一个数组,如下:</p>\n\n<pre><code><Menu items={['aubergine', 'butternut squash', 'clementine']} /> \n</code></pre>\n\n<p>再次提醒,你能够在 props 中传递整个 React 组件如果你喜欢。</p>\n\n<h4 id=\"context\">在生命周期方法引用 context</h4>\n\n<p>如果 <code>contextTypes</code> 被定义在一个组件中,下列生命周期方法将会收到额外的参数:</p>\n\n<pre><code>void componentWillReceiveProps( \n object nextProps, object nextContext\n)\n\nboolean shouldComponentUpdate( \n object nextProps, object nextState, object nextContext\n)\n\nvoid componentWillUpdate( \n object nextProps, object nextState, object nextContext\n)\n\nvoid componentDidUpdate( \n object prevProps, object prevState, object prevContext\n)\n</code></pre>\n\n<h4 id=\"context\">在无状态函数组件引用 context</h4>\n\n<p>无状态函数组件也能够引用 context ,如果 <code>contextTypes</code> 被定义为函数的参数。下列代码显示了基于无状态函数的 Button 组件。</p>\n\n<pre><code>const Button = ({children}, context) => \n <button style={{background: context.color}}>\n {children}\n </button>;\n\nButton.contextTypes = {color: React.PropTypes.string}; \n</code></pre>\n\n<h4 id=\"context\">更新 context</h4>\n\n<p>当 state 或 props 改变, <code>getChildContext</code> 函数将被调用。为了更新 context 中的数据, 用 <code>this.setState</code> 触发一个本地的 state 的更新。这将会触发一个新的 context 然后子组件将会收到改变的 context。</p>\n\n<pre><code>class MediaQuery extends React.Component { \n constructor(props) {\n super(props);\n this.state = {type:'desktop'};\n }\n\n getChildContext() {\n return {type: this.state.type};\n }\n\n componentDidMount() {\n const checkMediaQuery = () => {\n const type = window.matchMedia(\"(min-width: 1025px)\").matches ? 'desktop' : 'mobile';\n if (type !== this.state.type) {\n this.setState({type});\n }\n };\n\n window.addEventListener('resize', checkMediaQuery);\n checkMediaQuery();\n }\n\n render() {\n return this.props.children;\n }\n}\n\nMediaQuery.childContextTypes = { \n type: React.PropTypes.string\n};\n</code></pre>\n\n<h4 id=\"context\">什么时候不该使用 context</h4>\n\n<p>在写干净的代码时,全局变量是最好要避免的,你应当避免使用 context 在那多数情况下。尤其是在使用它去节省代码和代替明确的传递 props 时,要思考再三。</p>\n\n<p>最好的方式对于 context 是绝对地向下传递 登录用户、当前语言或主题信息等。所有这些除了全局变量,context 推荐你把它们规定在一个单一的 React 组件树内。</p>\n\n<p>在组件中不要使用 context 去传递你的模型数据。在整个树上明确地传递你的数据是更加容易去理解的。使用 context 使你的组件更多的耦合和更少的复用性,因为它们的渲染因所处的环境而不同。</p>\n\n<h4 id=\"\">已知的限制</h4>\n\n<p>如果一个 context 的值被组件的改变提供,当中间组件使用了<code>shouldComponentUpdate</code> 并返回了 <code>false\n</code> 那么后代的值将不会改变。详见 github issue <a href=\"https://github.com/facebook/react/issues/2517\">#2517</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464684672461,"created_by":1,"updated_at":1467696306538,"updated_by":1,"published_at":1467685948549,"published_by":1},{"id":13,"uuid":"e38601e3-c8d0-4448-9fae-9f792e249b1a","title":"react native 踩坑实录","slug":"react-native-keng","markdown":"### 持续更新。。。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26402069&auto=1&height=66\"></iframe>\n\n1、 [Android] StatusBar Translucent not working in Android 4.x.x after upgrading to react 0.23 [#6876](https://github.com/facebook/react-native/issues/6876)\n\n> solution: I changed the implementation of translucent and it only works on API 21+ now. Can you just not add top padding on Android 4.4- using Platform.Version < 21?\n\n2、 在使用pure-render相关的组件时,尽量不要使用箭头函数或者是行内的bind,因为这两种方式都是会返回一个新的实例,从而导致re-render。\n\n> solution: bind function in the constructor.\n\n3、 定时器在rn中使用还是比较广泛的,但是 timeMixin 暂时不支持在 es6 class 中的使用,于是去github看了下issue,官网也推荐使用 reactMixin 将 mixin 绑定到 es6 class ,并亲自测试了一下,通过。中文网也给出了手动 clear 的方案,相比之下复用性低,其实就是不用 mixin 的方案,但是依赖比较少,各有优势吧!\n\n```\nimport React, { Component } from 'react-native';\nimport TimerMixin from 'react-timer-mixin';\nimport reactMixin from 'react-mixin';\nclass MyClass extends Component {\n componentDidMount() {\n this.setTimeout(\n () => { console.log('tick'); },\n 5000\n );\n }\n}\nreactMixin(MyClass.prototype, TimerMixin);\nmodule.exports = MyClass;\n```\n\n4、 这两天集成 redux 时,发现rn的 ListView 经常不更新,于是深入到了源码内一看了究竟。看看是什么时候 ListView 会更新row。[有点多,新开了一篇](http://m2mbob.cn:1314/listview-row-update/)\n\n5、 不知道哪个版本 ListView 引入了 ` enableEmptySections `属性,这个属性只能有一个值true,为true时,会渲染没有 ` row ` 的 ` section ` 。下面这段代码可以看到,这个属性将在之后的版本不被推荐使用,官方建议在传参数时把空的 ` section ` 过滤掉。不过在实践过程中发现空数组实际上也会构造一个 ` section ` ,导致 warning ,强迫症想把它去掉啊!😂 \n```\nif (rowIDs.length === 0) {\n if (this.props.enableEmptySections === undefined) {\n var warning = require('fbjs/lib/warning');\n warning(false, 'In next release empty section headers will be rendered.'\n + ' In this release you can use \\'enableEmptySections\\' flag to render empty section headers.');\n continue;\n } else {\n var invariant = require('fbjs/lib/invariant');\n invariant(\n this.props.enableEmptySections,\n 'In next release \\'enableEmptySections\\' flag will be deprecated, empty section headers will always be rendered.'\n + ' If empty section headers are not desirable their indices should be excluded from sectionIDs object.'\n + ' In this release \\'enableEmptySections\\' may only have value \\'true\\' to allow empty section headers rendering.');\n }\n }\n```\n\n6、 对于 TouchableHighlight 组件,会出现没有效果的情况,解决方案是在外面包一层背景。\n\n7、 ios 模拟器点击无效的解决方案,在模拟器的Debug菜单中,关掉Slow Animations就好了。关掉Slow Animations之后,模拟器响应的都快了,非常好用,建议模拟器这一项不要开着。\n\n8、 这个不是 react native 本身的问题,而是在使用 react-native-vector-icons 的 ToolBarAndroid 组件过程中遇到的问题。首先 actions 的 title字段是必须的,其次是在 react-native-vector-icons 当中不是使用 icon 而是使用 iconName, iconColor, iconSize 这三个参数!! 😒\n\n9、 今天拿出自己的安卓机测流程时发现,文本在安卓上的 padding 不见了,然后去 github ,果然有道友提出了相同的问题[#7848](https://github.com/facebook/react-native/issues/7848#issuecomment-233517201)。不过 margin 是工作良好的,所以暂时先用 margin 替换了原来的 padding。\n\n10、 TouchableNativeFeedback 组件只支持 个人 View 作为子元素,其他元素将无法显示,多个元素则会报错。\n\n11、 WebSocket connection to 'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome' failed: Invalid frame header[6627](https://github.com/facebook/react-native/issues/6627) node 6.3 下面 rn 调试会出问题,建议先滚回 6.2,不过 rn 0.30 貌似解决了这个问题,但是暂时不升级 rn ,所以先记一下。","html":"<h3 id=\"\">持续更新。。。</h3>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26402069&auto=1&height=66\"></iframe>\n\n<p>1、 [Android] StatusBar Translucent not working in Android 4.x.x after upgrading to react 0.23 <a href=\"https://github.com/facebook/react-native/issues/6876\">#6876</a></p>\n\n<blockquote>\n <p>solution: I changed the implementation of translucent and it only works on API 21+ now. Can you just not add top padding on Android 4.4- using Platform.Version < 21?</p>\n</blockquote>\n\n<p>2、 在使用pure-render相关的组件时,尽量不要使用箭头函数或者是行内的bind,因为这两种方式都是会返回一个新的实例,从而导致re-render。</p>\n\n<blockquote>\n <p>solution: bind function in the constructor.</p>\n</blockquote>\n\n<p>3、 定时器在rn中使用还是比较广泛的,但是 timeMixin 暂时不支持在 es6 class 中的使用,于是去github看了下issue,官网也推荐使用 reactMixin 将 mixin 绑定到 es6 class ,并亲自测试了一下,通过。中文网也给出了手动 clear 的方案,相比之下复用性低,其实就是不用 mixin 的方案,但是依赖比较少,各有优势吧!</p>\n\n<pre><code>import React, { Component } from 'react-native'; \nimport TimerMixin from 'react-timer-mixin'; \nimport reactMixin from 'react-mixin'; \nclass MyClass extends Component { \n componentDidMount() {\n this.setTimeout(\n () => { console.log('tick'); },\n 5000\n );\n }\n}\nreactMixin(MyClass.prototype, TimerMixin); \nmodule.exports = MyClass; \n</code></pre>\n\n<p>4、 这两天集成 redux 时,发现rn的 ListView 经常不更新,于是深入到了源码内一看了究竟。看看是什么时候 ListView 会更新row。<a href=\"http://m2mbob.cn:1314/listview-row-update/\">有点多,新开了一篇</a></p>\n\n<p>5、 不知道哪个版本 ListView 引入了 <code>enableEmptySections</code>属性,这个属性只能有一个值true,为true时,会渲染没有 <code>row</code> 的 <code>section</code> 。下面这段代码可以看到,这个属性将在之后的版本不被推荐使用,官方建议在传参数时把空的 <code>section</code> 过滤掉。不过在实践过程中发现空数组实际上也会构造一个 <code>section</code> ,导致 warning ,强迫症想把它去掉啊!😂 </p>\n\n<pre><code>if (rowIDs.length === 0) { \n if (this.props.enableEmptySections === undefined) {\n var warning = require('fbjs/lib/warning');\n warning(false, 'In next release empty section headers will be rendered.'\n + ' In this release you can use \\'enableEmptySections\\' flag to render empty section headers.');\n continue;\n } else {\n var invariant = require('fbjs/lib/invariant');\n invariant(\n this.props.enableEmptySections,\n 'In next release \\'enableEmptySections\\' flag will be deprecated, empty section headers will always be rendered.'\n + ' If empty section headers are not desirable their indices should be excluded from sectionIDs object.'\n + ' In this release \\'enableEmptySections\\' may only have value \\'true\\' to allow empty section headers rendering.');\n }\n }\n</code></pre>\n\n<p>6、 对于 TouchableHighlight 组件,会出现没有效果的情况,解决方案是在外面包一层背景。</p>\n\n<p>7、 ios 模拟器点击无效的解决方案,在模拟器的Debug菜单中,关掉Slow Animations就好了。关掉Slow Animations之后,模拟器响应的都快了,非常好用,建议模拟器这一项不要开着。</p>\n\n<p>8、 这个不是 react native 本身的问题,而是在使用 react-native-vector-icons 的 ToolBarAndroid 组件过程中遇到的问题。首先 actions 的 title字段是必须的,其次是在 react-native-vector-icons 当中不是使用 icon 而是使用 iconName, iconColor, iconSize 这三个参数!! 😒</p>\n\n<p>9、 今天拿出自己的安卓机测流程时发现,文本在安卓上的 padding 不见了,然后去 github ,果然有道友提出了相同的问题<a href=\"https://github.com/facebook/react-native/issues/7848#issuecomment-233517201\">#7848</a>。不过 margin 是工作良好的,所以暂时先用 margin 替换了原来的 padding。</p>\n\n<p>10、 TouchableNativeFeedback 组件只支持 个人 View 作为子元素,其他元素将无法显示,多个元素则会报错。</p>\n\n<p>11、 WebSocket connection to 'ws://localhost:8081/debugger-proxy?role=debugger&name=Chrome' failed: Invalid frame header<a href=\"https://github.com/facebook/react-native/issues/6627\">6627</a> node 6.3 下面 rn 调试会出问题,建议先滚回 6.2,不过 rn 0.30 貌似解决了这个问题,但是暂时不升级 rn ,所以先记一下。</p>","image":null,"featured":1,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1464770450231,"created_by":1,"updated_at":1470033491815,"updated_by":1,"published_at":1466558940000,"published_by":1},{"id":14,"uuid":"5b126970-3765-4b5e-9e06-920981bfd050","title":"Immutable.js 文档翻译(API部分)","slug":"immutable-js-wen-dang-fan-yi-apibu-fen","markdown":"不可变数据鼓励纯函数(相同的数据输入得到相同的输出),适用于更加简单的应用开发,并支持延迟计算等函数式编程的技术。\n\n当设计把这些强大的功能性概念带给 ` JavaScript ` 时,它提出了一个 ` JavaScript ` 工程师熟悉的面向对象的 API并十分接近地反映了 ` Array ` , ` Map\n ` 和 ` Set ` 等数据结构。它能够很容易并高效地与普通 ` JavaScript ` 基本类型转换。 \n\n注意:所有的例子用 [ES6](https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla)。为了在所有浏览器中运行,它们需要呗专为 ES3 。例如:\n\n```\n// ES6\nfoo.map(x => x * x);\n// ES3\nfoo.map(function (x) { return x * x; });\n```\n\n#### API\n\n[fromJS()](www.baidu.com)\n\n把普通的 JS 对象和数组深度转换为不可变的 Maps 和 Lists。\n\n[is()](www.baidu.com)\n\n比较直是否相等,类似于 ES6 新引入的 [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) ,但是它把不可变的 [Iterable](www.baidu.com) 当作值,如果第二个 [Iterable](www.baidu.com) 包含相等的值就相等。\n\n[List](www.baidu.com)\n\nLists 是排序索引密集的集合,与 ` JavaScript ` 的 ` Array ` 十分相似。 \n\n[Map](www.baidu.com)\n\n不可变的 Map ,获取和设置的复杂度为 ` O(log32 N) ` 。 \n\n[OrderedMap](www.baidu.com)\n\n一种保证了内容顺序与 set() 顺序一致的 Map。 \n\n[Set](www.baidu.com)\n\n唯一值集合,添加和判断存在复杂度为 ` O(log32 N) ` 。 \n\n[OrderedSet](www.baidu.com)\n\n一种保证了内容顺序与 add() 顺序一致的 Set。\n\n[Stack](www.baidu.com)\n\n支持高效的添加和和删除的索引集合,栈。\n\n[Range()](www.baidu.com)","html":"<p>不可变数据鼓励纯函数(相同的数据输入得到相同的输出),适用于更加简单的应用开发,并支持延迟计算等函数式编程的技术。</p>\n\n<p>当设计把这些强大的功能性概念带给 <code>JavaScript</code> 时,它提出了一个 <code>JavaScript</code> 工程师熟悉的面向对象的 API并十分接近地反映了 <code>Array</code> , <code>Map\n</code> 和 <code>Set</code> 等数据结构。它能够很容易并高效地与普通 <code>JavaScript</code> 基本类型转换。 </p>\n\n<p>注意:所有的例子用 <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla\">ES6</a>。为了在所有浏览器中运行,它们需要呗专为 ES3 。例如:</p>\n\n<pre><code>// ES6\nfoo.map(x => x * x); \n// ES3\nfoo.map(function (x) { return x * x; }); \n</code></pre>\n\n<h4 id=\"api\">API</h4>\n\n<p><a href=\"www.baidu.com\">fromJS()</a></p>\n\n<p>把普通的 JS 对象和数组深度转换为不可变的 Maps 和 Lists。</p>\n\n<p><a href=\"www.baidu.com\">is()</a></p>\n\n<p>比较直是否相等,类似于 ES6 新引入的 <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\">Object.is</a> ,但是它把不可变的 <a href=\"www.baidu.com\">Iterable</a> 当作值,如果第二个 <a href=\"www.baidu.com\">Iterable</a> 包含相等的值就相等。</p>\n\n<p><a href=\"www.baidu.com\">List</a></p>\n\n<p>Lists 是排序索引密集的集合,与 <code>JavaScript</code> 的 <code>Array</code> 十分相似。 </p>\n\n<p><a href=\"www.baidu.com\">Map</a></p>\n\n<p>不可变的 Map ,获取和设置的复杂度为 <code>O(log32 N)</code> 。 </p>\n\n<p><a href=\"www.baidu.com\">OrderedMap</a></p>\n\n<p>一种保证了内容顺序与 set() 顺序一致的 Map。 </p>\n\n<p><a href=\"www.baidu.com\">Set</a></p>\n\n<p>唯一值集合,添加和判断存在复杂度为 <code>O(log32 N)</code> 。 </p>\n\n<p><a href=\"www.baidu.com\">OrderedSet</a></p>\n\n<p>一种保证了内容顺序与 add() 顺序一致的 Set。</p>\n\n<p><a href=\"www.baidu.com\">Stack</a></p>\n\n<p>支持高效的添加和和删除的索引集合,栈。</p>\n\n<p><a href=\"www.baidu.com\">Range()</a></p>","image":null,"featured":0,"page":0,"status":"draft","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1466583064760,"created_by":1,"updated_at":1466588205800,"updated_by":1,"published_at":null,"published_by":null},{"id":15,"uuid":"d84b75b7-f57b-4d44-abee-7ba5a9c92cc3","title":"深入RN ListView源码,了解 row 更新机制","slug":"listview-row-update","markdown":"> version 0.25.1\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=31445772&auto=1&height=66\"></iframe>\n\n打开`/node_modules/react-native/Libraries/CustomComponents/ListView` 文件夹,下面有两个js,分别为 ` ListView ` 和 ` ListViewDataSource `。首先看 ` ListView ` 321 行:\n```\n componentWillReceiveProps: function(nextProps) {\n // 只有传入了新的dataSource或者initialListSize改变时才会重新渲染整个列表\n if (this.props.dataSource !== nextProps.dataSource ||\n this.props.initialListSize !== nextProps.initialListSize) {\n this.setState((state, props) => {\n // 同一个dataSource上一次以渲染的行数置为0\n this._prevRenderedRowsCount = 0;\n return {\n // 第一次要渲染的行数\n curRenderedRowsCount: Math.min(\n Math.max(\n state.curRenderedRowsCount,\n props.initialListSize\n ),\n props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount()\n ),\n };\n }, () => this._renderMoreRowsIfNeeded());\n }\n },\n```\n其次是 `render` 函数:\n```\nfor (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {\n var rowID = rowIDs[rowIdx];\n var comboID = sectionID + '_' + rowID;\n // 当前行数大于已经渲染的行数且dataSource.rowShouldUpdate为true才进行新的一行的渲染。\n var shouldUpdateRow = rowCount >= this._prevRenderedRowsCount &&\n dataSource.rowShouldUpdate(sectionIdx, rowIdx);\n var row =\n <StaticRenderer\n key={'r_' + comboID}\n shouldUpdate={!!shouldUpdateRow}\n render={this.props.renderRow.bind(\n null,\n dataSource.getRowData(sectionIdx, rowIdx),\n sectionID,\n rowID,\n this._onRowHighlighted\n )}\n />;\n bodyComponents.push(row);\n totalIndex++;\n```\n最后就要看看 ` dataSource.rowShouldUpdate ` 发生了什么,打开 ` ListViewDataSource ` 236行:\n```\nrowShouldUpdate(sectionIndex: number, rowIndex: number): bool {\n // 尼玛_dirtyRows是啥\n var needsUpdate = this._dirtyRows[sectionIndex][rowIndex];\n warning(needsUpdate !== undefined,\n 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex);\n return needsUpdate;\n }\n```\n再到376行吧:\n```\n this._dirtyRows[sIndex] = [];\n for (var rIndex = 0; rIndex < this.rowIdentities[sIndex].length; rIndex++) {\n var rowID = this.rowIdentities[sIndex][rIndex];\n // 如果sectionID、rowID是新的或者调用我们定义的rowHasChanged返回是true的话就是脏的,就需要更新。\n dirty =\n !prevSectionsHash[sectionID] ||\n !prevRowsHash[sectionID][rowID] ||\n this._rowHasChanged(\n this._getRowData(prevDataBlob, sectionID, rowID),\n this._getRowData(this._dataBlob, sectionID, rowID)\n );\n this._dirtyRows[sIndex].push(!!dirty);\n }\n```\n到这里我终于发现自己的 row 为何不更新了,我们平时写的 ` rowHasChanged `方法都是直接 `===` 比较的,如果内容变了引用没变比较返回的就是 ` true ` 。这就要追溯到我在 reselect 中的操作了,我只改变了 列表数据的引用,而没有改变其中每一项数据的引用,所以在 ` rowHasChanged ` 比较时出了问题。不过改变引用之后,新的问题又来了, ` rowHasChanged `无论如何返回的都是true,然后每次都会更新,这是无法接受的。临时解决方案是,使用 fbjs 中的 ` shadowEqual ` 进行比较,而不是简单的 ` === `。 `shadowEqual` 只会比较第一层的属性,还不是最好的方案,最好的方案我认为是引入 `immutablejs`。","html":"<blockquote>\n <p>version 0.25.1</p>\n</blockquote>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=31445772&auto=1&height=66\"></iframe>\n\n<p>打开<code>/node_modules/react-native/Libraries/CustomComponents/ListView</code> 文件夹,下面有两个js,分别为 <code>ListView</code> 和 <code>ListViewDataSource</code>。首先看 <code>ListView</code> 321 行:</p>\n\n<pre><code> componentWillReceiveProps: function(nextProps) {\n // 只有传入了新的dataSource或者initialListSize改变时才会重新渲染整个列表\n if (this.props.dataSource !== nextProps.dataSource ||\n this.props.initialListSize !== nextProps.initialListSize) {\n this.setState((state, props) => {\n // 同一个dataSource上一次以渲染的行数置为0\n this._prevRenderedRowsCount = 0;\n return {\n // 第一次要渲染的行数\n curRenderedRowsCount: Math.min(\n Math.max(\n state.curRenderedRowsCount,\n props.initialListSize\n ),\n props.enableEmptySections ? props.dataSource.getRowAndSectionCount() : props.dataSource.getRowCount()\n ),\n };\n }, () => this._renderMoreRowsIfNeeded());\n }\n },\n</code></pre>\n\n<p>其次是 <code>render</code> 函数:</p>\n\n<pre><code>for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { \n var rowID = rowIDs[rowIdx];\n var comboID = sectionID + '_' + rowID;\n // 当前行数大于已经渲染的行数且dataSource.rowShouldUpdate为true才进行新的一行的渲染。\n var shouldUpdateRow = rowCount >= this._prevRenderedRowsCount &&\n dataSource.rowShouldUpdate(sectionIdx, rowIdx);\n var row =\n <StaticRenderer\n key={'r_' + comboID}\n shouldUpdate={!!shouldUpdateRow}\n render={this.props.renderRow.bind(\n null,\n dataSource.getRowData(sectionIdx, rowIdx),\n sectionID,\n rowID,\n this._onRowHighlighted\n )}\n />;\n bodyComponents.push(row);\n totalIndex++;\n</code></pre>\n\n<p>最后就要看看 <code>dataSource.rowShouldUpdate</code> 发生了什么,打开 <code>ListViewDataSource</code> 236行:</p>\n\n<pre><code>rowShouldUpdate(sectionIndex: number, rowIndex: number): bool { \n // 尼玛_dirtyRows是啥\n var needsUpdate = this._dirtyRows[sectionIndex][rowIndex];\n warning(needsUpdate !== undefined,\n 'missing dirtyBit for section, row: ' + sectionIndex + ', ' + rowIndex);\n return needsUpdate;\n }\n</code></pre>\n\n<p>再到376行吧:</p>\n\n<pre><code> this._dirtyRows[sIndex] = [];\n for (var rIndex = 0; rIndex < this.rowIdentities[sIndex].length; rIndex++) {\n var rowID = this.rowIdentities[sIndex][rIndex];\n // 如果sectionID、rowID是新的或者调用我们定义的rowHasChanged返回是true的话就是脏的,就需要更新。\n dirty =\n !prevSectionsHash[sectionID] ||\n !prevRowsHash[sectionID][rowID] ||\n this._rowHasChanged(\n this._getRowData(prevDataBlob, sectionID, rowID),\n this._getRowData(this._dataBlob, sectionID, rowID)\n );\n this._dirtyRows[sIndex].push(!!dirty);\n }\n</code></pre>\n\n<p>到这里我终于发现自己的 row 为何不更新了,我们平时写的 <code>rowHasChanged</code>方法都是直接 <code>===</code> 比较的,如果内容变了引用没变比较返回的就是 <code>true</code> 。这就要追溯到我在 reselect 中的操作了,我只改变了 列表数据的引用,而没有改变其中每一项数据的引用,所以在 <code>rowHasChanged</code> 比较时出了问题。不过改变引用之后,新的问题又来了, <code>rowHasChanged</code>无论如何返回的都是true,然后每次都会更新,这是无法接受的。临时解决方案是,使用 fbjs 中的 <code>shadowEqual</code> 进行比较,而不是简单的 <code>===</code>。 <code>shadowEqual</code> 只会比较第一层的属性,还不是最好的方案,最好的方案我认为是引入 <code>immutablejs</code>。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1466662979518,"created_by":1,"updated_at":1466845709975,"updated_by":1,"published_at":1466843081339,"published_by":1},{"id":16,"uuid":"00f8ea34-3b29-434c-a49c-c4f0fab84839","title":"关于 ES 草案","slug":"guan-yu-es-cao-an","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=414611126&auto=1&height=66\"></iframe>\n\nbabel6 模块化之后,使用 Presets 来引入 babel 转译时需要支持的特性,此时我们能够看到几个 stage-x 的 persets,一脸懵逼,于是就去了解了一下这是啥。\n\n这一切要从 ES 草案的制定说起。ECMA的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订ECMAScript标准,成员包括Microsoft、Mozilla、Google等大公司。\n\n任何人都可以向TC39提案,从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由TC39委员会批准。\n\n- Stage 0 - Strawman(展示阶段)\n- Stage 1 - Proposal(征求意见阶段)\n- Stage 2 - Draft(草案阶段)\n- Stage 3 - Candidate(候选人阶段)\n- Stage 4 - Finished(定案阶段) \n\n我们可以在 github 这个仓库查看各个阶段有哪些提案,[各阶段提案](https://github.com/tc39/proposals)。我们可以看到 ` Async Functions ` 已经进入 Stage 3 了呢,😬,这意味着离它进入 ES 规范的日子不远了,不过今年已经的新特性已经发布了哈哈,明年见。今年的规范更新了 ` Array.includes ` 和 指数两个特性。至于为什么今年相较于去年只更新了两个特性,可以看知乎这个回答,相当详细啊[如何评价 ECMAScript 2016(ES7)只新增2个特性?](http://www.zhihu.com/question/39993685)","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=414611126&auto=1&height=66\"></iframe>\n\n<p>babel6 模块化之后,使用 Presets 来引入 babel 转译时需要支持的特性,此时我们能够看到几个 stage-x 的 persets,一脸懵逼,于是就去了解了一下这是啥。</p>\n\n<p>这一切要从 ES 草案的制定说起。ECMA的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订ECMAScript标准,成员包括Microsoft、Mozilla、Google等大公司。</p>\n\n<p>任何人都可以向TC39提案,从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由TC39委员会批准。</p>\n\n<ul>\n<li>Stage 0 - Strawman(展示阶段)</li>\n<li>Stage 1 - Proposal(征求意见阶段)</li>\n<li>Stage 2 - Draft(草案阶段)</li>\n<li>Stage 3 - Candidate(候选人阶段)</li>\n<li>Stage 4 - Finished(定案阶段) </li>\n</ul>\n\n<p>我们可以在 github 这个仓库查看各个阶段有哪些提案,<a href=\"https://github.com/tc39/proposals\">各阶段提案</a>。我们可以看到 <code>Async Functions</code> 已经进入 Stage 3 了呢,😬,这意味着离它进入 ES 规范的日子不远了,不过今年已经的新特性已经发布了哈哈,明年见。今年的规范更新了 <code>Array.includes</code> 和 指数两个特性。至于为什么今年相较于去年只更新了两个特性,可以看知乎这个回答,相当详细啊<a href=\"http://www.zhihu.com/question/39993685\">如何评价 ECMAScript 2016(ES7)只新增2个特性?</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1466844324720,"created_by":1,"updated_at":1466845592657,"updated_by":1,"published_at":1466845477628,"published_by":1},{"id":17,"uuid":"cf9bc202-eb88-46f0-ad4c-a31097b78801","title":"最后一次了我的宝贝","slug":"zui-hou-yi-ci-liao-wo-de-bao-bei-2","markdown":"疯疯癫癫了一晚,四瓶酒下去,本以为能让自己睡个好觉,但是不到十二点酒醒来,却再也睡不着了,一遍遍循环着张悬翻唱的 you'll see。想写的很多,还是留在心底吧,花几个不眠之夜慢慢地消化。\n\n谢谢你,我的宝贝,这是最后一次这么叫了。今天的你还是那么地率真、勇敢,我也还是爱你的,但是却没有理由挽留你了,我知道你已经不那么爱我了,我是那么的无奈,像个小丑说这些无聊地话。\n\n不过还是谢谢了,谢谢你给了我这么好的时光,在你身上我学到了很多,祝你幸福!为什么把你删了,是我怕自己控制不值,我还是爱你的啊!还有这首 you'll see 送给你也送给我自己。晚安!最后一次了!应该听不到了吧!\n\n还有,应该说对不起的是我,没有能够兑现我的承诺,没有给你想要的爱情,对不起!\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26590183&auto=1&height=66\"></iframe>","html":"<p>疯疯癫癫了一晚,四瓶酒下去,本以为能让自己睡个好觉,但是不到十二点酒醒来,却再也睡不着了,一遍遍循环着张悬翻唱的 you'll see。想写的很多,还是留在心底吧,花几个不眠之夜慢慢地消化。</p>\n\n<p>谢谢你,我的宝贝,这是最后一次这么叫了。今天的你还是那么地率真、勇敢,我也还是爱你的,但是却没有理由挽留你了,我知道你已经不那么爱我了,我是那么的无奈,像个小丑说这些无聊地话。</p>\n\n<p>不过还是谢谢了,谢谢你给了我这么好的时光,在你身上我学到了很多,祝你幸福!为什么把你删了,是我怕自己控制不值,我还是爱你的啊!还有这首 you'll see 送给你也送给我自己。晚安!最后一次了!应该听不到了吧!</p>\n\n<p>还有,应该说对不起的是我,没有能够兑现我的承诺,没有给你想要的爱情,对不起!</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=26590183&auto=1&height=66\"></iframe>","image":null,"featured":0,"page":0,"status":"draft","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467047961295,"created_by":1,"updated_at":1467050137342,"updated_by":1,"published_at":1467048907781,"published_by":1},{"id":19,"uuid":"3cdea6e0-1225-42e2-bf91-42bd5bf05cb1","title":"脑洞和感动","slug":"nao-dong-he-gan-dong","markdown":"<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=3138291&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>","html":"<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=3138291&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467110745929,"created_by":1,"updated_at":1467111347667,"updated_by":1,"published_at":1467111153989,"published_by":1},{"id":20,"uuid":"217d0fe2-7ee8-44bc-9e26-e5688017cd83","title":"无题","slug":"wu-ti","markdown":"养不了你了,祝你幸福,找到那个‘尹天仇’!\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27876900&auto=1&height=66\"></iframe>","html":"<p>养不了你了,祝你幸福,找到那个‘尹天仇’!</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27876900&auto=1&height=66\"></iframe>","image":null,"featured":0,"page":0,"status":"draft","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467111739636,"created_by":1,"updated_at":1467120521732,"updated_by":1,"published_at":1467111989657,"published_by":1},{"id":21,"uuid":"e2fa0f4a-0bcf-49de-9520-ee74d98f21f5","title":"关于色彩搭配(基础知识篇)","slug":"guan-yu-se-cai-da-pei","markdown":"App写得差不多了,感觉其中的色彩的运用比较乱,没有统一起来,于是花了一个下午,学习了一下色彩搭配相关的一些知识,准备统一一下App的配色。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=29431066&auto=1&height=66\"></iframe>\n\n###### 一个色环\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f7768iqi36j305k05kweo.jpg)\n伊顿12色环是由近代著名的瑞士色彩学大叔😳约翰内斯•伊顿先生设计的,如上图。他把红、黄蓝作为三原色,而有两种原色不同比例混合混合所得到的为二次色,也叫间色。而由两个间色或是三个三原色混合而得到的颜色为三次色,或者称为复色,包括了除了原色和间色以外的所有颜色。\n\n###### 两种混色\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f7768wt9q6j30dw05fq3b.jpg)\n色光三原色(加法混色)和色料三原色(减法混色),这两个让我不明觉历啊🤔。水好深啊!\n\n加法混合是指色光的混合,两种以上的光混合在一起,光亮度会提高,混合色的光的总亮度等于相混各色光亮度之和。色光混合中,三原色是朱红、翠绿、蓝紫。这三色光是不能用其它别的色光相混而产生的。\n\n白色光线透过有色滤光片之后,一部分光线被反射而吸收其余的光线,减少掉一部分辐射功率,最后透过的光是两次减光的结果,这样的色彩混合称为减法混合。一般说来,透明性强的染料,混合后具有明显的减光作用???减法混合的三原色是加法混合的三原色的补色,即:翠绿的补色红(品红)、蓝紫的补色黄(淡黄)、朱红的补色蓝(天蓝)。\n\n###### 三种要素\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f77699dmzhj30go0a5dgk.jpg)\n色相😍😍,即各类色彩的相貌称谓,如大红、普蓝、柠檬黄等。色相是色彩的首要特征,是区别各种不同色彩的最准确的标准。事实上任何黑白灰以外的颜色都有色相的属性,而色相也就是由原色、间色和复色来构成的。\n\n饱和度(纯度),饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。\n\n明度可以简单理解为颜色的亮度,不同的颜色具有不同的明度,例如黄色就比蓝色的明度高。任何色彩都存在明暗变化。其中黄色明度最高,紫色明度最低,绿、红、蓝、橙的明度相近,为中间明度。另外在同一色相的明度中还存在深浅的变化。如绿色中由浅到深有粉绿、淡绿、翠绿等明度变化。 \n\n###### 六种关系\n![](https://ws3.sinaimg.cn/large/006bH5BKgw1f7769n991mj30go0apta8.jpg)\n接下来这个就比较重要啦,也就是各种颜色的关系,从图上我们可以看到,在伊顿12色环上,间隔180度或者两两相视的就是补色,间隔120度的是对比色,间隔90度的是中度色,间隔60度是类似色,间隔30度是相近色,0度就是同色啦,同色好像木有意义吧。\n\n未完待续。。。\n\n\n\n","html":"<p>App写得差不多了,感觉其中的色彩的运用比较乱,没有统一起来,于是花了一个下午,学习了一下色彩搭配相关的一些知识,准备统一一下App的配色。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=29431066&auto=1&height=66\"></iframe>\n\n<h6 id=\"\">一个色环</h6>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f7768iqi36j305k05kweo.jpg\" alt=\"\" />\n伊顿12色环是由近代著名的瑞士色彩学大叔😳约翰内斯•伊顿先生设计的,如上图。他把红、黄蓝作为三原色,而有两种原色不同比例混合混合所得到的为二次色,也叫间色。而由两个间色或是三个三原色混合而得到的颜色为三次色,或者称为复色,包括了除了原色和间色以外的所有颜色。</p>\n\n<h6 id=\"\">两种混色</h6>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f7768wt9q6j30dw05fq3b.jpg\" alt=\"\" />\n色光三原色(加法混色)和色料三原色(减法混色),这两个让我不明觉历啊🤔。水好深啊!</p>\n\n<p>加法混合是指色光的混合,两种以上的光混合在一起,光亮度会提高,混合色的光的总亮度等于相混各色光亮度之和。色光混合中,三原色是朱红、翠绿、蓝紫。这三色光是不能用其它别的色光相混而产生的。</p>\n\n<p>白色光线透过有色滤光片之后,一部分光线被反射而吸收其余的光线,减少掉一部分辐射功率,最后透过的光是两次减光的结果,这样的色彩混合称为减法混合。一般说来,透明性强的染料,混合后具有明显的减光作用???减法混合的三原色是加法混合的三原色的补色,即:翠绿的补色红(品红)、蓝紫的补色黄(淡黄)、朱红的补色蓝(天蓝)。</p>\n\n<h6 id=\"\">三种要素</h6>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f77699dmzhj30go0a5dgk.jpg\" alt=\"\" />\n色相😍😍,即各类色彩的相貌称谓,如大红、普蓝、柠檬黄等。色相是色彩的首要特征,是区别各种不同色彩的最准确的标准。事实上任何黑白灰以外的颜色都有色相的属性,而色相也就是由原色、间色和复色来构成的。</p>\n\n<p>饱和度(纯度),饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。</p>\n\n<p>明度可以简单理解为颜色的亮度,不同的颜色具有不同的明度,例如黄色就比蓝色的明度高。任何色彩都存在明暗变化。其中黄色明度最高,紫色明度最低,绿、红、蓝、橙的明度相近,为中间明度。另外在同一色相的明度中还存在深浅的变化。如绿色中由浅到深有粉绿、淡绿、翠绿等明度变化。 </p>\n\n<h6 id=\"\">六种关系</h6>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/006bH5BKgw1f7769n991mj30go0apta8.jpg\" alt=\"\" />\n接下来这个就比较重要啦,也就是各种颜色的关系,从图上我们可以看到,在伊顿12色环上,间隔180度或者两两相视的就是补色,间隔120度的是对比色,间隔90度的是中度色,间隔60度是类似色,间隔30度是相近色,0度就是同色啦,同色好像木有意义吧。</p>\n\n<p>未完待续。。。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467268894725,"created_by":1,"updated_at":1472195926784,"updated_by":1,"published_at":1467269256812,"published_by":1},{"id":22,"uuid":"ed8fba41-eab5-471c-b545-371e350cd4a9","title":"关于色彩搭配(实践篇)","slug":"guan-yu-se-cai-da-pei-shi-jian-pian","markdown":"先来简单介绍一下我要重构的 app 的色彩,主要包括以下几个方面,首先是主色调,例如滴滴的主色调是橙色;其次是背景色、字体颜色、图标颜色、边框颜色。相较于原来,本次需要做一个统一。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=247169&auto=1&height=66\"></iframe>\n\n主色调,主色调负责给整个应用一个整体的印象,而这个印象与每种色调所要表达的意思相关,各种颜色所能够表达的意思如下:\n\n- 红色:激情、爱情、愤怒。\n- 橙色:活力、快乐、生气。\n- 黄色:快乐、希望、虚伪。\n- 绿色:新生、富饶、自然\n- 蓝色:冷静、责任、忧伤。\n- 紫色:创造力、高贵、财富。\n- 黑色:神秘、优雅、邪恶。\n- 灰色:忧郁、保守、严谨。\n- 白色:纯洁、干净、贞洁。\n- 褐色:自然、健康、可靠。\n- 米黄色或茶色:保守、虔诚、无趣。\n- 奶油色或乳白色:平静、优雅、纯粹。 \n\n其次是背景色,背景色主要以白和灰色为主,虽然在传统的单色配色方案等中也有不同的选择,但是在 app 中,白色和灰色还更多的选择,在不同场景下也可能有别的选择,需要更多的实践。\n\n字体色,我选择了三种字体色,第一种是较深的,接近黑色,但又不那么浓黑;第二种是灰色;第三种是白色,白色字体主要使用在背景不是灰色或白色的场景,这样字能够比较突出;另外如果要增强色彩感,可以对于一些触发动作的字,使用主色调的颜色。\n\n图标颜色,暂时有三类情况,第一是两个寓意相反的图标情况,对比色能够比较好地表达;对于如尖头等图标,在一个没有色彩要求等场景中时,可以采用与字的颜色相近的色彩;第三种场景就是主色调的图标。\n\n边框:默认边框以灰色为主,而激活的边框则以主色调一致。\n\n以上是个人在简要学习了一些色彩搭配的知识后,对 app 的整体色彩进行了重构的内容。但是对于正规的项目,还是需要设计人员给出设计稿的,这样就省了开发很多的时间呢!不过作为一个有理想的开发,学习一下色彩搭配也是很有用的,比如美化一下自己的博客!\n\n未完待续。。。","html":"<p>先来简单介绍一下我要重构的 app 的色彩,主要包括以下几个方面,首先是主色调,例如滴滴的主色调是橙色;其次是背景色、字体颜色、图标颜色、边框颜色。相较于原来,本次需要做一个统一。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=247169&auto=1&height=66\"></iframe>\n\n<p>主色调,主色调负责给整个应用一个整体的印象,而这个印象与每种色调所要表达的意思相关,各种颜色所能够表达的意思如下:</p>\n\n<ul>\n<li>红色:激情、爱情、愤怒。</li>\n<li>橙色:活力、快乐、生气。</li>\n<li>黄色:快乐、希望、虚伪。</li>\n<li>绿色:新生、富饶、自然</li>\n<li>蓝色:冷静、责任、忧伤。</li>\n<li>紫色:创造力、高贵、财富。</li>\n<li>黑色:神秘、优雅、邪恶。</li>\n<li>灰色:忧郁、保守、严谨。</li>\n<li>白色:纯洁、干净、贞洁。</li>\n<li>褐色:自然、健康、可靠。</li>\n<li>米黄色或茶色:保守、虔诚、无趣。</li>\n<li>奶油色或乳白色:平静、优雅、纯粹。 </li>\n</ul>\n\n<p>其次是背景色,背景色主要以白和灰色为主,虽然在传统的单色配色方案等中也有不同的选择,但是在 app 中,白色和灰色还更多的选择,在不同场景下也可能有别的选择,需要更多的实践。</p>\n\n<p>字体色,我选择了三种字体色,第一种是较深的,接近黑色,但又不那么浓黑;第二种是灰色;第三种是白色,白色字体主要使用在背景不是灰色或白色的场景,这样字能够比较突出;另外如果要增强色彩感,可以对于一些触发动作的字,使用主色调的颜色。</p>\n\n<p>图标颜色,暂时有三类情况,第一是两个寓意相反的图标情况,对比色能够比较好地表达;对于如尖头等图标,在一个没有色彩要求等场景中时,可以采用与字的颜色相近的色彩;第三种场景就是主色调的图标。</p>\n\n<p>边框:默认边框以灰色为主,而激活的边框则以主色调一致。</p>\n\n<p>以上是个人在简要学习了一些色彩搭配的知识后,对 app 的整体色彩进行了重构的内容。但是对于正规的项目,还是需要设计人员给出设计稿的,这样就省了开发很多的时间呢!不过作为一个有理想的开发,学习一下色彩搭配也是很有用的,比如美化一下自己的博客!</p>\n\n<p>未完待续。。。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467618067380,"created_by":1,"updated_at":1467619563579,"updated_by":1,"published_at":1467619563580,"published_by":1},{"id":23,"uuid":"f5668ae7-96d1-48ff-9a50-2a811539d22e","title":"react native 布局","slug":"react-native-bu-ju","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27514453&auto=1&height=66\"></iframe>\n\n###### 尺寸单位\n\nreact native 使用的单位是 dp 。 dp 是设备独立像素,与 px 不同,他在像素密度不同的设备上能够拥有一致的表现。\n\n对于图片来说,我们获取图片时需要利用像素密度进行一下转换,如下:\n\n```\n var image = getImage({\n width: 200 * PixelRatio.get(),\n height: 100 * PixelRatio.get()\n });\n <Image source={image} style={{width: 200, height: 100}} />\n```\n\n这样基本就不用做太多屏幕适配的工作了。\n\n###### flex的布局\n\nreact native flex 布局默认的方向为竖直方向,使用 justifyContent 指定主轴方向的对其方式,alignItems 指定侧轴的对齐方式,因此垂直居中通过设置这两个参数就 ok 了。根节点下的view 宽度默认为 100% ,可设置宽度。\n\n###### 网格布局\n\n等分的网格\n\n```\n<View style={styles.flexContainer}>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell1\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell2\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell3\n </Text>\n </View>\n </View>\n\n styles = {\n flexContainer: {\n // 容器需要添加direction才能变成让子元素flex\n flexDirection: 'row'\n },\n cell: {\n flex: 1,\n height: 50,\n backgroundColor: '#aaaaaa'\n },\n welcome: {\n fontSize: 20,\n textAlign: 'center',\n margin: 10\n },\n }\n```\n\n左边固定, 右边固定,中间flex的布局\n\n```\n <View style={styles.flexContainer}>\n <View style={styles.cellfixed}>\n <Text style={styles.welcome}>\n fixed\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n flex\n </Text>\n </View>\n <View style={styles.cellfixed}>\n <Text style={styles.welcome}>\n fixed\n </Text>\n </View>\n </View>\n\n styles = {\n flexContainer: {\n // 容器需要添加direction才能变成让子元素flex\n flexDirection: 'row'\n },\n cell: {\n flex: 1,\n height: 50,\n backgroundColor: '#aaaaaa'\n },\n welcome: {\n fontSize: 20,\n textAlign: 'center',\n margin: 10\n },\n cellfixed: {\n height: 50,\n width: 80,\n backgroundColor: '#fefefe'\n } \n }\n```\n\n###### 绝对定位和相对定位\n\n和css的标准不同的是, 元素容器不用` position:'absolute|relative' ` 。另外测试下,不支持垂直居中的写法,只支持水平居中的写法。\n\n###### padding和margin\n\n在View上设置padding很顺利,没有任何问题, 但是如果在inline元素上设置padding, 发现会出现上面的错误, paddingTop和paddingBottom都被挤成marginBottom了。 按理说,不应该对Text做padding处理, 但是确实有这样的问题存在,所以可以将这个问题mark一下。\n\n我们知道,对于inline元素,设置margin-left和margin-right有效,top和bottom按理是不会生效的, 但是上图的结果可以看到,实际是生效了的。所以现在给我的感觉是Text元素更应该理解为一个不能设置padding的block。\n\n算了不要猜了, 我们看看官方文档怎么说Text,https://facebook.github.io/react-native/docs/text.html\n\n```\n <Text>\n <Text>First part and </Text>\n <Text>second part</Text>\n </Text>\n // Text container: all the text flows as if it was one\n // |First part |\n // |and second |\n // |part |\n\n <View>\n <Text>First part and </Text>\n <Text>second part</Text>\n </View>\n // View container: each text is its own block\n // |First part |\n // |and |\n // |second part|\n```\n\n也就是如果Text元素在Text里边,可以考虑为inline, 如果单独在View里边,那就是Block。 \n下面会专门研究一下文本相关的布局\n\n###### 文本元素\n\n在实践中,我主要定义三种大小的字体,12或13的小号字体,行高20;15-17的中号字体,行高23;24号左右的大号字体,行高27。\n\n字体选择默认的,如果自己引入字体那另当别论。\n\n样式继承最近的父元素的样式。\n\n支持的属性如下:\n\n```\n Attributes.style = {\n color string\n containerBackgroundColor string\n fontFamily string\n fontSize number\n fontStyle enum('normal', 'italic')\n fontWeight enum(\"normal\", 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900')\n lineHeight number\n textAlign enum(\"auto\", 'left', 'right', 'center')\n writingDirection enum(\"auto\", 'ltr', 'rtl')\n }\n```\n\n参考: https://segmentfault.com/a/1190000002658374","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=27514453&auto=1&height=66\"></iframe>\n\n<h6 id=\"\">尺寸单位</h6>\n\n<p>react native 使用的单位是 dp 。 dp 是设备独立像素,与 px 不同,他在像素密度不同的设备上能够拥有一致的表现。</p>\n\n<p>对于图片来说,我们获取图片时需要利用像素密度进行一下转换,如下:</p>\n\n<pre><code> var image = getImage({\n width: 200 * PixelRatio.get(),\n height: 100 * PixelRatio.get()\n });\n <Image source={image} style={{width: 200, height: 100}} />\n</code></pre>\n\n<p>这样基本就不用做太多屏幕适配的工作了。</p>\n\n<h6 id=\"flex\">flex的布局</h6>\n\n<p>react native flex 布局默认的方向为竖直方向,使用 justifyContent 指定主轴方向的对其方式,alignItems 指定侧轴的对齐方式,因此垂直居中通过设置这两个参数就 ok 了。根节点下的view 宽度默认为 100% ,可设置宽度。</p>\n\n<h6 id=\"\">网格布局</h6>\n\n<p>等分的网格</p>\n\n<pre><code><View style={styles.flexContainer}> \n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell1\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell2\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n cell3\n </Text>\n </View>\n </View>\n\n styles = {\n flexContainer: {\n // 容器需要添加direction才能变成让子元素flex\n flexDirection: 'row'\n },\n cell: {\n flex: 1,\n height: 50,\n backgroundColor: '#aaaaaa'\n },\n welcome: {\n fontSize: 20,\n textAlign: 'center',\n margin: 10\n },\n }\n</code></pre>\n\n<p>左边固定, 右边固定,中间flex的布局</p>\n\n<pre><code> <View style={styles.flexContainer}>\n <View style={styles.cellfixed}>\n <Text style={styles.welcome}>\n fixed\n </Text>\n </View>\n <View style={styles.cell}>\n <Text style={styles.welcome}>\n flex\n </Text>\n </View>\n <View style={styles.cellfixed}>\n <Text style={styles.welcome}>\n fixed\n </Text>\n </View>\n </View>\n\n styles = {\n flexContainer: {\n // 容器需要添加direction才能变成让子元素flex\n flexDirection: 'row'\n },\n cell: {\n flex: 1,\n height: 50,\n backgroundColor: '#aaaaaa'\n },\n welcome: {\n fontSize: 20,\n textAlign: 'center',\n margin: 10\n },\n cellfixed: {\n height: 50,\n width: 80,\n backgroundColor: '#fefefe'\n } \n }\n</code></pre>\n\n<h6 id=\"\">绝对定位和相对定位</h6>\n\n<p>和css的标准不同的是, 元素容器不用<code>position:'absolute|relative'</code> 。另外测试下,不支持垂直居中的写法,只支持水平居中的写法。</p>\n\n<h6 id=\"paddingmargin\">padding和margin</h6>\n\n<p>在View上设置padding很顺利,没有任何问题, 但是如果在inline元素上设置padding, 发现会出现上面的错误, paddingTop和paddingBottom都被挤成marginBottom了。 按理说,不应该对Text做padding处理, 但是确实有这样的问题存在,所以可以将这个问题mark一下。</p>\n\n<p>我们知道,对于inline元素,设置margin-left和margin-right有效,top和bottom按理是不会生效的, 但是上图的结果可以看到,实际是生效了的。所以现在给我的感觉是Text元素更应该理解为一个不能设置padding的block。</p>\n\n<p>算了不要猜了, 我们看看官方文档怎么说Text,<a href=\"https://facebook.github.io/react-native/docs/text.html\">https://facebook.github.io/react-native/docs/text.html</a></p>\n\n<pre><code> <Text>\n <Text>First part and </Text>\n <Text>second part</Text>\n </Text>\n // Text container: all the text flows as if it was one\n // |First part |\n // |and second |\n // |part |\n\n <View>\n <Text>First part and </Text>\n <Text>second part</Text>\n </View>\n // View container: each text is its own block\n // |First part |\n // |and |\n // |second part|\n</code></pre>\n\n<p>也就是如果Text元素在Text里边,可以考虑为inline, 如果单独在View里边,那就是Block。 \n下面会专门研究一下文本相关的布局</p>\n\n<h6 id=\"\">文本元素</h6>\n\n<p>在实践中,我主要定义三种大小的字体,12或13的小号字体,行高20;15-17的中号字体,行高23;24号左右的大号字体,行高27。</p>\n\n<p>字体选择默认的,如果自己引入字体那另当别论。</p>\n\n<p>样式继承最近的父元素的样式。</p>\n\n<p>支持的属性如下:</p>\n\n<pre><code> Attributes.style = {\n color string\n containerBackgroundColor string\n fontFamily string\n fontSize number\n fontStyle enum('normal', 'italic')\n fontWeight enum(\"normal\", 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900')\n lineHeight number\n textAlign enum(\"auto\", 'left', 'right', 'center')\n writingDirection enum(\"auto\", 'ltr', 'rtl')\n }\n</code></pre>\n\n<p>参考: <a href=\"https://segmentfault.com/a/1190000002658374\">https://segmentfault.com/a/1190000002658374</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467619667519,"created_by":1,"updated_at":1467621941112,"updated_by":1,"published_at":1467621393993,"published_by":1},{"id":24,"uuid":"e92a7d4f-bd2a-436e-ae77-9db3d4ccac97","title":"react Stateless Functions(官方文档翻译)","slug":"react-stateless-functions-guan-fang-wen-dang-fan-yi","markdown":"> 中文社区还停留在 0.14.7,文档上有不少落后了,比如 shadowEqual、context 等的知识,所以只能自己来了。\n\n你也可以用一个普通的 JavaScript 函数定义你的 React classes 。例如使用 stateless function 语法:\n\n```\nfunction HelloMessage(props) {\n return <div>Hello {props.name}</div>;\n}\nReactDOM.render(<HelloMessage name=\"Sebastian\" />, mountNode);\n```\n\n或者使用 ES6 的 箭头函数语法:\n\n```\nconst HelloMessage = (props) => <div>Hello {props.name}</div>;\nReactDOM.render(<HelloMessage name=\"Sebastian\" />, mountNode);\n```\n\n这个简化的组件 API 设计为纯函数几它们的参数。这些组件必须不能保留内部的状态,没有支持的实例并且没有生命周期方法。 它们是转换起输入的纯函数,没有副作用。然而,你仍然可以具体说明 ` .propTypes ` 和 ` .defaultProps ` 通过设置它们喂函数的参数,就像在 ES6 class上一样设置。\n\n> 注意:\n因为 stateless functions 没有一个支持的实例,你不能使用 ref 来得到一个stateless function 组件。通常这不是一个 issue,因为 stateless functions 不提供一个必须服从的 AP I。没有一个必须服从的 API,也就没有必须要对实例要求做的事。然而,如果一个用户想要找到 stateless function 组件的 DOM 节点 ,它们必须包裹在一个有状态的组件内,然后用 ref 去访问有状态的包裹组件。\n注意:\n在 React v0.14, stateless functional 组件将不被允许去返回 ` null ` 或 ` false `(变通的方法是返回 ` <noscript /> ` 代替)。这个问题在 React v15 当中被修复了, stateless functional 组件现在被允许返回 ` null ` 。\n\n在理想的情况下,你的大部分组件都将是 stateless functions,因为在将来我们也能够给这些组件带来性能优化的规范,通过避免不需要的检查和内存分配。如果可能的话,这是理想的模式。","html":"<blockquote>\n <p>中文社区还停留在 0.14.7,文档上有不少落后了,比如 shadowEqual、context 等的知识,所以只能自己来了。</p>\n</blockquote>\n\n<p>你也可以用一个普通的 JavaScript 函数定义你的 React classes 。例如使用 stateless function 语法:</p>\n\n<pre><code>function HelloMessage(props) { \n return <div>Hello {props.name}</div>;\n}\nReactDOM.render(<HelloMessage name=\"Sebastian\" />, mountNode); \n</code></pre>\n\n<p>或者使用 ES6 的 箭头函数语法:</p>\n\n<pre><code>const HelloMessage = (props) => <div>Hello {props.name}</div>; \nReactDOM.render(<HelloMessage name=\"Sebastian\" />, mountNode); \n</code></pre>\n\n<p>这个简化的组件 API 设计为纯函数几它们的参数。这些组件必须不能保留内部的状态,没有支持的实例并且没有生命周期方法。 它们是转换起输入的纯函数,没有副作用。然而,你仍然可以具体说明 <code>.propTypes</code> 和 <code>.defaultProps</code> 通过设置它们喂函数的参数,就像在 ES6 class上一样设置。</p>\n\n<blockquote>\n <p>注意:\n 因为 stateless functions 没有一个支持的实例,你不能使用 ref 来得到一个stateless function 组件。通常这不是一个 issue,因为 stateless functions 不提供一个必须服从的 AP I。没有一个必须服从的 API,也就没有必须要对实例要求做的事。然而,如果一个用户想要找到 stateless function 组件的 DOM 节点 ,它们必须包裹在一个有状态的组件内,然后用 ref 去访问有状态的包裹组件。\n 注意:\n 在 React v0.14, stateless functional 组件将不被允许去返回 <code>null</code> 或 <code>false</code>(变通的方法是返回 <code><noscript /></code> 代替)。这个问题在 React v15 当中被修复了, stateless functional 组件现在被允许返回 <code>null</code> 。</p>\n</blockquote>\n\n<p>在理想的情况下,你的大部分组件都将是 stateless functions,因为在将来我们也能够给这些组件带来性能优化的规范,通过避免不需要的检查和内存分配。如果可能的话,这是理想的模式。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467695307621,"created_by":1,"updated_at":1467697494691,"updated_by":1,"published_at":1467696282266,"published_by":1},{"id":25,"uuid":"9b062008-ea10-4824-b493-1b795e1eae9b","title":"小葵唱的《ソラニン》","slug":"xiao-kui-chang-de-soranin","markdown":"小葵的声音好好听啊,单曲循环,元气满满!😊就是木有找到能下载的mp3。\n\n<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=180951&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>\n\n思い违いは空のかなた\nさよならだけの人生か\nほんの少しの未来は见えたのに\nさよならなんだ\n\n昔住んでた小さな部屋は今は谁かが住んでんだ\n君に言われたひどい言叶も\n无駄な気がした毎日も\n\nあの日こうしていれば\nあの日に戻れれば\nあの顷の仆にはもう戻れないよ\n\nたとえばゆるい幸せがだらっと続いたとする\nきっと悪い种が芽を出して\nもうさよならんだ\n\n寒い冬の冷えた缶コーヒー 虹色の长いマフラー\n小走りで路地裏を抜けて思い出してみる\n\nたとえばゆるい幸せがだらっと続いたとする\nきっと悪い种が芽を出して\nもうさよならなんだ\n\nさよなら それもいいさ\nどこかで元気でやれよ\nさよなら 仆もどーにかやるさ\nさよなら そうするよ\n\n---\n\n误会在天空的彼端\n难道人生只充满了再见吗\n只差那麼一点就可以看见未来\n却只能说再见\n\n以前住过的小房间现在又是谁住在那里\n无论是你对我说过的过分话语\n还是感觉白费力气的每一天\n\n若说那天我这麼做\n若说能回到那一天\n我已无法再回到那一天\n\n如果说弛缓的幸福缓慢地延续\n这样下去坏种子一定会发芽\n还是只能说再见\n\n寒冷冬天中冷掉的罐装咖啡 七彩的长围巾\n小跑步穿过小巷中 我试著回忆起\n\n如果说弛缓的幸福缓慢地延续\n这样下去坏种子一定会发芽\n还是只能说再见\n\n说再见 这样也好\n无论在哪都要好好地过\n再见了 我也一定有办法的\n说再见 就这样吧\n\n---\n\no mo i chi ga i wa so ra no ka na ta\nsa yo na ra da ke no jin sei ka\nho n no su ko shi no mi rai wa mi e ta no ni\nsa yo na ra na n da\n\nmu ka shi su n de ta chii sa na he ya wa i ma wa da re ka ga su n de n da\nki mi ni i wa re ta hi do i ko ta ba mo\nmu da na ki ga shi ta mai ni chi mo\n\na no hi ko u shi te i re ba\na no hi ni mo do re re ba\na no ko ro no bo ku ni wa mo u mo do re nai yo\n\nta to e ba yu ru i shi a wa se ga da ra tto tsu du i ta to su ru\nki tto wa ru i ta ne ga me o da shi te\nmo u sa yo na ra na n da\n\nsa mu i fu yu no hi e ta kan ko- hi- ni ji i ro no na ga i ma fu ra-\nko ba shi ri de ro ji u ra o nu ke te o mo i da shi te mi ru\n\nta to e ba yu ru i shi a wa se ga da ra tto tsu du i ta to su ru\nki tto wa ru i ta ne ga me o da shi te\nmo u sa yo na ra na n da\n\nsa ya na ra so re mo i i sa\ndo ko ka de gen ki de ya re yo\nsa yo na ra bo ku mo do- ni ka ya ru sa\nsa yo na ra so u su ru yo","html":"<p>小葵的声音好好听啊,单曲循环,元气满满!😊就是木有找到能下载的mp3。</p>\n\n<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=180951&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>\n\n<p>思い违いは空のかなた\nさよならだけの人生か\nほんの少しの未来は见えたのに\nさよならなんだ</p>\n\n<p>昔住んでた小さな部屋は今は谁かが住んでんだ\n君に言われたひどい言叶も\n无駄な気がした毎日も</p>\n\n<p>あの日こうしていれば\nあの日に戻れれば\nあの顷の仆にはもう戻れないよ</p>\n\n<p>たとえばゆるい幸せがだらっと続いたとする\nきっと悪い种が芽を出して\nもうさよならんだ</p>\n\n<p>寒い冬の冷えた缶コーヒー 虹色の长いマフラー\n小走りで路地裏を抜けて思い出してみる</p>\n\n<p>たとえばゆるい幸せがだらっと続いたとする\nきっと悪い种が芽を出して\nもうさよならなんだ</p>\n\n<p>さよなら それもいいさ\nどこかで元気でやれよ\nさよなら 仆もどーにかやるさ\nさよなら そうするよ</p>\n\n<p>---</p>\n\n<p>误会在天空的彼端\n难道人生只充满了再见吗\n只差那麼一点就可以看见未来\n却只能说再见</p>\n\n<p>以前住过的小房间现在又是谁住在那里\n无论是你对我说过的过分话语\n还是感觉白费力气的每一天</p>\n\n<p>若说那天我这麼做\n若说能回到那一天\n我已无法再回到那一天</p>\n\n<p>如果说弛缓的幸福缓慢地延续\n这样下去坏种子一定会发芽\n还是只能说再见</p>\n\n<p>寒冷冬天中冷掉的罐装咖啡 七彩的长围巾\n小跑步穿过小巷中 我试著回忆起</p>\n\n<p>如果说弛缓的幸福缓慢地延续\n这样下去坏种子一定会发芽\n还是只能说再见</p>\n\n<p>说再见 这样也好\n无论在哪都要好好地过\n再见了 我也一定有办法的\n说再见 就这样吧</p>\n\n<p>---</p>\n\n<p>o mo i chi ga i wa so ra no ka na ta <br />\nsa yo na ra da ke no jin sei ka <br />\nho n no su ko shi no mi rai wa mi e ta no ni <br />\nsa yo na ra na n da</p>\n\n<p>mu ka shi su n de ta chii sa na he ya wa i ma wa da re ka ga su n de n da <br />\nki mi ni i wa re ta hi do i ko ta ba mo <br />\nmu da na ki ga shi ta mai ni chi mo</p>\n\n<p>a no hi ko u shi te i re ba <br />\na no hi ni mo do re re ba <br />\na no ko ro no bo ku ni wa mo u mo do re nai yo</p>\n\n<p>ta to e ba yu ru i shi a wa se ga da ra tto tsu du i ta to su ru <br />\nki tto wa ru i ta ne ga me o da shi te <br />\nmo u sa yo na ra na n da</p>\n\n<p>sa mu i fu yu no hi e ta kan ko- hi- ni ji i ro no na ga i ma fu ra- <br />\nko ba shi ri de ro ji u ra o nu ke te o mo i da shi te mi ru</p>\n\n<p>ta to e ba yu ru i shi a wa se ga da ra tto tsu du i ta to su ru <br />\nki tto wa ru i ta ne ga me o da shi te <br />\nmo u sa yo na ra na n da</p>\n\n<p>sa ya na ra so re mo i i sa <br />\ndo ko ka de gen ki de ya re yo <br />\nsa yo na ra bo ku mo do- ni ka ya ru sa <br />\nsa yo na ra so u su ru yo</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1467696313896,"created_by":1,"updated_at":1467762751870,"updated_by":1,"published_at":1467696583407,"published_by":1},{"id":26,"uuid":"6c84f077-565f-4041-b904-5f22ce2b3bf4","title":"Jersey 异常处理","slug":"jerseyyi-chang-chu-li","markdown":"今天的两篇文章都是关于异常处理的,一篇是服务端 Jersey 的异常处理,另一篇是 Fetch API Promise 的异常处理。废话不多说,直接开始。\n\n根据 Jersey 官方文档的说法,Jersey 异常处理有两种方式:\n\n第一是继承 WebApplicationException,不继承默认的状态码为 500,继承后可以修改成对应的状态码,下面是官网的一个例子:\n\n```\npublic class CustomNotFoundException extends WebApplicationException {\n \n /**\n * Create a HTTP 404 (Not Found) exception.\n */\n public CustomNotFoundException() {\n super(Responses.notFound().build());\n }\n \n /**\n * Create a HTTP 404 (Not Found) exception.\n * @param message the String that is the entity of the 404 response.\n */\n public CustomNotFoundException(String message) {\n super(Response.status(Responses.NOT_FOUND).\n entity(message).type(\"text/plain\").build());\n }\n}\n```\n\n第二是针对一些现有的异常,官网建议使用实现 javax.ws.rs.ext.ExceptionMapper<T> 的方式,T为所要抛出的异常类型。当 Jersey 捕获到T异常,就返回实现类的响应。\n\n当抛出 RuntimeException 异常,就会返回404异常。需要注意的是:实现类要加 @Provider 注解,而且要放在 Jersey 资源所在的包路径,以便jersey扫描到。\n\n下面是官网的例子:\n\n```\n@Provider\npublic class EntityNotFoundMapper implements ExceptionMapper<javax.persistence.EntityNotFoundException> {\n public Response toResponse(javax.persistence.EntityNotFoundException ex) {\n return Response.status(404).\n entity(ex.getMessage()).\n type(\"text/plain\").\n build();\n }\n}\n```\n\n了解了这些以后,需要针对接口调用的异常进行一个较为统一的处理,使得客户端能够等到统一格式的错误信息,而不会出现 JSON Parse 的错误。目前我的做法是加一个判断,针对 MediaType 为 application/json 的异常(包括 WebApplicationException 抛出的异常)进行 Response 的统一包装,来达到给客户端统一返回结果的目的。如果说要更加规范地解决这个问题,那应当是创建各个情况的异常类继承不同场景的基类,然后用 ExceptionMapper 统一处理该基类的异常,同时也能够区分 rest 接口的异常和网页端抛出的异常。\n\n关于异常更复杂,偏原理的部分请看参考资料,不在本文讨论范围,因为那可以写很长一篇文章了,我这里只关注这个项目中 Jersey 端的实现。\n\n参考资料:\n\n1、 [文档传送门](https://jersey.java.net/documentation/latest/representations.html#d0e6754)\n2、[Jersey Rest 异常统一处理机制](http://blog.csdn.net/niityzu/article/details/51112878)\n3、[Jersey框架的统一异常处理机制](http://redhacker.iteye.com/blog/1924071)\n4、[Checked 和 UnChecked 异常 的使用场合](http://www.tuicool.com/articles/ramuyu)","html":"<p>今天的两篇文章都是关于异常处理的,一篇是服务端 Jersey 的异常处理,另一篇是 Fetch API Promise 的异常处理。废话不多说,直接开始。</p>\n\n<p>根据 Jersey 官方文档的说法,Jersey 异常处理有两种方式:</p>\n\n<p>第一是继承 WebApplicationException,不继承默认的状态码为 500,继承后可以修改成对应的状态码,下面是官网的一个例子:</p>\n\n<pre><code>public class CustomNotFoundException extends WebApplicationException {\n\n /**\n * Create a HTTP 404 (Not Found) exception.\n */\n public CustomNotFoundException() {\n super(Responses.notFound().build());\n }\n\n /**\n * Create a HTTP 404 (Not Found) exception.\n * @param message the String that is the entity of the 404 response.\n */\n public CustomNotFoundException(String message) {\n super(Response.status(Responses.NOT_FOUND).\n entity(message).type(\"text/plain\").build());\n }\n}\n</code></pre>\n\n<p>第二是针对一些现有的异常,官网建议使用实现 javax.ws.rs.ext.ExceptionMapper<T> 的方式,T为所要抛出的异常类型。当 Jersey 捕获到T异常,就返回实现类的响应。</p>\n\n<p>当抛出 RuntimeException 异常,就会返回404异常。需要注意的是:实现类要加 @Provider 注解,而且要放在 Jersey 资源所在的包路径,以便jersey扫描到。</p>\n\n<p>下面是官网的例子:</p>\n\n<pre><code>@Provider\npublic class EntityNotFoundMapper implements ExceptionMapper<javax.persistence.EntityNotFoundException> { \n public Response toResponse(javax.persistence.EntityNotFoundException ex) {\n return Response.status(404).\n entity(ex.getMessage()).\n type(\"text/plain\").\n build();\n }\n}\n</code></pre>\n\n<p>了解了这些以后,需要针对接口调用的异常进行一个较为统一的处理,使得客户端能够等到统一格式的错误信息,而不会出现 JSON Parse 的错误。目前我的做法是加一个判断,针对 MediaType 为 application/json 的异常(包括 WebApplicationException 抛出的异常)进行 Response 的统一包装,来达到给客户端统一返回结果的目的。如果说要更加规范地解决这个问题,那应当是创建各个情况的异常类继承不同场景的基类,然后用 ExceptionMapper 统一处理该基类的异常,同时也能够区分 rest 接口的异常和网页端抛出的异常。</p>\n\n<p>关于异常更复杂,偏原理的部分请看参考资料,不在本文讨论范围,因为那可以写很长一篇文章了,我这里只关注这个项目中 Jersey 端的实现。</p>\n\n<p>参考资料:</p>\n\n<p>1、 <a href=\"https://jersey.java.net/documentation/latest/representations.html#d0e6754\">文档传送门</a> <br />\n2、<a href=\"http://blog.csdn.net/niityzu/article/details/51112878\">Jersey Rest 异常统一处理机制</a> <br />\n3、<a href=\"http://redhacker.iteye.com/blog/1924071\">Jersey框架的统一异常处理机制</a> <br />\n4、<a href=\"http://www.tuicool.com/articles/ramuyu\">Checked 和 UnChecked 异常 的使用场合</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1468372912162,"created_by":1,"updated_at":1471010424392,"updated_by":1,"published_at":1470999361119,"published_by":1},{"id":27,"uuid":"7107ba84-ee23-401d-90a0-6f72c9919ec9","title":"无题","slug":"dyht","markdown":"最近忙屎了,没时间写东西🙄。马上要开工,随便写点。最近大鱼可以说是在舆论的风口浪尖,虽然没去电影院看,但是通过b站的一个个影评,已是略知一二了。\n\n作为一部花了12年撸出的一个东西,确实在剧情上如大家所说有点不如人意,但是画面、配乐都是在向国外看齐的。正如一个up主说,大鱼给他的感觉绝不是让人失望的那种,只是有点微妙。这种微妙感,来源于12年的大鱼承载了太多,来源于作者不那么会讲故事。不过对于国漫来说,这不能说是划时代,却可以称得上是一个新的开始。大雨让我看到仍有那么一批人在认真地做着动画,这是最让我欣慰的。\n\n下面是昨天在b站看到的一组和我一样大的学生做的动画,瞬间对国漫燃起了信心🤗。有这么多有才的少年在追逐着梦想,希望10年、20年后能够看到每一个短片能够出线在大荧幕之上。干活去了!😭\n\n<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=5278719&page=4\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>","html":"<p>最近忙屎了,没时间写东西🙄。马上要开工,随便写点。最近大鱼可以说是在舆论的风口浪尖,虽然没去电影院看,但是通过b站的一个个影评,已是略知一二了。</p>\n\n<p>作为一部花了12年撸出的一个东西,确实在剧情上如大家所说有点不如人意,但是画面、配乐都是在向国外看齐的。正如一个up主说,大鱼给他的感觉绝不是让人失望的那种,只是有点微妙。这种微妙感,来源于12年的大鱼承载了太多,来源于作者不那么会讲故事。不过对于国漫来说,这不能说是划时代,却可以称得上是一个新的开始。大雨让我看到仍有那么一批人在认真地做着动画,这是最让我欣慰的。</p>\n\n<p>下面是昨天在b站看到的一组和我一样大的学生做的动画,瞬间对国漫燃起了信心🤗。有这么多有才的少年在追逐着梦想,希望10年、20年后能够看到每一个短片能够出线在大荧幕之上。干活去了!😭</p>\n\n<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=5278719&page=4\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1468459601710,"created_by":1,"updated_at":1468460846200,"updated_by":1,"published_at":1468460846201,"published_by":1},{"id":28,"uuid":"8aa4ff0b-b33b-4f2c-8741-d86ac04536c5","title":"转:CET,UTC,GMT,CST几种常见时间概述","slug":"zhuan-cet-utc-gmt-cstji-chong-chang-jian-shi-jian-gai-shu","markdown":"###### CET(欧洲中部时间)\n\n欧洲中部时间(英語:Central European Time,CET)是比世界标准时间(UTC)早一个小时的时区名称之一。它被大部分欧洲国家和部分北非国家采用。\n\n冬季时间为UTC+1,夏季欧洲夏令时为UTC+2。\n\n###### WET(欧洲西部时间)\n\n欧洲西部时间(Western European Time,缩写WET)和世界标准时间(UTC)相同。\n\n###### EET(欧洲东部时间)\n\n欧洲东部时间(Eastern European Time,缩写EET)是比世界标准时间(UTC)早二个小时的时区名称之一。它被部分欧洲国家、北非国家和中东国家采用。\n\n###### UTC(世界标准时间)\n\n协调世界时,又称世界标准时间或世界協調時間,简称UTC(从英文「Coordinated Universal Time」/法文「Temps Universel Cordonné」而来),是最主要的世界時間標準,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平時。\n\n###### GMT(格林尼治平时)\n\n格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。\n\n自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。\n\n理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。\n\n由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)。\n\n###### CST(北京时间)\n\n北京时间,China Standard Time,中国标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。\n\n不过这个CST这个缩写比较纠结的是它可以同时代表四个不同的时间:\n\nCentral Standard Time (USA) UT-6:00\nCentral Standard Time (Australia) UT+9:30\nChina Standard Time UT+8:00\nCuba Standard Time UT-4:00\n因此你平时编写程序过程中也有可能遇到javascript客户端时间和服务端时间不一致的问题,编程的时候还是要多注意。\n\n###### ISO 日期和时间的表示方法\n\n国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是第三版ISO8601:2004以替代第一版ISO8601:1988與第二版ISO8601:2000。\n\n这是仅仅就只是一个日期时间的表示方法,我们用这样的方法表示一个UTC时间。","html":"<h6 id=\"cet\">CET(欧洲中部时间)</h6>\n\n<p>欧洲中部时间(英語:Central European Time,CET)是比世界标准时间(UTC)早一个小时的时区名称之一。它被大部分欧洲国家和部分北非国家采用。</p>\n\n<p>冬季时间为UTC+1,夏季欧洲夏令时为UTC+2。</p>\n\n<h6 id=\"wet\">WET(欧洲西部时间)</h6>\n\n<p>欧洲西部时间(Western European Time,缩写WET)和世界标准时间(UTC)相同。</p>\n\n<h6 id=\"eet\">EET(欧洲东部时间)</h6>\n\n<p>欧洲东部时间(Eastern European Time,缩写EET)是比世界标准时间(UTC)早二个小时的时区名称之一。它被部分欧洲国家、北非国家和中东国家采用。</p>\n\n<h6 id=\"utc\">UTC(世界标准时间)</h6>\n\n<p>协调世界时,又称世界标准时间或世界協調時間,简称UTC(从英文「Coordinated Universal Time」/法文「Temps Universel Cordonné」而来),是最主要的世界時間標準,其以原子时秒长为基础,在时刻上尽量接近于格林尼治平時。</p>\n\n<h6 id=\"gmt\">GMT(格林尼治平时)</h6>\n\n<p>格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。</p>\n\n<p>自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。</p>\n\n<p>理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。</p>\n\n<p>由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)。</p>\n\n<h6 id=\"cst\">CST(北京时间)</h6>\n\n<p>北京时间,China Standard Time,中国标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。</p>\n\n<p>不过这个CST这个缩写比较纠结的是它可以同时代表四个不同的时间:</p>\n\n<p>Central Standard Time (USA) UT-6:00 <br />\nCentral Standard Time (Australia) UT+9:30 <br />\nChina Standard Time UT+8:00 <br />\nCuba Standard Time UT-4:00 <br />\n因此你平时编写程序过程中也有可能遇到javascript客户端时间和服务端时间不一致的问题,编程的时候还是要多注意。</p>\n\n<h6 id=\"iso\">ISO 日期和时间的表示方法</h6>\n\n<p>国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前是第三版ISO8601:2004以替代第一版ISO8601:1988與第二版ISO8601:2000。</p>\n\n<p>这是仅仅就只是一个日期时间的表示方法,我们用这样的方法表示一个UTC时间。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1468468310903,"created_by":1,"updated_at":1468469618333,"updated_by":1,"published_at":1468469618334,"published_by":1},{"id":29,"uuid":"026a8240-4811-4fa0-af76-7301460de880","title":"生存罪","slug":"react-native-xing-neng-zui-xin-guan-fang-wen-dang-fan-yi","markdown":"表白 up 主,剪得太棒了,又多了好多剧追!bgm 雨——清竜人,1P 2P 一起服用效果更佳,剩下的不多说了,慢慢感受吧!\n\n<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=4753273&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>","html":"<p>表白 up 主,剪得太棒了,又多了好多剧追!bgm 雨——清竜人,1P 2P 一起服用效果更佳,剩下的不多说了,慢慢感受吧!</p>\n\n<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=4753273&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1468483412197,"created_by":1,"updated_at":1471064171790,"updated_by":1,"published_at":1471064171791,"published_by":1},{"id":30,"uuid":"e043b29e-50be-470f-9a35-e7c8d04a7bc2","title":"js Date的国际化、格式化的探究","slug":"js","markdown":"这一篇是继续上一篇而写的,大致背景就是我在 react native 中需要实现格式化时间。我首先在 MDN 上找到的是下面几个方法:\n\n```\nDate.prototype.toLocaleDateString()\nDate.prototype.toLocaleFormat()\nDate.prototype.toLocaleString()\nDate.prototype.toLocaleTimeString()\n```\n\n` Date.prototype.toLocaleFormat() ` 方法不是 ES 标准当中,所以首先就舍弃了。专向剩下的三个方法,这三个方法都可以传入两个参数 ` locates ` 和 ` options ` 。下面简单介绍下这两个参数:\n\n` locales ` 是个字符串,指定单个[语言标签](https://www.w3.org/International/articles/language-tags/),或者包含多个语言标签的类数组对象。语言标签如下面的字符串:en(普通英语),de-AT(奥地利德语),zh-Hant-TW(台湾使用的繁体中文)。语言标签可以包含一个“Unicode扩展”,形式为-u-key1-value1-key2-value2..., 其中每个key是“扩展key”。不同的构造函数对此进行具体解释。\n\n` opions ` 是个对象,其属性(如果不存在,就赋值为undefined)决定格式化器(formatter)和整理器(collator)的行为。精确的解释由构造函数决定。\n\n给定区域信息和选项,实现会尝试生成近似理想行为的最接近行为。Firefox 支持用于整理(collation)的400+区域,用于date/time和数字格式化的600+区域,所以很可能(但不保证)你想要的区域是被支持的。\n\n这两个实际上是 js 的国际化 api 里的内容,js 国际化 api 包括了 日期、数字等的格式化,不过不是本文的重点,想了解可以参考[此文](http://www.open-open.com/lib/view/open1418779460683.html#articleHeader8),虽然两年前的但是讲的很细。\n\n于是我开始使用 ` locales ` 和简单的正则来格式化我的时间,如下:\n\n```\n// 用到的地方不多,所以没用写成通用的format\nfunction DateFormat(now) {\n return now.toLocaleDateString('zh-CN').substring(5).replace(/\\//g, '月').concat('日 ', now.toLocaleTimeString().slice(0, -3));\n}\n```\n\n这段代码在 ios 下感觉良好,但是今天测试流程用了我自己的安卓机,世界就崩塌了🙄,时间是乱的。然后试了下,发现无论传入什么 ` locates ` 在安卓下都没有效果。擦擦擦,那肯定是兼容性的问题了,再次来到 MDN 翻到兼容性的地方,我方了:\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f7767bp42kj311c0asdi0.jpg)\n\n然后只能去找别的方式了,最后用的是正则表达式,亲测可用,如下:\n\n```\n// 对Date的扩展,将 Date 转化为指定格式的String \n// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, \n// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) \n// 例子: \n// (new Date()).Format(\"yyyy-MM-dd hh:mm:ss.S\") ==> 2006-07-02 08:09:04.423 \n// (new Date()).Format(\"yyyy-M-d h:m:s.S\") ==> 2006-7-2 8:9:4.18 \nDate.prototype.Format = function(fmt) \n{ //author: meizz \n var o = { \n \"M+\" : this.getMonth()+1, //月份 \n \"d+\" : this.getDate(), //日 \n \"h+\" : this.getHours(), //小时 \n \"m+\" : this.getMinutes(), //分 \n \"s+\" : this.getSeconds(), //秒 \n \"q+\" : Math.floor((this.getMonth()+3)/3), //季度 \n \"S\" : this.getMilliseconds() //毫秒 \n }; \n if(/(y+)/.test(fmt)) \n fmt=fmt.replace(RegExp.$1, (this.getFullYear()+\"\").substr(4 - RegExp.$1.length)); \n for(var k in o) \n if(new RegExp(\"(\"+ k +\")\").test(fmt)) \n fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : ((\"00\"+ o[k]).substr((\"\"+ o[k]).length))); \n return fmt; \n} \n```\n\n虽然最后完成了,但是对于 js 国际化和正则的部分内容,需要做更深入的研究!开饭了!😬\n\n另外如果不想折腾的话,[moment](https://github.com/moment/moment)这个库可能是一个不错的选择。😜","html":"<p>这一篇是继续上一篇而写的,大致背景就是我在 react native 中需要实现格式化时间。我首先在 MDN 上找到的是下面几个方法:</p>\n\n<pre><code>Date.prototype.toLocaleDateString() \nDate.prototype.toLocaleFormat() \nDate.prototype.toLocaleString() \nDate.prototype.toLocaleTimeString() \n</code></pre>\n\n<p><code>Date.prototype.toLocaleFormat()</code> 方法不是 ES 标准当中,所以首先就舍弃了。专向剩下的三个方法,这三个方法都可以传入两个参数 <code>locates</code> 和 <code>options</code> 。下面简单介绍下这两个参数:</p>\n\n<p><code>locales</code> 是个字符串,指定单个<a href=\"https://www.w3.org/International/articles/language-tags/\">语言标签</a>,或者包含多个语言标签的类数组对象。语言标签如下面的字符串:en(普通英语),de-AT(奥地利德语),zh-Hant-TW(台湾使用的繁体中文)。语言标签可以包含一个“Unicode扩展”,形式为-u-key1-value1-key2-value2..., 其中每个key是“扩展key”。不同的构造函数对此进行具体解释。</p>\n\n<p><code>opions</code> 是个对象,其属性(如果不存在,就赋值为undefined)决定格式化器(formatter)和整理器(collator)的行为。精确的解释由构造函数决定。</p>\n\n<p>给定区域信息和选项,实现会尝试生成近似理想行为的最接近行为。Firefox 支持用于整理(collation)的400+区域,用于date/time和数字格式化的600+区域,所以很可能(但不保证)你想要的区域是被支持的。</p>\n\n<p>这两个实际上是 js 的国际化 api 里的内容,js 国际化 api 包括了 日期、数字等的格式化,不过不是本文的重点,想了解可以参考<a href=\"http://www.open-open.com/lib/view/open1418779460683.html#articleHeader8\">此文</a>,虽然两年前的但是讲的很细。</p>\n\n<p>于是我开始使用 <code>locales</code> 和简单的正则来格式化我的时间,如下:</p>\n\n<pre><code>// 用到的地方不多,所以没用写成通用的format\nfunction DateFormat(now) { \n return now.toLocaleDateString('zh-CN').substring(5).replace(/\\//g, '月').concat('日 ', now.toLocaleTimeString().slice(0, -3));\n}\n</code></pre>\n\n<p>这段代码在 ios 下感觉良好,但是今天测试流程用了我自己的安卓机,世界就崩塌了🙄,时间是乱的。然后试了下,发现无论传入什么 <code>locates</code> 在安卓下都没有效果。擦擦擦,那肯定是兼容性的问题了,再次来到 MDN 翻到兼容性的地方,我方了:\n<img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f7767bp42kj311c0asdi0.jpg\" alt=\"\" /></p>\n\n<p>然后只能去找别的方式了,最后用的是正则表达式,亲测可用,如下:</p>\n\n<pre><code>// 对Date的扩展,将 Date 转化为指定格式的String \n// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, \n// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) \n// 例子: \n// (new Date()).Format(\"yyyy-MM-dd hh:mm:ss.S\") ==> 2006-07-02 08:09:04.423 \n// (new Date()).Format(\"yyyy-M-d h:m:s.S\") ==> 2006-7-2 8:9:4.18 \nDate.prototype.Format = function(fmt) \n{ //author: meizz \n var o = { \n \"M+\" : this.getMonth()+1, //月份 \n \"d+\" : this.getDate(), //日 \n \"h+\" : this.getHours(), //小时 \n \"m+\" : this.getMinutes(), //分 \n \"s+\" : this.getSeconds(), //秒 \n \"q+\" : Math.floor((this.getMonth()+3)/3), //季度 \n \"S\" : this.getMilliseconds() //毫秒 \n }; \n if(/(y+)/.test(fmt)) \n fmt=fmt.replace(RegExp.$1, (this.getFullYear()+\"\").substr(4 - RegExp.$1.length)); \n for(var k in o) \n if(new RegExp(\"(\"+ k +\")\").test(fmt)) \n fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : ((\"00\"+ o[k]).substr((\"\"+ o[k]).length))); \n return fmt; \n} \n</code></pre>\n\n<p>虽然最后完成了,但是对于 js 国际化和正则的部分内容,需要做更深入的研究!开饭了!😬</p>\n\n<p>另外如果不想折腾的话,<a href=\"https://github.com/moment/moment\">moment</a>这个库可能是一个不错的选择。😜</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1468860542709,"created_by":1,"updated_at":1472195793038,"updated_by":1,"published_at":1468899840142,"published_by":1},{"id":31,"uuid":"40e9f305-a20a-4f5c-b7d3-2fe928b04664","title":"自己撸表单验证插件——简陋版","slug":"zi-ji-lu-biao-dan-yan-zheng-cha-jian-jian-lou-ban","markdown":"写之前先吐槽一下:昨晚半夜起来好像把眼镜踩坏了😭,下午只好去了趟眼镜店,顶着杭城下午两点的太阳,也是很拼。没想到传来了更大的噩耗🙄,测散光时说我原来的眼镜散光做反了,没得救了😒。最后只好勉强得整了一下,但愿新镜片能够慢慢调整好😂。\n\n然后是正片,今天鱼头说把司机招募的验证加上去,刚好写完后台消息的AOP没什么事,所以我就寻思写的一劳永逸的验证模块,岂不是很好很nice。不过作为懒人还是先去 github 上着了一番,不过没有比较适合现在的项目的。主要原因还是页面已经完成了,改用其它组件的工程量太大。所以最后决定自己整一个不侵入现有组件的,只关注验证逻辑,可扩展的验证模块。\n\n先来看看引入插件之前的的表单验证:\n\n```\nhandleSubmit() {\n if(!this.state.credit_card){\n Alert.alert('必须填写银行卡号!');\n return;\n }\n if(!this.state.rest || this.state.rest < 0){\n Alert.alert('请填写正确的提现金额!');\n return;\n }\n if(this.state.rest > this.props.driver.driver_state.rest){\n Alert.alert('提现金额不能大于余额!');\n return;\n }\n rest.post...\n }\n```\n\n可以看到是通过 if 进行判断,这种做法,代码十分丑陋,而且复用性十分低。有什么办法能够改进上面的代码呢!进一步思考,其实表单验证的逻辑无非是对某个表单的输入,进行相关逻辑的判断。进行的逻辑判断是不变的,而判断的方式是可以改变的。是不是有点感觉了,我们可以把不变的逻辑固定下来,而可变的策略抽离出来。说到底就是策略模式了,策略模式就是通过把可变的判断策略提取出来,帮助我们省去那些 ` if ` 。废话不多说,先来看看在 web 上我们是如何使用的:[出处](http://brizer.github.io/2016/05/04/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/)\n\n```\n//定义验证策略\nvar strategies = {\n inNonEmpty:function(value,errorMsg){//不能为空\n if(value === ''){\n return errorMsg;\n }\n },\n minLength:function(value,length,errorMsg){//最小长度\n if(value.length < length){\n return errorMsg;\n }\n },\n isReg:function(value,reg,errorMsg){//正则匹配\n if(!reg.test(value)){\n return errorMsg;\n }\n }\n};\n//定义验证类\nvar Validator = function(){\n this.cache = [];//保存效验规则\n};\n//添加验证规则\nValidator.prototype.add = function(dom,rules){\n var that = this;\n //多条规则分别对应\n for(var i = 0 ,rule; rule = rules[i++];){\n (function(rule){\n var stratgyAry = rule.strategy.split(':');\n var errorMsg = rule.errorMsg;\n\t\t\t//将数据按照策略的格式塞入参数数组\n that.cache.push(function(){\n var strategy = strategyAry.shift();\n strategyAry.unshift(dom.value);\n strategyAry.push(errorMsg);\n return strategies[strategy].apply(dom,strategyAry);\n });\n })(rule)\n }\n};\nValidator.prototype.start = function(){\n //依次验证\n for(var i = 0,validatorFunc;validatorFunc = this.cache[i++];){\n var errorMsg = validatorFunc();\n if(errorMsg){\n return errorMsg;\n }\n }\n};\n\n//调用代码\nvar validataFunc = function(){\n var validator = new Validator();\n //多个条件判断\n validator.add(form.name,[{\n strategy:'isNonEmpty',\n errorMsg:'用户不能为空'\n },{\n strategy:'minLength:10',\n errorMsg:'用户长度不能小于10位'\n }]);\n validator.add(form.password,[{\n strategy:'minLength:6',\n errorMsg:'密码长度不能低于6位'\n }]);\n var errorMsg = validator.start();\n return errorMsg;\n}\nform.onsubmit = function(){\n var errorMsg = validateFunc();\n if(errorMsg){\n alert(errorMsg);\n return false;\n }\n //验证通过,提交表单\n}\n```\n\n上面的代码简单来说,就是定义了一个验证器,在验证前把验证的 dom 和 对应的策略添加到验证器中,然后调用 start 函数,一次运行验证函数,如有验证错误则返回错误信息。这样我们就可以复用验证策略,只需要关心每个输入具体要符合什么规则并传入就可以了,代码也很清爽。\n\n但是这还不能直接运用到 rn 当中,需要稍微改造下,因为我没有 dom ,我有的是 state。[tcomb-form-native](https://github.com/gcanti/tcomb-form-native)这个库给了我一些灵感。\n\n```\n// here we are: define your domain model\nvar Person = t.struct({\n name: t.String, // a required string\n surname: t.maybe(t.String), // an optional string\n age: t.Number, // a required number\n rememberMe: t.Boolean // a boolean\n});\n```\n\n它是这么定义验证的信息的,那其实我的 state 当中的需要验证的字段和验证规则之间也可以建立一种类似的规则。于是就有了我最后的简陋版的验证插件,先看代码,然后再谈谈不足:\n\n```\n/**\n * Created by m2mbob on 16/7/20.\n */\nconst strategies = {\n isRequired:function(value,errorMsg) {\n if(value === ''){\n return errorMsg;\n }\n },\n maxLength:function(value,length,errorMsg) {//最小长度\n if(value.length > length){\n return errorMsg;\n }\n },\n minLength:function(value,length,errorMsg) {//最小长度\n if(value.length < length){\n return errorMsg;\n }\n },\n isPhone:function(value,errorMsg) {\n return this.isReg(value, /^1[3|4|5|7|8]\\d{9}$/g, errorMsg);\n },\n isEmail:function(value, errorMsg) {\n return this.isReg(value, /^(\\w-*\\.*)+@(\\w-?)+(\\.\\w{2,})+$/g, errorMsg);\n },\n isIdCard:function(value, errorMsg) {\n return this.isReg(value, /(^\\d{15}$)|(^\\d{17}([0-9]|X)$)/g, errorMsg);\n },\n isCar:function(value, errorMsg) {\n return this.isReg(value, /^[\\u4e00-\\u9fa5]{1}[A-Z]{1}[A-Z_0-9]{5}$/g, errorMsg);\n },\n isReg:function(value,reg,errorMsg) {//正则匹配\n if(!reg.test(value)){\n return errorMsg;\n }\n }\n};\n\nexport default class Validator {\n constructor() {\n this.errMsg = [];\n }\n\n validate(value, rules) {\n let flag = true;\n for(let i = 0 ,rule; rule = rules[i++];){\n let strategyAry = rule.strategy.split(':');\n let errorMsg = rule.errorMsg;\n let strategy = strategyAry.shift();\n strategyAry.unshift(value);\n strategyAry.push(errorMsg);\n let err = strategies[strategy].apply(strategies, strategyAry);\n if(err){\n flag = false;\n this.errMsg.push(err);\n }\n }\n return flag;\n }\n\n validates(source, rules) {\n for (let key of Object.keys(rules)){\n if(source[key]){\n let result = this.validate(source[key], rules[key]);\n if(!result){\n const firstError = this.errMsg.shift();\n this.errMsg = [];\n return firstError;\n }\n }else{\n return rules[key][0].errorMsg;\n }\n }\n\n }\n\n}\n\n// 调用\nconst firstError = new Validator().validates(this.state.driver, {\n ...\n car_model: [{\n strategy:'isRequired',\n errorMsg:'车牌号不能为空'\n },{\n strategy:'isCar',\n errorMsg:'车牌号格式错误'\n }]\n ...\n });\n if (firstError) {\n Alert.alert(firstError);\n return;\n }\n```\n\n主要改动在于 validates 函数,这个函数接受两个参数 ` source ` 和 ` rules ` , ` source ` 是一个包含了所有待验证输入字段的源对象,大部分情况下就是 state 或 state 中的对象; ` rules ` 是一个对象,它的 ` Object.keys ` 求值结果是 ` source ` 求值结果的子集,也就是说每一个 ` rules ` 的键都能够在 ` source ` 中找到,所以我遍历了 ` rules ` 的 ` key ` 然后向 ` validate ` 方法传入 ` source[key] ` 和 ` rules[key] ` 进行校验, ` validate ` 方法改动不大。\n\n最后来说说不足的地方,首先是没能够将错误反映到实际的表单当中,而是 alert 的方式,体验稍差,这部分是需要改进的。其次,实际使用当中,当字段很多时,有不少重复的规则,是否可以合并?也就是换个角度来做这个问题!不过在这个项目中已经够用了。\n\n不早了,先写到这,后面在研究下,撸一个更好,更通用的版本!","html":"<p>写之前先吐槽一下:昨晚半夜起来好像把眼镜踩坏了😭,下午只好去了趟眼镜店,顶着杭城下午两点的太阳,也是很拼。没想到传来了更大的噩耗🙄,测散光时说我原来的眼镜散光做反了,没得救了😒。最后只好勉强得整了一下,但愿新镜片能够慢慢调整好😂。</p>\n\n<p>然后是正片,今天鱼头说把司机招募的验证加上去,刚好写完后台消息的AOP没什么事,所以我就寻思写的一劳永逸的验证模块,岂不是很好很nice。不过作为懒人还是先去 github 上着了一番,不过没有比较适合现在的项目的。主要原因还是页面已经完成了,改用其它组件的工程量太大。所以最后决定自己整一个不侵入现有组件的,只关注验证逻辑,可扩展的验证模块。</p>\n\n<p>先来看看引入插件之前的的表单验证:</p>\n\n<pre><code>handleSubmit() { \n if(!this.state.credit_card){\n Alert.alert('必须填写银行卡号!');\n return;\n }\n if(!this.state.rest || this.state.rest < 0){\n Alert.alert('请填写正确的提现金额!');\n return;\n }\n if(this.state.rest > this.props.driver.driver_state.rest){\n Alert.alert('提现金额不能大于余额!');\n return;\n }\n rest.post...\n }\n</code></pre>\n\n<p>可以看到是通过 if 进行判断,这种做法,代码十分丑陋,而且复用性十分低。有什么办法能够改进上面的代码呢!进一步思考,其实表单验证的逻辑无非是对某个表单的输入,进行相关逻辑的判断。进行的逻辑判断是不变的,而判断的方式是可以改变的。是不是有点感觉了,我们可以把不变的逻辑固定下来,而可变的策略抽离出来。说到底就是策略模式了,策略模式就是通过把可变的判断策略提取出来,帮助我们省去那些 <code>if</code> 。废话不多说,先来看看在 web 上我们是如何使用的:<a href=\"http://brizer.github.io/2016/05/04/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/\">出处</a></p>\n\n<pre><code>//定义验证策略\nvar strategies = { \n inNonEmpty:function(value,errorMsg){//不能为空\n if(value === ''){\n return errorMsg;\n }\n },\n minLength:function(value,length,errorMsg){//最小长度\n if(value.length < length){\n return errorMsg;\n }\n },\n isReg:function(value,reg,errorMsg){//正则匹配\n if(!reg.test(value)){\n return errorMsg;\n }\n }\n};\n//定义验证类\nvar Validator = function(){ \n this.cache = [];//保存效验规则\n};\n//添加验证规则\nValidator.prototype.add = function(dom,rules){ \n var that = this;\n //多条规则分别对应\n for(var i = 0 ,rule; rule = rules[i++];){\n (function(rule){\n var stratgyAry = rule.strategy.split(':');\n var errorMsg = rule.errorMsg;\n //将数据按照策略的格式塞入参数数组\n that.cache.push(function(){\n var strategy = strategyAry.shift();\n strategyAry.unshift(dom.value);\n strategyAry.push(errorMsg);\n return strategies[strategy].apply(dom,strategyAry);\n });\n })(rule)\n }\n};\nValidator.prototype.start = function(){ \n //依次验证\n for(var i = 0,validatorFunc;validatorFunc = this.cache[i++];){\n var errorMsg = validatorFunc();\n if(errorMsg){\n return errorMsg;\n }\n }\n};\n\n//调用代码\nvar validataFunc = function(){ \n var validator = new Validator();\n //多个条件判断\n validator.add(form.name,[{\n strategy:'isNonEmpty',\n errorMsg:'用户不能为空'\n },{\n strategy:'minLength:10',\n errorMsg:'用户长度不能小于10位'\n }]);\n validator.add(form.password,[{\n strategy:'minLength:6',\n errorMsg:'密码长度不能低于6位'\n }]);\n var errorMsg = validator.start();\n return errorMsg;\n}\nform.onsubmit = function(){ \n var errorMsg = validateFunc();\n if(errorMsg){\n alert(errorMsg);\n return false;\n }\n //验证通过,提交表单\n}\n</code></pre>\n\n<p>上面的代码简单来说,就是定义了一个验证器,在验证前把验证的 dom 和 对应的策略添加到验证器中,然后调用 start 函数,一次运行验证函数,如有验证错误则返回错误信息。这样我们就可以复用验证策略,只需要关心每个输入具体要符合什么规则并传入就可以了,代码也很清爽。</p>\n\n<p>但是这还不能直接运用到 rn 当中,需要稍微改造下,因为我没有 dom ,我有的是 state。<a href=\"https://github.com/gcanti/tcomb-form-native\">tcomb-form-native</a>这个库给了我一些灵感。</p>\n\n<pre><code>// here we are: define your domain model\nvar Person = t.struct({ \n name: t.String, // a required string\n surname: t.maybe(t.String), // an optional string\n age: t.Number, // a required number\n rememberMe: t.Boolean // a boolean\n});\n</code></pre>\n\n<p>它是这么定义验证的信息的,那其实我的 state 当中的需要验证的字段和验证规则之间也可以建立一种类似的规则。于是就有了我最后的简陋版的验证插件,先看代码,然后再谈谈不足:</p>\n\n<pre><code>/**\n * Created by m2mbob on 16/7/20.\n */\nconst strategies = { \n isRequired:function(value,errorMsg) {\n if(value === ''){\n return errorMsg;\n }\n },\n maxLength:function(value,length,errorMsg) {//最小长度\n if(value.length > length){\n return errorMsg;\n }\n },\n minLength:function(value,length,errorMsg) {//最小长度\n if(value.length < length){\n return errorMsg;\n }\n },\n isPhone:function(value,errorMsg) {\n return this.isReg(value, /^1[3|4|5|7|8]\\d{9}$/g, errorMsg);\n },\n isEmail:function(value, errorMsg) {\n return this.isReg(value, /^(\\w-*\\.*)+@(\\w-?)+(\\.\\w{2,})+$/g, errorMsg);\n },\n isIdCard:function(value, errorMsg) {\n return this.isReg(value, /(^\\d{15}$)|(^\\d{17}([0-9]|X)$)/g, errorMsg);\n },\n isCar:function(value, errorMsg) {\n return this.isReg(value, /^[\\u4e00-\\u9fa5]{1}[A-Z]{1}[A-Z_0-9]{5}$/g, errorMsg);\n },\n isReg:function(value,reg,errorMsg) {//正则匹配\n if(!reg.test(value)){\n return errorMsg;\n }\n }\n};\n\nexport default class Validator { \n constructor() {\n this.errMsg = [];\n }\n\n validate(value, rules) {\n let flag = true;\n for(let i = 0 ,rule; rule = rules[i++];){\n let strategyAry = rule.strategy.split(':');\n let errorMsg = rule.errorMsg;\n let strategy = strategyAry.shift();\n strategyAry.unshift(value);\n strategyAry.push(errorMsg);\n let err = strategies[strategy].apply(strategies, strategyAry);\n if(err){\n flag = false;\n this.errMsg.push(err);\n }\n }\n return flag;\n }\n\n validates(source, rules) {\n for (let key of Object.keys(rules)){\n if(source[key]){\n let result = this.validate(source[key], rules[key]);\n if(!result){\n const firstError = this.errMsg.shift();\n this.errMsg = [];\n return firstError;\n }\n }else{\n return rules[key][0].errorMsg;\n }\n }\n\n }\n\n}\n\n// 调用\nconst firstError = new Validator().validates(this.state.driver, { \n ...\n car_model: [{\n strategy:'isRequired',\n errorMsg:'车牌号不能为空'\n },{\n strategy:'isCar',\n errorMsg:'车牌号格式错误'\n }]\n ...\n });\n if (firstError) {\n Alert.alert(firstError);\n return;\n }\n</code></pre>\n\n<p>主要改动在于 validates 函数,这个函数接受两个参数 <code>source</code> 和 <code>rules</code> , <code>source</code> 是一个包含了所有待验证输入字段的源对象,大部分情况下就是 state 或 state 中的对象; <code>rules</code> 是一个对象,它的 <code>Object.keys</code> 求值结果是 <code>source</code> 求值结果的子集,也就是说每一个 <code>rules</code> 的键都能够在 <code>source</code> 中找到,所以我遍历了 <code>rules</code> 的 <code>key</code> 然后向 <code>validate</code> 方法传入 <code>source[key]</code> 和 <code>rules[key]</code> 进行校验, <code>validate</code> 方法改动不大。</p>\n\n<p>最后来说说不足的地方,首先是没能够将错误反映到实际的表单当中,而是 alert 的方式,体验稍差,这部分是需要改进的。其次,实际使用当中,当字段很多时,有不少重复的规则,是否可以合并?也就是换个角度来做这个问题!不过在这个项目中已经够用了。</p>\n\n<p>不早了,先写到这,后面在研究下,撸一个更好,更通用的版本!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469026705376,"created_by":1,"updated_at":1469029771903,"updated_by":1,"published_at":1469029771905,"published_by":1},{"id":32,"uuid":"bc1e84b8-b50e-41b3-ad8d-acea8fa0d04c","title":"有趣、有感、有情","slug":"you-qu-you-gan-you-qing","markdown":"<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=2684667&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>","html":"<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=2684667&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469065163248,"created_by":1,"updated_at":1470055042869,"updated_by":1,"published_at":1470055042869,"published_by":1},{"id":33,"uuid":"b0c15dab-e1f8-41fc-8bd1-d6de3326ca2b","title":"react native —— 安卓知识与踩坑篇(一)","slug":"react-native-an-zhuo-zhi-shi-bu-chong-pian","markdown":"这个系列文章是在编写[react native 高德后台定位插件](https://github.com/yptech/react-native-yunpeng-amplocation)的安卓部分时,使用到的一些原生安卓知识的记录。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=297728&auto=1&height=66\"></iframe>\n\n##### android studio 真机运行的时候gradle 报错\n\nFAILURE: Build failed with an exception.\n\n* What went wrong:\n\nExecution failed for task ':app:preDexDebug'.\n\n> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'C:\\Program Files\\Java\\jdk1.7.0_71\\bin\\java.exe'' finished with non-zero exit value 1\n\n* Try:\n\nRun with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.\n\n* solution\n\n>1. 在project structure中去掉重复的jar包,\n2. 如果你的jar包是放到了libs目录中,且在gradle中发现了如下代码:\ncompile fileTree(include: ['*.jar'], dir: 'libs')\n则注释掉即可。\n\n##### Android中Parcelable序列化总结\n\nParcelable 接口与 JDK 当中的 Serializable 接口类似,是为了做对象序列化的。在我本次编写插件过程中,需要通过 intent 传递配置的对象,而这个对象要传递就需要实现 Parcelable 接口。\n\n###### Serializable与Parcelable的区别\n\n1. Serializable是JDK提供的接口,而Parcelable是Android SDK提供的。\n2. Serializable序列化是基于磁盘的,而Parcelable是基于内存的。在内存中读写肯定效率要高于磁盘,所以Android中跨进程传递对象都是使用Parcelable。\n\n最后来看看如何使用,我们需要实现 Parcelable 接口的如下方法:\n\n 1. describeContents方法。内容接口描述,默认返回0就可以;\n 2. writeToParcel 方法。该方法将类的数据写入外部提供的Parcel中.即打包需要传递的数据到Parcel容器保存,以便从parcel容器获取数据,该方法声明如下:`writeToParcel (Parcel dest, int flags)`\n 3. 静态的Parcelable.Creator接口,本接口有两个方法:`createFromParcel(Parcel in)` 从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层。`newArray(int size)` 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。方法是供外部类反序列化本类数组使用。\n\n下面的例子包括了大部分的使用情况:\n\n```\npackage com.suning.mobile.paysdk.pay;\n \nimport java.util.ArrayList;\n \nimport android.os.Parcel;\nimport android.os.Parcelable;\n \nimport com.yaya.test.OrderInfoBean;\n \n/**\n * \n * 〈一句话功能简述〉<br>\n * 〈功能详细描述〉 数据类型序列化\n */\npublic class ParcelableType implements Parcelable {\n /** int 类型 */\n int age;\n /** String 类型 */\n String name;\n /** boolean 注意该boolean的get和set方法 **/\n boolean isGood;\n /** boolean 类型 **/\n boolean complete;\n /** 数组 **/\n private String[] ids;\n /** 对象 [内部已经序列化] **/\n private OrderInfoBean bean;\n /** list **/\n private ArrayList<orderinfobean> listBeans;\n \n /**\n * 默认构造方法\n */\n public ParcelableType() {\n // TODO Auto-generated constructor stub\n }\n \n public ParcelableType(Parcel in) {\n readFromParcel(in);\n }\n \n /***\n * 默认实现\n */\n @Override\n public int describeContents() {\n // TODO Auto-generated method stub\n return 0;\n }\n \n @Override\n public void writeToParcel(Parcel dest, int flags) {\n /** int 写入 **/\n dest.writeInt(age);\n /** string 写入 **/\n dest.writeString(name);\n /** boolean 写入 **/\n dest.writeInt(isGood ? 1 : 0);\n /** boolean 写入 **/\n dest.writeInt(complete ? 1 : 0);\n /** 数组 写入 **/\n if (ids != null) {\n dest.writeInt(ids.length);\n } else {\n dest.writeInt(0);\n }\n dest.writeStringArray(ids);\n /** 对象 写入 **/\n dest.writeParcelable(bean, flags);\n /** list 写入 **/\n dest.writeList(listBeans);\n \n }\n \n @SuppressWarnings(\"unchecked\")\n private void readFromParcel(Parcel in) {\n \n /** int 读出 */\n age = in.readInt();\n /** stirng 读出 */\n name = in.readString();\n /** boolean 读出 */\n isGood = (in.readInt() == 1) ? true : false;\n /** boolean 读出 */\n complete = (in.readInt() == 1) ? true : false;\n /** 数组 读出 */\n int length = in.readInt();\n ids = new String[length];\n in.readStringArray(ids);\n /** 对象 读出 */\n bean = in.readParcelable(OrderInfoBean.class.getClassLoader());\n /** list 读出 */\n listBeans = in.readArrayList(OrderInfoBean.class.getClassLoader());\n \n }\n \n public static final Parcelable.Creator<parcelabletype> CREATOR = new Parcelable.Creator<parcelabletype>() {\n public ParcelableType createFromParcel(Parcel in) {\n return new ParcelableType(in);\n }\n \n public ParcelableType[] newArray(int size) {\n return new ParcelableType[size];\n }\n };\n \n public int getAge() {\n return age;\n }\n \n public void setAge(int age) {\n this.age = age;\n }\n \n public String getName() {\n return name;\n }\n \n public void setName(String name) {\n this.name = name;\n }\n \n /**\n * \n * 功能描述: <br>\n * 〈功能详细描述〉 fastJson解析时需要格式\n */\n public boolean isIsGood() {\n return isGood;\n }\n \n public void setIsGood(boolean isGood) {\n this.isGood = isGood;\n }\n \n public boolean isComplete() {\n return complete;\n }\n \n public void setComplete(boolean complete) {\n this.complete = complete;\n }\n \n public String[] getIds() {\n return ids;\n }\n \n public void setIds(String[] ids) {\n this.ids = ids;\n }\n \n public OrderInfoBean getBean() {\n return bean;\n }\n \n public void setBean(OrderInfoBean bean) {\n this.bean = bean;\n }\n \n public ArrayList<orderinfobean> getListBeans() {\n return listBeans;\n }\n \n public void setListBeans(ArrayList<orderinfobean> listBeans) {\n this.listBeans = listBeans;\n }\n \n}\n```\n\n\n\n","html":"<p>这个系列文章是在编写<a href=\"https://github.com/yptech/react-native-yunpeng-amplocation\">react native 高德后台定位插件</a>的安卓部分时,使用到的一些原生安卓知识的记录。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=297728&auto=1&height=66\"></iframe>\n\n<h5 id=\"androidstudiogradle\">android studio 真机运行的时候gradle 报错</h5>\n\n<p>FAILURE: Build failed with an exception.</p>\n\n<ul>\n<li>What went wrong:</li>\n</ul>\n\n<p>Execution failed for task ':app:preDexDebug'.</p>\n\n<blockquote>\n <p>com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'C:\\Program Files\\Java\\jdk1.7.0_71\\bin\\java.exe'' finished with non-zero exit value 1</p>\n</blockquote>\n\n<ul>\n<li>Try:</li>\n</ul>\n\n<p>Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.</p>\n\n<ul>\n<li>solution</li>\n</ul>\n\n<blockquote>\n <ol>\n <li>在project structure中去掉重复的jar包,</li>\n <li>如果你的jar包是放到了libs目录中,且在gradle中发现了如下代码: <br />\n compile fileTree(include: ['*.jar'], dir: 'libs') <br />\n 则注释掉即可。</li>\n </ol>\n</blockquote>\n\n<h5 id=\"androidparcelable\">Android中Parcelable序列化总结</h5>\n\n<p>Parcelable 接口与 JDK 当中的 Serializable 接口类似,是为了做对象序列化的。在我本次编写插件过程中,需要通过 intent 传递配置的对象,而这个对象要传递就需要实现 Parcelable 接口。</p>\n\n<h6 id=\"serializableparcelable\">Serializable与Parcelable的区别</h6>\n\n<ol>\n<li>Serializable是JDK提供的接口,而Parcelable是Android SDK提供的。 </li>\n<li>Serializable序列化是基于磁盘的,而Parcelable是基于内存的。在内存中读写肯定效率要高于磁盘,所以Android中跨进程传递对象都是使用Parcelable。</li>\n</ol>\n\n<p>最后来看看如何使用,我们需要实现 Parcelable 接口的如下方法:</p>\n\n<ol>\n<li>describeContents方法。内容接口描述,默认返回0就可以;</li>\n<li>writeToParcel 方法。该方法将类的数据写入外部提供的Parcel中.即打包需要传递的数据到Parcel容器保存,以便从parcel容器获取数据,该方法声明如下:<code>writeToParcel (Parcel dest, int flags)</code></li>\n<li>静态的Parcelable.Creator接口,本接口有两个方法:<code>createFromParcel(Parcel in)</code> 从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层。<code>newArray(int size)</code> 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。方法是供外部类反序列化本类数组使用。</li>\n</ol>\n\n<p>下面的例子包括了大部分的使用情况:</p>\n\n<pre><code>package com.suning.mobile.paysdk.pay;\n\nimport java.util.ArrayList;\n\nimport android.os.Parcel; \nimport android.os.Parcelable;\n\nimport com.yaya.test.OrderInfoBean;\n\n/**\n * \n * 〈一句话功能简述〉<br>\n * 〈功能详细描述〉 数据类型序列化\n */\npublic class ParcelableType implements Parcelable { \n /** int 类型 */\n int age;\n /** String 类型 */\n String name;\n /** boolean 注意该boolean的get和set方法 **/\n boolean isGood;\n /** boolean 类型 **/\n boolean complete;\n /** 数组 **/\n private String[] ids;\n /** 对象 [内部已经序列化] **/\n private OrderInfoBean bean;\n /** list **/\n private ArrayList<orderinfobean> listBeans;\n\n /**\n * 默认构造方法\n */\n public ParcelableType() {\n // TODO Auto-generated constructor stub\n }\n\n public ParcelableType(Parcel in) {\n readFromParcel(in);\n }\n\n /***\n * 默认实现\n */\n @Override\n public int describeContents() {\n // TODO Auto-generated method stub\n return 0;\n }\n\n @Override\n public void writeToParcel(Parcel dest, int flags) {\n /** int 写入 **/\n dest.writeInt(age);\n /** string 写入 **/\n dest.writeString(name);\n /** boolean 写入 **/\n dest.writeInt(isGood ? 1 : 0);\n /** boolean 写入 **/\n dest.writeInt(complete ? 1 : 0);\n /** 数组 写入 **/\n if (ids != null) {\n dest.writeInt(ids.length);\n } else {\n dest.writeInt(0);\n }\n dest.writeStringArray(ids);\n /** 对象 写入 **/\n dest.writeParcelable(bean, flags);\n /** list 写入 **/\n dest.writeList(listBeans);\n\n }\n\n @SuppressWarnings(\"unchecked\")\n private void readFromParcel(Parcel in) {\n\n /** int 读出 */\n age = in.readInt();\n /** stirng 读出 */\n name = in.readString();\n /** boolean 读出 */\n isGood = (in.readInt() == 1) ? true : false;\n /** boolean 读出 */\n complete = (in.readInt() == 1) ? true : false;\n /** 数组 读出 */\n int length = in.readInt();\n ids = new String[length];\n in.readStringArray(ids);\n /** 对象 读出 */\n bean = in.readParcelable(OrderInfoBean.class.getClassLoader());\n /** list 读出 */\n listBeans = in.readArrayList(OrderInfoBean.class.getClassLoader());\n\n }\n\n public static final Parcelable.Creator<parcelabletype> CREATOR = new Parcelable.Creator<parcelabletype>() {\n public ParcelableType createFromParcel(Parcel in) {\n return new ParcelableType(in);\n }\n\n public ParcelableType[] newArray(int size) {\n return new ParcelableType[size];\n }\n };\n\n public int getAge() {\n return age;\n }\n\n public void setAge(int age) {\n this.age = age;\n }\n\n public String getName() {\n return name;\n }\n\n public void setName(String name) {\n this.name = name;\n }\n\n /**\n * \n * 功能描述: <br>\n * 〈功能详细描述〉 fastJson解析时需要格式\n */\n public boolean isIsGood() {\n return isGood;\n }\n\n public void setIsGood(boolean isGood) {\n this.isGood = isGood;\n }\n\n public boolean isComplete() {\n return complete;\n }\n\n public void setComplete(boolean complete) {\n this.complete = complete;\n }\n\n public String[] getIds() {\n return ids;\n }\n\n public void setIds(String[] ids) {\n this.ids = ids;\n }\n\n public OrderInfoBean getBean() {\n return bean;\n }\n\n public void setBean(OrderInfoBean bean) {\n this.bean = bean;\n }\n\n public ArrayList<orderinfobean> getListBeans() {\n return listBeans;\n }\n\n public void setListBeans(ArrayList<orderinfobean> listBeans) {\n this.listBeans = listBeans;\n }\n\n}\n</code></pre>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469240531228,"created_by":1,"updated_at":1469541841611,"updated_by":1,"published_at":1469535290970,"published_by":1},{"id":34,"uuid":"44877e28-bbec-4496-a6f0-b21cc68f2c79","title":"Android Studio 中 .gitignore 的编写","slug":"zhuan-android-studio-zhong-gitignore-de-bian-xie","markdown":"<embed src=\"http://www.xiami.com/widget/49883737_1770692731/singlePlayer.swf\" type=\"application/x-shockwave-flash\" width=\"257\" height=\"33\" wmode=\"transparent\"></embed>\n\n最近花了比较大的力气,写了一个 react native 版高德地图定位及后台持续监听定位的插件,主要写的是安卓的原生代码,所以后面会有不少关于本次编写当中原生知识相关的博文!这算是第一篇😪。还有好久木有放歌了,懒得回去加了,不过这首确实很好听,如果弹过吉他更会有不同的体会,T121 3121!😊。\n\n因为之前的项目的 .gitignore 文件都是 node 或者 react 相关的模板,或者稍微定制一下,用在这个插件当中,发现传上去很多不必要的文件,所以找了一个比较通用的放在这,以后肯定用得到。\n\n```\n# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ngen/\nout/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Windows thumbnail db\nThumbs.db\n\n# OSX files\n.DS_Store\n\n# Eclipse project files\n.classpath\n.project\n\n# Android Studio\n*.iml\n.idea\n\n# Local IDEA workspace\n.idea/workspace.xml\n\n# Gradle cache\n.gradle\n\n#NDK\nobj/\n```","html":"<p><embed src=\"http://www.xiami.com/widget/49883737_1770692731/singlePlayer.swf\" type=\"application/x-shockwave-flash\" width=\"257\" height=\"33\" wmode=\"transparent\"></embed></p>\n\n<p>最近花了比较大的力气,写了一个 react native 版高德地图定位及后台持续监听定位的插件,主要写的是安卓的原生代码,所以后面会有不少关于本次编写当中原生知识相关的博文!这算是第一篇😪。还有好久木有放歌了,懒得回去加了,不过这首确实很好听,如果弹过吉他更会有不同的体会,T121 3121!😊。</p>\n\n<p>因为之前的项目的 .gitignore 文件都是 node 或者 react 相关的模板,或者稍微定制一下,用在这个插件当中,发现传上去很多不必要的文件,所以找了一个比较通用的放在这,以后肯定用得到。</p>\n\n<pre><code># built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/ \ngen/ \nout/ \nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Windows thumbnail db\nThumbs.db\n\n# OSX files\n.DS_Store\n\n# Eclipse project files\n.classpath\n.project\n\n# Android Studio\n*.iml\n.idea\n\n# Local IDEA workspace\n.idea/workspace.xml\n\n# Gradle cache\n.gradle\n\n#NDK\nobj/ \n</code></pre>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469436400951,"created_by":1,"updated_at":1469437453475,"updated_by":1,"published_at":1469436760761,"published_by":1},{"id":35,"uuid":"1a7afcb5-1488-4fac-891d-5ea2b4df142a","title":"react native —— 安卓知识与踩坑篇(二)","slug":"react-native-an-zhuo-zhi-shi-yu-cai-keng-pian-er","markdown":"这个系列文章是在编写[react native 高德后台定位插件](https://github.com/yptech/react-native-yunpeng-amplocation)的安卓部分时,使用到的一些原生安卓知识的记录。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=668472&auto=1&height=66\"></iframe>\n\n##### Android Service \n\nService 是我实现后台持续监听定位的主要工具,他是 android 的四大组件之一,它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。Service 的内容很多可以看下面这几篇文章。\n\n在这里主要要讲的是如何让 Service 不被杀死,因为 app 进入后台后,当内存比较紧张时,后台 Service 就有可能被杀死,在下面的文章一中对三种杀死的情况做了讨论。\n\n1. 系统根据资源分配情况杀死服务\n2. 用户通过settings->Apps->Running->Stop方式杀死服务\n3. 用户通过settings->Apps->Downloaded->Force Stop方式杀死服务\n\n###### 第一种情况:\n\n用户不干预,完全靠系统来控制,办法有很多。比如 ` onStartCommand() ` 方法的返回值设为 ` START_STICKY ` ,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。\n\n###### 第二种情况:\n 用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 ` onDestory() ` 方法,这时候一个方案就是在` onDestory() ` 中发送广播开启自己。这样杀死服务后会立即启动。如下:\n\n```\n@Override\npublic void onCreate() {\n // TODO Auto-generated method stub\n super.onCreate();\n\n mBR = new BroadcastReceiver() {\n @Override\n public void onReceive(Context context, Intent intent) {\n // TODO Auto-generated method stub\n Intent a = new Intent(ServiceA.this, ServiceA.class);\n startService(a);\n }\n };\n mIF = new IntentFilter();\n mIF.addAction(\"listener\");\n registerReceiver(mBR, mIF);\n}\n\n@Override\npublic void onDestroy() {\n // TODO Auto-generated method stub\n super.onDestroy();\n\n Intent intent = new Intent();\n intent.setAction(\"listener\");\n sendBroadcast(intent);\n\n unregisterReceiver(mBR);\n}\n```\n当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。\n\n###### 第三种情况:\n 强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉force stop和uninstall按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。\n\n 最后再说一句,别在这上面太折腾,弄成流氓软件就不好了。我就是讨厌一些软件乱发通知,起服务才转而用iPhone的。不过下一代Android好像可以支持用户选择是否开启软件设置的权限了,倒是可以期待一下。\n\n我最后使用的是设置前台服务的方式,基本够用了。不过据说高德本身已经实现了后台定位的功能,据说还和我写的差不多😂。不过通过写这个插件,基本熟悉了 Service 用法的各个细节了。\n\n参考资料:\n\n1. http://www.cnblogs.com/rossoneri/p/4530216.html\n2. http://blog.csdn.net/guolin_blog/article/details/11952435\n3. http://blog.csdn.net/guolin_blog/article/details/9797169\n4. http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html\n\n##### Android Intent启动flag\n\n网上一查这个 flag 有 20 多种,而且内容很多,所以先只记录下我在插件中使用的 FLAG_FROM_BACKGROUND ,这个 flag 表示这个Intent来自一个后台操作,而不是用户交互。用到了再继续记录。","html":"<p>这个系列文章是在编写<a href=\"https://github.com/yptech/react-native-yunpeng-amplocation\">react native 高德后台定位插件</a>的安卓部分时,使用到的一些原生安卓知识的记录。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=668472&auto=1&height=66\"></iframe>\n\n<h5 id=\"androidservice\">Android Service</h5>\n\n<p>Service 是我实现后台持续监听定位的主要工具,他是 android 的四大组件之一,它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。Service 的内容很多可以看下面这几篇文章。</p>\n\n<p>在这里主要要讲的是如何让 Service 不被杀死,因为 app 进入后台后,当内存比较紧张时,后台 Service 就有可能被杀死,在下面的文章一中对三种杀死的情况做了讨论。</p>\n\n<ol>\n<li>系统根据资源分配情况杀死服务 </li>\n<li>用户通过settings->Apps->Running->Stop方式杀死服务 </li>\n<li>用户通过settings->Apps->Downloaded->Force Stop方式杀死服务</li>\n</ol>\n\n<h6 id=\"\">第一种情况:</h6>\n\n<p>用户不干预,完全靠系统来控制,办法有很多。比如 <code>onStartCommand()</code> 方法的返回值设为 <code>START_STICKY</code> ,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。</p>\n\n<h6 id=\"\">第二种情况:</h6>\n\n<p> 用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 <code>onDestory()</code> 方法,这时候一个方案就是在<code>onDestory()</code> 中发送广播开启自己。这样杀死服务后会立即启动。如下:</p>\n\n<pre><code>@Override\npublic void onCreate() { \n // TODO Auto-generated method stub\n super.onCreate();\n\n mBR = new BroadcastReceiver() {\n @Override\n public void onReceive(Context context, Intent intent) {\n // TODO Auto-generated method stub\n Intent a = new Intent(ServiceA.this, ServiceA.class);\n startService(a);\n }\n };\n mIF = new IntentFilter();\n mIF.addAction(\"listener\");\n registerReceiver(mBR, mIF);\n}\n\n@Override\npublic void onDestroy() { \n // TODO Auto-generated method stub\n super.onDestroy();\n\n Intent intent = new Intent();\n intent.setAction(\"listener\");\n sendBroadcast(intent);\n\n unregisterReceiver(mBR);\n}\n</code></pre>\n\n<p>当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。</p>\n\n<h6 id=\"\">第三种情况:</h6>\n\n<p> 强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉force stop和uninstall按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。</p>\n\n<p> 最后再说一句,别在这上面太折腾,弄成流氓软件就不好了。我就是讨厌一些软件乱发通知,起服务才转而用iPhone的。不过下一代Android好像可以支持用户选择是否开启软件设置的权限了,倒是可以期待一下。</p>\n\n<p>我最后使用的是设置前台服务的方式,基本够用了。不过据说高德本身已经实现了后台定位的功能,据说还和我写的差不多😂。不过通过写这个插件,基本熟悉了 Service 用法的各个细节了。</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"http://www.cnblogs.com/rossoneri/p/4530216.html\">http://www.cnblogs.com/rossoneri/p/4530216.html</a> </li>\n<li><a href=\"http://blog.csdn.net/guolin_blog/article/details/11952435\">http://blog.csdn.net/guolin_blog/article/details/11952435</a> </li>\n<li><a href=\"http://blog.csdn.net/guolin_blog/article/details/9797169\">http://blog.csdn.net/guolin_blog/article/details/9797169</a> </li>\n<li><a href=\"http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html\">http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html</a></li>\n</ol>\n\n<h5 id=\"androidintentflag\">Android Intent启动flag</h5>\n\n<p>网上一查这个 flag 有 20 多种,而且内容很多,所以先只记录下我在插件中使用的 FLAG<em>FROM</em>BACKGROUND ,这个 flag 表示这个Intent来自一个后台操作,而不是用户交互。用到了再继续记录。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469538467105,"created_by":1,"updated_at":1469541769399,"updated_by":1,"published_at":1469540968037,"published_by":1},{"id":36,"uuid":"5bd403a4-4255-4ca8-9c0f-344e89f98391","title":"关于电商网站订单号","slug":"build-gradle-pei-zhi-wen-jian-xiang-jie","markdown":"在我自主编写的支付系统生成的签名串当中采用的订单号就是自增的账单 id ,好处是在支付宝的异步回调当中操作方便。但是在发布正式版测试时出现了订单号重复的问题,于是我决定修改传递给支付宝的订单号的生成策略。\n\n关于生成电商订单号,我觉得需要考虑:\n\n1. 唯一性(主要是分布式环境中,这部分我不必须满足)\n2. 不暴露信息(自增订单号会暴露网站的经营状况)\n3. 长度适中(32位guid略长,控制在15位左右)\n\n我的方案参考淘宝:\n\n> 时间 + 随机数或业务要素ID\n\n建议结合实际情况,充分利用时间、随机数、商家ID、会员ID、自增ID这些来组合,根据自身运营特点来制定。如果真的对并发要求特别高,在 guid 上做文章也是不错的方案。\n\n更多的业务场景,可以看参考资料三。\n\n参考资料:\n\n1. https://segmentfault.com/q/1010000004104517/a-1020000004119558\n2. http://blog.sina.com.cn/s/blog_47039e010100ho7v.html\n3. http://www.php1.cn/article/8506.html","html":"<p>在我自主编写的支付系统生成的签名串当中采用的订单号就是自增的账单 id ,好处是在支付宝的异步回调当中操作方便。但是在发布正式版测试时出现了订单号重复的问题,于是我决定修改传递给支付宝的订单号的生成策略。</p>\n\n<p>关于生成电商订单号,我觉得需要考虑:</p>\n\n<ol>\n<li>唯一性(主要是分布式环境中,这部分我不必须满足) </li>\n<li>不暴露信息(自增订单号会暴露网站的经营状况) </li>\n<li>长度适中(32位guid略长,控制在15位左右)</li>\n</ol>\n\n<p>我的方案参考淘宝:</p>\n\n<blockquote>\n <p>时间 + 随机数或业务要素ID</p>\n</blockquote>\n\n<p>建议结合实际情况,充分利用时间、随机数、商家ID、会员ID、自增ID这些来组合,根据自身运营特点来制定。如果真的对并发要求特别高,在 guid 上做文章也是不错的方案。</p>\n\n<p>更多的业务场景,可以看参考资料三。</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"https://segmentfault.com/q/1010000004104517/a-1020000004119558\">https://segmentfault.com/q/1010000004104517/a-1020000004119558</a> </li>\n<li><a href=\"http://blog.sina.com.cn/s/blog_47039e010100ho7v.html\">http://blog.sina.com.cn/s/blog_47039e010100ho7v.html</a> </li>\n<li><a href=\"http://www.php1.cn/article/8506.html\">http://www.php1.cn/article/8506.html</a></li>\n</ol>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469542163724,"created_by":1,"updated_at":1470842942480,"updated_by":1,"published_at":1470842891895,"published_by":1},{"id":37,"uuid":"822a333b-0ec6-4596-b8b3-952731a62b35","title":"react native —— 安卓知识与踩坑篇(三)","slug":"react-native-an-zhuo-zhi-shi-yu-cai-keng-pian-er-2","markdown":"这个系列文章是在编写[react native 高德后台定位插件](https://github.com/yptech/react-native-yunpeng-amplocation)的安卓部分时,使用到的一些原生安卓知识的记录。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=2742998&auto=1&height=66\"></iframe>\n\n下午没事,思考和总结了下 flux 、 redux 相关的东西,爆炸了!还没有想得特别的清楚,先放松一下,把这个系列最后的一些部分写一下!\n\n##### Gradle 简介\n\n一个构建工具,写过 javaWeb 的同学肯定知道另一个构建工具 Apache Maven,两者做的工作基本差不多,就是依赖管理、编译、打包等等工作,当然还有古老的 ant。不过这篇文章不会对这几者进行比较,这里的重点是 Android 和 Gradle。\n\n##### 安装Gradle\n\n在 Android Studio 中新建项目成功后会下载Gradle,貌似这个过程不翻墙也是可以下载,但是访问特别慢,建议翻墙下载。\n\n* Mac上会默认下载到 /Users/<用户名>/.gradle/wrapper/dists 目录\n* Win平台会默认下载到 C:\\Documents and Settings<用户名>.gradle\\wrapper\\dists 目录\n\n你会看到这个目录下有个 gradle-x.xx-all 的文件夹, 如果下载实在太慢,但是又不想翻墙的话,可以自己手动到Gradle官网下载对应的版本,然后将下载的.zip文件(也可以解压)复制到上述的gradle-x.xx-all 文件夹下,不过还是建议让它直接下载的好。\n\n##### 项目下的 gradle 文件\n\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f7763a9c6rj30aq0nhwhm.jpg)\n\n首先简单介绍下那个, yunpeng_cms_app_scx 是我的 app,其余是这个 app 所依赖的第三方模块。\n\n文件一: yunpeng_cms_app_scx/app/build.gradle\n\n这个文件是app文件夹下这个Module的gradle配置文件,也可以算是整个项目最主要的gradle配置文件。也是我们在编写 react native 应用时会修改的一个文件,当然有 rnpm 很多都是自动的。这个文件一般以 `apply plugin: \"com.android.application\"` 开头,而下面会提到的模块则是一般以 `apply plugin: \"com.android.library\"` 开头,原因也显而易见。\n\n这个文件其他的配置还很多,如 task,java版本信息,android sdk版本信息,签名等 ,但平时用到不多,常用的配置主要还是依赖的配置:\n\n```\ndependencies {\n// compile project(':react-native-yunpeng-amaplocation')\n compile project(':react-native-umeng-push')\n compile project(':react-native-yunpeng-datetime')\n compile project(':react-native-image-picker')\n compile project(':react-native-share')\n compile project(':react-native-linear-gradient')\n compile project(':react-native-amaplocating')\n compile project(':react-native-vector-icons')\n compile project(':react-native-yunpeng-alipay')\n compile project(':react-native-yyamap')\n compile fileTree(dir: \"libs\", include: [\"*.jar\"])\n compile \"com.android.support:appcompat-v7:23.0.1\"\n compile \"com.facebook.react:react-native:+\" // From node_modules\n}\n``` \n\n文件二:react-native-yunpeng-datetime/build.gradle\n\n这个就是上面提到的模块的 gradle 配置文件。每一个Module都需要有一个gradle配置文件,语法都是一样,唯一不同的是开头声明的是 apply plugin: 'com.android.library'。\n\n文件三:yunpeng_cms_app_scx/gradle/\n\n这个目录下有个 wrapper 文件夹,里面可以看到有两个文件,我们主要看下 gradle-wrapper.properties 这个文件的内容:\n\n```\n#Thu Dec 18 16:02:24 CST 2014\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.2.1-all.zip\n```\n\n可以看到里面声明了gradle的目录与下载路径以及当前项目使用的gradle版本,这些默认的路径我们一般不会更改的,这个文件里指明的gradle版本不对也是很多导包不成功的原因之一。\n\n文件四:yunpeng_cms_app_scx/build.gradle\n\n这个文件是整个项目的gradle基础配置文件,我们来看看这里面的内容\n\n```\n// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n repositories {\n jcenter()\n }\n dependencies {\n classpath 'com.android.tools.build:gradle:1.0.0'\n }\n}\n\nallprojects {\n repositories {\n jcenter()\n }\n}\n```\n\n内容主要包含了两个方面:一个是声明仓库的源,这里可以看到是指明的jcenter(), 之前版本则是mavenCentral(), jcenter可以理解成是一个新的中央远程仓库,兼容maven中心仓库,而且性能更优。另一个是声明了android gradle plugin的版本,android studio 1.0正式版必须要求支持gradle plugin 1.0的版本。不过实际使用中jenter很慢啊,mavenCentral相对来说快一点。\n\n另外就是这里的buildscript和allprojects都定义了repositories,他们的区别是,前者定义的gradle自身需要使用的资源,而后者是项目所需要的资源。\n\n文件五:yunpeng_cms_app_scx/setting.gradle\n\n这是在 react native 编写过程当中另一个需要修改的文件,这个文件是全局的项目配置文件,里面主要声明一些需要加入gradle的module,因此我们往往在添加插件时会要修改这个文件。不过在编写原生模块时要注意,要把模块代码放在android文件夹下,否则rnpm的自动链接是无效的。\n\n##### sqlite\n本来还有一篇 sqlite 的,但是这个网上资料很多,就把写模块过程中的文章记在这供以后回来回顾啦。后面就要回到rn的编写啦。\n\n资料:\n\n1. http://blog.sina.com.cn/s/blog_8191005601019so8.html\n2. http://blog.csdn.net/showdy/article/details/51578138\n3. http://blog.csdn.net/codeeer/article/details/30237597\n4. http://www.cnblogs.com/youxilua/archive/2011/09/18/2180654.html\n\n\n\n\n\n\n\n","html":"<p>这个系列文章是在编写<a href=\"https://github.com/yptech/react-native-yunpeng-amplocation\">react native 高德后台定位插件</a>的安卓部分时,使用到的一些原生安卓知识的记录。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=2742998&auto=1&height=66\"></iframe>\n\n<p>下午没事,思考和总结了下 flux 、 redux 相关的东西,爆炸了!还没有想得特别的清楚,先放松一下,把这个系列最后的一些部分写一下!</p>\n\n<h5 id=\"gradle\">Gradle 简介</h5>\n\n<p>一个构建工具,写过 javaWeb 的同学肯定知道另一个构建工具 Apache Maven,两者做的工作基本差不多,就是依赖管理、编译、打包等等工作,当然还有古老的 ant。不过这篇文章不会对这几者进行比较,这里的重点是 Android 和 Gradle。</p>\n\n<h5 id=\"gradle\">安装Gradle</h5>\n\n<p>在 Android Studio 中新建项目成功后会下载Gradle,貌似这个过程不翻墙也是可以下载,但是访问特别慢,建议翻墙下载。</p>\n\n<ul>\n<li>Mac上会默认下载到 /Users/<用户名>/.gradle/wrapper/dists 目录</li>\n<li>Win平台会默认下载到 C:\\Documents and Settings<用户名>.gradle\\wrapper\\dists 目录</li>\n</ul>\n\n<p>你会看到这个目录下有个 gradle-x.xx-all 的文件夹, 如果下载实在太慢,但是又不想翻墙的话,可以自己手动到Gradle官网下载对应的版本,然后将下载的.zip文件(也可以解压)复制到上述的gradle-x.xx-all 文件夹下,不过还是建议让它直接下载的好。</p>\n\n<h5 id=\"gradle\">项目下的 gradle 文件</h5>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f7763a9c6rj30aq0nhwhm.jpg\" alt=\"\" /></p>\n\n<p>首先简单介绍下那个, yunpeng<em>cms</em>app_scx 是我的 app,其余是这个 app 所依赖的第三方模块。</p>\n\n<p>文件一: yunpeng<em>cms</em>app_scx/app/build.gradle</p>\n\n<p>这个文件是app文件夹下这个Module的gradle配置文件,也可以算是整个项目最主要的gradle配置文件。也是我们在编写 react native 应用时会修改的一个文件,当然有 rnpm 很多都是自动的。这个文件一般以 <code>apply plugin: \"com.android.application\"</code> 开头,而下面会提到的模块则是一般以 <code>apply plugin: \"com.android.library\"</code> 开头,原因也显而易见。</p>\n\n<p>这个文件其他的配置还很多,如 task,java版本信息,android sdk版本信息,签名等 ,但平时用到不多,常用的配置主要还是依赖的配置:</p>\n\n<pre><code>dependencies { \n// compile project(':react-native-yunpeng-amaplocation')\n compile project(':react-native-umeng-push')\n compile project(':react-native-yunpeng-datetime')\n compile project(':react-native-image-picker')\n compile project(':react-native-share')\n compile project(':react-native-linear-gradient')\n compile project(':react-native-amaplocating')\n compile project(':react-native-vector-icons')\n compile project(':react-native-yunpeng-alipay')\n compile project(':react-native-yyamap')\n compile fileTree(dir: \"libs\", include: [\"*.jar\"])\n compile \"com.android.support:appcompat-v7:23.0.1\"\n compile \"com.facebook.react:react-native:+\" // From node_modules\n}\n</code></pre>\n\n<p>文件二:react-native-yunpeng-datetime/build.gradle</p>\n\n<p>这个就是上面提到的模块的 gradle 配置文件。每一个Module都需要有一个gradle配置文件,语法都是一样,唯一不同的是开头声明的是 apply plugin: 'com.android.library'。</p>\n\n<p>文件三:yunpeng<em>cms</em>app_scx/gradle/</p>\n\n<p>这个目录下有个 wrapper 文件夹,里面可以看到有两个文件,我们主要看下 gradle-wrapper.properties 这个文件的内容:</p>\n\n<pre><code>#Thu Dec 18 16:02:24 CST 2014\ndistributionBase=GRADLE_USER_HOME \ndistributionPath=wrapper/dists \nzipStoreBase=GRADLE_USER_HOME \nzipStorePath=wrapper/dists \ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.2.1-all.zip \n</code></pre>\n\n<p>可以看到里面声明了gradle的目录与下载路径以及当前项目使用的gradle版本,这些默认的路径我们一般不会更改的,这个文件里指明的gradle版本不对也是很多导包不成功的原因之一。</p>\n\n<p>文件四:yunpeng<em>cms</em>app_scx/build.gradle</p>\n\n<p>这个文件是整个项目的gradle基础配置文件,我们来看看这里面的内容</p>\n\n<pre><code>// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript { \n repositories {\n jcenter()\n }\n dependencies {\n classpath 'com.android.tools.build:gradle:1.0.0'\n }\n}\n\nallprojects { \n repositories {\n jcenter()\n }\n}\n</code></pre>\n\n<p>内容主要包含了两个方面:一个是声明仓库的源,这里可以看到是指明的jcenter(), 之前版本则是mavenCentral(), jcenter可以理解成是一个新的中央远程仓库,兼容maven中心仓库,而且性能更优。另一个是声明了android gradle plugin的版本,android studio 1.0正式版必须要求支持gradle plugin 1.0的版本。不过实际使用中jenter很慢啊,mavenCentral相对来说快一点。</p>\n\n<p>另外就是这里的buildscript和allprojects都定义了repositories,他们的区别是,前者定义的gradle自身需要使用的资源,而后者是项目所需要的资源。</p>\n\n<p>文件五:yunpeng<em>cms</em>app_scx/setting.gradle</p>\n\n<p>这是在 react native 编写过程当中另一个需要修改的文件,这个文件是全局的项目配置文件,里面主要声明一些需要加入gradle的module,因此我们往往在添加插件时会要修改这个文件。不过在编写原生模块时要注意,要把模块代码放在android文件夹下,否则rnpm的自动链接是无效的。</p>\n\n<h5 id=\"sqlite\">sqlite</h5>\n\n<p>本来还有一篇 sqlite 的,但是这个网上资料很多,就把写模块过程中的文章记在这供以后回来回顾啦。后面就要回到rn的编写啦。</p>\n\n<p>资料:</p>\n\n<ol>\n<li><a href=\"http://blog.sina.com.cn/s/blog_8191005601019so8.html\">http://blog.sina.com.cn/s/blog_8191005601019so8.html</a> </li>\n<li><a href=\"http://blog.csdn.net/showdy/article/details/51578138\">http://blog.csdn.net/showdy/article/details/51578138</a> </li>\n<li><a href=\"http://blog.csdn.net/codeeer/article/details/30237597\">http://blog.csdn.net/codeeer/article/details/30237597</a> </li>\n<li><a href=\"http://www.cnblogs.com/youxilua/archive/2011/09/18/2180654.html\">http://www.cnblogs.com/youxilua/archive/2011/09/18/2180654.html</a></li>\n</ol>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469611788552,"created_by":1,"updated_at":1472195568071,"updated_by":1,"published_at":1469625294015,"published_by":1},{"id":38,"uuid":"0df4ce60-891a-40bb-a8b3-ac2cfa62da34","title":"flux与设计模式","slug":"yi-ju-hua-she-ji-mo-shi","markdown":"##### 外观模式\n\n包装子系统接口,实现一个可统一调用的高层接口,举例来说:ie只支持attachEvent,而FF和Chrome只支持addEventListener,那么为了兼容性考虑,我们就可以封装一个高层的接口来屏蔽底层接口兼容性的问题。\n\n##### 观察者模式\n\n主要是一主题对多观察者的通信,多个观察者订阅一个主题,然后主题发生某种变化时,调用订阅的回调方法,修改观察者的状态,举例来说,多个求职者订阅了某个猎头,当有新的职位出现时,猎头会调用所有的订阅函数通知订阅的求职者。\n\n##### 中介者\n\n客户之间通信的问题。注意是客户间的通信,这是和观察者模式本质上的区别。\n\n##### flux\nreact 组件本身是一个状态机,state和props决定了组件最终的渲染,也就是中介者模式中的客户,一个组件状态的改变很可能影响到另一个组件,flux 所做的就是首先在改变的组件上触发一个 action ,这个 action 会被 Dispatcher 分发,并得到 store 的响应并触发一个 change 事件,然后所有订阅了这个事件的组件能够根据事件修改自身的状态,不过这里更像是观察者模式。","html":"<h5 id=\"\">外观模式</h5>\n\n<p>包装子系统接口,实现一个可统一调用的高层接口,举例来说:ie只支持attachEvent,而FF和Chrome只支持addEventListener,那么为了兼容性考虑,我们就可以封装一个高层的接口来屏蔽底层接口兼容性的问题。</p>\n\n<h5 id=\"\">观察者模式</h5>\n\n<p>主要是一主题对多观察者的通信,多个观察者订阅一个主题,然后主题发生某种变化时,调用订阅的回调方法,修改观察者的状态,举例来说,多个求职者订阅了某个猎头,当有新的职位出现时,猎头会调用所有的订阅函数通知订阅的求职者。</p>\n\n<h5 id=\"\">中介者</h5>\n\n<p>客户之间通信的问题。注意是客户间的通信,这是和观察者模式本质上的区别。</p>\n\n<h5 id=\"flux\">flux</h5>\n\n<p>react 组件本身是一个状态机,state和props决定了组件最终的渲染,也就是中介者模式中的客户,一个组件状态的改变很可能影响到另一个组件,flux 所做的就是首先在改变的组件上触发一个 action ,这个 action 会被 Dispatcher 分发,并得到 store 的响应并触发一个 change 事件,然后所有订阅了这个事件的组件能够根据事件修改自身的状态,不过这里更像是观察者模式。</p>","image":null,"featured":0,"page":0,"status":"draft","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469671882667,"created_by":1,"updated_at":1470925996662,"updated_by":1,"published_at":null,"published_by":null},{"id":39,"uuid":"3932a515-76f5-4594-aff1-ff63492da9d3","title":"随笔","slug":"sui-bi","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30635613&auto=1&height=66\"></iframe>\n\n打完球、吃了点东西、看了部东野圭吾小说改编的电影《麒麟之翼》、洗了个澡还木有弹吉他就已经10点了。\n\n没投几个球,手就酸了,还是要多运动运动啊!不过现在每周能够保持打球,还是有进步的哈!\n\n三天晚饭都是蛋黄粽加酸奶,有点腻了啊,明天老弟来我这玩,可以改善改善伙食了。\n\n东野圭吾小说改编的剧还是一如往常,不到最后不知所以,越到后面则越是让人感慨,到最后更是让人泪目。另外这一部的阵容意外的强大啊,宽叔、gakki、黑木明纱。。。尤其是宽叔,还是一如龙樱中的帅,和《嫌疑人X》中的福山雅治两种风格,都很让人喜欢。🤗\n\n虽然懒得洗衣服,还是洗掉了,因为衣架空出来了,好牵强的理由🤔。\n\n好了,扯到这,大半夜也不弹琴了,滚去看看能不能睡着😪。","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30635613&auto=1&height=66\"></iframe>\n\n<p>打完球、吃了点东西、看了部东野圭吾小说改编的电影《麒麟之翼》、洗了个澡还木有弹吉他就已经10点了。</p>\n\n<p>没投几个球,手就酸了,还是要多运动运动啊!不过现在每周能够保持打球,还是有进步的哈!</p>\n\n<p>三天晚饭都是蛋黄粽加酸奶,有点腻了啊,明天老弟来我这玩,可以改善改善伙食了。</p>\n\n<p>东野圭吾小说改编的剧还是一如往常,不到最后不知所以,越到后面则越是让人感慨,到最后更是让人泪目。另外这一部的阵容意外的强大啊,宽叔、gakki、黑木明纱。。。尤其是宽叔,还是一如龙樱中的帅,和《嫌疑人X》中的福山雅治两种风格,都很让人喜欢。🤗</p>\n\n<p>虽然懒得洗衣服,还是洗掉了,因为衣架空出来了,好牵强的理由🤔。</p>\n\n<p>好了,扯到这,大半夜也不弹琴了,滚去看看能不能睡着😪。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1469714938923,"created_by":1,"updated_at":1470283308375,"updated_by":1,"published_at":1469715620612,"published_by":1},{"id":40,"uuid":"489ce5b2-00a0-4fd0-a9b8-0028deaa2bf7","title":"致鱼头","slug":"zhi-yutou","markdown":"马上就要开学了,是时候给鱼头一个回复了。事实上这不光是给鱼头的回复,也是给自己大四生活的一个回复,所以这几天思考这个问题都没怎么睡好!\n\n废话不多说,直接上我的决定,然后再解释吧!我的决定包括两部分,首先是我决定接下来的一段时间闭关,不接任何项目不找任何实习;其次是关于闭关结束以后是否留在公司,我把这个决定交给公司还有我两个兄弟,和钱无关,也就是我不会要那5%的股份,如果公司能够在我闭关的这一两个月里让他们能够慢慢产生要留下来的想法,那么毕业前我之后会继续留在公司。毕业后的事我还不能够确定。\n\n接下来是我做出这个决定的原因:第一个部分是个人原因,我的大学三年有两年是在项目中度过的,前端、后端、移动端都有所涉及,但是始终没有一个主要的方向,也没有对于一个方向以专家为目标进行深层次的努力,这是我目前想要改善的东西,也是这次闭关所要做的事。第二部分是公司的原因,作为在公司呆了一年多的老人,看着原来的伙伴一个个离开,确实也有过离开的想法。所以第二个决定是我希望这段时间鱼头你能够想办法解决掉留不住人这个公司目前致命的问题,这个问题绝不是仅是钱的问题,而是从管理、个人发展等各个方面来处理的。不解决这个问题,我即使留下来,公司也不会有比较长远的发展。\n\n以上大致是我的回答!","html":"<p>马上就要开学了,是时候给鱼头一个回复了。事实上这不光是给鱼头的回复,也是给自己大四生活的一个回复,所以这几天思考这个问题都没怎么睡好!</p>\n\n<p>废话不多说,直接上我的决定,然后再解释吧!我的决定包括两部分,首先是我决定接下来的一段时间闭关,不接任何项目不找任何实习;其次是关于闭关结束以后是否留在公司,我把这个决定交给公司还有我两个兄弟,和钱无关,也就是我不会要那5%的股份,如果公司能够在我闭关的这一两个月里让他们能够慢慢产生要留下来的想法,那么毕业前我之后会继续留在公司。毕业后的事我还不能够确定。</p>\n\n<p>接下来是我做出这个决定的原因:第一个部分是个人原因,我的大学三年有两年是在项目中度过的,前端、后端、移动端都有所涉及,但是始终没有一个主要的方向,也没有对于一个方向以专家为目标进行深层次的努力,这是我目前想要改善的东西,也是这次闭关所要做的事。第二部分是公司的原因,作为在公司呆了一年多的老人,看着原来的伙伴一个个离开,确实也有过离开的想法。所以第二个决定是我希望这段时间鱼头你能够想办法解决掉留不住人这个公司目前致命的问题,这个问题绝不是仅是钱的问题,而是从管理、个人发展等各个方面来处理的。不解决这个问题,我即使留下来,公司也不会有比较长远的发展。</p>\n\n<p>以上大致是我的回答!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470044991675,"created_by":1,"updated_at":1473249380874,"updated_by":1,"published_at":1473246900301,"published_by":1},{"id":41,"uuid":"4df8c76c-4bf5-40d6-85ab-fcabeb3319f4","title":"有生之年系列——卡卡西露脸","slug":"you-sheng-zhi-nian-xi-lie-qia-qia-xi-lu-lian","markdown":"哈哈哈,打球回来看了最新一期的火影,卡卡西终于露脸了,此生无憾啊,必须发一波!顺便预告下,正翻译 MobX.js 中,可能会断更几天。\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=531925&auto=1&height=66\"></iframe>\n\n![](https://ws4.sinaimg.cn/large/006bH5BKgw1f7761harpyj31kw0zkwjl.jpg)\n\n![](https://ws3.sinaimg.cn/large/006bH5BKgw1f7761xl5icj31kw0zktng.jpg)\n\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f7762a3re3j31kw0zktpq.jpg)","html":"<p>哈哈哈,打球回来看了最新一期的火影,卡卡西终于露脸了,此生无憾啊,必须发一波!顺便预告下,正翻译 MobX.js 中,可能会断更几天。</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=531925&auto=1&height=66\"></iframe>\n\n<p><img src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f7761harpyj31kw0zkwjl.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/006bH5BKgw1f7761xl5icj31kw0zktng.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f7762a3re3j31kw0zktpq.jpg\" alt=\"\" /></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470222294757,"created_by":1,"updated_at":1472195505573,"updated_by":1,"published_at":1470222651618,"published_by":1},{"id":42,"uuid":"08b55319-bdd4-4f5e-a337-ce03dcf9c01e","title":"十分钟介绍MobX和React(译)","slug":"shi-fen-zhong-jie-shao-mobxhe-react-yi","markdown":"<style>\nhr {\n border: none;\n border-top: 1px dotted #999;\n width: 30%;\n margin: 10pt auto;\n}\n\n#project_title {\n font-family: 'Pacifico', cursive;\n font-size: 60pt;\n padding-top: 20pt;\n}\n#project_tagline {\n clear: left;\n font-family: 'arial';\n font-size: 18pt;\n text-align: center;\n padding-top: 16pt;\n}\n</style>\n<header>\n<a href=\"index.html\" style=\"float:left\">\n<img style=\"width: 120px; padding-right: 20px;\" src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f775zli3msj305k05k3yh.jpg\" id=\"logo\"></a>\n<h1 id=\"project_title\">MobX</h1>\n<h2 id=\"project_tagline\" style=\"font-size: 18pt\">Ten minute introduction to MobX and React</h2>\n<hr>\n</header>\n\nMobX 是一个简单的、可扩展的、经过实战检验的状态管理解决方案。这篇教程将会在十分钟内把 MobX 所有重要的概念教给你。MobX 是一个独立的库,但是大多数人正把他和 React 一起使用,因此本篇教程将关注两者如何一起工作。\n\n##### 核心思想\n\n状态是每个应用的心脏,产生不一致的状态或者状态与持久的本地变量不同步往往是导致应用漏洞多、难以管理最快的影响因素。因此产生了大量状态管理解决方案,这些方案限制你修改状态的方式,例如使状态不可变。但是这引入了新的问题;数据需要被规范化,参照完整性不再能够保证;它也使得像原型这样强大的概念不能够使用。\n\nMobX 通过解决核心问题,使状态管理再次变得简单:它使得应用不可能去产生一个不一致的状态。实现这一策略是简单的:*保证所有可以从应用状态派生的东西,都自动地被派生。*\n\n从概念上讲,MobX 对待你的应用就像对待一张电子表格。\n\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f7760acznkj30mi0geq4d.jpg)\n\n1. 首先,这里有应用状态。对象、数组、基础类型、引用类型的组合组成了你的应用模型。这些值就是你的应用的“数据单元”。\n2. 其次,这还有 *derivations*(推导)。从根本上来讲,任何值能够从你的应用状态上自动地被计算出来。这些被推导出来或者计算出来的值,可以是没有完成的 todos 的数量这类简单的值;也可以是代表你的 todos 的一段可渲染的 HTML 这类复杂的东西。用电子表格程序的术语,它就是你的应用的公式和图表。\n3. *Reactions*(响应)与 *derivations*(推导)类似。主要不同是响应的函数不产生新的值。相反,它会自动地运行来完成一些任务。通常是一些 I/O 相关的任务。它保证 DOM 更新和网络请求的自动触发都在正确的时间进行。\n4. 最后是 *actions*(动作)。 *actions* 是修改状态的唯一途径。MobX 将会确保所有应用状态的变动由 *actions* 产生,并自动地进行推导和响应。同步且没有干扰地。\n\n##### 一个简单的 todo store \n\n有了充足的理论,让我们用一个实际的例子来更全面地解释上面的东西。为了独创性,让我们从一个非常简单的 ToDo store 开始。下面是一个十分直白的 `TodoStore` ,它包邮一个 todos 的集合,还不涉及 MobX 。可以在[JSFiddle](https://jsfiddle.net/mweststrate/wv3yopo0/)或者[原文处](https://mobxjs.github.io/mobx/getting-started.html)运行代码修改代码。\n\n```\nclass TodoStore {\n\ttodos = [];\n\n\tget completedTodosCount() {\n \treturn this.todos.filter(\n\t\t\ttodo => todo.completed === true\n\t\t).length;\n }\n\n\treport() {\n\t\tif (this.todos.length === 0)\n\t\t\treturn \"<none>\";\n\t\treturn `Next todo: \"${this.todos[0].task}\". ` + \n\t\t\t`Progress: ${this.completedTodosCount}/${this.todos.length}`; \n\t}\n\n addTodo(task) {\n\t\tthis.todos.push({ \n\t\t\ttask: task,\n\t\t\tcompleted: false,\n assignee: null\n\t\t});\n\t}\n}\n\nconst todoStore = new TodoStore();\n```\n\n我们只是创建了一个带有 todo 集合的 `todoStore` 。是时候给 `todoStore` 加入一些对象了。为了确保能够看到改变的效果,我们在每个改变后调用 `todoStore.report` 方法,打印它。注意,这个打印过程总是有意地只输出第一条任务。它使得这个例子有点假,但是正如你下面将要看到的,它很漂亮地展示了 MobX 的追踪的动态性。\n\n```\ntodoStore.addTodo(\"read MobX tutorial\");\nconsole.log(todoStore.report());\n// Next todo: \"read MobX tutorial\". Progress: 0/1\n\ntodoStore.addTodo(\"try MobX\");\nconsole.log(todoStore.report());\n// Next todo: \"read MobX tutorial\". Progress: 0/2\n\ntodoStore.todos[0].completed = true;\nconsole.log(todoStore.report());\n// Next todo: \"read MobX tutorial\". Progress: 1/2\n\ntodoStore.todos[1].task = \"try MobX in own project\";\nconsole.log(todoStore.report());\n// Next todo: \"read MobX tutorial\". Progress: 1/2\n\ntodoStore.todos[0].task = \"grok MobX tutorial\";\nconsole.log(todoStore.report());\n// Next todo: \"grok MobX tutorial\". Progress: 1/2\n```\n\n##### 变成响应式\n\n到目前为止,这些代码还没有什么特殊的。但是如果我们不需要显式地去调用 `report` 方法,而只要声明什么需要在状态改变的时候被调用。这能够将我们从在每个可能会触发报告的地方调用 `report` 方法的则责任中解放出来。我们想要确保最新的报告被打印了,但是我们不想要被组织相关代码而干扰。\n\n幸运的是,这正是 MobX 能够为你做的。完全依据状态自动地执行代码。所以我们的 `report` 函数更新自动地,就像电子表格中的一张图表。为了达到这个目的,`TodoStore` 必须变得可观察,以至于 MobX 能够跟踪所有改变。让我们修改这个类,使它能够做到这一点。\n\n同时,`completedTodosCount` 属性能够被自动地从 todo 列表派生。通过使用 `@observable` 和 `@computed` 装饰器,我们能够在一个对象上引入可观察的属性。\n\n```\nclass ObservableTodoStore {\n\t@observable todos = [];\n @observable pendingRequests = 0;\n\n constructor() {\n mobx.autorun(() => console.log(this.report));\n }\n\n\t@computed get completedTodosCount() {\n \treturn this.todos.filter(\n\t\t\ttodo => todo.completed === true\n\t\t).length;\n }\n\n\t@computed get report() {\n\t\tif (this.todos.length === 0)\n\t\t\treturn \"<none>\";\n\t\treturn `Next todo: \"${this.todos[0].task}\". ` + \n\t\t\t`Progress: ${this.completedTodosCount}/${this.todos.length}`; \n\t}\n\n\taddTodo(task) {\n\t\tthis.todos.push({ \n\t\t\ttask: task,\n\t\t\tcompleted: false,\n\t\t\tassignee: null\n\t\t});\n\t}\n}\n\n\nconst observableTodoStore = new ObservableTodoStore();\n```\n\n就是这样!我们用 `@observable` 标记一些属性,使得 MobX能够知道这些值能够随着时间改变。计算方法用 `@computed` 装饰,来辨认那些可以从状态派生的值。\n\n`pendingRequests` 和 `assignee` 属性目前还未用到,将会在本教程后面部分使用。为了使所有的例子代码简洁,本教程使用 ES6、JSX 和 装饰器。但是不用担心过,所有的装饰器在 MobX中都有对应的 [ES5](https://mobxjs.github.io/mobx/best/syntax.html) 版本。\n\n在构造函数中,我们创建了一个小函数来打印 `report` 函数的返回值,并把它包裹在 `autorun` 函数当中。`autorun` 函数创建了一个只运行一次的响应,在这之后每当在该函数中使用的任何被观察数据改变时,这个响应都会被自动地触发。因为 `report` 函数使用了被观察的 `todos` 属性,他将会在适当的时候打印报告。这在下面的例子中展示,可以在[JSFiddle][原文处](https://mobxjs.github.io/mobx/getting-started.html)运行代码。\n\n```\nobservableTodoStore.addTodo(\"read MobX tutorial\");\n// Next todo: \"read MobX tutorial\". Progress: 0/1\nobservableTodoStore.addTodo(\"try MobX\");\n// Next todo: \"read MobX tutorial\". Progress: 0/2\nobservableTodoStore.todos[0].completed = true;\n// Next todo: \"read MobX tutorial\". Progress: 1/2\nobservableTodoStore.todos[1].task = \"try MobX in own project\";\nobservableTodoStore.todos[0].task = \"grok MobX tutorial\";\n// Next todo: \"grok MobX tutorial\". Progress: 1/2\n```\n\n`report` 函数返回值确实被自动地、同步地打印出来了,并且没有泄露中间值。如果你看日志比较仔细,你肯定看到了第四行没有触发报告的输出。因为 `report` 函数返回值被没有因为重命名的行为而真正的改变,虽然背后的数据变了。在另一方面,改变第一条 todo 的名字,更新了 `report` 函数返回值,因为这个名字在 `report` 函数中被使用。这很漂亮地演示了不仅仅是 `todos` 数组被 `autorun` 函数观察,todo 列表项中的私有属性也可以被观察。\n\n##### 使 React 响应式\n\n好了,到目前为止,我们做了一个无聊的报告响应例子。是时候用十分相似的 store 去构建一个响应式的用户界面了。\n\n##### 未完待续。。。\n","html":"<style> \nhr { \n border: none;\n border-top: 1px dotted #999;\n width: 30%;\n margin: 10pt auto;\n}\n\n#project_title {\n font-family: 'Pacifico', cursive;\n font-size: 60pt;\n padding-top: 20pt;\n}\n#project_tagline {\n clear: left;\n font-family: 'arial';\n font-size: 18pt;\n text-align: center;\n padding-top: 16pt;\n}\n</style> \n\n<header> \n<a href=\"index.html\" style=\"float:left\"> \n<img style=\"width: 120px; padding-right: 20px;\" src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f775zli3msj305k05k3yh.jpg\" id=\"logo\"></a> \n<h1 id=\"project_title\">MobX</h1> \n<h2 id=\"project_tagline\" style=\"font-size: 18pt\">Ten minute introduction to MobX and React</h2> \n<hr> \n</header>\n\n<p>MobX 是一个简单的、可扩展的、经过实战检验的状态管理解决方案。这篇教程将会在十分钟内把 MobX 所有重要的概念教给你。MobX 是一个独立的库,但是大多数人正把他和 React 一起使用,因此本篇教程将关注两者如何一起工作。</p>\n\n<h5 id=\"\">核心思想</h5>\n\n<p>状态是每个应用的心脏,产生不一致的状态或者状态与持久的本地变量不同步往往是导致应用漏洞多、难以管理最快的影响因素。因此产生了大量状态管理解决方案,这些方案限制你修改状态的方式,例如使状态不可变。但是这引入了新的问题;数据需要被规范化,参照完整性不再能够保证;它也使得像原型这样强大的概念不能够使用。</p>\n\n<p>MobX 通过解决核心问题,使状态管理再次变得简单:它使得应用不可能去产生一个不一致的状态。实现这一策略是简单的:<em>保证所有可以从应用状态派生的东西,都自动地被派生。</em></p>\n\n<p>从概念上讲,MobX 对待你的应用就像对待一张电子表格。</p>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f7760acznkj30mi0geq4d.jpg\" alt=\"\" /></p>\n\n<ol>\n<li>首先,这里有应用状态。对象、数组、基础类型、引用类型的组合组成了你的应用模型。这些值就是你的应用的“数据单元”。 </li>\n<li>其次,这还有 <em>derivations</em>(推导)。从根本上来讲,任何值能够从你的应用状态上自动地被计算出来。这些被推导出来或者计算出来的值,可以是没有完成的 todos 的数量这类简单的值;也可以是代表你的 todos 的一段可渲染的 HTML 这类复杂的东西。用电子表格程序的术语,它就是你的应用的公式和图表。 </li>\n<li><em>Reactions</em>(响应)与 <em>derivations</em>(推导)类似。主要不同是响应的函数不产生新的值。相反,它会自动地运行来完成一些任务。通常是一些 I/O 相关的任务。它保证 DOM 更新和网络请求的自动触发都在正确的时间进行。 </li>\n<li>最后是 <em>actions</em>(动作)。 <em>actions</em> 是修改状态的唯一途径。MobX 将会确保所有应用状态的变动由 <em>actions</em> 产生,并自动地进行推导和响应。同步且没有干扰地。</li>\n</ol>\n\n<h5 id=\"todostore\">一个简单的 todo store</h5>\n\n<p>有了充足的理论,让我们用一个实际的例子来更全面地解释上面的东西。为了独创性,让我们从一个非常简单的 ToDo store 开始。下面是一个十分直白的 <code>TodoStore</code> ,它包邮一个 todos 的集合,还不涉及 MobX 。可以在<a href=\"https://jsfiddle.net/mweststrate/wv3yopo0/\">JSFiddle</a>或者<a href=\"https://mobxjs.github.io/mobx/getting-started.html\">原文处</a>运行代码修改代码。</p>\n\n<pre><code>class TodoStore { \n todos = [];\n\n get completedTodosCount() {\n return this.todos.filter(\n todo => todo.completed === true\n ).length;\n }\n\n report() {\n if (this.todos.length === 0)\n return \"<none>\";\n return `Next todo: \"${this.todos[0].task}\". ` + \n `Progress: ${this.completedTodosCount}/${this.todos.length}`; \n }\n\n addTodo(task) {\n this.todos.push({ \n task: task,\n completed: false,\n assignee: null\n });\n }\n}\n\nconst todoStore = new TodoStore(); \n</code></pre>\n\n<p>我们只是创建了一个带有 todo 集合的 <code>todoStore</code> 。是时候给 <code>todoStore</code> 加入一些对象了。为了确保能够看到改变的效果,我们在每个改变后调用 <code>todoStore.report</code> 方法,打印它。注意,这个打印过程总是有意地只输出第一条任务。它使得这个例子有点假,但是正如你下面将要看到的,它很漂亮地展示了 MobX 的追踪的动态性。</p>\n\n<pre><code>todoStore.addTodo(\"read MobX tutorial\"); \nconsole.log(todoStore.report()); \n// Next todo: \"read MobX tutorial\". Progress: 0/1\n\ntodoStore.addTodo(\"try MobX\"); \nconsole.log(todoStore.report()); \n// Next todo: \"read MobX tutorial\". Progress: 0/2\n\ntodoStore.todos[0].completed = true; \nconsole.log(todoStore.report()); \n// Next todo: \"read MobX tutorial\". Progress: 1/2\n\ntodoStore.todos[1].task = \"try MobX in own project\"; \nconsole.log(todoStore.report()); \n// Next todo: \"read MobX tutorial\". Progress: 1/2\n\ntodoStore.todos[0].task = \"grok MobX tutorial\"; \nconsole.log(todoStore.report()); \n// Next todo: \"grok MobX tutorial\". Progress: 1/2\n</code></pre>\n\n<h5 id=\"\">变成响应式</h5>\n\n<p>到目前为止,这些代码还没有什么特殊的。但是如果我们不需要显式地去调用 <code>report</code> 方法,而只要声明什么需要在状态改变的时候被调用。这能够将我们从在每个可能会触发报告的地方调用 <code>report</code> 方法的则责任中解放出来。我们想要确保最新的报告被打印了,但是我们不想要被组织相关代码而干扰。</p>\n\n<p>幸运的是,这正是 MobX 能够为你做的。完全依据状态自动地执行代码。所以我们的 <code>report</code> 函数更新自动地,就像电子表格中的一张图表。为了达到这个目的,<code>TodoStore</code> 必须变得可观察,以至于 MobX 能够跟踪所有改变。让我们修改这个类,使它能够做到这一点。</p>\n\n<p>同时,<code>completedTodosCount</code> 属性能够被自动地从 todo 列表派生。通过使用 <code>@observable</code> 和 <code>@computed</code> 装饰器,我们能够在一个对象上引入可观察的属性。</p>\n\n<pre><code>class ObservableTodoStore { \n @observable todos = [];\n @observable pendingRequests = 0;\n\n constructor() {\n mobx.autorun(() => console.log(this.report));\n }\n\n @computed get completedTodosCount() {\n return this.todos.filter(\n todo => todo.completed === true\n ).length;\n }\n\n @computed get report() {\n if (this.todos.length === 0)\n return \"<none>\";\n return `Next todo: \"${this.todos[0].task}\". ` + \n `Progress: ${this.completedTodosCount}/${this.todos.length}`; \n }\n\n addTodo(task) {\n this.todos.push({ \n task: task,\n completed: false,\n assignee: null\n });\n }\n}\n\n\nconst observableTodoStore = new ObservableTodoStore(); \n</code></pre>\n\n<p>就是这样!我们用 <code>@observable</code> 标记一些属性,使得 MobX能够知道这些值能够随着时间改变。计算方法用 <code>@computed</code> 装饰,来辨认那些可以从状态派生的值。</p>\n\n<p><code>pendingRequests</code> 和 <code>assignee</code> 属性目前还未用到,将会在本教程后面部分使用。为了使所有的例子代码简洁,本教程使用 ES6、JSX 和 装饰器。但是不用担心过,所有的装饰器在 MobX中都有对应的 <a href=\"https://mobxjs.github.io/mobx/best/syntax.html\">ES5</a> 版本。</p>\n\n<p>在构造函数中,我们创建了一个小函数来打印 <code>report</code> 函数的返回值,并把它包裹在 <code>autorun</code> 函数当中。<code>autorun</code> 函数创建了一个只运行一次的响应,在这之后每当在该函数中使用的任何被观察数据改变时,这个响应都会被自动地触发。因为 <code>report</code> 函数使用了被观察的 <code>todos</code> 属性,他将会在适当的时候打印报告。这在下面的例子中展示,可以在[JSFiddle]<a href=\"https://mobxjs.github.io/mobx/getting-started.html\">原文处</a>运行代码。</p>\n\n<pre><code>observableTodoStore.addTodo(\"read MobX tutorial\"); \n// Next todo: \"read MobX tutorial\". Progress: 0/1\nobservableTodoStore.addTodo(\"try MobX\"); \n// Next todo: \"read MobX tutorial\". Progress: 0/2\nobservableTodoStore.todos[0].completed = true; \n// Next todo: \"read MobX tutorial\". Progress: 1/2\nobservableTodoStore.todos[1].task = \"try MobX in own project\"; \nobservableTodoStore.todos[0].task = \"grok MobX tutorial\"; \n// Next todo: \"grok MobX tutorial\". Progress: 1/2\n</code></pre>\n\n<p><code>report</code> 函数返回值确实被自动地、同步地打印出来了,并且没有泄露中间值。如果你看日志比较仔细,你肯定看到了第四行没有触发报告的输出。因为 <code>report</code> 函数返回值被没有因为重命名的行为而真正的改变,虽然背后的数据变了。在另一方面,改变第一条 todo 的名字,更新了 <code>report</code> 函数返回值,因为这个名字在 <code>report</code> 函数中被使用。这很漂亮地演示了不仅仅是 <code>todos</code> 数组被 <code>autorun</code> 函数观察,todo 列表项中的私有属性也可以被观察。</p>\n\n<h5 id=\"react\">使 React 响应式</h5>\n\n<p>好了,到目前为止,我们做了一个无聊的报告响应例子。是时候用十分相似的 store 去构建一个响应式的用户界面了。</p>\n\n<h5 id=\"\">未完待续。。。</h5>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470231539886,"created_by":1,"updated_at":1472195389840,"updated_by":1,"published_at":1470231894955,"published_by":1},{"id":43,"uuid":"3b2adc01-8ea0-4bc1-85da-adfaaa286f01","title":"网站备案中","slug":"wang-zhan-bei-an-zhong","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30814948&auto=1&height=66\"></iframe>\n\n刚刚收到阿里云的提醒,为了保证网站能够一直正常的访问,需要备下案,虽然麻烦但是已经提交了备案申请,期间可能会出现无法访问的情况😂,见谅。不过备案成功了就不用带着端口号访问了😀!","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30814948&auto=1&height=66\"></iframe>\n\n<p>刚刚收到阿里云的提醒,为了保证网站能够一直正常的访问,需要备下案,虽然麻烦但是已经提交了备案申请,期间可能会出现无法访问的情况😂,见谅。不过备案成功了就不用带着端口号访问了😀!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470294695518,"created_by":1,"updated_at":1470294921676,"updated_by":1,"published_at":1470294781906,"published_by":1},{"id":44,"uuid":"f50fdad1-3b8c-4f77-8269-68a96d35b7d3","title":"有生之年系列——生日","slug":"you-sheng-zhi-nian-xi-lie-sheng-ri","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30814948&auto=1&height=66\"></iframe>\n\n虽然生日在周日,但是因为明天要回家,星爷也马上要回家了,所以今天请星爷、兵哥哥还有鸣鸣吃了饭,聚了下,也算是提前过了生日。\n\n很久没有这么过生日了,很高兴,谢谢星爷的蛋糕、军哥哥的GitHub T恤,鸣鸣的全程直播,还有傻奶奶的红包!感谢有你们陪我度过这个生日!便便也不要生气,下次会补回来的,但是你要戒网瘾哇!🤗\n\n马上大四了,将要迎来又一次的离别,但是人生本就是如此一次次的别离而构成的!哈哈,要开心,不要伤感!😅\n\n许愿的时候没多想,就是希望大家都幸福!说出来会不会不灵了🤔!睡了,晚安,世界!\n\n","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=30814948&auto=1&height=66\"></iframe>\n\n<p>虽然生日在周日,但是因为明天要回家,星爷也马上要回家了,所以今天请星爷、兵哥哥还有鸣鸣吃了饭,聚了下,也算是提前过了生日。</p>\n\n<p>很久没有这么过生日了,很高兴,谢谢星爷的蛋糕、军哥哥的GitHub T恤,鸣鸣的全程直播,还有傻奶奶的红包!感谢有你们陪我度过这个生日!便便也不要生气,下次会补回来的,但是你要戒网瘾哇!🤗</p>\n\n<p>马上大四了,将要迎来又一次的离别,但是人生本就是如此一次次的别离而构成的!哈哈,要开心,不要伤感!😅</p>\n\n<p>许愿的时候没多想,就是希望大家都幸福!说出来会不会不灵了🤔!睡了,晚安,世界!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470404187519,"created_by":1,"updated_at":1470405320116,"updated_by":1,"published_at":1470404914579,"published_by":1},{"id":45,"uuid":"8fa7990c-f296-4073-ad83-577e01c3eaf1","title":"有生之年系列——面试","slug":"you-sheng-zhi-nian-xi-lie-zhi-mian-shi","markdown":"今天参加了人生的第一场面试,没什么经验,没有做什么准备,想展示最真实的自身状态,是伯乐自然会相中。🤗\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=28786397&auto=1&height=66\"></iframe>\n\n首先是一个黑衣的大叔,有点资历的,先是和他简单探讨了一些现在的情况,这一部分还是比较顺利的。然后就开始了做题,第一题是 cookie 和 storage 的区别,这就尴尬了,写了那么多应用,没有 cookie 也活的好好的我自然是答不太好,然后是一些 http 及 dom 的知识,基本也是死伤惨重,因为大部分前端的经验来自于 supersonic 或是 react native。我讲 react native 的场景,他讲前端场景,两个人都有点 get 不到对方的点,所以感觉有些不妙。大叔也意识到这点,然后开始问了一些我比较擅长的部分,js 异步、redux 等,虽然语言组织的一般,但是还是能够讲得比较全面的。\n\n然后换了一个白衣小哥,先让我自我介绍,于是照着简历上的目录介绍了一番。可是这小哥便抓住了期望职位和发展方向问了不少问题,这可能是我多写了点,导致他多想了吧,后面还是把这玩意拿掉吧。然后是我觉得本次面试收获最大的一点,我到底想写什么,面试当场的我有些犹豫,但回去路上包括现在,我认真思考过后,觉得自己会以 react native 、 weex 这类的移动开发为暂时的主要方向,把网收小,专注一点。选择这个方向主要是我看好基于 virtual dom 的这种跨平台的应用开发方式,它必将在之后几年改变整个移动端开发的格局,另外就是自己在这个方面经验和理论都比较丰富,如果今天面的是这方面的职位那结果应该不会是这样了吧。\n\n今天面试的主要失败在于对自己定位不清,选择了一个自己不擅长的职位,说了一些不该说的话,以至于把自己的缺点暴露的太明显,而优势则没有体现出来。\n\n总结一下收获:第一、找准方向,继续努力;第二、面试技巧上要提高,要诱导面试官往自己优势的地方看;第三、对问题的研究要更加深入\n\n小牢骚:🙄最后就是要吐槽这家公司面试太敷衍,没有看过我的博客,没有看过我的开源项目,没有看过一行我写的代码,即使给了我下次的机会,我想我也不会去了吧!另外我并不觉得自己只是学了一些高大上表面的东西,我能够把他们用得很好,处理复杂的业务场景,甚至能在一定基础上创造一些东西,在体验和性能上也绝不会比面试公司的人做出来的东西差。\n","html":"<p>今天参加了人生的第一场面试,没什么经验,没有做什么准备,想展示最真实的自身状态,是伯乐自然会相中。🤗</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=28786397&auto=1&height=66\"></iframe>\n\n<p>首先是一个黑衣的大叔,有点资历的,先是和他简单探讨了一些现在的情况,这一部分还是比较顺利的。然后就开始了做题,第一题是 cookie 和 storage 的区别,这就尴尬了,写了那么多应用,没有 cookie 也活的好好的我自然是答不太好,然后是一些 http 及 dom 的知识,基本也是死伤惨重,因为大部分前端的经验来自于 supersonic 或是 react native。我讲 react native 的场景,他讲前端场景,两个人都有点 get 不到对方的点,所以感觉有些不妙。大叔也意识到这点,然后开始问了一些我比较擅长的部分,js 异步、redux 等,虽然语言组织的一般,但是还是能够讲得比较全面的。</p>\n\n<p>然后换了一个白衣小哥,先让我自我介绍,于是照着简历上的目录介绍了一番。可是这小哥便抓住了期望职位和发展方向问了不少问题,这可能是我多写了点,导致他多想了吧,后面还是把这玩意拿掉吧。然后是我觉得本次面试收获最大的一点,我到底想写什么,面试当场的我有些犹豫,但回去路上包括现在,我认真思考过后,觉得自己会以 react native 、 weex 这类的移动开发为暂时的主要方向,把网收小,专注一点。选择这个方向主要是我看好基于 virtual dom 的这种跨平台的应用开发方式,它必将在之后几年改变整个移动端开发的格局,另外就是自己在这个方面经验和理论都比较丰富,如果今天面的是这方面的职位那结果应该不会是这样了吧。</p>\n\n<p>今天面试的主要失败在于对自己定位不清,选择了一个自己不擅长的职位,说了一些不该说的话,以至于把自己的缺点暴露的太明显,而优势则没有体现出来。</p>\n\n<p>总结一下收获:第一、找准方向,继续努力;第二、面试技巧上要提高,要诱导面试官往自己优势的地方看;第三、对问题的研究要更加深入</p>\n\n<p>小牢骚:🙄最后就是要吐槽这家公司面试太敷衍,没有看过我的博客,没有看过我的开源项目,没有看过一行我写的代码,即使给了我下次的机会,我想我也不会去了吧!另外我并不觉得自己只是学了一些高大上表面的东西,我能够把他们用得很好,处理复杂的业务场景,甚至能在一定基础上创造一些东西,在体验和性能上也绝不会比面试公司的人做出来的东西差。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1470838177557,"created_by":1,"updated_at":1471146581368,"updated_by":1,"published_at":1470840846607,"published_by":1},{"id":46,"uuid":"009b885b-f44e-4942-a915-1757cf8cb480","title":"react native 踩坑实录续集","slug":"react-native-cai-keng-shi-lu-er","markdown":"前面的踩坑实录已经记录比较多了,而且翻过去也比较麻烦,所以新开一篇续集,更加精彩哦!😜\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=442031&auto=1&height=66\"></iframe>\n\n1、 [Android] FormData fails to send data in multipart/form-data(简单来说就是 android 下用 fetch 上传图片时存在问题)\n\n这个问题困扰了我很久,今天在 github 找到了相对满意的答案,[5308](https://github.com/facebook/react-native/issues/5308)。简单来讲就是在使用 FormData 时不需要再设置 header 中的 Content-Type 为 multipart/form-data,因为这是 FormData 会为我们做的事。另外本人在测试中还发现的事去掉 Content-Type 后, base64 格式图片还是无法正确上传的,而直接上传图片是可以的。这部分的原因需要更加深入的而研究!\n\n2、 Android 和 IOS 的 WebView 在处理没有 DOCTYPE 以及其他的 html、body 标签的情况,而只拥有 h1 这类标签时,有着不一样的规则 其中 Android 能够正常解析,IOS 解析异常,建议手动加上 html 文件必要的这些标签,来解决这个问题。\n","html":"<p>前面的踩坑实录已经记录比较多了,而且翻过去也比较麻烦,所以新开一篇续集,更加精彩哦!😜</p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=442031&auto=1&height=66\"></iframe>\n\n<p>1、 [Android] FormData fails to send data in multipart/form-data(简单来说就是 android 下用 fetch 上传图片时存在问题)</p>\n\n<p>这个问题困扰了我很久,今天在 github 找到了相对满意的答案,<a href=\"https://github.com/facebook/react-native/issues/5308\">5308</a>。简单来讲就是在使用 FormData 时不需要再设置 header 中的 Content-Type 为 multipart/form-data,因为这是 FormData 会为我们做的事。另外本人在测试中还发现的事去掉 Content-Type 后, base64 格式图片还是无法正确上传的,而直接上传图片是可以的。这部分的原因需要更加深入的而研究!</p>\n\n<p>2、 Android 和 IOS 的 WebView 在处理没有 DOCTYPE 以及其他的 html、body 标签的情况,而只拥有 h1 这类标签时,有着不一样的规则 其中 Android 能够正常解析,IOS 解析异常,建议手动加上 html 文件必要的这些标签,来解决这个问题。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":"","author_id":1,"created_at":1470925501002,"created_by":1,"updated_at":1471262300828,"updated_by":1,"published_at":1470925890042,"published_by":1},{"id":47,"uuid":"5791cb6f-5f21-477f-998d-0bde0859e898","title":"转:Cookie与Session的区别-总结很好的文章","slug":"zhuan-cookieyu-sessionde-qu-bie-zong-jie-hen-hao-de-wen-zhang","markdown":"[猛戳原文](http://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=504090000&idx=3&sn=f57d4f194c902daadd80296d5b8ed001#rd)\n\n虽然写了这么多项目,因为应用的场景有限、应用的并发有限,所以基本使用的都是 Session,今天在百川的公众号上看到这篇文章,觉得总结地很好,转过来给自己给大家补补知识!\n\n##### cookie机制\n\nCookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies 。\n\n具体来说cookie机制采用的是在客户端保持状态的方案。它是在用户端的会话状态的存贮机制,他需要用户打开客户端的cookie支持。cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。\n\n正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。\n\ncookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。\n\n而session机制采用的是一种在服务器端保持状态的解决方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。而session提供了方便管理全局变量的方式 。\n\nsession是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分是哪个用户session变量,这个值是通过用户的浏览器在访问的时候返回给服务器,当客户禁用cookie时,这个值也可能设置为由get来返回给服务器。\n\n就安全性来说:当你访问一个使用session 的站点,同时在自己机子上建立一个cookie,建议在服务器端的session机制更安全些,因为它不会任意读取客户存储的信息。\n\n##### session机制\n\nsession机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。\n\n当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。\n\n保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。\n\n经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。\n\nCookie与Session都能够进行会话跟踪,但是完成的原理不太一样。普通状况下二者均能够满足需求,但有时分不能够运用Cookie,有时分不能够运用Session。下面经过比拟阐明二者的特性以及适用的场所。\n\n1、存取方式的不同\n\nCookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二进制数据,需求先进行编码。Cookie中也不能直接存取Java对象。若要存储略微复杂的信息,运用Cookie是比拟艰难的。\n\n而Session中能够存取任何类型的数据,包括而不限于String、Integer、List、Map等。Session中也能够直接保管Java Bean乃至任何Java类,对象等,运用起来十分便当。能够把Session看做是一个Java容器类。\n\n2、隐私策略的不同\n\nCookie存储在客户端阅读器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以至修正Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。\n\n假如选用Cookie,比较好的方法是,敏感的信息如账号密码等尽量不要写到Cookie中。最好是像Google、Baidu那样将Cookie信息加密,提交到服务器后再进行解密,保证Cookie中的信息只要本人能读得懂。而假如选择Session就省事多了,反正是放在服务器上,Session里任何隐私都能够有效的保护。\n\n3、有效期上的不同\n\n使用过Google的人都晓得,假如登录过Google,则Google的登录信息长期有效。用户不用每次访问都重新登录,Google会持久地记载该用户的登录信息。要到达这种效果,运用Cookie会是比较好的选择。只需要设置Cookie的过期时间属性为一个很大很大的数字。\n\n由于Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过期时间默许为–1,只需关闭了阅读器该Session就会失效,因而Session不能完成信息永世有效的效果。运用URL地址重写也不能完成。而且假如设置Session的超时时间过长,服务器累计的Session就会越多,越容易招致内存溢出。\n\n4、服务器压力的不同\n\nSession是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。因而像Google、Baidu、Sina这样并发访问量极高的网站,是不太可能运用Session来追踪客户会话的。\n\n而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。关于Google、Baidu、Sina来说,Cookie或许是唯一的选择。\n\n5、浏览器支持的不同\n\nCookie是需要客户端浏览器支持的。假如客户端禁用了Cookie,或者不支持Cookie,则会话跟踪会失效。关于WAP上的应用,常规的Cookie就派不上用场了。\n\n假如客户端浏览器不支持Cookie,需要运用Session以及URL地址重写。需要注意的是一切的用到Session程序的URL都要进行URL地址重写,否则Session会话跟踪还会失效。关于WAP应用来说,Session+URL地址重写或许是它唯一的选择。\n\n假如客户端支持Cookie,则Cookie既能够设为本浏览器窗口以及子窗口内有效(把过期时间设为–1),也能够设为一切阅读器窗口内有效(把过期时间设为某个大于0的整数)。但Session只能在本阅读器窗口以及其子窗口内有效。假如两个浏览器窗口互不相干,它们将运用两个不同的Session。(IE8下不同窗口Session相干)\n\n6、跨域支持上的不同\n\nCookie支持跨域名访问,例如将domain属性设置为“.biaodianfu.com”,则以“.biaodianfu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中,例如Google、Baidu、Sina等。而Session则不会支持跨域名访问。Session仅在他所在的域名内有效。\n\n仅运用Cookie或者仅运用Session可能完成不了理想的效果。这时应该尝试一下同时运用Cookie与Session。Cookie与Session的搭配运用在实践项目中会完成很多意想不到的效果。","html":"<p><a href=\"http://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=504090000&idx=3&sn=f57d4f194c902daadd80296d5b8ed001#rd\">猛戳原文</a></p>\n\n<p>虽然写了这么多项目,因为应用的场景有限、应用的并发有限,所以基本使用的都是 Session,今天在百川的公众号上看到这篇文章,觉得总结地很好,转过来给自己给大家补补知识!</p>\n\n<h5 id=\"cookie\">cookie机制</h5>\n\n<p>Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies 。</p>\n\n<p>具体来说cookie机制采用的是在客户端保持状态的方案。它是在用户端的会话状态的存贮机制,他需要用户打开客户端的cookie支持。cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。</p>\n\n<p>正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。</p>\n\n<p>cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。</p>\n\n<p>而session机制采用的是一种在服务器端保持状态的解决方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。而session提供了方便管理全局变量的方式 。\n\nsession是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分是哪个用户session变量,这个值是通过用户的浏览器在访问的时候返回给服务器,当客户禁用cookie时,这个值也可能设置为由get来返回给服务器。</p>\n\n<p>就安全性来说:当你访问一个使用session 的站点,同时在自己机子上建立一个cookie,建议在服务器端的session机制更安全些,因为它不会任意读取客户存储的信息。</p>\n\n<h5 id=\"session\">session机制</h5>\n\n<p>session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。</p>\n\n<p>当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。</p>\n\n<p>保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。</p>\n\n<p>经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。</p>\n\n<p>Cookie与Session都能够进行会话跟踪,但是完成的原理不太一样。普通状况下二者均能够满足需求,但有时分不能够运用Cookie,有时分不能够运用Session。下面经过比拟阐明二者的特性以及适用的场所。</p>\n\n<p>1、存取方式的不同</p>\n\n<p>Cookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二进制数据,需求先进行编码。Cookie中也不能直接存取Java对象。若要存储略微复杂的信息,运用Cookie是比拟艰难的。</p>\n\n<p>而Session中能够存取任何类型的数据,包括而不限于String、Integer、List、Map等。Session中也能够直接保管Java Bean乃至任何Java类,对象等,运用起来十分便当。能够把Session看做是一个Java容器类。</p>\n\n<p>2、隐私策略的不同</p>\n\n<p>Cookie存储在客户端阅读器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以至修正Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。</p>\n\n<p>假如选用Cookie,比较好的方法是,敏感的信息如账号密码等尽量不要写到Cookie中。最好是像Google、Baidu那样将Cookie信息加密,提交到服务器后再进行解密,保证Cookie中的信息只要本人能读得懂。而假如选择Session就省事多了,反正是放在服务器上,Session里任何隐私都能够有效的保护。</p>\n\n<p>3、有效期上的不同</p>\n\n<p>使用过Google的人都晓得,假如登录过Google,则Google的登录信息长期有效。用户不用每次访问都重新登录,Google会持久地记载该用户的登录信息。要到达这种效果,运用Cookie会是比较好的选择。只需要设置Cookie的过期时间属性为一个很大很大的数字。</p>\n\n<p>由于Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过期时间默许为–1,只需关闭了阅读器该Session就会失效,因而Session不能完成信息永世有效的效果。运用URL地址重写也不能完成。而且假如设置Session的超时时间过长,服务器累计的Session就会越多,越容易招致内存溢出。</p>\n\n<p>4、服务器压力的不同</p>\n\n<p>Session是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。因而像Google、Baidu、Sina这样并发访问量极高的网站,是不太可能运用Session来追踪客户会话的。</p>\n\n<p>而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。关于Google、Baidu、Sina来说,Cookie或许是唯一的选择。</p>\n\n<p>5、浏览器支持的不同</p>\n\n<p>Cookie是需要客户端浏览器支持的。假如客户端禁用了Cookie,或者不支持Cookie,则会话跟踪会失效。关于WAP上的应用,常规的Cookie就派不上用场了。</p>\n\n<p>假如客户端浏览器不支持Cookie,需要运用Session以及URL地址重写。需要注意的是一切的用到Session程序的URL都要进行URL地址重写,否则Session会话跟踪还会失效。关于WAP应用来说,Session+URL地址重写或许是它唯一的选择。</p>\n\n<p>假如客户端支持Cookie,则Cookie既能够设为本浏览器窗口以及子窗口内有效(把过期时间设为–1),也能够设为一切阅读器窗口内有效(把过期时间设为某个大于0的整数)。但Session只能在本阅读器窗口以及其子窗口内有效。假如两个浏览器窗口互不相干,它们将运用两个不同的Session。(IE8下不同窗口Session相干)</p>\n\n<p>6、跨域支持上的不同</p>\n\n<p>Cookie支持跨域名访问,例如将domain属性设置为“.biaodianfu.com”,则以“.biaodianfu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中,例如Google、Baidu、Sina等。而Session则不会支持跨域名访问。Session仅在他所在的域名内有效。</p>\n\n<p>仅运用Cookie或者仅运用Session可能完成不了理想的效果。这时应该尝试一下同时运用Cookie与Session。Cookie与Session的搭配运用在实践项目中会完成很多意想不到的效果。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471071021724,"created_by":1,"updated_at":1471071706555,"updated_by":1,"published_at":1471071283539,"published_by":1},{"id":48,"uuid":"69cc2b15-2e63-45ce-b878-3c7bdfdd9a55","title":"有生之年系列——护国神翼","slug":"you-sheng-zhi-nian-xi-lie-zhi-hu-guo-shen-yi","markdown":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=4209157&auto=1&height=66\"></iframe>\n\n作为一个多年的老 Dotaer,虽然不玩 Dota2 而且也很久没有玩 Dota 这款游戏了,但是还是十分关注本届的 Ti 。看了一早上,我们的护国神翼终于翻盘 3:1 拿下了本届 Ti 的冠军。虽然护国神翼这个名字有些又爱又恨的味道,但是这一刻,他们值得这么叫。\n\n![](https://ws4.sinaimg.cn/large/006bH5BKgw1f775vnmgbqj30dw08pwgf.jpg)\n\n这是一只年轻的队伍,和去年的 CDEC 很像,但是但又很不同,因为我在这支队伍上看到了不凡的创造力,在国内这样的竞技环境下,他们能够保持这样一种创造力实在让人惊讶。于创造力之外,他们又显得十分的稳重,在先失一局的情况下,连下三局,拿下冠军。完全不像是几个比我可能还小的男孩所做的事,但是看到这些真的很高兴,我们的创造力、实力是完全不输给欧美的。\n\n![](https://ws4.sinaimg.cn/large/006bH5BKgw1f775vnlqolj30dw08pq6m.jpg)\n\n当然车长老和 DC 老师以及弹幕们的毒奶也是功不可没,O(∩_∩)O哈哈~尤其是当最后一句,Wings 在拿下一场团战胜利,扭转胜负的时候,DC 老师果断阻止了车长老的毒奶,真是笑死了!那一刻的弹幕就是冷静啊,车长老😂。\n\n再来谈谈 DC,之前看了他和 EHome 的比赛,从 BP 上就能看出这是一只同样具备灵性及韧性的队伍,能够干倒 EG 冲回来确实也令人刮目相看!\n\n最后希望拿到冠军的几个小伙子能够保持这样的一种创造力和团队,在明年给我们带来新的惊喜!\n\n![](https://ws2.sinaimg.cn/large/006bH5BKgw1f775vnlt8kj30dw08p0ww.jpg)","html":"<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=4209157&auto=1&height=66\"></iframe>\n\n<p>作为一个多年的老 Dotaer,虽然不玩 Dota2 而且也很久没有玩 Dota 这款游戏了,但是还是十分关注本届的 Ti 。看了一早上,我们的护国神翼终于翻盘 3:1 拿下了本届 Ti 的冠军。虽然护国神翼这个名字有些又爱又恨的味道,但是这一刻,他们值得这么叫。</p>\n\n<p><img src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f775vnmgbqj30dw08pwgf.jpg\" alt=\"\" /></p>\n\n<p>这是一只年轻的队伍,和去年的 CDEC 很像,但是但又很不同,因为我在这支队伍上看到了不凡的创造力,在国内这样的竞技环境下,他们能够保持这样一种创造力实在让人惊讶。于创造力之外,他们又显得十分的稳重,在先失一局的情况下,连下三局,拿下冠军。完全不像是几个比我可能还小的男孩所做的事,但是看到这些真的很高兴,我们的创造力、实力是完全不输给欧美的。</p>\n\n<p><img src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f775vnlqolj30dw08pq6m.jpg\" alt=\"\" /></p>\n\n<p>当然车长老和 DC 老师以及弹幕们的毒奶也是功不可没,O(∩_∩)O哈哈~尤其是当最后一句,Wings 在拿下一场团战胜利,扭转胜负的时候,DC 老师果断阻止了车长老的毒奶,真是笑死了!那一刻的弹幕就是冷静啊,车长老😂。</p>\n\n<p>再来谈谈 DC,之前看了他和 EHome 的比赛,从 BP 上就能看出这是一只同样具备灵性及韧性的队伍,能够干倒 EG 冲回来确实也令人刮目相看!</p>\n\n<p>最后希望拿到冠军的几个小伙子能够保持这样的一种创造力和团队,在明年给我们带来新的惊喜!</p>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/006bH5BKgw1f775vnlt8kj30dw08p0ww.jpg\" alt=\"\" /></p>","image":"/content/images/2016/08/17185891_1200x1000_0-1.jpg","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471146558750,"created_by":1,"updated_at":1472195202663,"updated_by":1,"published_at":1471147979226,"published_by":1},{"id":49,"uuid":"e0b8e7ae-089d-4506-9d08-696fba564fd0","title":"http 缓存","slug":"http-huan-cun","markdown":"怎么说呢,这块知识说重要但是往往不是我们直接需要做的,因此很容易在实际的项目中所忽略。毕竟像 Last-Modified 以及 Etag 这些缓存的策略大部分的服务器已经帮我们实现了,拿比较熟悉的 Tomcat 来说,Tomcat 自带了 DefaultServlet,用来处理静态资源的缓存问题。但是了解了 http 缓存我们还是能够做更多的事的,比如启用 Cache-Control ,这样静态资源得到的状态码将是 200(from cache)而不是 304,可以减少 http 请求。\n\n讲了了一些废话,那就开始慢慢介绍 http 缓存的一些基础知识!\n![](https://ws4.sinaimg.cn/large/006bH5BKgw1f775tm5vg3j30fe0eojsk.jpg)\n\n##### Pragma 和 Expires\n\n这是 http 1.0 时代,关于缓存的两个用来控制缓存的字段。\n\nPragma 的启用是通过添加如下的信息到 http 文件头部,来禁用客户端缓存该资源,主要是页面资源。\n\n```\n<meta http-equiv=\"Pragma\" content=\"no-cache\">\n```\n\n但是使用时存在一些注意点,首先只有 IE 支持这个 meta 属性,其次是往往需要放到 body 后面[点我点我](https://support.microsoft.com/zh-cn/kb/222064)。总的来说这种客户端定义Pragma的形式基本没起到多少作用。而在响应头部中加入这个字段,反而能够禁用缓存生效。\n\n有了 Pragma 来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对http 1.0 而言,Expires 就是做这件事的首部字段。\n\nExpires 的值对应一个GMT(格林尼治时间),比如“Mon, 22 Jul 2002 11:12:01 GMT”来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。\n\n在客户端我们同样可以使用meta标签来知会IE(也仅有IE能识别)页面(同样也只对页面有效,对页面上的资源无效)缓存时间:\n\n```\n<meta http-equiv=\"expires\" content=\"mon, 18 apr 2016 14:30:00 GMT\">\n```\n\n如果希望在IE下页面不走缓存,希望每次刷新页面都能发新请求,那么可以把“content”里的值写为“-1”或“0”。\n\n注意的是该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段。如果是在服务端报头返回Expires字段,则在任何浏览器中都能正确设置资源缓存的时间。\n\n另外在优先级上 Pragma 要高于 Expires,并且 Expires 存在的劣势也很明显,就是客户端和服务器时间的不一致可能导致问题。\n\n实践:为了向下兼容 http 1.0 的标准,还是有很多网站会使用这两个字段的,正确的使用姿势也是在服务端向 response header 中设置对应的参数。\n\n##### Cache-Control\n\n针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。\n\nCache-Control 是一个通用首部,有很多取值,这里主要介绍几个常用的值,其余的可以查阅 RFC 2616 文档。\n\n>值可以是public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age\n>\n各个消息中的指令含义如下:\n>\nPublic指示响应可被任何缓存区缓存。\nPrivate指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。\nno-cache指示请求或响应消息不能缓存,该选项并不是说可以设置”不缓存“,容易望文生义~\nno-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存,完全不存下來。\nmax-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。\nmin-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。\nmax-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。\n\n实践,针对页面资源,主要还是要根据页面的更新速度,如果经常变化则可以不用缓存或者设置一个较短的过期时间。腾讯首页选择的是设置 max-age=60,较短时间的缓存,而百度则是使用 private(简要介绍一下private,首先这是默认值,在地址栏回车或后退键是不会重新请求的,刷新或者第一次访问时才会请求)。而针对静态资源,一般可以设置一个较长的缓存时间,百度设置的是 30 天,部分资源会达到一年,不过在工程化的前端当中静态资源的更新是一个比较常见的场景,这会在之后的文章中有所涉及。\n\n##### Last-Modified 和 Etag\n\n服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。\n\n客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。\n\n至于传递标记起来的最终修改时间的请求报文首部字段一共有两个:\n\n1、 If-Modified-Since: Last-Modified-value\n\n示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT\n该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。\n\n2、 If-Unmodified-Since: Last-Modified-value\n\n告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。\n\n当遇到下面情况时,If-Unmodified-Since 字段会被忽略:\n\n1. Last-Modified值对上了(资源在服务端没有新的修改);\n2. 服务端需返回2XX和412之外的状态码;\n3. 传来的指定日期不合法\n\nLast-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。\n\n为了解决上面的这个问题,http 1.1 又提出了Etag。服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。\n\n客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。\n\n如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。\n\n那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值:\n\n1、If-None-Match: ETag-value\n\n示例为 If-None-Match: \"56fcccc8-1699\"\n告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。\n\n当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。\n\n2、If-Match: ETag-value\n\n告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。\n\nIf-Match 的一个应用场景是,客户端走PUT方法向服务端请求上传/更替资源,这时候可以通过 If-Match 传递资源的ETag。\n\n需要注意的是,如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。\n\nLast-Modified 和 Etag 很好,但是配合上面提到的三个东西一起使用,会有更好的效果,因为对于一些资源,完全可以使用 200(from cache),而不是重新去请求,通过 304 在做,这样可以减少大量的不必要的 http 请求。\n\n##### 不同用户行为的影响\n\n<table>\n<thead>\n<tr>\n<td>用户操作</td>\n<td>Expires/Cache-Control</td>\n<td>Last-Modified/Etag</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>地址栏回车</td>\n<td>有效</td>\n<td>有效</td>\n</tr>\n<tr>\n<td>页面链接跳转</td>\n<td>有效</td>\n<td>有效</td>\n</tr>\n<tr>\n<td>新开窗口</td>\n<td>有效</td>\n<td>有效</td>\n</tr>\n<tr>\n<td>前进、后退</td>\n<td>有效</td>\n<td>有效</td>\n</tr>\n<tr>\n<td>F5/按钮刷新</td>\n<td>无效(BR重置max-age=0)</td>\n<td>有效</td>\n</tr>\n<tr>\n<td>Ctrl+F5刷新</td>\n<td>无效(重置CC=no-cache)</td>\n<td>无效(请求头丢弃该选项)</td>\n</tr>\n</tbody>\n</table>\n\n##### 总结\n\n在服务器为我们实现了 Last-Modified 和 Etag 的基础上,我们需要对自己的静态资源以及页面资源进行缓存的设置。对于静态资源可以设置一个较长时间的缓存,而对于页面的缓存可以根据页面的更新频次进行精确地控制,具体的策略我也需要更多的实践来进行试验。当然写了这么多很多东西还是没有涉及的,有兴趣可以再回去啃啃 http 1.1 的 RFC 文档,虽然看起来一点都不好啃。最后不得不吐槽下 ghost 博客的 markdown 支持太差了!🙄\n\n参考资料:\n\n1、 [浅谈浏览器http的缓存机制](http://www.cnblogs.com/vajoy/p/5341664.html)\n","html":"<p>怎么说呢,这块知识说重要但是往往不是我们直接需要做的,因此很容易在实际的项目中所忽略。毕竟像 Last-Modified 以及 Etag 这些缓存的策略大部分的服务器已经帮我们实现了,拿比较熟悉的 Tomcat 来说,Tomcat 自带了 DefaultServlet,用来处理静态资源的缓存问题。但是了解了 http 缓存我们还是能够做更多的事的,比如启用 Cache-Control ,这样静态资源得到的状态码将是 200(from cache)而不是 304,可以减少 http 请求。</p>\n\n<p>讲了了一些废话,那就开始慢慢介绍 http 缓存的一些基础知识!\n<img src=\"https://ws4.sinaimg.cn/large/006bH5BKgw1f775tm5vg3j30fe0eojsk.jpg\" alt=\"\" /></p>\n\n<h5 id=\"pragmaexpires\">Pragma 和 Expires</h5>\n\n<p>这是 http 1.0 时代,关于缓存的两个用来控制缓存的字段。</p>\n\n<p>Pragma 的启用是通过添加如下的信息到 http 文件头部,来禁用客户端缓存该资源,主要是页面资源。</p>\n\n<pre><code><meta http-equiv=\"Pragma\" content=\"no-cache\"> \n</code></pre>\n\n<p>但是使用时存在一些注意点,首先只有 IE 支持这个 meta 属性,其次是往往需要放到 body 后面<a href=\"https://support.microsoft.com/zh-cn/kb/222064\">点我点我</a>。总的来说这种客户端定义Pragma的形式基本没起到多少作用。而在响应头部中加入这个字段,反而能够禁用缓存生效。</p>\n\n<p>有了 Pragma 来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对http 1.0 而言,Expires 就是做这件事的首部字段。</p>\n\n<p>Expires 的值对应一个GMT(格林尼治时间),比如“Mon, 22 Jul 2002 11:12:01 GMT”来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。</p>\n\n<p>在客户端我们同样可以使用meta标签来知会IE(也仅有IE能识别)页面(同样也只对页面有效,对页面上的资源无效)缓存时间:</p>\n\n<pre><code><meta http-equiv=\"expires\" content=\"mon, 18 apr 2016 14:30:00 GMT\"> \n</code></pre>\n\n<p>如果希望在IE下页面不走缓存,希望每次刷新页面都能发新请求,那么可以把“content”里的值写为“-1”或“0”。</p>\n\n<p>注意的是该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段。如果是在服务端报头返回Expires字段,则在任何浏览器中都能正确设置资源缓存的时间。</p>\n\n<p>另外在优先级上 Pragma 要高于 Expires,并且 Expires 存在的劣势也很明显,就是客户端和服务器时间的不一致可能导致问题。</p>\n\n<p>实践:为了向下兼容 http 1.0 的标准,还是有很多网站会使用这两个字段的,正确的使用姿势也是在服务端向 response header 中设置对应的参数。</p>\n\n<h5 id=\"cachecontrol\">Cache-Control</h5>\n\n<p>针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。</p>\n\n<p>Cache-Control 是一个通用首部,有很多取值,这里主要介绍几个常用的值,其余的可以查阅 RFC 2616 文档。</p>\n\n<blockquote>\n <p>值可以是public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age</p>\n \n <p>各个消息中的指令含义如下:</p>\n \n <p>Public指示响应可被任何缓存区缓存。 <br />\n Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。 <br />\n no-cache指示请求或响应消息不能缓存,该选项并不是说可以设置”不缓存“,容易望文生义~ <br />\n no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存,完全不存下來。 <br />\n max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。 <br />\n min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。 <br />\n max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。</p>\n</blockquote>\n\n<p>实践,针对页面资源,主要还是要根据页面的更新速度,如果经常变化则可以不用缓存或者设置一个较短的过期时间。腾讯首页选择的是设置 max-age=60,较短时间的缓存,而百度则是使用 private(简要介绍一下private,首先这是默认值,在地址栏回车或后退键是不会重新请求的,刷新或者第一次访问时才会请求)。而针对静态资源,一般可以设置一个较长的缓存时间,百度设置的是 30 天,部分资源会达到一年,不过在工程化的前端当中静态资源的更新是一个比较常见的场景,这会在之后的文章中有所涉及。</p>\n\n<h5 id=\"lastmodifiedetag\">Last-Modified 和 Etag</h5>\n\n<p>服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。</p>\n\n<p>客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。</p>\n\n<p>至于传递标记起来的最终修改时间的请求报文首部字段一共有两个:</p>\n\n<p>1、 If-Modified-Since: Last-Modified-value</p>\n\n<p>示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT\n该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。</p>\n\n<p>2、 If-Unmodified-Since: Last-Modified-value</p>\n\n<p>告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。</p>\n\n<p>当遇到下面情况时,If-Unmodified-Since 字段会被忽略:</p>\n\n<ol>\n<li>Last-Modified值对上了(资源在服务端没有新的修改); </li>\n<li>服务端需返回2XX和412之外的状态码; </li>\n<li>传来的指定日期不合法</li>\n</ol>\n\n<p>Last-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。</p>\n\n<p>为了解决上面的这个问题,http 1.1 又提出了Etag。服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。</p>\n\n<p>客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。</p>\n\n<p>如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。</p>\n\n<p>那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值:</p>\n\n<p>1、If-None-Match: ETag-value</p>\n\n<p>示例为 If-None-Match: \"56fcccc8-1699\"\n告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。</p>\n\n<p>当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。</p>\n\n<p>2、If-Match: ETag-value</p>\n\n<p>告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。</p>\n\n<p>If-Match 的一个应用场景是,客户端走PUT方法向服务端请求上传/更替资源,这时候可以通过 If-Match 传递资源的ETag。</p>\n\n<p>需要注意的是,如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。</p>\n\n<p>Last-Modified 和 Etag 很好,但是配合上面提到的三个东西一起使用,会有更好的效果,因为对于一些资源,完全可以使用 200(from cache),而不是重新去请求,通过 304 在做,这样可以减少大量的不必要的 http 请求。</p>\n\n<h5 id=\"\">不同用户行为的影响</h5>\n\n<table> \n<thead> \n<tr> \n<td>用户操作</td> \n<td>Expires/Cache-Control</td> \n<td>Last-Modified/Etag</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>地址栏回车</td> \n<td>有效</td> \n<td>有效</td> \n</tr> \n<tr> \n<td>页面链接跳转</td> \n<td>有效</td> \n<td>有效</td> \n</tr> \n<tr> \n<td>新开窗口</td> \n<td>有效</td> \n<td>有效</td> \n</tr> \n<tr> \n<td>前进、后退</td> \n<td>有效</td> \n<td>有效</td> \n</tr> \n<tr> \n<td>F5/按钮刷新</td> \n<td>无效(BR重置max-age=0)</td> \n<td>有效</td> \n</tr> \n<tr> \n<td>Ctrl+F5刷新</td> \n<td>无效(重置CC=no-cache)</td> \n<td>无效(请求头丢弃该选项)</td> \n</tr> \n</tbody> \n</table>\n\n<h5 id=\"\">总结</h5>\n\n<p>在服务器为我们实现了 Last-Modified 和 Etag 的基础上,我们需要对自己的静态资源以及页面资源进行缓存的设置。对于静态资源可以设置一个较长时间的缓存,而对于页面的缓存可以根据页面的更新频次进行精确地控制,具体的策略我也需要更多的实践来进行试验。当然写了这么多很多东西还是没有涉及的,有兴趣可以再回去啃啃 http 1.1 的 RFC 文档,虽然看起来一点都不好啃。最后不得不吐槽下 ghost 博客的 markdown 支持太差了!🙄</p>\n\n<p>参考资料:</p>\n\n<p>1、 <a href=\"http://www.cnblogs.com/vajoy/p/5341664.html\">浅谈浏览器http的缓存机制</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471156822874,"created_by":1,"updated_at":1472195008893,"updated_by":1,"published_at":1471164888546,"published_by":1},{"id":50,"uuid":"7b3a5b8f-2231-4013-bed4-c073406dec87","title":"随笔","slug":"shenlun","markdown":"晚上做完客回到家已是 11 点,洗完澡看了这周的火影已经是 12 点,本来想分享点关于今天看的 《css揭秘》一书中的 css 历史及草案制定的内容的,但是感觉还需要看一遍,再找点资料,才能写的好点,所以放到明天。但还是要写点什么的,养成习惯了,毕竟这不仅仅是一个单纯的技术博客,所以扯点别的也无所谓,金色梦乡嘛!!\n\n晚上饭后,爸妈和叔叔阿姨讨论着那些未来的事,大致也就是考研啊、工作啊什么的!听了很多遍了,也烦了,就管自己看新歌声周董队的 pk 了!\n\n对于已经放弃保研的我来说,好像都关系不大了。为什么要放弃保研?我是不是傻!🤔\n\n简单讲讲我的想法吧,首先,我本身对于本科阶段三年的接受的教学是失望的,我所学的只是大多来自自发的学习和实践。这也是目前普通大学的普遍现象,清华北大是否如此我不知道。面对这个现象,我大致可以有两个选择,一是进入更好的学府一探究竟;第二是借助我目前的能力投入更丰富的实践,为这个问题提出申论,甚至和一群志同道合的同学一起为解决这个问题而努力。我选择了后者,因为意义更大!\n\n另外一个原因是,我能够拿到保研名额,一部分功劳是来自于导师发的论文的。在这篇论文与我毫无干系,并不能提现我大学期间所做的事,我不想靠着与我无关的东西保研。我也并不觉得论文、奖项比我参与过的项目、开源的代码要来得有分量。所以可能从我大学期间的方向来看也并不适合读研。\n\n第三点是我的疑问,就是读研与工作,哪一个能够真正地提升我的能力和视野,而不是工作的身价。钱固然重要,但是够了就好,我很知足!我觉得就我目前接受的教育水平来看,读研能够带给我的很有限,当然不包括所有大学的教育!\n\n最后回到第一点申论的话题,这是一个很喜欢的歌手张悬在14年一席的演讲上提到的。她大致讲到,我们这个世代的年轻人,需要找到一个关于自身或者社会问题困惑的申论的方式。我很有感触,但是申论如何提出,如何得到更多人的响应,如何去做实验,拿出一个相对更好的方案,这是我所在思考的!我想借助现有的力量做出这样一个东西,可能是小众的,但是绝对是意义非凡的!帮助我们这个世代的年轻人能够真正团结起来为解决自身的困惑而努力,不碌碌一生!\n\n一点了,就先写到这,熬夜都长痘痘了!😓外链不支持 https,外链暂时就不放了,只放个连接![一点点——周董](http://music.163.com/#/song?id=418603076),因为晚上新声音看到周董,所以回来听了它的新专辑,这首挺喜欢😊。","html":"<p>晚上做完客回到家已是 11 点,洗完澡看了这周的火影已经是 12 点,本来想分享点关于今天看的 《css揭秘》一书中的 css 历史及草案制定的内容的,但是感觉还需要看一遍,再找点资料,才能写的好点,所以放到明天。但还是要写点什么的,养成习惯了,毕竟这不仅仅是一个单纯的技术博客,所以扯点别的也无所谓,金色梦乡嘛!!</p>\n\n<p>晚上饭后,爸妈和叔叔阿姨讨论着那些未来的事,大致也就是考研啊、工作啊什么的!听了很多遍了,也烦了,就管自己看新歌声周董队的 pk 了!</p>\n\n<p>对于已经放弃保研的我来说,好像都关系不大了。为什么要放弃保研?我是不是傻!🤔</p>\n\n<p>简单讲讲我的想法吧,首先,我本身对于本科阶段三年的接受的教学是失望的,我所学的只是大多来自自发的学习和实践。这也是目前普通大学的普遍现象,清华北大是否如此我不知道。面对这个现象,我大致可以有两个选择,一是进入更好的学府一探究竟;第二是借助我目前的能力投入更丰富的实践,为这个问题提出申论,甚至和一群志同道合的同学一起为解决这个问题而努力。我选择了后者,因为意义更大!</p>\n\n<p>另外一个原因是,我能够拿到保研名额,一部分功劳是来自于导师发的论文的。在这篇论文与我毫无干系,并不能提现我大学期间所做的事,我不想靠着与我无关的东西保研。我也并不觉得论文、奖项比我参与过的项目、开源的代码要来得有分量。所以可能从我大学期间的方向来看也并不适合读研。</p>\n\n<p>第三点是我的疑问,就是读研与工作,哪一个能够真正地提升我的能力和视野,而不是工作的身价。钱固然重要,但是够了就好,我很知足!我觉得就我目前接受的教育水平来看,读研能够带给我的很有限,当然不包括所有大学的教育!</p>\n\n<p>最后回到第一点申论的话题,这是一个很喜欢的歌手张悬在14年一席的演讲上提到的。她大致讲到,我们这个世代的年轻人,需要找到一个关于自身或者社会问题困惑的申论的方式。我很有感触,但是申论如何提出,如何得到更多人的响应,如何去做实验,拿出一个相对更好的方案,这是我所在思考的!我想借助现有的力量做出这样一个东西,可能是小众的,但是绝对是意义非凡的!帮助我们这个世代的年轻人能够真正团结起来为解决自身的困惑而努力,不碌碌一生!</p>\n\n<p>一点了,就先写到这,熬夜都长痘痘了!😓外链不支持 https,外链暂时就不放了,只放个连接!<a href=\"http://music.163.com/#/song?id=418603076\">一点点——周董</a>,因为晚上新声音看到周董,所以回来听了它的新专辑,这首挺喜欢😊。</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471245064499,"created_by":1,"updated_at":1472318288846,"updated_by":1,"published_at":1472317683444,"published_by":1},{"id":51,"uuid":"31dbfd34-fafb-452f-a108-a2c81e166de7","title":"金色梦乡","slug":"jin-se-meng-xiang","markdown":"备案上交管局也有一周了吧,但是还毫无音讯啊🙄。提交备案时选择的名称是“金色梦乡”,The Beatles 的一首歌,亦是雅人叔的一部电影。当时还没有看雅人叔的这部电影,仅是看了影评,也有另一个选择,小葵演的“编舟记”。\n\n“编舟记”之意是这个博客是我不断成长的小舟,而我的成长之路亦是编舟之路,不断积累的过程。从这层意思上来讲,“编舟记”也不失为一个恰当的题目。但是最终我还是莫名其妙的选择了“金色梦乡”,这个更加温润的题目。今晚看了雅人叔的 10 年的这部代表作,有种感觉,我是选对了啊!\n\n从披头士讲起吧,之前听披头士是高一的时候了吧,Yesterday、Hey Jude,还有那首充满回忆的 Let It Be。记得还学唱过 Yesterday;记得班长唱 hey jude 很好听;记得教室里放着这首歌,然后和同桌调侃着 let it be 好像方言中一句骂人的话,都是回忆啊!不过不记得当时是否听过 Golden Slumbers 这首了,不过 Golden Slumbers 之于片中的几人,就像 let it be 之于我和同桌吧。\n\n不矫情,继续!《Golden Slumbers》选自Beatles 1969年的专辑《Abbey Road》。虽然从时间上来看,《Let It Be》是发表于1970年,但实际上69年的时候 Beatles 已经走向分裂,而《Abbey Road》才是Beatles 真正的告别。 Golden Slumbers 是保罗麦卡特尼创作的,对四人过去岁月的一种诗意的怀念。\n\n而影片中刚好也是四个人,四个人虽然已经分开过上了各自的生活,但是他们的生活通过青柳的这件事再次联系起来。不管他们的现状如何,在这件事中,他们回忆起的过往都是那么的美妙,他们也没有真正的背叛过青柳。加上剧本的戏剧性,我甚至想要设想这是青柳的一个梦,青柳的金色梦乡。梦中的那一切不幸就像是大学毕业后进入社会,遭受到的束缚与挫折;面对这些,朋友家人的回忆在青柳的脑中构建出了以种种力量,帮助他逃离!\n\n这力量告诉他:听好青柳!快逃!不管多么凄惨!总之要逃走!活下去!能活下来是最好的!快逃啊!青柳!\n\n快逃啊!青柳!脑洞大了点,也不符合影片的细节,但是我想要这么解读。不服打我呀!😜\n\n影片感动、温润的地方还很多,就不更多据透露了!而这个博客之于我,绝不仅仅是一个技术博客那么简单,它记录了我美好的点点滴滴,像个梦但是却又那么的真实。另外还有牵强的一点,就是外部的力量,也就是青柳从朋友、家人甚至陌生人身上得到的力量,这是我不想忽略的东西。\n\n😂12点半了,不行了,不写了,关机睡觉!\n\n<embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=2696215&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed>\n\n","html":"<p>备案上交管局也有一周了吧,但是还毫无音讯啊🙄。提交备案时选择的名称是“金色梦乡”,The Beatles 的一首歌,亦是雅人叔的一部电影。当时还没有看雅人叔的这部电影,仅是看了影评,也有另一个选择,小葵演的“编舟记”。</p>\n\n<p>“编舟记”之意是这个博客是我不断成长的小舟,而我的成长之路亦是编舟之路,不断积累的过程。从这层意思上来讲,“编舟记”也不失为一个恰当的题目。但是最终我还是莫名其妙的选择了“金色梦乡”,这个更加温润的题目。今晚看了雅人叔的 10 年的这部代表作,有种感觉,我是选对了啊!</p>\n\n<p>从披头士讲起吧,之前听披头士是高一的时候了吧,Yesterday、Hey Jude,还有那首充满回忆的 Let It Be。记得还学唱过 Yesterday;记得班长唱 hey jude 很好听;记得教室里放着这首歌,然后和同桌调侃着 let it be 好像方言中一句骂人的话,都是回忆啊!不过不记得当时是否听过 Golden Slumbers 这首了,不过 Golden Slumbers 之于片中的几人,就像 let it be 之于我和同桌吧。</p>\n\n<p>不矫情,继续!《Golden Slumbers》选自Beatles 1969年的专辑《Abbey Road》。虽然从时间上来看,《Let It Be》是发表于1970年,但实际上69年的时候 Beatles 已经走向分裂,而《Abbey Road》才是Beatles 真正的告别。 Golden Slumbers 是保罗麦卡特尼创作的,对四人过去岁月的一种诗意的怀念。</p>\n\n<p>而影片中刚好也是四个人,四个人虽然已经分开过上了各自的生活,但是他们的生活通过青柳的这件事再次联系起来。不管他们的现状如何,在这件事中,他们回忆起的过往都是那么的美妙,他们也没有真正的背叛过青柳。加上剧本的戏剧性,我甚至想要设想这是青柳的一个梦,青柳的金色梦乡。梦中的那一切不幸就像是大学毕业后进入社会,遭受到的束缚与挫折;面对这些,朋友家人的回忆在青柳的脑中构建出了以种种力量,帮助他逃离!</p>\n\n<p>这力量告诉他:听好青柳!快逃!不管多么凄惨!总之要逃走!活下去!能活下来是最好的!快逃啊!青柳!</p>\n\n<p>快逃啊!青柳!脑洞大了点,也不符合影片的细节,但是我想要这么解读。不服打我呀!😜</p>\n\n<p>影片感动、温润的地方还很多,就不更多据透露了!而这个博客之于我,绝不仅仅是一个技术博客那么简单,它记录了我美好的点点滴滴,像个梦但是却又那么的真实。另外还有牵强的一点,就是外部的力量,也就是青柳从朋友、家人甚至陌生人身上得到的力量,这是我不想忽略的东西。</p>\n\n<p>😂12点半了,不行了,不写了,关机睡觉!</p>\n\n<p><embed height=\"415\" width=\"544\" quality=\"high\" allowfullscreen=\"true\" type=\"application/x-shockwave-flash\" src=\"http://static.hdslb.com/miniloader.swf\" flashvars=\"aid=2696215&page=1\" pluginspage=\"http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash\"></embed></p>","image":"/content/images/2016/08/d1160924ab18972b76de0bb1e6cd7b899f510ae0.jpg","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471361067936,"created_by":1,"updated_at":1471365533527,"updated_by":1,"published_at":1471365183323,"published_by":1},{"id":52,"uuid":"05bcaa58-2e47-4f49-9397-a11fe66598bd","title":"git 代码合并:Merge、Rebase的选择","slug":"git-xiao-jie","markdown":"[原文](https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase%E7%9A%84%E9%80%89%E6%8B%A9)\n\n因为原文已经写得很清楚了,所以我就不再写一遍了,仅仅是总结一下。\n\n##### git Merge \n\n- 优势:一个安全的操作,现有的分支不会被更改,可跟踪;\n- 劣势:合并时会引入外来提交,或多或少会污染你的分支历史。\n\n##### git Rebase\n\n- 优势:项目历史会非常整洁;\n- 劣势:安全性和可跟踪性。\n\n##### 应用场景(最佳实践)\n\n什么时候使用 Rebase?(符合黄金法则)\n\n黄金法则:绝不要在公共的分支上使用它。个人总结了一下就是下面几个场景符合黄金法则:\n\n1. 一个人的项目,只有自己提交;\n2. 交互式 Rebase,用于清理本地的提交(原文中有);\n3. 这个分支只有你在工作(不光是主分支,如果一个特性分支上多人在提交,也不能使用 Rebase)。\n\n除了以上几个场景,我认为都建议用 Merge。另外,扯点其他的,首先就是养成比较好的习惯,上班第一件事 pull;其次,和同事沟通好;另外就是尽量先 fetch,再 merge,而不是直接pull,可控一点。\n\n参考资料:\n\n1. [Git Rebase原理以及黄金准则详解](https://segmentfault.com/a/1190000005937408)\n2. [Git 分支 - 分支的衍合](https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E8%A1%8D%E5%90%88)","html":"<p><a href=\"https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase%E7%9A%84%E9%80%89%E6%8B%A9\">原文</a></p>\n\n<p>因为原文已经写得很清楚了,所以我就不再写一遍了,仅仅是总结一下。</p>\n\n<h5 id=\"gitmerge\">git Merge</h5>\n\n<ul>\n<li>优势:一个安全的操作,现有的分支不会被更改,可跟踪;</li>\n<li>劣势:合并时会引入外来提交,或多或少会污染你的分支历史。</li>\n</ul>\n\n<h5 id=\"gitrebase\">git Rebase</h5>\n\n<ul>\n<li>优势:项目历史会非常整洁;</li>\n<li>劣势:安全性和可跟踪性。</li>\n</ul>\n\n<h5 id=\"\">应用场景(最佳实践)</h5>\n\n<p>什么时候使用 Rebase?(符合黄金法则)</p>\n\n<p>黄金法则:绝不要在公共的分支上使用它。个人总结了一下就是下面几个场景符合黄金法则:</p>\n\n<ol>\n<li>一个人的项目,只有自己提交; </li>\n<li>交互式 Rebase,用于清理本地的提交(原文中有); </li>\n<li>这个分支只有你在工作(不光是主分支,如果一个特性分支上多人在提交,也不能使用 Rebase)。</li>\n</ol>\n\n<p>除了以上几个场景,我认为都建议用 Merge。另外,扯点其他的,首先就是养成比较好的习惯,上班第一件事 pull;其次,和同事沟通好;另外就是尽量先 fetch,再 merge,而不是直接pull,可控一点。</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"https://segmentfault.com/a/1190000005937408\">Git Rebase原理以及黄金准则详解</a> </li>\n<li><a href=\"https://git-scm.com/book/zh/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E8%A1%8D%E5%90%88\">Git 分支 - 分支的衍合</a></li>\n</ol>","image":"/content/images/2016/08/1-e-tlWqLwbUd1UmZyC_KbGg.png","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471421090540,"created_by":1,"updated_at":1471422860787,"updated_by":1,"published_at":1471422860788,"published_by":1},{"id":53,"uuid":"2aa68f9f-c0b4-4742-8778-42d3156bee1d","title":"备案成功😀","slug":"bei-an-cheng-gong-2","markdown":"早上管局终于来了电话,然后问了几个问题,备案成功的短信就收到了!没想到等了半个月,最后就这么敷衍的成功了!以后就直接访问 https://m2mbob.cn 就行了,原来的端口号,我看看配置下 nginx 能不能成功!😎\n\nnginx 配置 https 失败,证书搞了半天居然没用!!!😷 \n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=28784417&auto=1&height=66\"></iframe>","html":"<p>早上管局终于来了电话,然后问了几个问题,备案成功的短信就收到了!没想到等了半个月,最后就这么敷衍的成功了!以后就直接访问 <a href=\"https://m2mbob.cn\">https://m2mbob.cn</a> 就行了,原来的端口号,我看看配置下 nginx 能不能成功!😎</p>\n\n<p>nginx 配置 https 失败,证书搞了半天居然没用!!!😷 </p>\n\n<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"http://music.163.com/outchain/player?type=2&id=28784417&auto=1&height=66\"></iframe>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1471917700169,"created_by":1,"updated_at":1472194478509,"updated_by":1,"published_at":1471917809949,"published_by":1},{"id":54,"uuid":"92886ad7-43fc-4222-902c-337c02d7a439","title":"升级博客到 HTTP/2 实录——CentOS升级Nginx到最新版","slug":"sheng-ji-bo-ke-dao-http2shi-lu-centossheng-ji-nginxdao-zui-xin-ban","markdown":"最近看 w3c 的文档都快吐了,尤其是视觉格式化模型,但是收获还是很大的,更加深入的理解了很多东西。这部分的东西会在之后总结完放出来的,而这个系列升级博客到 HTTP/2 实录,则是一边熟悉 HTTP 、 HTTPS 、HTTP/2,一边实践的产物,每天会实践一点,更新一点,直到把博客升级到 HTTP/2 为止。对于博客搞这个虽然是有点大材小用了,但是玩玩加上学习还是很不错的。\n\n这是本系列的第一篇,是关于 CentOS 安装和升级 nginx 的,因为 nginx 要到 1.9.0 才支持 HTTP/2 。废话不多讲,开始。\n\n首先,我的服务器是阿里云 CentOS 7,64位的,所以以下内容都是在这个环境下的。打开命令行,执行 `yum install yum-fastestmirror`,这个插件可以帮助我们自动选择最快的 yum 源。\n\n然后执行 `yum install nginx` ,安装 nginx。一般这时候安装的 nginx 版本是 1.6.3 。而且执行 `yum update nginx`,也无法更新。这就无法满足版本大于 1.9.0 的要求。\n\n在查阅了一些资料后,得到的方案是,在 update 之前,需要配置一下 nginx 源。打开命令行执行 `vim /etc/yum.repos.d/nginx.repo`,然后在 vim 中输入以下内容:\n\n```\n#nginx.repo \n \n[nginx] \nname=nginx repo \nbaseurl=http://nginx.org/packages/centos/7/$basearch/ \ngpgcheck=0 \nenabled=1 \n```\n\n配置完之后,在执行 `yum update nginx` 可以更新 nginx 了。打出 `nginx -v`,可以看到 nginx 已经升级到了 1.10.1 了😁。最后别忘了重启一下 nginx,`service nginx restart`。\n\n![](https://ws3.sinaimg.cn/large/006bH5BKgw1f774ek4suvj30m80bp43m.jpg)\n\n至此,我们的 nginx就升级成功了,明天继续升级!!!🤗\n\n参考资料:\n\n1. [CentOS配置Nginx官方的Yum源](http://zhangzifan.com/centos-nginx-yum-source.html)\n2. [CentOS下安装nginx并且升级nginx到最新版](http://www.centoscn.com/nginx/2015/0505/5363.html)\n\n","html":"<p>最近看 w3c 的文档都快吐了,尤其是视觉格式化模型,但是收获还是很大的,更加深入的理解了很多东西。这部分的东西会在之后总结完放出来的,而这个系列升级博客到 HTTP/2 实录,则是一边熟悉 HTTP 、 HTTPS 、HTTP/2,一边实践的产物,每天会实践一点,更新一点,直到把博客升级到 HTTP/2 为止。对于博客搞这个虽然是有点大材小用了,但是玩玩加上学习还是很不错的。</p>\n\n<p>这是本系列的第一篇,是关于 CentOS 安装和升级 nginx 的,因为 nginx 要到 1.9.0 才支持 HTTP/2 。废话不多讲,开始。</p>\n\n<p>首先,我的服务器是阿里云 CentOS 7,64位的,所以以下内容都是在这个环境下的。打开命令行,执行 <code>yum install yum-fastestmirror</code>,这个插件可以帮助我们自动选择最快的 yum 源。</p>\n\n<p>然后执行 <code>yum install nginx</code> ,安装 nginx。一般这时候安装的 nginx 版本是 1.6.3 。而且执行 <code>yum update nginx</code>,也无法更新。这就无法满足版本大于 1.9.0 的要求。</p>\n\n<p>在查阅了一些资料后,得到的方案是,在 update 之前,需要配置一下 nginx 源。打开命令行执行 <code>vim /etc/yum.repos.d/nginx.repo</code>,然后在 vim 中输入以下内容:</p>\n\n<pre><code>#nginx.repo \n\n[nginx] \nname=nginx repo \nbaseurl=http://nginx.org/packages/centos/7/$basearch/ \ngpgcheck=0 \nenabled=1 \n</code></pre>\n\n<p>配置完之后,在执行 <code>yum update nginx</code> 可以更新 nginx 了。打出 <code>nginx -v</code>,可以看到 nginx 已经升级到了 1.10.1 了😁。最后别忘了重启一下 nginx,<code>service nginx restart</code>。</p>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/006bH5BKgw1f774ek4suvj30m80bp43m.jpg\" alt=\"\" /></p>\n\n<p>至此,我们的 nginx就升级成功了,明天继续升级!!!🤗</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"http://zhangzifan.com/centos-nginx-yum-source.html\">CentOS配置Nginx官方的Yum源</a> </li>\n<li><a href=\"http://www.centoscn.com/nginx/2015/0505/5363.html\">CentOS下安装nginx并且升级nginx到最新版</a></li>\n</ol>","image":"/content/images/2016/08/QQ20160824-0-2x.png","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1472050275710,"created_by":1,"updated_at":1472192071553,"updated_by":1,"published_at":1472051608652,"published_by":1},{"id":55,"uuid":"3261c781-f0d4-482d-b8d5-584756c661ae","title":"升级博客到 HTTP/2 实录——Nginx启用Let's Encrypt SSL证书","slug":"sheng-ji-bo-ke-dao-http-2-shi-lu-centossheng-ji-nginxdao-zui-xin-ban","markdown":"这篇本来是昨晚发的,但是由于阿里云的学生服务器太渣了、房间网速又不行,一直卡在 letsencrypt 的安装上。早上起来一试,没想到就这么成功了。\n\n[Let's Encrypt](https://letsencrypt.org/) 是一个免费的 SSL/TLS 证书发行机构,证书有效期为90天,到期前30内可续期, 实现永久免费。\n\nLet's Encrypt SSL 证书的的获取并不是像其他网站一样,在页面上填写资申请证书,而是需要在域名所在的服务器上安装一个客户端(python写的)去获取证书和续期。而像 StartSSL 之前搞了半天,弄下来的证书也没办法用。而这个利用 python 客户端的,只要敲敲命令就可以完成一切,方便多了。\n\n本次安装使用的服务器配置:\nAliyun9.9学生服务器 + CentOS 7 + Nginx 1.10.1\n\n##### 客户端\n\nLet's Encrypt 提供了两种客户端。Certbot 和 官方客户端。前者是官方推荐的,不过我使用的是后者,所以前者就不介绍了,感兴趣的可以看参考资料里的链接。\n\nLet's Encrypt 官方的客户端托管在 github 上,每次运行客户端都会先自动升级,再运行最新的客户端,所以需要安装 git。因为是 python 写的程序,所以需要安装 python。\n\n第一步,安装 python 及 git,已经安装的则跳过;\n\n```\nyum install git python\n```\n\n第二步,下载客户端, 放到某路径下;\n\n```\ngit clone https://github.com/letsencrypt/letsencrypt\n```\n\n第三步,运行一次客户端,自动检查升级,请确保内存足够多,大概要几十兆吧。\n\n```\ncd letsencrypt\n./letsencrypt-auto --help\n```\n\n第四步,验证域名所有权,其原理就是申请人在域名所在的服务器上申请证书,然后 Let's Encrypt 会访问绑定的域名与客户端通信成功即可通过。\n\n这个验证的方法有两种,一种需要停止当前的 web server 服务,让出 80 端口,由客户端内置的 web server 启动与 Let's Encrypt 通信. 另一种不需要停止当前 web server,但需要在域名根目录下创建一个临时目录,并要保证外网通过域名可以访问这个目录。\n\n###### 通过客户端 web server 获取证书\n\n```\n#停止nginx\nsystemctl stop nginx\n \n#获取证书,--standalone 参数:使用内置web server。--email 参数:管理员邮箱,证书到期前会发邮件到此邮箱提醒。-d 参数:要绑定的域名,同一域的不同子域都要输入。\n./letsencrypt-auto certonly --standalone --email [email protected] -d m2mbob.cn -d www.m2mbob.cn\n \n#启动nginx\nsystemctl start nginx\n```\n\n###### 通过临时目录获取证书\n\n```\n#创建临时目录,可能要修改nginx rewrite 规则才能从外网访问\nmkdir -p /usr/share/nginx/html/.well-known/acme-challenge\n \n#--webroot 参数:指定使用临时目录的方式。-w 参数:指定后面-d 域名所在的根目录,如果一次申请多个域的, 可以附加更多 -w...-d... 这段。\n./letsencrypt-auto certonly --webroot --email [email protected] -w /usr/sha\n```\n\n执行此命令后会生成证书,保存在 /etc/letsencrypt/live 中对应的域名目录下面,其实这里面并不是真正的证书文件,而是通过链接的形式链到了 /etc/letsencrypt/archive 中对应的域名目录下。\n\n##### 自动更新\n\n完成以上四步,其实就可以配置 nginx ,完成升级了。但是考虑到证书有效期为90天,每次到期需要在申请证书一波很麻烦,所以在这里我们需要写个脚本来自动更新证书。\n\n```\n#!/bin/sh\n#停止 nginx 服务,使用 --standalone 独立服务器验证需要停止当前 web server.\nsystemctl stop nginx\nif ! /path/to/letsencrypt-auto renew -nvv --standalone > /var/log/letsencrypt/renew.log 2>&1 ; then\n echo Automated renewal failed:\n cat /var/log/letsencrypt/renew.log\n exit 1\nfi\n#启动 nginx\nsystemctl start nginx\n```\n\n然后添加执行权限 \n\n```\nchmod +x letsencrypt-renew.sh\n```\n\n最后一步就是编辑 crontab 配置文件或执行 crontab -e 添加 cron 任务\n\n```\nnano /etc/crontab\n```\n\n我这里设置为每月25号23点59分执行此脚本.\n\n```\n#分 时 日 月 星期 执行用户 执行命令\n 59 23 25 * * root /脚本目录/letsencrypt-renew.sh\n```\n\n保存退出即可。\n\n##### 配置 nginx\n\n```\n#设置非安全连接永久跳转到安全连接\nserver{\n listen 80;\n server_name m2mbob.cn www.m2mbob.cn;\n #告诉浏览器有效期内只准用 https 访问\n add_header Strict-Transport-Security max-age=15768000;\n #永久重定向到 https 站点\n return 301 https://$server_name$request_uri;\n}\nserver {\n #启用 https,使用 http/2 协议\n listen 443 ssl http2;\n server_name m2mbob.cn www.m2mbob.cn;\n #告诉浏览器当前页面禁止被frame\n add_header X-Frame-Options DENY;\n #告诉浏览器不要猜测mime类型\n add_header X-Content-Type-Options nosniff;\n \n #证书路径\n ssl_certificate /etc/letsencrypt/live/m2mbob.cn/fullchain.pem;\n #私钥路径\n ssl_certificate_key /etc/letsencrypt/live/m2mbob.cn/privkey.pem;\n #安全链接可选的加密协议\n ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n #可选的加密算法,顺序很重要,越靠前的优先级越高.\n ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:HIGH:!RC4-SHA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!CBC:!EDH:!kEDH:!PSK:!SRP:!kECDH;\n #在 SSLv3 或 TLSv1 握手过程一般使用客户端的首选算法,如果启用下面的配置,则会使用服务器端的首选算法.\n ssl_prefer_server_ciphers on;\n #储存SSL会话的缓存类型和大小\n ssl_session_cache shared:SSL:10m;\n #缓存有效期\n ssl_session_timeout 60m;\n \n #省略后面与证书无关的设置\n}\n```\n\n最后效果😬:\n\n![](https://ws1.sinaimg.cn/large/006bH5BKgw1f774d06ggdj30gu064t9e.jpg)\n\n可是[极简图床](http://www.yotuku.cn/)挂了,而且由于提供的图片不是 https 的,所以访问一些页面时,chrome 绿色的认证又木有了。下午换用了微博图床,支持 HTTPS 而且是 chrome 插件,很好用。但是b站和网易云音乐外链的问题还解决不了!😂再看看,再看看!\n\n到这里,博客其实已经成功升级到 HTTP/2 了,但是仅仅是实践,对于 HTTP/2 及 HTTPS 理解还不是那么的深刻,需要找时间在学习学习!😳\n\n参考资料:\n\n1. [CentOS 7 Nginx Let’ s Encrypt SSL 证书安装配置](https://blog.itnmg.net/letsencrypt-ssl/)\n2. [Nginx启用Let’s Encrypt SSL证书](https://www.iwwenbo.com/nginx-lets-encrypt-ssl/)","html":"<p>这篇本来是昨晚发的,但是由于阿里云的学生服务器太渣了、房间网速又不行,一直卡在 letsencrypt 的安装上。早上起来一试,没想到就这么成功了。</p>\n\n<p><a href=\"https://letsencrypt.org/\">Let's Encrypt</a> 是一个免费的 SSL/TLS 证书发行机构,证书有效期为90天,到期前30内可续期, 实现永久免费。</p>\n\n<p>Let's Encrypt SSL 证书的的获取并不是像其他网站一样,在页面上填写资申请证书,而是需要在域名所在的服务器上安装一个客户端(python写的)去获取证书和续期。而像 StartSSL 之前搞了半天,弄下来的证书也没办法用。而这个利用 python 客户端的,只要敲敲命令就可以完成一切,方便多了。</p>\n\n<p>本次安装使用的服务器配置:\nAliyun9.9学生服务器 + CentOS 7 + Nginx 1.10.1</p>\n\n<h5 id=\"\">客户端</h5>\n\n<p>Let's Encrypt 提供了两种客户端。Certbot 和 官方客户端。前者是官方推荐的,不过我使用的是后者,所以前者就不介绍了,感兴趣的可以看参考资料里的链接。</p>\n\n<p>Let's Encrypt 官方的客户端托管在 github 上,每次运行客户端都会先自动升级,再运行最新的客户端,所以需要安装 git。因为是 python 写的程序,所以需要安装 python。</p>\n\n<p>第一步,安装 python 及 git,已经安装的则跳过;</p>\n\n<pre><code>yum install git python \n</code></pre>\n\n<p>第二步,下载客户端, 放到某路径下;</p>\n\n<pre><code>git clone https://github.com/letsencrypt/letsencrypt \n</code></pre>\n\n<p>第三步,运行一次客户端,自动检查升级,请确保内存足够多,大概要几十兆吧。</p>\n\n<pre><code>cd letsencrypt \n./letsencrypt-auto --help\n</code></pre>\n\n<p>第四步,验证域名所有权,其原理就是申请人在域名所在的服务器上申请证书,然后 Let's Encrypt 会访问绑定的域名与客户端通信成功即可通过。</p>\n\n<p>这个验证的方法有两种,一种需要停止当前的 web server 服务,让出 80 端口,由客户端内置的 web server 启动与 Let's Encrypt 通信. 另一种不需要停止当前 web server,但需要在域名根目录下创建一个临时目录,并要保证外网通过域名可以访问这个目录。</p>\n\n<h6 id=\"webserver\">通过客户端 web server 获取证书</h6>\n\n<pre><code>#停止nginx\nsystemctl stop nginx\n\n#获取证书,--standalone 参数:使用内置web server。--email 参数:管理员邮箱,证书到期前会发邮件到此邮箱提醒。-d 参数:要绑定的域名,同一域的不同子域都要输入。\n./letsencrypt-auto certonly --standalone --email [email protected] -d m2mbob.cn -d www.m2mbob.cn\n\n#启动nginx\nsystemctl start nginx \n</code></pre>\n\n<h6 id=\"\">通过临时目录获取证书</h6>\n\n<pre><code>#创建临时目录,可能要修改nginx rewrite 规则才能从外网访问\nmkdir -p /usr/share/nginx/html/.well-known/acme-challenge\n\n#--webroot 参数:指定使用临时目录的方式。-w 参数:指定后面-d 域名所在的根目录,如果一次申请多个域的, 可以附加更多 -w...-d... 这段。\n./letsencrypt-auto certonly --webroot --email [email protected] -w /usr/sha\n</code></pre>\n\n<p>执行此命令后会生成证书,保存在 /etc/letsencrypt/live 中对应的域名目录下面,其实这里面并不是真正的证书文件,而是通过链接的形式链到了 /etc/letsencrypt/archive 中对应的域名目录下。</p>\n\n<h5 id=\"\">自动更新</h5>\n\n<p>完成以上四步,其实就可以配置 nginx ,完成升级了。但是考虑到证书有效期为90天,每次到期需要在申请证书一波很麻烦,所以在这里我们需要写个脚本来自动更新证书。</p>\n\n<pre><code>#!/bin/sh\n#停止 nginx 服务,使用 --standalone 独立服务器验证需要停止当前 web server.\nsystemctl stop nginx \nif ! /path/to/letsencrypt-auto renew -nvv --standalone > /var/log/letsencrypt/renew.log 2>&1 ; then \n echo Automated renewal failed:\n cat /var/log/letsencrypt/renew.log\n exit 1\nfi \n#启动 nginx\nsystemctl start nginx \n</code></pre>\n\n<p>然后添加执行权限 </p>\n\n<pre><code>chmod +x letsencrypt-renew.sh \n</code></pre>\n\n<p>最后一步就是编辑 crontab 配置文件或执行 crontab -e 添加 cron 任务</p>\n\n<pre><code>nano /etc/crontab \n</code></pre>\n\n<p>我这里设置为每月25号23点59分执行此脚本.</p>\n\n<pre><code>#分 时 日 月 星期 执行用户 执行命令\n 59 23 25 * * root /脚本目录/letsencrypt-renew.sh\n</code></pre>\n\n<p>保存退出即可。</p>\n\n<h5 id=\"nginx\">配置 nginx</h5>\n\n<pre><code>#设置非安全连接永久跳转到安全连接\nserver{ \n listen 80;\n server_name m2mbob.cn www.m2mbob.cn;\n #告诉浏览器有效期内只准用 https 访问\n add_header Strict-Transport-Security max-age=15768000;\n #永久重定向到 https 站点\n return 301 https://$server_name$request_uri;\n}\nserver { \n #启用 https,使用 http/2 协议\n listen 443 ssl http2;\n server_name m2mbob.cn www.m2mbob.cn;\n #告诉浏览器当前页面禁止被frame\n add_header X-Frame-Options DENY;\n #告诉浏览器不要猜测mime类型\n add_header X-Content-Type-Options nosniff;\n\n #证书路径\n ssl_certificate /etc/letsencrypt/live/m2mbob.cn/fullchain.pem;\n #私钥路径\n ssl_certificate_key /etc/letsencrypt/live/m2mbob.cn/privkey.pem;\n #安全链接可选的加密协议\n ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n #可选的加密算法,顺序很重要,越靠前的优先级越高.\n ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-RC4-SHA:!ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:HIGH:!RC4-SHA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!CBC:!EDH:!kEDH:!PSK:!SRP:!kECDH;\n #在 SSLv3 或 TLSv1 握手过程一般使用客户端的首选算法,如果启用下面的配置,则会使用服务器端的首选算法.\n ssl_prefer_server_ciphers on;\n #储存SSL会话的缓存类型和大小\n ssl_session_cache shared:SSL:10m;\n #缓存有效期\n ssl_session_timeout 60m;\n\n #省略后面与证书无关的设置\n}\n</code></pre>\n\n<p>最后效果😬:</p>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/006bH5BKgw1f774d06ggdj30gu064t9e.jpg\" alt=\"\" /></p>\n\n<p>可是<a href=\"http://www.yotuku.cn/\">极简图床</a>挂了,而且由于提供的图片不是 https 的,所以访问一些页面时,chrome 绿色的认证又木有了。下午换用了微博图床,支持 HTTPS 而且是 chrome 插件,很好用。但是b站和网易云音乐外链的问题还解决不了!😂再看看,再看看!</p>\n\n<p>到这里,博客其实已经成功升级到 HTTP/2 了,但是仅仅是实践,对于 HTTP/2 及 HTTPS 理解还不是那么的深刻,需要找时间在学习学习!😳</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"https://blog.itnmg.net/letsencrypt-ssl/\">CentOS 7 Nginx Let’ s Encrypt SSL 证书安装配置</a> </li>\n<li><a href=\"https://www.iwwenbo.com/nginx-lets-encrypt-ssl/\">Nginx启用Let’s Encrypt SSL证书</a></li>\n</ol>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1472175887078,"created_by":1,"updated_at":1472200210647,"updated_by":1,"published_at":1472181171990,"published_by":1},{"id":56,"uuid":"5c03c309-e87f-4fb6-a79c-2d4840a8cb6d","title":"升级博客到 HTTP/2 实录——新版Chrome下滚回HTTP/1.1","slug":"sheng-ji-bo-ke-dao-http-2-shi-lu-chromexia-gun-hui-http1-1bug","markdown":"本以为这个系列就要这么结束,但是当我用 Chrome 查看协议版本时,惊奇地发现还是 HTTP/1.1,这又是怎么回事,然后就开始了一路的踩坑。有几个大牛写着 HTTP/2,实际还不知道自己的还是HTTP/1.1,其他的不知是歪打正着,实现了HTTP/2,但是都没有提到这个问题。于是只能从度娘走向谷歌,才找到了问题所在。\n\n首先明确,只有在新版的 Chrome 下才有这个现象,其次造成这个现象的原因与 openssl 有关。然后我们来看一段英文:\n\n> Users of the Google Chrome web browser are seeing some sites that they previously accessed over HTTP/2 falling back to HTTP/1. This is because of a policy change in the most recent update to Chrome, released in late May, which removes support for NPN, one method for upgrading a connection to HTTP/2.\n\n> The only way Chrome users can continue using HTTP/2 to access these websites is by switching to a different browser. Website administrators can restore HTTP/2 support for Chrome users by upgrading their OpenSSL installation to the recently released 1.0.2 version. Unfortunately, this requires either a major operating system upgrade or using a private build of NGINX.\n\n大致意思就是说 Chrome 在最近的更新中放弃了对 NPN 的支持,如果想要继续在 Chrome上支持 HTTP/2 ,则需要安装最新 1.0.2 版的 OpenSSL,并且用 1.0.2 的 OpenSSL 重新编译 Nginx。\n\n知道问题的所在了,后面找日子再试试了,今天已经搞得身心俱疲了,而且明天还要处理 gitlab 上的一大堆 issue。急着解决问题的同学直接看参考链接哈!!!\n\n参考链接:\n\n1. [Supporting HTTP/2 for Google Chrome Users](https://www.nginx.com/blog/supporting-http2-google-chrome-users/)\n2. [Nginx HTTP2 编译](http://www.tuicool.com/articles/3eeIVfi)","html":"<p>本以为这个系列就要这么结束,但是当我用 Chrome 查看协议版本时,惊奇地发现还是 HTTP/1.1,这又是怎么回事,然后就开始了一路的踩坑。有几个大牛写着 HTTP/2,实际还不知道自己的还是HTTP/1.1,其他的不知是歪打正着,实现了HTTP/2,但是都没有提到这个问题。于是只能从度娘走向谷歌,才找到了问题所在。</p>\n\n<p>首先明确,只有在新版的 Chrome 下才有这个现象,其次造成这个现象的原因与 openssl 有关。然后我们来看一段英文:</p>\n\n<blockquote>\n <p>Users of the Google Chrome web browser are seeing some sites that they previously accessed over HTTP/2 falling back to HTTP/1. This is because of a policy change in the most recent update to Chrome, released in late May, which removes support for NPN, one method for upgrading a connection to HTTP/2.</p>\n \n <p>The only way Chrome users can continue using HTTP/2 to access these websites is by switching to a different browser. Website administrators can restore HTTP/2 support for Chrome users by upgrading their OpenSSL installation to the recently released 1.0.2 version. Unfortunately, this requires either a major operating system upgrade or using a private build of NGINX.</p>\n</blockquote>\n\n<p>大致意思就是说 Chrome 在最近的更新中放弃了对 NPN 的支持,如果想要继续在 Chrome上支持 HTTP/2 ,则需要安装最新 1.0.2 版的 OpenSSL,并且用 1.0.2 的 OpenSSL 重新编译 Nginx。</p>\n\n<p>知道问题的所在了,后面找日子再试试了,今天已经搞得身心俱疲了,而且明天还要处理 gitlab 上的一大堆 issue。急着解决问题的同学直接看参考链接哈!!!</p>\n\n<p>参考链接:</p>\n\n<ol>\n<li><a href=\"https://www.nginx.com/blog/supporting-http2-google-chrome-users/\">Supporting HTTP/2 for Google Chrome Users</a> </li>\n<li><a href=\"http://www.tuicool.com/articles/3eeIVfi\">Nginx HTTP2 编译</a></li>\n</ol>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1472229137358,"created_by":1,"updated_at":1472230036207,"updated_by":1,"published_at":1472230018473,"published_by":1},{"id":57,"uuid":"719704a5-6f93-4ea8-9a99-156caf71b95c","title":"转:JS中函数定义和函数表达式的区别","slug":"zhuan-jszhong-han-shu-ding-yi-he-han-shu-biao-da-shi-de-qu-bie","markdown":"Javascript中有2个语法都与function关键字有关,分别是:\n\n> 函数定义:function FunctionName(FormalParameterList) { FunctionBody }\n\n> 函数表达式:function [FunctionName](FormalParameterList) { FunctionBody }\n\n从语法的定义上看,这两者几乎是一模一样的(唯一的区别是函数表达式可以省略函数名称),那么就解释器而言,当遇到这个结构的语句时,判定为函数表达式还是函数定义呢?\n\n就javascript的语法而言,如果一条语句是以function关键字开始,那么这段会被判定为函数定义。而函数定义是不能被立即执行的,这无疑会导致语法的错误(SyntaxError),因此就必须有一个办法,使解析器可以将之识别为函数表达式。\n\n前面已经说到,解析器识别函数定义的条件是以function关键字开始,那么自然,只要在function关键字的前面有任何其他的元素,就会从函数定义转变为函数表达式,以下方法都是可以的,这个大家都知道:\n\n```\n~function() {}();\n\n!function() {}();\n\nvoid function() {}();\n```\n\n但是这几个方法都有一个特点,就是看起来很别扭,所以现在为止,以括号包裹成了比较公认的方案。\n\n回到正题,括号包裹同样有2个方式:(function() {})();和(function(){}());\n\n他们的共通点是:都有括号。而括号在javascript中有2种作用:确立运算优先级,以及分组运算符,从代码上看,显然没有进行数学或逻辑运算,因此我认为这里的括号属于分组运算符。\n\n根据标准,分组运算符的作用是:\n\n> Return the result of evaluating Expression. This may be of type Reference. \n\n返回评估括号中的表达式的结果。结果可能是Reference类型。\n\n抛开像Reference类型这种词汇,这里的一个关键词应当是“ 评估 ”,但是关于分组运算符,又有一个很重要的下文:\n\n> This algorithm does not apply GetValue to the result of evaluating Expression.\n\n这个算法不会对估算的结果使用GetValue。\n\n有很多专用的名词,看起来确实复杂,简而言之,使用括号运算符本身不会让括号中的代码立即执行,只有当括号包含的这个“分组”参与其他运算时,才会执行。因此,(function(){})()这个语句,其实是首先用分组运算符评估了一个函数表达式,随后参与“函数调用”。而(function(){}())这个语句,则是用分组运算符评估了一个函数调用,随后由于语句的结束而被执行。\n\n[原文地址](http://www.zhihu.com/question/20292224)","html":"<p>Javascript中有2个语法都与function关键字有关,分别是:</p>\n\n<blockquote>\n <p>函数定义:function FunctionName(FormalParameterList) { FunctionBody }</p>\n \n <p>函数表达式:function <a href=\"FormalParameterList\">FunctionName</a> { FunctionBody }</p>\n</blockquote>\n\n<p>从语法的定义上看,这两者几乎是一模一样的(唯一的区别是函数表达式可以省略函数名称),那么就解释器而言,当遇到这个结构的语句时,判定为函数表达式还是函数定义呢?</p>\n\n<p>就javascript的语法而言,如果一条语句是以function关键字开始,那么这段会被判定为函数定义。而函数定义是不能被立即执行的,这无疑会导致语法的错误(SyntaxError),因此就必须有一个办法,使解析器可以将之识别为函数表达式。</p>\n\n<p>前面已经说到,解析器识别函数定义的条件是以function关键字开始,那么自然,只要在function关键字的前面有任何其他的元素,就会从函数定义转变为函数表达式,以下方法都是可以的,这个大家都知道:</p>\n\n<pre><code>~function() {}();\n\n!function() {}();\n\nvoid function() {}(); \n</code></pre>\n\n<p>但是这几个方法都有一个特点,就是看起来很别扭,所以现在为止,以括号包裹成了比较公认的方案。</p>\n\n<p>回到正题,括号包裹同样有2个方式:(function() {})();和(function(){}());</p>\n\n<p>他们的共通点是:都有括号。而括号在javascript中有2种作用:确立运算优先级,以及分组运算符,从代码上看,显然没有进行数学或逻辑运算,因此我认为这里的括号属于分组运算符。</p>\n\n<p>根据标准,分组运算符的作用是:</p>\n\n<blockquote>\n <p>Return the result of evaluating Expression. This may be of type Reference. </p>\n</blockquote>\n\n<p>返回评估括号中的表达式的结果。结果可能是Reference类型。</p>\n\n<p>抛开像Reference类型这种词汇,这里的一个关键词应当是“ 评估 ”,但是关于分组运算符,又有一个很重要的下文:</p>\n\n<blockquote>\n <p>This algorithm does not apply GetValue to the result of evaluating Expression.</p>\n</blockquote>\n\n<p>这个算法不会对估算的结果使用GetValue。</p>\n\n<p>有很多专用的名词,看起来确实复杂,简而言之,使用括号运算符本身不会让括号中的代码立即执行,只有当括号包含的这个“分组”参与其他运算时,才会执行。因此,(function(){})()这个语句,其实是首先用分组运算符评估了一个函数表达式,随后参与“函数调用”。而(function(){}())这个语句,则是用分组运算符评估了一个函数调用,随后由于语句的结束而被执行。</p>\n\n<p><a href=\"http://www.zhihu.com/question/20292224\">原文地址</a></p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1472875383514,"created_by":1,"updated_at":1472875565217,"updated_by":1,"published_at":1472875537256,"published_by":1},{"id":58,"uuid":"6ea8836d-d787-4574-bdfd-9d217fed6ac7","title":"js正则表达式总结","slug":"wei-shi-yao-bu-yong-a-undefined","markdown":"类范围类:[]和[^]\n量词:{}、?、+、*\n预定义类:\\d \\D \\W \\w .\n边界:\\b \\B ^ $\n分组:()\n反向引用:$1$2$3\n正反前瞻(?=)(?!)\n忽略分组(?:)\n?启用非贪婪\n对象属性:Global、ignoreCase、Multlines、source、lastIndex\n\n\nRegExp.test和exec\nString.match、replace、search、split\n\nes6:构造函数修复、字符串四个方法从Regexp上调用、修改符u(针对四个字节的UTF-16编码)、y修饰符(黏连)、对象属性也就多了sticky、flags属性\n\nes7:字符串转义才能作为正则模式escape方法、后顾","html":"<p>类范围类:[]和[^]\n量词:{}、?、+、*\n预定义类:\\d \\D \\W \\w .\n边界:\\b \\B ^ $\n分组:()\n反向引用:$1$2$3\n正反前瞻(?=)(?!)\n忽略分组(?:)\n?启用非贪婪\n对象属性:Global、ignoreCase、Multlines、source、lastIndex</p>\n\n<p>RegExp.test和exec <br />\nString.match、replace、search、split</p>\n\n<p>es6:构造函数修复、字符串四个方法从Regexp上调用、修改符u(针对四个字节的UTF-16编码)、y修饰符(黏连)、对象属性也就多了sticky、flags属性</p>\n\n<p>es7:字符串转义才能作为正则模式escape方法、后顾</p>","image":null,"featured":0,"page":0,"status":"draft","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1472880637599,"created_by":1,"updated_at":1473249762103,"updated_by":1,"published_at":null,"published_by":null},{"id":59,"uuid":"b60f6f88-f27f-4908-9ea3-63b7afd72950","title":"随笔","slug":"sui-bi-2","markdown":"昨晚寝室没网,看了部存在硬盘中的电影《第八日的蝉》就睡了。挺好的片子,如果后面还记得的话会写写这片子。\n\n早上起得挺早,就准备找个有网能自习的地方去,然而学校图书馆没开,只好跑远点到了浙江图书馆。环境挺不错,就是计算机类的书又少又旧。还好了带了本书,好了,开始学习了!\n\n看了一章,发现普通的书已经解决不了我的问题了,只能上文档了,就是有点厚!","html":"<p>昨晚寝室没网,看了部存在硬盘中的电影《第八日的蝉》就睡了。挺好的片子,如果后面还记得的话会写写这片子。</p>\n\n<p>早上起得挺早,就准备找个有网能自习的地方去,然而学校图书馆没开,只好跑远点到了浙江图书馆。环境挺不错,就是计算机类的书又少又旧。还好了带了本书,好了,开始学习了!</p>\n\n<p>看了一章,发现普通的书已经解决不了我的问题了,只能上文档了,就是有点厚!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1473385751621,"created_by":1,"updated_at":1473389562246,"updated_by":1,"published_at":1473386005922,"published_by":1},{"id":60,"uuid":"20ff95ab-2c25-491a-a722-17bfc527be8b","title":"MDN js bind 解读","slug":"mdn-js-bind-jie-du","markdown":"#### 基础\n\n###### 语法\n\n>fun.bind(thisArg[, arg1[, arg2[, ...]]])\n\n###### 参数\n\n> thisArg\n\n> 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。\n\n> arg1, arg2, ...\n\n> 当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。\n\n###### 返回值\n\n> 返回由指定的this值和初始化参数改造的原函数拷贝\n\n注意点:上面bind返回的新函数可以作为构造函数,此时bind指定的this无效,而会使用构造函数产生的对象作为this。其次,就是最终的参数包括两部分,一是bind时指定的参数,二是函数调用是指定的参数,此处涉及了偏函数以及函数科里化的只是后面会补充。\n\n#### 有了call、apply,为何还要bind\n\nbind能够和call、apply一样绑定上下文,但是相较于call和apply,它还有很多别的特点。首先,bind会返回一个新的函数,而不是立即执行;其次,我们在bind时,可以固定一些参数,此时产生的便是一个偏函数。偏函数在js或者python中就是指固定一些参数,返回一个新的函数,来方便函数调用的。举个例子:\n\n```\n// parseInt()支持第二个参数,表示进制,如果我们要指定第一个参数是二进制,并且在很多地方使用,我们就可以把第二个参数固定下来,然后返回一个新的函数,方便调用。\n\nfunction toInt2(x){\n return parseInt(x, 2);\n}\n```\n\n第三是作为构造函数的绑定函数,不过不建议在生产环境中使用。\n\n```\nfunction Point(x, y) {\n this.x = x;\n this.y = y;\n}\n\nPoint.prototype.toString = function() { \n return this.x + ',' + this.y; \n};\n\nvar p = new Point(1, 2);\np.toString(); // '1,2'\n\nvar emptyObj = {};\nvar YAxisPoint = Point.bind(emptyObj, 0/*x*/);\n// 以下这行代码在 polyfill 不支持,\n// 在原生的bind方法运行没问题:\n//(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)\nvar YAxisPoint = Point.bind(null, 0/*x*/);\n\nvar axisPoint = new YAxisPoint(5);\naxisPoint.toString(); // '0,5'\n\naxisPoint instanceof Point; // true\naxisPoint instanceof YAxisPoint; // true\nnew Point(17, 42) instanceof YAxisPoint; // true\n```\n\n上面这段代码使用原生的bind和其他的实现运行的效果有出入,所以在构造函数上还是不建议使用bind吧。\n\n第四是快捷调用,你可以用 Array.prototype.slice 来将一个类似于数组的对象(array-like object)转换成一个真正的数组,就拿它来举例子吧。你可以创建这样一个捷径:\n\n```\nvar slice = Array.prototype.slice;\n//...\nslice.apply(arguments);\n```\n\n但是使用bind,可以使这个过程变得简单。\n\n```\n// same as \"slice\" in the previous example\nvar unboundSlice = Array.prototype.slice;\nvar slice = Function.prototype.call.bind(unboundSlice);\n// ...\nslice(arguments);\n```\n\n主要要讲的是`Function.prototype.call.bind(unboundSlice)`,这是什么写法?其实就是把call执行的时候的this绑定为unboundSlice,然后返回一个新的函数,此时执行`slice(arguments)`,就相当于执行`unboundSlice.call(arguments)`。\n\n#### bind的劣势\n\n目前看到的主要有两个劣势,第一是兼容性,IE9以下不支持;第二是性能,引用一段话:\n\n> 我测试了一下浏览器原生的Function.prototype.bind,发现使用了bind之后,函数的内存占用增加了近2倍!CoffeeScript实现的绑定稍微轻量一点,内存占用也增加了1倍多。\n\n>再顺便测试了下ES6新增的Arrow function(也是=>),因为这个特殊函数是自带绑定技能的,结果惊奇地发现,它的内存占用和普通的Function没啥区别。所以以后需要或者不需要bind的场景如果一定要滥用bind图个安心的话,可以通通上高逼格的箭头函数。:)\n\n>文/寂寞的原子(简书作者)\n原文链接:http://www.jianshu.com/p/45515682be0d\n著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。\n\nbind和箭头函数又是一个新的问题了,这里不展开讨论了。\n\n不过总的来说,bind还是一个比较好的方案,像很多库都在使用,underscore、lodash、jquery也都有对应的实现。\n\n#### bind的实现\n\n最简单的实现:\n\n```\nFunction.prototype.bind = function(context){ \n self = this; //保存this,即调用bind方法的目标函数\n return function(){\n return self.apply(context,arguments);\n };\n};\n```\n\n加入科里化,也就是把两部分参数进行合并:\n\n```\nFunction.prototype.bind = function(context){ \n var args = Array.prototype.slice.call(arguments, 1),\n self = this;\n return function(){\n var innerArgs = Array.prototype.slice.call(arguments);\n var finalArgs = args.concat(innerArgs);\n return self.apply(context,finalArgs);\n };\n};\n```\n\n最后要分析一下Polyfill(兼容旧浏览器),它考虑到了构造函数,但是与原生的实现还是有点区别,可以见构造函数一节的注释:\n\n```\n// 判断是否原生支持\nif (!Function.prototype.bind) {\n Function.prototype.bind = function (oThis) {\n // 如果需要bind的不是函数则报错\n if (typeof this !== \"function\") {\n // closest thing possible to the ECMAScript 5\n // internal IsCallable function\n throw new TypeError(\"Function.prototype.bind - what is trying to be bound is not callable\");\n }\n\n // aArgs是bind时固定下来的参数\n // fToBind保存了要bind函数的引用\n // fNOP作为中介\n // fBound是最后返回的函数\n var aArgs = Array.prototype.slice.call(arguments, 1), \n fToBind = this, \n fNOP = function () {},\n fBound = function () {\n return fToBind.apply(this instanceof fNOP\n ? this\n : oThis || this,\n// 连接两次的参数 aArgs.concat(Array.prototype.slice.call(arguments)));\n };\n\n // 保证bind出来的函数与借用的函数拥有相同的原型链\n fNOP.prototype = this.prototype;\n fBound.prototype = new fNOP();\n\n return fBound;\n };\n}\n```\n\n剩下还有`this instanceof fNOP ? this : oThis || this`是我百思不得其解的,最后在一篇博客上找到了答案,当bind返回的函数用作构造函数时,`this instanceof fNOP`为true,也就是定义中所说的忽略this的情况。\n\n至于为什么需要fNOP作为中介,第一是直接把`this.prototype`给`fBound.prototype`两者会指向同一个引用,其次上面一段的判断也无法达成。\n\n#### 扩展es7绑定函数\n\n该语法还是ES7的一个提案,但是Babel转码器已经支持。\n\n函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。\n\n```\nfoo::bar;\n// 等同于\nbar.bind(foo);\n\nfoo::bar(...arguments);\n// 等同于\nbar.apply(foo, arguments);\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\nfunction hasOwn(obj, key) {\n return obj::hasOwnProperty(key);\n}\n```\n\n详情见阮老师的es6入门吧!\n\n#### 最后扯两句\n\n仅仅是bind就有这么多东西,如果把它放到整块知识中肯定能够有更多的收获,不过在晕之前还先把阶段性的结果写下来,到时候好整合!","html":"<h4 id=\"\">基础</h4>\n\n<h6 id=\"\">语法</h6>\n\n<blockquote>\n <p>fun.bind(thisArg[, arg1[, arg2[, ...]]])</p>\n</blockquote>\n\n<h6 id=\"\">参数</h6>\n\n<blockquote>\n <p>thisArg</p>\n \n <p>当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。</p>\n \n <p>arg1, arg2, ...</p>\n \n <p>当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数。</p>\n</blockquote>\n\n<h6 id=\"\">返回值</h6>\n\n<blockquote>\n <p>返回由指定的this值和初始化参数改造的原函数拷贝</p>\n</blockquote>\n\n<p>注意点:上面bind返回的新函数可以作为构造函数,此时bind指定的this无效,而会使用构造函数产生的对象作为this。其次,就是最终的参数包括两部分,一是bind时指定的参数,二是函数调用是指定的参数,此处涉及了偏函数以及函数科里化的只是后面会补充。</p>\n\n<h4 id=\"callapplybind\">有了call、apply,为何还要bind</h4>\n\n<p>bind能够和call、apply一样绑定上下文,但是相较于call和apply,它还有很多别的特点。首先,bind会返回一个新的函数,而不是立即执行;其次,我们在bind时,可以固定一些参数,此时产生的便是一个偏函数。偏函数在js或者python中就是指固定一些参数,返回一个新的函数,来方便函数调用的。举个例子:</p>\n\n<pre><code>// parseInt()支持第二个参数,表示进制,如果我们要指定第一个参数是二进制,并且在很多地方使用,我们就可以把第二个参数固定下来,然后返回一个新的函数,方便调用。\n\nfunction toInt2(x){ \n return parseInt(x, 2);\n}\n</code></pre>\n\n<p>第三是作为构造函数的绑定函数,不过不建议在生产环境中使用。</p>\n\n<pre><code>function Point(x, y) { \n this.x = x;\n this.y = y;\n}\n\nPoint.prototype.toString = function() { \n return this.x + ',' + this.y; \n};\n\nvar p = new Point(1, 2); \np.toString(); // '1,2'\n\nvar emptyObj = {}; \nvar YAxisPoint = Point.bind(emptyObj, 0/*x*/); \n// 以下这行代码在 polyfill 不支持,\n// 在原生的bind方法运行没问题:\n//(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)\nvar YAxisPoint = Point.bind(null, 0/*x*/);\n\nvar axisPoint = new YAxisPoint(5); \naxisPoint.toString(); // '0,5'\n\naxisPoint instanceof Point; // true \naxisPoint instanceof YAxisPoint; // true \nnew Point(17, 42) instanceof YAxisPoint; // true \n</code></pre>\n\n<p>上面这段代码使用原生的bind和其他的实现运行的效果有出入,所以在构造函数上还是不建议使用bind吧。</p>\n\n<p>第四是快捷调用,你可以用 Array.prototype.slice 来将一个类似于数组的对象(array-like object)转换成一个真正的数组,就拿它来举例子吧。你可以创建这样一个捷径:</p>\n\n<pre><code>var slice = Array.prototype.slice; \n//...\nslice.apply(arguments); \n</code></pre>\n\n<p>但是使用bind,可以使这个过程变得简单。</p>\n\n<pre><code>// same as \"slice\" in the previous example\nvar unboundSlice = Array.prototype.slice; \nvar slice = Function.prototype.call.bind(unboundSlice); \n// ...\nslice(arguments); \n</code></pre>\n\n<p>主要要讲的是<code>Function.prototype.call.bind(unboundSlice)</code>,这是什么写法?其实就是把call执行的时候的this绑定为unboundSlice,然后返回一个新的函数,此时执行<code>slice(arguments)</code>,就相当于执行<code>unboundSlice.call(arguments)</code>。</p>\n\n<h4 id=\"bind\">bind的劣势</h4>\n\n<p>目前看到的主要有两个劣势,第一是兼容性,IE9以下不支持;第二是性能,引用一段话:</p>\n\n<blockquote>\n <p>我测试了一下浏览器原生的Function.prototype.bind,发现使用了bind之后,函数的内存占用增加了近2倍!CoffeeScript实现的绑定稍微轻量一点,内存占用也增加了1倍多。</p>\n \n <p>再顺便测试了下ES6新增的Arrow function(也是=>),因为这个特殊函数是自带绑定技能的,结果惊奇地发现,它的内存占用和普通的Function没啥区别。所以以后需要或者不需要bind的场景如果一定要滥用bind图个安心的话,可以通通上高逼格的箭头函数。:)</p>\n \n <p>文/寂寞的原子(简书作者)\n 原文链接:<a href=\"http://www.jianshu.com/p/45515682be0d\">http://www.jianshu.com/p/45515682be0d</a>\n 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。</p>\n</blockquote>\n\n<p>bind和箭头函数又是一个新的问题了,这里不展开讨论了。</p>\n\n<p>不过总的来说,bind还是一个比较好的方案,像很多库都在使用,underscore、lodash、jquery也都有对应的实现。</p>\n\n<h4 id=\"bind\">bind的实现</h4>\n\n<p>最简单的实现:</p>\n\n<pre><code>Function.prototype.bind = function(context){ \n self = this; //保存this,即调用bind方法的目标函数\n return function(){\n return self.apply(context,arguments);\n };\n};\n</code></pre>\n\n<p>加入科里化,也就是把两部分参数进行合并:</p>\n\n<pre><code>Function.prototype.bind = function(context){ \n var args = Array.prototype.slice.call(arguments, 1),\n self = this;\n return function(){\n var innerArgs = Array.prototype.slice.call(arguments);\n var finalArgs = args.concat(innerArgs);\n return self.apply(context,finalArgs);\n };\n};\n</code></pre>\n\n<p>最后要分析一下Polyfill(兼容旧浏览器),它考虑到了构造函数,但是与原生的实现还是有点区别,可以见构造函数一节的注释:</p>\n\n<pre><code>// 判断是否原生支持\nif (!Function.prototype.bind) { \n Function.prototype.bind = function (oThis) {\n // 如果需要bind的不是函数则报错\n if (typeof this !== \"function\") {\n // closest thing possible to the ECMAScript 5\n // internal IsCallable function\n throw new TypeError(\"Function.prototype.bind - what is trying to be bound is not callable\");\n }\n\n // aArgs是bind时固定下来的参数\n // fToBind保存了要bind函数的引用\n // fNOP作为中介\n // fBound是最后返回的函数\n var aArgs = Array.prototype.slice.call(arguments, 1), \n fToBind = this, \n fNOP = function () {},\n fBound = function () {\n return fToBind.apply(this instanceof fNOP\n ? this\n : oThis || this,\n// 连接两次的参数 aArgs.concat(Array.prototype.slice.call(arguments)));\n };\n\n // 保证bind出来的函数与借用的函数拥有相同的原型链\n fNOP.prototype = this.prototype;\n fBound.prototype = new fNOP();\n\n return fBound;\n };\n}\n</code></pre>\n\n<p>剩下还有<code>this instanceof fNOP ? this : oThis || this</code>是我百思不得其解的,最后在一篇博客上找到了答案,当bind返回的函数用作构造函数时,<code>this instanceof fNOP</code>为true,也就是定义中所说的忽略this的情况。</p>\n\n<p>至于为什么需要fNOP作为中介,第一是直接把<code>this.prototype</code>给<code>fBound.prototype</code>两者会指向同一个引用,其次上面一段的判断也无法达成。</p>\n\n<h4 id=\"es7\">扩展es7绑定函数</h4>\n\n<p>该语法还是ES7的一个提案,但是Babel转码器已经支持。</p>\n\n<p>函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。</p>\n\n<pre><code>foo::bar; \n// 等同于\nbar.bind(foo);\n\nfoo::bar(...arguments); \n// 等同于\nbar.apply(foo, arguments);\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty; \nfunction hasOwn(obj, key) { \n return obj::hasOwnProperty(key);\n}\n</code></pre>\n\n<p>详情见阮老师的es6入门吧!</p>\n\n<h4 id=\"\">最后扯两句</h4>\n\n<p>仅仅是bind就有这么多东西,如果把它放到整块知识中肯定能够有更多的收获,不过在晕之前还先把阶段性的结果写下来,到时候好整合!</p>","image":null,"featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1473729418209,"created_by":1,"updated_at":1473741583079,"updated_by":1,"published_at":1473733465651,"published_by":1},{"id":61,"uuid":"149d7964-a6f5-4ecc-8b5d-4a98d5b2dc1c","title":"有生之年系列之——编剧出来谈人生","slug":"you-sheng-zhi-nian-xi-lie-zhi-bian-ju-chu-lai-tan-ren-sheng","markdown":"大哥搞完事情,编剧又出来搞事情,哈哈哈哈哈哈!\n\n坐等下周大结局!!!\n\n![](https://ws2.sinaimg.cn/large/76fc6301gw1f7rw8zmuxej20x00qcq91.jpg)\n\n![](https://ws1.sinaimg.cn/large/76fc6301gw1f7rw9l9pi4j218w0oogst.jpg)\n\n![](https://ws3.sinaimg.cn/large/76fc6301gw1f7rwa2amjej214u0q2n66.jpg)\n\n![](https://ws4.sinaimg.cn/large/76fc6301gw1f7rwahfw9nj21bs0vg7s0.jpg)","html":"<p>大哥搞完事情,编剧又出来搞事情,哈哈哈哈哈哈!</p>\n\n<p>坐等下周大结局!!!</p>\n\n<p><img src=\"https://ws2.sinaimg.cn/large/76fc6301gw1f7rw8zmuxej20x00qcq91.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws1.sinaimg.cn/large/76fc6301gw1f7rw9l9pi4j218w0oogst.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws3.sinaimg.cn/large/76fc6301gw1f7rwa2amjej214u0q2n66.jpg\" alt=\"\" /></p>\n\n<p><img src=\"https://ws4.sinaimg.cn/large/76fc6301gw1f7rwahfw9nj21bs0vg7s0.jpg\" alt=\"\" /></p>","image":"/content/images/2016/09/76fc6301gw1f7rwahfw9nj21bs0vg7s0.jpg","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1473744682232,"created_by":1,"updated_at":1473756483186,"updated_by":1,"published_at":1473744849848,"published_by":1},{"id":62,"uuid":"43bc67b4-73d6-4764-b873-1d351631b482","title":"js 正则表达式小结","slug":"js-zheng-ze-biao-da-shi-xiao-jie","markdown":"这是之前欠下的,一直没空总结一下。看完剧,先把之前的总结了,在进行下一步的学习。在讲js的正则之前,先要讲一下正则的基本概念。结合一些例子,应该能快速了解吧!\n\n#### 基础\n\n##### 字符\n\n1、普通字符(单个字符)\n\n字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是\"普通字符\"。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。\n\n2、转义字符(单个字符)\n\n一些不便书写的字符,采用在前面加 \"\\\" 的方法。这些字符其实我们都已经熟知了。\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>可匹配</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\\r, \\n</td>\n<td>代表回车和换行符</td>\n</tr>\n<tr>\n<td>\\t</td>\n<td>制表符</td>\n</tr>\n<tr>\n<td>\\/</td>\n<td>/本身</td>\n</tr>\n<tr>\n<td>...</td>\n<td>...</td>\n</tr>\n</tbody>\n</table>\n\n还有其他一些在后边章节中有特殊用处的标点符号,在前面加 \"\\\" 后,就代表该符号本身。比如:^,$ 都有特殊意义,如果要想匹配字符串中 \"^\" 和 \"$\" 字符,则表达式就需要写成 \"\\^\" 和 \"\\$\"。\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>可匹配</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\\^</td>\n<td>匹配 ^ 符号本身</td>\n</tr>\n<tr>\n<td>\\$</td>\n<td>匹配 $ 符号本身</td>\n</tr>\n<tr>\n<td>\\.</td>\n<td>匹配小数点(.)本身</td>\n</tr>\n<tr>\n<td>...</td>\n<td>...</td>\n</tr>\n</tbody>\n</table>\n\n这些转义字符的匹配方法与 \"普通字符\" 是类似的。也是匹配与之相同的一个字符。\n\n3、自定义范围类(多个字符)\n\n上面两个匹配的都是单个字符,当我们需要匹配多个字符时,就可以用\"[]\"来表示一个范围,可以匹配范围内的任意字符,另外只匹配一个字符。\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>可匹配</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>[ab5@]</td>\n<td>匹配 \"a\" 或 \"b\" 或 \"5\" 或 \"@\"</td>\n</tr>\n<tr>\n<td>[^abc]</td>\n<td>匹配 \"a\",\"b\",\"c\" 之外的任意一个字符</td>\n</tr>\n<tr>\n<td>[f-k]</td>\n<td>匹配 \"f\"~\"k\" 之间的任意一个字母</td>\n</tr>\n<td>[^A-F0-3]</td>\n<td>匹配 \"A\"~\"F\",\"0\"~\"3\" 之外的任意一个字符</td>\n</tr>\n<tr>\n<td>...</td>\n<td>...</td>\n</tr>\n</tbody>\n</table>\n\n4、预范围类(多个字符)\n\n除了上面的自定义的范围类,为了写起来比较方便,还有一些预定义的范围类。\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>可匹配</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\\d</td>\n<td>任意一个数字,0~9 中的任意一个</td>\n</tr>\n<tr>\n<td>\\w</td>\n<td>任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中任意一个</td>\n</tr>\n<tr>\n<td>\\s</td>\n<td>包括空格、制表符、换页符等空白字符的其中任意一个</td>\n</tr>\n<td>.</td>\n<td>小数点可以匹配除了换行符(\\n)以外的任意一个字符</td>\n</tr>\n<tr>\n<td>...</td>\n<td>...</td>\n</tr>\n</tbody>\n</table>\n\n##### 量词\n\n上文的字符或者范围类匹配的都是原串中的一个字符,如果要重复匹配多次,则需要量词,主要有以下一些:\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>作用</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>{n}</td>\n<td>表达式重复n次,比如:\"\\w{2}\" 相当于 \"\\w\\w\";\"a{5}\" 相当于 \"aaaaa\"</td>\n</tr>\n<tr>\n<td>{m,n}</td>\n<td>表达式至少重复m次,最多重复n次,比如:\"ba{1,3}\"可以匹配 \"ba\"或\"baa\"或\"baaa\"</td>\n</tr>\n<tr>\n<td>{m,}</td>\n<td>表达式至少重复m次,比如:\"\\w\\d{2,}\"可以匹配 \"a12\",\"_456\",\"M12344\"...</td>\n</tr>\n<tr>\n<td>?</td>\n<td>匹配表达式0次或者1次,相当于 {0,1},比如:\"a[cd]?\"可以匹配 \"a\",\"ac\",\"ad\"</td>\n</tr>\n<tr>\n<td>+</td>\n<td>表达式至少出现1次,相当于 {1,},比如:\"a+b\"可以匹配 \"ab\",\"aab\",\"aaab\"...</td>\n</tr>\n<tr>\n<td>*</td>\n<td>表达式不出现或出现任意次,相当于 {0,},比如:\"\\^*b\"可以匹配 \"b\",\"^^^b\"...</td>\n</tr>\n</tbody>\n</table>\n\n##### 边界\n\n我们经常要匹配文件名后缀,这时候我们就会用到边界匹配符号。当然匹配开头、单词边界都很常见。\n\n<table>\n<thead>\n<tr>\n<td width=\"20%\">表达式</td>\n<td>作用</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>^</td>\n<td>与字符串开始的地方匹配,不匹配任何字符</td>\n</tr>\n<tr>\n<td>$</td>\n<td>与字符串结束的地方匹配,不匹配任何字符</td>\n</tr>\n<td>\\b</td>\n<td>匹配一个单词边界,也就是单词和空格之间的位置,不匹配任何字符</td>\n</tr>\n</tbody>\n</table>\n\n##### 分组\n\n分组的符号是\"()\",它的作用是在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰;并且取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到。\n\n另外还有一个\"|\",用于表达两个表达式之间或的关系。\n\n#### 高级\n\n##### 贪婪、非贪婪\n\n量词在匹配的时候是不确定的,例如aaaaa匹配a{1,3},可以匹配一到三次,但是如何选择呢?正则表达式默认规定是使用贪婪模式,也就是越多越好,尽量多地匹配。相反的还有一种非贪婪模式,要启用非贪婪模式只要在量词表达式之后跟上\"?\"即可。\n\n##### 反向引用\n\n表达式在匹配时,表达式引擎会将小括号 \"( )\" 包含的表达式所匹配到的字符串记录下来。并且记录下来的字符串我们可以通过\"$1\",\"$2\"的方式进行引用。举个例子,有电话号码13588884444,我们通常会把中间四位变成\"*\",来保护用户信息,这用正则表达式来做很简单。\n\n```\n'13588884444'.replace(/(\\d{3})\\d{4}(\\d{4})/g, '$1****$2')\n```\n\n上面的表达式将前三位和后四位分组,然后在替换的时候通过\"$1\"和\"$2\"进行反向引用,很神奇吧!\n\n##### 忽略分组\n\n假设上面的例子如果我们把中间四位也进行分组,但是中间四位没有记录下来的必要,此时我们就可以忽略分组,用\"(?:)\"表达式。\n\n##### 前瞻后顾(正向预搜索、反向预搜索)\n\njs目前只支持前瞻,所以只介绍前瞻了,后顾的道理是一样的。有的时候我们会有如下的需求,就是在匹配的时候,字符串的前后需要满足一定的要求。前瞻就是右边满足一定的要求,后顾就是左边满足一定的要求。例子如下:\n\n```\n'windows95,windows98,windows2000'.match(/windows(?=95|98)/g);//[\"windows\", \"windows\"]\n```\n\n上面的表达式匹配要求匹配windows的同时,后边是95或98,匹配的结果也就是[\"windows\", \"windows\"],可以看到条件是不会被匹配进结果的。\n\n#### js 中的正则\n\n##### es6之前\n\n###### 创建正则表达式:\n\n```\n// 构造函数\nvar regex = new RegExp('xyz', 'i');\nvar regex = new RegExp(/xyz/i);\nvar regex = new RegExp(/xyz/, 'i');//es6才支持\n// 字面量\nvar regex = /xyz/i;\n```\n\n###### 修饰符:\n\n<table>\n<thead>\n<tr><td width=\"20%\">修饰符</td><td>描述</td></tr>\n</thead>\n<tbody>\n<tr><td>i</td><td>执行对大小写不敏感的匹配。</td></tr>\n<tr><td>g</td><td>执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。</td></tr>\n<tr><td>m</td><td>执行多行匹配。</td></tr>\n</tbody>\n</table>\n\n###### RegExp 对象属性:\n\n<table>\n<thead>\n<tr><td width=\"20%\">属性</td><td>描述</td></tr>\n</thead>\n<tbody>\n<tr><td>global</td><td>RegExp 对象是否具有标志 g。</td></tr>\n<tr><td>ignoreCase</td><td>RegExp 对象是否具有标志 i。</td></tr>\n<tr><td>lastIndex</td><td>一个整数,标示开始下一次匹配的字符位置。</td></tr>\n<tr><td>multiline</td><td>RegExp 对象是否具有标志 m。</td></tr>\n<tr><td>source</td><td>正则表达式的源文本。</td></tr>\n</tbody>\n</table>\n\n###### Regex.prototype.test\n\ntest() 方法用于检测一个字符串是否匹配某个模式。存在则返回true,否则返回false。表单验证时经常会用到。\n\n###### Regex.prototype.exec \n\n```\n// Match \"quick brown\" followed by \"jumps\", ignoring characters in between\n// Remember \"brown\" and \"jumps\"\n// Ignore case\nvar re = /quick\\s(brown).+?(jumps)/ig;\nvar result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');\n```\n\n下面的表格展示这个脚本的返回值:\n<table>\n<thead>\n<tr>\n<td width=\"33%\">属性/索引</td>\n<td width=\"33%\">描述</td>\n<td>例子</td>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>[0]</td><td>匹配的全部字符串</td><td>\tQuick Brown Fox Jumps</td>\n</tr>\n<tr>\n<td>[1], ...[n ]</td><td>括号中的分组捕获</td><td>\t[1] = Brown[2] = Jumps</td>\n</tr>\n<tr>\n<td>index</td><td>匹配到的字符位于原始字符串的基于0的索引值</td><td>4</td>\n</tr>\t\t\n<tr>\n<td>input</td><td>原始字符串</td><td>The Quick Brown Fox Jumps Over The Lazy Dog</td>\n</tr>\n</tbody>\n</table>\t\n\n全局模式下:它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。\n\n> 注意:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0。\n\n###### String.prototype.match \n\n非全局模式:和Regex.prototype.exec返回的信息相同。\n\n全局模式:全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。\n\n> 注意:在全局检索模式下,match() 即不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用 RegExp.prototype.exec()。\n\n###### String.prototype.search\n\nsearch() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置。\n\n###### String.prototype.replace\n\n字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。\nreplacement 可以是字符串,也可以是函数。如果它是字符串,那么每个匹配都将由字符串替换。\n\n但是 replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换,也就是反向引用。\n\n###### String.prototype.split\n\n这个比较简单,举个例子吧:\n\n```\n'How are you doing today?'.split(/o/); //[\"H\", \"w are y\", \"u d\", \"ing t\", \"day?\"]\n```\n\n另外这个函数还接收第二个参数,表示数组的最大长度。\n\n##### es6正则扩展\n\n1、构造函数支持第一个参数为正则的同时,还可以使用修饰符参数。\n\n```\nvar regex = new RegExp(/xyz/, 'i');\n// ES5 Uncaught TypeError: Cannot supply flags when constructing one RegExp from another\n// ES6支持了这种写法\n```\n\n2、字符串的四个正则相关的方法定义在了Regex对象上。\n\n3、加入了u修饰符,含义为“Unicode模式”,用来正确处理大于\\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。\n\n4、加入了y修饰符,y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。\n\n```\nvar s = 'aaa_aa_a';\nvar r1 = /a+/g;\nvar r2 = /a+/y;\n\nr1.exec(s) // [\"aaa\"]\nr2.exec(s) // [\"aaa\"]\n\nr1.exec(s) // [\"aa\"]\nr2.exec(s) // null\n```\n\n5、正则对象增加了两个属性,flags和sticky。分别表示修饰符和是否设置y修饰符。\n\n##### es7正则扩展\n\n1、RegExp.escape()方法。\n\n2、加入后顾(反向预搜索)哈哈,迟早要支持的吧!\n\nes6和es7的部分只是列了一下,具体还是看阮一峰老师的[es6入门](http://es6.ruanyifeng.com/#docs/regex)。\n\n参考资料:\n\n1. [es6入门](http://es6.ruanyifeng.com/#docs/regex)\n2. [js正则表达式语法](http://blog.csdn.net/zaifendou/article/details/5746988)\n3. [将正则表达式图形化工具](https://regexper.com/)","html":"<p>这是之前欠下的,一直没空总结一下。看完剧,先把之前的总结了,在进行下一步的学习。在讲js的正则之前,先要讲一下正则的基本概念。结合一些例子,应该能快速了解吧!</p>\n\n<h4 id=\"\">基础</h4>\n\n<h5 id=\"\">字符</h5>\n\n<p>1、普通字符(单个字符)</p>\n\n<p>字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是\"普通字符\"。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。</p>\n\n<p>2、转义字符(单个字符)</p>\n\n<p>一些不便书写的字符,采用在前面加 \"\\\" 的方法。这些字符其实我们都已经熟知了。</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>可匹配</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>\\r, \\n</td> \n<td>代表回车和换行符</td> \n</tr> \n<tr> \n<td>\\t</td> \n<td>制表符</td> \n</tr> \n<tr> \n<td>\\/</td> \n<td>/本身</td> \n</tr> \n<tr> \n<td>...</td> \n<td>...</td> \n</tr> \n</tbody> \n</table>\n\n<p>还有其他一些在后边章节中有特殊用处的标点符号,在前面加 \"\\\" 后,就代表该符号本身。比如:^,$ 都有特殊意义,如果要想匹配字符串中 \"^\" 和 \"$\" 字符,则表达式就需要写成 \"\\^\" 和 \"\\$\"。</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>可匹配</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>\\^</td> \n<td>匹配 ^ 符号本身</td> \n</tr> \n<tr> \n<td>\\$</td> \n<td>匹配 $ 符号本身</td> \n</tr> \n<tr> \n<td>\\.</td> \n<td>匹配小数点(.)本身</td> \n</tr> \n<tr> \n<td>...</td> \n<td>...</td> \n</tr> \n</tbody> \n</table>\n\n<p>这些转义字符的匹配方法与 \"普通字符\" 是类似的。也是匹配与之相同的一个字符。</p>\n\n<p>3、自定义范围类(多个字符)</p>\n\n<p>上面两个匹配的都是单个字符,当我们需要匹配多个字符时,就可以用\"[]\"来表示一个范围,可以匹配范围内的任意字符,另外只匹配一个字符。</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>可匹配</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>[ab5@]</td> \n<td>匹配 \"a\" 或 \"b\" 或 \"5\" 或 \"@\"</td> \n</tr> \n<tr> \n<td>[^abc]</td> \n<td>匹配 \"a\",\"b\",\"c\" 之外的任意一个字符</td> \n</tr> \n<tr> \n<td>[f-k]</td> \n<td>匹配 \"f\"~\"k\" 之间的任意一个字母</td> \n</tr> \n<td>[^A-F0-3]</td> \n<td>匹配 \"A\"~\"F\",\"0\"~\"3\" 之外的任意一个字符</td> \n</tr> \n<tr> \n<td>...</td> \n<td>...</td> \n</tr> \n</tbody> \n</table>\n\n<p>4、预范围类(多个字符)</p>\n\n<p>除了上面的自定义的范围类,为了写起来比较方便,还有一些预定义的范围类。</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>可匹配</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>\\d</td> \n<td>任意一个数字,0~9 中的任意一个</td> \n</tr> \n<tr> \n<td>\\w</td> \n<td>任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中任意一个</td> \n</tr> \n<tr> \n<td>\\s</td> \n<td>包括空格、制表符、换页符等空白字符的其中任意一个</td> \n</tr> \n<td>.</td> \n<td>小数点可以匹配除了换行符(\\n)以外的任意一个字符</td> \n</tr> \n<tr> \n<td>...</td> \n<td>...</td> \n</tr> \n</tbody> \n</table>\n\n<h5 id=\"\">量词</h5>\n\n<p>上文的字符或者范围类匹配的都是原串中的一个字符,如果要重复匹配多次,则需要量词,主要有以下一些:</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>作用</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>{n}</td> \n<td>表达式重复n次,比如:\"\\w{2}\" 相当于 \"\\w\\w\";\"a{5}\" 相当于 \"aaaaa\"</td> \n</tr> \n<tr> \n<td>{m,n}</td> \n<td>表达式至少重复m次,最多重复n次,比如:\"ba{1,3}\"可以匹配 \"ba\"或\"baa\"或\"baaa\"</td> \n</tr> \n<tr> \n<td>{m,}</td> \n<td>表达式至少重复m次,比如:\"\\w\\d{2,}\"可以匹配 \"a12\",\"_456\",\"M12344\"...</td> \n</tr> \n<tr> \n<td>?</td> \n<td>匹配表达式0次或者1次,相当于 {0,1},比如:\"a[cd]?\"可以匹配 \"a\",\"ac\",\"ad\"</td> \n</tr> \n<tr> \n<td>+</td> \n<td>表达式至少出现1次,相当于 {1,},比如:\"a+b\"可以匹配 \"ab\",\"aab\",\"aaab\"...</td> \n</tr> \n<tr> \n<td>*</td> \n<td>表达式不出现或出现任意次,相当于 {0,},比如:\"\\^*b\"可以匹配 \"b\",\"^^^b\"...</td> \n</tr> \n</tbody> \n</table>\n\n<h5 id=\"\">边界</h5>\n\n<p>我们经常要匹配文件名后缀,这时候我们就会用到边界匹配符号。当然匹配开头、单词边界都很常见。</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"20%\">表达式</td> \n<td>作用</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>^</td> \n<td>与字符串开始的地方匹配,不匹配任何字符</td> \n</tr> \n<tr> \n<td>$</td> \n<td>与字符串结束的地方匹配,不匹配任何字符</td> \n</tr> \n<td>\\b</td> \n<td>匹配一个单词边界,也就是单词和空格之间的位置,不匹配任何字符</td> \n</tr> \n</tbody> \n</table>\n\n<h5 id=\"\">分组</h5>\n\n<p>分组的符号是\"()\",它的作用是在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰;并且取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到。</p>\n\n<p>另外还有一个\"|\",用于表达两个表达式之间或的关系。</p>\n\n<h4 id=\"\">高级</h4>\n\n<h5 id=\"\">贪婪、非贪婪</h5>\n\n<p>量词在匹配的时候是不确定的,例如aaaaa匹配a{1,3},可以匹配一到三次,但是如何选择呢?正则表达式默认规定是使用贪婪模式,也就是越多越好,尽量多地匹配。相反的还有一种非贪婪模式,要启用非贪婪模式只要在量词表达式之后跟上\"?\"即可。</p>\n\n<h5 id=\"\">反向引用</h5>\n\n<p>表达式在匹配时,表达式引擎会将小括号 \"( )\" 包含的表达式所匹配到的字符串记录下来。并且记录下来的字符串我们可以通过\"$1\",\"$2\"的方式进行引用。举个例子,有电话号码13588884444,我们通常会把中间四位变成\"*\",来保护用户信息,这用正则表达式来做很简单。</p>\n\n<pre><code>'13588884444'.replace(/(\\d{3})\\d{4}(\\d{4})/g, '$1****$2') \n</code></pre>\n\n<p>上面的表达式将前三位和后四位分组,然后在替换的时候通过\"$1\"和\"$2\"进行反向引用,很神奇吧!</p>\n\n<h5 id=\"\">忽略分组</h5>\n\n<p>假设上面的例子如果我们把中间四位也进行分组,但是中间四位没有记录下来的必要,此时我们就可以忽略分组,用\"(?:)\"表达式。</p>\n\n<h5 id=\"\">前瞻后顾(正向预搜索、反向预搜索)</h5>\n\n<p>js目前只支持前瞻,所以只介绍前瞻了,后顾的道理是一样的。有的时候我们会有如下的需求,就是在匹配的时候,字符串的前后需要满足一定的要求。前瞻就是右边满足一定的要求,后顾就是左边满足一定的要求。例子如下:</p>\n\n<pre><code>'windows95,windows98,windows2000'.match(/windows(?=95|98)/g);//[\"windows\", \"windows\"] \n</code></pre>\n\n<p>上面的表达式匹配要求匹配windows的同时,后边是95或98,匹配的结果也就是[\"windows\", \"windows\"],可以看到条件是不会被匹配进结果的。</p>\n\n<h4 id=\"js\">js 中的正则</h4>\n\n<h5 id=\"es6\">es6之前</h5>\n\n<h6 id=\"\">创建正则表达式:</h6>\n\n<pre><code>// 构造函数\nvar regex = new RegExp('xyz', 'i'); \nvar regex = new RegExp(/xyz/i); \nvar regex = new RegExp(/xyz/, 'i');//es6才支持 \n// 字面量\nvar regex = /xyz/i; \n</code></pre>\n\n<h6 id=\"\">修饰符:</h6>\n\n<table> \n<thead> \n<tr><td width=\"20%\">修饰符</td><td>描述</td></tr> \n</thead> \n<tbody> \n<tr><td>i</td><td>执行对大小写不敏感的匹配。</td></tr> \n<tr><td>g</td><td>执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。</td></tr> \n<tr><td>m</td><td>执行多行匹配。</td></tr> \n</tbody> \n</table>\n\n<h6 id=\"regexp\">RegExp 对象属性:</h6>\n\n<table> \n<thead> \n<tr><td width=\"20%\">属性</td><td>描述</td></tr> \n</thead> \n<tbody> \n<tr><td>global</td><td>RegExp 对象是否具有标志 g。</td></tr> \n<tr><td>ignoreCase</td><td>RegExp 对象是否具有标志 i。</td></tr> \n<tr><td>lastIndex</td><td>一个整数,标示开始下一次匹配的字符位置。</td></tr> \n<tr><td>multiline</td><td>RegExp 对象是否具有标志 m。</td></tr> \n<tr><td>source</td><td>正则表达式的源文本。</td></tr> \n</tbody> \n</table>\n\n<h6 id=\"regexprototypetest\">Regex.prototype.test</h6>\n\n<p>test() 方法用于检测一个字符串是否匹配某个模式。存在则返回true,否则返回false。表单验证时经常会用到。</p>\n\n<h6 id=\"regexprototypeexec\">Regex.prototype.exec</h6>\n\n<pre><code>// Match \"quick brown\" followed by \"jumps\", ignoring characters in between\n// Remember \"brown\" and \"jumps\"\n// Ignore case\nvar re = /quick\\s(brown).+?(jumps)/ig; \nvar result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); \n</code></pre>\n\n<p>下面的表格展示这个脚本的返回值:</p>\n\n<table> \n<thead> \n<tr> \n<td width=\"33%\">属性/索引</td> \n<td width=\"33%\">描述</td> \n<td>例子</td> \n</tr> \n</thead> \n<tbody> \n<tr> \n<td>[0]</td><td>匹配的全部字符串</td><td> Quick Brown Fox Jumps</td> \n</tr> \n<tr> \n<td>[1], ...[n ]</td><td>括号中的分组捕获</td><td> [1] = Brown[2] = Jumps</td> \n</tr> \n<tr> \n<td>index</td><td>匹配到的字符位于原始字符串的基于0的索引值</td><td>4</td> \n</tr> \n<tr> \n<td>input</td><td>原始字符串</td><td>The Quick Brown Fox Jumps Over The Lazy Dog</td> \n</tr> \n</tbody> \n</table> \n\n<p>全局模式下:它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。</p>\n\n<blockquote>\n <p>注意:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0。</p>\n</blockquote>\n\n<h6 id=\"stringprototypematch\">String.prototype.match</h6>\n\n<p>非全局模式:和Regex.prototype.exec返回的信息相同。</p>\n\n<p>全局模式:全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。</p>\n\n<blockquote>\n <p>注意:在全局检索模式下,match() 即不提供与子表达式匹配的文本的信息,也不声明每个匹配子串的位置。如果您需要这些全局检索的信息,可以使用 RegExp.prototype.exec()。</p>\n</blockquote>\n\n<h6 id=\"stringprototypesearch\">String.prototype.search</h6>\n\n<p>search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置。</p>\n\n<h6 id=\"stringprototypereplace\">String.prototype.replace</h6>\n\n<p>字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。\nreplacement 可以是字符串,也可以是函数。如果它是字符串,那么每个匹配都将由字符串替换。</p>\n\n<p>但是 replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换,也就是反向引用。</p>\n\n<h6 id=\"stringprototypesplit\">String.prototype.split</h6>\n\n<p>这个比较简单,举个例子吧:</p>\n\n<pre><code>'How are you doing today?'.split(/o/); //[\"H\", \"w are y\", \"u d\", \"ing t\", \"day?\"] \n</code></pre>\n\n<p>另外这个函数还接收第二个参数,表示数组的最大长度。</p>\n\n<h5 id=\"es6\">es6正则扩展</h5>\n\n<p>1、构造函数支持第一个参数为正则的同时,还可以使用修饰符参数。</p>\n\n<pre><code>var regex = new RegExp(/xyz/, 'i'); \n// ES5 Uncaught TypeError: Cannot supply flags when constructing one RegExp from another\n// ES6支持了这种写法\n</code></pre>\n\n<p>2、字符串的四个正则相关的方法定义在了Regex对象上。</p>\n\n<p>3、加入了u修饰符,含义为“Unicode模式”,用来正确处理大于\\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。</p>\n\n<p>4、加入了y修饰符,y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。</p>\n\n<pre><code>var s = 'aaa_aa_a'; \nvar r1 = /a+/g; \nvar r2 = /a+/y;\n\nr1.exec(s) // [\"aaa\"] \nr2.exec(s) // [\"aaa\"]\n\nr1.exec(s) // [\"aa\"] \nr2.exec(s) // null \n</code></pre>\n\n<p>5、正则对象增加了两个属性,flags和sticky。分别表示修饰符和是否设置y修饰符。</p>\n\n<h5 id=\"es7\">es7正则扩展</h5>\n\n<p>1、RegExp.escape()方法。</p>\n\n<p>2、加入后顾(反向预搜索)哈哈,迟早要支持的吧!</p>\n\n<p>es6和es7的部分只是列了一下,具体还是看阮一峰老师的<a href=\"http://es6.ruanyifeng.com/#docs/regex\">es6入门</a>。</p>\n\n<p>参考资料:</p>\n\n<ol>\n<li><a href=\"http://es6.ruanyifeng.com/#docs/regex\">es6入门</a> </li>\n<li><a href=\"http://blog.csdn.net/zaifendou/article/details/5746988\">js正则表达式语法</a> </li>\n<li><a href=\"https://regexper.com/\">将正则表达式图形化工具</a></li>\n</ol>","image":"/content/images/2016/09/QQ20160913-5-2x.png","featured":0,"page":0,"status":"published","language":"zh_CN","meta_title":null,"meta_description":null,"author_id":1,"created_at":1473745279085,"created_by":1,"updated_at":1473755764810,"updated_by":1,"published_at":1473748641713,"published_by":1}],"users":[{"id":1,"uuid":"f183c222-672a-47e1-80a4-561830ba1d27","name":"m2mbob","slug":"m2mbob","password":"$2a$10$Xcdj.QcZXVRWoWmvgQTC/.QvFXigb4CHjfLM0kE.44gNKy/j0Z6oq","email":"[email protected]","image":"/content/images/2016/05/113.jpeg","cover":null,"bio":"","website":"https://xxn520.github.io/jiayi520/","location":"杭州","accessibility":null,"status":"active","language":"en_US","meta_title":null,"meta_description":null,"tour":null,"last_login":1472197283786,"created_at":1463567133080,"created_by":1,"updated_at":1472197283787,"updated_by":1},{"id":5,"uuid":"26139dd3-2a1d-41f0-a185-654731c39cef","name":"laowei38ban","slug":"laowei38ban","password":"$2a$10$5k4p6NzBZwU7vcH4zdMtl.mxkc.oXKCywxgDjRGoGXaGc0CVnjDc.","email":"[email protected]","image":null,"cover":null,"bio":null,"website":null,"location":null,"accessibility":null,"status":"invited-pending","language":"zh_CN","meta_title":null,"meta_description":null,"tour":null,"last_login":null,"created_at":1467527925700,"created_by":1,"updated_at":1467528053760,"updated_by":5}],"roles":[{"id":1,"uuid":"a205663c-0051-448b-8d64-c083afc8cd1f","name":"Administrator","description":"Administrators","created_at":1463567128189,"created_by":1,"updated_at":1463567128189,"updated_by":1},{"id":2,"uuid":"f3c3f834-15cf-4992-b37e-9fc241cce9ea","name":"Editor","description":"Editors","created_at":1463567128217,"created_by":1,"updated_at":1463567128217,"updated_by":1},{"id":3,"uuid":"974b0d0e-2a8c-4c45-9932-dbb233bc9cc8","name":"Author","description":"Authors","created_at":1463567128243,"created_by":1,"updated_at":1463567128243,"updated_by":1},{"id":4,"uuid":"e1e437c2-a08b-4152-bb91-130ff9670e31","name":"Owner","description":"Blog Owner","created_at":1463567128271,"created_by":1,"updated_at":1463567128271,"updated_by":1}],"roles_users":[{"id":1,"role_id":4,"user_id":1},{"id":2,"role_id":3,"user_id":2},{"id":3,"role_id":3,"user_id":3},{"id":4,"role_id":3,"user_id":4},{"id":5,"role_id":3,"user_id":5}],"permissions":[{"id":1,"uuid":"17ffb52c-5e47-4520-bb3a-923eee539fae","name":"Export database","object_type":"db","action_type":"exportContent","object_id":null,"created_at":1463567128298,"created_by":1,"updated_at":1463567128298,"updated_by":1},{"id":2,"uuid":"0b603a34-66db-4290-9f1c-17411a32d5af","name":"Import database","object_type":"db","action_type":"importContent","object_id":null,"created_at":1463567128326,"created_by":1,"updated_at":1463567128326,"updated_by":1},{"id":3,"uuid":"63d14f73-132e-4504-9f3c-a5496e529604","name":"Delete all content","object_type":"db","action_type":"deleteAllContent","object_id":null,"created_at":1463567128351,"created_by":1,"updated_at":1463567128351,"updated_by":1},{"id":4,"uuid":"4b3cd8a9-e96e-45f1-8746-ba36e61d837b","name":"Send mail","object_type":"mail","action_type":"send","object_id":null,"created_at":1463567128379,"created_by":1,"updated_at":1463567128379,"updated_by":1},{"id":5,"uuid":"b3a9c3fe-26ef-4f19-895a-bb6b09cfa07a","name":"Browse notifications","object_type":"notification","action_type":"browse","object_id":null,"created_at":1463567128407,"created_by":1,"updated_at":1463567128407,"updated_by":1},{"id":6,"uuid":"7d1c64de-07c8-49e8-87cf-eef785bd3500","name":"Add notifications","object_type":"notification","action_type":"add","object_id":null,"created_at":1463567128431,"created_by":1,"updated_at":1463567128431,"updated_by":1},{"id":7,"uuid":"d490cd6d-e086-4e27-86f0-4a4638f020b0","name":"Delete notifications","object_type":"notification","action_type":"destroy","object_id":null,"created_at":1463567128456,"created_by":1,"updated_at":1463567128456,"updated_by":1},{"id":8,"uuid":"e00cf85a-7d10-4908-9952-164dbb7e76d5","name":"Browse posts","object_type":"post","action_type":"browse","object_id":null,"created_at":1463567128480,"created_by":1,"updated_at":1463567128480,"updated_by":1},{"id":9,"uuid":"388edf22-9724-4936-840b-4972018c83f5","name":"Read posts","object_type":"post","action_type":"read","object_id":null,"created_at":1463567128506,"created_by":1,"updated_at":1463567128506,"updated_by":1},{"id":10,"uuid":"8cafbddb-afdb-4ce8-82be-17dd7dab9b39","name":"Edit posts","object_type":"post","action_type":"edit","object_id":null,"created_at":1463567128534,"created_by":1,"updated_at":1463567128534,"updated_by":1},{"id":11,"uuid":"2f54798f-05bc-45b4-a477-0cae3a8c15fb","name":"Add posts","object_type":"post","action_type":"add","object_id":null,"created_at":1463567128556,"created_by":1,"updated_at":1463567128556,"updated_by":1},{"id":12,"uuid":"b85480a8-ff4d-4ed1-baad-595ea2452fca","name":"Delete posts","object_type":"post","action_type":"destroy","object_id":null,"created_at":1463567128578,"created_by":1,"updated_at":1463567128578,"updated_by":1},{"id":13,"uuid":"4760b85c-cc79-4ee4-87c1-643bc500136e","name":"Browse settings","object_type":"setting","action_type":"browse","object_id":null,"created_at":1463567128602,"created_by":1,"updated_at":1463567128602,"updated_by":1},{"id":14,"uuid":"b60aa8d7-8605-4c2e-84bb-35fd98b9e05b","name":"Read settings","object_type":"setting","action_type":"read","object_id":null,"created_at":1463567128631,"created_by":1,"updated_at":1463567128631,"updated_by":1},{"id":15,"uuid":"a7437212-afe7-4df4-9370-9e4cb8507d64","name":"Edit settings","object_type":"setting","action_type":"edit","object_id":null,"created_at":1463567128655,"created_by":1,"updated_at":1463567128655,"updated_by":1},{"id":16,"uuid":"c334e5b6-c398-4b0b-ad00-9b3c747aa564","name":"Generate slugs","object_type":"slug","action_type":"generate","object_id":null,"created_at":1463567128688,"created_by":1,"updated_at":1463567128688,"updated_by":1},{"id":17,"uuid":"3338699f-c4e5-41b8-9ca4-4d22be830633","name":"Browse tags","object_type":"tag","action_type":"browse","object_id":null,"created_at":1463567128719,"created_by":1,"updated_at":1463567128719,"updated_by":1},{"id":18,"uuid":"b58d136d-763d-4cfc-bcc3-ab32fdc2d4d1","name":"Read tags","object_type":"tag","action_type":"read","object_id":null,"created_at":1463567128746,"created_by":1,"updated_at":1463567128746,"updated_by":1},{"id":19,"uuid":"76384049-8ad7-4233-9259-f7b511018001","name":"Edit tags","object_type":"tag","action_type":"edit","object_id":null,"created_at":1463567128770,"created_by":1,"updated_at":1463567128770,"updated_by":1},{"id":20,"uuid":"2c117ad5-76ab-438a-9e2e-2a707d15b2fa","name":"Add tags","object_type":"tag","action_type":"add","object_id":null,"created_at":1463567128795,"created_by":1,"updated_at":1463567128795,"updated_by":1},{"id":21,"uuid":"94bf77cf-2356-47ba-be2b-6e51ff3b4e0b","name":"Delete tags","object_type":"tag","action_type":"destroy","object_id":null,"created_at":1463567128820,"created_by":1,"updated_at":1463567128820,"updated_by":1},{"id":22,"uuid":"6f3064f3-7109-4049-807b-0b5986a00077","name":"Browse themes","object_type":"theme","action_type":"browse","object_id":null,"created_at":1463567128848,"created_by":1,"updated_at":1463567128848,"updated_by":1},{"id":23,"uuid":"589fe891-4edb-4504-bcdd-443950122687","name":"Edit themes","object_type":"theme","action_type":"edit","object_id":null,"created_at":1463567128876,"created_by":1,"updated_at":1463567128876,"updated_by":1},{"id":24,"uuid":"91e1d531-9886-4cf5-ab90-868e851150ee","name":"Browse users","object_type":"user","action_type":"browse","object_id":null,"created_at":1463567128903,"created_by":1,"updated_at":1463567128903,"updated_by":1},{"id":25,"uuid":"f898d0b8-7a24-4da8-9ae0-d2d71082c0fe","name":"Read users","object_type":"user","action_type":"read","object_id":null,"created_at":1463567128926,"created_by":1,"updated_at":1463567128926,"updated_by":1},{"id":26,"uuid":"69769fb8-0707-41d9-a941-108e78458828","name":"Edit users","object_type":"user","action_type":"edit","object_id":null,"created_at":1463567128950,"created_by":1,"updated_at":1463567128950,"updated_by":1},{"id":27,"uuid":"f077887e-a6bb-4d7e-a5a2-669f2c709db7","name":"Add users","object_type":"user","action_type":"add","object_id":null,"created_at":1463567128976,"created_by":1,"updated_at":1463567128976,"updated_by":1},{"id":28,"uuid":"1a13365e-a47e-4355-92fd-5ca74fa7259e","name":"Delete users","object_type":"user","action_type":"destroy","object_id":null,"created_at":1463567129004,"created_by":1,"updated_at":1463567129004,"updated_by":1},{"id":29,"uuid":"d331b27f-cd04-43f2-8e13-4cc90a9fe017","name":"Assign a role","object_type":"role","action_type":"assign","object_id":null,"created_at":1463567129036,"created_by":1,"updated_at":1463567129036,"updated_by":1},{"id":30,"uuid":"d6a8f870-d4aa-4565-bbb9-08274700fcd7","name":"Browse roles","object_type":"role","action_type":"browse","object_id":null,"created_at":1463567129059,"created_by":1,"updated_at":1463567129059,"updated_by":1}],"permissions_users":[],"permissions_roles":[{"id":1,"role_id":1,"permission_id":1},{"id":2,"role_id":1,"permission_id":2},{"id":3,"role_id":1,"permission_id":3},{"id":4,"role_id":1,"permission_id":4},{"id":5,"role_id":1,"permission_id":5},{"id":6,"role_id":1,"permission_id":6},{"id":7,"role_id":1,"permission_id":7},{"id":8,"role_id":1,"permission_id":8},{"id":9,"role_id":1,"permission_id":9},{"id":10,"role_id":1,"permission_id":10},{"id":11,"role_id":1,"permission_id":11},{"id":12,"role_id":1,"permission_id":12},{"id":13,"role_id":1,"permission_id":13},{"id":14,"role_id":1,"permission_id":14},{"id":15,"role_id":1,"permission_id":15},{"id":16,"role_id":1,"permission_id":16},{"id":17,"role_id":1,"permission_id":17},{"id":18,"role_id":1,"permission_id":18},{"id":19,"role_id":1,"permission_id":19},{"id":20,"role_id":1,"permission_id":20},{"id":21,"role_id":1,"permission_id":21},{"id":22,"role_id":1,"permission_id":22},{"id":23,"role_id":1,"permission_id":23},{"id":24,"role_id":1,"permission_id":24},{"id":25,"role_id":1,"permission_id":25},{"id":26,"role_id":1,"permission_id":26},{"id":27,"role_id":1,"permission_id":27},{"id":28,"role_id":1,"permission_id":28},{"id":29,"role_id":1,"permission_id":29},{"id":30,"role_id":1,"permission_id":30},{"id":31,"role_id":2,"permission_id":8},{"id":32,"role_id":2,"permission_id":9},{"id":33,"role_id":2,"permission_id":10},{"id":34,"role_id":2,"permission_id":11},{"id":35,"role_id":2,"permission_id":12},{"id":36,"role_id":2,"permission_id":13},{"id":37,"role_id":2,"permission_id":14},{"id":38,"role_id":2,"permission_id":16},{"id":39,"role_id":2,"permission_id":17},{"id":40,"role_id":2,"permission_id":18},{"id":41,"role_id":2,"permission_id":19},{"id":42,"role_id":2,"permission_id":20},{"id":43,"role_id":2,"permission_id":21},{"id":44,"role_id":2,"permission_id":24},{"id":45,"role_id":2,"permission_id":25},{"id":46,"role_id":2,"permission_id":26},{"id":47,"role_id":2,"permission_id":27},{"id":48,"role_id":2,"permission_id":28},{"id":49,"role_id":2,"permission_id":29},{"id":50,"role_id":2,"permission_id":30},{"id":51,"role_id":3,"permission_id":8},{"id":52,"role_id":3,"permission_id":9},{"id":53,"role_id":3,"permission_id":11},{"id":54,"role_id":3,"permission_id":13},{"id":55,"role_id":3,"permission_id":14},{"id":56,"role_id":3,"permission_id":16},{"id":57,"role_id":3,"permission_id":17},{"id":58,"role_id":3,"permission_id":18},{"id":59,"role_id":3,"permission_id":20},{"id":60,"role_id":3,"permission_id":24},{"id":61,"role_id":3,"permission_id":25},{"id":62,"role_id":3,"permission_id":30}],"permissions_apps":[],"settings":[{"id":1,"uuid":"07767b0a-31a8-4f7c-9089-0cac6be45867","key":"databaseVersion","value":"004","type":"core","created_at":1463567133162,"created_by":1,"updated_at":1463567133162,"updated_by":1},{"id":2,"uuid":"64a07b8e-9ca6-4464-940a-985f4241f6e7","key":"dbHash","value":"e00711f4-02e7-4f60-a9c5-b870666a2a83","type":"core","created_at":1463567133162,"created_by":1,"updated_at":1463567133836,"updated_by":1},{"id":3,"uuid":"d57d0b4c-9dbf-4810-a010-651b42825bf5","key":"nextUpdateCheck","value":"1473842795","type":"core","created_at":1463567133163,"created_by":1,"updated_at":1473756392671,"updated_by":1},{"id":4,"uuid":"b43fd992-cc8f-4f1e-bc47-c060dfdcae3f","key":"displayUpdateNotification","value":"0.7.0","type":"core","created_at":1463567133163,"created_by":1,"updated_at":1473756392673,"updated_by":1},{"id":5,"uuid":"0b0d23f5-c9f3-4a41-9c47-ba9170f1cec5","key":"title","value":"金色梦乡","type":"blog","created_at":1463567133163,"created_by":1,"updated_at":1473660127373,"updated_by":1},{"id":6,"uuid":"e53b4cef-36a8-4208-9605-9dd6062e7407","key":"description","value":"Thoughts, stories and ideas.","type":"blog","created_at":1463567133163,"created_by":1,"updated_at":1473660127374,"updated_by":1},{"id":7,"uuid":"adccbe11-7283-472b-b363-b517530d874c","key":"logo","value":"","type":"blog","created_at":1463567133163,"created_by":1,"updated_at":1473660127375,"updated_by":1},{"id":8,"uuid":"9ef9dbff-161f-45db-921e-4857f9d617b3","key":"cover","value":"/content/images/2016/08/f1a79dc7f4573842399295901c16baa8.jpg","type":"blog","created_at":1463567133164,"created_by":1,"updated_at":1473660127377,"updated_by":1},{"id":9,"uuid":"c205e750-b7b4-4a65-be94-e33aa231e523","key":"defaultLang","value":"en_US","type":"blog","created_at":1463567133164,"created_by":1,"updated_at":1473660127378,"updated_by":1},{"id":10,"uuid":"d7917f90-456e-42d0-a6fe-429f84898f0b","key":"postsPerPage","value":"5","type":"blog","created_at":1463567133164,"created_by":1,"updated_at":1473660127379,"updated_by":1},{"id":11,"uuid":"66503e4a-acf0-4aca-8dbc-0e36ae39bcdd","key":"forceI18n","value":"true","type":"blog","created_at":1463567133165,"created_by":1,"updated_at":1473660127380,"updated_by":1},{"id":12,"uuid":"8755c9a1-7e8c-42c6-a665-df37701f6072","key":"permalinks","value":"/:year/:month/:day/:slug/","type":"blog","created_at":1463567133165,"created_by":1,"updated_at":1473660127381,"updated_by":1},{"id":13,"uuid":"1b0a112b-d8b5-47b6-8b13-56c323eaa83e","key":"ghost_head","value":"","type":"blog","created_at":1463567133165,"created_by":1,"updated_at":1473660127385,"updated_by":1},{"id":14,"uuid":"9675e29f-a962-49a0-ad3a-f15533d0f7dc","key":"ghost_foot","value":"","type":"blog","created_at":1463567133165,"created_by":1,"updated_at":1473660127386,"updated_by":1},{"id":15,"uuid":"6d4ab5d5-75d4-4b1e-b5ec-6435f411cb3a","key":"labs","value":"{}","type":"blog","created_at":1463567133165,"created_by":1,"updated_at":1473660127387,"updated_by":1},{"id":16,"uuid":"a10d0aed-69e5-4de0-899c-8ad0fbc6c955","key":"navigation","value":"[{\"label\":\"Home\",\"url\":\"/\"},{\"label\":\"admin\",\"url\":\"/ghost/\"},{\"label\":\"mail\",\"url\":\"http://mail.m2mbob.cn/\"}]","type":"blog","created_at":1463567133166,"created_by":1,"updated_at":1473660127388,"updated_by":1},{"id":17,"uuid":"821e540b-22d6-49b5-acb5-bc963b77100f","key":"activeApps","value":"[]","type":"app","created_at":1463567133166,"created_by":1,"updated_at":1463567133166,"updated_by":1},{"id":18,"uuid":"2a154f41-dcc1-4035-bbe9-886d2f715d8e","key":"installedApps","value":"[]","type":"app","created_at":1463567133167,"created_by":1,"updated_at":1472228447220,"updated_by":1},{"id":19,"uuid":"96f6c4a2-30d4-4490-968d-449261420d65","key":"isPrivate","value":"false","type":"private","created_at":1463567133167,"created_by":1,"updated_at":1473660127389,"updated_by":1},{"id":20,"uuid":"90857a68-2fe1-4798-9654-88a635a54ddb","key":"password","value":"null","type":"private","created_at":1463567133167,"created_by":1,"updated_at":1473660127390,"updated_by":1},{"id":21,"uuid":"fcd59843-2747-411c-8ab4-7e97a1c9c3b4","key":"activeTheme","value":"casper","type":"theme","created_at":1463567133166,"created_by":1,"updated_at":1473660127383,"updated_by":1}],"tags":[{"id":1,"uuid":"ecb77cf1-1be2-443a-b555-f1c1fa8992fb","name":"get-started","slug":"get-started","description":"get-started","image":"","hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463567128109,"created_by":1,"updated_at":1463754330921,"updated_by":1},{"id":2,"uuid":"f73b4d4f-12cc-498c-a159-be13b472fec7","name":"redux源码分析系列","slug":"redux","description":"","image":"/content/images/2016/05/687474703a2f2f692e696d6775722e636f6d2f4a65567164514d2e706e67-1.png","hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463754046804,"created_by":1,"updated_at":1463817817169,"updated_by":1},{"id":3,"uuid":"2ce8db62-0a14-47c4-b1b8-1e3b920c420a","name":"life","slug":"life","description":"关于生活","image":"/content/images/2016/05/u-1687420627-4175839764-fm-21-gp-0.jpg","hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463817797112,"created_by":1,"updated_at":1463817887321,"updated_by":1},{"id":4,"uuid":"495550a6-a385-4ff4-8e4f-afb9870f94fa","name":"乱七八糟","slug":"digression","description":"杂谈","image":"/content/images/2016/05/u-1468361829-4039309630-fm-116-gp-0.jpg","hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463834283409,"created_by":1,"updated_at":1463834926309,"updated_by":1},{"id":5,"uuid":"4723a273-0582-4a37-842d-c771a73f85f8","name":"mongodb","slug":"mongodb","description":"","image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463838088406,"created_by":1,"updated_at":1463838094311,"updated_by":1},{"id":6,"uuid":"31ada047-4501-406d-917f-488e23b03f36","name":"mac","slug":"mac","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463838131596,"created_by":1,"updated_at":1463838131596,"updated_by":1},{"id":7,"uuid":"91b7d980-5ce6-4340-aaf1-9a6b5dc80667","name":"koa","slug":"koa","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463838270749,"created_by":1,"updated_at":1463838270749,"updated_by":1},{"id":8,"uuid":"be14e3b0-fb8f-4924-81b5-0924b220508c","name":"nodejs","slug":"nodejs","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1463838276529,"created_by":1,"updated_at":1463838283586,"updated_by":1},{"id":9,"uuid":"c332f075-6919-4dd1-9b6e-cdd1b93e86db","name":"react","slug":"react","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1464247836082,"created_by":1,"updated_at":1464247836082,"updated_by":1},{"id":10,"uuid":"5d8a3fba-d4a7-4e50-856f-2b3034b30d5d","name":"git","slug":"git","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1464335179221,"created_by":1,"updated_at":1464335179221,"updated_by":1},{"id":11,"uuid":"3dc60c76-7ce0-44e2-93eb-da3879afc085","name":"eslint","slug":"eslint","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1464346513640,"created_by":1,"updated_at":1464346513640,"updated_by":1},{"id":12,"uuid":"5f5ac01a-3d8d-4fde-bf30-52f04398df03","name":"react native","slug":"react-native","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1466558983520,"created_by":1,"updated_at":1466558983520,"updated_by":1},{"id":13,"uuid":"f2a13e9c-f961-4e08-96be-16fcb05eca63","name":"人生","slug":"ren-sheng","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1467111340939,"created_by":1,"updated_at":1467111340939,"updated_by":1},{"id":14,"uuid":"985d4ca6-c298-4e23-83d8-93dac19d7987","name":"有趣视频","slug":"you-qu-shi-pin","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1467111340949,"created_by":1,"updated_at":1467111340949,"updated_by":1},{"id":15,"uuid":"768f7c77-48fa-40ee-9b14-b8dd4d8e7393","name":"翻译","slug":"fan-yi","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1467688909351,"created_by":1,"updated_at":1467688909351,"updated_by":1},{"id":16,"uuid":"f3ad56e4-023f-45ee-bdd1-2bd05ea6f8d2","name":"随笔","slug":"sui-bi","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1468460842999,"created_by":1,"updated_at":1468460842999,"updated_by":1},{"id":17,"uuid":"a3c86f17-4e46-41f9-b79e-e8f12c534a1b","name":"常识","slug":"chang-shi","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1468469618360,"created_by":1,"updated_at":1468469618360,"updated_by":1},{"id":18,"uuid":"257fd3de-50cc-4358-a378-78a6e7d561d0","name":"js","slug":"js","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1468896571000,"created_by":1,"updated_at":1468896571000,"updated_by":1},{"id":19,"uuid":"052b64f0-5ab2-4dad-a3a7-63351c98ff62","name":"策略模式","slug":"ce-lue-mo-shi","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1469029769238,"created_by":1,"updated_at":1469029769238,"updated_by":1},{"id":20,"uuid":"cbb3738c-77e3-43ac-bb21-cc594f49b866","name":"android","slug":"android","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1469436760798,"created_by":1,"updated_at":1469436760798,"updated_by":1},{"id":21,"uuid":"edc646b2-0dd0-4190-b271-7f3ed0b0ee6e","name":"mobx","slug":"mobx","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1470231894998,"created_by":1,"updated_at":1470231894998,"updated_by":1},{"id":22,"uuid":"7d673880-09c5-477c-86a3-67dedc86dea9","name":"业务","slug":"ye-wu","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1470842891947,"created_by":1,"updated_at":1470842891947,"updated_by":1},{"id":23,"uuid":"db41c8ca-827a-46d9-9e20-aaeca92b8431","name":"jersey","slug":"jersey","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1470999361155,"created_by":1,"updated_at":1470999361155,"updated_by":1},{"id":24,"uuid":"b6597668-99d0-4422-9119-85a2d695b7a7","name":"fetch","slug":"fetch","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1471011841089,"created_by":1,"updated_at":1471011841089,"updated_by":1},{"id":25,"uuid":"66f8d61d-43bc-4967-9ab3-3a185db4bf8a","name":"web","slug":"web","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1471071283575,"created_by":1,"updated_at":1471071283575,"updated_by":1},{"id":26,"uuid":"6b8cc1f3-e78a-4b21-8f5c-8c9c4db8e4fb","name":"http","slug":"http","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1471164888580,"created_by":1,"updated_at":1471164888580,"updated_by":1},{"id":27,"uuid":"b7239a66-063e-417f-b648-34a7a3d5c222","name":"缓存","slug":"huan-cun","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1471164888585,"created_by":1,"updated_at":1471164888585,"updated_by":1},{"id":28,"uuid":"279f3c3e-ec4b-4f0d-b919-d2455ac963f9","name":"电影","slug":"dian-ying","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1471365183357,"created_by":1,"updated_at":1471365183357,"updated_by":1},{"id":29,"uuid":"20d5dbf8-355b-4d34-8268-8f7ceb664174","name":"centos","slug":"centos","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1472051602556,"created_by":1,"updated_at":1472051602556,"updated_by":1},{"id":30,"uuid":"60bd7479-a26e-4d5e-a04f-10f4c98c26fd","name":"nginx","slug":"nginx","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1472051602566,"created_by":1,"updated_at":1472051602566,"updated_by":1},{"id":31,"uuid":"5d8b3007-0add-40c2-b757-30a3bfe60f94","name":"正则","slug":"zheng-ze","description":null,"image":null,"hidden":0,"parent_id":null,"meta_title":null,"meta_description":null,"created_at":1473748641758,"created_by":1,"updated_at":1473748641758,"updated_by":1}],"posts_tags":[{"id":1,"post_id":1,"tag_id":1,"sort_order":0},{"id":2,"post_id":2,"tag_id":2,"sort_order":0},{"id":3,"post_id":3,"tag_id":4,"sort_order":0},{"id":4,"post_id":3,"tag_id":6,"sort_order":1},{"id":5,"post_id":4,"tag_id":5,"sort_order":0},{"id":6,"post_id":4,"tag_id":6,"sort_order":1},{"id":7,"post_id":5,"tag_id":7,"sort_order":0},{"id":8,"post_id":5,"tag_id":8,"sort_order":1},{"id":9,"post_id":6,"tag_id":4,"sort_order":0},{"id":10,"post_id":9,"tag_id":4,"sort_order":0},{"id":11,"post_id":8,"tag_id":9,"sort_order":0},{"id":12,"post_id":10,"tag_id":10,"sort_order":0},{"id":13,"post_id":11,"tag_id":11,"sort_order":0},{"id":14,"post_id":13,"tag_id":12,"sort_order":0},{"id":15,"post_id":15,"tag_id":12,"sort_order":0},{"id":16,"post_id":19,"tag_id":13,"sort_order":0},{"id":17,"post_id":19,"tag_id":14,"sort_order":1},{"id":18,"post_id":23,"tag_id":12,"sort_order":0},{"id":19,"post_id":12,"tag_id":9,"sort_order":0},{"id":20,"post_id":12,"tag_id":15,"sort_order":1},{"id":21,"post_id":24,"tag_id":9,"sort_order":0},{"id":22,"post_id":24,"tag_id":15,"sort_order":1},{"id":23,"post_id":27,"tag_id":16,"sort_order":0},{"id":24,"post_id":28,"tag_id":17,"sort_order":0},{"id":25,"post_id":30,"tag_id":18,"sort_order":0},{"id":26,"post_id":31,"tag_id":12,"sort_order":0},{"id":27,"post_id":31,"tag_id":18,"sort_order":1},{"id":28,"post_id":31,"tag_id":19,"sort_order":2},{"id":29,"post_id":34,"tag_id":20,"sort_order":1},{"id":30,"post_id":34,"tag_id":10,"sort_order":0},{"id":31,"post_id":33,"tag_id":12,"sort_order":0},{"id":32,"post_id":33,"tag_id":20,"sort_order":1},{"id":33,"post_id":35,"tag_id":12,"sort_order":0},{"id":34,"post_id":35,"tag_id":20,"sort_order":1},{"id":35,"post_id":37,"tag_id":12,"sort_order":0},{"id":36,"post_id":37,"tag_id":20,"sort_order":1},{"id":37,"post_id":39,"tag_id":16,"sort_order":0},{"id":38,"post_id":39,"tag_id":3,"sort_order":1},{"id":39,"post_id":32,"tag_id":13,"sort_order":0},{"id":40,"post_id":32,"tag_id":14,"sort_order":1},{"id":41,"post_id":42,"tag_id":9,"sort_order":0},{"id":42,"post_id":42,"tag_id":21,"sort_order":1},{"id":43,"post_id":44,"tag_id":13,"sort_order":0},{"id":44,"post_id":44,"tag_id":16,"sort_order":1},{"id":45,"post_id":44,"tag_id":3,"sort_order":2},{"id":46,"post_id":45,"tag_id":3,"sort_order":0},{"id":47,"post_id":45,"tag_id":13,"sort_order":1},{"id":48,"post_id":45,"tag_id":16,"sort_order":2},{"id":49,"post_id":36,"tag_id":22,"sort_order":0},{"id":50,"post_id":46,"tag_id":12,"sort_order":0},{"id":51,"post_id":26,"tag_id":23,"sort_order":0},{"id":52,"post_id":7,"tag_id":24,"sort_order":0},{"id":53,"post_id":7,"tag_id":12,"sort_order":1},{"id":54,"post_id":29,"tag_id":13,"sort_order":0},{"id":55,"post_id":29,"tag_id":3,"sort_order":1},{"id":56,"post_id":29,"tag_id":14,"sort_order":2},{"id":57,"post_id":47,"tag_id":25,"sort_order":0},{"id":58,"post_id":48,"tag_id":3,"sort_order":0},{"id":59,"post_id":48,"tag_id":13,"sort_order":1},{"id":60,"post_id":49,"tag_id":26,"sort_order":0},{"id":61,"post_id":49,"tag_id":27,"sort_order":1},{"id":62,"post_id":51,"tag_id":3,"sort_order":0},{"id":63,"post_id":51,"tag_id":13,"sort_order":1},{"id":64,"post_id":51,"tag_id":28,"sort_order":2},{"id":65,"post_id":52,"tag_id":10,"sort_order":0},{"id":66,"post_id":54,"tag_id":26,"sort_order":0},{"id":67,"post_id":54,"tag_id":29,"sort_order":1},{"id":68,"post_id":54,"tag_id":30,"sort_order":2},{"id":69,"post_id":55,"tag_id":26,"sort_order":0},{"id":70,"post_id":55,"tag_id":30,"sort_order":1},{"id":71,"post_id":55,"tag_id":29,"sort_order":2},{"id":72,"post_id":56,"tag_id":30,"sort_order":0},{"id":73,"post_id":56,"tag_id":26,"sort_order":1},{"id":74,"post_id":56,"tag_id":29,"sort_order":2},{"id":75,"post_id":50,"tag_id":16,"sort_order":0},{"id":76,"post_id":50,"tag_id":3,"sort_order":1},{"id":77,"post_id":50,"tag_id":13,"sort_order":2},{"id":78,"post_id":57,"tag_id":18,"sort_order":0},{"id":79,"post_id":60,"tag_id":18,"sort_order":0},{"id":80,"post_id":62,"tag_id":18,"sort_order":0},{"id":81,"post_id":62,"tag_id":31,"sort_order":1}],"apps":[],"app_settings":[],"app_fields":[],"client_trusted_domains":[]}}]}