奇舞团博客 http://www.75team.com 奇舞团 | 奇虎360旗下前端开发团队 zh-cn Wed, 24 May 2017 02:13:02 GMT 使用 Node.js 对文本内容分词和关键词抽取 http://www.75team.com/post/segment-with-nodejs.html <div class="toc"></div><blockquote> <p>原文:<a href="https://www.h5jun.com/post/segment-with-nodejs.html">https://www.h5jun.com/post/segment-with-nodejs.html</a></p> </blockquote> <p>在讨论技术前先卖个萌,吃货的世界你不懂~~</p> <p><img src="https://p1.ssl.qhimg.com/t01ee4cb9bf6b602f75.png" alt=""></p> <!--more--> <p><a href="http://zcfy.cc/">众成翻译</a>的文章有 tag,用户可以基于 tag 来快速筛选感兴趣的文章,文章也可以依照 tag 关联来进行相关推荐。但是现在众成翻译的 tag 是在推荐文章的时候设置的,都是英文的,而且人工设置难免不规范和不完全。虽然发布文章后也可以人工编辑,但是我们也不能指望用户或管理员能够时时刻刻编辑出恰当的 tag,所以我们需要用工具来自动生成 tag。</p> <p>在现在开源的分词工具里面,<a href="https://github.com/fxsjy/jieba">jieba</a>是一个功能强大性能优越的分词组件,更幸运地是,它有 <a href="https://github.com/yanyiwu/nodejieba">node 版本</a>。</p> <p>nodejieba 的安装和使用十分简单:</p> <pre><code class="hljs lang-undefined">npm install nodejieba </code></pre> <pre><code class="hljs lang-js"><span class="hljs-keyword">var</span> nodejieba = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodejieba"</span>); <span class="hljs-keyword">var</span> result = nodejieba.cut(<span class="hljs-string">"帝国主义要把我们的地瓜分掉"</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '帝国主义', '要', '把', '我们', '的', '地', '瓜分', '掉' ]</span> result = nodejieba.cut(<span class="hljs-string">'土地,俺老孙的金箍棒在哪里?'</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '土地', ',', '俺', '老', '孙', '的', '金箍棒', '在', '哪里', '?' ]</span> result = nodejieba.cut(<span class="hljs-string">'大圣,您的金箍棒就棒在特别配您的头型!'</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '大圣',',','您','的','金箍棒','就','棒','在','特别','配','您','的','头型','!' ]</span> </code></pre> <p>我们可以载入自己的字典,在字典里给每个词分别设置权重和词性:</p> <p>编辑 <code>user.uft8</code></p> <pre><code class="hljs lang-lsl">地瓜 <span class="hljs-number">9999</span> n 金箍 <span class="hljs-number">9999</span> n 棒就棒在 <span class="hljs-number">9999</span> </code></pre><p>然后通过 <code>nodejieba.load</code> 加载字典。</p> <pre><code class="hljs lang-js"><span class="hljs-keyword">var</span> nodejieba = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodejieba"</span>); nodejieba.load({ <span class="hljs-attr">userDict</span>: <span class="hljs-string">'./user.utf8'</span>, }); <span class="hljs-keyword">var</span> result = nodejieba.cut(<span class="hljs-string">"帝国主义要把我们的地瓜分掉"</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '帝国主义', '要', '把', '我们', '的', '地瓜', '分', '掉' ]</span> result = nodejieba.cut(<span class="hljs-string">'土地,俺老孙的金箍棒在哪里?'</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '土地', ',', '俺', '老', '孙', '的', '金箍棒', '在', '哪里', '?' ]</span> result = nodejieba.cut(<span class="hljs-string">'大圣,您的金箍棒就棒在特别配您的头型!'</span>); <span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">//[ '大圣', ',', '您', '的', '金箍', '棒就棒在', '特别', '配', '您', '的', '头型', '!' ]</span> </code></pre> <p>除了分词以外,我们可以利用 nodejieba 提取关键词:</p> <pre><code class="hljs lang-js"><span class="hljs-keyword">const</span> content = <span class="hljs-string">` HTTP、HTTP/2与性能优化 本文的目的是通过比较告诉大家,为什么应该从HTTP迁移到HTTPS,以及为什么应该添加到HTTP/2的支持。在比较HTTP和HTTP/2之前,先看看什么是HTTP。 什么是HTTP HTTP是在万维网上通信的一组规则。HTTP属于应用层协议,跑在TCP/IP层之上。用户通过浏览器请求网页时,HTTP负责处理请求并在Web服务器与客户端之间建立连接。 有了HTTP/2,不使用雪碧图、压缩、拼接,也可以提升性能。然而,这不代表不应该使用这些技术。不过这已经清楚表明了我们从HTTP/1.1移动到HTTP/2的必要性。 `</span>; <span class="hljs-keyword">const</span> nodejieba = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodejieba"</span>); <span class="hljs-keyword">const</span> result = nodejieba.extract(content, <span class="hljs-number">20</span>); <span class="hljs-built_in">console</span>.log(result); </code></pre> <p>输出的结果类似下面这样:</p> <pre><code class="hljs lang-groovy">[ { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">140.8704516850025</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'请求'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">14.23018001394</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'应该'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">14.052171126120001</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'万维网'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">12.2912397395</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'TCP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'1.1'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'Web'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'雪碧图'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTPS'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'IP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'应用层'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.2616203224</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'客户端'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.1926274509</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'浏览器'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">10.8561552143</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'拼接'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.85762638414</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'比较'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.5435285574</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'网页'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.53122979951</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'服务器'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.41204128224</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'使用'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.03259988558</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'必要性'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">8.81927328699</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'添加'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">8.0484751722</span> } ] </code></pre><p>我们添加一些新的关键词到字典里:</p> <pre><code class="hljs lang-lsl">性能 HTTP/<span class="hljs-number">2</span> </code></pre><p>输出结果如下:</p> <pre><code class="hljs lang-groovy">[ { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">105.65283876375187</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTP/2'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">58.69602153541771</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'请求'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">14.23018001394</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'应该'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">14.052171126120001</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'性能'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">12.61259281884</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'万维网'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">12.2912397395</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'IP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTPS'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'1.1'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'TCP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'Web'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'雪碧图'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'应用层'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.2616203224</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'客户端'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.1926274509</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'浏览器'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">10.8561552143</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'拼接'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.85762638414</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'比较'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.5435285574</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'网页'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.53122979951</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'服务器'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.41204128224</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'使用'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">9.03259988558</span> } ] </code></pre><p>在这个基础上,我们采用白名单的方式过滤出一些可以作为 tag 的词:</p> <pre><code class="hljs lang-js"><span class="hljs-keyword">const</span> content = <span class="hljs-string">` HTTP、HTTP/2与性能优化 本文的目的是通过比较告诉大家,为什么应该从HTTP迁移到HTTPS,以及为什么应该添加到HTTP/2的支持。在比较HTTP和HTTP/2之前,先看看什么是HTTP。 什么是HTTP HTTP是在万维网上通信的一组规则。HTTP属于应用层协议,跑在TCP/IP层之上。用户通过浏览器请求网页时,HTTP负责处理请求并在Web服务器与客户端之间建立连接。 有了HTTP/2,不使用雪碧图、压缩、拼接,也可以提升性能。然而,这不代表不应该使用这些技术。不过这已经清楚表明了我们从HTTP/1.1移动到HTTP/2的必要性。 `</span>; <span class="hljs-keyword">const</span> nodejieba = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodejieba"</span>); nodejieba.load({ <span class="hljs-attr">userDict</span>: <span class="hljs-string">'./user.utf8'</span>, }); <span class="hljs-keyword">const</span> result = nodejieba.extract(content, <span class="hljs-number">20</span>); <span class="hljs-keyword">const</span> tagList = [<span class="hljs-string">'HTTPS'</span>, <span class="hljs-string">'HTTP'</span>, <span class="hljs-string">'HTTP/2'</span>, <span class="hljs-string">'Web'</span>, <span class="hljs-string">'浏览器'</span>, <span class="hljs-string">'性能'</span>]; <span class="hljs-built_in">console</span>.log(result.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> tagList.indexOf(item.word) &gt;= <span class="hljs-number">0</span>)); </code></pre> <p>最后得到:</p> <pre><code class="hljs lang-groovy">[ { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTP'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">105.65283876375187</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTP/2'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">58.69602153541771</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'性能'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">12.61259281884</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'HTTPS'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'Web'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">11.739204307083542</span> }, { <span class="hljs-string">word:</span> <span class="hljs-string">'浏览器'</span>, <span class="hljs-string">weight:</span> <span class="hljs-number">10.8561552143</span> } ] </code></pre><p>这就是我们想要的结果。</p> <p>以上就是分词库 nodejieba 基本的使用方法,在将来我们可以利用它对众成翻译发布的译文自动分析添加相应的 tag,以为各位译者和读者提供更好的用户体验。</p> <p>有任何问题,欢迎在讨论区讨论~</p> Fri, 12 May 2017 08:56:15 GMT http://www.75team.com/post/segment-with-nodejs.html 使用 webpack2 和 NPM Scripts 进行 JavaScript 组件开发 http://www.75team.com/post/using-webpack2-and-npm-scripts.html <div class="toc"><ul> <li><a href="#toc-180">搭建一个简易环境</a></li> <li><a href="#toc-b59">配置 webpack</a><ul> <li><a href="#toc-464">生产环境和开发环境</a></li> <li><a href="#toc-8c8">使用 babel 编译 ES6</a></li> <li><a href="#toc-35d">配置开发调试的 webpack-dev-server</a></li> </ul> </li> <li><a href="#toc-1a3">创建 NPM Scripts</a></li> <li><a href="#toc-029">开始项目开发</a><ul> <li><a href="#toc-5e2">构建和发布</a></li> </ul> </li> <li><a href="#toc-25f">总结</a></li> </ul> </div><blockquote> <p>原文:<a href="https://www.h5jun.com/post/using-webpack2-and-npm-scripts.html">https://www.h5jun.com/post/using-webpack2-and-npm-scripts.html</a></p> </blockquote> <p>最近 <a href="https://webpack-china.org/">webpack</a> 成为非常流行的打包工具,很多项目都在使用它。在我们进行 <strong>JavaScript 独立组件</strong>开发的时候,如果我们想要使用语言新特性,又想发布的时候产出兼容性好的代码,那么使用 webpack 就能够很大程度上帮助我们实现这一目标。</p> <p>现在让我们来看看究竟该怎么做吧。</p> <!--more--> <h2><a id="toc-180" class="anchor" href="#toc-180"></a>搭建一个简易环境</h2> <p>首先,第一步是初始化和安装一些必要的依赖,搭建一个简易的开发和调试环境:</p> <pre><code class="hljs lang-bash"><span class="hljs-comment"># 初始化 package.json</span> npm init -i <span class="hljs-comment"># 安装 http-server</span> npm install http-server --save-dev <span class="hljs-comment"># 安装 webpack </span> npm install webpack webpack-dev-server --save-dev <span class="hljs-comment"># 安装 babel 和 babel 插件,如果不想使用 babel 编译 ES6+,可以跳过这一步</span> npm install babel-loader babel-core babel-preset-env babel-plugin-transform-runtime --save-dev </code></pre> <p>有了这个基础环境之后,我们来配置一份 webpack.config.js</p> <h2><a id="toc-b59" class="anchor" href="#toc-b59"></a>配置 webpack</h2> <p>我们在项目目录里添加 webpack.config.js</p> <pre><code class="hljs lang-js"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">env = {}</span>)</span>{ <span class="hljs-keyword">const</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>), path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>), fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>), packageConf = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(<span class="hljs-string">'package.json'</span>, <span class="hljs-string">'utf-8'</span>)); <span class="hljs-keyword">let</span> name = packageConf.name, version = packageConf.version, library = name.replace(<span class="hljs-regexp">/^(\w)/</span>, m =&gt; m.toUpperCase()), proxyPort = <span class="hljs-number">8081</span>, plugins = [], loaders = []; <span class="hljs-keyword">if</span>(env.production){ name += <span class="hljs-string">`-<span class="hljs-subst">${version}</span>.min`</span>; <span class="hljs-comment">//compress js in production environment</span> plugins.push( <span class="hljs-keyword">new</span> webpack.optimize.UglifyJsPlugin({ <span class="hljs-attr">compress</span>: { <span class="hljs-attr">warnings</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">drop_console</span>: <span class="hljs-literal">false</span>, } }) ); } <span class="hljs-keyword">if</span>(fs.existsSync(<span class="hljs-string">'./.babelrc'</span>)){ <span class="hljs-comment">//use babel</span> <span class="hljs-keyword">let</span> babelConf = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(<span class="hljs-string">'.babelrc'</span>)); loaders.push({ <span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.js$/</span>, <span class="hljs-attr">exclude</span>: <span class="hljs-regexp">/(node_modules|bower_components)/</span>, <span class="hljs-attr">loader</span>: <span class="hljs-string">'babel-loader'</span>, <span class="hljs-attr">query</span>: babelConf }); } <span class="hljs-keyword">return</span> { <span class="hljs-attr">entry</span>: <span class="hljs-string">'./lib/app.js'</span>, <span class="hljs-attr">output</span>: { <span class="hljs-attr">filename</span>: <span class="hljs-string">`<span class="hljs-subst">${name}</span>.js`</span>, <span class="hljs-attr">path</span>: path.resolve(__dirname, <span class="hljs-string">'dist'</span>), <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'/static/js/'</span>, <span class="hljs-attr">library</span>: <span class="hljs-string">`<span class="hljs-subst">${library}</span>`</span>, <span class="hljs-attr">libraryTarget</span>: <span class="hljs-string">'umd'</span> }, <span class="hljs-attr">plugins</span>: plugins, <span class="hljs-attr">module</span>: { <span class="hljs-attr">loaders</span>: loaders }, <span class="hljs-attr">devServer</span>: { <span class="hljs-attr">proxy</span>: { <span class="hljs-string">"*"</span>: <span class="hljs-string">`http://127.0.0.1:<span class="hljs-subst">${proxyPort}</span>`</span>, } } }; } </code></pre> <p>代码比较长,但不复杂,这里我分别解释一下各部分的作用:</p> <h3><a id="toc-464" class="anchor" href="#toc-464"></a>生产环境和开发环境</h3> <p>首先我们从 package.json 里面取出一些信息,包括模块名和版本号,我们依赖它们来生成对应的 umd 模块的 library、输出的文件名以及版本号。</p> <p>在这里我们规定在开发环境时输出 <code>模块名.js</code>,在生产环境发布时输出 <code>模块名-版本号.min.js</code>。</p> <p>在 webpack2 里,我们可以通过 env.production 获取命令行参数 <code>--env.production</code>,从而区别是开发环境还是生产环境。</p> <pre><code class="hljs lang-js"> <span class="hljs-keyword">if</span>(env.production){ name += <span class="hljs-string">`-<span class="hljs-subst">${version}</span>.min`</span>; <span class="hljs-comment">//compress js in production environment</span> plugins.push( <span class="hljs-keyword">new</span> webpack.optimize.UglifyJsPlugin({ <span class="hljs-attr">compress</span>: { <span class="hljs-attr">warnings</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">drop_console</span>: <span class="hljs-literal">false</span>, } }) ); } </code></pre> <p>在生产环境中,我们不仅改变输出的文件名,还配置 UglifyJsPlugin 来压缩脚本。</p> <h3><a id="toc-8c8" class="anchor" href="#toc-8c8"></a>使用 babel 编译 ES6</h3> <p>如果脚本用到 ES6,我们希望用 babel 编译的话,还需要加载 babel-loader 来进行编译。我们采用 babel 的默认配置 <code>.babelrc</code>,在项目目录里添加 .babelrc:</p> <pre><code class="hljs lang-json">{ <span class="hljs-attr">"presets"</span>: [<span class="hljs-string">"env"</span>], <span class="hljs-attr">"plugins"</span>: [<span class="hljs-string">"transform-runtime"</span>] } </code></pre> <p>然后我们在 webpack.config.json 里根据配置来添加 loader:</p> <pre><code class="hljs lang-js"> <span class="hljs-keyword">if</span>(fs.existsSync(<span class="hljs-string">'./.babelrc'</span>)){ <span class="hljs-comment">//use babel</span> <span class="hljs-keyword">let</span> babelConf = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(<span class="hljs-string">'.babelrc'</span>)); loaders.push({ <span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.js$/</span>, <span class="hljs-attr">exclude</span>: <span class="hljs-regexp">/(node_modules|bower_components)/</span>, <span class="hljs-attr">loader</span>: <span class="hljs-string">'babel-loader'</span>, <span class="hljs-attr">query</span>: babelConf }); } </code></pre> <h3><a id="toc-35d" class="anchor" href="#toc-35d"></a>配置开发调试的 webpack-dev-server</h3> <p>最后,为了在开发环境里调试,我们还需要配置 webpack-dev-server:</p> <pre><code class="hljs lang-js"> devServer: { <span class="hljs-attr">proxy</span>: { <span class="hljs-string">"*"</span>: <span class="hljs-string">`http://127.0.0.1:<span class="hljs-subst">${proxyPort}</span>`</span>, } } </code></pre> <p>webpack-dev-server 是一个代理,我们之前安装了 http-server,我们用 webpack-dev-server 来代理它,所以开发时我们让 http-server 运行在 8081 端口:</p> <pre><code class="hljs lang-undefined">webpack-dev-server --quiet &amp; http-server -p 8081 -c-1 </code></pre> <h2><a id="toc-1a3" class="anchor" href="#toc-1a3"></a>创建 NPM Scripts</h2> <p>配置好了 webpack,创建 NPM Scripts 是个非常简单的过程:</p> <pre><code class="hljs lang-js"> <span class="hljs-string">"scripts"</span>: { [...] <span class="hljs-string">"start"</span>: <span class="hljs-string">"webpack-dev-server --quiet &amp; http-server -c-1 -p 8081"</span>, <span class="hljs-string">"build-release"</span>: <span class="hljs-string">"webpack --env.production"</span> }, </code></pre> <p>我们在 package.json 里添加两个脚本,这样我们就可以使用 <code>npm start</code> 来启动开发环境,使用 <code>npm run build-release</code> 来发布到生产环境了。</p> <h2><a id="toc-029" class="anchor" href="#toc-029"></a>开始项目开发</h2> <p>接下来我们创建 lib/app.js:</p> <pre><code class="hljs lang-js"><span class="hljs-built_in">module</span>.exports = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./demo'</span>); </code></pre> <p>然后创建 lib/demo.js:</p> <pre><code class="hljs lang-js"><span class="hljs-built_in">module</span>.exports = <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span></span>{ <span class="hljs-keyword">async</span> test(){ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> { setTimeout(resolve, <span class="hljs-number">1000</span>) }); } } </code></pre> <p>创建 examples/index.html:</p> <pre><code class="hljs lang-html"><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/js/demo.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript"> <span class="hljs-keyword">var</span> d = <span class="hljs-keyword">new</span> Demo(); d.test().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'test done!'</span>); }); </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span> </code></pre> <p>运行 <code>npm start</code>,访问 <a href="http://localhost:8080/examples/index.html">http://localhost:8080/examples/index.html</a></p> <p>打开控制台,1秒后就能看到输出 <code>test done</code>。</p> <h3><a id="toc-5e2" class="anchor" href="#toc-5e2"></a>构建和发布</h3> <p>接下来,我们可以停止开发服务器,运行 <code>npm run build-release</code>。如果你的 package.json 里的版本号是 1.0.0,那么它将在 dist 目录下生成 demo-1.0.0.min.js。这是一个 umd 的包,所以你可以在浏览器中使用 amd/cmd 库加载或者直接在 script 标签中引入和使用。</p> <h2><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h2> <p>我们使用 webpack 搭建了一个简单的组件开发环境,这样我们可以简单地开始我们的 JS 组件开发,使用新的语言规范,然后通过 webpack-dev-server 代理 http-server 进行调试,通过 webpack --env.production 进行发布。我们还可以将它们与 NPM Scripts 集成,这样我们的组件开发就非常方便了。</p> <p>你喜欢的组件开发方式是怎样的?欢迎在评论区讨论。</p> Wed, 08 Mar 2017 04:03:08 GMT http://www.75team.com/post/using-webpack2-and-npm-scripts.html 用60行代码实现JavaScript动画序列播放 http://www.75team.com/post/sixty-lines-of-code-animation.html <div class="toc"><ul> <li><a href="#toc-ea4">基于 Promise 的动画库</a><ul> <li><a href="#toc-380">具体实现</a></li> </ul> </li> <li><a href="#toc-185">用 CSS3 如何?</a></li> <li><a href="#toc-25f">总结</a></li> </ul> </div><blockquote> <p>原文:<a href="https://www.h5jun.com/post/sixty-lines-of-code-animation.html">https://www.h5jun.com/post/sixty-lines-of-code-animation.html</a></p> </blockquote> <p>最近在给学生上课,上周六的第一堂课是关于 JavaScript 动画的内容,其中包括一些简单的动画,比如匀速或者匀加/减速的运动,也包括复杂一些的组合动画。而动画的基本原理,在我<a href="https://www.h5jun.com/post/animations-you-should-know.html">之前的文章</a>已经有了详细的介绍。在这里,我想谈一谈的是,我们可以如何针对现代浏览器设计更加简单的 API,来实现动画的序列播放。</p> <!--more--> <h2><a id="toc-ea4" class="anchor" href="#toc-ea4"></a>基于 Promise 的动画库</h2> <p>所谓的动画序列,也就是说可以在上一段动画播放结束之后进行下一段动画的播放,这样可以方便用多段动画实现各种不同的复杂效果。而我们不难想到,要实现这个目的,将动画接口实现成 Promise 是一个非常好的方案:</p> <p><a class="jsbin-embed" href="//code.h5jun.com/sug/9/embed?js,output">基于 Promise 的循环圆周运动</a><script src="https:////code.h5jun.com/js/embed.min.js?3.40.2"></script></p> <p>上面这个例子,在支持 async/await 的现代浏览器中代码非常简洁和优雅。如果要兼容旧的浏览器,也并不复杂,只需要<a href="https://github.com/stefanpenner/es6-promise">针对 es6-promise 做 polyfill</a> 或引入<a href="http://bluebirdjs.com/docs/getting-started.html">第三方库</a>即可。再来看一个例子:</p> <p><a class="jsbin-embed" href="//code.h5jun.com/fino/4/embed?html,js,output">小球沿矩形框运动</a><script src="https:////code.h5jun.com/js/embed.min.js?3.40.2"></script></p> <p>有了 Promise,像这样的序列运动非常简单。那么要实现这个动画库,具体该怎么做呢?</p> <h3><a id="toc-380" class="anchor" href="#toc-380"></a>具体实现</h3> <p>其实整个库实现起来并不复杂,只需要将基础动画封装为 Promise 就可以了。</p> <p>不过在这里,为了兼容老版本的浏览器,我们先对一些基础函数进行封装:</p> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nowtime</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> performance !== <span class="hljs-string">'undefined'</span> &amp;&amp; performance.now){ <span class="hljs-keyword">return</span> performance.now(); } <span class="hljs-keyword">return</span> <span class="hljs-built_in">Date</span>.now ? <span class="hljs-built_in">Date</span>.now() : (<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()).getTime(); } </code></pre> <p>我们说<strong>动画是关于时间的函数</strong>,因此我们需要一个简单的获取时间功能。在新的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame 规范</a>中,frame 回调的参数 timestamp 是一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame">DOMHighResTimeStamp</a> 对象,它比 Date 的计时要更精确(可以精确到纳秒)。因此获取时间我们优先使用 performance.now(),如果浏览器不支持 performance.now(),我们再降级使用 Date.now()。</p> <p>接下来,我们对 requestAnimationFrame 进行 polyfill:</p> <pre><code class="hljs lang-js"><span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> global.requestAnimationFrame === <span class="hljs-string">'undefined'</span>){ global.requestAnimationFrame = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">callback</span>)</span>{ <span class="hljs-keyword">return</span> setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-comment">//polyfill</span> callback.call(<span class="hljs-keyword">this</span>, nowtime()); }, <span class="hljs-number">1000</span>/<span class="hljs-number">60</span>); } global.cancelAnimationFrame = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">qId</span>)</span>{ <span class="hljs-keyword">return</span> clearTimeout(qId); } } </code></pre> <p>然后,是具体的 Animator 实现:</p> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Animator</span>(<span class="hljs-params">duration, update, easing</span>)</span>{ <span class="hljs-keyword">this</span>.duration = duration; <span class="hljs-keyword">this</span>.update = update; <span class="hljs-keyword">this</span>.easing = easing; } Animator.prototype = { <span class="hljs-attr">animate</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-keyword">var</span> startTime = <span class="hljs-number">0</span>, duration = <span class="hljs-keyword">this</span>.duration, update = <span class="hljs-keyword">this</span>.update, easing = <span class="hljs-keyword">this</span>.easing, self = <span class="hljs-keyword">this</span>; <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">resolve, reject</span>)</span>{ <span class="hljs-keyword">var</span> qId = <span class="hljs-number">0</span>; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">step</span>(<span class="hljs-params">timestamp</span>)</span>{ startTime = startTime || timestamp; <span class="hljs-keyword">var</span> p = <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">1.0</span>, (timestamp - startTime) / duration); update.call(self, easing ? easing(p) : p, p); <span class="hljs-keyword">if</span>(p &lt; <span class="hljs-number">1.0</span>){ qId = requestAnimationFrame(step); }<span class="hljs-keyword">else</span>{ resolve(self); } } self.cancel = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ cancelAnimationFrame(qId); update.call(self, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>); reject(<span class="hljs-string">'User canceled!'</span>); } qId = requestAnimationFrame(step); }); }, <span class="hljs-attr">ease</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">easing</span>)</span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Animator(<span class="hljs-keyword">this</span>.duration, <span class="hljs-keyword">this</span>.update, easing); } }; <span class="hljs-built_in">module</span>.exports = Animator; </code></pre> <p>Animator 构造的时候可以传三个参数,第一个是动画的总时长,第二个是动画每一帧的 update 事件,在这里可以改变元素的属性,从而实现动画。update 事件回调提供两个参数,第一个是 ep,是经过 easing 之后的动画进程,第二个是 p,是不经过 easing 的动画进程,ep 和 p 的值都是从 0 开始,到 1 结束。(为什么要使用 ep 和 p,在<a href="https://www.h5jun.com/post/animations-you-should-know.html">前一个动画教程</a>里已经说明了。)</p> <p>Animator 有一个 animate 的对象方法,它返回一个 promise,当动画播放完成时,它的 promise 被 resolve,使用者还可以在 promise resolve 前调用 cancel 方法,这样它的 promise 会被 reject。</p> <p>于是这样,很简单地我们就<strong>通过将 animator 封装为带有返回 Promise 接口的方法</strong>,实现了动画序列。它的实现虽然简单,但功能却是很强大的,用它实现的动画代码也很优雅:</p> <p><a class="jsbin-embed" href="//code.h5jun.com/bol/6/embed?js,output">弹跳的小球</a><script src="https:////code.h5jun.com/js/embed.min.js?3.40.2"></script></p> <p>我们还提供了一个 ease 方法(0.2.0+版),能够传入新的 easing,并返回新的 Animator 对象,这样我们就可以在原动画的基础上扩展我们的动画效果:</p> <p><a class="jsbin-embed" href="//code.h5jun.com/zoc/2/embed?js,output">镜像的贝塞尔曲线动画</a><script src="https:////code.h5jun.com/js/embed.min.js?3.40.2"></script></p> <h2><a id="toc-185" class="anchor" href="#toc-185"></a>用 CSS3 如何?</h2> <p>的确,许多动画可以用 CSS3 来实现。不过 JavaScript 动画与 CSS3 动画有其不同的特点和使用场景。总体来说, CSS3 动画适用于任何纯展现效果的简单动画。虽然它也能提供基本的动画组合方法(有 animationEnd 时间,但标准化较晚),但操作起来依然不方便,而且还需要 JavaScript 来控制。有些动画库用降级的方式,能采用 CSS3 动画的采用 CSS3 动画,不能的自动降级为 JavaScript 动画,这不失为一种好方式,但也有利有弊。因为 CSS3 动画是绑定为操作元素属性的,而 JavaScript 更灵活一些。就像我们这个封装的动画库,其实提供的是更底层的 API,操作的只是<em>时间</em>和<em>进度</em>,并没有耦合任何元素、属性或者其他展示类的东西,因此它完全可以用来操作 DOM、Canvas、SVG、音频/视频流甚至是其他异步动作。另外,如果在动画过程中需要有其他一些精细的动作处理,也还是应该使用 JavaScript 动画而不是 CSS3 动画。</p> <h2><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h2> <p>使用 Promise 实现的简单动画库,能够很好地执行组合的时序动画,配合 async/await 代码实现简洁且优雅,同时还具有非常好的扩展性,能够组合出非常强大的动画效果。我相信这将成为未来浏览器上 JavaScript 动画的主要实现方式。</p> Tue, 07 Mar 2017 01:29:50 GMT http://www.75team.com/post/sixty-lines-of-code-animation.html 轻松管理你的 Node 版本 http://www.75team.com/post/manage_node_with_n.html <div class="toc"><ul> <li><a href="#toc-1c2">安装 n</a></li> <li><a href="#toc-1fb">使用和设置</a></li> <li><a href="#toc-4cb">直接启动不同版本的 Node</a></li> </ul> </div><blockquote> <p>原文:<a href="https://www.h5jun.com/post/manage_node_with_n.html">https://www.h5jun.com/post/manage_node_with_n.html</a></p> </blockquote> <p>玩 Node.js 的小伙伴们都知道,现在 Node 的版本更新很快,目前最新稳定版已经更新到 v7.6.0 了,而生产环境一般选择使用 <a href="https://github.com/nodejs/LTS#lts-schedule">LTS</a>(Long-term Support)版本,目前最新的是 v6.10.0。</p> <p>新版的 Node 7.x.x 有非常有用的更新,那就是支持了 --harmony-async-await。这样就不用依赖 babel 来使用 async/await 特性了。</p> <p>但是,如何让 7.x.x 和 LTS 的 6.x.x 并存呢?就需要用 Node 版本管理工具了。</p> <!--more--> <p>之前常用的 Node 版本管理工具是 <a href="https://github.com/creationix/nvm">nvm</a>,这是一个 shell 工具,能够比较方便地切换 Node 版本。</p> <p>不过今天我要介绍给大家的是另一款更简单好用的 Node 版本管理工具,它本身是一个 Node 模块,叫做 <a href="https://github.com/tj/n">n</a>,它是由 <a href="https://github.com/tj/n">TJ</a> 大大开发的,强调简单化的版本管理工具:</p> <blockquote> <p>Node.js version management: no subshells, no profile setup, no convoluted API, just simple.</p> </blockquote> <h3><a id="toc-1c2" class="anchor" href="#toc-1c2"></a>安装 n</h3> <p>要安装 n 非常简单,它本身是一个 NPM 模块,因此:</p> <pre><code class="hljs lang-undefined">npm -g install n </code></pre> <h3><a id="toc-1fb" class="anchor" href="#toc-1fb"></a>使用和设置</h3> <p>要使用 n 安装特定版本的 node,只需要如下命令:</p> <pre><code class="hljs lang-bash">n stable <span class="hljs-comment">#安装最新的稳定版</span> n lts <span class="hljs-comment">#安装最新的 TLS 版</span> n 6.9.0 <span class="hljs-comment">#安装特定的 v6.9.0 版本</span> </code></pre> <p>安装完成多个版本后,直接输入不带参数的 n 命令,会出现一个已安装版本的列表:</p> <p><img src="https://p.ssl.qhimg.com/d/inn/b2a93336/2C678E28-C7C0-478E-BD7D-A3D0D2FEC5FB.png" alt="选择版本"></p> <p>用键盘上下键选择版本,然后回车,就可以切换默认 Node 版本。</p> <h3><a id="toc-4cb" class="anchor" href="#toc-4cb"></a>直接启动不同版本的 Node</h3> <p>假如我们将默认的 Node 版本设置为 6.10.0 了,而我们要使用 7.6.0 启动某个应用,也非常简单,只需要:</p> <pre><code class="hljs lang-undefined">n use 7.6.0 index.js </code></pre> <p>于是,我们可以这么用:</p> <p><strong>async.js</strong></p> <pre><code class="hljs lang-js"><span class="hljs-meta">'use strict'</span> <span class="hljs-keyword">let</span> randomDelay = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">resolve</span>)</span>{ <span class="hljs-keyword">var</span> delay = <span class="hljs-built_in">Math</span>.round(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>); setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'delay '</span> + delay + <span class="hljs-string">' ms'</span>); resolve(delay); }, delay); }); <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([randomDelay(), randomDelay()]); <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'pass'</span>); <span class="hljs-keyword">await</span> randomDelay(); } main(); </code></pre> <pre><code class="hljs lang-undefined">n use 7.6.0 async.js </code></pre> <p>你会看到类似这样的输出结果,说明我们不需要 babel,直接可以用 Node 7.6.0 支持 async/await 了。</p> <pre><code class="hljs lang-undefined">delay 252 ms delay 964 ms pass delay 536 ms </code></pre> <p>最后,我们可以创建一个快捷的命令:</p> <pre><code class="hljs lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-built_in">alias</span> node7=<span class="hljs-string">"\"n use 7.6.0 --harmony-async-await\""</span> &gt;&gt; ~/.bashrc <span class="hljs-built_in">source</span> ~/.bashrc </code></pre> <p>这样我们就可以愉快地使用 node v7.x.x 运行我们的 js 了:</p> <pre><code class="hljs lang-js">node7 <span class="hljs-keyword">async</span>.js </code></pre> Thu, 23 Feb 2017 02:39:46 GMT http://www.75team.com/post/manage_node_with_n.html 5分钟现场撸代码——谈总结会抽奖程序 http://www.75team.com/post/luckey-draw-in-5-minutes.html <div class="toc"><ul> <li><a href="#toc-5bf">直接抽取</a></li> <li><a href="#toc-42e">先洗牌</a></li> <li><a href="#toc-ef6">不需要洗所有的牌</a></li> <li><a href="#toc-6f1">构造器负责初始化</a></li> <li><a href="#toc-db5">更优雅的解决方式?</a></li> <li><a href="#toc-25f">总结</a></li> </ul> </div><blockquote> <p>原文:<a href="https://www.h5jun.com/post/luckey-draw-in-5-minutes.html">https://www.h5jun.com/post/luckey-draw-in-5-minutes.html</a></p> </blockquote> <p>昨天给<a href="https://75team.com/">奇舞团</a>小伙伴们开年度总结会。JK大大为我们捐了10个<a href="http://mall.360.cn/shop/item?item_id=5747bdc0e059bf20d2197eb4">小水滴</a>摄像机,<strong>在开会前5分钟</strong>,裕波临时说要写一个抽奖程序,现场抽10名中奖的小伙伴,于是这个抽奖任务就理(莫)所(名)当(其)然(妙)地落到了我这个团长的头上。</p> <p>闲话少说,那么如何在开会前现场写一个抽奖程序,满足这一要求呢?</p> <!--more--> <p>首先,裕波、成银用白纸给同学做了简单的“奖券”,奖券上只有号码,从 1 ~ 62,一共有 62 人,从其中要公平地抽取出 10 人,而且不重复。所以,初步判断,这是一个简单的随机抽取过程,有 N 个数,从中抽出 M 个(M &lt; N)。直接随机抽取是最容易想到的:</p> <h3><a id="toc-5bf" class="anchor" href="#toc-5bf"></a>直接抽取</h3> <pre><code class="hljs lang-js"><span class="hljs-keyword">const</span> cards = <span class="hljs-built_in">Array</span>(<span class="hljs-number">62</span>).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>)=&gt;</span>i+<span class="hljs-number">1</span>); <span class="hljs-comment">//初始化一个 1~62 的数组</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params">n = <span class="hljs-number">1</span></span>)</span>{ <span class="hljs-comment">// 一次抽取 n 个,默认一次 1 个</span> <span class="hljs-keyword">var</span> ret = []; <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; n; i++){ <span class="hljs-keyword">let</span> idx = <span class="hljs-built_in">Math</span>.floor(cards.length * <span class="hljs-built_in">Math</span>.random()); ret.push(...cards.splice(idx, <span class="hljs-number">1</span>)); } <span class="hljs-keyword">return</span> ret; } <span class="hljs-built_in">console</span>.log(draw(<span class="hljs-number">10</span>)); <span class="hljs-comment">//抽取一次,10个中奖者</span> </code></pre> <p>上面这个方法非常直观,首先生成一个顺序的 1 ~ 62 号的数组,然后从其中随机抽取 10 次,为了不重复,将抽取的数字通过 <code>cards.splice(idx, 1)</code> 从原数组中取出来。</p> <p>上面这种方式可行,但它不是最好的,因为每次 splice 一个数字,取 10 个数字需要 splice 10 次,这看起来不是特别好。可以想到另一种方法,先对数组进行<a href="https://www.h5jun.com/post/array-shuffle.html">“洗牌”</a>,然后一次把 10 个数字取出来:</p> <h3><a id="toc-42e" class="anchor" href="#toc-42e"></a>先洗牌</h3> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params">amount, n = <span class="hljs-number">1</span></span>)</span>{ <span class="hljs-keyword">const</span> cards = <span class="hljs-built_in">Array</span>(amount).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>)=&gt;</span>i+<span class="hljs-number">1</span>); <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = amount - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i--){ <span class="hljs-keyword">let</span> rand = <span class="hljs-built_in">Math</span>.floor((i + <span class="hljs-number">1</span>) * <span class="hljs-built_in">Math</span>.random()); [cards[rand], cards[i]] = [cards[i], cards[rand]]; } <span class="hljs-keyword">return</span> cards.slice(<span class="hljs-number">0</span>, n); } <span class="hljs-built_in">console</span>.log(draw(<span class="hljs-number">62</span>, <span class="hljs-number">10</span>)); </code></pre> <p>上面这个版本是月影实际现场写出的(略有修改),它是不错的,但是它也有明显缺点。首先它先把所有的牌都排序了,但实际上只需要排序 10 张牌就好,多余的排序没有必要。其次,它不方便连续抽奖,比如第一次抽取 10 个号,然后再想多抽取 5 个号,它就做不到了。</p> <p>我们先解决第一个问题:</p> <h3><a id="toc-ef6" class="anchor" href="#toc-ef6"></a>不需要洗所有的牌</h3> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params">amount, n = <span class="hljs-number">1</span></span>)</span>{ <span class="hljs-keyword">const</span> cards = <span class="hljs-built_in">Array</span>(amount).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>)=&gt;</span>i+<span class="hljs-number">1</span>); <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = amount - <span class="hljs-number">1</span>, stop = amount - n - <span class="hljs-number">1</span>; i &gt; stop; i--){ <span class="hljs-keyword">let</span> rand = <span class="hljs-built_in">Math</span>.floor((i + <span class="hljs-number">1</span>) * <span class="hljs-built_in">Math</span>.random()); [cards[rand], cards[i]] = [cards[i], cards[rand]]; } <span class="hljs-keyword">return</span> cards.slice(-n); } <span class="hljs-built_in">console</span>.log(draw(<span class="hljs-number">62</span>, <span class="hljs-number">10</span>)); </code></pre> <p>上面这个版本是优化过的版本,显然如果取 10 个数,只需要循环 10 次即可,不需要把 64 张牌都洗了。</p> <p>要解决可以连续抽奖的问题,就需要把 cards 提取出来(就像方案 1 的随机抽取一样),但是那样的话就使得函数<strong>有副作用</strong>,虽说是临时写一个抽奖,也不喜欢设计得太糙。或者,那就加一个构造器执行初始化?</p> <h3><a id="toc-6f1" class="anchor" href="#toc-6f1"></a>构造器负责初始化</h3> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Box</span>(<span class="hljs-params">amount</span>)</span>{ <span class="hljs-keyword">this</span>.cards = <span class="hljs-built_in">Array</span>(amount).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>)=&gt;</span>i+<span class="hljs-number">1</span>); } Box.prototype.draw = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">n = <span class="hljs-number">1</span></span>)</span>{ <span class="hljs-keyword">let</span> amount = <span class="hljs-keyword">this</span>.cards.length, cards = <span class="hljs-keyword">this</span>.cards; <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = amount - <span class="hljs-number">1</span>, stop = amount - n - <span class="hljs-number">1</span>; i &gt; stop; i--){ <span class="hljs-keyword">let</span> rand = <span class="hljs-built_in">Math</span>.floor((i + <span class="hljs-number">1</span>) * <span class="hljs-built_in">Math</span>.random()); [cards[rand], cards[i]] = [cards[i], cards[rand]]; } <span class="hljs-keyword">let</span> ret = cards.slice(-n); cards.length = amount - n; <span class="hljs-keyword">return</span> ret; } <span class="hljs-keyword">var</span> box = <span class="hljs-keyword">new</span> Box(<span class="hljs-number">62</span>); <span class="hljs-built_in">console</span>.log(box.draw(<span class="hljs-number">5</span>), box.draw(<span class="hljs-number">5</span>)); <span class="hljs-comment">//一次取 5 个,取 2 次</span> </code></pre> <h3><a id="toc-db5" class="anchor" href="#toc-db5"></a>更优雅的解决方式?</h3> <p>实际上,对于一次可能抽取任意多个获奖人的场景,用 ES6 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*">generators</a> 非常合适,我们可以直接拿洗牌的版本略做修改:</p> <pre><code class="hljs lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> * <span class="hljs-title">draw</span>(<span class="hljs-params">amount</span>)</span>{ <span class="hljs-keyword">const</span> cards = <span class="hljs-built_in">Array</span>(amount).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>)=&gt;</span>i+<span class="hljs-number">1</span>); <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = amount - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i--){ <span class="hljs-keyword">let</span> rand = <span class="hljs-built_in">Math</span>.floor((i + <span class="hljs-number">1</span>) * <span class="hljs-built_in">Math</span>.random()); [cards[rand], cards[i]] = [cards[i], cards[rand]]; <span class="hljs-keyword">yield</span> cards[i]; } } <span class="hljs-keyword">var</span> drawer = draw(<span class="hljs-number">62</span>); <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">Array</span>(<span class="hljs-number">10</span>).fill().map(<span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>drawer.next().value)); <span class="hljs-comment">//一次取出10个结果</span> </code></pre> <p>最后补充一个小技巧,利用 <code>Array(n).fill().map(...)</code> 可以方便快速地构造数组:</p> <pre><code class="hljs lang-js"><span class="hljs-built_in">Array</span>(<span class="hljs-number">10</span>).fill().map(<span class="hljs-function">(<span class="hljs-params">_,i</span>) =&gt;</span> i+<span class="hljs-number">1</span>); <span class="hljs-comment">// 得到 [1,2,3,4,5,6,7,8,9,10]</span> </code></pre> <h3><a id="toc-25f" class="anchor" href="#toc-25f"></a>总结</h3> <p>现场抽奖需求虽简单,但也有那么多可以思考的点,不知道你 get 到哪些点,不知道你喜欢哪个版本的代码。或者你有自己的思路?欢迎在底部评论区写下来~</p> Tue, 17 Jan 2017 10:29:49 GMT http://www.75team.com/post/luckey-draw-in-5-minutes.html Chrome Dev Summit 2016 内容整理(部分) http://www.75team.com/post/cds.html <blockquote> <p>原文:<a href="http://www.zhengqingxin.com/post/cds.html">http://www.zhengqingxin.com/post/cds.html</a></p> </blockquote> <h2 id="progressivewebapps">Progressive Web Apps</h2><div class="toc"><ul> <li><a href="#progressivewebapps">Progressive Web Apps</a></li> <li><a href="#keynote">KeyNote</a></li> <li><a href="#toc-8bf">Building Progressive Web apps.</a></li> <li><a href="#toc-bc2">Future App Model: Advanced Service Worker</a></li> <li><a href="#progressiveperformance">Progressive Performance</a></li> <li><a href="#toc-710">The “Progressive” in Progressive Web Apps</a></li> <li><a href="#toc-ecf">What Comes Next for the Web?</a></li> <li><a href="#toc-c4d">观后感</a></li> </ul> </div> <p>今年如果你关注过 Chrome 开发团队的话,那一定或多或少的会听到过 Progressive Web Apps(PWA),实际上,今年 Chrome 团队可谓是 倾尽全力的推广 PWA,从上半年的 Google IO 大会关于 各类有关 PWA 的 9 个专题,直到今天刚刚结束的,基本是 PWA 推广大会的 CDS,可见随着 PWA 技术的成熟以及浏览器的支持度提高,不久必将会迎来一次爆发。如果你目前还不了解什么是 PWA ,请去 <a href="https://developers.google.com/web/progressive-web-apps/">这里 </a>自行补脑,这里我简单来说,PWA 就是能够提供类似像 Navive App 一样体验的 Web App。它主要有几个特点:</p> <ul> <li>可以添加到桌面,文艺点说就是具有可安装性</li> <li>离线能力</li> <li>消息推送</li> <li>安全</li> <li>响应式</li> </ul> <blockquote> <p>博主凑热闹,最近把博客添加了离线和添加到主屏幕功能,如果你的手机支持(安卓手机新版 Chrome,目前苹果还不支持),当然,或者是 PC 端的 Chrome,欢迎体验。</p> </blockquote> <p>好了,废话不多说,这次大会在 youtube 上实时更新,我趁热挑选了自己感兴趣的几个主题,整理了主要内容,由于时间比较短,有些内容只是知道了概念(甚至有些只知道了个新名词),如果不对,欢迎大家留言指正,我后面也会陆续更新。 <!--more--></p> <h2 id="keynote">KeyNote</h2> <blockquote> <p>点击 <a href="https://www.youtube.com/watch?v=eI3B6x0fw9s">这里</a> 观看视频。</p> </blockquote> <p>首先是 Darin Fisher 的开场,介绍了 Chrome 以及 Web 的现状。</p> <ul> <li>目标:让 Web 变得更先进。</li> <li>目前 Chrome 拥有 20 亿用户(手机+PC)</li> <li>Link 很牛</li> <li>每次交互的改变将会挖掘 20% 的潜在用户</li> <li>移动端 Web:局限带来创新</li> <li>移动端的挑战:分辨率,cpu,内存,电池,网络</li> <li>60% 的手机用户是 2G 网络</li> <li>印度 2.3 亿,美国 4.6 亿,中国 7.6 亿的网民</li> <li>在印度 65% 人不上网,美国 20%</li> <li>Progressive Web Apps 从根本上提高 web 体验</li> <li><a href="http://cnet.com/tech-today">PWA demo</a></li> <li>用<a href="https://m.alibaba.com/"> 阿里 </a>的 PWA 举例(原来老外也知道双十一),提高了 76% 的转化率</li> <li>超过 3 秒的网页,53% 的用户选择离开</li> <li>Service Worker API</li> <li>在 3G 环境下,要确保页面 5 秒内加载完成</li> <li>“添加到主屏幕”这个功能提高了用户 4 倍的浏览频率</li> <li>消息推送:每天都有来自 5 万个域名下的 180 亿次推送</li> <li>Seamless Sign-In:在 AliExpress 中降低 85% 登陆失败的几率,提高了 11% 的转化率</li> <li>Payment API</li> <li>Lighthouse 是个测试 PWA 的工具(测试了一下我博客的分数,居然到了80分,窃喜~)</li> <li>Polymer</li> <li><a href="https://beta.webcomponents.org/">Beta webcomponents</a></li> <li>AMP(Accelerated Mobile Pages)</li> <li><a href="https://browser-issue-tracker-search.appspot.com/"> browser-issue-tracker-search </a>为开发者提供的网站,包括bug,w3c 标准,API 建议等</li> <li>Web assembly, WebGL 2.0, WebVR, WebAR, WebBluetooth</li> </ul> <h2 id="toc-8bf">Building Progressive Web apps.</h2> <blockquote> <p>点击 <a href="https://www.youtube.com/watch?v=U52dD0tegsA">这里</a> 观看视频。</p> </blockquote> <p>Thao Tran 主要是用一些数据和示例为我们展示了 PWA 的价值以及意义。</p> <ul> <li>去年的时候基本只有 Flipkart 做了 PWA 的产品,但今年目前为止已经有来自 35 个国家的 17K 个 PWA。</li> <li>在美帝,PWA 已经常常会出现在各个热门技术杂志的头条。</li> <li>简单介绍什么是 PWA</li> <li>列举了几个 PWA 的例子以及带来有力数据,<a href="https://m.alibaba.com/">https://m.alibaba.com/</a>,<a href="https://housing.com/">https://housing.com</a>,这里不得不膜拜一下阿里。数据主要集中在用户转化率,效率以及开发成本。</li> <li>用 <a href="https://mobile-beta.westelm.com/">West elm</a> show 一下 PWA。</li> <li>围绕 PWA 加载快,省流量列举数据。</li> <li>再次列举几个 PWA 产品。</li> </ul> <h2 id="toc-bc2">Future App Model: Advanced Service Worker</h2> <blockquote> <p><a href="https://jakearchibald.com/">Jake 大神</a> 的演讲,点击 <a href="https://www.youtube.com/watch?v=J2dOTKBoTL4">这里</a> 观看视频。</p> </blockquote> <p>Jake 大神的演讲其实在他<a href="https://jakearchibald.com/2016/streams-ftw/"> 之前的博客 </a>中介绍过一些内容,演讲诙谐幽默,并又带来了很多干货:</p> <ul> <li>介绍了 Service Worker 浏览器支持情况:<ul> <li>Chrome/Firefox:支持</li> <li>Edge:实现中(高优先级)</li> <li>Safari:Considering...(这里不知道怎么翻译,犹豫中?思考中?)</li> </ul> </li> <li>Stream<ul> <li>Async iterators(<a href="https:// github.com/tc39/proposal-async-iteration">https://github.com/tc39/proposal-async-iteration</a>)</li> <li>Transform streams(<a href="https://streams.spec.whatwg.org/">https://streams.spec.whatwg.org/</a>) </li> <li>Identity Streams</li> </ul> </li> <li>抛出 SPA 不如 Server Rendering 快的问题。</li> <li>Service Worker + Streams 可以改善,但还是不能做到 SPA 比 Server Render 快并用 github 举了例子。</li> <li>然而,PWA != SPA</li> <li>Navigation preload</li> <li>Forigen Fetch</li> <li>Background Fetch</li> <li>Navigation transitions</li> </ul> <h2 id="progressiveperformance">Progressive Performance</h2> <blockquote> <p>点击 <a href="https://www.youtube.com/watch?v=4bZvq3nodf4">这里</a> 观看视频。</p> </blockquote> <p>这次 Alex 并没有直接说 PWA 的话题,而是为我们带来了移动端性能方面的分享,指出了现在移动端性能低下,要注重在恶劣环境下(网络差,设备低电量等)还保持一个好的用户体验。</p> <ul> <li>Mobile website 越来越重要</li> <li>在 PC 开发环境下看起来性能还不错的网站,在手机上其实并不尽人意</li> <li>如果你的页面在 3 秒钟加载不出来的话,有 53% 的用户会选择离开</li> <li>而报告中表示手机网页打开的平均时间在 19s</li> <li>Motion Mark 测试,PC 要比手机端快 25 倍</li> <li>要用真机 Debug</li> <li>要重视低端设备</li> <li>Power = Heat,硬件限制以至于手机不能比 PC 快</li> <li><a href="https://hpbn.co/">High Performance Browser Networking</a></li> <li>少加载 code,在合适的时间执行合适的代码</li> <li>Service Worker 不仅仅是离线,更重要的是提升效率</li> </ul> <h2 id="toc-710">The “Progressive” in Progressive Web Apps</h2> <blockquote> <p>点击 <a href="https://www.youtube.com/watch?v=ARkPBm6AcNA">这里</a> 观看视频。</p> </blockquote> <p>Patrick Kettner 是来自微软 Edge 的 PM,他演讲的主线是他儿子顺便介绍了一下 PWA,但由此可见微软支持 PWA 只是早晚的事情。</p> <ul> <li>2g 网络毁了他的生活</li> <li>回顾了一下 App Cache </li> <li>提供了一种新的<a href="https://webmanife.st/"> 开发方式 </a></li> <li>PWA 在 Edge 上</li> <li>WebWorkerPreProcessor</li> </ul> <h2 id="toc-ecf">What Comes Next for the Web?</h2> <blockquote> <p>点击 <a href="https://www.youtube.com/watch?v=YJwrBbze_Ec&amp;t=8s">这里</a> 观看视频。</p> </blockquote> <p>Paul 主要分享了关于 Web 未来的发展方向,包括 WebVR,性能提升等等。</p> <ul> <li>Web 一定会赶超 Native APIs</li> <li>手机游戏会与 Native 平起平坐,并带来更高的利润</li> <li>现有 API<ul> <li>定位(Geolocation)</li> <li>相机(Camera)</li> <li>麦克风(Microphone)</li> <li>电池(Battery)</li> <li>权限(Permissions)</li> <li>网络状况(Network)</li> <li>自动填充(Autofill)</li> <li>用户授权(Credential Management API)</li> <li>支付(PaymentRequest API)</li> <li>消息推送(Push notifications)</li> <li>离线(Offline)</li> <li>可安装(Installability)</li> </ul> </li> <li>SLICE(Secure,Linkable,Indexable,Composable,Ephemeral)</li> <li>计划:<ul> <li>已经实现的<ul> <li>position: sticky,Intersection Observer, Web Componensts, PointerEvents</li> <li>PWA:App drawer,system UI.</li> <li>PWA ACTION_VIEW</li> <li>从消息启动应用</li> <li>名称和图片可更新(manifest.json)</li> <li>manifest.json 添加 scope 属性</li> <li><a href="chrome://flags/">chrome://flags/</a> 启用这些功能</li> </ul> </li> <li>将会实现<ul> <li><a href="https://infrequently.org/2015/08/doing-science-on-the-web/">Doing Science On The Web</a></li> <li><a href="https://github.com/jpchase/origintrials">https://github.com/jpchase/origintrials</a></li> <li>Web Share API</li> <li>Media improvements(多设备之间)</li> <li>Background playback(比如锁屏后的音乐)</li> <li>canvas.captureStream() + Web RTC</li> <li>canvas.captureStream() + MediaRecorder.</li> <li>ImageCaputre API.</li> <li>FaceDetector API.</li> <li><a href="https://w3c.github.io/sensors/">Generic Sensor API</a>.</li> <li>AmbientLightSensor API.</li> <li>Fused Sensors</li> </ul> </li> <li>新的交互方式<ul> <li>Physical Web.</li> <li>Web Bluetooth.</li> <li>WebUSB.</li> <li>WebVR.</li> </ul> </li> </ul> </li> </ul> <h2 id="toc-c4d">观后感</h2> <p>这周末在家宅两天来看会议视频其实还是值得的,有了不少收获。这次大会几乎所有人的演讲都会提到 PWA,PWA 是 Chrome 团队近两年来全力推广,安卓系统也提供全方位的支持,PWA 必将会是未来 Web 发展中非常重要的一步。同时 PWA 也涵盖了很多新技术,Service Worker,Streams,Cache,用户授权还有大会中提到的相机,支付等等原生 API ,所以看来前端之路,路漫漫其修远兮。</p> Sun, 13 Nov 2016 15:32:36 GMT http://www.75team.com/post/cds.html 【Service Worker】生命周期那些事儿 http://www.75team.com/post/lifecycle.html <blockquote> <p>原文:<a href="http://www.zhengqingxin.com/post/lifecycle.html">http://www.zhengqingxin.com/post/lifecycle.html</a></p> <p>生命周期是 Service Worker 中比较复杂的一部分,如果你不知道它什么时间将要做什么,以及它带来的好处,那么你可能会有一种感觉:就是它一直在和你较劲。如果理解它的工作机制,你就可以给用户提供完美的,无感知的更新体验。 这篇文章是 Chrome 团队最近总结的一片文章,配合例子讲述生命周期,让我们更容易理解,也解决了我之前开发中遇到的一些困惑,所以决定翻译出来。此处<a href="https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle#updates"> 阅读原文 </a>。</p> </blockquote> <!--more--> <script type="text/javascript"> function iframeLoaded(id) { var iFrameID = document.getElementById(id); if(iFrameID) { iFrameID.height = ""; iFrameID.height = iFrameID.contentWindow.document.body.scrollHeight + "px"; } } </script> <h2 id="toc-d99">目的</h2><div class="toc"><ul> <li><a href="#toc-d99">目的</a></li> <li><a href="#toc-aeb">第一个 Service Worker</a><ul> <li><a href="#toc-98d">作用域与控制</a></li> <li><a href="#toc-ca3">下载-解析-执行</a></li> <li><a href="#toc-faf">安装(install)</a></li> <li><a href="#toc-f91">激活(Activate)</a></li> <li><a href="#toc-78a">clients.claim</a></li> </ul> </li> <li><a href="#toc-d2c">更新 Service Worker</a><ul> <li><a href="#install">install</a></li> <li><a href="#waiting">Waiting</a></li> <li><a href="#activate">Activate</a></li> <li><a href="#skipwaiting">skipWaiting</a></li> <li><a href="#toc-681">手动更新</a></li> <li><a href="#toc-135">避免改变 SW 的 URL</a></li> </ul> </li> <li><a href="#toc-7e7">让开发更简单</a><ul> <li><a href="#updateonreload">Update on reload</a></li> <li><a href="#skipwaiting">Skip Waiting</a></li> <li><a href="#toc-5ad">强制刷新</a></li> </ul> </li> <li><a href="#toc-bce">处理更新</a></li> </ul> </div> <p>本文中介绍利用生命周期可以实现的功能大概有如下几点:</p> <ul> <li><p>实现缓存优先(offline-first)</p> </li> <li><p>在不打断现有 SW 的情况下,准备好一个新的 SW </p> </li> <li><p>让注册 SW 的页面同一时间只归属同一个 SW 控制</p> </li> <li><p>确保你的网站只有一个版本在运行</p> </li> </ul> <p>最后一点尤其重要,一般情况下(没有 SW 的情况),用户浏览你的网站时可能先打开一个 tab,过了一会儿又打开了一个 tab,结果就是在同一时间,你的页面运行了两个版本,大部分时候,这样是没问题的,但是如果你使用了缓存,那么两个 tab 就要面临如何管理缓存的问题,如果处理不好,它可能会造成异常,严重的造成数据丢失。</p> <blockquote> <p>用户非常不喜欢数据丢失,这会让他们非常忧桑。</p> </blockquote> <h2 id="toc-aeb">第一个 Service Worker</h2> <ul> <li><code>install</code> 事件是 SW 触发的第一个事件,并且仅触发一次。</li> <li><code>installEvent.waitUntil()</code>接收一个 Promise 参数,用它来表示 SW 安装的成功与否。</li> <li>SW 在安装成功并激活之前,不会响应 <code>fetch</code>或<code>push</code>等事件。</li> <li>默认情况下,页面的请求(fetch)不会通过 SW,除非它本身是通过 SW 获取的,也就是说,在安装 SW 之后,需要刷新页面才能有效果。</li> <li><code>clients.claim()</code>可以改变这种默认行为。 </li> </ul> <div style="max-width:600px;"> <iframe id="iframe-1" onload="iframeLoaded('iframe-1')" style="width:100%;border:none;" src="https://www.zhengqingxin.com/static/demo/5-lifecycle/iframe1.html"> </iframe> </div> <p>举个例子:</p> <pre><code class="hljs lang-xml"><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span> 3秒后将出现一张图片: <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript"> navigator.serviceWorker.register(<span class="hljs-string">"/sw.js"</span>) .then(reg =&gt; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"SW registered!"</span>, reg)) .catch(err =&gt; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Boo!"</span>, err)); setTimeout(() =&gt; { <span class="hljs-keyword">const</span> img = <span class="hljs-keyword">new</span> Image(); img.src = <span class="hljs-string">"/dog.svg"</span>; <span class="hljs-built_in">document</span>.body.appendChild(img); }, <span class="hljs-number">3000</span>); </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> </code></pre><p>这里注册了一个 SW,并且在 3 秒后在页面上添加一个图片。</p> <p>这是 SW 的代码:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"install"</span>, event =&gt; { console<span class="hljs-selector-class">.log</span>(<span class="hljs-string">"V1 installing…"</span>); <span class="hljs-comment">// 这里缓存一个 cat.svg</span> event<span class="hljs-selector-class">.waitUntil</span>( caches.open(<span class="hljs-string">"static-v1"</span>).then(cache =&gt; cache.add(<span class="hljs-string">"/cat.svg"</span>)) ); }); <span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"activate"</span>, event =&gt; { console<span class="hljs-selector-class">.log</span>(<span class="hljs-string">"V1 now ready to handle fetches!"</span>); }); <span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"fetch"</span>, event =&gt; { const url = new URL(event<span class="hljs-selector-class">.request</span><span class="hljs-selector-class">.url</span>); <span class="hljs-comment">//如果是同域并且请求的是 dog.svg 的话,那么返回 cat.svg</span> <span class="hljs-selector-tag">if</span> (url.origin == location.origin &amp;&amp; url.pathname == <span class="hljs-string">"/dog.svg"</span>) { event<span class="hljs-selector-class">.respondWith</span>(caches.match(<span class="hljs-string">"/cat.svg"</span>)); } }); </code></pre><p>这里 SW 缓存了 cat.svg,并当请求 dog.svg 的时候返回它。然而,当你运行<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/"> 这个例子 </a> 的时候,你会发现第一次出现一只狗,刷新后,猫才会出现。</p> <h3 id="toc-98d"><a class="anchor" href="#toc-98d"></a>作用域与控制</h3> <p>SW 的默认作用域为基于当前文件 URL 的 <code>./</code>。意思就是如果你在<code>//example.com/foo/bar.js</code>里注册了一个 SW,那么它默认的作用域为 <code>//example.com/foo/</code>。</p> <p>我们把页面,workers,shared workers 叫做<code>clients</code>。SW 只能对作用域内的<code>clients</code>有效。一旦一个<code>client</code>被“控制”了,那么它的请求都会经过这个 SW。我们可以通过查看<code>navigator.serviceWorker.controller</code>是否为 null 来查看一个<code>client</code>是否被 SW 控制。</p> <h3 id="toc-ca3"><a class="anchor" href="#toc-ca3"></a>下载-解析-执行</h3> <p>当你调用<code>.register()</code>的时候,第一个 SW 被下载下来,这过程中如果下载,解析或者在初始化中有错误的话,那么 <code>register</code> 的Promise 会返回 reject,然后 SW 会被销毁。</p> <p>Chrome 开发者工具会展示出来错误,在 Application 中的 Service Workers Tab:</p> <p><img src="https://www.zhengqingxin.com/static/upload/201611/zCoqxSR5wEjsCEgwOh-HodHB.png" alt="alt"></p> <h3 id="toc-faf"><a class="anchor" href="#toc-faf"></a>安装(install)</h3> <p>SW 首先会触发<code>install</code>,每个 SW 只会被触发一次,当你修改你的 SW 后,浏览器会认为这是一个新的 SW,从而会再触发这个新 SW 的<code>install</code>事件,在后面会详细说到。</p> <p><code>install</code>是在 SW 控制 <code>clients</code>之前处理缓存很好的时机。在 <code>event.waitUntil()</code>传入的 Promise 会让浏览器知道 SW 什么时候安装成功以及是否成功。</p> <p>当 Promise reject 的时候,代表着安装失败,浏览器将这个 SW 废弃掉,不会控制任何 clients。</p> <h3 id="toc-f91"><a class="anchor" href="#toc-f91"></a>激活(Activate)</h3> <p>安装成功后并激活(activate)成功后,SW 就可以处理“功能性的事件“了,比如<code>push</code>,<code>sync</code>。<strong><em>但这并不代表调用<code>.register()</code>的页面会立即生效。</em></strong></p> <p>第一次你请求<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/"> 这个demo </a>的时候,虽然在 SW 被激活后很久才请求了<code>dog.svg</code>(因为这里等待了三秒),但 SW 也并没有处理这个请求,结果你看见了一只狗。当你第二次请求的时候,也就是刷新页面,这时请求被处理了,当前页面和图片都经过了 SW 的 <code>fetch</code>事件,所以你看见了一只猫。</p> <h3 id="toc-78a"><a class="anchor" href="#toc-78a"></a>clients.claim</h3> <p>你可以在<code>activate</code>事件中通过调用<code>clients.claim()</code>来让没被控制的 clients 受控。</p> <p>比如<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/df4cae41fa658c4ec1fa7b0d2de05f8ba6d43c94/"> 这个demo </a>,可能第一次你就会看见一只猫,这里我说“可能”,是因为这时时间敏感的,仅当 SW 激活并且<code>clients.claim()</code>被调用成功在图片请求之前的时候才可以。</p> <p>所以,可想而知,当你用 SW 加载与正常请求不同资源的时候(比如上面的例子),那用<code>clients.claim()</code>可能会遇到一些问题,这时有些资源可能不会通过你的SW。</p> <blockquote> <p>我见过很多人在代码中把<code>clients.claim()</code>当做了必选项,但我自己很少这样做,因为仅仅是第一次加载不会通过 SW,而且页面还是都会正常运行的。</p> </blockquote> <h2 id="toc-d2c">更新 Service Worker</h2> <p>简单来说:</p> <ul> <li>触发更新的几种情况:<ul> <li>第一次导航到作用域范围内页面的时候</li> <li>当在24小时内没有进行更新检测并且触发功能性时间如<code>push</code>或<code>sync</code>的时候</li> <li>SW 的 URL 发生变化并调用<code>.register()</code>时</li> </ul> </li> <li>当 SW 代码发生变化,SW 会做更新(还将包括引入的脚本)</li> <li>更新后的 SW 会和原始的 SW 共同存在,并运行它的<code>install</code></li> <li>如果新的 SW 不是成功状态,比如 404,解析失败,执行中报错或者在 install 过程中被 reject,它将会被废弃,之前版本的 SW 还是激活状态不变。</li> <li>一旦新 SW 安装成功,它会进入<code>wait</code>状态直到原始 SW 不控制任何 clients。</li> <li><code>self.skipWaiting()</code>可以阻止等待,让新 SW 安装成功后立即激活。</li> </ul> <div style="max-width:600px;"> <iframe id="iframe-2" onload="iframeLoaded('iframe-2')" style="width:100%;border:none;" src="https://www.zhengqingxin.com/static/demo/5-lifecycle/iframe2.html"> </iframe> </div> <p>下面我们来举个 SW 更新的例子,这是一个将猫变成马的故事:</p> <pre><code class="hljs lang-php"><span class="hljs-keyword">const</span> expectedCaches = [<span class="hljs-string">"static-v2"</span>]; <span class="hljs-keyword">self</span>.addEventListener(<span class="hljs-string">"install"</span>, event =&gt; { console.log(<span class="hljs-string">"V2 installing…"</span>); <span class="hljs-comment">// 将 horse.svg 缓存在新的缓存 static-v2 中</span> event.waitUntil( caches.open(<span class="hljs-string">"static-v2"</span>).then(cache =&gt; cache.add(<span class="hljs-string">"/horse.svg"</span>)) ); }); <span class="hljs-keyword">self</span>.addEventListener(<span class="hljs-string">"activate"</span>, event =&gt; { <span class="hljs-comment">// 删除额外的缓存,static-v1 将被删掉</span> event.waitUntil( caches.keys().then(keys =&gt; Promise.all( keys.map(key =&gt; { <span class="hljs-keyword">if</span> (!expectedCaches.includes(key)) { <span class="hljs-keyword">return</span> caches.delete(key); } }) )).then(() =&gt; { console.log(<span class="hljs-string">"V2 now ready to handle fetches!"</span>); }) ); }); <span class="hljs-keyword">self</span>.addEventListener(<span class="hljs-string">"fetch"</span>, event =&gt; { <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(event.request.url); <span class="hljs-comment">//如果是同域并且请求的是 dog.svg 的话,那么返回 horse.svg</span> <span class="hljs-keyword">if</span> (url.origin == location.origin &amp;&amp; url.pathname == <span class="hljs-string">"/dog.svg"</span>) { event.respondWith(caches.match(<span class="hljs-string">"/horse.svg"</span>)); } }); </code></pre><p>如果你打开<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/index-v2.html"> 这个 demo </a>,你会看到一只喵,原因是这样的...</p> <h3 id="install"><a class="anchor" href="#install"></a>install</h3> <p>注意这里我们将缓存从 static-v1 换到了 static-v2,这代表了我用了一个新的缓存空间覆盖了之前 SW 正在使用的缓存。</p> <p>这里新建了一块缓存的做法类似于原生 app 中将每块资源打包到一块指定的执行空间的做法,有时候结合实际情况,你也可以不这么做。</p> <h3 id="waiting"><a class="anchor" href="#waiting"></a>Waiting</h3> <p>一旦新 SW 安装成功,它会进入<code>wait</code>状态直到原始 SW 不控制任何 clients。这个状态是<code>waiting</code>,这也是浏览器确保在同一时间只有一个版本的 SW 运行的机制。</p> <p>如果你再次打开<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/index-v2.html"> 这个 demo </a>,你还是会看到一只喵,因为新的 SW 还是没有被激活,在开发者工具里你依然看到它是 waiting 状态。</p> <p><img src="https://www.zhengqingxin.com/static/upload/201611/Yv0Gle9WvauL3JO85VcDUX47.png" alt="alt"></p> <p>尽管这个例子中你仅打开了一个 tab,但刷新页面并没有用,这是由于浏览器本身的机制,当你刷新的时候,当前页面不会离开,直到收到了一个响应头,而且即使这样,如果响应中包含<code>Content-Disposition</code>的话,当前页面还是不会离开。由于这个时间上的重叠,在刷新的时候当前的 SW 总是控制了一个 client。</p> <p>为了让 SW 更新,你需要把所有用原始 SW 的页面 tab 关闭或者跳转走,这时你再访问<a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/index-v2.html"> 这个 demo </a>,你就会看到了一匹野马。</p> <p>这种机制类似于 Chrome 本身的更新机制,Chrome 在后台更新,只有当你重启浏览器的时候才会生效,在这期间你不会被打扰,可以继续使用当前版本。然而,这样可能会使我们开发者比较痛苦,好在开发者工具帮我们解决了这个事情,后面会说到。</p> <h3 id="activate"><a class="anchor" href="#activate"></a>Activate</h3> <p>Activate 在旧的 SW 离开时会被触发,这时新的 SW 可以控制 clients。这时候你可以做一些在老 SW 运行时不能做的事情,比如清理缓存。</p> <p>在上面的例子中,之前保留的缓存,在<code>activate</code>时间执行的时候被清理掉。</p> <blockquote> <p>这里最好不要更新以前的版本,而是直接分配新的缓存空间。</p> </blockquote> <p>如果你在<code>event.waitUntil()</code>中传入了一个 Promise,SW 将会缓存住功能性事件(<code>fetch</code>,<code>push</code>,<code>sync</code>等等),直到 Promise 返回 resolve 的时候再触发,也就是说,当你的<code>fetch</code>事件被触发的时候,SW 已经被完全激活了。</p> <blockquote> <p>cache storage API 和 localStorage,IndexedDB 一样是“同域”的。如果你在一个父域下运行多个网站,比如 <code>yourname.github.io/myapp</code>,这就要小心你不要把别的网站的缓存删掉了。避免这个问题,你可以将 cache 的 key 设的具有唯一性,比如 myapp-static-v1 并且约束不要碰不以 myapp- 开头的缓存。</p> </blockquote> <h3 id="skipwaiting"><a class="anchor" href="#skipwaiting"></a>skipWaiting</h3> <p>waiting 意在让你的网站同一时间只有一个 SW 在运行,但如果你不想要这样的话,你可以通过调用<code>self.skipWaiting()</code>来让新 SW 立即激活。</p> <p>这么做会让你的新 SW 踢掉旧的,然后当它变为 waiting 状态时立即激活,注意这里不会跳过 installing,只会跳过 waiting。</p> <p>在 waiting 之前或者之后调用<code>skipWaiting()</code>都可以,一般情况我们在 <code>install</code> 事件中调用:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"install"</span>, event =&gt; { self<span class="hljs-selector-class">.skipWaiting</span>(); event<span class="hljs-selector-class">.waitUntil</span>( <span class="hljs-comment">// caching etc</span> ); }); </code></pre><p><a href="https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/index-v3.html">这个例子</a>中,你可能直接可以看到一只奶牛,和<code>clients.claim()</code>一样,这是一场赛跑,仅当你的新 SW 安装,激活等早于你请求图片时,奶牛才会出现。</p> <blockquote> <p><code>skipWaiting()</code>意味着新 SW 控制了之前用旧 SW 获取的页面,也就是说你的页面有一部分资源是通过旧 SW 获取,剩下一部分是通过新 SW 获取的,如果这样做会给你带来麻烦,那就不要用<code>skipWaiting()</code>,这点我们应该根据具体情况评估。</p> </blockquote> <h3 id="toc-681"><a class="anchor" href="#toc-681"></a>手动更新</h3> <p>像我之前说的,当页面刷新或者执行功能性事件时,浏览器会自动检查更新,其实我们也可以手动的来触发更新:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.register</span>(<span class="hljs-string">"/sw.js"</span>)<span class="hljs-selector-class">.then</span>(reg =&gt; { <span class="hljs-comment">// sometime later…</span> reg<span class="hljs-selector-class">.update</span>(); }); </code></pre><p>如果你希望你的用户访问页面很长时间而且不用刷新,那么你可以每个一段时间调用一次<code>update()</code>。</p> <h3 id="toc-135"><a class="anchor" href="#toc-135"></a>避免改变 SW 的 URL</h3> <p>如果你看过我的文章<a href="https://jakearchibald.com/2016/caching-best-practices/">缓存最佳实践</a>,你可能会考虑给每个 SW 不同的 URL。<strong>千万不要这么做!</strong>在 SW 中这么做是“最差实践”,要在原地址上修改 SW。</p> <p>举个例子来说明为什么:</p> <p>1.<code>index.html</code>注册了<code>sw-v1.js</code>作为SW。</p> <p>2.<code>sw-v1.js</code>对<code>index.html</code>做了缓存,也就是缓存优先(offline-first)。</p> <p>3.你更新了<code>index.html</code>重新注册了在新地址的 SW <code>sw-v2.js</code>.</p> <p>如果你像上面那么做,用户永远也拿不到<code>sw-v2.js</code>,因为<code>index.html</code>在<code>sw-v1.js</code>缓存中,这样的话,如果你想更新为<code>sw-v2.js</code>,还需要更改原来的<code>sw-v1.js</code>。</p> <p>在上面的 demo 里,我给每个 SW 用了不同的 URL,这只是为了做演示,不要在生产环境中这么做。</p> <h2 id="toc-7e7">让开发更简单</h2> <p>SW 的生命周期是为了用户构建的,但这样难免让我们开发带来一些烦恼,幸亏与一些工具来帮助我们。</p> <h3 id="updateonreload"><a class="anchor" href="#updateonreload"></a>Update on reload</h3> <p>这是我最喜欢的:</p> <p><img src="https://www.zhengqingxin.com/static/upload/201611/3U701pG4rTcvhD6p4cbuhe1y.png" alt="alt"></p> <p>这样把生命周期变得对开发友好了,每次跳转将会:</p> <p>1.重新获取 SW</p> <p>2.尽管字节一致,也会重新安装,也就是说<code>install</code>事件被执行并且更新缓存。</p> <p>3.跳过 waiting,激活新的 SW。</p> <p>4.导航到这个页面。</p> <p>这就是说你每次操作都会更新而不用刷新页面或者关闭 tab。</p> <h3 id="skipwaiting"><a class="anchor" href="#skipwaiting"></a>Skip Waiting</h3> <p><img src="https://www.zhengqingxin.com/static/upload/201611/ozuhSZ9OREQH_A0cCZ2eUhmZ.png" alt="alt"> 如果你有个 SW 在等待状态,你可以点击 skipWaiting 让它立即变为激活状态。</p> <h3 id="toc-5ad"><a class="anchor" href="#toc-5ad"></a>强制刷新</h3> <p>如果你强制刷新页面,那么会绕过 SW,变成不受控,这个功能已被定为规范,所以在其他支持 SW 的浏览器中也适用。</p> <h2 id="toc-bce">处理更新</h2> <p>Service Worker 是<a href="https://extensiblewebmanifesto.org/"> 可扩展web </a>的一部分。想法初衷是,作为浏览器开发者,有时候我们可能不如 web 开发者更了解 web,所以,我们其实不应该提供仅仅可以解决具体问题的 API,而是应该给 web 开发者更多的权限从而更好的解决问题。</p> <p>所以,我们尽可能的开放更多,SW 整个生命周期都是可查看的:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.register</span>(<span class="hljs-string">"/sw.js"</span>)<span class="hljs-selector-class">.then</span>(reg =&gt; { reg<span class="hljs-selector-class">.installing</span>; <span class="hljs-comment">// 安装中的 SW,或者是undefined</span> reg<span class="hljs-selector-class">.waiting</span>; <span class="hljs-comment">// 等待中的 SW,或者是undefined</span> reg<span class="hljs-selector-class">.active</span>; <span class="hljs-comment">// 激活中的 SW,或者是undefined</span> <span class="hljs-selector-tag">reg</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"updatefound"</span>, () =&gt; { <span class="hljs-comment">// 正在安装的新的 SW</span> const newWorker = reg<span class="hljs-selector-class">.installing</span>; newWorker<span class="hljs-selector-class">.state</span>; <span class="hljs-comment">// "installing" - 安装事件被触发,但还没完成</span> <span class="hljs-comment">// "installed" - 安装完成</span> <span class="hljs-comment">// "activating" - 激活事件被触发,但还没完成</span> <span class="hljs-comment">// "activated" - 激活成功</span> <span class="hljs-comment">// "redundant" - 废弃,可能是因为安装失败,或者是被一个新版本覆盖</span> <span class="hljs-selector-tag">newWorker</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"statechange"</span>, () =&gt; { <span class="hljs-comment">// newWorker 状态发生变化</span> }); }); }); <span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"controllerchange"</span>, () =&gt; { <span class="hljs-comment">// 当 SW controlling 变化时被触发,比如新的 SW skippedWaiting 成为一个新的被激活的 SW</span> }); </code></pre> Sat, 05 Nov 2016 06:10:04 GMT http://www.75team.com/post/lifecycle.html 值得订阅的 12 份优质前端期刊 http://www.75team.com/post/the-12-best-weekly.html <blockquote> <p>原文:<a href="https://www.h5jun.com/post/the-12-best-weekly.html">https://www.h5jun.com/post/the-12-best-weekly.html</a></p> </blockquote> <h1 id="toc-8a2"><a class="anchor" href="#toc-8a2"></a>值得订阅的 12 份优质前端期刊</h1> <p>作为前端从业人员,能够及时接收行业最新最前沿技术资讯,是个人成长的一项必备技能。大家都知道前端发展迅速,每周都有新东西出现。那么行业里面究竟有哪些值得关注和订阅的前端资讯类期刊呢?小编将多年整理和收集的 12 份优质期刊分享给大家(排名不分先后)</p> <!--more--> <h2 id="toc-621">英文期刊</h2><div class="toc"><ul> <li><a href="#toc-8a2">值得订阅的 12 份优质前端期刊</a><ul> <li><a href="#toc-621">英文期刊</a><ul> <li><a href="#frontendfocus">Frontend Focus</a></li> <li><a href="#javascriptweekly">JavaScript Weekly</a></li> <li><a href="#cssweekly">CSS Weekly</a></li> <li><a href="#nodeweekly">Node Weekly</a></li> <li><a href="#mobilewebweekly">Mobile Web Weekly</a></li> <li><a href="#fullwebweekly">Full Web Weekly</a></li> <li><a href="#webdesignweekly">Web Design Weekly</a></li> </ul> </li> <li><a href="#toc-0fd">中文期刊</a><ul> <li><a href="#toc-b4b">奇舞周刊</a></li> <li><a href="#toc-ec1">FEX 技术周刊</a></li> <li><a href="#toc-a52">众成翻译每周精选</a></li> <li><a href="#toc-e6e">湾区日报</a></li> </ul> </li> <li><a href="#toc-ca5">日文期刊</a><ul> <li><a href="#frontendweekly">Frontend Weekly</a></li> </ul> </li> </ul> </li> </ul> </div> <h3 id="frontendfocus"><a class="anchor" href="#frontendfocus"></a>Frontend Focus</h3> <p><img src="https://p4.ssl.qhimg.com/d/inn/a3896375/frontend-focus.jpg" alt=""></p> <p>老牌前端周刊,以介绍 HTML/CSS 技术的文章为主。目前第 263 期,原来叫做 html5 weekly,现在改为 FrontEnd Focus。有不少适合新手的入门文章,内容难度适中,比较适合于前端新人。一般是每周四更新。</p> <h3 id="javascriptweekly"><a class="anchor" href="#javascriptweekly"></a>JavaScript Weekly</h3> <p><img src="https://p2.ssl.qhimg.com/d/inn/a3896375/javascript-weekly.jpg" alt=""></p> <p>老牌 JavaScript 期刊,目前已经第 307 期。最近介绍 Node.js 和 ES6 的内容比较多。技术难度适中,比较适合于新人以及有一定经验的 JavaScript 工程师。除了资讯和技术文章它也会推荐一些适合新人入门的教程和视频(视频在某网站,要翻墙,你懂的),一般是每周五更新。</p> <h3 id="cssweekly"><a class="anchor" href="#cssweekly"></a>CSS Weekly</h3> <p><img src="https://p1.ssl.qhimg.com/d/inn/a3896375/css-weekly.jpg" alt=""></p> <p>老牌 CSS 期刊,目前已经 236 期。主要介绍 CSS 基础、动画、响应式设计、CSS 预处理等。也介绍一些不错的工具和开源项目。比较适合于前端新人。一般是每周三更新。</p> <h3 id="nodeweekly"><a class="anchor" href="#nodeweekly"></a>Node Weekly</h3> <p><img src="https://p5.ssl.qhimg.com/d/inn/a3896375/node-weekly.jpg" alt=""></p> <p>Node.js 开始流行的时候出现的周刊,目前到第 161 期,内容是以介绍 Node 和 ES6 为主。它会定期介绍和推荐一些新的开源库、框架和工具,其中不乏有优秀的值得关注的项目。一般是每周五更新。</p> <h3 id="mobilewebweekly"><a class="anchor" href="#mobilewebweekly"></a>Mobile Web Weekly</h3> <p><img src="https://p5.ssl.qhimg.com/d/inn/a3896375/mobileweb-weekly.jpg" alt=""></p> <p>从周刊名字就可以知道,这个周刊主要是介绍移动端的 web 技术。它目前更新到第 131 期。介绍响应式设计、web 游戏开发、移动端交互设计以及各种移动 web 开发相关的库、工具和技术。部分内容比较深入,适合主要工作以移动开发为主的前端工程师订阅。一般是每周三更新。</p> <h3 id="fullwebweekly"><a class="anchor" href="#fullwebweekly"></a>Full Web Weekly</h3> <p><img src="https://p0.ssl.qhimg.com/d/inn/a3896375/fullweb-weekly.jpg" alt=""></p> <p>以 web 全栈技术为主的期刊,内容涉及面较广,不仅仅有 JavaScript,还有 Python、PHP 等服务端技术,比较适合有一定经验的前端工程师,尤其是希望跨界的高级前端工程师扩展学习。一般是每周三更新。</p> <h3 id="webdesignweekly"><a class="anchor" href="#webdesignweekly"></a>Web Design Weekly</h3> <p><img src="https://p1.ssl.qhimg.com/d/inn/a3896375/web-design-weekly.jpg" alt=""></p> <p>名字听起来像是介绍 UI 设计的周刊,但是其实介绍前端开发的内容更多。不愧是 web design,周刊界面比较好看而且是响应式的。一般是每周三更新。</p> <h2 id="toc-0fd">中文期刊</h2> <h3 id="toc-b4b"><a class="anchor" href="#toc-b4b"></a>奇舞周刊</h3> <p><img src="https://p2.ssl.qhimg.com/d/inn/a3896375/75-weekly.jpg" alt=""></p> <p>老牌的国内前端期刊了,由 360 前端,奇舞团小伙伴们每周整理。目前已经到第 182 期。内容包括行业内最新的中文前端技术原创和翻译文章以及最新资讯。内容全面,其中也不乏好的入门教程,比较适合新人。除了邮件订阅之外,有微信公众号,可以在微信里订阅。一般是每周五更新。</p> <h3 id="toc-ec1"><a class="anchor" href="#toc-ec1"></a>FEX 技术周刊</h3> <p><img src="https://p3.ssl.qhimg.com/d/inn/a3896375/fex-weekly.jpg" alt=""></p> <p>由百度 FEX 整理的技术周刊,内容很全,从行业会议到各类优秀中英文精品文章,还有最新资讯和互联网产品和其他相关内容。是小编目前所知道的国内唯一同时有中英文文章推荐的周刊,每期内容非常非常多,比前面各个周刊都全。有微信公众号,可以在微信里订阅。一般是每周一更新。</p> <h3 id="toc-a52"><a class="anchor" href="#toc-a52"></a>众成翻译每周精选</h3> <p><img src="https://p0.ssl.qhimg.com/d/inn/a3896375/zcfy-weekly.jpg" alt=""></p> <p>由众成翻译提供的每周译文精选。众成翻译是国内最大的前端技术文章翻译平台。上面介绍的几个英文期刊的文章中很大一部分都会及时在众成翻译上翻译成中文。因此如果英文阅读比较吃力的同学,可以直接阅读众成翻译每周精选上的中文译文版。这也是国内唯一的推荐专业译文的前端周刊。一般是每周日更新。</p> <h3 id="toc-e6e"><a class="anchor" href="#toc-e6e"></a>湾区日报</h3> <p><img src="https://p1.ssl.qhimg.com/d/inn/a3896375/wanqu-daily.jpg" alt=""></p> <p>这是台湾地区的技术日报,它严格来说不属于前端范畴,但它每天推荐高质量的互联网创业与技术资讯,非常适合前端和其他互联网从业人员阅读。可以通过邮箱订阅,一般是每日更新。</p> <h2 id="toc-ca5">日文期刊</h2> <h3 id="frontendweekly"><a class="anchor" href="#frontendweekly"></a>Frontend Weekly</h3> <p><img src="https://p5.ssl.qhimg.com/d/inn/a3896375/frontend-weekly.jpg" alt=""></p> <p>这是日本前端从业者整理的周刊,推荐的文章主要还是英文的,文章跨域度比较大,几乎包含所有前端相关的领域,其中不乏有优质文章。不过内容上和前面几个英文周刊有一定的重复。一般是每周三更新。</p> <p>以上就是小编常阅读的技术期刊,如果大家有自己订阅的其他不错的期刊,欢迎在评论区讨论~</p> Tue, 01 Nov 2016 03:36:51 GMT http://www.75team.com/post/the-12-best-weekly.html 【Service Worker】消息推送功能“全军覆没” http://www.75team.com/post/push-knock-the-door.html <blockquote> <p>原文:<a href="http://www.zhengqingxin.com/post/push-knock-the-door.html">http://www.zhengqingxin.com/post/push-knock-the-door.html</a></p> <p>最近项目节奏放缓,我们的项目属于内部系统,使用者大部分为紧跟科技脚步的“潮人”,chrome 在我们系统中占据了半壁江山(谦虚的说),所以打算在项目中试试 Service Worker ,打算先从推送功能入手,然而我却被啪啪打脸,于是我怒写了这篇文章。</p> </blockquote> <!--more--> <h2 id="toc-9be">兴高采烈的开始</h2><div class="toc"><ul> <li><a href="#toc-9be">兴高采烈的开始</a><ul> <li><a href="#toc-f22">切 https</a></li> <li><a href="#toc-60a">配置 FCM/GCM</a></li> <li><a href="#toc-e6a">码前端代码</a></li> <li><a href="#toc-5d6">推送任务</a></li> </ul> </li> <li><a href="#toc-14e">现实很骨感</a></li> <li><a href="#toc-db6">换一种奇葩方式实现功能</a></li> </ul> </div> <h3 id="toc-f22"><a class="anchor" href="#toc-f22"></a>切 https</h3> <p>首先把前端和后台接口切换成https,由于之前不是很熟悉,这个还是费了我一些时间的,这里我生成了两套 https 证书,并配置好了 nginx,由于自己生成的证书是不被信任的,所以我们开发时经常需要手动点击信任。</p> <h3 id="toc-60a"><a class="anchor" href="#toc-60a"></a>配置 FCM/GCM</h3> <p>基本点几个按钮就生成了服务,然后把<code>gcm_sender_id</code>和 key 配置到项目里,具体参考<a href="https://developers.google.com/web/updates/2015/03/push-notifications-on-the-open-web#make_a_project_on_the_firebase_developer_console"> 这里 </a></p> <h3 id="toc-e6a"><a class="anchor" href="#toc-e6a"></a>码前端代码</h3> <p>环境准备好后,我迫不及待的开始写码了,其实前端代码比较简单,大致代码如下:</p> <p>index.html</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">if</span> (<span class="hljs-string">"serviceWorker"</span> in navigator) { <span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.register</span>(<span class="hljs-string">"/sw.js"</span>)<span class="hljs-selector-class">.catch</span>(function (error) { console<span class="hljs-selector-class">.log</span>(<span class="hljs-string">"Service Worker Error :^("</span>, error); }); <span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.ready</span><span class="hljs-selector-class">.then</span>(function (reg) { <span class="hljs-selector-tag">reg</span><span class="hljs-selector-class">.pushManager</span><span class="hljs-selector-class">.getSubscription</span>()<span class="hljs-selector-class">.then</span>(res=&gt; { <span class="hljs-selector-tag">if</span> (!res) { subscribe(reg); } else { console<span class="hljs-selector-class">.log</span>(<span class="hljs-string">"remain endpoint:"</span>, res.endpoint); } }) }); <span class="hljs-selector-tag">function</span> <span class="hljs-selector-tag">subscribe</span>(reg) { <span class="hljs-selector-tag">reg</span><span class="hljs-selector-class">.pushManager</span><span class="hljs-selector-class">.subscribe</span>({<span class="hljs-attribute">userVisibleOnly</span>: true})<span class="hljs-selector-class">.then</span>(function (pushSubscription) { sub = pushSubscription; console<span class="hljs-selector-class">.log</span>(<span class="hljs-string">"Subscribed! Endpoint:"</span>, sub.endpoint); }); } } </code></pre><p>sw.js</p> <pre><code class="hljs lang-php"><span class="hljs-keyword">self</span>.addEventListener(<span class="hljs-string">"push"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(event)</span> </span>{ event.waitUntil( <span class="hljs-keyword">self</span>.<span class="hljs-keyword">self</span>.registration.showNotification(<span class="hljs-string">"发布新文章啦"</span>, { body: <span class="hljs-string">"有新文章发布啦,点击查看。"</span> }) ); }); <span class="hljs-keyword">self</span>.addEventListener(<span class="hljs-string">"notificationclick"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(event)</span> </span>{ event.notification.close(); <span class="hljs-keyword">var</span> url = <span class="hljs-string">"https://www.zhengqingxin.com"</span>; event.waitUntil( clients.matchAll({ type: <span class="hljs-string">"window"</span> }) .then(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">if</span> (clients.openWindow) { <span class="hljs-keyword">return</span> clients.openWindow(url); } }) ); }); </code></pre><h3 id="toc-5d6"><a class="anchor" href="#toc-5d6"></a>推送任务</h3> <p>其实如果只想看一下推送的效果 ,上面的代码就够了,用上面获得到的 endpoint,执行</p> <pre><code class="hljs lang-stylus">curl --<span class="hljs-selector-tag">header</span> <span class="hljs-string">"Authorization: key={your key}"</span> --<span class="hljs-selector-tag">header</span> <span class="hljs-string">"Content-Type: application/json"</span> https:<span class="hljs-comment">//android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"{your endpoint}\"]}"</span> </code></pre><p>然而实际情况我们需要知道消息推送给谁,推送什么内容,这会稍有些麻烦了,我们需要有自己的后台接口,有自己的数据库。于是我基于<a href="https://thinkjs.org/"> ThinkJs </a>+ mysql 搭了一个简单的推送后台。大家可以自行选择技术,这里后端代码我就不贴了,数据库表非常简单,类似于下面:</p> <p>user表:</p> <table> <thead> <tr> <th>userId</th> <th style="text-align:center">token</th> <th style="text-align:right">ctime</th> </tr> </thead> <tbody> <tr> <td></td> </tr> </tbody> </table> <p>notification表:</p> <table> <thead> <tr> <th>id</th> <th style="text-align:center">userId</th> <th style="text-align:right">pushTime</th> <th style="text-align:right">result</th> <th style="text-align:right">title</th> <th style="text-align:right">content</th> <th style="text-align:right">url</th> </tr> </thead> <tbody> <tr> <td></td> </tr> </tbody> </table> <p>两个表基本可以满足我们需求了,然后我们需要 3 个接口:</p> <ul> <li>注册接口:接收参数 userId 和 token,userId 为用户在本系统下的唯一标示,token 为用户在 GCM 为我们分配的 endpoint。</li> <li>获取推送内容接口:根据 userId 来获取需要推送内容(对应 notification 表)</li> <li>推送:接收参数userId,用于给 FCM/GCM 发送消息,进行推送。</li> </ul> <p>要实现可定制的推送内容,这里 sw.js 的代码我们还需要修改一下:</p> <pre><code class="hljs lang-monkey"><span class="hljs-built_in">self</span>.addEventListener(<span class="hljs-string">"push"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (</span>event) { event.waitUntil( fetch(SERVER_BASE_URL + <span class="hljs-string">"/push/get"</span>).<span class="hljs-keyword">then</span>((res)=&gt; { <span class="hljs-keyword">if</span> (res.status !== <span class="hljs-number">200</span>) { console.<span class="hljs-built_in">log</span>(<span class="hljs-string">"Looks like there was a problem. Status Code: "</span> + response.status); <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(); } <span class="hljs-keyword">return</span> res.json() }).<span class="hljs-keyword">then</span>(res=&gt; { <span class="hljs-built_in">self</span>.<span class="hljs-built_in">self</span>.registration.showNotification(res.title, { body: res.content }) })); }); </code></pre><p>以上就基本完成了整个推送功能了,大致流程为:</p> <ul> <li><p>注册流程</p> <p> 用户访问页面 --&gt; 注册sw --&gt; sw准备就绪 --&gt; 订阅推送服务 --&gt; 获得endpoint --&gt; 获取userId(这个可选) --&gt; 调用注册接口 --&gt; 完成注册.</p> </li> <li><p>推送流程</p> <p> 某时某刻触发了推送(比如某系统用户端申请了一个工单,指定审核人,调用了推送接口) --&gt; 推送 --&gt; sw 接收到消息,触发“push”事件 --&gt; sw 在调用获取推送内容接口获取推送内容 --&gt; 接口返回后展示推送</p> </li> </ul> <p>于是乎我本地测试,各种折腾,关闭页面,多个用户...边测试边笑,想着这功能上线有多牛逼,很多用户肯定没见过,有种步入人生巅峰的既视感,上个效果图吧!</p> <p><img src="https://www.zhengqingxin.com/static/upload/201610/NKtQKxPWuTfjAXST6gUa8ZqW.png" alt="alt"></p> <h2 id="toc-14e">现实很骨感</h2> <p>以我的个性,搞了这么潮的功能,一定要先显摆一番。于是怀揣着满满的自信,发个链接给朋友看,结果发现注册流程只成功了三步,无论怎么折腾,后台都拿不到 endpoint,由于朋友使用的是 windows 下最新的 chrome,我打开 windows,尝试若干次,依旧不行,我断定是 windows 版 chrome 的问题,尝试了 chrome 下各种配置,依旧未果,于是怒开一罐可乐,仰天长啸:这可咋办?</p> <p>沉思数秒,感觉不对,windows 这么牛逼,chrome 团队怎么也不会让它比 mac 版 low 吧?于是感觉还是自己的问题,突然灵机一动,想起了之前收藏的 chrome 有监控 gcm 的功能(还是自家功能用着方便),于是迫不及待的打开<code>chrome://gcm-internals/</code>,点击<code>Start Recording</code>,发现 windows 下检测结果是这个样子的: <img src="https://www.zhengqingxin.com/static/upload/201610/3X7j8wCX4ZiShoiD0J6R_ByI.jpg" alt="alt"></p> <p>然后我又在 mac 上试了一下,结果是:</p> <p><img src="https://www.zhengqingxin.com/static/upload/201610/gK7Qsj7FtSTvOZQgxymXlYib.png" alt="alt"></p> <p>为什么 windows 下 gcm 监控下只有请求,没有响应呢?</p> <p>我又直接分别在控制台运行了一次订阅推送的代码:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">navigator</span><span class="hljs-selector-class">.serviceWorker</span><span class="hljs-selector-class">.ready</span><span class="hljs-selector-class">.then</span>(reg=&gt;{<span class="hljs-selector-tag">reg</span><span class="hljs-selector-class">.pushManager</span><span class="hljs-selector-class">.subscribe</span>({<span class="hljs-attribute">userVisibleOnly</span>: true})<span class="hljs-selector-class">.then</span>(p=&gt;{console<span class="hljs-selector-class">.log</span>(p)})}) </code></pre><p>果然结果如我所料,mac 上打印出了订阅结果的结果,包括 endpoint ,windows 机上 Promise 一直是 pending 状态。这是为什么呢?这时我已经排除了是 mac 和 windows 的差异,两个机器唯一差别就是 mac 翻墙了。于是我把windows 上配上代理,确认可以翻墙成功后,再次刷新了一次页面,果然成功了。这时我一点也不开心了,给我带来的是一脸懵逼,我不想相信这个现实,于是在网上查了又查,但是由于 Service Worker 在实验阶段,国外又不存在威力如此强大的 “Great Wall”,所以资料也比较少,搜变了 stackoverflow 也没找到合适的答案,于是我回归到了 MDN,终于找到了这样的一句话(<a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API">查看原文</a>):</p> <blockquote> <p>The service worker also has to subscribe to the push messaging service. Each session is given its own unique endpoint when it subscribes to the push messaging service. This endpoint is obtained from the (PushSubscription.endpoint) property on the subscription object. This endpoint can be sent to your server and used to send a message to that session&#39;s active service worker. Each browser has its own push messaging server to handle sending the push message.</p> </blockquote> <p>每个浏览器有它们自己的服务器用来处理消息推送。我的表情是这样的。</p> <p><img src="https://www.zhengqingxin.com/static/upload/201610/RKOeSw_g8YH593JogwsmLyNg.jpeg" alt="alt"></p> <p>这代表了什么?代表着用户只有翻墙,才能收到推送,代表着 Service Worker 所有的推送功能都被挡在外面了!</p> <p>我还是不死心,去找到同事咨询,这里还要特别感谢<a href="http://weibo.com/itchina100"> 裕波 </a>联系到了 google 的人,特意咨询了这个事情,确认了这个悲痛的事实并留下了这么一段话:</p> <blockquote> <p>suggest that you contact the chrome developers. This isn&#39;t an issue with the specification, which allows browsers to make their own choices about push service. Google&#39;s choice to use Google&#39;s proprietary service is something that I suspect will not change. Of course, chromium is open source, so I guess anything is possible if you are sufficiently motivated.</p> <p>Good to hear that someone is implementing the protocol though. I hope that some day there will be an option to use a personal push server in Firefox, but we too have our own proprietary protocol right now.</p> </blockquote> <h2 id="toc-db6">换一种奇葩方式实现功能</h2> <p>虽然我满怀愤怒,但却不得不接受这个现实。于是我只能换一种奇葩的方式去实现,代码如下:</p> <p>index.html:</p> <pre><code class="hljs lang-lua">navigator.serviceWorker.ready.<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(swRegistration)</span></span> { <span class="hljs-keyword">return</span> swRegistration.sync.register(<span class="hljs-string">"workOrderSync"</span>); }); </code></pre><p>sw.js:</p> <pre><code class="hljs lang-less"><span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.addEventListener</span>(<span class="hljs-string">"sync"</span>, function (event) { <span class="hljs-selector-tag">if</span> (event.tag == <span class="hljs-string">"workOrderSync"</span>) { workOrderSync(); } }); <span class="hljs-selector-tag">function</span> <span class="hljs-selector-tag">workOrderSync</span>() { <span class="hljs-selector-tag">setInterval</span>(function () { <span class="hljs-selector-tag">fetch</span>(<span class="hljs-string">"https://www.zhengqingxin.com:8000/push/get"</span>)<span class="hljs-selector-class">.then</span>(res=&gt; { <span class="hljs-selector-tag">return</span> <span class="hljs-selector-tag">res</span><span class="hljs-selector-class">.json</span>() })<span class="hljs-selector-class">.then</span>(res=&gt; { console<span class="hljs-selector-class">.log</span>(res); })<span class="hljs-selector-class">.then</span>(()=&gt; { console<span class="hljs-selector-class">.log</span>(self.self.registration.showNotification); <span class="hljs-selector-tag">self</span><span class="hljs-selector-class">.self</span><span class="hljs-selector-class">.registration</span><span class="hljs-selector-class">.showNotification</span>(<span class="hljs-string">"温馨提示"</span>, { <span class="hljs-attribute">body</span>: <span class="hljs-string">"您有新的工单需要处理"</span>, <span class="hljs-attribute">icon</span>: <span class="hljs-string">"/static/img/logo.png"</span> }) })<span class="hljs-selector-class">.catch</span>(err=&gt; { console<span class="hljs-selector-class">.log</span>(err); }) }, <span class="hljs-selector-tag">20000</span>) } </code></pre><p>简单来说,就是把“推”变成了“拉”,浏览器不断进化新技术,无论是 Websocket 还是 Service Worker 都希望把“拉”变为“推”,可一道墙却把它打回原形。</p> <p>顺便说一句,这种方式只适用于实现效果,Service Worker 的 Sync API 目前还在婴儿期,目前只有 chrome 支持而且 API 随时都可能改变,并且这种轮询的方式对服务器压力也会比较大,所以不建议在实际中使用。</p> Mon, 24 Oct 2016 16:27:08 GMT http://www.75team.com/post/push-knock-the-door.html 【译】前端是 ? 而 JavaScript 是 ? http://www.75team.com/post/front-end-is-and-javascript-is.html <blockquote> <p>原文:<a href="http://www.zcfy.cc/article/1446">http://www.zcfy.cc/article/1446</a></p> </blockquote> <p>前端将要统治世界,让 JavaScript 君临天下,而码农们则在争论着我们的技术究竟变得有多复杂。</p> <p>现代前端技术饱受争议,但是我们只顾争吵,似乎忘记了我们所得到的回报。我们,web 王国的劳动人民、web 设计师、前 Wordpress 工程师和吟唱代码的诗人,将要统治数字世界,能够建造各种神迹。谁会想到这一切都是我们的老朋友 JavaScript 所带来的。这个当年的 ? 语言只能用来做些许有趣的 DOM 操作。谁又想到它竟然有这样的超能力?</p> <!--more--> <p>我听说了一些观点比如<strong>“现代前端被过度工程化了”</strong>,<strong>“别用大炮打蚊子”</strong>以及<strong>“webpack有必要搞那么复杂吗?”</strong>?但我不赞同。我认为最近前端工具和库的发展简直是非凡的成就。web 的未来将由一个充满狂热者和理想主义者的社区创建,他们创造伟大的工具给所有 web 开发者免费使用。这其中许多人的背后的支持者是一些大公司,它们默默地做了许多繁重的工作,投资开源项目和工具。除了前端,还有哪个行业如此开放?</p> <h4 id="toc-497"><a class="anchor" href="#toc-497"></a>追求卓越天然不易</h4> <p>所以我们在这里开发应用,这些应用曾经得完全依靠 C#、Java 或者 .NET 来实现,用很慢的服务器渲染界面,或者更糟,开发灰暗而令人压抑的桌面应用 ?。在那段岁月中,如果某些 UX 设计师被雇佣,只要能够改变应用中文本的颜色,他们就感到谢天谢地。这段苦日子已经过去了,如今我们可以创造我们最想要的 ❤️。但是,我们也得付出努力。</p> <p>谁说前端是容易的?创建一个应用让它<strong>超级快</strong>,<strong>使用起来有趣</strong>以及<strong>可维护</strong>可不是一件琐碎的事。实际上这是一门艺术。不过幸运地是,伟大的新工具每周都会涌现。它们都是来拯救我们并赋予我们创造这些神奇应用更强大的能力的?。</p> <h4 id="toc-288"><a class="anchor" href="#toc-288"></a>拥抱学习</h4> <p>拥抱现代前端技术,我们要让学习成为我们工作的一部分。我不是说你需要学习你从前端资讯中了解到的一切新东西,但是,如果你兴趣十足并且心态开放,多学学是一个好的开始。</p> <p>在众多批评现代前端的声音中我感受到了一些阻力。一些反对学习和理解新知识的阻力。这种态度会让你退缩,让你跟不上时代。前端已经驶上了发展的快车道?,它不会停下来等待那些质疑者。跳上快车可能令人内心恐惧,但最好不要犹豫。前方风景优美,别让恐惧阻止你前进的脚步。</p> <p>说到这,我们也需要承认我们不是全能的专家。性能优化、常规部署、安全性和应用架构,这些不需要也不可能全部精通。当我们往前深入下去,我们将选择更专业的方向。这也是为什么几年前很火的“全栈工程师”逐渐淡出人们的视野。</p> <h4 id="toc-440"><a class="anchor" href="#toc-440"></a>为什么学习很有必要</h4> <p>我们用户对使用诸如 Uber 和 AirBnb 一类的在线服务的期望与日俱增。所以只满足于“能做到”已经远远不够了,我们需要创造用户真正喜欢的在线服务。要实现这个目标我们需要知识,以及许多工具,让你不需要一切都从头开始做。我们需要框架、库、模块,打包工具,辅助工具,包管理工具等等。要记得,一切工具的目的都是为了解决一个特定的问题。必须要确定哪个问题是痛点,以及你是否确实需要解决它?。</p> <p>如果你开发的网站比较大,我非常确定引入现代前端工具可以让你的网站和你的生活都变得更美好。它们能让你的网站更健壮、易于维护以及,最后但同样重要的,让你的网站有更漂亮的 UI 和更丰富的交互体验。</p> <h4 id="toc-fde"><a class="anchor" href="#toc-fde"></a>JavaScript 的未来</h4> <p>所以,我们才刚刚开始。JavaScript 赢得了编程语言的战争并将要统治世界?。JavaScript 的简易性和几乎是通用的标准让它变得极为强大。它在客户端和服务器上都能够快速渲染的能力是令人瞩目的。JavaScript 已经打入原生应用开发,我们周围的世界已经被 JavaScript 连接起来,JavaScript 将会出现在各种地方。</p> <p>有了这些能力,我们可以创造奇迹。可以开发无人机来救援被困的人,开发新一代支付解决方案,开发人造机器人以及其他各种不可思议的产品。有一件事情是明确的,现在是成为前端工程师的好时代!?</p> <blockquote> <p>英文原文:<a href="https://medium.com/front-end-hacking/front-end-is-and-javascript-is-d4bc3a8edbb7#.eq0qre6du">https://medium.com/front-end-hacking/front-end-is-and-javascript-is-d4bc3a8edbb7#.eq0qre6du</a></p> </blockquote> Wed, 19 Oct 2016 09:03:48 GMT http://www.75team.com/post/front-end-is-and-javascript-is.html