SegmentFault Front-End Study最新的文章
2019-07-15T09:05:32+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
如何从零开源一个React组件
https://segmentfault.com/a/1190000019760106
2019-07-15T09:05:32+08:00
2019-07-15T09:05:32+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
11
<p>有没有遇到这样一种情况,你花了很大精力在业务项目中写了一个组件,你觉得这个组件很通用,除了当前的业务场景还应该有其他的应用场景,所以你想开源这个组件,但又不知道从何入手。这篇文章就来聊聊如何开源一个前端组件,无论是业务中已有的组件还是新的组件。</p>
<h2>1. 初始化</h2>
<h4>1.1 Git</h4>
<p>可以在 Github 上新建一个项目,这里取名 "component-example",然后使用 <code>git clone</code> 命令克隆到本地进行开发。也可以先在本地建好文件,然后使用 <code>git remote add</code> 命令将本地项目与远程项目进行绑定。</p>
<h4>1.2 NPM</h4>
<p>进入项目根目录,执行 <code>npm init</code> 命令初始化项目,这里没什么注意点,一路默认即可,命令执行结束后会得到一个 "package.json" 文件。</p>
<h4>1.3 React</h4>
<p>在项目根目录新增 "src" 文件夹,进入 "src",新建 "index.js"、"index.css"、"App.js" 和 "App.css" 4 个文件。</p>
<p><strong>"index.js"</strong></p>
<pre><code class="react">import React from 'react';
import ReactDOM from 'react-dom';
import styles from './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
</code></pre>
<p><strong>"APP.js"</strong></p>
<pre><code class="react">import React from 'react';
import styles from './App.css';
export default class APP extends React.Component {
render() {
return <div>Hello, React Component!</div>
}
}</code></pre>
<p>安装 react 和 react-dom:</p>
<pre><code class="bash">npm install --save react react-dom</code></pre>
<h2>2. 构建</h2>
<h4>2.1 Webpack</h4>
<p>这里使用 webpack 作为打包构建工具。执行下面命令安装 webpack:</p>
<pre><code class="bash">npm install --save webpack webpack-cli</code></pre>
<p>新建 "webpack.common.js"、"webpack.dev.js" 和 "webpack.prod.js" 3 个文件,用于配置 webpack。因为开发环境和生产环境的需要不一样,比如开发环境需要 source map 以便快速定位问题,而生产环境需要文件尽量小以减少网络加载时间,所以需要维护两套不同的构建配置。虽然是两套配置,但仍然有很多配置是相同的,所以将相同的配置抽成 "webpack.common.js" 文件,开发环境特有的配置放在 "webpack.dev.js",生产环境特有的配置放在 "webpack.prod.js" 中。</p>
<p><strong>"webpack.common.js"</strong></p>
<pre><code class="js">const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "component-example.js",
libraryTarget: "umd"
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader?modules"
},
{
test: /\.(jpg|png)$/,
loader: "url-loader?limit=25000"
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
plugins: []
};
</code></pre>
<p><strong>"webpack.dev.js"</strong></p>
<pre><code class="js">const merge = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
watch: true
});
</code></pre>
<p><strong>"webpack.prod.js"</strong></p>
<pre><code class="js">const merge = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
externals: ["react", "react-dom"]
});
</code></pre>
<p>上面的配置中用到了 <code>style-loader</code>、<code>css-loader</code>、<code>url-loader</code> 和 <code>webpack-merge</code>,也需要安装一下:</p>
<pre><code class="bash">npm install --save-dev css-loader style-loader url-loader webpack-merge</code></pre>
<p>"webpack.common.js" 中,output 的 path 和 filename 指定构建结果存在 /dist/component-example.js 中;libraryTarget 的值为 <strong>umd</strong>,这条配置必不可少,表明将 component-example.js 文件暴露为所有模块定义下都能运行的方式,即可以被当做 CommonJS 模块、 AMD 模块等不同类型的模块,从而可以在不同环境下运行。</p>
<p>另外,在 "webpack.common.js" 中还引入了 3 个 loader: <code>style-loader</code>、<code>css-loader</code> 和 <code>url-loader</code>,前两者帮助 webpack 预处理样式文件,这里指 CSS 文件;<code>url-loader</code> 用于将本地文件处理成 base64,一般用于引用背景图时,如果没有需要,也可以删除。</p>
<p>"webpack.dev.js" 中有 3 个额外的配置。</p>
<ul>
<li>mode: 代表构建模式,有 <code>development</code> 和 <code>production</code> 两个值可选,不同的值会触发 webpack 不同的 Plugin,比如 <code>development</code> 会触发 NamedChunksPlugin,可以将 chunk id 变成字符串标识符,而 <code>production</code> 则会触发 UglifyJsPlugin,可以 uglify 代码;</li>
<li>devtool:代表是否生成以及如何生成 source map,这里使用了 inline-source-map,source map 会转换为 DataUrl 后添加到 bundle 中,因此 bundle 会变得很大,但毕竟开发环境并且仅仅是个组件项目,大小可控;</li>
<li>watch:可以监听文件变化,文件修改后可以自动重新编译。</li>
</ul>
<p>"webpack.prod.js" 中除了 mode,还有 externals 配置项,表示构建的 bundle 中排除对 react 和 react-dom 的依赖,因为是 React 组件,用到该组件的地方肯定同时也引用了 react 和 react-dom,所以没必要再在组件的 bundle 中引入,这样又可以大大减少构建完的文件大小。</p>
<h4>2.2 Babel</h4>
<p>Babel 可以让我们使用 ES6 写 JS,所以我们也需要为项目添加 Babel 配置。</p>
<p>新建 ".babelrc" 文件,输入配置:</p>
<pre><code class="json">{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
</code></pre>
<p>"webpack.common.js" 中添加 babel 相关的 配置:</p>
<pre><code class="json">rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]</code></pre>
<p>需要安装:</p>
<pre><code class="bash">npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react</code></pre>
<h4>2.3 package.json</h4>
<p>在 "package.json" 文件中根据 webpack 的 output 配置指定项目的入口文件,另外添加两条命令,分别是开发时启动命令和生产构建命令。</p>
<pre><code class="json">"main": "./dist/component-example.js",
"scripts": {
"start": "webpack --config webpack.dev.js --progress --colors",
"build": "webpack --config webpack.prod.js",
}</code></pre>
<h2>3. 调试</h2>
<h4>3.1 npm link</h4>
<p>既然是组件,那便需要放入到一个完整的项目中去调试,这里使用 <code>npm link</code>。</p>
<p>首先需要进入刚刚的组件根目录,执行 <code>pwd</code>,获取组件的绝对路径,然后进入一个完整的目标项目的根目录,执行 <code>npm link $path</code> ,"$path" 指的是刚刚执行 <code>pwd</code> 后获取到的路径 ,这样在目标项目的 "node_modules" 中创建了一个指向组件的软链接,相当于在目标文件中执行了 <code>npm install --save component-example</code>,只不过任何在 "component-example" 中的修改都会立即反馈到目标项目中。</p>
<p><code>npm link</code> 还有一种方式,即在 "component-example" 根目录直接执行 <code>npm link</code>,那么会在全局的 <code>node_modules</code> 中创建名为 <code>component-example</code> 的 NPM 包,然后在目标项目中执行 <code>npm link component-example</code> 以引用组件文件。但这种方式会污染全局,一般不建议这么做。</p>
<p>引入组件后,可以像正常 NPM 包一样 import 和使用:</p>
<pre><code class="react">import ComponentExample from 'component-example';
export default class FullProject extends React.Component {
render() {
return <ComponentExample />;
}
} </code></pre>
<p>在组件项目中启动 <code>npm start</code>,任何更改可以自动构建,变化也会随时反馈到目标项目中。</p>
<p>开发结束后,记得在目标项目中解除 link:<code>npm unlink component-example</code>。</p>
<h4>3.2 CSS Modules</h4>
<p>既然是前端组件,那 CSS 必不可少,如何引入 CSS 文件呢?这里使用 CSS Modules。任何一个 CSS 样式规则都是全局的,CSS Modules 的思路就是产生一个唯一的哈希字符串表示当前的规则,从而避免全局污染的情况。</p>
<p>那怎么使用呢?webpack 的 <code>css-loader</code> 便可以支持 CSS Modules,并且配置和使用起来都很方便。如果留意的话,你可能会发现 "webpack.common.js" 中引入 <code>css-loader</code> 时在后面添加了个参数: <code>modules</code>,这样便可以打开 CSS Modules 功能。</p>
<p>"App.css"</p>
<pre><code class="css">.title {
color: red;
}</code></pre>
<p>"App.js"</p>
<pre><code class="react">import React from 'react';
import styles from './App.css';
export default class APP extends React.Component {
render() {
return <div className={styles.title}>Hello, React Component!</div>
}
}</code></pre>
<p>JS 文件引入 CSS 模块,并命名为 styles, JSX 通过 <code>className={styles.title}</code> 声明类名。</p>
<h2>4. 发布</h2>
<p>经过一番编码调试,组件终于可以发布了,使用 <code>npm run build</code> 命令对项目进行构建,如果打开 "./dist/component-example.js" 的话,你会发现代码与开发时有很大差别。再执行 <code>npm publish</code> 将组件发布到 NPM 上。</p>
<p>使用 <code>npm publish</code> 时有几个注意点,首先你需要注册 NPM 账号,没有的话需要执行 <code>npm adduser</code>,注册完毕后登录 <code>npm login</code>,执行 <code>npm who am i</code> 验证是否已经成功登录,然后就可以愉快地 <code>npm publish</code> 了。另外一个注意点是每次发布时需要更新 "package.json" 中的 version,执行 <code>npm version *.*.*</code> 指定当前发布的版本,一定要大于上一次。</p>
<p>上面讲了如何从零开源一个 React 组件,只是从技术角度出发,开源过程中还会涉及其他的方方面面,这里不再详述。</p>
HTTP/3 都来了,你却还在用 HTTP/1.1?
https://segmentfault.com/a/1190000018444930
2019-03-09T17:04:25+08:00
2019-03-09T17:04:25+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
3
<p><strong>Tip:</strong></p>
<ol>
<li><strong>平台会对图片进行压缩,可在微信公众号 FEPulse(ID:FEPulse)后台回复 HTTP3 获取高清大图;</strong></li>
<li><strong>腾讯暑期实习招聘全面启动,可公众号后台联系我帮助内推,有意向团队比如 AlloyTeam 等亦可推荐。</strong></li>
</ol>
<p>2015 年 HTTP/2 标准发表后,大多数主流浏览器也于当年年底支持该标准。此后,凭借着多路复用、头部压缩、服务器推送等优势,HTTP/2 得到了越来越多知名互联网公司的青睐。就在大家刚刚为了解了 HTTP/2 新特性而舒口气儿的时候,HTTP/3 却又紧锣密鼓地准备着了。今天就跟大家聊一聊这第三代 HTTP 技术。</p>
<h2>1. HTTP 历史</h2>
<p>在介绍 HTTP 之前,我们先简单看下 HTTP 的历史,了解下 HTTP/3 出现的背景。</p>
<p><img src="/img/bVbpyv9?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>随着网络技术的发展,1999 年设计的 HTTP/1.1 已经不能满足需求,所以 Google 在 2009 年设计了基于 TCP 的 SPDY,后来 SPDY 的开发组推动 SPDY 成为正式标准,不过最终没能通过。不过 SPDY 的开发组全程参与了 HTTP/2 的制定过程,参考了 SPDY 的很多设计,所以我们一般认为 SPDY 就是 HTTP/2 的前身。无论 SPDY 还是 HTTP/2,都是基于 TCP 的,TCP 与 UDP 相比效率上存在天然的劣势,所以 2013 年 Google 开发了基于 UDP 的名为 QUIC 的传输层协议,QUIC 全称 Quick UDP Internet Connections,希望它能替代 TCP,使得网页传输更加高效。后经<a href="https://link.segmentfault.com/?enc=VTWj8ePhxPGNZ1vOCowQ4A%3D%3D.yru1i8RuDVpQPgsfIkUkOwJ0YGCRS7UzKK7B%2BS0l54kMsRmXdVSCbwbmKL9%2B4lN4ulW%2B2ETi2aJZXJ2RaXFxC68V4WfcpocvM5so1nULJfo%3D" rel="nofollow">提议</a>,互联网工程任务组正式将基于 QUIC 协议的 HTTP (HTTP over QUIC)重命名为 HTTP/3。</p>
<h2>2. QUIC</h2>
<h3>2.1 QUIC 协议概览</h3>
<p>TCP 一直是传输层中举足轻重的协议,而 UDP 则默默无闻,在面试中问到 TCP 和 UDP 的区别时,有关 UDP 的回答常常寥寥几语,长期以来 UDP 给人的印象就是一个很快但不可靠的传输层协议。但有时候从另一个角度看,缺点可能也是优点。QUIC(Quick UDP Internet Connections,快速 UDP 网络连接) 基于 UDP,正是看中了 UDP 的速度与效率。同时 QUIC 也整合了 TCP、TLS 和 HTTP/2 的优点,并加以优化。用一张图可以清晰地表示他们之间的关系。</p>
<p><img src="/img/bVbpywb?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>那 QUIC 和 HTTP/3 什么关系呢?QUIC 是用来替代 TCP、SSL/TLS 的传输层协议,在传输层之上还有应用层,我们熟知的应用层协议有 HTTP、FTP、IMAP 等,这些协议理论上都可以运行在 QUIC 之上,其中运行在 QUIC 之上的 HTTP 协议被称为 HTTP/3,这就是”HTTP over QUIC 即 HTTP/3“的含义。</p>
<p>那么想了解 HTTP/3,QUIC 是绕不过去的,下面主要通过几个重要的特性让大家对 QUIC 有更深的理解。</p>
<h3>2.2 零 RTT 建立连接</h3>
<p>用一张图可以形象地看出 HTTP/2 和 HTTP/3 建立连接的差别,如图2-2 和图2-3 所示。</p>
<p><img src="/img/bVbpywc?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>HTTP/2 的连接需要 3 RTT,如果考虑会话复用,即把第一次握手算出来的对称密钥缓存起来,那么也需要 2 RTT,更进一步的,如果 TLS 升级到 1.3,那么 HTTP/2 连接需要 2 RTT,考虑会话复用则需要 1 RTT。有人会说 HTTP/2 不一定需要 HTTPS,握手过程还可以简化。这没毛病,HTTP/2 的标准的确不需要基于 HTTPS,但实际上所有浏览器的实现都要求 HTTP/2 必须基于 HTTPS,所以 HTTP/2 的加密连接必不可少。而 HTTP/3 首次连接只需要 1 RTT,后面的连接更是只需 0 RTT,意味着客户端发给服务端的第一个包就带有请求数据,这一点 HTTP/2 难以望其项背。那这背后是什么原理呢?结合图2-3,我们具体看下 QUIC 的连接过程。</p>
<p><strong>Step1</strong>:首次连接时,客户端发送 Inchoate Client Hello 给服务端,用于请求连接;</p>
<p><strong>Step2</strong>:服务端生成 g、p、a,根据 g、p 和 a 算出 A,然后将 g、p、A 放到 Server Config 中再发送 Rejection 消息给客户端;</p>
<p><strong>Step3</strong>:客户端接收到 g、p、A 后,自己再生成 b,根据 g、p、b 算出 B,根据 A、p、b 算出初始密钥 K。B 和 K 算好后,客户端会用 K 加密 HTTP 数据,连同 B 一起发送给服务端;</p>
<p><strong>Step4</strong>:服务端接收到 B 后,根据 a、p、B 生成与客户端同样的密钥,再用这密钥解密收到的 HTTP 数据。为了进一步的安全(前向安全性),服务端会更新自己的随机数 a 和公钥,再生成新的密钥 S,然后把公钥通过 Server Hello 发送给客户端。连同 Server Hello 消息,还有 HTTP 返回数据;</p>
<p><strong>Step5</strong>:客户端收到 Server Hello 后,生成与服务端一致的新密钥 S,后面的传输都使用 S 加密。</p>
<p>这样,QUIC 从请求连接到正式接发 HTTP 数据一共花了 1 RTT,这 1 个 RTT 主要是为了获取 Server Config,后面的连接如果客户端缓存了 Server Config,那么就可以直接发送 HTTP 数据,实现 0 RTT 建立连接。</p>
<p>QUIC 实现 0 RTT 的一个技术细节是使用了 DH密钥交换算法。结合图2-4 可以更好地理解上面的过程。</p>
<p><img src="/img/bVbpywd?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>DH 算法的核心就是服务端生成 a、g、p 3 个随机数,a 自己持有,g 和 p 要传输给客户端,而客户端会生成 b 这 1 个随机数,通过 DH 算法客户端和服务端可以算出同样的密钥。在这过程中 a 和 b 并不参与网络传输,安全性大大提高。因为 p 和 g 是大数,所以即使在网络中传输的 p、g、A、B 都被劫持,那么靠现在的计算机算力也没法破解密钥。</p>
<h3>2.3 连接迁移</h3>
<p>TCP 连接基于四元组(源 IP、源端口、目的 IP、目的端口),切换网络时至少会有一个因素发生变化,导致连接发生变化。当连接发生变化时,如果还使用原来的 TCP 连接,则会导致连接失败,就得等原来的连接超时后重新建立连接,所以我们有时候发现切换到一个新网络时,即使新网络状况良好,但内容还是需要加载很久。如果实现得好,当检测到网络变化时立刻建立新的 TCP 连接,即使这样,建立新的连接还是需要几百毫秒的时间。</p>
<p>QUIC 的连接不受四元组的影响,当这四个元素发生变化时,原连接依然维持。那这是怎么做到的呢?道理很简单,QUIC 连接不以四元组作为标识,而是使用一个 64 位的随机数,这个随机数被称为 Connection ID,即使 IP 或者端口发生变化,只要 Connection ID 没有变化,那么连接依然可以维持。</p>
<h3>2.4 队头阻塞/多路复用</h3>
<p>HTTP/1.1 和 HTTP/2 都存在队头阻塞问题(Head of line blocking),那什么是队头阻塞呢?</p>
<p>TCP 是个面向连接的协议,即发送请求后需要收到 ACK 消息,以确认对方已接收到数据。如果每次请求都要在收到上次请求的 ACK 消息后再请求,那么效率无疑很低,如图2-5 所示。后 HTTP/1.1 提出了 Pipelining 技术,允许一个 TCP 连接同时发送多个请求,这样就大大提升了传输效率,如图2-6 所示。</p>
<p><img src="/img/bVbpywe?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>在这个背景下,下面就来谈 HTTP/1.1 的队头阻塞。图2-7 中的一个 TCP 连接同时传输 10 个请求,其中第 1、2、3 个请求已被客户端接收,但第 4 个请求丢失,那么后面第 5 - 10 个请求都被阻塞,需要等第 4 个请求处理完毕才能被处理,这样就浪费了带宽资源。</p>
<p><img src="/img/bVbpywf?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>因此,HTTP 一般又允许每个主机建立 6 个 TCP 连接,这样可以更加充分地利用带宽资源,但每个连接中队头阻塞的问题还是存在。</p>
<p>HTTP/2 的多路复用解决了上述的队头阻塞问题。不像 HTTP/1.1 中只有上一个请求的所有数据包被传输完毕下一个请求的数据包才可以被传输,HTTP/2 中每个请求都被拆分成多个 Frame 通过一条 TCP 连接同时被传输,这样即使一个请求被阻塞,也不会影响其他的请求。如图2-8 所示,不同颜色代表不同的请求,相同颜色的色块代表请求被切分的 Frame。</p>
<p><img src="/img/bVbpywg?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>事情还没完,HTTP/2 虽然可以解决“请求”这个粒度的阻塞,但 HTTP/2 的基础 TCP 协议本身却也存在着队头阻塞的问题。HTTP/2 的每个请求都会被拆分成多个 Frame,不同请求的 Frame 组合成 Stream,Stream 是 TCP 上的逻辑传输单元,这样 HTTP/2 就达到了一条连接同时发送多条请求的目标,这就是多路复用的原理。如图2-9 所示,我们看一个例子,在一条 TCP 连接上同时发送 4 个 Stream,其中 Stream1 已正确送达,Stream2 中的第 3 个 Frame 丢失,TCP 处理数据时有严格的前后顺序,先发送的 Frame 要先被处理,这样就会要求发送方重新发送第 3 个 Frame,Stream3 和 Stream4 虽然已到达但却不能被处理,那么这时整条连接都被阻塞。</p>
<p><img src="/img/bVbpywk?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>不仅如此,由于 HTTP/2 必须使用 HTTPS,而 HTTPS 使用的 TLS 协议也存在队头阻塞问题。TLS 基于 Record 组织数据,将一堆数据放在一起(即一个 Record)加密,加密完后又拆分成多个 TCP 包传输。一般每个 Record 16K,包含 12 个 TCP 包,这样如果 12 个 TCP 包中有任何一个包丢失,那么整个 Record 都无法解密,如图2-10 所示。</p>
<p><img src="/img/bVbpywl?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>队头阻塞会导致 HTTP/2 在更容易丢包的弱网络环境下比 HTTP/1.1 更慢!</p>
<p>那 QUIC 是如何解决队头阻塞问题的呢?主要有两点。</p>
<ul>
<li>QUIC 的传输单元是 Packet,加密单元也是 Packet,整个加密、传输、解密都基于 Packet,这样就能避免 TLS 的队头阻塞问题;</li>
<li>QUIC 基于 UDP,UDP 的数据包在接收端没有处理顺序,即使中间丢失一个包,也不会阻塞整条连接,其他的资源会被正常处理。</li>
</ul>
<h3>2.5 <strong>拥塞控制</strong>
</h3>
<p>拥塞控制的目的是避免过多的数据一下子涌入网络,导致网络超出最大负荷。QUIC 的拥塞控制与 TCP 类似,并在此基础上做了改进。所以我们先简单介绍下 TCP 的拥塞控制。</p>
<p>TCP 拥塞控制由 4 个核心算法组成:慢启动、拥塞避免、快速重传和快速恢复,理解了这 4 个算法,对 TCP 的拥塞控制也就有了大概了解。</p>
<ul>
<li>慢启动:发送方向接收方发送 1 个单位的数据,收到对方确认后会发送 2 个单位的数据,然后依次是 4 个、8 个……呈指数级增长,这个过程就是在不断试探网络的拥塞程度,超出阈值则会导致网络拥塞;</li>
<li>拥塞避免:指数增长不可能是无限的,到达某个限制(慢启动阈值)之后,指数增长变为线性增长;</li>
<li>快速重传:发送方每一次发送时都会设置一个超时计时器,超时后即认为丢失,需要重发;</li>
<li>快速恢复:在上面快速重传的基础上,发送方重新发送数据时,也会启动一个超时定时器,如果收到确认消息则进入拥塞避免阶段,如果仍然超时,则回到慢启动阶段。</li>
</ul>
<p>QUIC 重新实现了 TCP 协议的 Cubic 算法进行拥塞控制,并在此基础上做了不少改进。下面介绍一些 QUIC 改进的拥塞控制的特性。</p>
<h4>热插拔</h4>
<p>TCP 中如果要修改拥塞控制策略,需要在系统层面进行操作。QUIC 修改拥塞控制策略只需要在应用层操作,并且 QUIC 会根据不同的网络环境、用户来动态选择拥塞控制算法。</p>
<h4>单调递增的 Packet Number</h4>
<p>TCP 为了保证可靠性,使用 Sequence Number 和 ACK 来确认消息是否有序到达,但这样的设计存在缺陷。</p>
<p>超时发生后客户端发起重传,后来接收到了 ACK 确认消息,但因为原始请求和重传请求接收到的 ACK 消息一样,所以客户端就郁闷了,不知道这个 ACK 对应的是原始请求还是重传请求。如果客户端认为是原始请求的 ACK,但实际上是图2-11 的情形,则计算的采样 RTT 偏大;如果客户端认为是重传请求的 ACK,但实际上是图2-12 的情形,又会导致采样 RTT 偏小。图中有几个术语,RTO 是指超时重传时间(Retransmission TimeOut),跟我们熟悉的 RTT(Round Trip Time,往返时间)很长得很像。采样 RTT 会影响 RTO 计算,超时时间的准确把握很重要,长了短了都不合适。</p>
<p><img src="/img/bVbpywn?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>QUIC 解决了上面的歧义问题。与 Sequence Number 不同的是,Packet Number 严格单调递增,如果 Packet N 丢失了,那么重传时 Packet 的标识不会是 N,而是比 N 大的数字,比如 N + M,这样发送方接收到确认消息时就能方便地知道 ACK 对应的是原始请求还是重传请求。如图2-13 所示,客户端接收到的是 ACK N + M,毫无疑问对应重传请求,图2-14 客户端收到的是 ACK N,所以对应的是原始请求。</p>
<p><img src="/img/bVbpywo?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<h4>ACK Delay</h4>
<p>TCP 计算 RTT 时没有考虑接收方接收到数据到发送确认消息之间的延迟,如图2-15 所示,这段延迟即 ACK Delay。QUIC 考虑了这段延迟,使得 RTT 的计算更加准确。</p>
<p><img src="/img/bVbpywp?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<h4>更多的 ACK 块</h4>
<p>一般来说,接收方收到发送方的消息后都应该发送一个 ACK 回复,表示收到了数据。但每收到一个数据就返回一个 ACK 回复太麻烦,所以一般不会立即回复,而是接收到多个数据后再回复,TCP SACK 最多提供 3 个 ACK block。但有些场景下,比如下载,只需要服务器返回数据就好,但按照 TCP 的设计,每收到 3 个数据包就要“礼貌性”地返回一个 ACK。而 QUIC 最多可以捎带 256 个 ACK block。在丢包率比较严重的网络下,更多的 ACK block 可以减少重传量,提升网络效率。</p>
<h3>2.6 流量控制</h3>
<p>TCP 会对每个 TCP 连接进行流量控制,流量控制的意思是让发送方不要发送太快,要让接收方来得及接收,不然会导致数据溢出而丢失,TCP 的流量控制主要通过滑动窗口来实现的。可以看出,拥塞控制主要是控制发送方的发送策略,但没有考虑到接收方的接收能力,流量控制是对这部分能力的补齐。</p>
<p>QUIC 只需要建立一条连接,在这条连接上同时传输多条 Stream,好比有一条道路,两头分别有一个仓库,道路中有很多车辆运送物资。QUIC 的流量控制有两个级别:连接级别(Connection Level)和 Stream 级别(Stream Level),好比既要控制这条路的总流量,不要一下子很多车辆涌进来,货物来不及处理,也不能一个车辆一下子运送很多货物,这样货物也来不及处理。</p>
<p>那 QUIC 是怎么实现流量控制的呢?我们先看单条 Stream 的流量控制。Stream 还没传输数据时,接收窗口(flow control receive window)就是最大接收窗口(flow control receive window),随着接收方接收到数据后,接收窗口不断缩小。在接收到的数据中,有的数据已被处理,而有的数据还没来得及被处理。如图2-16 所示,蓝色块表示已处理数据,黄色块表示未处理数据,这部分数据的到来,使得 Stream 的接收窗口缩小。</p>
<p><img src="/img/bVbpywq?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>随着数据不断被处理,接收方就有能力处理更多数据。当满足 (flow control receive offset - consumed bytes) < (max receive window / 2) 时,接收方会发送 WINDOW_UPDATE frame 告诉发送方你可以再多发送些数据过来。这时 flow control receive offset 就会偏移,接收窗口增大,发送方可以发送更多数据到接收方。</p>
<p><img src="/img/bVbpywu?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>Stream 级别对防止接收端接收过多数据作用有限,更需要借助 Connection 级别的流量控制。理解了 Stream 流量那么也很好理解 Connection 流控。Stream 中,<code>接收窗口(flow control receive window) = 最大接收窗口(max receive window) - 已接收数据(highest received byte offset) </code>,而对 Connection 来说:<code>接收窗口 = Stream1接收窗口 + Stream2接收窗口 + ... + StreamN接收窗口 </code>。</p>
<h2>3. QUIC 应用</h2>
<h3>3.1 协商升级</h3>
<p>因为不确认服务器是否支持 QUIC,所以需要经历协商升级过程才能决定能够使用 QUIC。</p>
<p><img src="/img/bVbpyww?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>首次请求时,客户端会使用 HTTP/1.1 或者 HTTP/2,如果服务器支持 QUIC,则在响应的数据中返回 alt-svc 头部,主要包含以下信息:</p>
<ul>
<li>quic:监听的端口;</li>
<li>ma:有效时间,单位是秒,承诺在这段时间内都支持 QUIC;</li>
<li>版本号:QUIC 的迭代很快,这里列出所有支持的版本号。</li>
</ul>
<p>确认服务器支持 QUIC 之后,客户端向服务端同时发起 QUIC 连接和 TCP 连接,比较两个连接的速度,然后选择较快的协议,这个过程叫“竞速”,一般都是 QUIC 获胜。</p>
<h3>3.2 应用情况</h3>
<p><img src="/img/bVbpywx?w=1920&h=1080" alt="图片描述" title="图片描述"></p>
<p>目前 Google、Gmail、QQ 会员等业务已经陆续使用 QUIC。本文主要侧重介绍 QUIC 本身,也限于笔者这方面实践经验有限,QUIC 应用部分不再详述,大家可以找相关实践文章,比如这篇<a href="https://link.segmentfault.com/?enc=1FehJXgGhHD21NV70rF8ww%3D%3D.PiB4EN8Oj8mYiGwTrWGRca0HEWBX5HD2rlDblWJPrGgYhBj3XE32K0%2BkseN%2BkeOY" rel="nofollow">《让互联网更快的协议,QUIC在腾讯的实践及性能优化》</a>。</p>
<h2>4. 总结</h2>
<p>QUIC 丢掉了 TCP、TLS 的包袱,基于 UDP,并对 TCP、TLS、HTTP/2 的经验加以借鉴、改进,实现了一个安全高效可靠的 HTTP 通信协议。凭借着零 RTT 建立连接、平滑的连接迁移、基本消除了队头阻塞、改进的拥塞控制和流量控制等优秀的特性,QUIC 在绝大多数场景下获得了比 HTTP/2 更好的效果,HTTP/3 未来可期。</p>
<p><img src="/img/bVbpywY?w=1432&h=692" alt="图片描述" title="图片描述"></p>
【前端资讯】React v16.6 发布
https://segmentfault.com/a/1190000016810366
2018-10-26T09:44:39+08:00
2018-10-26T09:44:39+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
8
<p>本文转自 FEPulse 公众号(微信搜索 <strong>FEPulse</strong>,精选国内外最新前端资讯,为你把握前端脉搏)。</p>
<p>React v16.6 发布,包含一些便捷的功能,我们对此进行梳理。<br><img src="/img/bVbiHhT?w=1192&h=1146" alt="图片描述" title="图片描述"></p>
<h3>React.memo</h3>
<p>React.memo 是一个高阶组件,类似于 React.PureComponent,但参数是函数组件而不是类组件。</p>
<p>纯函数的意思是传入同样的输入应该得到同样的输出,对应的,对于一个函数组件而言,如果传入同样的 props,渲染结果也应该一样的话,那么使用 React.memo 包裹这个函数组件则可以获得较大的性能提升。<br><img src="/img/bVbiHhY?w=1360&h=198" alt="图片描述" title="图片描述"></p>
<p>原理是 React 会对传入的 props 进行浅比较,如果 props 没有变化,则直接返回上一次渲染结果,避免重复渲染。你也可以在 React.memo 的第二个参数中定制自己的比较逻辑。<br><img src="/img/bVbiHhZ?w=1360&h=486" alt="图片描述" title="图片描述"></p>
<p>这里有个小插曲,在起名上,有人问为啥不叫 React.pure,而叫 React.memo 呢?对此,React 的作者 Dan 对此回应:React.memo 中的 memo 是 memoization,即缓存的意思,React.memo 赋予了函数组件缓存的能力,并且 memoization 太难拼写,因此这个方法最终被称为 React.memo。<br><img src="/img/bVbiHh1?w=1198&h=1204" alt="图片描述" title="图片描述"></p>
<h3>React.lazy: Code-Splitting with Suspense</h3>
<p>Suspense 最初是由 Dan 在今年 3 月份的 JSConf Iceland 2018 中提出,我们对此也做了详细介绍:<a href="https://link.segmentfault.com/?enc=tUY1MRHEwE1GwknygA6PDA%3D%3D.DpieiftUyrKBMklVFtGq27hYOeImg25Ro2WJX6kmZlCaXLCBo6KwEi%2BAUps9XDNbrVbU13JJOEFGAAgnrPSiY7nOLzRUODS2%2BL4UFhdvDvtLztz%2FJELwMz5d0%2F6Hm4FQsK5fxEdVLdL7GBtVc7I4Gf1RO%2Brt5T7hNNaSoj%2BQbCDE4axWBP8KWb9k%2FGffywU1PKwFDreBzicM3P5MfszT1dRsg9qdy%2F4wPtAv593YYkfHy0ou6uRU4kdmvC9n5hGKblL2dsMsayqgG24QNUewi5DBUE%2FMS7wVWoYfwX0A5hHi8qu56wsdctFZxyus%2Fow6" rel="nofollow">【前端资讯】React 的未来:Time Slicing 和 Suspense</a></p>
<p>从 v16.6 开始,你可以使用 Suspense 组件和 React.lazy 方法做 Code Splitting。<br><img src="/img/bVbiHig?w=1360&h=450" alt="图片描述" title="图片描述"></p>
<p>需要注意的是,这种使用方式还不支持 SSR。</p>
<h3>static contextType</h3>
<p>从 v16.3 开始,React 引进了 new Context API,但发布之后反馈不太理想,使用起来比较困难,因此从 v16.6 开始添加了一个便利的 API 来使用类组件中的 context value。<br><img src="/img/bVbiHiq?w=1360&h=918" alt="图片描述" title="图片描述"></p>
<h3>static getDerivedStateFromError()</h3>
<p>React v16 引入了 Error Boundaries 来处理渲染时抛出的错误,同时错误发生时也会触发 componentDidCatch。在触发之前,错误的组件将被当做 null 处理,但这可能不符合父组件的 ref 不能为 null 的预期。同时,它也无法从服务器上的错误中恢复,因为 Did 开头的生命周期方法在服务器端并不会触发。</p>
<p>因此,React v16.6 添加了 static getDerivedStateFromError(error) 方法,允许开发者在 render 完成之前渲染 Fallback UI。这个生命周期函数触发的条件是子组件抛出错误,然后 getDerivedStateFromError 接收这个错误参数后更新 state。<br><img src="/img/bVbiHiv?w=1360&h=810" alt="图片描述" title="图片描述"></p>
<h3>Deprecations in StrictMode</h3>
<p>弃用了 StrictMode 中的两个 API:ReactDOM.findDOMNode() 和 Legacy Context。</p>
<p><img src="/img/bVbitqp?w=1080&h=537" alt="图片描述" title="图片描述"></p>
Github Actions:所有软件开发者必须掌握的技能
https://segmentfault.com/a/1190000016757021
2018-10-22T09:37:41+08:00
2018-10-22T09:37:41+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
2
<p>本文转自 FEPulse 公众号(微信搜索 <strong>FEPulse</strong>,精选国内外最新前端资讯,为你把握前端脉搏)。</p>
<p>Github Actions 是 GitHub Universe 大会上发布的,被 Github 主管 Sam Lambert 称为“再次改变软件开发”的一款重磅功能(“we believe we will once again revolutionize software development.”)。本文目的是向大家介绍这一 Github 全新的功能,更多内容可以查看文末的拓展阅读。</p>
<p>什么是 Github Actions,<a href="https://link.segmentfault.com/?enc=u839LFkiFVjEiguCW8onfg%3D%3D.Unzy9z635Jj0p0H95t4TSSs5uizMvSFdxdiIUeei%2BaDjq1knfE7Wss3Yb1vQrC%2Fh" rel="nofollow">官网</a>的介绍是:</p>
<blockquote>With GitHub Actions you can automate your workflow from idea to production.</blockquote>
<p>还是很迷糊。不急,我们先看现在的 Github 是什么?代码仓库,一个提供了分布式版本控制和源代码管理的代码仓库。想象一下这样一种场景,你写好了一个网站的代码,并且存储到了 Github 上,但完事了吗?没有,你还需要部署代码才能让别人访问你的网站。另外,如果你修改了代码,还需要单独测试。理想的情况应该是:当你将代码提交到 master 时,测试、部署等等所有工作自动执行。之前,Travis、Pre-commit Hooks 可以帮助我们实现部分自动化,而现在有了 Github Actions,通通皆可抛。</p>
<p>Github Actions 可以自动化和定制化项目的 Workflow,像官网显示的那样。</p>
<p><img src="/img/bVbitqD?w=3860&h=4623" alt="图片描述" title="图片描述"></p>
<p>Workflow 比较好理解,将对项目的操作概括和按顺序整理,在遇到触发条件时 Workflow 就会按照开发者事先的设置串行或并行地运行一系列 Action,这就是 Github Actions 名称的由来。上面那张图中,Action 即一个个方框,Workflow 即将 Action 连接起来的图表。触发条件有很多种,比如 push 代码到 Github,比如 assign 了一个 issue,比如创建了一个 milestone 等等,这些都是 Github 提供的事件,工作流只要监听关心的事件即可。(目前 Github 一共提供了 26 种事件,想看所有事件可以查看:<a href="https://link.segmentfault.com/?enc=Tp1%2By4h%2FnVhGs3owAHlhJA%3D%3D.3MLniEoFo6Pylye%2FoMW9AA36Slzo7anW8GYrcLviSwDccO7B9QMKgY3qlr%2BAxd3S%2BaWYJjG%2BgAsKjIAiE6T0n1m7qYgAciv9M5nAstSQnx%2BpBXAClhfA9UeppL3giVZoJ0m3yiRd2WjWijQ3Lv4AkyMm4wuXS%2BxHMr4dWhZTKWw%3D" rel="nofollow">https://developer.github.com/...</a>)</p>
<p>直观地理解了 Workflow 和 Action,下面再对 Github Actions 的核心 Action 作更深入地理解。Action 是一小段可以运行的代码,可以用来做很多事情。比如你可以设置一个自动测试的 Action,当提交代码到 Github 后,Action 便会触发自动测试;再比如你可以设置一个自动部署的 Action,当代码通过测试后直接部署到腾讯云、阿里云、Azure 上。除此以外,你还可以拿 Action 做很多事。比如当前项目是一个 NPM Package,你可以设置一个 Action 用来自动 Publish;比如你需要监听项目的 issue,所以你可以设置一个 Action,当项目中有 issue 创建,给你的微信发一条提醒;比如 minify 或 uglify 你的 JS 代码……Action 的想象空间很大,全看你的需求。目前 Github 一共发布了 450 个示例 Action,你也可以创建、分享你的 Action,别人也能搜到你的 Action。</p>
<p>讲道理,讲完基本概念下面就要开始实操了,但 Github Acions 还处于 Beta 阶段,并没有对所有人开放,想要提前使用的可以在<a href="https://link.segmentfault.com/?enc=GudXdYH1ZSs88GvQjg%2BqKQ%3D%3D.0qpF8qf5N1lXspcAdfAbij10ZI4zjwGm8zJXCyeWi41JmtEKtqEbKSCCZdgIC30n" rel="nofollow">官网</a>尝试申请。因为我还没拿到测试资格,所以后面有机会的话再说吧。不过已经有 Github Actions 的第一批实践者写了一篇<a href="https://link.segmentfault.com/?enc=gze9pLR5X7c7wsvpJ29BVA%3D%3D.hyKQiGOCFpZuIDiw%2FVGJ1RUJRWDOQ3nZmKmfOFo0weFIUU8S%2BWnnvzoqCfrTD%2BucDs7n59Yfr7wBNB6uX4I5jQ%3D%3D" rel="nofollow">文章</a>关于如何设置以及如何创建一个 Action。</p>
<p><img src="/img/bVbitqF?w=1656&h=822" alt="图片描述" title="图片描述"><br><img src="/img/bVbitqG?w=2090&h=822" alt="图片描述" title="图片描述"></p>
<p><img src="/img/bVbitqp?w=1080&h=537" alt="图片描述" title="图片描述"></p>
<p><strong>拓展阅读:</strong></p>
<ol>
<li>
<a href="https://link.segmentfault.com/?enc=zCCO%2B0nXIaz8cOxvBuaJug%3D%3D.LEuABpMWOZzCzq1to%2BiHlyYBuhixAUkgj4RIDsdI9a0exaR9bZ7RkDe%2F%2FosCYntV" rel="nofollow">https://github.com/features/a...</a>:官网;</li>
<li>
<a href="https://link.segmentfault.com/?enc=op7ToTf9Kgfm5hBDGwD0Kg%3D%3D.QS3NvcMTt7sADFjewlyXgmq0WfFwvYq8%2Bpn0q62xnN5wlKC8RfjPINZnbB48jojs" rel="nofollow">https://developer.github.com/...</a>: 文档;</li>
<li>
<a href="https://link.segmentfault.com/?enc=XD9mfj5twdkNNQvwtMKEWA%3D%3D.MW7TaX3DFPxf5uZLa6lISHXgRnJRlFhKXLw8izxTSybrujbDqbuTGXZHUURgSdu%2Bf4isFirViR0sbL5YUHVTRA%3D%3D" rel="nofollow">Introducing GitHub Actions</a>:详细介绍了如何设置 Action 和创建新的 Action;</li>
<li>
<a href="https://link.segmentfault.com/?enc=OriR2aQnp2wEJBW8sn3PVQ%3D%3D.HHK%2BY3s23CJ88Xtlad92%2Bun%2B8cZ%2B9BlX7hUW6%2BxBNiuZ2JK9%2FrxXsrJjzmE%2F30z9Urs9X5PfZk2EenqUvm1KNQ%3D%3D" rel="nofollow">GitHub Actions: built by you, run by us</a>:一些 Action Demo;</li>
<li>
<a href="https://link.segmentfault.com/?enc=pHMldSNRG76JdcMIZxJ9DQ%3D%3D.JqEaw9ILNe2hf85s%2Faea0h5MTBqTUcFADCHPwXtSWdBdK9B4By3H9X0Sr8L%2FtFrMp2NGC6FV0IR5DTHYA9fnMosDGho7euzKYjqanMy%2Fb8aQ9NLb4vCdUP1yADSvDhtQ" rel="nofollow">GitHub Actions Creates a Buzz for Automated Dev Workflows</a>:新闻报道;</li>
<li>
<a href="https://link.segmentfault.com/?enc=j2f7mA%2F3V9CMnXMt0RqnGQ%3D%3D.eTdg34A0Tsvm1VS1m%2BvATckg%2BDsgv6%2FNvoV%2F8o7kEarfRZsrDn526mvUzuMNCCujNh3CsD1oidqCA7UdvR0nqg%3D%3D" rel="nofollow">GitHub grabs a piece of the Actions: 'A project that will do for software development what we did for the pull request'</a>:新闻报道。</li>
</ol>
【本周项目】9.8-9.14
https://segmentfault.com/a/1190000016408730
2018-09-15T12:15:12+08:00
2018-09-15T12:15:12+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
4
<p>本文转自 FEPulse 公众号(微信搜索 <strong>FEPulse</strong>,精选国内外最新前端资讯,为你把握前端脉搏)。</p>
<p>FEPulse 的【本周项目】模块精选 Github 一周中 Star 增长最快的最新前端项目,帮助前端开发者了解最新项目动向。</p>
<hr>
<h2>1. WatermelonDB</h2>
<p>下一代功能强大的 React 和 React Native APP 的数据库,WatermelonDB 特地为复杂的 React 和 React Native APP 做了优化,首要目标就是性能,换句话说就是要快速启动。</p>
<p>当应用简单时,可以使用 Redux 或者 Mobx,但如果扩展到数千或数万个数据库记录,应用程序启动速度会受到很大影响,尤其在速度较慢的 Android 设备上,将完整的数据库加载到 JavaScript 代价巨大。WatermelonDB 的解决方案就是延迟加载,并且因为所有查询都是以单线程的方式直接在 SQLite 数据库上执行,所以即使在较慢的 Android 设备上,大多数查询也会在不到 1 毫秒的时间内解析,即使有 10000 条记录!<br><img src="/img/bVbg0OP?w=1078&h=918" alt="图片描述" title="图片描述"></p>
<h2>2. Pigeon Maps</h2>
<p>不需要额外依赖的地图组件库,旨在提供以 React 为中心性能优先的可扩展地图引擎,压缩之后只有 8KB。</p>
<p>因为主打小而快,所以功能上并不如 Google Map 这样的专业地图库完善,比较适合简单场景的地图应用。</p>
<p><img src="/img/bVbg0OX?w=480&h=342" alt="图片描述" title="图片描述"></p>
<p><img src="/img/bVbg0OY?w=720&h=400" alt="图片描述" title="图片描述"></p>
<h2>3. You Dont Need Momentjs</h2>
<p>这篇文档主要列举了可以使用 date-fns 或原生方法替代 Momentjs 的场景。</p>
<p>Momentjs 是一个功能强大的时间库,但正因如此,Momentjs 同时也有 API 复杂、包庞大等缺点。大部分时候我们只需要 Momentjs 的部分功能,而这些功能可以通过 date-fns 或者原生方法实现,同时又能减少包的大小和提升性能。</p>
<p>期待下一个项目 You Dont Need Girl Friend~<br><img src="/img/bVbg0O3?w=1076&h=802" alt="图片描述" title="图片描述"></p>
<h2>4. Ky</h2>
<p>Ky 是一个基于浏览器 Fetch API 的小巧而优雅的 HTTP 客户端,让你的网络请求更简单。</p>
<p>我们看一段代码感受一下:<br><img src="/img/bVbg0O4?w=1074&h=1464" alt="图片描述" title="图片描述"></p>
<p>(以上 Star 数均以截稿时为准)</p>
<p>更多精彩内容,更好看的排版,可以关注FEPulse微信公众号(ID:FEPulse)<br><img src="/img/bVbg0Pe?w=1380&h=702" alt="图片描述" title="图片描述"></p>
【前端轶事】Chrome 小恐龙背后的故事
https://segmentfault.com/a/1190000016394154
2018-09-14T09:28:24+08:00
2018-09-14T09:28:24+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
3
<p>本文转自 FEPulse 公众号(微信搜索 <strong>FEPulse</strong>,精选国内外最新前端资讯,为你把握前端脉搏)。</p>
<p>如果你是 Chrome 用户,一定对那萌萌哒的小恐龙不陌生,每当互联网连接断开时,你便能看到那只小恐龙,点击空格键就能开启小恐龙跑酷游戏。<br><img src="/img/bVbgWZ4?w=480&h=152" alt="图片描述" title="图片描述"></p>
<p>这个游戏的名字叫:Chrome Dino,你现在可以打开 Chrome,不需要断开网络,只需要在地址栏输入 chrome://dino 即可畅玩。</p>
<p>这些都是众所周知的。正值 Chrome 10 周年之际,今天我们来听一听 Chrome 设计团队小恐龙的创作者 Edward、Sebastien Gabriel 和 Alan Bettes 讲述这只小恐龙背后的故事。</p>
<p>Chrome Dino 最初作为复活节彩蛋诞生于 2014 年初,在网络无处不在的现代社会,断网仿佛让人回到了史前时代,所以便有了这只小恐龙。从第一个版本开始,游戏在视觉上采用了像素点的风格,并且小恐龙的动作很僵硬,主要也是让用户立刻想到复古电子游戏。<br><img src="/img/bVbgW0i?w=1000&h=750" alt="图片描述" title="图片描述"></p>
<p>设计时大家对如何对待这只小恐龙意见不一,最初的小恐龙也有咆哮的,像下图一样,咆哮着向世人宣告自己还活着。不过最终还是设计成了我们现在看到的奔跑的小恐龙。<br><img src="/img/bVbgW0B?w=960&h=310" alt="图片描述" title="图片描述"></p>
<p>据 Edward 说,这是他第一次写游戏,要考虑物理跳跃、碰撞检测、平台兼容等各个问题,游戏上线之后运行得并不理想,尤其在较旧的 Android 设备上,所以设计团队重写了代码,截至 2014 年 12 月,该游戏已经扩展到所有平台。</p>
<p>目前,该游戏每个月在桌面端或移动端被运行超 2.7 亿次,这些数据大都来自新兴市场,用户大都来源于印度、巴西、墨西哥或印度尼西亚等国家。</p>
<p>经过四年的迭代,现在游戏中还添加了翼龙、夜间模式等元素。为了纪念 Chrome 10 周年,设计团队还推出了“周年纪念版”,添加了生日蛋糕、生日帽、气球等元素。<br><img src="/img/bVbgW0L?w=900&h=600" alt="图片描述" title="图片描述"></p>
<p>终极问题来了!!!</p>
<p>小恐龙前面的路到底有多长,路的尽头又是什么?</p>
<p>Edward 告诉我们,小恐龙大约可以跑 1700 万年,与它在地球上存活的时间差不多......什么?你不相信?那就去试试嘛~</p>
<p>最后,再次邀请大家关注我们的公众号 FEPulse,第一时间获取我们精心整理的最新前端资讯。也欢迎在公众号文章或者后台留言讨论。<br><img src="/img/bVbgW08?w=1080&h=537" alt="图片描述" title="图片描述"></p>
React 的未来:Time Slicing 和 Suspense
https://segmentfault.com/a/1190000013524698
2018-03-05T13:34:47+08:00
2018-03-05T13:34:47+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
7
<p>本文转自 FEPulse 公众号(微信搜索 <strong>FEPulse</strong>,每日精选一条国内外最新前端资讯,为你把握前端脉搏)。</p>
<p>JSConf Iceland 大会于 3.1 - 3.2 在冰岛举行,在会中,React 核心团队的 Dan Abramov 发表了名为 “Beyond React 16” 的演讲,描述了对 React 未来的展望,本文根据 Dan 的演讲整理。</p>
<p>React 作为一个通用的框架,需要考虑各种网络状况(网速有快有慢)以及各种设备类型(CPU 性能有好有坏),而框架开发者的一个重要使命就是帮助开发者们开发在各种情况下用户体验都很好的应用程序。</p>
<p>影响用户体验的因素主要可以归为两大类:计算能力(Computing Power)和网络速度(Network Speed),他们对应计算设备的 CPU 和 IO 能力。在 React 中,CPU 主要影响 DOM 元素创建和更新的效率,而 IO 则影响获取数据和懒加载的代码。下面 Dan 用了两个 Demo 来展示 React 在这两个方面的尝试。</p>
<p>Demo 1</p>
<p><img src="/img/bV4UtA?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>第一个例子,由一个输入框和下面的三个图表组成,当输入的内容越复杂,下面的图表也越来越复杂。</p>
<p>为了更直观地看到页面刷新的效率,Dan 还写了一个时钟,绿色表示页面刷新时帧与帧之间间隔的时间很短,而颜色越深则表示间隔时间越长,页面卡顿感越强,用户体验自然也越差。<br><img src="/img/bV4UtE?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>这种场景中,提升用户体验的一个经典的做法是 Debounce(Throttle 类似),即等用户暂停输入后再刷新页面,对应的 Demo 如下:<br><img src="/img/bV4UtH?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>但这么做用户体验上也是有缺陷的,如果用户的 CPU 很强劲,那也不得不等暂停输入后才看到结果。那有没有更好的解决方案呢?有的,Dan 给了一种异步刷新的方案,先看图:<br><img src="/img/bV4Ut5?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>引用 Dan PPT 中的一句话,用来体现这种异步方案的精髓。</p>
<blockquote>We've built a generic way to ensure that high-priority updates like user input don't get blocked by rendering low-priority updates.</blockquote>
<p>Dan 称这种特性为 “Time Slicing”,主要包括以下几点:<br><img src="/img/bV4UzX?w=1368&h=644" alt="图片描述" title="图片描述"></p>
<p>Demo 2<br><img src="/img/bV4Uun?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>第二个 Demo 是一个电影信息展示的应用,选择一个电影,点击进入可查看电影的详情和评论。在后面的演示中,Dan 通过一步步修改这个 Demo 的代码来让我们理解 React 在网络方面如何提升用户体验的。</p>
<p>首先,Dan 将数据从硬编码改成了从网络获取。代码如下:<br><img src="/img/bV4Uus?w=1360&h=810" alt="图片描述" title="图片描述"></p>
<p>在上面这段代码中,当 React 渲染 MovieDetails 组件时,会先看对应电影 ID 的详情有没有被缓存,因为是第一次,电影详情信息还没被缓存,所以需要去网络拉取,下面高能!<strong>React 会阻止渲染过程,直到数据被拉取回来!</strong>而我们需要做的,仅仅告诉 React,从电影列表页到电影详情页的过程是个异步过程即可。代码如下:<br><img src="/img/bV4Uuu?w=1360&h=738" alt="图片描述" title="图片描述"></p>
<p>这样修改过后,下面的动图展示了这种异步过程:点击一个电影,React 去网络拉取对应电影的详情数据,等数据拉取回来后,React 再将页面渲染出来。<br><img src="/img/bV4UuI?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>同时,页面还保持良好的可交互性,点击一个电影之后紧接着用户也可以点击其他的电影。</p>
<p>上面的修改仅仅模拟了 1s 的网络延迟,如果延迟更长咋办,用户点击了一个电影,发现页面像卡死了一般,用户体验肯定很差。Dan 紧接着又对代码做了修改:使用一个叫 “Placeholder” 的组件包裹即将要渲染的异步组件,当组件在加载的过程中,Placehodler 会显示一个“安慰剂”。<br><img src="/img/bV4Uvk?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>同样的,这个过程界面仍然保持着良好的交互性,当页面在转圈时,用户可以选择返回。<br><img src="/img/bV4UvL?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>上面的修改中,只有一个电影详情数据需要从网络获取,如果有多个数据需要从网络获取,那么 React 可以选择等所有数据都返回后显示最终的页面,也可以选择优先展示先获取到的数据。<br><img src="/img/bV4UwB?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>除了在详情页中展示安慰剂,也可以在电影列表处展示,这需要通过一个叫 Loading 的组件来实现,Loading 与 Placehodler 一样,是一个未来的特性。<br><img src="/img/bV4UwR?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>除此以外,还可以使用 Code Splitting 来优化用户体验。但点击一个电影时,再去拉取跟电影详情页相关的代码。这里还是使用未来提供的 createFetcher 接口来实现 Code Splitting。<br><img src="/img/bV4Ux0?w=1360&h=486" alt="图片描述" title="图片描述"></p>
<p>运行代码,点击一个电影,会发现 Network 中新拉下来一个叫 “1.chunk.js” 的文件。<br><img src="/img/bV4Ux5?w=444&h=250" alt="图片描述" title="图片描述"></p>
<p>最后,Dan 还做了一个用户体验方面的优化,目前为止的 Demo 中,打开页面时,电影海报可能没加载出来(从上面一张动图中也可以看出来),所以 Dan 做了优化,需要等图片加载完成后,页面才能显示。这里的细节不再详述,当然这也是用 createFetcher 实现的。</p>
<p>到这里,Demo 2 就结束了。Dan 用一句话概括了这个新特性:</p>
<blockquote>We've built a generic way to suspend rendering while they load asynchronous data.</blockquote>
<p>Dan 称这个新特性为 “Suspense”,主要包括以下几点:<br><img src="/img/bV4Ux7?w=658&h=290" alt="图片描述" title="图片描述"></p>
<p>这两项新的特性据说将在今年发布,是不是很期待呢?你对 Timing Slicing 和 Suspense 又有什么看法呢?欢迎留言。</p>
<p>最后,再次邀请大家关注我们的公众号 <strong>FEPulse</strong>,第一时间获取我们精心整理的最新的前端资讯。</p>
最经典的前端面试题之一,你能答出什么幺蛾子?
https://segmentfault.com/a/1190000010947472
2017-08-31T15:16:21+08:00
2017-08-31T15:16:21+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
43
<p>本文的目标是以“输入 URL 后发生了什么”这个经典面试题为引子,写一篇既能够涵盖面试中大部分网络试题,又能够将“输入 URL 后发生什么”讲得有深度的文章。以前写过一篇类似的<a href="https://link.segmentfault.com/?enc=Z%2F7KgNQEHMTR%2FGIEm%2FBXqw%3D%3D.bBBhEydfrX3fe7n04W5HfaxzL%2F2TEe4qRYs9pWf1CY5hhiURDXunXbHt7GYoAM1O" rel="nofollow">文章</a>,但实在过于简单。另外,HTTPS 逐渐普及,文章中没有这部分过程也说不过去。不想修改原来的文章,就重新写一篇吧。文中以我所在的项目“兴趣部落”的官网 <a href="https://link.segmentfault.com/?enc=bjWbFC9br6o6eCF%2BDvgJkA%3D%3D.0oYODvYFlLXYPOxXgH9jUHxryF%2B4pbvnlT2WPrFrYb4%3D" rel="nofollow">https://buluo.qq.com/index.html</a> 为例子。</p>
<h3>生成 HTTP 请求消息</h3>
<p>解析完要访问的目标服务器是啥了,接下来浏览器就会用 HTTP 协议生成请求消息去 web服务器请求资源,消息格式如下:<img src="/img/remote/1460000010947477" alt="" title=""></p>
<p>请求信息主要包括:</p>
<ul>
<li>请求行:请求的方法(POST/GET/…)、URL、HTTP版本(1.1/2);</li>
<li>消息头:请求的附加信息,以空行结束;</li>
<li>消息体:数据,比如 POST 请求时的表单数据。</li>
</ul>
<p>对应的,响应消息也有 3 个部分组成:</p>
<ul>
<li>状态行:HTTP版本、状态码(200/304/404/…)、解释状态的响应短语;</li>
<li>消息头</li>
<li>消息体:返回的数据。</li>
</ul>
<p>用图表示:<img src="/img/remote/1460000010947478" alt="" title=""></p>
<h3>DNS</h3>
<p>生成 HTTP 消息后,浏览器委托操作系统将消息发送给 web服务器。而通过 web服务器的名称是没法找到服务器在哪的,好比知道一个人的名字没法找到他家在哪一样,网络中的地址是用 IP 地址表示的,所以要想跟服务器通信,得先找到它的 IP 地址,使用 DNS(Domain Name System,域名服务系统) 服务器可以将 web服务器名称转换成 IP 地址。那这个过程是怎样的呢?</p>
<p>操作系统有一个 Socket 库,这个库中的程序主要是让应用程序调用操作系统的网络功能,而在这些功能中,浏览器需要调取操作系统的 DNS 解析功能。DNS 解析器生成一条表示“告诉我 <a href="https://link.segmentfault.com/?enc=16oGzAUE6flI9eDXnNGAHg%3D%3D.is7faSW7WC72z4uh6SF1uaCmoYBjnI5ZxWFe77TLNL0%3D" rel="nofollow">https://buluo.qq.com/index.html</a> 的 IP 地址”的消息,然后委托操作系统的协议栈发送 UDP 消息到 DNS 服务器。那这条消息是如何发送到 DNS 服务器又是如何将 IP 地址返回的呢?</p>
<p>首先介绍下操作系统中 DNS 解析器发送给 DNS 服务器的消息内容,消息中包含 1)域名:buluo.qq.com;2)Class: IN,代表当前的网络是因特网,DNS 设计之初还考虑了其他网络,虽然现在只有互联网,但这个字段还是保留了下来;3)记录类型:A,表示域名对应的是 IP 地址,因为 DNS 还能解析其他地址,比如类型为 MX 时 DNS 服务器会查询邮件服务器地址。DNS 服务器中维护一张表,表的每一项包含上面三个字段还有服务器地址,当域名、Class、记录类型全部匹配时,DNS 服务器返回地址,在例子中会返回兴趣部落首页的 IP 地址。</p>
<p>但这个时候问题来了,世界上有不计其数的服务器,将这些所有的服务器信息都保存在一个 DNS 的表中肯定是不现实的,所以肯定有很多台 DNS 服务器一起配合完成这个域名解析过程的,那具体过程是什么样的呢?</p>
<p>首先,DNS 服务器中的所有信息都是按照域名来划分层次的,这个层次是用 <code>.</code> 来分隔的,越靠右层次越高,比如 “buluo.qq.com” 中 “com” 层次最高,“qq” 次之,“buluo” 最后,其中每一层都被称为“域”,比如 “com 域”下是 “qq” 域,再下是 “buluo” 域,域的层次划分是为了更好地分配给不同国家、公司和组织等,典型的例子像南京市政府的官网:“www.nanjing.gov.cn”,“cn” 代表中国这个国家的域,“gov” 代表这个国家下的政府组织,“nanjing” 代表南京市政府。域有层次之分,那 DNS 服务器呢?规定将管理下级域的 DNS 服务器的 IP地址注册到上级的 DNS 服务器中,比如管理 “buluo.qq.com” 这个域的 DNS 服务器的 IP地址需要注册到 “qq.com” 域的 DNS 服务器中,以此类推,一直到“根域”,就是 “cn”、“com” 这类域的上一层次,根域中就保存了 “cn”、“com” 等域名的 DNS 服务器信息。此外,还需要将根域的 DNS 服务器信息保存在所有的 DNS 服务器中,这样只要找到一台 DNS 服务器就可以顺藤摸瓜找到下层任何一个 DNS 服务器。知道了域的层次划分以及 DNS 服务器的分布,下面就正式介绍如何寻找到相应的 DNS 服务器并获取 IP 地址。</p>
<p>首先,客户端会访问最近的一台 DNS 服务器,但由于这台 DNS 服务器上没有 “buluo.qq.com” 这个域名的对应的信息,所以就向根域 DNS 服务器发请求询问,但根域中也没有,但判定这个域名是属于 “com” 域的,所以就返回其管理的 “com” 域的 DNS 服务器的 IP 地址,意思是“虽然我不知道,但你可以去某某处问问,他应该知道”。然后 最近的那个 DNS 服务器又向 “com” 域的 DNS 服务器发请求,同理,也不知道,然后返回 “qq.com” 域的 DNS 服务器,然后这台最近的 DNS 服务器又向 “qq.com” 域 DNS 服务器发请求,仍然没有,直到最后,向 “buluo.qq.com” 这个域下的 DNS 服务器发请求才拿到 IP 地址。接着,这台最近的 DNS 服务器将获得的 “buluo.qq.com” 的 IP 地址返回给客户端,客户端再拿着这个 IP 地址去请求资源。以上的过程用图表示如下:<img src="/img/remote/1460000010947479" alt="" title=""></p>
<p>以上就是通过 DNS 服务获取目标服务器 IP 地址的过程,可以说是非常耗时,为了优化性能,DNS 服务器会对中间的查询结果做个缓存,为了保存缓存的实时性,每隔一段时间就会将缓存设为过期。</p>
<h3>委托协议栈发送消息</h3>
<p>现在客户端拿到了目标服务器的 IP 地址,下面就要与其连接并发送消息了,这个过程同样不是浏览器做的,而是委托协议栈来完成的,具体过程是:</p>
<ol>
<li>操作系统创建一个套接字,协议栈返回一个描述符,浏览器存储起来,这个描述符是套接字的 ID,用于识别套接字,原因是同一个客户端可能跟很多服务器同时连接;</li>
<li>客户端的套接字与服务端的套接字进行连接,连接成功后,协议栈将目标服务器的 IP 地址和端口号保存在套接字中,下面就可以收发数据;</li>
<li>发送的数据是 HTTP 请求消息,发送的过程是:浏览器通过描述符查找到指定的套接字,并向套接字发送数据,数据便会通过网络传输到服务端的套接字,服务器接收到消息后处理然后返回响应消息;</li>
<li>消息返回后会被放入一块内存缓冲区内,浏览器可以直接读取这段消息。之后,操作系统断开套接字连接,本地的套接字也会被删除。</li>
</ol>
<h3>TCP 连接</h3>
<p>在“委托协议栈发送消息”部分简单地提了下客户端和服务端利用套接字进行连接,那这个连接具体是什么样的呢?</p>
<p>首先什么是套接字?套接字其实就是个放在内存的备忘录,协议栈在发送数据时先看一眼备忘录,了解这个数据是发到哪个端口,当数据发送出去后,这个备忘录还得记录什么时间收到响应、什么时候断开等控制信息,协议栈需要根据这些信息来决定下一步做什么。</p>
<p>客户端和服务端的连接是通过套接字连接的,那“连接”又是什么意思呢?连接实际上是客户端和服务端互相交换控制信息的过程,控制信息主要包含两种,一种是上面提到的套接字里要来帮助协议栈进行下一步操作的信息,另一种是客户端和服务端通信时交换的控制信息,这种控制信息就是我们俗称的 TCP 头部。 那连接的过程是怎样的呢?</p>
<p>这个连接过程就是我们平时经常听到的三次握手。</p>
<ul>
<li>首先客户端创建 TCP 头部,头部包含目标服务器的端口号等,同时将头部的 SYN 设为 1,表示开始请求连接。TCP 头部创建好了之后,TCP 模块便将信息传递给 IP 模块并委托它发送,然后信息经过网络到达服务器的 IP 模块再到 TCP 模块,TCP 模块则会根据 TCP 头部的信息找到端口号对应的套接字,套接字则会写入相应的信息,然后将状态改为“正在连接”;</li>
<li>服务端的 TCP 模块收到连接请求后就要回应,与客户端一样, 需要在 TCP 头部设置发送方和接收方的端口号,以及将 SYN 设为 1,同时,返回响应时还要将 ACK 设为 1,表示已经接收到相应的包。接着,将信息打包好,发送给客户端;</li>
<li>客户端收到消息后,发现 SYN 为 1,则表示连接成功,所以在套接字中写入服务器的端口号,同时将状态改为连接完毕。为了告诉服务器收到消息,客户端也要将 ACK 设为 1,接着发送给服务端。</li>
</ul>
<p>整个过程用图表示如下:<img src="/img/remote/1460000010947480" alt="" title=""></p>
<h3>HTTPS 的握手过程</h3>
<p>上面的过程是最简单的 HTTP 三次握手,但现在越来越多的网站使用了 HTTPS 协议,那与 HTTP 连接有什么不同呢?</p>
<p>先介绍一下什么是 HTTPS。HTTPS 正如其名字,HTTP 代表其并不是自己创建一个新的协议,而是建立在 HTTP 的基础之上,S 代表其是安全的,如何保证安全?利用 SSL/TLS。SSL(Secure Sockets Layer,安全套接层)是网景设计的安全传输协议,经历了 1.0、2.0 和 3.0 版本,但因为 1.0 有严重安全缺陷,所以从未公布。后来 IETF 将 SSL <a href="https://link.segmentfault.com/?enc=EWMOay2BG9%2BojgfQKJo%2BmA%3D%3D.pRuaX256NAVzfh45aygVkjASm3XcCslGTwt%2FiS3YdzXUqgJ5f5gWwGudar843EW6" rel="nofollow">标准化</a>,称为 TLS(Transport Layer Security, 传输层安全协议) ,TLS 1.0 与 SSL 3.0 差别很小。TLS 经历了 1.0、1.1 到现在最新的 1.2。在 HTTPS 通信中具体使用哪一种还要看客户端和服务端的支持程度。那 SSL/TLS 在网络模型中属于哪一层呢?直接上图:<img src="/img/remote/1460000010947481" alt="" title=""></p>
<p>在客户端和服务端通过 HTTPS 连接的过程中,除了正常的 HTTP 连接中的事情,还有身份验证和加密信息两件事,下面看看具体过程(更详细内容可以查看标准:<a href="https://link.segmentfault.com/?enc=SrLhIm342Jt6cjB9nmlIUw%3D%3D.DOhiUyW6cB0d9hfXtrGBiOEsehCHbefhK0DT8ITdtkD9DRkFp6ivrjAhKt6FAeR1" rel="nofollow">RFC5246</a>)。</p>
<ul>
<li>
<p>Client Hello:这次握手是客户端向服务端发起加密通信请求,请求中包含以下关键信息:</p>
<ul>
<li>Version:客户端支持的协议版本,比如 TLS 1.2;</li>
<li>Random:第一个随机数,作用在后面的握手步骤中介绍;</li>
<li>Session ID:“空”表示这是一次新的连接,“不为空”表示维持前面的连接;</li>
<li>Cipher Suites:<a href="https://link.segmentfault.com/?enc=QFONW86pWDN1SFn0%2FPiTJA%3D%3D.ynoWaRzQshtwDCedXsd5GpiX9ID6vvi23VkP5AAqrAFmjlzQjXURIInXlcxu5%2F4o" rel="nofollow">密码套件</a>;</li>
<li>Compression:客户端支持的压缩方法;</li>
<li>Extensions:扩展。</li>
</ul>
</li>
<li>Server Hello:服务端收到客户端消息后返回响应,响应信息跟 ClientHello 类似,只不过每个字段都是一个确定的值,是服务端根据客户端传过来的候选值的最终选择结果,如果服务端没有在候选值中找到合适的,那么将会返回错误提示,需要提一下的是,这次的响应信息中包含第二个随机数。</li>
<li>Server Certificate:服务端紧接着向客户端发送证书;</li>
<li>Server Key Exchange Message:当上一条证书消息中的信息不全时,服务端会再次发送一些额外数据到客户端;</li>
<li>Certificate Request:如果服务端要求客户端提供证书,会发出这样一个请求;</li>
<li>Server Hello Done:这条消息表示服务端这阶段数据发送完毕,下面就是等待客户端的响应;</li>
<li>Client Certificate:如果服务端要求客户端提供证书,那么客户端会返回自己的证书;</li>
<li>
<p>Client Key Exchange Message:这一步非常关键,客户端会生成 premaster secret(预主密钥),为什么叫 premaster secret?因为后面客户端和服务端会根据 premaster secret 和前面过程中两个随机数共同生成一个 master secret(主密钥,48字节),后面通信的安全全靠这个 master secret。前两个随机数客户端和服务端都知道了,这个步骤最主要的就是协商一个 premaster secret,这个过程叫做“密钥交换”,这里介绍两个方法:</p>
<ul>
<li>RSA 密钥交换:客户端生成 46 字节的随机数,使用服务器的公钥加密,然后发送出去,服务器便可以用私钥解密。但这种方式不太安全,所以现在逐渐使用 DH 密钥交换;</li>
<li>Diffie-Hellman 密钥交换:DH 的精髓就是正向计算简单,反向计算困难,好比两种颜色的颜料,混在一起你知道什么颜色,但就给你一种颜色,你几乎没法说出其是由哪两种颜色混合而来。具体生成 premaster secret 的方式可以看<a href="https://link.segmentfault.com/?enc=7LGjs7T68htIul1GBPV05g%3D%3D.OxvvAaDZNvIqX2rpdK5oK9lFdFJPJBu9YY2uAnTR8nqzUIMqKYumV%2FM91%2FnQJIv5L7vcfrfOlNw0CZDdqmBnEM26rADKFH6C3myrt%2BVC5Hw%3D" rel="nofollow">Diffie–Hellman key exchange</a>,这里简单提一下,密钥交换需要 6 个参数,其中 2 个叫“域参数”,由服务器选取,交换过程中客户端和服务器各自生成 2 个参数,但是只相互发送 1 个,所以客户端和服务器各自知道 5 个参数,根据这 5 个参数,双方计算得到一个同样的 premaster secret。</li>
</ul>
</li>
</ul>
<ul>
<li>Certificate Verify:验证客户端的私钥和之前发送的客户端证书中的公钥是对应的;</li>
<li>Finished:客户端的握手已经完成,消息内容加密,并且包含 verify_data 字段,值是整个握手过程中所有消息的摘要,供服务端验证消息完整性;</li>
<li>Finished:表示服务端握手结束,同时也发送前面过程的消息的摘要。</li>
</ul>
<p>用图表示一下就是:<img src="/img/remote/1460000010947482" alt="" title=""></p>
<p>整个握手过程总结一下就是:</p>
<ul>
<li>客户端提出 HTTPS 连接请求;</li>
<li>服务器表明身份,表示自己是李逵而不是李鬼;</li>
<li>客户端生成一个用于以后通信的密钥,并把密钥也告诉了服务器;</li>
<li>客户端和服务器结束握手。</li>
</ul>
<p>以上就是握手的整个通信细节,但细心的同学可能会发现少了一个重要步骤,客户端收到服务器发来的证书时是如何判定对方就是自己想要找的服务器呢?这时候就要验证证书的有效性,证书就像现实中的身份证,可以确认某个网站的确是我要访问的网站。那怎么验证证书的有效性呢?首先,数字证书和身份证一样由权威机构签发,不同的是身份证只能由政府签发,而数字证书由 CA(Certification Authorities,数字证书认证机构)签发,Mac 用户可以通过“文件-应用程序-实用工具-钥匙串访问”来查看根 CA,根 CA 可以签发其他 CA,所以一个网站的签发者不是根 CA 也没关系,只要这个 CA 的签发者是根 CA 也行。了解了 CA,下面看一下证书包含什么,先看图:<img src="/img/remote/1460000010947483" alt="" title=""></p>
<p>证书中包含:网站的基本信息、网站的公钥、CA 的名字等信息(详细请看 <a href="https://link.segmentfault.com/?enc=SReIoJTVZfAn1lndjCnezg%3D%3D.I97dZj05B8b8bi5xIj8fayuoOINpLA8N7Va0Cb6y0CA5TD64IqAL5XsxGk%2BeJ%2BnL" rel="nofollow">X.509</a>),然后 CA 根据这几个内容生成摘要(digest),再对摘要用 CA 的私钥加密,加密后的结果即数字签名,最后将数字签名也放入到证书中。那么当系统收到一个证书后,先用公钥解密,解得开说明对方是由权威 CA 签发的,然后再根据证书的信息生成摘要,跟解密出来的摘要对比。</p>
<h3>数据传输</h3>
<p>建立连接之后,客户端和服务端便可以开始进行数据传输。同样,浏览器委托协议栈来帮忙收发消息,协议栈收到消息后不会立即发送出去,而是先放入到缓存区中,因为向协议栈发送的数据长度由浏览器控制,如果协议栈一收到数据就发送出去,那么可能会发送大量小包,导致网络效率降低,所以协议栈一般会等数据量积累到一定程度再发送出去,那这个程度具体是啥样?</p>
<p>首先,在以太网中,一个包的MTU(Maximum Transmission Unit,最大传输单元)是 1500 字节,除去 TCP、IP 头部的 40字节,MSS(Maximum Segment Size,最大分段大小)就是 1460 字节,但因为加密需要,头部可能会增加,相对的 MSS 就会减少。当缓存区内的数据接近 MSS 时再发送,可以避免发送小包。但是如果数据量本来就很小,或者应用程序发送数据的频率很小,那协议栈就不得不等很长时间,所以协议栈内部还有一个定时器,一定时间之后就会将包发送出去。如果数据较小,那就几个拼个车,放在一个包里发出去,如果数据很大,就要进行拆分。大概是下面这样:<img src="/img/remote/1460000010947484" alt="" title=""></p>
<p>本地一切就绪之后,协议栈就会将消息发送出去,这时还没完,客户端还要确保服务器收到了消息。我们一直都说 TCP 是面向连接的协议,因为它可以纠正丢包错误、连接失败提示等等,使得传输更加可靠。那具体又是怎么样的呢?</p>
<p>首先 TCP 模块在拆分数据时会先算好每一块数据相当于从头开始是第几个字节,然后将这个数字写入到 TCP 头部的“序号”字段中,通过这个字段,接收方就能知道包有没有丢失,比如一个消息长度为 4380(1460 * 3),那么这条消息就被拆分到三个数据块中,三个数据块的 TCP 头部的“序号”依次是 0、1460 和 2920,所以接收方先收到一个序号为 0 的包,再收到一个序号为 2920 的包,但是没收到序号为 1460 的包,说明这个包丢失了,现实中的序号为了安全不会从 0 开始,而是以一个随机数作为初始值。如果确认没有遗漏,那么接收方会将到目前为止收到的数据长度加起来,写入 TCP 的 “ACK 号”中发送给对方,注意 “ACK 号”与 ACK 标记位不是一回事,前者是数字,后者就是一个比特的标记位,但是 “ACK 号”只有在 ACK 标记位为 1 是才有效。</p>
<h3>断开连接</h3>
<p>当数据发送完毕后,一方(可能是客户端,可能是服务端)就会发起断开连接过程。这个过程也是大家很熟悉的,即四次挥手。下面以客户端发起断开请求为例:</p>
<ul>
<li>浏览器调用 Socket 库关闭连接程序,客户端的协议栈生成 TCP 头部,将 FIN 标记位设为 1,告诉服务器打算断开连接,后面不会再发送数据,同时套接字也记录断开连接操作;</li>
<li>服务器收到 FIN 为 1 的 TCP 头部时,协议栈将套接字记录为进入断开操作状态,同时向客户端发送一个 ACK 号,告诉客户端已经收到消息;</li>
<li>服务器收到断开连接信息时,可能还有数据没有传完,所以等待数据全部传输结束后,再发送一条 FIN 为 1 的信息,告诉对方也做了断开连接的准备,但没有断开;</li>
<li>一段时间后,客户端返回确认信号,到此,连接结束。</li>
</ul>
<p><img src="/img/remote/1460000010947485" alt="" title=""></p>
<p>以上就是输入 URL 后大概发生的一些事情,但是从面试角度看,仍然还有很多部分没有涉及。后续还会继续更新这篇文章,添加一些重要内容,这里先挖个坑:</p>
<ul>
<li>常见状态码解析;</li>
<li>HTTP 缓存;</li>
<li>滑动窗口;</li>
<li>握手与挥手过程中的异常处理。</li>
</ul>
<p>好,坑就挖这么多,再多怕自己不想填,等填完再继续挖。</p>
兴趣部落的前端代码规范实践
https://segmentfault.com/a/1190000010673156
2017-08-15T15:51:03+08:00
2017-08-15T15:51:03+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
2
<p>刚加入团队时,团队中没有统一的代码规范,虽然大多数人都有良好的代码风格,但毕竟习惯还是有差异的,导致代码中存在很多风格不一致的情况,所以我们便开启了统一团队代码风格的尝试。下面将这个过程做一个整理与总结。</p>
<h2>ESLint</h2>
<p>业界主要使用四种 JavaScript 代码检查工具:JSLint、JSHint、JSCS 和 ESLint,那为什么选择 ESLint 呢?可以参考文章 <a href="https://link.segmentfault.com/?enc=gdhk70KUEuWU%2BRdJIf9wkw%3D%3D.EDzyAdP5nnfHduQpPAD3%2F9KaikrzUT7XRe3vcooIJLZjUrcWtS0ffCpwvSzpRu%2F33QJKhMN6rOR7L2q3UNHGsQ%3D%3D" rel="nofollow">A Comparison of JavaScript Linting Tools</a> 来比较这四种工具的优缺点,总的来说,我们主要看中 ESLint 的以下优点:</p>
<ul>
<li><p>规则全面,且每个规则都可以配置;</p></li>
<li><p>支持 ES6 和 JSX;</p></li>
<li><p>输出结果易于理解。</p></li>
</ul>
<h2>新建 <code>.eslintrc-alloy.js</code>
</h2>
<pre><code class="javascript">module.exports = {
parser: 'babel-elint', // 在 ESLint 中使用 Babel parser
parserOptions: { // 解析器选项
ecmaVersion: 2017, // 默认 5,表示支持 ES5 语法。2017 表示 ES2017
sourceType: 'module', // 默认 ‘script’,‘module’ 表示代码是 ECMAScript 模块
ecmaFeatures: { // 想使用的额外的语言特性
experimentalOjbectRestSpread: true, // 启用对实验性的 object rest/spread properties 的支持
jsx: true // 启用 JSX
}
},
env: { // 指定启用的环境
browser: true,
node: true,
commonjs: true,
es6: true
},
plugins: [
'react'
],
root: true, // 避免 ESLint 在父级目录找配置信息
rules: {
...
}
};</code></pre>
<p>在项目根目录下新建 ".eslintrc-alloy.js" 文件,它是我们团队代码规范的核心,将之单独抽成一个文件的目的一方面是为了便于管理团队整体的代码规范,另一方面是为了后期的开源。</p>
<h2>迁移 ESLint Rules</h2>
<p>将官网所有的 <a href="https://link.segmentfault.com/?enc=J1e5%2FZzhMLU6F51LdFcvBQ%3D%3D.LSI0ETAxfe2l4Le1Cq6mZU92Kc628Jas9oOqxGn0F68%3D" rel="nofollow">Rules</a> 都放入到 ".eslintrc-alloy.js" 文件的 rules 中,这就是一个需要细心和耐心的时候了。每条 rule 的值都是以下之一:</p>
<ul>
<li><p><code>off</code> 或 <code>0</code>:关闭规则。为什么用两种方式来表示呢?一开始的时候使用数字,后来发现语义化很差,就换成了字符串,为了向前兼容便保留了数字,现在推荐使用字符串;</p></li>
<li><p><code>warn</code> 或 <code>1</code>:检查出不符合规范的代码提示警告,一般不用这个,因为程序员经常忽视 warning,[摊手];</p></li>
<li><p><code>error</code> 或 <code>2</code>:检查出不符合规范的代码提示错误。</p></li>
</ul>
<p>除了配置这三个值外,有的 rule 还有 options,所以还需要额外的配置。比如第二条 rule <code>getter-return</code> 有个 <code>allowImplicit</code> 的选项,表示是否可以返回 <code>undefined</code>,所以写法就是:</p>
<pre><code class="javascript">'getter-return': [
'error',
{
'allowImplicit': false // 不允许返回 undefined
}
]</code></pre>
<p>ESLint 配置 rule 的方式有些奇怪,rule 的值既可以是 String('off'/'warn'/'error'),也可以是 Number(0/1/2),同时也可以是 Array,如果是 Array 的话,第 0 位表示规则的关闭/警告/开启,同前面一样,第 1 位及后面可以是个 Object,也可以是个 String。Object 的例子如上面的 <code>getter-return</code> 选项。String 的例子如第五条 rule <code>no-cond-assign</code> ,如果写成:</p>
<pre><code class="javascript">'no-cond-assign': [
'error',
'always'
]</code></pre>
<p>那么就表示任何时候都不能在条件语句中使用赋值语句。具体什么时候用 Object 时候用 String,需要查看文档,看这条 rule 支持哪种写法。</p>
<p>就这样,对照文档,将每一条规则以及规则的配置信息都一一写入 ".eslintrc-alloy.js" 文件中,规则的选项配置需要根据团队的实际情况确定,如果有一些拿捏不准的,需要开会由小组成员共同确定,这毕竟以后就成为团队的代码规范。</p>
<h2>代码改造</h2>
<p>有了 “.eslintrc-alloy.js” 文件后,就有了团队的代码规范,代表着将来的代码都要以此规范为准,那现有项目的代码怎么办呢?修改呗!但问题来了,项目那么大,代码那么多,从哪里开始?一天全部改完?改不完咋办?你会发现有各种各样的问题,下面就跟大家分享一下我们团队代码改造的过程。</p>
<p><strong>新建 <code>.eslintrc.js</code></strong></p>
<pre><code class="javascript">module.exports = {
extends: [
'./.eslintrc-alloy.js'
],
globals: {
$: false,
...
},
rules: {
...
}
};</code></pre>
<p>在项目根目录下新建 “.eslintrc.js” 文件,继承同目录下的 ".eslintrc-alloy.js",这个文件最终确定项目要遵守哪些规范。我们改造的过程大概是先在 “.eslintrc.js” 文件中将所有 rules 都设为 <code>off</code>,然后一条一条删掉,删一条就找到所有不符合这个规则的代码然后修改,具体过程如下。</p>
<p><strong>迁移 ESLint Rules</strong></p>
<p>这个步骤跟把 <a href="https://link.segmentfault.com/?enc=55hujYKwEaFB85lK5kOkWQ%3D%3D.cmgTo2OqXXKcd7I%2ByTkJ%2Fi%2F0QTJrUVHxXRNxKdO8ao4%3D" rel="nofollow">Rules</a> 迁移到 ".eslintrc-alloy.js" 文件中很像,只不过这次是将 rules 全部迁移到 ".eslintrc.js" 中,并且将所有的 rule 的值都赋为 0,即关闭所有规则,这样一来就跟一开始没有 ESLint 一样了。</p>
<p><strong>构建工具</strong></p>
<p>因为我们项目采用 grunt 构建项目,所以我们使用 grunt 写了一个任务,执行 <code>grunt eslint</code> 时,可以输出整个项目中不符合 ESLint 规范的代码所在文件及行数。</p>
<p><strong>逐步删除 Rule</strong></p>
<p>下面才真正开始代码改造。每次删除一个 ".eslintrc.js" 中的 rule,因为继承了 ".eslintrc-alloy.js" 的缘故,就等同于开启了这条规则,然后执行 <code>grunt eslint</code>,找到不符合这条规范代码的文件以及行数,一一修改。修改的时候,不同情况要不同对待。修改一个规则后,要及时提交,那么后面的人就可以使用这条规则了。</p>
<ul>
<li><p><code>--fix</code>:看<a href="https://link.segmentfault.com/?enc=7cTfNwXm3%2BZASIzl%2BZJC6g%3D%3D.eii4FcRt4zkoCg9TBYW4%2FsZHv9%2BoesMkU8jkwdvqabU%3D" rel="nofollow">官网 Rules 列表</a>,你会发现第二列,有的 rule 有“扳手”图标,那代表工具可以自动修复,比如第一个有“扳手”的 rule:<code>no-debugger</code>,当执行 <code>grunt eslint</code> 之后,所有的 <code>debugger</code> 都会被删除,当然前提是你在 grunt 的 eslint 任务中开启自动 fix 的功能;</p></li>
<li><p>代码简单:比如 <code>no-console</code> 规则,不允许在程序中使用 <code>console.log</code>、<code>console.error</code> 等语句,同时没有 <code>--fix</code>,所以只要自己手动地“无脑删”即可;</p></li>
<li><p>代码复杂:有些地方代码逻辑很复杂,这时就不能“无脑改”,你需要搞懂上下文的含义,比如 <code>no-eval</code>,执行了 <code>grunt elsint</code> 后发现整个项目中就 5 处左右使用到 eval,但每一处使用场景不一样,有的是为了在低版本机器中兼容 <code>JSON.parse</code> 用来将字符串转成 JSON 格式,有的是为了将字符串转成正则,所以你只有对上下文很熟悉你才能修改,不然出一点小差错可能影响的是数百万甚至数千万用户。如果实在看不懂原来代码或者修改风险很大,那么最好在文件头部加上 ESLint 的注释,在当前文件中关闭这条规则;</p></li>
<li><p>文件较多:有的规则一开启,你会发现有数百文件、上千行代码报错,比如 <code>no-var</code>,这种情况下影响修改最关键的因素就是时间了,修改一条规则可能要花好几天,那怎么做呢?一种做法是在 ".eslintrc.js" 中保留这条规则的关闭状态,只有在执行 <code>grunt eslint</code> 时开启,找到不符合规范的代码后,立刻再次关闭这条规则,然后一条一条地慢慢改,改不完也不要紧,因为这条规则没有开启。我们采取的是另一种做法,在 ".eslintrc.js" 中删除这条规则,即开启了这条规则,然后修改不符合规则的代码,对于来不及修改的代码我们写了<a href="https://link.segmentfault.com/?enc=nDcoTnzqEsZTAX02DOeTuA%3D%3D.iS65BmoIufnRUurmaeAE4AlzYbtvknwg3VIN3em8mqxukNsTL7%2BODSZsR7EIljKC" rel="nofollow">脚本</a>将含有不符合规范的文件头部加上 ESLint 注释,关闭规则,下一次再删除头部的注释,然后修改不符合规范的代码。这样做的好处是可以及时将规则开启,避免其他人再次增加新的不符合规范的代码;</p></li>
<li><p>其他:其他代码难度中等、错误个数中等的也要好好改。</p></li>
</ul>
<p>将以上步骤做完改造工作也基本结束了。</p>
<h2>代码维护</h2>
<p>经过代码改造,这时从远程仓库拉下来的代码都是符合代码规范的了,那如何保证新增的代码也全都合格呢?我们主要做了两件事情,一件是推荐大家使用最新的编辑器,比如 Atom 或者 VSCode,两者都有丰富的插件帮助你在写代码过程中就能发现哪些地方不符合规范。另一件事是我们写了一段脚本,在代码 push 到远程仓库时会先运行这段脚本来检查代码的规范性,如果发现有一处不符合规范,则终止上传过程,同时会有错误提示。</p>
<p>经过改造过的代码更加赏心悦目可维护性也高不少,如果你的团队也有同样的问题,那也来尝试改造一下吧。</p>
兴趣部落的前端性能优化实践概览
https://segmentfault.com/a/1190000010511185
2017-08-04T21:49:43+08:00
2017-08-04T21:49:43+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p>本文对兴趣部落项目前端开发中使用到的性能优化方式进行总结。兴趣部落项目是手机QQ(以下简称手Q)中最大的纯网页应用,每日有大量的用户访问,对于腾讯这样一个对产品有着极致要求的公司,性能优化是一个绕不开的话题。下面就对项目中所使用的性能优化的方式做一个梳理。</p>
<h2>离线包</h2>
<p>Hybrid App 最大的一个问题就是页面需要从网络拉取,所以加载速度慢,从而影响用户体验,兴趣部落中使用一个叫做 AlloyKit Mobile 的技术架构,这种技术架构中包含很多有利于提升性能的模块,这里要说的是离线包模块。</p>
<p>离线包模块可以允许手Q 永久缓存 HTML、JS、CSS 等静态资源,从而当用户访问网页时实现“秒开”,统计数据显示,使用离线包能够提升 85% 以上的速度。那有人问,既然这样, 为什么不把所有静态资源一起打个包,然后在 APP 发布时一起包进去呢?其实这样做就失去了 Hybrid App 的一个优势:快速迭代,因为网页中的静态资源的更新必须跟随 APP 更新的节奏,而我们知道 APP 更新频率相比于一般的网页应用是相当慢的,而离线包保持了网页应用快速迭代的优良传统。</p>
<p>那离线包是如何保持快速迭代的优良传统的呢?这要从离线包的更新原理来说了。首先需要事先说明的是,并不是所有的网页都被放入到离线包里的,那样的话,离线包还不得爆炸!一般只会放几个非常重要的网页,比如兴趣部落里就放三个最重要的页面到离线包里。对于这几个最重要的页面,请求资源时会在 URL 中带有 _bid 参数。每次请求带有 _bid 的 URL 时,都会询问客户端是否有离线包,有的话则走离线包,否则正常访问网络。另外,还会发出去一个请求,检查离线包有没有更新,如果离线包有更新,那么下载离线包,以便下次使用。当然内部还会更加复杂,比如会检查上次检查更新的时间,时间间隔太短的话就不会去检查更新,再比如离线包使用了 <a href="https://link.segmentfault.com/?enc=hGEtCaqOyczcGcyeE1IXTg%3D%3D.0rXeDY7HHqtFnkSExNBubnqFSkbI1itJyHLc5Uf%2BjrEktQpcuGKLv8v0OVj%2FBBwg" rel="nofollow">CMEM</a> 做缓存等等。现在你知道为什么手Q 用了一段时间后体积变大了很多吧,逃)当然你可以在手Q 的“设置-聊天记录-清空缓存数据”来清除所有的离线包等缓存资源。</p>
<p>那每次更新是否将离线包里所有的东西都更新呢?非也。比如说这次新增了一个图片,新的离线包只会更新这一张图片,而不会更新所有离线包内容,这在离线包里被称为“增量包”。又有人问,如果是修改一个文件里的一小部分内容呢,那也需要将这一个文件更新吗?亦非也。其实增量包分为两种,一种叫“基于文件的增量包”,刚刚新增一张图片就属于这种情况。还有一种叫“bsdiff 增量包”,它采用二进制方式对比,生成的增量包仅仅包含变化的那一小部分,这使得更新效率更高。</p>
<p>关于离线包还有很多可以聊的,比如刚发布的离线包发现错误应该咋办?现在的策略是用户点击了“兴趣部落”后才会下载离线包,下次再次访问时才能使用离线包,如果我等不及,而想用户第一次就能访问离线包怎么办?还有等等的一系列问题。后面可以再写一篇博客,着重讲解一下离线包。</p>
<h2>资源加载策略</h2>
<p>在资源加载上做优化的根本原因就是网络通信时间占据了响应时间的大部分,很多的性能优化都是从这方面着手的,包括上面的“离线包”策略,但因为“离线包”实在太过重要,所以将之单独抽出来讲。下面讲一讲其他的资源加载策略。</p>
<h3>构建</h3>
<p>因为兴趣部落开始于 2014 年,所以项目采用 grunt 作为构建工具,以及使用 webpack 来打包,最近刚刚升级到 webpack2。我们使用 grunt 来对文件进行 minify、uglify,以及对部分文件进行 concat,从而有效地减小文件的大小以及向服务端请求的次数。简单介绍下上面三种构建的策略,minify 是指去除代码中的空白以及一些多余的字符,比如分号;uglify 从名字上看是“丑化”,处理之后的代码几乎无法阅读,乍一看它是用来混乱代码的,但它其实最大的作用是压缩代码,混乱代码可以使用 obfuscate;concat 就是将几个静态文件合并成一个文件。</p>
<p>另外,还使用 webpack 对文件进行打包,也能够减少向服务端请求的次数。前一段时间发布了 webpack3,其中一个特性就是 Scope Hoisting,简单点介绍就是 webpack2 以及 webpack1 会将每个模块都打包在一个单独的闭包中,这些闭包是拖累浏览器速度的一个因素,而 Scope Hoisting 解决这个问题的方案就是将不同的模块打包在一个大的闭包中,从而提升代码运行效率。不过技术新出,不能完全了解里面的坑,将之正式使用到这样一个大型项目中还需要一段时间。</p>
<h3>资源按需加载</h3>
<p>这个方式很常见,即在打开页面时不必要加载所有内容,而是根据自己的需要来加载。比如下面这个页面:</p>
<p><img src="/img/remote/1460000010511190" alt="" title=""></p>
<p>首页选择最上面的“排行”菜单时,只会加载类目列表以及明星列表的部分内容,当下拉明星列表时,还会根据需要再次向服务器请求列表后面的内容。</p>
<h3>模块按需加载</h3>
<p>还是以上面的图举例,当我点击屏幕下方的“部落“Tab 时,”部落“组件才会被加载,背后是利用 webpack 的 <code>require.ensure()</code> 实现的。</p>
<h3>减少不必要的通信</h3>
<p>很多时候存在多余通信的情况,比如最近我在做的一个“红点需求”,简单点讲就是本来需要向服务器发送大量请求,经过优化,将请求数量减少了到了几乎为 0,这些都可以说是不必要的请求。下一段是对这个过程的详细描述,自己都觉得有些啰嗦,不感兴趣的可以直接略过哈。</p>
<p>看下图,在兴趣部落“我的”面板,“我的” Tab 会根据“留言消息”、“任务领心”和“系统通知”的红点而确定,也就是说只要这三者有一个有红点,那么“我的” Tab 上就会有红点,一般情况下“留言消息”和“系统通知”没有红点,而“任务领心”产品经理给的需求是:有红点时点进去就消除红点,如果之后再做一些加心的操作,那红点重新出现。而加心的操作有很多,在很多页面用户都可能会做一些加心的操作,所以“任务领心”的红点会一会儿出现一会儿消失,紧随着的是“我的” Tab 上的红点也会一会儿出现一会儿消失。问题就出现在这,因为“我的” Tab 是首页的一部分,所以每次进入首页时都要跟服务器请求一次,而首页的 PV 是非常巨大的,相对应的红点状态的请求数量也非常巨大。所以想到的一个策略就是当用户操作加心时,利用 localStorage 设置一个标志位,回到首页时就读取 localStorage 里的标志位,根据这个标志位来判断是否显示红点,当点击“任务领心”时,就把标志位归位。这样,顿时减少了上亿的请求。</p>
<p><img src="/img/remote/1460000010511191" alt="" title=""></p>
<p>上面只是做一个简单的举例,项目中还有很多这样的地方。除此以外,如果一个页面需要发出多个请求,还会根据实际情况合并一些请求。这些细小的改动在请求数量较小时可能没多大影响,但每秒 PV 以万计时,将会使得请求数量大大减少,自然优化了性能。</p>
<h3>缓存</h3>
<p><strong>localStorage</strong>:因为 WebView 的缘故,手Q也能同普通浏览器一样使用 localStorage。项目中有很多场景需要缓存,比如有些功能基于地理位置的,第一次获取到的经纬度数据可以使用 localStorage 缓存起来,当一定时间内再次需要获取地理位置时,直接使用缓存即可,除了缓存地理位置本身,还会缓存根据地理信息获得的数据,经过一定算法计算,在一定范围内可以直接从缓存中获取这些数据,避免重复请求数据;</p>
<p><strong>静态资源缓存</strong>:一般对静态资源的 Response Header 加上 <code>cache-control:max-age=3600</code>以及 <code>expires:***</code>来缓存这些静态资源。</p>
<h3>HTTP/2</h3>
<p><img src="/img/remote/1460000010511192" alt="" title=""></p>
<p>看上面的图片,很多的资源加载的协议已经使用了 HTTP/2,再看 Connection ID 一栏,你会发现有的资源的 Connection ID 是一样的,这说明什么呢?这就是 HTTP/2 中的一个很重要的特性:多路复用。除了多路复用,当然还利用了 HTTP/2 的一些其他特性。</p>
<h3>图片资源加载</h3>
<p>图片占据了绝大部分的带宽,所以优化图片对性能优化作用巨大,我们在项目中用到了以下技术:</p>
<p><strong>Base64</strong>:将一些小的、程序中用到的图片用 Base64 表示,减少请求次数;</p>
<p><strong>WebP</strong>:一些图片使用 WebP 格式,使得图片压缩地更小;</p>
<p><strong>sharpP</strong>:腾讯自己的一套压图方案,帧压缩效率比 WebP 高 31%,比 jpeg 高 43%。</p>
<h3>DNS 预获取</h3>
<p>网络请求中域名解析一般会花费很大部分时间,而加了 <code>dns-prefetch</code> 可以提前去解析资源的域名,这样可以减少网络请求时间。</p>
<pre><code class="html"><meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="http://www.spreadfirefox.com/"></code></pre>
<p>可以在 HTML 页面中加入上面的代码来使用 DNS 预获取功能,有的支持 <code>dns-prefetch</code> 的浏览器并不需要第一行代码。</p>
<h2>直出</h2>
<p>目前业界普遍的做法就是前后端分离,服务端提供数据,客户端根据数据渲染页面,前后端分离在这里其实也就是数据与渲染逻辑分离。直出,几乎等同于服务端渲染,是在服务端将数据通过渲染逻辑生成一个页面,然后将生成的页面直接传给客户端。为什么这种方式会比客户端渲染有更高的性能呢?看两张图:</p>
<p><img src="/img/remote/1460000010511193" alt="" title=""><img src="/img/remote/1460000010511194" alt="" title=""></p>
<p>(图片来源:<a href="https://link.segmentfault.com/?enc=XX8kBSTqAecjxzxnRcsg7A%3D%3D.N3wGNztoas7XVJX0H0JrSmPVMqdSIQM8oC%2BDltZ6GJ7QQ7zvtjopxpDBUOIZo8gYxUViU3QiqWz%2FslDFqWgssw%3D%3D" rel="nofollow">InfoQ</a>)</p>
<p>上面两张图分别是服务端渲染(Server Side Rendering, SSR)和客户端渲染(Client Side Rendering)。我们仔细看一下图中请求资源到页面展示的一个简单过程。在 SSR 中,服务端接收到请求后就在服务端将 HTML 页面生成好,然后将之返回给客户端,客户端拿到完整的 HTML 很快便能够将页面渲染出来,用户便能看到页面,与此同时,客户端也在拉取 JS 等其他资源,当 JS 拉取到本地并执行完后,页面就变得可交互了。而客户端的过程是将数据、JS 等资源拉取到本地,由本地执行 JS,然后渲染页面,渲染出的页面可以立即交互。对比上面两种渲染方式可以看出,服务端渲染以客户看到页面为第一要务,也就是很多公司考核的首屏加载时间,交互可以放在次要的位置。</p>
<p>在兴趣部落中,我们利用“玄武”框架来逐渐实现服务端渲染。玄武为古代四大神兽之一,是乌龟和蛇的合体,乌龟象征长寿、稳重,蛇象征灵敏,命名寄托了开发者想把它做成一个即稳定又灵活可拓展的 Web 应用框架的希冀。玄武框架基于 koa,可以使得开发者只需要关注业务逻。</p>
<h2>合并上报</h2>
<p>但凡线上项目,没几个不上报数据的,上报数据就会进行网络通信,而数据上报跟性能息息相关,如何处理数据上报便是一个很重要的问题。兴趣部落同样也涉及很多上报,比如老板关心的 DAU(Daily Active User,日活跃用户数量),产品经理关心的 PV(Page View,页面浏览量)、UV(Unique Visitor,独立访客),运营关心的引流,开发关心的脚本错误量等等,那这些数据从哪里来?都是通过数据上报。而兴趣部落项目访问量巨大,现在日常 PV 达数十亿,每一次 PV 都会伴随数个上报请求,粗略计算,上报请求也要达上百亿。兴趣部落采取的做法是合并上报,前端收集上报请求,而不是立即发送,将这些上报放到一个队列中,延时对这些队列里的请求参数做压缩,生成一个统一的 URL,再将之发送至服务器中。采用这种方式后,请求次数减少超过 80%,流量也节省了 70%。</p>
<p>以上仅仅简单描述了这些实践的大概原理,对于每一种实践都可以单独抽出来述以长篇大论。因为实习不久,对团队项目还不是完全了解,以后再发现有什么好的实践就继续补充。如果你的团队有什么好的性能优化实践,希望不吝留言。</p>
兴趣部落的 Git 迁移实践
https://segmentfault.com/a/1190000010497387
2017-08-04T09:09:44+08:00
2017-08-04T09:09:44+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p>因为历史原因,刚进小组时,组内主要利用 SVN 作为兴趣部落的代码托管工具,最近组里重新定义了一套代码 Review 规范,而 SVN 不能满足我们的需求,再加上公司的 Git 平台已经成熟可用,我们便萌生了将兴趣部落的代码从 SVN 迁移到 Git 上的念头。经过几天的努力,现在代码已迁移完成,下面就把过程与总结写下来供大家参考。</p>
<h2>方法1</h2>
<p>先介绍一个简单粗暴的方法,TortoiseSVN 有自带的 Export 功能,可以将 SVN 的代码全部导出到一个新的文件夹下。下面简单描述下步骤,以 Github 为例。</p>
<p>先在 Github 上建一个 repository,并将之 clone 到本地,假设文件夹叫 A。然后找到你要迁移的 SVN 项目,右击选择 TortoiseSVN -> Export,选择 A 文件夹,确定后 SVN 项目中的代码便到了 A 文件夹中,下面就可以将这个项目当做一般的 Git 项目来对待了。</p>
<p><img src="/img/remote/1460000010497392" alt="" title=""></p>
<p>说这个方法简单粗暴,是因为这个方法其实跟将整个 SVN 项目的代码直接复制粘贴到 Git 项目文件夹下没多大差别,并且这次到 Github 上的提交会被当成整个项目的第一次提交,也就是以前的提交记录没有被迁移过来。</p>
<h2>方法2</h2>
<p>如果你想做的完美一点,不仅迁移代码,还要迁移以前的提交记录,那应该怎么做呢?那就要用到 git-svn 了,详细文档请参考:<a href="https://link.segmentfault.com/?enc=pPoRSxtXDP8T2XOgzDDXPA%3D%3D.x0lQusVpMg5xSqtU%2BPIXr4Z8JtVNngaGH3%2BH2qxSozLgYfy7Jr1h6FxqzM26%2FysD" rel="nofollow">git-svn</a>。</p>
<p>使用 <code>git svn clone your-svn-url your-target-folder</code> 可以将 SVN 的主干代码以及提交记录拉取到了本地目标文件夹 your-target-folder 中。这种方式只能迁移一个特定文件夹的代码(一般迁移 trunk)。但如果你不仅想迁移主干代码,还想将 branch 和 tag 的代码一并迁移,你可以使用: <code>git svn clone root-path --trunk="trunk/project" --branches=“branches” --tags="tags"</code>。需要注意的是,trunk、branches 以及 tags 的路径是相对的。如果你的项目很庞大并且有很长的历史,那么迁移过程将会非常漫长(以天为单位)。如果你迁移过程中很幸运没遇见程序出错、电脑崩溃而最终成功的话,你可以执行 <code>git branch -a</code> 看看分支情况。因为我们没有迁移 branch 和 tag,所以不了解有什么坑。</p>
<p>新的文件夹是没有 <code>.gitignore</code> 文件的,所以需要新建一个。</p>
<p>当准备工作都做好后,下面可以将代码提交到远程仓库中:</p>
<pre><code class="bash">git remote add origin your-git-repository
git push -u origin master</code></pre>
<h3>用户对应</h3>
<p>如果 SVN 的账号和 Git 的账号名称不一样,并且 SVN 中的账户就是一个 ID,而 Git 中的账号是 username 和 email,SVN 的 ID 可以和 Git 的 username 和 email 在迁移过程中对应起来,具体做法如下:</p>
<pre><code>// 在 SVN 项目根目录:
svn log --xml | grep author | sort -u | perl -pe 's/.>(.?)<./$1 = /'</code></pre>
<p>输出一列 SVN 用户列表,将之保存到 users.txt 中,并且修改账号对应关系,格式如下:</p>
<pre><code>zhangsan = 张三 <zhangsan@tencent.com>
...
wangwu = 王五 <wangwu@tencent.com></code></pre>
<p>上面的 <code>zhangsan </code> 就是 SVN 的 ID,后面对应的就是 username 和 email。有了 users.txt 的账号对应信息,下面可以迁移了:</p>
<pre><code>git svn clone your-svn-url --authors-file=users.txt your-target-folder</code></pre>
<p>也就是在迁移命令中加入 <code>--authors-file</code> 参数。</p>
<p>整体来看,迁移过程很顺利,没有很复杂的步骤。如果你在迁移中有什么困惑,可以去<a href="https://link.segmentfault.com/?enc=Gt%2F%2BF%2B0Q4n%2BC%2BHeEHf6NxQ%3D%3D.nlMW77lB1Y2XGoHem8P1SDjsu%2FuE8M0%2Fy%2FzkhLJi%2BRSoygTjxZnTgOigtumUJSMO" rel="nofollow">官网</a>上瞧瞧。</p>
<p></p>
详解 DOMContentLoaded
https://segmentfault.com/a/1190000008759412
2017-03-20T12:12:59+08:00
2017-03-20T12:12:59+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
4
<h2>详解 DOMContentLoaded</h2>
<p>首先我们先直观地感受下什么是 DOMContentLoaded。打开 Chrome DevTools,切到 Network 面板,重新加载网页,得到如下截图:</p>
<p><img src="/img/bVKUSK?w=1440&h=285" alt="图片描述" title="图片描述"></p>
<p>标记 1 指向的蓝线以及标记 2 指向的蓝色字 “DOMContentLoaded:1.29s” 均表示 DOMContentLoaded 这个事件触发的时间,只不过表现形式不同而已。</p>
<p>直观地感受了 DOMContentLoaded,那它究竟是个什么东东呢?</p>
<h3>什么是 DOMContentLoaded</h3>
<p>有兴趣的可以看下 <a href="https://link.segmentfault.com/?enc=algfVVENi66MoB%2BMa2F1BA%3D%3D.NWaM%2FHwSRActZreIJS1WwVZpE5MzvCTG8wdy6QQpbgARrrfwSdixwrGUam2i73hT" rel="nofollow">W3C 的 HTML5 规范</a>是如何描述 DOMContentLoaded 的,不感兴趣也没关系,我们继续往下走。那么我们可以看一下 <a href="https://link.segmentfault.com/?enc=4plYpknqBm0yohZIGCgiVw%3D%3D.ScPcKk2KM0LcunoSYZlHDSKb7ZJD97Oee9Nl%2Bc0Ryc4e5agQH0Lj6jjxnYjWE%2FOWqe3Qy%2BJa8Mylabga%2F6%2FeoXtYKCffGF0V9wJFxuPy2Wc%3D" rel="nofollow">MDN 对 DOMContentLoaded 的描述</a>,啥,你还是不感兴趣啊?也没关系,接触一个新概念时,没人喜欢一上来就看那么晦涩的文字的。那我下面就用通俗的语言跟你聊聊什么是 DOMContentLoaded。</p>
<p>我们先来思考一个问题,如何衡量一个网页的加载速度?</p>
<p>有人说可以用网页加载完全的时间来衡量。我觉得这没有问题,但不够好。为什么呢?在日常生活中,很多时候因为网络原因我们并不需要等待网页上的所有内容都加载完毕,而是只需要加载主要内容就可以了,比如你打开这篇博客时,可能并不需要等所有图片都加载完成,而是看到博客的正文就可以正常阅读了。把上面的话提炼一下就是,用户有时候只需要在空白的网页上看见内容就可以了,而不需要等待所有内容都加载出来。那既然这样,回到刚刚的问题,我觉得衡量一个网页加载速度的一个方法就是“计算这个网页从空白到出现内容所花费的时间”。那怎么计算这段时间?<a href="https://link.segmentfault.com/?enc=k3hRg5fGKTeOjY4HT%2B2LIg%3D%3D.hFvdEHXbpfYB7Fqb10awrFzPkNbon2fwYELGU2Uk6Is09CwmVKyQkO3jGnH37MxU" rel="nofollow">HTML5 规范</a>已经帮我们完成了相应的工作,就是当一个 HTML 文档被加载和解析完成后,DOMContentLoaded 事件便会被触发。</p>
<p>这时问题又来了,“HTML 文档被加载和解析完成”是什么意思呢?或者说,HTML 文档被加载和解析完成之前,浏览器做了哪些事情呢?那我们需要从浏览器渲染原理来谈谈。</p>
<p>浏览器向服务器请求到了 HTML 文档后便开始解析,产物是 DOM(文档对象模型),到这里 HTML 文档就被加载和解析完成了。如果有 CSS 的会根据 CSS 生成 CSSOM(CSS 对象模型),然后再由 DOM 和 CSSOM 合并产生渲染树。有了渲染树,知道了所有节点的样式,下面便根据这些节点以及样式计算它们在浏览器中确切的大小和位置,这就是布局阶段。有了以上这些信息,下面就把节点绘制到浏览器上。所有的过程如下图所示:</p>
<p><img src="/img/bVKUSS?w=626&h=155" alt="图片描述" title="图片描述"></p>
<p>现在你可能了解 HTML 文档被加载和解析完成前浏览器大概做了哪些工作,但还没完,因为我们还没有考虑现在前端的主角之一 JavaScript。</p>
<p>JavaScript 可以阻塞 DOM 的生成,也就是说当浏览器在解析 HTML 文档时,如果遇到 <code><script></code>,便会停下对 HTML 文档的解析,转而去处理脚本。如果脚本是内联的,浏览器会先去执行这段内联的脚本,如果是外链的,那么先会去加载脚本,然后执行。在处理完脚本之后,浏览器便继续解析 HTML 文档。看下面的例子:</p>
<pre><code class="html"><body>
<script type="text/javascript">
console.log(document.getElementById('ele')); // null
</script>
<div id="ele"></div>
<script type="text/javascript">
console.log(document.getElementById('ele')); // <div id="ele"></div>
</script>
</body></code></pre>
<p>另外,因为 JavaScript 可以查询任意对象的样式,所以意味着在 CSS 解析完成,也就是 CSSOM 生成之后,JavaScript 才可以被执行。</p>
<p>到这里,我们可以总结一下。当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等 CSSOM 构建完成才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。</p>
<h3>异步脚本</h3>
<p>我们到这里一直在说同步脚本对网页渲染的影响,如果我们想让页面尽快显示,那我们可以使用异步脚本。HTML5 中定义了两个定义异步脚本的方法:defer 和 async。我们来看一看他们的区别。</p>
<p><img src="/img/bVKUSU?w=600&h=373" alt="图片描述" title="图片描述"></p>
<p>同步脚本(标签中不含 async 或 defer): <code><script src="***.js" charset="utf-8"></script></code></p>
<p>当 HTML 文档被解析时如果遇见(同步)脚本,则停止解析,先去加载脚本,然后执行,执行结束后继续解析 HTML 文档。过程如下图:</p>
<p><img src="/img/bVKUSX?w=600&h=55" alt="图片描述" title="图片描述"></p>
<p>defer 脚本:<code><script src="***.js" charset="utf-8" defer></script></code></p>
<p>当 HTML 文档被解析时如果遇见 defer 脚本,则在后台加载脚本,文档解析过程不中断,而等文档解析结束之后,defer 脚本执行。另外,defer 脚本的执行顺序与定义时的位置有关。过程如下图:</p>
<p><img src="/img/bVKUSY?w=600&h=55" alt="图片描述" title="图片描述"></p>
<p>async 脚本:<code><script src="***.js" charset="utf-8" async></script></code></p>
<p>当 HTML 文档被解析时如果遇见 async 脚本,则在后台加载脚本,文档解析过程不中断。脚本加载完成后,文档停止解析,脚本执行,执行结束后文档继续解析。过程如下图:</p>
<p><img src="/img/bVKUSZ?w=600&h=55" alt="图片描述" title="图片描述"></p>
<p>(图片来源:<a href="https://link.segmentfault.com/?enc=cMznkkmYx8Lw70%2FXbL%2Fl%2BA%3D%3D.h7kEfIcmEtbHCiOtBji7wVYZjP%2FZN3llgfxoOhHPdQuRhBhdTI%2BE%2BHzEpAEhhhyBBldOrr8OLh4CleHxU%2FSYp20AulNnoHxVRShRFLJVy2M%3D" rel="nofollow">async vs defer attributes</a>)</p>
<p>如果你 Google "async 和 defer 的区别",你可能会发现一堆类似上面的文章或图片,而在这里,我想跟你分享的是 async 和 defer 对 DOMContentLoaded 事件触发的影响。</p>
<p><strong>defer 与 DOMContentLoaded</strong></p>
<p>如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。 所以这意味着什么呢?HTML 文档解析不受影响,等 DOM 构建完成之后 defer 脚本执行,但脚本执行之前需要等待 CSSOM 构建完成。在 DOM、CSSOM 构建完毕,defer 脚本执行完成之后,DOMContentLoaded 事件触发。</p>
<p><strong>async 与 DOMContentLoaded</strong></p>
<p>如果 script 标签中包含 async,则 HTML 文档构建不受影响,解析完毕后,DOMContentLoaded 触发,而不需要等待 async 脚本执行、样式表加载等等。</p>
<h3>DOMContentLoaded 与 load</h3>
<p>在回头看第一张图:</p>
<p><img src="/img/bVKUSK?w=1440&h=285" alt="图片描述" title="图片描述"></p>
<p>与标记 1 的蓝线平行的还有一条红线,红线就代表 load 事件触发的时间,对应的,在最下面的概览部分,还有一个用红色标记的 "Load:1.60s",描述了 load 事件触发的具体时间。</p>
<p>这两个事件有啥区别呢?点击这个网页你就能明白:<a href="https://link.segmentfault.com/?enc=hk83EknGNM77%2FtxE7AMCjw%3D%3D.hs0GBSOSh82990v%2FRrGFzNtxL7IM%2FRlzN%2FdOfz%2FZVZuqEYNnpak%2BUaJ1F587msFqIk4UbODf5bsF2K91cvD7wEsu1V5t0vkmqOqXVa7w4EE%3D" rel="nofollow">https://testdrive-archive.azu...</a></p>
<p>解释一下,当 HTML 文档解析完成就会触发 DOMContentLoaded,而所以资源加载完成之后,load 事件才会被触发。</p>
<p>另外需要提一下的是,我们在 jQuery 中经常使用的 <code>$(document).ready(function() { // ...代码... });</code> 其实监听的就是 DOMContentLoaded 事件,而 <code>$(document).load(function() { // ...代码... });</code> 监听的是 load 事件。</p>
一篇文章让你知道被 Google 攻破的 SHA-1 是什么
https://segmentfault.com/a/1190000008570768
2017-03-04T23:04:26+08:00
2017-03-04T23:04:26+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p>前段时间被一个重大的新闻刷屏了,那就是 Google 攻破了 SHA-1。很多人看了后的第一反应大概就是:哦,知道了~ 很多事情看起来跟我们无关,实际上却对我们产生了巨大的影响,比如川普当选总统,我们很多人总觉得这事离我们很遥远,但其实就像一滴水滴落水中,很快便会波及四周。Google 攻破 SHA-1 这事也是,看似跟我们无关,其实与我们息息相关。鸡汤不说了,下面我们具体地聊一聊这个 SHA-1。</p>
<p>我们先从 HTTP 协议讲起。搞前端的绝大多数对此并不陌生,HTTP 协议就是在网络中用于客户端和服务器端之间通信的一种协议。所谓协议,就是一种约定,约定通信的双方由谁发起通信,信件应该发往何处,信件的格式什么样,怎样结束通信等等事项。就好比一个人要给另一个人写信,首先要确定信应该寄到哪里,再确定信的格式怎么写才能让对方看得懂以及怎么确认对方收到信等等问题,这些都是 HTTP 协议所规定的。</p>
<p>有了这些约定,我们客串之一赵铁柱按照约定写好信件打算寄给李小花,下面信件就上路了。这个时候,问题来了...</p>
<ul>
<li><p>这封信件可能半路被张全蛋给偷看了,那可是满满的羞羞的情话啊!</p></li>
<li><p>这封信件可能被张全蛋修改了,信的结尾坦白自己其实喜欢男的...</p></li>
<li><p>李小花收到了信,但她怎么能确定这是赵铁柱写的,而不是张全蛋或者其他人写的呢?(这个问题这里不谈)</p></li>
</ul>
<p>好在赵铁柱进厂组装手机前干过锁匠,他便想出了一个办法。</p>
<p>他发明了一种“鸳鸯锁”,这种锁高级了,有两把钥匙,A 钥匙、B钥匙,这把锁如果用 A 钥匙锁上的话只能用 B 钥匙打开,而用 B 钥匙锁上的话只能用 A 钥匙打开。这样,他也为李小花做了一把鸳鸯锁,并把 A 钥匙(私钥)留给了李小花自己,而把 B 钥匙(公钥)拿了过来,每次他写信时就用 B 钥匙把信件加密,而解密的钥匙,即钥匙 A 只有李小花有,这样就保证了安全。反过来,李小花想发信件,就用赵铁柱的公钥加密,然后赵铁柱用自己的私钥解密。</p>
<p>这样还不够,赵铁柱还担心信在中途被人篡改了,好在他干锁匠前也干过码农,于是他又想到一种方法。(请注意,下面我们今天的主人公登场)</p>
<p>他用计算机写了个算法,这个算法的特点就是根据不同的内容生成一串字符,就是说当内容一样时,生成的字符一定是一样的,而只要内容发生一丁点改变,生成的字符串就完全变了样。举个例子,你们感受下:</p>
<pre><code>我爱你:5890a73fed38bf09622c34ad9391f1d09c0ec100
我不爱你:39e22987f658c46a8eab02e6302dc980d9236014</code></pre>
<p>这样赵铁柱每次发信件前会用这个算法根据自己信的内容生成一个字符串,然后把这个字符串也用上面的加密的方式发送给李小花。这样,李小花收到了两样东西,一个是信本身,还有一个是赵铁柱根据信件原文生成的字符串。李小花用赵铁柱给他的这个算法根据她收到的信也生成一个字符串,然后跟收到的字符串比对,如果一样,那基本肯定这封信的内容没有被人给篡改过。</p>
<p>这样, 赵铁柱和李小花过上了安安静静谈恋爱的生活~张全蛋却孤独终老...(画外音:找个会写代码的男票多重要!)</p>
<p>然而,我们的故事才刚刚开始。</p>
<p>上面赵铁柱发明的这个算法叫做 SHA-1,属于 <a href="https://link.segmentfault.com/?enc=Zf1JnycOKRtbOHb7v9pfew%3D%3D.fXRsvI2SGe3LOmDfNWi%2BqyDL5F5zhas%2FQVZvpWGjvkBBA2CuzzWD2zLE62F0z7L2SonfqGF0O%2FshoI5VvC8LYA%3D%3D" rel="nofollow">SHA 家族</a>,SHA-1 是一个 <a href="https://link.segmentfault.com/?enc=3ImRWsMRQIa05jFmQWfdaQ%3D%3D.rcDIoTbdFjBtmQl4nD%2BtOuGwkFCtIPukwE2NmwX4LmCHIqaNrnC5W%2BbUsQlJ5cbhPGNV6QuKJ9IFu1PBt3qTDQ4DAqEcRMf6rOk%2BuxZg8Wc%3D" rel="nofollow">Hash 函数</a>,在这个使用场景下叫做<a href="https://link.segmentfault.com/?enc=tZaEzNm6N%2FZwvETNW3PBmQ%3D%3D.%2FWcNW%2F3%2BUCV%2BvBbbCh7dYnZNY1ZzLP1PkOBwAmBG6VfRc14l9whffFn1DKTaj5qRb1WQMXMuc%2FUqQQsINc8P8oxdHZpXWlbML%2FHuV4hAwew%2FbO%2FqXUClr3o80yoy8bHl" rel="nofollow">密码散列函数</a>。</p>
<p>SHA-1 的特点就是:1)特定的内容生成同样的结果(一个固定长度的字符串);2)内容不一样则生成的不一样的结果。但是,这仅仅是理想情况罢了。特点 1 是没有争议的,但特点 2 真的在所有情况下都这样吗?有没有一种可能就是,两个不一样的内容生成的结果是一样的呢?答案是有的。这种情况还有个专有名词,“哈希碰撞”,就是用两个不一样的内容刻意算出两个同样的 Hash 结果。报道中,“SHA-1 被攻破”的意思就是说找到了两个不同的内容,用 SHA-1 算法计算之后的 Hash 值是一样的。其实不仅仅是 SHA-1 算法,包括现在常用的 SHA-256、SHA-384 等等算法都可能产生哈希碰撞。但是为什么说 SHA-1 被攻破而不是 SHA-256 被攻破呢?因为需要考虑当今计算机的计算能力,如果用当今计算机算 100 年才能发现某哈希算法的哈希碰撞,那毫无疑问这个哈希算法是安全的,这也就是 SHA-1 前几年是安全的,因为前几年的计算能力还不足够快到很快能制造哈希碰撞。那问题来了,这“快”到底是多快呢?</p>
<p>SHA-1 的输出是 40 个字符组成的字符串,在计算机中占 160 bit,那么如果想找出一组碰撞的话,那就需要选 (2^160 + 1) 组不同的数据来计算他们的 Hash 值,根据抽屉原理这很好理解,13 个人当中肯定有两个人的生日在同一个月。但是 2^160 是个什么概念呢,地球上的沙子差不多 <a href="https://link.segmentfault.com/?enc=4aVjuKF8VhtDVqGZXt8tLg%3D%3D.PdLTF%2FE8qOJRgWGAXysiy%2BBVP0KM0D9tfYlYQUTFqR3dR%2BfNsRR6a%2FZSwY6uy2gv" rel="nofollow">2^60 个</a>,乖乖。但如果我们不要求 100% 能找到一组碰撞,我们只要求 50%,那要算多少组呢?根据概率计算,大约需要计算 2^80 组,密码学上规定,如果有一种方法能够在计算时间小于 2^80 内 50% 的可能性找到一组碰撞,那么这种哈希算法就不安全了。</p>
<p>但是,不得不提的是,我国的密码学家王小云在 2005 年找到了一个快速的攻击方法,使得能够在 2^69 时间内大概率找到碰撞(此处应该有掌声)。之后又有人发现其他的攻击方法,使得时间缩减到 2^57.5。但问题来了,SHA-1 原来早就被攻破了,那为毛这次又说被 Google 攻破了呢?因为以前碰撞的结果可能是两个没有意义的内容,但这次 Google 构造了两个有意义但内容不同的文件。两个文件长这样:</p>
<p><img src="/img/bVJ7Of?w=2133&h=1541" alt="图片描述" title="图片描述"></p>
<p><img src="/img/bVJ7Oj?w=2133&h=1541" alt="图片描述" title="图片描述"></p>
<p>文件可以在<a href="https://link.segmentfault.com/?enc=wW20FCM8wMoaEZn9zfEYjw%3D%3D.HGA5gBSE9UP4pDHDV8eGMDYAg5izWquTI7RFTZ7bj9M%3D" rel="nofollow">这里</a>下载,你可以用<a href="https://link.segmentfault.com/?enc=KJXIT53xNPsO1WnK1VDcaw%3D%3D.XlRRvog5E04wtzzDfuYYnzQrhB%2Fgsbylh3%2Btd1Nm%2BLOav9CVilpnhpIVQ%2FcK50ma" rel="nofollow">在线的 SHA-1 工具</a>测试。</p>
<p>这就厉害了,那有可能你已经加密的合同上写的是 100 万,结果有人构造了一个 1000 万的订单但 Hash 值一样,你到了法院直呼冤屈。</p>
<p>我们普通人虽然没有大单子,但毕竟牵涉到我们的私密,就问你们怕不怕?嘿,不过也不用怕,现在业界正在逐渐用更安全的算法替换 SHA-1。</p>
<p>参考:</p>
<ol><li><p><a href="https://link.segmentfault.com/?enc=V7gs7oaYnZa%2Fh6bLVOfY9Q%3D%3D.bJ%2BkhVJ7JDaFxnJoTlPdLZ2HToDEL2mPcvH0BTVcRpe9uAiBE%2BpsQGl5nMP4nNbVIzBvCyvvBThtZeY5RHJT1w%3D%3D" rel="nofollow">https://www.zhihu.com/questio...</a></p></li></ol>
HTML5 中的 b/strong、i/em 详解
https://segmentfault.com/a/1190000008344518
2017-02-14T11:25:08+08:00
2017-02-14T11:25:08+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
1
<p>这篇文章主要聊一聊 HTML5 中的 <code><b></code> 和 <code><strong></code>,以及 <code><i></code> 和 <code><em></code>。</p>
<p>从页面显示效果来看,被 <code><b></code> 和 <code><strong></code> 包围的文字将会被加粗,而被 <code><i></code> 和 <code><em></code> 包围的文字将以斜体的形式呈现。那大家可能就会疑惑了,既然效果一样,那为什么还要重复定义标签呢?这就要从 HTML5 的一个最大的特性 -- 语义化来谈了。</p>
<p>不得不说,<code><b></code> 和 <code><i></code> 创建之初就是简单地表示粗体和斜体样式,但现在是 HTML5 的天下。语义化是 HTML5 最大的特性之一,而所有被 HTML5 保留的标签都带有其特有的语义,<code><b></code> 和 <code><i></code> 也不例外,它们分别被重新赋予了语义。相比较而言,标签的样式反而变得无足轻重,所以上面所讲的两组标签,虽然样式上表现极其相似,但其实语义上各有侧重。</p>
<p>下面就来一一介绍这 4 个标签。</p>
<h2><code><i></code></h2>
<p>根据 W3C 对 <a href="https://link.segmentfault.com/?enc=jTpDIbrkdwtoFWNTn2vJdg%3D%3D.akYtSSR4El3E5oCKHjN3%2FoTMgRf%2BIMXbirWkn5K2pJzpTtCShVJiVko4IGiWH3UO8ayPvvOe60rynexrjhGKytAi5B%2B1P6q6vfcUiPNnmZw%3D" rel="nofollow"><code><i></code></a> 的定义:</p>
<blockquote><p>The i element represents a span of text in an alternate voice or mood, or otherwise offset from the normal prose in a manner indicating a different quality of text, such as a taxonomic designation, a technical term, an idiomatic phrase from another language, transliteration, a thought, or a ship name in Western texts.</p></blockquote>
<p>翻译一下就是:</p>
<blockquote><p>i 元素代表在普通文本中具有不同语态或语气的一段文本,某种程度上表明一段不同特性的文本,比如一个分类学名称,一个技术术语,一个外语习语,一个音译,一个想法,或者西方文本中的一艘船名。</p></blockquote>
<pre><code class="html">// 分类学名称
<p>The <i class="taxonomy">Felis silvestris catus</i> is cute.</p>
// 术语
<p>The term <i>prose content</i> is defined above.</p>
// 外语习语
<p>There is a certain <i lang="fr">je ne sais quoi</i> in the air.</p></code></pre>
<h2><code><b></code></h2>
<p>根据 W3C 对 <a href="https://link.segmentfault.com/?enc=I4VzLSM6w8HPQpA0tT7ADQ%3D%3D.4qlPlTOm8oPMvSymjYddlZk1KCISGqo6Jfbobntz3dGCoQbABJu5BhjVf6FrZ7IkDaAoLDLAMAOwKqZUTBR831W96GL0WODxYSqS32rUDqI%3D" rel="nofollow"><code><b></code></a> 的定义:</p>
<blockquote><p>The b element represents a span of text to which attention is being drawn for utilitarian purposes without conveying any extra importance and with no implication of an alternate voice or mood, such as key words in a document abstract, product names in a review, actionable words in interactive text-driven software, or an article lede.</p></blockquote>
<p>翻译一下就是:</p>
<blockquote><p>b 元素代表侧重实用目的而不带有任何额外重要性也不暗示不同语态或语气的一段文本,比如一段文本摘要中的关键词、一段审查中的产品名称、文本驱动软件中的可执行语句或者一篇文章的导语。</p></blockquote>
<pre><code class="html">// 下面的 b 元素起到突出关键词的作用,但不具备强调重要性的作用
<p>The <b>frobonitor</b> and <b>barbinator</b> components are fried.</p>
// 下面的 b 元素让被包围的词特殊化
<p>You enter a small room. Your <b>sword</b> glows
brighter. A <b>rat</b> scurries past the corner wall.</p>
// 下面的 b 元素标注了文章的导语
<article>
<h2>Kittens 'adopted' by pet rabbit</h2>
<p><b class="lede">Six abandoned kittens have found an
unexpected new mother figure — a pet rabbit.</b></p>
<p>Veterinary nurse Melanie Humble took the three-week-old
kittens to her Aberdeen home.</p>
...
</article></code></pre>
<h2><code><em></code></h2>
<p>根据 W3C 对 <a href="https://link.segmentfault.com/?enc=stqOWGtzHhfD3CYasoxPvg%3D%3D.R7x%2BXzVHHwrcvkNtJ16EsIFfwr8R6fRI%2BzWvy7%2FVJBp4HO3rwSb4TgOwaFGLgtaTMaUke93JOJjQaZ4RjFMMtY2Jq18XJxmH6f6GD4Bq%2BGI%3D" rel="nofollow"><code><em></code></a> 的定义:</p>
<blockquote><p>The em element represents stress emphasis of its contents.</p></blockquote>
<p>翻译一下就是:</p>
<blockquote><p>em 元素代表对其内容的强调</p></blockquote>
<p>强调位置的不同通常会带来整个句子含义的变化。看下面举的例子:</p>
<pre><code class="html">// 这是一句不带任何强调的句子
<p>Cats are cute animals.</p>
// em 包围 Cats,强调猫是种可爱的动物,而不是狗或者其他动物
<p><em>Cats</em> are cute animals.</p>
// em 包围 are,代表句子所说是事实,来反驳那些说猫不可爱的人
<p>Cats <em>are</em> cute animals.</p>
// em 包围 cute,强调猫是一种可爱的动物,而不是有人说的刻薄、讨厌的动物
<p>Cats are <em>cute</em> animals.</p>
// 这里强调猫是动物,而不是植物
<p>Cats are cute <em>animals</em>.</p></code></pre>
<h2><code><strong></code></h2>
<p>根据 W3C 对 <a href="https://link.segmentfault.com/?enc=JlfobDBZM4sY%2B0lKhTYikw%3D%3D.FThh82sO36WluY0IBP6ID0Q5TqxHbXtg9Wx0twzL%2BbXfP5FRA%2Bu%2BDJU7%2F7evIV7JxUn%2BNO%2FQ3wJMZBjg%2BR06LxnYsVl39Q8kXq7zRFSp1kY%3D" rel="nofollow"><code><strong></code></a> 的定义:</p>
<blockquote><p>The strong element represents strong importance, seriousness, or urgency for its contents.</p></blockquote>
<p>翻译一下就是:</p>
<blockquote><p>strong 元素代表内容的强烈的重要性、严重性或者紧急性。</p></blockquote>
<h3>重要性</h3>
<p><code><strong></code> 元素可以被用在标题(heading)、说明(caption)或者段落(paragraph)上,来显示这部分被包围的文字的重要性。</p>
<pre><code class="html">// 章节序号不重要,章节的名字才重要
<h1>Chapter 1: <strong>The Praxis</strong></h1></code></pre>
<h3>严重性</h3>
<p><code><strong></code> 元素可以被用来标记警告或者警示标志。</p>
<pre><code class="html"><p><strong>Warning.</strong> This dungeon is dangerous.</p></code></pre>
<h3>紧急性</h3>
<p><code><strong></code> 元素可以被用来表示需要被尽快看见的部分。</p>
<p>需要注意的是,<code><strong></code> 元素仅仅对文本内容的重要性、严重性或紧急性产生作用,而不像 <code><em></code> 对句子含义进行改变。</p>
<pre><code class="html"><p>Welcome to Remy, the reminder system.</p>
<p>Your tasks for today:</p>
<ul>
<li><p><strong>Turn off the oven.</strong></p></li>
<li><p>Put out the trash.</p></li>
<li><p>Do the laundry.</p></li>
</ul></code></pre>
<h2>小结</h2>
<p><code><em></code> 用于对文本内容进行强调,强调位置的不同通常会改变句子的含义。如果仅仅在语态或语气上为了突出某一个文本,那应该使用 <code><i></code>。但如果为了突出某一部分的重要性、严重性或紧急性,那应该使用 <code><strong></code>。根据 W3C 对 <a href="https://link.segmentfault.com/?enc=%2B3TxilfDga2NftFgLI2mDw%3D%3D.IVGNwrT97MkB5OWQCvMc996Oib%2BT39mTTCQUDA4nVhRhj%2BAfvn7%2Bt0lY0KuQ%2FO%2FXi5SNAVA2LZzoUbm15qCsXIjGDG3FfqWWz6Ab14VbZy8%3D" rel="nofollow"><code><b></code></a> 元素的说明,<code><b></code> 元素应当是在其他标签都不合适的情况下最后一个考虑使用的标签。相同的,在考虑使用 <code><i></code> 之前,也要想想是否用 <code><em></code>、<code><strong></code>、<code><dfn></code> 或 <code><mark></code> 等元素更合适。</p>
<p><strong>结语</strong></p>
<p>人类的语言真是个伟大的发明,为数不多的单词、文字的组合便能组合成数不胜数、含义千差万别的句子,而同一个句子又可能因为不同的语音语调导致含义天壤之别,这同时也是自然语言处理的瓶颈之处。正因为如此,HTML5 才会定义那么多像 b/strong、i/em 这样差别微小的标签吧。</p>
前端开发者的 Chrome extensions/apps
https://segmentfault.com/a/1190000008127810
2017-01-15T20:35:48+08:00
2017-01-15T20:35:48+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
6
<p>在文章开头,需要普及一下几个概念,那就是 Chrome 中的 extension、app 和 plugin,分别是扩展、应用和插件的意思,不能混为一谈。</p>
<ul>
<li><p>extension(扩展):在 Chrome 地址栏输入 <a>chrome://extensions</a> 打开。Chrome 扩展是指可以增加 Chrome 浏览器功能或性能的小程序。“扩展”经常会被大家说成是“插件”</p></li>
<li><p>app(应用):Chrome 地址栏输入 <a>chrome://apps</a> 打开。利用网页技术实现与本地桌面程序一样的应用程序。不过除了 Chrome OS,Google 将不再为 Windows、Mac、Linux 提供 App 支持,并且建议我们利用 <a href="https://link.segmentfault.com/?enc=wDUPQK4gxH2GDjlBXsuVhg%3D%3D.ls4bGkkgoSUCaa3bBD8METwrkET6aFpQKTGViyeij6wFU7hEz9EpQwpXZZ%2FT%2BdAGI%2BnNclBdGe2JejZ%2BVrsMkA%3D%3D" rel="nofollow">PWA</a> 技术创建 Web app,或者改成写扩展,或者利用 <a href="https://link.segmentfault.com/?enc=Af1pRsnMAWsPrCNcTX%2Bcsg%3D%3D.7%2FAHNGQy9cHPsInTJ3eIo5yAImrKn5OpugBlIpcrJNsOsZrmp0YHK%2BWXsVMeZ1KO" rel="nofollow">Electron</a> 或者 <a href="https://link.segmentfault.com/?enc=yb28z9hW9A2v9UIJlhA4GQ%3D%3D.vkX1NsFSC1WBy6zTxn6SQoBPXeQdyzCoS%2B00RvxdP%2Fs%3D" rel="nofollow">nw</a> 创建本地应用</p></li>
<li><p>plugin(插件):Chrome 地址栏输入 <a>chrome://plugins</a> 打开。这才是 Chrome 的“插件”,这是对浏览器本身功能的增强。比如 "Chrome PDF Viewer" 插件可以使得浏览器具有浏览 PDF 文件的能力</p></li>
</ul>
<p>平时我们不用关心插件,我们经常用到的是“扩展”,并且偶尔也会使用到“应用”。所以今天主要就介绍几款 Chrome 扩展和应用。</p>
<h2>Extensions(扩展)</h2>
<p>对于开发者来说,Chrome 不仅自身厉害(可以参见<a href="https://link.segmentfault.com/?enc=O2uV74UkBK%2BGpqSM36w1sg%3D%3D.Bh26zpFr3nXtJjDIsq6eBImiAjuDME4F4u%2FetyMitDf14nfARNyddWHT8mgjmdKILEScFxo2h%2FctKUD6THQBoh2CsxDo%2BbPQhGp8PrkcaKi%2Fl%2FP82kuViPiPMRLZdXe%2BCsG0Q8bwIzdASKFcbOccDw%3D%3D" rel="nofollow">Chrome DevTools</a>),而且背后还有强大的社区,今天就跟大家整理一下 Chrome 中那些针对开发者的扩展及应用。</p>
<h3><a href="https://link.segmentfault.com/?enc=mBrH1hldn%2F5%2FmKgfAI4xjg%3D%3D.rEJ5ZmIfYfJn0yJb4qjeDZRSOLP2Qlgq5MclF8D3McK8XRSKuVGfPHIeBNRVtyFmd8B1hZH3NAg1sboT4qqhQc4qSTJWHVViEXl9ugQ0YRGHMCRpAKsNRHZNgbQ%2F2mri" rel="nofollow">octotree</a></h3>
<p>当你查看 Github 文件时你有没有因为不停切换文件而感到烦躁?octotree 能够将一个 Repository 以文件树的形式展现。</p>
<p><img src="/img/bVIgy9?w=1800&h=1179" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=wIfHyoMBh6SJ%2BRvTQ5X7UA%3D%3D.5x20Joyu%2FneUW%2BDCzwPHNZwZa4Uyzpu%2F9XNW%2FT%2FPi6%2BDw4ljjgRzYu9eXnHW64yqdjdiPo73rdasLsGatVw9eoIF6QBXTCAC3T0WeOGm7TDvsTv0fQJjDb4JSPZ706Bt" rel="nofollow">JSONView</a></h3>
<p>将你的 JSON 数据更好地展现出来。</p>
<p><img src="/img/bVIgzb?w=1280&h=800" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=sXlIjEZcSxu66Jist0tS%2Bg%3D%3D.d8i%2FScVImtIzb7d%2FjuyHevPcYE1PMXJRToUK0x5E%2FlBc9UtHcwxrLdKkhDYxFYV9tu69ZFPF%2BbCKYCm2oaaErYp61UKfz7jjQxPDu4xpMcZ3cQuEN%2Ba1LNZ9b5jERLB4" rel="nofollow">Code Cola</a></h3>
<p>以可视化方式在线编辑页面样式。</p>
<p><img src="/img/bVIgzc?w=1440&h=804" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=JV%2FgfwyGn1rBCF85MH5Zlg%3D%3D.GG%2FRN4AhOx55KiFSyZidKAMqQCZvp%2FZMYUnhsXUDzyR1qOYmLdGPoa%2BjSopTRoiLl3T3I%2BLoihsxRqoW2MbOPz09dXcpYL9GTNYndQgH36k%2BI1WzOz%2F6bluEFWc65o%2Bd" rel="nofollow">CSSViewer</a></h3>
<p>查看页面任意一个元素的 CSS 样式。</p>
<p><img src="/img/bVIgzd?w=1440&h=804" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=kL06leqrDQfyVaYheV34JQ%3D%3D.6S%2FTvE5UFoCRPTxlqRYbwveWg5uHpLOjFE9XWATQyBYvCR%2BQ%2BceZ%2Bh6mbYvt0twU5pskcc1UVtS62O73Peqo4qQsmg2mUE%2BdllzwxyGusgPL8y8lN693UPagVH%2F26kaR" rel="nofollow">Font Playground</a></h3>
<p>以可视化的方式为页面选中的元素设置不同的字体。</p>
<p><img src="/img/bVIgze?w=1440&h=804" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=ehZKkOdELN4RLjbMedliuw%3D%3D.R0WL4UzojilG8VWI2KBp%2FKDaCexcOX1xd9UAxN1G1OsD%2BcTqDimAts4nWYUJ1G4BD89F8vt1tkM6FI0VLuYr%2F8fEaR8ARVdSncsH5wPYzNpL%2Fkri40Csfk2teBK6cXIA" rel="nofollow">WhatFont</a></h3>
<p>检查页面中任意元素的字体。</p>
<p><img src="/img/bVIgzf?w=260&h=220" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=NdhahMbJWubg36e7Kn262w%3D%3D.It0%2BtYqw85F%2Bg6uTYE1%2FC7XHFyslD%2FX2kxixKDdIJUzPQ1YMV5Xamz3LoctoGbCIz8nY0BMVVqPFKtacO4VCE7Ad2nQ1%2FkjFYX%2BToN7jv4wGm%2BPTSxRFtqhxBSDu2xPe" rel="nofollow">Page Ruler</a></h3>
<p>在页面上画一把“尺子”,可以度量宽高、位置等信息。</p>
<p><img src="/img/bVIgzi?w=1437&h=799" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=eLnAHiQABjyuqyedgPX9vw%3D%3D.O3Gw6lRg2Dvyt3K0aL0XolZ7GcF7C0zePZ4NoW9RA6kbQyosMS3XuQjLdFyEWQp4bs2t1jo1YKGHpygKlppd8dYZgS2P53MjTHb5GWIsCwkHFtHwtzYRAYJUkIe1C%2FIXN4LUwnQNqCqjTpsHmcbPag%3D%3D" rel="nofollow">PerfectPixel by WellDoneCode</a></h3>
<p>将图片插入页面后可以在像素级上调整图片位置,对于像素控而言尤其有用。</p>
<p><img src="/img/bVIgzk?w=1440&h=804" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=5ZWOexpB%2FeUcAZMZFl%2BpRA%3D%3D.X3CVsMR1RZpPnWDh5ij4QRVQIGsfhoCphGCQsuRhIPTa0bQtf48Ev6HKb61cRaFBiE2xDpI0%2FfliN1NuafQRCHplOSScsuIgEE0Q9fsNBcKOwvSdyAvW%2FBiLM4IscgmGB5XdNitOEcYhU%2FfpCxo2Dg%3D%3D" rel="nofollow">Google PageSpeed Insights Extension</a></h3>
<p>原理同将网址放入到 <a href="https://link.segmentfault.com/?enc=lJgULqLb%2BTF2N8yBd54Cpw%3D%3D.FZW8fHGG1SiWjHLuyjAm5Ng1FFEFDn31zlXOu7Z%2BUHP9FkKJP7FhJezuNd7uISOC65ucd62PxloOvABGb91L3w%3D%3D" rel="nofollow">PageSpeed Insights</a> 中,来测试网页的加载速度。这个插件可以一键为网页的加载速度打分,并且可以为你链接到 <a href="https://link.segmentfault.com/?enc=NE7jaCEOjPIZAbDaznYLuw%3D%3D.O4Jlu%2BCjoDyZP2crBRxDGE99S9xmkVBesR2rpPZO4Q2EntZecdY508HXCETZqElu6lNuBBW6eidkiwtGVTqspg%3D%3D" rel="nofollow">PageSpeed Insights</a>。</p>
<p><img src="/img/bVIgzn?w=207&h=47" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=1EacG61r74s0yqUB8u00Cw%3D%3D.w8OOEnDneeYqalC%2Fyj5ss6MJ1M0H%2FRlv5Q78uhi8SsbKKNizH%2B0RRuhzdCrgvHroEbucNVeyUgDTPkw%2F0kPKJ8nSfA5h%2FiXN7Gxd2Q6b%2Fef3b3lJt1B0EHOCIjDXRizP8GqxIXjwb2nAuGxeiJEs0Q%3D%3D" rel="nofollow">Responsive Web Design Tester</a></h3>
<p>测试响应式网页的利器。自认为比 Chrome DevTools 自带的 "Device Toolbar" 看得舒心。</p>
<p><img src="/img/bVIgzp?w=424&h=133" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=D%2FWigXbqhlr%2FYRQRIU2ifA%3D%3D.M%2FbS211Jv3q4JXs30k0b1DpxRDFuGl3y9ljblfLdJgJd9YtnOiTUEfbvoilArwyn%2BktfQyX6o21FXtQQX51Xvz6WrQapf9kudXyGQX1X1Rm4v1OTQn%2BlC20mVtIrx7kB3xCpPpwbrrJlaGA0xc1DwA%3D%3D" rel="nofollow">User-Agent Switcher for Chrome</a></h3>
<p>切换浏览器的 User Agent。</p>
<p><img src="/img/bVIgzq?w=256&h=183" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=%2FAhDoodK6gZJg85kkVahFQ%3D%3D.1fiFbGGWAbTqGtS3lmvErRWsaL9YC%2BHiqClzZdtTo3vOGBQyhCg9n%2FmPYuh6iP67AYSyXAaNfQrMu7Ojxh3CMKQE8Bz0WtUHt5uwE9FlYa%2FzOwlZx61vrX%2BqqKmELHfW7u0yKmarj8jrrRAFi1ACqA%3D%3D" rel="nofollow">Usersnap - visual feedback & bug reports</a></h3>
<p>让你快速以可视化的方式提交 bug。</p>
<p><img src="/img/bVIgzt?w=654&h=384" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=8uRT%2BmV6wtZWuN3cwKInOw%3D%3D.KwPzanqcQ0Y0nhQBgYfXHO8PBovnmEc9QG8w3DmmqA7VKEjPmLlGCi%2FV7wN%2FDgcYuRYu2%2FNyBGKdfWkd9q0oYGhCqkjkJsJy79CecG8%2FKFnRY5UDp0Y3rOCF%2FMNT0UFM" rel="nofollow">Wappalyzer</a></h3>
<p>一键识别网页中用到了哪些软件,但并不是很全。</p>
<p><img src="/img/bVIgzu?w=1100&h=700" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=CJ09TRqrlGzosIZN2RnEeA%3D%3D.NElxQ6brQq480eRBJ8AHGKkKnnlDM87hXJ8IuokKF4lUkziI8EY9LpXsDm2cndQ6K282FpXkHVUmj0v5378HOhxVLhpG33WbIRaCE2pOEBYGOXKJXpbQd15qM6LILG%2FX" rel="nofollow">Web Developer</a></h3>
<p>添加一个工具栏供开发者调试网页。</p>
<p><img src="/img/bVIgzv?w=802&h=140" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=OS8lJrkDHSPr0QHJwGDPhg%3D%3D.zLtM6LUk1IQuNMa4jX5GV8%2FIe%2F9TJ7yHuc2Pu20pjMBsFzvbbO9qglsUlSurqL9SDUWGA5zyQwbeOOLtlquootT5l84Ar%2Flb3OvAcHPO2FDIm4F3YG312%2B7vZZKnEBJTRQ9f%2F88%2FIQObrPl0%2Bmul1Q%3D%3D" rel="nofollow">Web Developer Checklist</a></h3>
<p>为开发者提供一个最佳实践的检查表。</p>
<p><img src="/img/bVIgzx?w=268&h=536" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=WB5AtH2XTBGAC5Ww%2BfVhzA%3D%3D.Tb%2B2%2BrrTBp1KTttm16Vf5x6fGAR6AHLyRoAQyPgXKwMMx7f3RNfmUAvoNDgDsoe9gljqNwKHy4KeUK1HfP6FrM58gsYk5d4dYl%2Boenv%2FAgRHZQvuk5P%2Bqhm2vPCdMtyI" rel="nofollow">PicMonkey Extension</a></h3>
<p>可以修改页面中的所有图片或者页面截图。</p>
<p><img src="/img/bVIgzy?w=172&h=603" alt="图片描述" title="图片描述"></p>
<h2>Apps(应用)</h2>
<p>在介绍开发者用的 Apps 之前,我想先介绍一款扩展:<a href="https://link.segmentfault.com/?enc=1gM%2FiXOs%2FagHXBkGchTI3g%3D%3D.fSe%2BmVnXpzZI7flTsgv7m5yO6S%2FYXWkMZ6TPkom4y%2FAcdqbQZHa5vi0TKLWi6DhpMiUFgajjkyQlLZfpfyHPcja8%2FawUDP4mDhZNAupvFuiubAHKy3zwww%2FpBn7MEJe9" rel="nofollow">远方 New Tab</a>,与大家熟悉的 <a href="https://link.segmentfault.com/?enc=q8GsUyw8RwCCK3HkeMB%2Fmg%3D%3D.uXGLKB29nFI0BOcdPvzcuVPI7KnJ%2FBCFzAIpEYRr%2BvdSvlKVhf8IBz3Rd0vHc8ow6lSgvB89SZQ3dEsZ79OtrwR8zcSq4EryFTqn8BHESS8F%2FA97dt2AW9zYkIoCUmZJ" rel="nofollow">Momentum</a> 相似,但我觉得这款扩展更适合我,不仅因为新的 Tab 页面上保留了 Google 原来的搜索框,也添加了几个有用的菜单:History、Bookmarks 和 Apps。所以我平时打开 Chrome apps 的入口都在这个页面。可能有人觉得搜索框影响图片的展现,你还可以将搜索框设置成隐藏,当鼠标悬浮到相应位置时,搜索框也能自动出现。</p>
<p><img src="/img/bVIgzK?w=1439&h=802" alt="图片描述" title="图片描述"></p>
<p>介绍了这款能够快速打开 Chrome apps 的扩展后,我们来看看有哪些适合开发者使用的应用。</p>
<h3><a href="https://link.segmentfault.com/?enc=RRxjnAQZufWWgpQPxgW3tg%3D%3D.jVdrwe0P%2F8R5T46oPZIK4rlPGikPx6ouWq0P5LqVNmy92xy06jl7G2dQSUOAEWYHfz7H58NmZYt3mnHuSxGEuaUo1CeQjtnks%2FfzVHXUW0DtmlnuvpsHqEX1HTZpyRfs" rel="nofollow">Postman</a></h3>
<p>使用 Postman 可以模拟向服务器发起请求。</p>
<p><img src="/img/bVIgzM?w=1439&h=896" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=XJvAIOzKwLjK%2FC089yu3SA%3D%3D.2I1PzNa0ZHVs06aUR0RnhKkQ4aTUbBlJ%2BGsp1hmOsGFJBc44akOpbamwa6zQKHLUfQohLjcCNeJLSDlYKC8%2BYSky%2BpRLXlaR3ZRonWqGJvnyE1Zb7GBbosB8y7BX21td" rel="nofollow">Google 文档</a></h3>
<p>具有云储存、同时编辑等功能。</p>
<p><img src="/img/bVIgzP?w=750&h=469" alt="图片描述" title="图片描述"></p>
<h3><a href="https://link.segmentfault.com/?enc=CqVkeq0pYz%2BHP7ZqWYAyLA%3D%3D.YSkk7Tn8JOLlqGegd5vBk8rpOBCN%2BpiSun7aJvVJ%2FGF0ZIQvsWlEUyAOItFuOJ%2FAATgH%2Bu2Pg91ay2zF4I%2BhXrUC4L7ByKijHV6V3YpKf2YV2Ky6eCdVseY%2BF2I9rkC1" rel="nofollow">Marmoset</a></h3>
<p>为代码创建酷炫的快照</p>
<p><img src="/img/bVIgzR?w=1023&h=514" alt="图片描述" title="图片描述"></p>
<p>如果你有其他好用的扩展或应用,希望不吝分享。</p>
用 Chrome 调试你的 JavaScript
https://segmentfault.com/a/1190000008044531
2017-01-07T21:46:53+08:00
2017-01-07T21:46:53+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
5
<p>写在前面:文章是 Chrome DevTools 介绍系列的一部分,查看全部文章可以关注 Github 上的 <a href="https://link.segmentfault.com/?enc=CFKxIZFgWPuZodr20N8Q6Q%3D%3D.M9HQA72SzSQPtJE80w4VBXzrAZo1qZWHleOZui%2FWK%2FIZvMK1gwsMpVu3xMCUpS%2BEH20IUaQAll41cGwXHKCQEb6gdObc202C%2ByPo9dw1rESLQtt8RFhWM3DyKH33qcINxRXYf7A5AsBtRo1UZzjfFw%3D%3D" rel="nofollow">Chrome DevTools 详解</a>,也可以关注这个系列所在的项目:<a href="https://link.segmentfault.com/?enc=8zaHE07JWwjrCcSUMlzR0w%3D%3D.OyQxabRDWlNeQxOxpu3%2Fcgw7qvkmP2UbWQ9CDL7tRtNgxhYn2BOfaPX9sR9d5dRX" rel="nofollow">front-end-study</a>。</p>
<p>在 Chrome 中调试 JS 代码,那你不得不与 Chrome DevTools 的 Sources 面板打交道,所以文章主要通过介绍 Sources 面板上的各部分功能来介绍如何调试网页中的 JS。</p>
<h2>熟悉 Sources 面板</h2>
<p>先来认识一下 Sources 面板(以我的 Github 首页举例)。</p>
<p><img src="/img/bVHUSF?w=1440&h=579" alt="图片描述" title="图片描述"></p>
<p>可以看到面板被分为左中右三个部分,左边是文件导航,中间是文件的具体内容,右边可以统称为调试面板。整个面板就像一个 IDE,所以还是挺亲切的。</p>
<p>左边的文件导航面板包含 3 个面板:<img src="/img/bVHUSO?w=286&h=28" alt="图片描述" title="图片描述">,分别是:</p>
<ul>
<li><p>Sources:这个面板很好理解,展示了网页所用到的所有文件</p></li>
<li><p>Content scripts:Content scripts 指的是 Chrome 拓展注入在网页中的脚本。比如我安装了一个叫 JSONView 的 Chrome 拓展,打开我的 Content scripts 面板会看到:</p></li>
</ul>
<p><img src="/img/bVHUSQ?w=241&h=95" alt="图片描述" title="图片描述"></p>
<ul><li><p>Snippets:Snippets 的含义是片段,在这里指的是一小段程序,这个一小段程序跟在其他地方不一样的是,可以访问这个页面中的变量和函数等。</p></li></ul>
<p>中间面板的其他操作都比较显而易见,只是有 4 点需要简单提一下。</p>
<p><img src="/img/bVHUSY?w=957&h=177" alt="图片描述" title="图片描述"></p>
<p>标记 1、2 处可以隐藏/展开左右两个面板,标记 3 处格式化代码,使得代码变得易于阅读,当代码被压缩时尤其有用。另外需要提一下的是打开文件的快捷方式,可以用 Cmd + p / Ctrl + p 在任何一个功能面板上搜索一个文件,按 enter 键在 Sources 面板上打开。</p>
<p>右边的调试面板比较复杂,需要借助调试的例子来解释作用。不过我们可以先大概熟悉一下:</p>
<p><img src="/img/bVHUS1?w=249&h=224" alt="图片描述" title="图片描述"></p>
<p>右侧的面板为上下结构,上面是一组功能按钮,下面由很多面板组成,这些面板中,看名字大概能知道第二个显示的是调用栈,从四个开始就是各种类型的断点。那真相是什么呢?我们下面结合调试实例来解释这些按钮/面板的功能。</p>
<h2>添加断点与断点类型</h2>
<p>本文用到的测试代码为自己所写的 Demo。</p>
<h3>添加断点</h3>
<p>打开一个文件,中间的面板中显示了代码,代码的左侧有代码行号,代码行号所在的位置叫做行号槽,点击行号槽,为相应的行添加断点,并在相应的行号上面加上一个类似肩章的五边形图标。特别提一下的是,这个图标的颜色是蓝色的。如下:</p>
<p><img src="/img/bVHUS6?w=283&h=19" alt="图片描述" title="图片描述"></p>
<p>另外,如果一条语句由多行组成,如果在这条语句的行中添加断点的话,那么断点将会被加到下一条语句。举例如下:</p>
<p><img src="/img/bVHUS8?w=297&h=104" alt="图片描述" title="图片描述"></p>
<p>在上面的代码中,你可以在 13 行添加断点,但如果你想在 14-17 行添加断点的话,那么断点将会被添加到 19 行。另外,你也不能为空行添加断点,那也会被添加到下一条语句上。比如你想在 18 行添加断点,但实际会被添加到 19 行。</p>
<p><strong>条件断点</strong></p>
<p>右键一个没有添加断点的行号,选择 "Add conditional breakpoint",输入你的条件,当条件满足时,断点才会生效。回车后,效果如下:</p>
<p><img src="/img/bVHUS9?w=282&h=18" alt="图片描述" title="图片描述"></p>
<p>可以看见,条件断点跟一般断点的区别就是颜色变成了黄色。</p>
<p><strong>行内断点</strong></p>
<p>之前有人在评论里问,为什么我的这个系列文章要加一个 v57 这个前提,行内断点就是一个很好的回答。行内断点是从 Chrome(v55) 才有的一个功能,意思是你可以在一行内添加多个断点。看下面的例子:</p>
<p><img src="/img/bVHUTs?w=558&h=19" alt="图片描述" title="图片描述"></p>
<p>跟前面添加断点方式一样,我先在 15 行添加了一个断点,当程序中断在 15 行时,出现了上图的例子。但与一般的例子不同的是,上面有 3 处标红的位置,表示 3 处断点。但第 1 个断点跟后 2 个不一样的是,第 1 个断点是默认处于激活状态,而后 2 个则不是,只有点击激活后才能生效。</p>
<h3>断点的其他操作</h3>
<ul>
<li><p>忽略:如果你想暂时忽略某个断点,右键断点,选择 "Disable breakpoint"</p></li>
<li><p>修改:修改断点生效的条件。你可以将一个非条件断点通过这个方式修改成条件断点,也可以将条件断点变成非条件断点</p></li>
<li><p>删除:你可以直接点击断点,或者右键 "Remove breakpoint"</p></li>
</ul>
<h3>黑盒脚本</h3>
<p>右键行号槽的时候,第一个选项总是:"Blackbox Script"。</p>
<p><img src="/img/bVHUTv?w=217&h=81" alt="图片描述" title="图片描述"></p>
<p>那什么是黑盒脚本呢?</p>
<p>我们写项目时,很多时候是要引用第三方库或框架的,当我们调试时,调试的对象应该是我们自己写的代码,但很多时候,我们经常在焦灼地进行下一步下一步时,突然代码跳到了第三方库或框架的源码上去,这让我们焦灼的内心更添了一把柴火。黑盒脚本就是用来解决这个问题的。它能够将一个脚本文件标记为 "Blackbox Script",那么我们就<strong>永远</strong>不可能进入这个文件内部,这个文件对我们来讲就是一个黑盒子。为什么要强调“永远”呢?因为不仅普通的断点不能访问这个被标记了的脚本,其他的,比如说 DOM 断点、事件断点等等都无法访问那个脚本文件内部。</p>
<h3>面板介绍 -- Breakpoints</h3>
<p><img src="/img/bVHUTw?w=249&h=351" alt="图片描述" title="图片描述"></p>
<p>这个面板会显示你所有的通过行号留下的断点。你可以右键管理某个或全部断点:</p>
<ul>
<li><p>Remove Breakpoints:删除选中的断点</p></li>
<li><p>Deactivate Breakpoints:暂时忽略所有断点</p></li>
<li><p>Disable all Breakpoints:功能同上(与上一功能有细微差别,但表现类似)</p></li>
<li><p>Remove all Breakpoints:删除所有断点</p></li>
</ul>
<p>除了普通的中断类型,我们下面再介绍几款其他类型的。</p>
<h3>面板介绍 -- DOM Breakpoints</h3>
<p>在 Elements 面板,右键 body 元素,插入 "attribute modifications breakpoint",在 Sources 面板中显示如下:</p>
<p><img src="/img/bVHUTx?w=249&h=251" alt="图片描述" title="图片描述"></p>
<p>查看 DOM 断点的详细信息请查看另一篇博客:<a href="https://link.segmentfault.com/?enc=ALCbU98Fu9qslmyQ3BleTw%3D%3D.do4lD1iFVm%2BtlPea20odDU0ywTKLn9Uc3rmtsUp9utgQslsr19Lzpqy5YjQl6Nw2nfFgWvqa4DwLZREDvEg%2B7A%3D%3D" rel="nofollow">Elements</a></p>
<h3>面板介绍 -- XHR Breakpoints</h3>
<p>XHR 断点跟 DOM 断点很类似,通过 XHR 断点可以很容易的找到 ajax 调用的触发点和调用堆栈。最新的 Chrome DevTools 中要么为所有 ajax 调用添加断点,要么都不添加断点。</p>
<p><img src="/img/bVHUTA?w=249&h=250" alt="图片描述" title="图片描述"></p>
<h3>面板介绍 -- Event Listener Breakpoints</h3>
<p>展开 Event Listener Breakpoints 可以看到一组事件类型,展开一个事件类型可以看到具体的事件名称。</p>
<p><img src="/img/bVHUTC?w=249&h=537" alt="图片描述" title="图片描述"></p>
<p>每个事件名称和事件类型前面都有个复选框,选中即指当页面中触发了所选的事件的话,就会触发中断。</p>
<h3>面板介绍 -- Global Listeners</h3>
<p>显示全局监听器,在浏览器中 window 是全局对象,所以在 Global Listeners 面板中显示绑定在 window 对象上的事件监听。</p>
<h3>异常中断</h3>
<p>这个跟上面几种不一样,这个是放在功能按钮组里面的。</p>
<p><img src="/img/bVHUTH?w=249&h=54" alt="图片描述" title="图片描述"></p>
<p>选中 "Pause on exceptions" 按钮,如上图,当执行的脚本出现异常时会触发中断。</p>
<p>介绍了如何添加断点的方式以及几款中断类型,下面介绍一下如何利用断点进行调试。</p>
<h2>断点调试</h2>
<h3>功能按钮</h3>
<p>我们先来介绍几个功能按钮:</p>
<p><img src="/img/bVHUTK?w=249&h=28" alt="图片描述" title="图片描述"></p>
<ul>
<li><p><img src="/img/bVHUTO?w=21&h=19" alt="图片描述" title="图片描述">:当程序中断在断点处时,点击去往下一个断点</p></li>
<li><p><img src="/img/bVHUTQ?w=18&h=15" alt="图片描述" title="图片描述">:当程序中断在断点处时,长按上面的按钮出现,点击这个按钮可以在 0.5s 内忽略任何中断,当中断出现在循环内部时一般比较有用</p></li>
<li><p><img src="/img/bVHUTW?w=18&h=10" alt="图片描述" title="图片描述">:执行下一条语句</p></li>
<li><p><img src="/img/bVHUT0?w=19&h=20" alt="图片描述" title="图片描述">:当中断停留在一个函数调用处时,点击这个按钮会进入函数内部,而上面的按钮则会执行函数调用的下一句,不会进入函数内部</p></li>
<li><p><img src="/img/bVHUT6?w=19&h=20" alt="图片描述" title="图片描述">:当中断停留在函数内部时,点击这个按钮则会跳出函数内部,停留在函数调用的下一个语句</p></li>
<li><p><img src="/img/bVHUUg?w=26&h=22" alt="图片描述" title="图片描述">:在不取消断点标记的情况下,使得所有断点失效</p></li>
</ul>
<h3>面板介绍 -- Scope</h3>
<p><img src="/img/bVHUUh?w=626&h=440" alt="图片描述" title="图片描述"></p>
<p>Scope 面板显示了你当前定义的所有属性的值,例子如上图。除了 Scope 面板,你还可以在左侧的代码区域,中断的旁边看到语句中包含的变量的值。除此以外,你还可以把鼠标放在变量上面,也显示对应变量的值。</p>
<p>Scope 会显示三种类型的值: Local、Closure 和 Global。</p>
<h3>面板介绍 -- Call Stack</h3>
<p>当代码中断在一处时,Call Stack 面板会显示代码的执行路径。比如在 a() 中调用了 b(),b() 中调用了 c(),那么中断如果在 c() 内部的话,那么 Call Stack 面板会依次显示 c、b、a。</p>
<p><img src="/img/bVHUUl?w=639&h=344" alt="图片描述" title="图片描述"></p>
<p>在 JS 中,我们常常会写匿名函数,显而易见,在调试时,尤其在查看调用栈时,这样很不友好,所以建议尽量为每个函数命名。</p>
<p>如果还记得前面所讲的黑盒脚本(Blackbox Script)的话,这里就再重复一句,是的,黑盒脚本永远不可见,所以你即使在查看调用栈时你也没法看到黑盒脚本里的内容。这种情况下会出现下面这样的结果:</p>
<p><img src="/img/bVHUUm?w=259&h=59" alt="图片描述" title="图片描述"></p>
<h3>查看与修改你的值</h3>
<p>前面讲 Scope 面板时介绍了三种查看中断状态下的变量值,还有一个隐蔽的小技巧也能查看,按 esc 按键打开 Console drawer(不清楚是什么可以看<a href="https://link.segmentfault.com/?enc=yavvMiB8kUvi4tkBrQ3FNg%3D%3D.Z0xIHgoVFBDY22c3B9qhXwFL4bfZiCBWLXzL9dUnHeXmQw%2BuVbdYFPcvPvl3iloacCQjGcwFIUfrgnn2iqTGNcwWVsVtEhSoCy1%2BXIQG%2BFmmJpvTx%2BhsSfEtQOozmWur" rel="nofollow">Console</a>),然后在里面输入你想查看的值,回车,bingo~</p>
<p>如果你以为 Chrome DevTools 就简单看看这些值那就太小瞧她了,在中断状态下,还能动态修改变量的值。比如中断处有个变量叫 v,值是 1,如果我直接按 "Resume script execution" 的话,那么下一次的 v 也是 1,但如果我在按恢复执行按钮之前,我在 Console drawer 中输入 <code>v = 2</code> 回车,那么,下一处的 v 就是 2 了。</p>
<p>还有更厉害的,你不仅可以修改变量的值,你还可以修改代码!当程序中断时,你可以在 Sources 面板修改你的代码。</p>
<p>介绍到这,还有一个面板:Watch,下面就讲讲这个。</p>
<h3>面板介绍 -- Watch</h3>
<p>正如名字所表示的,观察,观察什么呢?主要观察变量。</p>
<p><img src="/img/bVHUTb?w=259&h=248" alt="图片描述" title="图片描述"></p>
<p>前面我们讲过,当程序中断时,可以查看这个状态下的变量的值,但局限是只能一个一个查看,而 Watch 的好处是可以让我们同时查看多个变量。你可以通过 "+" 来添加变量,当添加的变量存在时会显示对应的值,不存在的话则会显示 "not availble"。需要注意的是,这里的变量不会随着代码的执行而发生改变,所以到了下一个状态时,你需要点击刷新按钮来获得关注的变量的新的值。</p>
<h3>源码调试</h3>
<p>现在的项目几乎都是经过编译过的,所以当我们调试时会与编译后的代码打交道,但那并不是我们想要的。不要急,Chrome DevTools 提供了预处理过的代码与源码的映射,主要表现在两点:</p>
<ul>
<li><p>在 console 上,源链接指向的是源码,而不是编译后的文件</p></li>
<li><p>在 debug 时,在 Call Stack 面板上的源链接指向的也是源码,不是编译后的文件</p></li>
</ul>
<p>不过需要注意的是,上面所讲的能查看源码的前提是 Chrome DevTools 在设置中提供了相应权限,具体是:Settings - Sources - Enable Javascript source maps / Enable CSS source maps,勾选这两项即可。不过,默认情况下就是勾选。</p>
最新 Chrome DevTools(v57) 使用详解
https://segmentfault.com/a/1190000007877846
2016-12-21T16:04:24+08:00
2016-12-21T16:04:24+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
16
<p>写在前面:Chrome DevTools 系列文章正在紧张地整理当中,目前正在整理 DevTools 的第一部分: Elements,大家可以关注 <a href="https://link.segmentfault.com/?enc=3Ziuw%2B4vazKanuQHJKHkKg%3D%3D.XRPXSDmdYF3AipK0%2BbM8CPEPaoFyBoxU0kbupLX4beodTOGWvOl4%2Bwi4y4mFuNYuyO%2F%2BBAU%2FZNDuiSNk8ToUoXXiXrZWAaCAQ%2FuXmT0ejIwmwpJIDOy4gsvwtbwnHAKhro9mEDPjd4urUabvc0bzgQ%3D%3D" rel="nofollow">Chrome DevTools</a> 来获得最新的信息,也可以关注整个项目<a href="https://link.segmentfault.com/?enc=dnkm39w9qr35Pe1uzuqn%2FQ%3D%3D.uLjQYilY0lhtKvf9pZ6l0IeqkKoE%2FBFeAKojbcxMrmRV8LOZfjB2ROxva8GW5DL4" rel="nofollow">front-end-study</a>。</p>
<p>Chrome DevTools(Chrome 开发者工具) 是内嵌在 Chrome 浏览器里的一组用于网页制作和调试的工具。官网还推荐一款叫做 <a href="https://link.segmentfault.com/?enc=oEN7yEe6h3dhBNyTYsQG%2Fg%3D%3D.YtPziL6VDfKdqGDWLkd%2FZso996p022Lm3iAwzjiTuzBp7wZEwjP5RQSSaWbGXzFjBvLAtcbPIOYsNK7IANwBjQ%3D%3D" rel="nofollow">Chrome 金丝雀版本(Chrome Canary)</a>的 Chrome 浏览器,从这里你可以获得最新版本的 DevTools。为什么 Google 称之为金丝雀呢,因为金丝雀早期在矿井中被用来预警,而该版本的 Chrome 一定程度上也能起到该作用。不用担心 Chrome Canary 会覆盖原本的 Chrome,从 Logo 就可以看出这是两个软件。本文的实操性很强,建议大家在阅读时进行尝试,以加深印象。另外,需要注意一下的是,本文不是对 <a href="https://link.segmentfault.com/?enc=j0cvWu%2BLnfYsaiIoXgfiBQ%3D%3D.3wB4y24XLnW4UlTiNUaEl4QsohuxFZkmVBQXA7WFO3lZYYOA5JcCdy0E8v2DAxQA" rel="nofollow">DevTools 官方文档</a>的翻译,只是对我们实际使用中经常使用到的有用的功能进行整理。</p>
<p><img src="/img/bVHdo2?w=219&h=150" alt="图片描述" title="图片描述"></p>
<h2>访问 DevTools</h2>
<p>可以通过以下这些方式打开 Chrome DevTools:</p>
<ul>
<li><p>选择右上角Chrome 菜单,然后选择更多工具 -> 开发者工具</p></li>
<li><p>右键,选择检查/审查元素</p></li>
</ul>
<p>当然,比较推荐利用快捷键来打开:</p>
<ul>
<li><p><code>Ctrl + Shift + I, F12 / Cmd + Opt + I</code>,打开 DevTools</p></li>
<li><p><code>Ctrl + Shift + J / Cmd + Opt + J</code>,打开 DevTools,并且定位到控制台面板</p></li>
</ul>
<p>上面两种方式不仅可以打开 DevTools,还可以关闭 DevTools。当然,还有一种方式可以打开 DevTools。</p>
<ul><li><p><code>Ctrl + Shift + C / Cmd + Opt + C</code>,打开 DevTools,并且开启审查元素模式(相当于点击了 DevTools 左上角的图标: <img src="/img/bVHdpe?w=23&h=22" alt="图片描述" title="图片描述">)</p></li></ul>
<p>说到快捷键,这里再跟大家介绍几个非常有用的:</p>
<ul>
<li><p><code>F5, Ctrl + R / Cmd + R</code>,刷新页面</p></li>
<li><p><code>Ctrl + F5, Ctrl + Shift + R / Cmd + Shift + R</code>,刷新页面并忽略缓存</p></li>
<li><p><code>Ctrl + '+' / Cmd + Shift + '+'</code>,放大 DevTools</p></li>
<li><p><code>Ctrl + '-' / Cmd + Shift + '-'</code>,缩小 DevTools</p></li>
<li><p><code>Ctrl + 0 / Cmd + 0</code>,DevTools 恢复大小</p></li>
</ul>
<p>当然,DevTools 里不仅仅这些有用的快捷键,下面在介绍到具体的场景时再介绍。</p>
<h2>DevTools 窗口</h2>
<p><img src="/img/bVHdpm?w=1440&h=339" alt="图片描述" title="图片描述"><br>(图片来自于 Chrome v57.0 截图)</p>
<p>DevTools 是很多功能的集合,而在窗口顶部的工具栏是对这些功能的分组。最新的 Chrome 主要有 9 个功能组,分别对应了 9 个面板:</p>
<ul>
<li><p><strong>Elements</strong>:在 Elements 面板中可以通过 DOM 树的形式查看所有页面元素,同时也能对这些页面元素进行所见即所得的编辑</p></li>
<li><p><strong>Console</strong>:一方面用来记录页面在执行过程中的信息(一般通过<a href="https://link.segmentfault.com/?enc=1SmTMVnUBIzLVI26xPqnYw%3D%3D.ZSJSEvcUKb7O54znVIzTusV1GsBXSiYopCtmOkb%2B6rB5VdNDTfGsnb5rsl1NqxHnbTnP%2FuYjouU8RA5Qic%2BNir9HC%2FkS8n7X7IgqsJpESTwZNgqQbI7GoCzVe6FdojXROi32yWtClG9ae8IZQkR%2Bo30bZtR%2BoxADuP3YdxNuZZk%3D" rel="nofollow">各种 console 语句</a>来实现),另一方面用来当做 shell 窗口来执行脚本以及与页面文档、DevTools等进行交互</p></li>
<li><p><strong>Sources</strong>:Sources 面板主要用来调试页面中的 JavaScript</p></li>
<li><p><strong>Network</strong>:在 Network 面板中可以查看通过网络来请求来的资源的详细信息以及请求这些资源的耗时</p></li>
<li><p><strong>Performance</strong>:在 Performance 面板可以查看页面加载过程中的详细信息,比如在什么时间开始做什么事情,耗时多久等等。有人会问,这个跟上面的 Network 有什么区别呢,上面也能显示耗时信息。在 Performance 面板中,你不仅可以看到通过网络加载资源的信息,还能看到解析 JS、计算样式、重绘等页面加载的方方面面的信息</p></li>
<li><p><strong>Memory</strong>:Memory 面板主要显示页面 JS 对象和相关联的 DOM 节点的内存分布情况</p></li>
<li><p><strong>Application</strong>:记录网页加载的所有资源,包括存储信息、缓存信息以及页面用到的图片、字体、脚本、样式等信息</p></li>
<li><p><strong>Security</strong>:用于检测当面页面的安全性</p></li>
<li><p><strong>Audits</strong>:审计面板会对页面的加载进行分析,然后给出提高页面性能的建议,官网建议查看 <a href="https://link.segmentfault.com/?enc=J%2BExAbNmgOAobZbUA30D8Q%3D%3D.RCvDwrsSQp0bYqwnxLb6NTXArIObqPdcgxpc97IdcspwOagp%2BhAV%2BrTVb0JlGXcuW84%2FRx4PsJL8n2y6ZW9nUQ%3D%3D" rel="nofollow">PageSpeed Insights</a> 来获得更多的页面加载建议。</p></li>
</ul>
<p>细心的同学一定发现了我们现在使用的 Chrome 上面没有 Performance 和 Memory,而是 Timeline 和 Profiles,是不是我写错了呢?不是的,而是 Chrome 到 v57 后,便将 Timeline 更名为 Performance,将 Profiles 更名为 Memory。目前来看,Google 仅仅是更名以及调整了部分功能所属的面板而已,并没有功能上的增删,这个会在下面介绍各个面板时详细介绍。</p>
<p>除了 9 个功能面板,工具栏还有 3 个选项,分别是左侧的 <img src="/img/bVHdpV?w=23&h=25" alt="图片描述" title="图片描述">、<img src="/img/bVHdpY?w=24&h=26" alt="图片描述" title="图片描述"> 和右侧的 <img src="/img/bVHdp5?w=19&h=26" alt="图片描述" title="图片描述"> ,分别表示检查元素、设备切换以及 DevTools 的定制化面板。检查元素后面在介绍 Elements 时会提到;设备切换是我们比较常用的功能,能够将你的浏览器模拟成一个带触屏功能的移动设备;DevTools 定制化面板是对 DevTools 本身的定制与控制。下面着重介绍下“切换设备”这一功能。</p>
<h3>切换设备</h3>
<p>使用这个功能能够将你的浏览器变成任意一款移动设备,也能为你的网页设定宽高,这在做单页应用时非常有用。现在选中“切换设备”选项,图标由黑色变成蓝色,整个浏览器内容窗口变成上下两部分,上面是选项,下面是网页内容:</p>
<p><img src="/img/bVHdqh?w=1439&h=467" alt="图片描述" title="图片描述"></p>
<p>上面有 4 个部分组成,第一个是设置设备类型,第二个代表下面网页的宽和高,第三个代表缩放比例,第四个代表旋转设备甚至选择设备的状态(需要设备支持,比如选择Nexus 5X 时)。我们着重看第一个。</p>
<p><img src="/img/bVHdqo?w=212&h=143" alt="图片描述" title="图片描述"></p>
<p>点击第一个下拉框,出现上图,你们会发现有几条分割线,先说浏览器默认的,有两条分割线,将所有选项分成 3 个部分,分别是</p>
<ul>
<li><p>Responsive,代表响应式,即你可以随意的拖动改变网页的宽高</p></li>
<li><p>常见移动设备,比如 Galaxy S5 / iPhone 6 / iPad 等,选择一项后,就能将浏览器模拟成那个设备的大小、User Agent 等行为</p></li>
<li>
<p>edit,编辑,选中之后出现如下界面:<br><img src="/img/bVHdqr?w=1434&h=289" alt="图片描述" title="图片描述"><br>这个界面也可以通过 <img src="/img/bVHdp5?w=19&h=26" alt="图片描述" title="图片描述"> -> Settings -> Devices 来打开。在这个界面上,你可以选择你经常需要使用的设备,当然也可以定制你自己的设备。</p>
<p><img src="/img/bVHdqx?w=353&h=177" alt="图片描述" title="图片描述"></p>
<p>分别输入设备名称、设备宽高、<a href="https://link.segmentfault.com/?enc=Xb9j9ng9U%2BIYK9Z6T1SWrg%3D%3D.ZULBl2kAfOreju57IYNzRVa2ke0IfvjU8ZZhrXQEwLDEjty4Q79XhckRRI9ZjZBs9%2F2%2B%2F0pHc4lSzgLrYS0lbEYukt1a5toMfaK5e%2Buy1uite6OJZ9uBFn%2FoXT6h4U0co6PK8D0ueJcppcN4Z9dvEV8U5jp4eoHNbD55drUhAwk%3D" rel="nofollow">DPR(默认是 1,可不填)</a>、<a href="https://link.segmentfault.com/?enc=ga3oaMuzCRSzpLw1R6AtYw%3D%3D.ngnkn7hUjLw9hRydZxlWnnSkouY%2B4f2J%2B6YdBfV%2Bv5dBBxUBN32H6fL%2Fyx%2FoMdS%2BcTQh1vmlAN2dUfasc9rxRpPuGLWKFax7tzlvgqUooeK7JE9FopidhwTrqrBla8xkRxAwju%2B7V%2FedY%2B%2F83g4y0A%3D%3D" rel="nofollow">User-agent(用户代理,可不填)</a>,选择屏幕类型,最后点击 "Add",便可以将你的设备加到设备列表里了。这里再多说一下屏幕类型:</p>
<ul>
<li><p>Mobile:可触屏的移动设备,鼠标指针是粗黑的点,代表手指与屏幕的触点</p></li>
<li><p>Mobile(no touch):不可触屏的移动设备</p></li>
<li><p>Desktop:一般指 PC 上的网页,鼠标指针与普通 PC 网页类似</p></li>
<li><p>Desktop(touch):带触屏的 PC 上的网页</p></li>
</ul>
<p>所有大家看到鼠标指针是个粗黑的点时不要觉得奇怪,那只是代表你现在在操控一台移动设备。</p>
</li>
</ul>
<p>当你将你自定义的设备添加进设备列表时,DevTools 会自动选中,从而可以在下拉框中看到第四部分:</p>
<ul><li><p>自定义的设备</p></li></ul>
<p>那我的设备举例,我一般不怎么用到移动设备,所以我就保留了一个 iPhone 6,另外我还需要一个 1920 * 1080 的高清屏,所以我以 HD 命名,定义了一个 1920 * 1080 的设备。</p>
<p><img src="/img/bVHdqo?w=212&h=143" alt="图片描述" title="图片描述"></p>
<p>上面大概介绍了 DevTools 的几个面板和另外 3 个功能选项的作用,着重介绍了切换设备的功能,下面我们一起来看一下如何使用每一个面板。</p>
<h2>详细介绍</h2>
<p>下面的例子没有特殊说明均拿 <a href="https://link.segmentfault.com/?enc=CbWh6Q8nuZHCo6vdw%2BfnOQ%3D%3D.pD%2BJP4iRa5UAdrszqjQOOyUPbL6KzSvLh7Af4N3ZBvY%3D" rel="nofollow">Github</a> 进行举例。</p>
<h3>Elements</h3>
<p><a href="https://link.segmentfault.com/?enc=dLGu4WiK3krHplwYkKAYGA%3D%3D.Hk36C2tUUiySyi2YjwQk0jabquTwDA23qIrrbxU6BY4uWdj5NcEfiJihJzk4QIXZ1hNQs7TtRQm1HvRe5ORh7QgRTCb6HntrKzux4Pxh%2BDCQZjxEiBnrdU7ntchEs3qi" rel="nofollow">Elements</a></p>
<h3>Console</h3>
<p><a href="https://link.segmentfault.com/?enc=Xt1xs%2FVzkefHBQWoN1IRHg%3D%3D.GFRgJmhxwOnNygNmszwtaUQ9F3CjlbHYbVtTSO%2F5GI76Ow5k3U%2BIQIR6KMbdN0ays0DSDSrCdNQcLT8SyfNixs1Km8pCMZ%2BN6P2faxNjU8ayv%2B%2B1z7hJoTsY470P7cXV" rel="nofollow">Console</a></p>
<h3>Sources</h3>
<p><a href="https://link.segmentfault.com/?enc=gbuSKYVeqP6jNTNy%2BEdD9Q%3D%3D.WJzTNgQe8iGLPnGb4nCOEQMIOgu4eirI%2FFyjZYhjx5cXpR07OgH1tu8Iyc4g%2B9Ez7a1DZzQaxrcZb9s7Ky95%2BWoe1gy%2FDlMVFvinNTfj%2FOFcyrySXbsLYAzPH9157EZ5" rel="nofollow">Sources</a></p>
<h3>Network</h3>
<p><a href="https://link.segmentfault.com/?enc=c4betQEQdBSGVtcT84ONZw%3D%3D.x0Ml6WIJkK6QO8ixck0w0xGmYpu5NXQmred%2FcSEEKB3T7dMJ%2BoNI9dHgyw%2FEdXm2%2BSI6%2FGrWlLnh5e00YX7iW%2BYgaKhmbT36KcrbPjI3jN67iaR6V7Nq5qpxHNHVBqyL" rel="nofollow">Network</a></p>
<h3>Performance</h3>
<p><a href="https://link.segmentfault.com/?enc=wm%2FNBNdG7LybcM4tXrJrAA%3D%3D.5VWV%2Bq%2FqY8POWv29xY2v1eDBGVquRLx%2BgopwaJghJjrjHQAv4lsLMhj7fsU7svm2LKSn2QV9OJzPj2pk%2FGsiv6%2B6M2w2wUPf0Az5%2BSDlIjVYlbTL1PNhZZTzevULLatd" rel="nofollow">Performance</a></p>
<h3>Memory</h3>
<p><a href="https://link.segmentfault.com/?enc=eg8kG3tKRGc3s88d70Y97g%3D%3D.eMRLOXpLQTcVx4C9nE8CGGoD60aBvFBRtz6nACttHjvAwT3ZuCZn7Qw0XAFR2azpuW9Br08yWk9eFKUSdxtT5JhrkmCSWGEVq6u93izG7jP8%2FoqaBS%2FZfQ%2FP8HNWSHcu" rel="nofollow">Memory</a></p>
<h3>Application</h3>
<p><a href="https://link.segmentfault.com/?enc=EX7exwLr24V593okQjscTw%3D%3D.s639%2FCQh%2BX7XB8LrMg%2BmsFBsDNzDzkVtQfFB7tgMLhqeS2QivhG2RJvpCRNGLCDMaiRw4su1vQufN5Ci6JouMcz6W%2BNw%2F2RJ%2BR56uxEvtxrvXe4y1qY5NU9Qq5Zf67XA" rel="nofollow">Application</a></p>
<h3>Security</h3>
<p><a href="https://link.segmentfault.com/?enc=Y%2FiWUF1T%2FFoUZp2GLrvZwQ%3D%3D.WBHH1orj%2F4x06tYsAbCpstYYHwa3DRZ5F1V3QQzLHLUhynVIQT0V7Hys21cKqqxYE4U3vjflNddPp2EhFmRcKTS7mbD24XSG1dal7yoYq%2FR%2BVVt8SjO%2BWQW4wgHzE2V8" rel="nofollow">Security</a></p>
<h3>Audits</h3>
<p><a href="https://link.segmentfault.com/?enc=yCB3%2B86Y4PltAB3wixqj2A%3D%3D.AZQb1%2FMSPF9PfApIMtlJmAz1PgNHpYqYTyemeeFnrTicKkgUhGNp5xuLy8W8rvxhAbOXJ6UAeKAlxKlp4ti%2FrImZflaep7wrvGw3A1BwN9SMpijDRVAEqT0beMY8XLv0" rel="nofollow">Audits</a></p>
技术人员的写作利器:Markdown
https://segmentfault.com/a/1190000007765812
2016-12-11T21:07:17+08:00
2016-12-11T21:07:17+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p>Markdown 是一种轻量级的标记语言,其用简单的标记语法便可达到排版的目的,其可以使我们更加专注于内容的编写,而不需过多关注排版。本文主要整理了 Markdown 中的常用的标记语法,以便自己与他人以后查用。</p>
<h2>段落元素</h2>
<h3>1、段落与换行</h3>
<p>Markdown 中的段落指连续的一段文字,编写时段落之间用至少一个<strong>空行</strong>隔开,段落内多个空格将被视为一个空格,段首不支持缩进。</p>
<p>如果想要在显示时显示多个空行,可以插入 <code><br/></code> 来实现,注意的是,插入的 <code><br/></code> 应与前后的段落中间至少空一行。</p>
<h3>2、标题</h3>
<p>Markdown 支持两种类型的标题。</p>
<pre><code>//类型 1
这是一级标题
==========
这是二级标题
----------
//类型 2
# 这是一级标题
## 这是二级标题
...
###### 这是六级标题
</code></pre>
<p>从上面可以看出类型 1 是在标题下面插入 <code>=</code> 或者 <code>-</code> 来标识标题等级,但局限是其只能表示两个级别的标题。</p>
<p>类型 2 是在标题前面插入 1 - 6 个 # ,表示 6 个等级的标题,这是比较推荐的用法。</p>
<h3>3、引用</h3>
<p>Markdown 中使用 <code>></code> 来引用。我们可以在一段文字中的每行开头加上 <code>></code> 来表示一段引用文字,也可以只在一段文字的开头插入一个 <code>></code> 来表示,如下面的 1、2 两种方式:</p>
<pre><code>//方式 1
> 这是一句话
> 这是第二句话
//方式 2
> 这是一句话
这是第二句话</code></pre>
<p>Markdown 支持使用不同数量的 <code>></code> 表示嵌套引用。</p>
<pre><code>> 这是外层的引用
> > 这是内层的引用</code></pre>
<h3>4、无序列表</h3>
<p>无序列表使用 <code>-</code>、 <code>+</code> 或 <code>*</code> 来作为标记。</p>
<pre><code>- 第一项
- 第二项
- 第三项</code></pre>
<p>上面的 <code>-</code> 可以用 <code>+</code>、 <code>*</code>替换。需要注意的是,<code>-</code> 等符号与后面的文字至少空一格空格。</p>
<h3>5、有序列表</h3>
<p>有序列表使用数字和紧挨着的点号表示。</p>
<pre><code>1. 第一项
2. 第二项
3. 第三项</code></pre>
<p>同无序列表一样,标记符号与后面的文字至少空一格空格。但编辑时的数字对显示无影响。</p>
<pre><code>2. 第一项
6. 第二项
1. 第三项</code></pre>
<p>上面的例子与前一个显示的结果完全一致,但建议编辑时按照数字顺序。</p>
<h4>列表</h4>
<ul>
<li><p>有序列表和无序列表的每一项中均可嵌套其他列表;</p></li>
<li><p>在列表项之间要插入段落时,这时需要将列表项之间的段落缩进 4 个空格;</p></li>
<li><p>使用 <code>1. </code> 来输出 <code>1. </code>;</p></li>
</ul>
<h3>6、代码区块</h3>
<p>缩进 4 个空格,需要注意的是,每行代码都需要至少缩进 4 个空格,不能像段落一样采用首行标记的偷懒写法,一个代码区会一直持续到没有缩进 4 个空格的那一行。</p>
<p>也可以用一对三个连续的撇号 <code> ` </code> 来包裹代码段。</p>
<pre><code>code</code></pre>
<p>有的解释器还能根据代码的语言从而给代码加上语法高亮。</p>
<pre><code class="javascript">function func() {}</code></pre>
<h3>7、分割线</h3>
<p>使用三个及以上的 <code>*</code>、 <code>-</code> 或 <code>_</code>来表示一个分割线,符号不能混用,符号之间可以插入多个空格。需要注意的是,使用 <code>-</code> 来插入分割线时需要与上一个段落至少空一行,否则 Markdown 会将上一行文字解释为二级标题。</p>
<h3>8、表格</h3>
<p>表格是 Markdown 比较复杂的一种表示。</p>
<pre><code>| Table | Col1 | Col2 |
| ----- |:----:| ----:|
| Row1 | 1-1 | 1-2 |
| Row2 | 2-1 | 2-2 |
| Row3 | 3-1 | 3-2 |
</code></pre>
<p>上面第二行中的点代表对齐方式,分别是默认(居右)、居中、居左。</p>
<hr>
<h2>行内元素</h2>
<h3>9、超链接</h3>
<p>Markdown 中有三种方式实现超链接。</p>
<pre><code>//方式 1
[百度](http://www.baidu.com)
//方式 2
[百度][Baidu-url]
[Baidu-url]: http://www.baidu.com
</code></pre>
<p>方式 1 较为常用,也可以为链接的文字加上提示文字,只要在括号中超链接加上空格后添加提示内容即可。</p>
<pre><code>[百度](http://www.baidu.com "这是提示文字")
</code></pre>
<p>方式 2 由链接文字和链接地址组成,不同的是两者均由 <code>[]</code> 包裹。链接地址的格式为:</p>
<ul>
<li><p>方括号,里面输入链接地址;</p></li>
<li><p>紧接着是一个冒号;</p></li>
<li><p>冒号后面至少一个空格;</p></li>
<li><p>链接地址;</p></li>
<li><p>若有提示文字,空格后用引号或者括号包裹提示文字。</p></li>
</ul>
<p>下面是完整示例:</p>
<pre><code>[百度][Baidu-url]
[Baidu-url]: http://www.baidu.com "这是提示文字"</code></pre>
<p>第三种方式是用 <code><></code> 来包裹 URL。</p>
<pre><code>//方式 3
<http://www.baidu.com>
</code></pre>
<h3>10、加粗和斜体</h3>
<p>Markdown 使用 <code>*</code> 和 <code>_</code> 来表示粗体和斜体。</p>
<pre><code>//加粗
**这是加粗文字**
__这也是加粗文字__
//斜体
*这是斜体文字*
_这也是斜体文字_
</code></pre>
<p>被偶数个 <code>*</code> 或 <code>_</code> 包裹的文字显示加粗效果,被奇数个包裹的为倾斜效果。</p>
<p>需要注意的是,<code>*</code> 和 <code>-</code> 要成对出现,不能混合使用,也不能只出现一个。同时,标识符号要与标识的文字紧挨着,符号与符号之间、符号文字之间不能有任何空格。</p>
<h3>11、代码</h3>
<p>使用 <code> ` </code> (撇号) 来包裹一小段代码。</p>
<pre><code>`Hello world.`
</code></pre>
<p>若想在代码中添加撇号,可以使用多个撇号包裹里面需要添加的撇号,但注意里面的连续的撇号数量不能超过外面的数量。</p>
<pre><code>//显示一个撇号
`` ` ``
//显示两个撇号
``` `` ```
</code></pre>
<h3>12、图片</h3>
<p>图片的插入方式跟超链接前两种插入方式类似。</p>
<pre><code>//方式 1
![如果图片不能显示,就显示这段文字](图片 url)
//方式 2
![如果图片不能显示,就显示这段文字][Image-url]
[Image-url]: 图片url "这是提示文字"
</code></pre>
<h2>反斜杠 <code>\</code>
</h2>
<p>我们经常需要在文章中插入一些特殊符号,而这些符号恰好是前面所讲的标识符号,可以在特殊符号前插入 <code>\</code> 来直接显示符号,而不让 Markdown 来将其解释为标识符号。</p>
<p>Markdown 支持以下这些符号前插入 `` 而显示其本来样子:</p>
<pre><code> \ 反斜线
` 反引号
* 星号
_ 底线
{} 花括号
[] 方括号
() 括弧
# 井字号
+ 加号
- 减号
. 英文句点
! 惊叹号</code></pre>
<h2>拓展</h2>
<p>其实,市场上有很多的 Markdown 解释器,它们大都能支持上面所讲的语法,但呈现出的样式往往不一。另外,不同的解释器还能支持其他自己定义的语法,比如 Github 还能支持 emoji。下面再着重介绍 Github 支持的几个 Markdown 语法。不过需要注意的是,有些语法只能在 issue 或者 pull request 上使用,这个在后面讲每个语法时会标记(约定:“通用”表示在 Github 任何地方可以使用的语法,“特殊”表示只能在 issue 或者 pull request 上使用)。</p>
<h3>语法高亮(通用)</h3>
<p>上面说过,有的解释器是能够显示语法高亮的,Github 就可以。</p>
<h3>任务列表(通用)</h3>
<pre><code>- [ ] task one
- [x] task two
</code></pre>
<p>用法跟普通列表的用法差不多,只不过在每一项文字前面加了 <code>[ ]</code> 或者 <code>[x]</code>。<code>[ ]</code> 中间有且只有一个空格,表示未完成,另一个表示已完成。</p>
<h3>表格(通用)</h3>
<p>Github 支持更简单的 table 语法。</p>
<pre><code>First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column</code></pre>
<p>表头与项用一排 <code>-</code> 分隔开,每一列用 <code>|</code> 分隔开。</p>
<h3>SHA 引用(特殊)</h3>
<p>每一次 commit 都会产生一个 id,用 <code>@id</code> 的方式可以链接到某个项目的特定的 commit。比如用 <code>jquery/jquery@1b9575b9d14399e9426b9eacdd92b3717846c3f2</code> 就能链接到 jquery 的一次 commit 记录上。</p>
<h3>issue 引用(特殊)</h3>
<p>用 <code>#1</code> 来引用当前 repo 的第一个 issue,也可以用 <code>jquery/jquery#1</code> 引用 jquery 的第一次 issue。</p>
<h3>@(特殊)</h3>
<p>用 <code>@</code> 来提醒目标用户。比如 <code>@CompileYouth</code> 可以 @ 到我。</p>
<h3>删除符号(通用)</h3>
<p>用连续两个 <code>~</code> 包围的词会被加上删除符。比如 <code>~~This is removed~~</code>。</p>
<h3>Emoji(通用)</h3>
<p>Github 比较有意思的是可以支持 emoji。比如 <code>:smile:</code> 表示笑脸等等,具体可以查看 <a href="https://link.segmentfault.com/?enc=MlMvVsafHhNYKl9bWQ%2BJbQ%3D%3D.fT%2Fkeo2mtsATqsrovIlsVJJefJMdKL3kTJtadsp7z9H8h9YhAvP5m2thtogQpOEqs6xlUZh8peLYujYQXdloIw%3D%3D" rel="nofollow">Emoji Cheat Sheet</a></p>
<p>详细信息可以查看<a href="https://link.segmentfault.com/?enc=eaHTEhQe8lFrzfFAOty7XQ%3D%3D.FWohO0ID9r0PyP%2BjHrsf%2BMkU8mnKbZxWKDlA7KXszCpIanhgLk%2BYBxOOezTkXuI1QtfbBZ8qvBD%2FXNRJtntuvw%3D%3D" rel="nofollow">官方文档</a>。最后 po 两张 Github 官方推荐的 Markdown Cheat Sheet:<a>通用语法</a>,<a>Github 支持语法</a></p>
<h2>工具</h2>
<ul>
<li><p>Windows 环境下,推荐 <a href="https://link.segmentfault.com/?enc=AMhOquhcL9fMqBBdIL%2Bq4A%3D%3D.tk39FrtqBEWr1tE1LVSzbQzWvy0194lSZdUUmu5G0%2Fs%3D" rel="nofollow">Typora</a>、<a href="https://link.segmentfault.com/?enc=Z7PbcKY3io8LCATTep1sCQ%3D%3D.SHxmb%2BIXSVSNqBLnxc%2Fwck3ffm4CzfGg7%2BjxrEV55c8%3D" rel="nofollow">Markdownpad</a>(自带图床功能)</p></li>
<li><p>Mac 环境下,推荐 Typora、<a href="https://link.segmentfault.com/?enc=3B7Q37mgnUb45M0fJx3kOA%3D%3D.8MzDglevmRHAnvPBzMWHk21daqdpbZk2qGgmXSvhOJs%3D" rel="nofollow">Mou</a></p></li>
<li><p>笔记软件: 为知笔记较好的支持 markdown,且支持 Windows、Mac、web、ios、android 等各个平台</p></li>
<li><p>当然,还有 Github</p></li>
</ul>
<h2>实践</h2>
<p>Markdown 文件的编辑在不同编辑器中有不一样的表现,我主要介绍一下在 Atom 中使用 Markdown 的情况。</p>
<p>Atom 官方自带了一个插件,叫 <a href="https://link.segmentfault.com/?enc=kTAv6yxixWmXr2MMgkxx7w%3D%3D.a5IYkRDgCE8XOB0ROi9i0rFiFUHmC8R1qUbPuaVkpY0D1qrp6uCZbVKecKov49Ae" rel="nofollow">markdown-preview</a>,可以在 Atom 中进行预览。当然,Atom 还有很多第三方的 Markdown 插件,我使用的是 <a href="https://link.segmentfault.com/?enc=8dMOcxJmttuOZYiul7maOg%3D%3D.fdThhZwgsLVMjXvXMAJEdAPq6elWlwoNr%2FM0jRQ66Dd3KowdEKj7NOkRDSJYnoVB" rel="nofollow">markdown-writer</a>,可以使我更方便地编辑 Markdown 文件,当然你可以根据自己的需要下载插件。</p>
<p>在 Atom 中,其他的使用方式就不赘述了,唯一需要提一下的是插入 table。在 Markdown 中插入 table 是一件比较麻烦的事情,而在 Atom 中,可以简化操作,就是直接输入 <code>table</code> 然后按 Enter 键,编辑器便会自动生成 table 的模板,比自己手写便捷多了。</p>
<p>拓展阅读:</p>
<ol><li><p><a href="https://link.segmentfault.com/?enc=%2BL2A9D8I55AexzT71EehnQ%3D%3D.0uheHTLBQi72yBjcjXiz58b95nAocP%2F8SB5gxnhzZsA%3D" rel="nofollow">Markdown 语法说明</a></p></li></ol>
磨刀不误砍柴工,配置你的前端开发环境:Atom
https://segmentfault.com/a/1190000007690359
2016-12-04T18:43:32+08:00
2016-12-04T18:43:32+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
3
<p><a href="https://link.segmentfault.com/?enc=J6l8JlFL5vd2zxtO4Dv0zg%3D%3D.3vn6ajfsY4Czpil%2FNrdWH8cjF6JBpJGJcx0zL0l4IKI%3D" rel="nofollow">Atom</a> 是 Github 官方开发的一款可定制化的、跨平台的文本编辑器。有两点需要注意一下,一是 Atom 是由 Github 官方开发,二是其定制化程度非常高。前者的结果是编辑器对 Github 非常友好,如果项目托管在 Github 上的话,用 Atom 可以很方便地进行 Git 操作。后者的结果就是我们可以根据自己的需要定制自己的编辑器,使得自己的效率最大化,也让自己舒舒服服、开开心心地码代码。</p>
<h2>Atom 的特色</h2>
<h3>Atom Package Library</h3>
<p>Atom 的背后有一个强大的社区,带来了一大波插件,被称为 packages,利用这些 packages 你可以很好的自定义你的编辑器。</p>
<h3>对 Git 友好</h3>
<p>当你在一个 git 项目中添加或修改文件时,Atom 会用特定的颜色标记出来,比如绿色代表新建的文件,黄色代表修改的文件。</p>
<p><img src="/img/bVGqKB?w=305&h=517" alt="图片描述" title="图片描述"></p>
<p>另外,在编辑器的右下角,还能显示你在哪个分支,你添加、删除了多少行。</p>
<p><img src="/img/bVGqKD?w=369&h=31" alt="图片描述" title="图片描述"></p>
<h3>Command Palette</h3>
<p>命令行面板用 cmd + shift + p / ctrl + shift + p 调出,在这个面板中你可以输入命令控制你的编辑器甚至控制你的安装的 package。</p>
<p><img src="/img/bVGqKH?w=1131&h=435" alt="图片描述" title="图片描述"></p>
<h3>Fuzzy File Finder</h3>
<p>按键: cmd + T / ctrl + T,调出查询面板,当项目比较大时,你可以利用这个面板,输入关键字,Atom 便能帮你找到关键字所在的文件。</p>
<p><img src="/img/bVGqKN?w=1087&h=345" alt="图片描述" title="图片描述"></p>
<h3>Atom Shell Commands</h3>
<p>在打开这项功能之前,需要先安装 Atom Shell Commands。</p>
<p><img src="/img/bVGqKO?w=252&h=407" alt="图片描述" title="图片描述"></p>
<p>安装好了后,就可以在终端(Terminal)中用命令行控制 Atom。比如:</p>
<ul>
<li><p><code>atom</code>: 在当前目录下新建一个新的 Atom 窗口</p></li>
<li><p><code>atom .</code>: 将当前目录在 Atom 中打开</p></li>
<li><p><code>atom myProject</code>: 在 Atom 中打开一个特定的文件夹或文件</p></li>
<li><p><code>atom -h</code>:查看 atom 命令后面可以跟哪些参数</p></li>
</ul>
<p><img src="/img/bVGqKT?w=176&h=64" alt="图片描述" title="图片描述"></p>
<p>当然你也可以使用 <code>apm</code> 命令在终端来安装 package,前提是安装了 <a href="https://link.segmentfault.com/?enc=AQxarMy9Av%2BQhlxuT7i7dA%3D%3D.GsDl7s5hr2VsstYtba5bgE9%2FwntPHSq4r%2B6g7V%2FU3wU%3D" rel="nofollow">Node</a>。比如安装一个叫 “pigments” 的 package,那么你可以使用如下命令:</p>
<pre><code>apm install pigments</code></pre>
<p>这里就插播一句,安装了 Node 后,npm(Node Package Manager) 也一起安装了,通过 <code>npm</code> 命令可以安装 Node 的 package。但是速度一般会比较慢,所以我们习惯上会再安装一个 nrm(Npm Regisry Manager),这是一个 npm 注册中心的管理工具,npm 注册中心代表 packages 存放的地方。如果使用默认的注册中心,那么安装时就需要从国外的服务器中下载 package,速度就比较慢,所以我们会利用 nrm 来将国外的注册中心切换至中国的 taobao 注册中心,具体讲是利用如下命令:</p>
<pre><code>nrm use taobao</code></pre>
<p>检查是否成功切换的标志是输入 <code>nrm current</code> 或者 <code>nrm ls</code>。然后以后安装 package 时便可以从国内镜像进行下载了。</p>
<p><img src="/img/bVGqKU?w=572&h=257" alt="图片描述" title="图片描述"></p>
<h3>Snippets</h3>
<p>将你一般比较常写的代码做成模板,方便以后自动填充。</p>
<p><img src="/img/bVGqKW?w=253&h=408" alt="图片描述" title="图片描述"></p>
<p>比如我经常需要些 ES6 的类,所以我便将 ES6 类做成模板。</p>
<p><img src="/img/bVGqK0?w=318&h=244" alt="图片描述" title="图片描述"></p>
<p>以后,只要我在 JS 文件中输入 <code>cl</code> 再按 Enter 键,则会出现:</p>
<p><img src="/img/bVGqK2?w=211&h=111" alt="图片描述" title="图片描述"></p>
<p>这里解释一下模板的用法,以我上面的模板为例。</p>
<ul>
<li><p><code>.source.js</code>: 目标文件类型,就是模板只有在 JS 文件中自动填充才能生效</p></li>
<li><p><code>class</code>: 模板的名字,仅仅作为你模板的一个标识</p></li>
<li><p><code>prefix</code>: 你在使用时输入的缩略词,这例子中我需要输入 <code>cl</code> 来自动补全代码</p></li>
<li><p><code>body</code>: 模板的正文,单行语句只需要 <code>''</code>,多行语句则需要 <code>"""</code></p></li>
<li><p><code>$1</code>,<code>$2</code>... 光标的位置,按 Tab 或者 Enter 键进入下一个位置</p></li>
</ul>
<h2>定制你的 Atom</h2>
<p>下载好对应操作系统的 Atom 并且安装后,首先就是进入设置界面(Windows 下的 Settings 或 Mac 下的 Preferences)。Atom 设置的一个我认为最大的好处就是,她将所有的设置都通过图形化界面来操作,而不像一些编辑器通过一大堆很繁琐的配置文件。你会看到设置界面的导航栏:</p>
<p><img src="/img/bVGqK3?w=200&h=369" alt="图片描述" title="图片描述"></p>
<p>分别是:</p>
<ul>
<li><p>Core:这部分是整个编辑器的核心设置,一般情况下全部默认即可</p></li>
<li><p>Editor: 主要对编辑器的可视化进行设置,一般修改样式即可</p></li>
<li><p>Keybingdings: 编辑器中操作的快捷键</p></li>
<li><p>Packages: Atom 集成了很多的 package ,这是她可以被定制化的一个重要因素。在这里会看到 Atom 装的所有 package</p></li>
<li><p>Themes: Atom 的主题,也可以被定制化</p></li>
<li><p>Updates: 显示需要更新的 package 或者主题</p></li>
<li><p>Install: 在这个界面,我们可以安装各种各样的 package 、主题,这是我们用的最多的一个菜单</p></li>
</ul>
<h3>定制编辑器</h3>
<p>Core 部分有一个比较常用,就是 “Ignored Names”。在这里你可以列举不想在左侧文件列表里看到的文件或文件夹,比如 node_modules。</p>
<p>你可以在 Editor 部分设置编辑器的 Font Family, Font Size 等等。我的习惯是这些都默认,除了三个:</p>
<ul>
<li><p>Tab Length: 改成 4,意思是一个 Tab 键占用 4 个空格,默认是 2 个</p></li>
<li><p>Scroll Past End: 选中,意思是你可以将代码的最后一行显示在屏幕的最上方</p></li>
</ul>
<p><img src="/img/bVGqK6?w=1024&h=1350" alt="图片描述" title="图片描述"></p>
<ul><li><p>Show Indent Guide: 选中,可以清晰地标记同一层次的代码,当代码嵌套层次比较复杂时尤其有用</p></li></ul>
<p><img src="/img/bVGqK8?w=1024&h=1272" alt="图片描述" title="图片描述"></p>
<p>Themes 部分你可以设置编辑器的主题,我采用了默认的主题。如果你不喜欢默认的主题,那你可以去尝试一下其他主题。Atom 自带了几种主题,另外你也可以从网上下载安装,比如 Material Design 风格的。具体安装主题的方法同安装一般的插件包一样,我接下来就重点介绍这一部分。</p>
<h3>安装 package</h3>
<p>Install 部分,我们可以搜索并安装 Atom 的插件包或主题。下面我主要介绍一下我主要使用的 packages(大部分图片来自 package 官网),大家可以根据需要选择,另外,需要提一下的是,大多数的 package 也能进行自定义设置。</p>
<ul><li><p>git-control: 这是我最常使用的 package,我可以不用记很多繁琐的 git 命令,常用的命令都能用界面完成,简单便捷,又能减少出错。这个插件的详细使用请参加[版本配置工具:Git 与 Github]()</p></li></ul>
<p><img src="/img/bVGqLe?w=720&h=437" alt="图片描述" title="图片描述"></p>
<ul><li><p>platformio-ide-terminal: 可以在 Atom 中直接打开终端</p></li></ul>
<p><img src="/img/bVGqLf?w=673&h=535" alt="图片描述" title="图片描述"></p>
<ul><li><p>autocomplete-paths: 自动补全路径</p></li></ul>
<p><img src="/img/bVGqLp?w=435&h=233" alt="图片描述" title="图片描述"></p>
<ul><li><p>autocomplete-modules: 自动补全模块名</p></li></ul>
<p><img src="/img/bVGqLs?w=1008&h=456" alt="图片描述" title="图片描述"></p>
<ul><li><p>auto-fold: 可以折叠代码</p></li></ul>
<p><img src="/img/bVGqLv?w=573&h=583" alt="图片描述" title="图片描述"></p>
<ul><li><p>file-icons: 根据文件类型显示不同的图标</p></li></ul>
<p><img src="/img/bVGqLx?w=204&h=340" alt="图片描述" title="图片描述"></p>
<ul><li><p>pigments:显示颜色</p></li></ul>
<p><img src="/img/bVGqLI?w=845&h=554" alt="图片描述" title="图片描述"></p>
<ul><li><p>color-picker: 以可视化的方式编辑颜色</p></li></ul>
<p><img src="/img/bVGqLN?w=500&h=550" alt="图片描述" title="图片描述"></p>
<ul><li><p>minimap:显示代码的缩略图,用过 sublime 的小伙伴会比较熟悉</p></li></ul>
<p><img src="/img/bVutN5" alt="图片描述" title="图片描述"></p>
<ul><li><p>ask-stack:当你有什么技术问题,你几乎都可以在 <a href="https://link.segmentfault.com/?enc=MXLAeSKm81JaRnrDGtAzWQ%3D%3D.niRZrDHUD7RBM1aOkvl3qj5%2F6YEWcVjiuFiI%2F0CsdR8%3D" rel="nofollow">stackoverflow</a> 上找到,而这个插件就是让你不用离开 Atom 就可以使用 stackoverflow</p></li></ul>
<p><img src="/img/bVGqLZ?w=1327&h=806" alt="图片描述" title="图片描述"></p>
<ul><li><p>project-manager:更好地管理你的项目</p></li></ul>
<p><img src="/img/bVGqL0?w=720&h=454" alt="图片描述" title="图片描述"></p>
<ul><li><p>javascript-snippet:高频语句的自动填充,是对 Atom 自带的 snippets 的一个拓展</p></li></ul>
<p><img src="/img/bVutNC" alt="图片描述" title="图片描述"></p>
<ul>
<li><p>editorconfig:自动对不同的项目配置不同的代码风格</p></li>
<li><p>emmet: 写 HTML 的利器,根据特定语法快速生成 HTML</p></li>
<li><p>language-babel: 支持 ES2015、JSX 等语法高亮</p></li>
</ul>
<h2>便捷的操作</h2>
<p>下面列举一些我常用的快捷操作,这些操作很大程度上帮助我提升了效率。部分内容会与上面的 Atom 特色重复。</p>
<ul>
<li><p>拖动一个文件夹到 Atom 窗口或者 Atom 应用图标,便能在 Atom 中打开这个文件夹</p></li>
<li><p>拖动一个文件到 Atom 窗口或者 Atom 应用图标,便在 Atom 中打开这个文件所在的文件夹</p></li>
<li><p>cmd + T / ctrl + T: 全局关键词快速模糊搜索</p></li>
<li><p>选中项目根目录,右键,选择 “Search in Directory”,可以全局准确搜索关键字</p></li>
<li><p>cmd + F / ctrl + F: 文件中关键词搜索及替换</p></li>
<li><p>选择多项:按住 cmd / ctrl,用鼠标点击另外一处你想选择的地方,这样,你就可以看到多个一起闪动的光标</p></li>
</ul>
<h2>Atom -- 常见问题解决</h2>
<h3>快捷键冲突</h3>
<ol>
<li><p>打开 设置 -> Keybingdings;</p></li>
<li><p>复制目标快捷键的配置信息,如下图所示,目标是将 <code>ctrl + alt + o</code> 快捷键配置为打开或关闭 <code>git-control</code>;</p></li>
</ol>
<p><img src="/img/bVGqMz?w=846&h=108" alt="图片描述" title="图片描述"></p>
<ol>
<li><p>打开 "keymap.cson"(ctrl + shift + p / cmd + shift + p, type "open keymap");</p></li>
<li><p>粘贴配置信息至文件末尾。</p></li>
</ol>
<h3>隐藏特定文件或文件夹</h3>
<p>我们在 Core 的 Ignored Names 中添加的文件或文件夹并不会在左侧文件栏中隐藏,需要我们额外设置。</p>
<ol>
<li><p>打开 设置 -> Packages;</p></li>
<li><p>找到 tree-view;</p></li>
<li><p>勾选 "Hide Ignored Names",搞定。</p></li>
</ol>
<p>以上是我在实际中对 Atom 的使用,如果你在实践中有其他更酷炫更有效的操作、package,记得 fork + pull request / 留言,如果你在使用中遇到了问题,也欢迎 fork + pull request / 留言。</p>
JavaScript 垃圾回收
https://segmentfault.com/a/1190000007616791
2016-11-27T22:24:13+08:00
2016-11-27T22:24:13+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p>根据 <a href="https://link.segmentfault.com/?enc=u3zsEVLHebnNB8bgoYEDKg%3D%3D.ASC41n6MPqSNsOc1UOxFRyptWeSzRLtZtt3HtPnbUOvym9%2FIVqlFRQHZw91n6iFhUuS8DFVkNJ9SWPTBCThPpg%3D%3D" rel="nofollow">Wiki</a> 的定义,<a href="https://link.segmentfault.com/?enc=JcfEG3gU%2FTRvYX6eE%2BzjPA%3D%3D.KZPsqhIetRHS7O03JZ88zA1n02jFeNSn9CXojLxPSmwje7lAo2BA7%2FL70sbppxjR7FnCy7AUyyQoMSF3RXpHb8c4rFIFERWxImf4RqWjzHdvxYgTbJEcLygHd%2BZ%2BGs8w%2FBvWvLPKzq3uUZEejA6QjaQeBBkfXwWXQf80ds4z4Hs%3D" rel="nofollow">垃圾回收</a>是一种自动的内存管理机制。当计算机上的动态内存不再需要时,就应该予以释放,以让出内存。直白点讲,就是程序是运行在内存里的,当声明一个变量、定义一个函数时都会占用内存。内存的容量是有限的,如果变量、函数等只有产生没有消亡的过程,那迟早内存有被完全占用的时候。这个时候,不仅自己的程序无法正常运行,连其他程序也会受到影响。好比生物只有出生没有死亡,地球总有被撑爆的一天。所以,在计算机中,我们需要垃圾回收。需要注意的是,定义中的“自动”的意思是语言可以帮助我们回收内存垃圾,但并不代表我们不用关心内存管理,如果操作失当,JavaScript 中依旧会出现内存溢出的情况。</p>
<p>垃圾回收基于两个原理:</p>
<ul>
<li><p>考虑某个变量或对象在未来的程序运行中将不会被访问</p></li>
<li><p>向这些对象要求归还内存</p></li>
</ul>
<p>而这两个原理中,最主要的也是最艰难的部分就是找到“所分配的内存确实已经不再需要了”。</p>
<h2>垃圾回收方法</h2>
<p>下面我们看看在 JavaScript 中是如何找到不再使用的内存的。主要有两种方式:引用计数和标记清除。</p>
<h3>引用计数(reference counting)</h3>
<p>在内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要”简化成“对象有没有其他对象引用到它”,如果没有对象引用这个对象,那么这个对象将会被回收。上例子:</p>
<pre><code class="javascript">let obj1 = { a: 1 }; // 一个对象(称之为 A)被创建,赋值给 obj1,A 的引用个数为 1
let obj2 = obj1; // A 的引用个数变为 2
obj1 = 0; // A 的引用个数变为 1
obj2 = 0; // A 的引用个数变为 0,此时对象 A 就可以被垃圾回收了</code></pre>
<p>但是引用计数有个最大的问题: 循环引用。</p>
<pre><code class="javascript">function func() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}</code></pre>
<p>当函数 func 执行结束后,返回值为 undefined,所以整个函数以及内部的变量都应该被回收,但根据引用计数方法,obj1 和 obj2 的引用次数都不为 0,所以他们不会被回收。</p>
<p>要解决循环引用的问题,最好是在不使用它们的时候手工将它们设为空。上面的例子可以这么做:</p>
<pre><code class="javascript">obj1 = null;
obj2 = null;</code></pre>
<h3>标记-清除(mark and sweep)</h3>
<p>这是 JavaScript 中最常见的垃圾回收方式。为什么说这是种最常见的方法,因为从 2012 年起,所有现代浏览器都使用了标记-清除的垃圾回收方法,除了低版本 IE...它们采用的是引用计数方法。</p>
<p>那什么叫标记清除呢?JavaScript 中有个全局对象,浏览器中是 window。定期的,垃圾回收期将从这个全局对象开始,找所有从这个全局对象开始引用的对象,再找这些对象引用的对象...对这些活着的对象进行标记,这是标记阶段。清除阶段就是清除那些没有被标记的对象。</p>
<p>标记-清除法的一个问题就是不那么有效率,因为在标记-清除阶段,整个程序将会等待,所以如果程序出现卡顿的情况,那有可能是收集垃圾的过程。</p>
<p>2012 年起,所有现代浏览器都使用了这个方法,所有的改进也都是基于这个方法,比如标记-整理方法。</p>
<p>标记清除有一个问题,就是在清除之后,内存空间是不连续的,即出现了内存碎片。如果后面需要一个比较大的连续的内存空间时,那将不能满足要求。而标记-整理方法可以有效地解决这个问题。标记阶段没有什么不同,只是标记结束后,标记-整理方法会将活着的对象向内存的一边移动,最后清理掉边界的内存。不过可以想象,这种做法的效率没有标记-清除高。计算机中的很多做法都是互相妥协的结果,哪有什么十全十美的事儿呢。</p>
<h2>内存泄漏</h2>
<p>在谈什么是良好实践(这里指有益于内存管理)之前,我想先谈谈内存泄漏,也就是差的实践。内存泄漏是指计算机可用的内存越来越少,主要是因为程序不能释放那些不再使用的内存。</p>
<h3>循环引用</h3>
<p>这个没什么好说的,上面已经介绍了。</p>
<p>需要强调的一点就是,一旦数据不再使用,最好通过将其值设为 null 来释放其引用,这个方法被称为“解除引用”。</p>
<h3>无意的全局变量</h3>
<pre><code class="javascript">function foo(arg) {
const bar = "";
}
foo();</code></pre>
<p>当 foo 函数执行后,变量 bar 就会被标记为可回收。因为当函数执行时,函数创造了一个作用域来让函数里的变量在里面声明。进入这个作用域后,浏览器就会为变量 bar 创建一个内存空间。当这个函数结束后,其所创建的作用域里的变量也会被标记为垃圾,在下一个垃圾回收周期到来时,这些变量将会被回收。</p>
<p>但事情并不会那么顺利。</p>
<pre><code class="javascript">function foo(arg) {
bar = "";
}
foo();</code></pre>
<p>上面的代码就无意中声明了一个全局变量,会得到 window 的引用,bar 实际上是 window.bar,它的作用域在 window 上,所以 foo 函数执行结束后,bar 也不会被内存收回。</p>
<p>另外一种无意的全局变量的情况是:</p>
<pre><code class="javascript">function foo() {
this.bar = "";
}</code></pre>
<p>在 foo 函数中,this 指的是 window(详细内容可参见我的另一篇<a href="https://link.segmentfault.com/?enc=MeaejG9d%2FpTFOu15W6cSdQ%3D%3D.so8Qp9ThYOfAhDs20DIXUEeZapWfwizwzI9bbEdmcPC2EEuPRMQgS4f80kRHZ2NtY01MtEWrQWIzg7eiewIeEg%3D%3D" rel="nofollow">博客:JavaScript this 讲解</a>),犯的错误跟上面类似。</p>
<h3>被遗忘的计时器和回调函数</h3>
<pre><code class="javascript">let someResource = getData();
setInterval(() => {
const node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);</code></pre>
<p>上面的例子中,我们每隔一秒就将得到的数据放入到文档节点中去。但在 setInterval 没有结束前,回调函数里的变量以及回调函数本身都无法被回收。那什么才叫结束呢?就是调用了 clearInterval。如果回调函数内没有做什么事情,并且也没有被 clear 掉的话,就会造成内存泄漏。不仅如此,如果回调函数没有被回收,那么回调函数内依赖的变量也没法被回收。上面的例子中,someResource 就没法被回收。同样的,setTiemout 也会有同样的问题。所以,当不需要 interval 或者 timeout 时,最好调用 clearInterval 或者 clearTimeout。</p>
<h3>DOM</h3>
<p>在 IE8 以下的版本里,DOM 对象经常会跟 JavaScript 之间产生循环引用。看一个例子:</p>
<pre><code class="javascript">function setHandler() {
const ele = document.getElementById('id');
ele.onclick = function() {};
}</code></pre>
<p>在这个例子中,DOM 对象通过 onclick 引用了一个函数,然而这个函数通过外部的词法环境引用了这个 DOM 对象,形成了循环引用。不过现在不必担心,因为所有现代浏览器都采用了标记-整理方法,避免了循环引用的问题。</p>
<p>除了这种情况,我们现在还会在其他时候在使用 DOM 时出现内存泄漏的问题。当我们需要多次访问同一个 DOM 元素时,一个好的做法是将 DOM 元素用一个变量存储在内存中,因为访问 DOM 的效率一般比较低,应该避免频繁地反问 DOM 元素。所以我们会这样写:</p>
<pre><code class="javascript">const button = document.getElementById('button');</code></pre>
<p>当删除这个按钮时:</p>
<pre><code class="javascript">document.body.removeChild(document.getElementById('button'));</code></pre>
<p>虽然这样看起来删除了这个 DOM 元素,但这个 DOM 元素仍然被 button 这个变量引用,所以在内存上,这个 DOM 元素是没法被回收的。所以在使用结束后,还需要将 button 设成 null。</p>
<p>另外一个值得注意的是,代码中保存了一个列表 ul 的某一项 li 的引用,将来决定删除整个列表时,我们自觉上会认为内存仅仅会保留那个特定的 li,而将其他列表项都删除。但事实并非如此,因为 li 是 ul 的子元素,子元素与父元素是引用关系,所以如果代码保存 li 的引用,那么整个 ul 将会继续呆在内存里。</p>
<h2>良好实践</h2>
<p>1、优化内存的一个最好的衡量方式就是只保留程序运行时需要的数据,对于已经使用的或者不需要的数据,应该将其值设为 null,这上面说过,叫“解除引用”。需要注意的是,解除一个值的引用不代表垃圾回收器会立即将这段内存回收,这样做的目的是让垃圾回收器在下一个回收周期到来时知道这段内存需要回收。</p>
<p>在内存泄漏部分,我们讨论了无意的全局变量会带来无法回收的内存垃圾。但有些时候,我们会有意识地声明一些全局变量,这个时候需要注意,如果声明的变量占用大量的内存,那么在使用完后将变量声明为 null。</p>
<p>2、减少内存垃圾的另一个方法就是避免创建对象。<code>new Object()</code> 是一个比较明显的创建对象的方式,另外 <code>const arr = [];</code>、<code>const obj = {};</code>也会创建新的对象。另外下面这种写法在每次调用函数时都会创建一个新的对象:</p>
<pre><code class="javascript">function func() {
return function() {};
}</code></pre>
<p>另外,当清空一个数组时,我们通常的做法是 <code>array = []</code>,但这种做法的背后是新建了一个新的数组然后将原来的数组当作内存垃圾。建议的做法是 <code>array.length = 0</code>,这样做不仅可以重用原来的变量,而且还避免创建了新的数组。</p>
<p></p>
<p>因为时间关系,关于垃圾回收的内容将在接下来1-2周内更新完毕,内容涉及更加详细的内存管理、V8 引擎中的垃圾回收等。另外对本文其他内容还有建议的也欢迎留言,我也会一并更新。</p>
<p>参考:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=Mh%2B%2FUQUAN0m21lh83x4SUA%3D%3D.SwW0LxNsmHGGYTSiwbPGe9TmrVjtd0AjI8ERiotaumPpI2Danf56slcNZlAX%2BP%2Bln1AhRaVeF8BINsF7wUQcehAFdC6UlWiZk3s%2BWVc%2B9BA%3D" rel="nofollow">内存管理</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=vJVh%2BQVyonWA9pWIxGdNSw%3D%3D.35W7XJQinTXPwKKNMYOWqjx8Aycu5U1h9bKAcpdeYFYt5uweqLwQ4YP7pXlvYDPK8sASgdz5JyGA73fl4RfbRBRttUib5Ki3Fywl9VCubl4%3D" rel="nofollow">A tour of V8: Garbage Collection</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=8VQYw2YxP%2FYfXV%2BqfhboUA%3D%3D.hpX4HJVhppX2bMYwVAyGM8wJ7alncDTCSPtD1vkrZT8%2Fe3cmZg7omkolR9Nd6v7L" rel="nofollow">Memory leaks</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=i1yIN9Uqyt%2FDLLCDMSt8JQ%3D%3D.rX1Lto7pqEGdHL0jbTN6RrnEX0La4BnFNTB1W5n9yt96xwYgxxTLBNagtKzyaDqAAV9wPrv4Zu7jiEHYPc7jCC9BvvOTS93rB%2BctInD0CP%2Fn19sij3ToILPLRJyIAmH4" rel="nofollow">4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=kwIjpsF6Qq7wneqd%2FxnlGQ%3D%3D.aA92xiUkAeI14mf8udvFUn9mCUujylJyVLgfBVGax3CwWrgEwqUUYz4iXr5WA0TyCIGUOpdBOSLwOMnelfanxQ%3D%3D" rel="nofollow">High-Performance, Garbage-Collector-Friendly Code</a></p></li>
</ol>
《黑客与画家》读书笔记
https://segmentfault.com/a/1190000007338269
2016-10-31T22:48:23+08:00
2016-10-31T22:48:23+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
1
<p><img src="/img/remote/1460000007338272?w=3181&h=3978" alt="" title=""></p>
<p><img src="/img/remote/1460000007338273" alt="" title=""></p>
<p><img src="/img/remote/1460000007338274" alt="" title=""></p>
《软件管理沉思录》读书笔记
https://segmentfault.com/a/1190000007178602
2016-10-15T18:56:35+08:00
2016-10-15T18:56:35+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<p><img src="/img/remote/1460000007178605?w=5488&h=3368" alt="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~" title="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~"></p>
<p><img src="/img/remote/1460000007178606?w=3113&h=3930" alt="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~" title="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~"></p>
<p><img src="/img/remote/1460000007178607?w=3290&h=5562" alt="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~" title="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~"></p>
<p><img src="/img/remote/1460000007178608?w=4734&h=4606" alt="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~" title="唉,对 segmentfault 的图片上传功能也是无语了。算了,就当这个功能不存在吧~"></p>
JavaScript arguments 对象全面介绍
https://segmentfault.com/a/1190000007091243
2016-10-08T09:10:20+08:00
2016-10-08T09:10:20+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
5
<h2>1. 什么是 arguments</h2>
<p><a href="https://link.segmentfault.com/?enc=%2FyjMe%2BwKIthLbQk3Icdh7A%3D%3D.LsG3NTj2bdhQsRbCG6eWz5TYNQrQ0IACOeQKO6loF5ZkZlx2M8VhQb50V95X6wkjTWjBfiNj6LlfINzdptSHKtbou5zBbvoQDQZGoMpQQcOLnko2AIqAP%2B%2BvkPmt6oqz" rel="nofollow">MDN</a> 上解释:</p>
<blockquote><p>arguments 是一个类数组对象。代表传给一个function的参数列表。</p></blockquote>
<p>我们先用一个例子直观了解下 JavaScript 中的 arguments 长什么样子。</p>
<pre><code>function printArgs() {
console.log(arguments);
}
printArgs("A", "a", 0, { foo: "Hello, arguments" });
</code></pre>
<p>执行结果是:</p>
<pre><code>["A", "a", 0, Object]
</code></pre>
<p>乍一看,结果是个数组,但并不是真正的数组,所以说 arguments 是一个类数组的对象(想了解真正数组与类数组对象的区别可以一直翻到最后)。</p>
<p>再看看 arguments 表示的内容,其表示了函数执行时传入函数的所有参数。在上面的例子中,代表了传入 <code>printArgs</code> 函数中的四个参数,可以分别用 <code>arguments[0]</code>、 <code>arguments[1]</code>... 来获取单个的参数。</p>
<h2>2. arguments 操作</h2>
<h3>2.1 arguments length</h3>
<p>arguments 是个类数组对象,其包含一个 <code>length</code> 属性,可以用 <code>arguments.length</code> 来获得传入函数的参数个数。</p>
<pre><code>function func() {
console.log("The number of parameters is " + arguments.length);
}
func();
func(1, 2);
func(1, 2, 3);
</code></pre>
<p>执行结果如下:</p>
<pre><code>The number of parameters is 0
The number of parameters is 2
The number of parameters is 3
</code></pre>
<h3>2.2 arguments 转数组</h3>
<p>通常使用下面的方法来将 arguments 转换成数组:</p>
<pre><code>Array.prototype.slice.call(arguments);
</code></pre>
<p>还有一个更简短的写法:</p>
<pre><code>[].slice.call(arguments);
</code></pre>
<p>在这里,只是简单地调用了空数组的 slice 方法,而没有从 Array 的原型层面调用。</p>
<p>为什么上面两种方法可以转换呢?</p>
<p>首先,slice 方法得到的结果是一个数组,参数便是 arguments。事实上,满足一定条件的对象都能被 slice 方法转换成数组。看个例子:</p>
<pre><code>const obj = { 0: "A", 1: "B", length: 2 };
const result = [].slice.call(obj);
console.log(Array.isArray(result), result);
</code></pre>
<p>执行结果是:</p>
<pre><code>true ["A", "B"]
</code></pre>
<p>从上面例子可以看出,条件就是: 1) 属性为 0,1,2...;2) 具有 length 属性;</p>
<p>另外,有一个需要注意的地方就是,<strong>不能将函数的 arguments 泄露或者传递出去</strong>。什么意思呢?看下面的几个泄露 arguments 的例子:</p>
<pre><code>// Leaking arguments example1:
function getArgs() {
return arguments;
}
// Leaking arguments example2:
function getArgs() {
const args = [].slice.call(arguments);
return args;
}
// Leaking arguments example3:
function getArgs() {
const args = arguments;
return function() {
return args;
};
}
</code></pre>
<p>上面的做法就直接将函数的 arguments 对象泄露出去了,最终的结果就是 V8 引擎将会跳过优化,导致相当大的性能损失。</p>
<p>你可以这么做:</p>
<pre><code>function getArgs() {
const args = new Array(arguments.length);
for(let i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
return args;
}
</code></pre>
<p>那就很好奇了,我们每次使用 arguments 时通常第一步都会将其转换为数组,同时 arguments 使用不当还容易导致性能损失,那么为什么不将 arguments 直接设计成数组对象呢?</p>
<p>这需要从这门语言的一开始说起。arguments 在语言的早期就引入了,当时的 Array 对象具有 4 个方法: toString、 join、 reverse 和 sort。arguments 继承于 Object 的很大原因是不需要这四个方法。而现在,Array 添加了很多强大的方法,比如 forEach、map、filter 等等。那为什么现在不在新的版本里让 arguments 重新继承自 Array呢?其实 ES5 的草案中就包含这一点,但为了向前兼容,最终还是被委员会否决了。</p>
<h3>2.3 修改 arguments 值</h3>
<p>在严格模式与非严格模式下,修改函数参数值表现的结果不一样。看下面的两个例子:</p>
<pre><code>function foo(a) {
"use strict";
console.log(a, arguments[0]);
a = 10;
console.log(a, arguments[0]);
arguments[0] = 20;
console.log(a, arguments[0]);
}
foo(1);
</code></pre>
<p>输出:</p>
<pre><code>1 1
10 1
10 20
</code></pre>
<p>另一个非严格模式的例子:</p>
<pre><code>function foo(a) {
console.log(a, arguments[0]);
a = 10;
console.log(a, arguments[0]);
arguments[0] = 20;
console.log(a, arguments[0]);
}
foo(1);
</code></pre>
<p>输出结果为:</p>
<pre><code>1 1
10 10
20 20
</code></pre>
<p>从上面的两个例子中可以看出,在严格模式下,函数中的参数与 arguments 对象没有联系,修改一个值不会改变另一个值。而在非严格模式下,两个会互相影响。</p>
<h3>2.4 将参数从一个函数传递到另一个函数</h3>
<p>下面是将参数从一个函数传递到另一个函数的推荐做法。</p>
<pre><code>function foo() {
bar.apply(this, arguments);
}
function bar(a, b, c) {
// logic
}
</code></pre>
<h3>2.5 arguments 与重载</h3>
<p>很多语言中都有重载,但 JavaScript 中没有。先看个例子:</p>
<pre><code>function add(num1, num2) {
console.log("Method one");
return num1 + num2;
}
function add(num1, num2, num3) {
console.log("Method two");
return num1 + num2 + num3;
}
add(1, 2);
add(1, 2, 3);
</code></pre>
<p>执行结果为:</p>
<pre><code>Method two
Method two
</code></pre>
<p>所以,JavaScript 中,函数并没有根据参数的不同而产生不同的调用。</p>
<p>是不是 JavaScript 中就没有重载了呢?并不是,我们可以利用 arguments 模拟重载。还是上面的例子。</p>
<pre><code>function add(num1, num2, num3) {
if (arguments.length === 2) {
console.log("Result is " + (num1 + num2));
}
else if (arguments.length === 3) {
console.log("Result is " + (num1 + num2 + num3));
}
}
add(1, 2);
add(1, 2, 3)
</code></pre>
<p>执行结果如下:</p>
<pre><code>Result is 3
Result is 6
</code></pre>
<h2>3. ES6 中的 arguments</h2>
<h3>3.1 扩展操作符</h3>
<p>直接上栗子:</p>
<pre><code>function func() {
console.log(...arguments);
}
func(1, 2, 3);
</code></pre>
<p>执行结果是:</p>
<pre><code>1 2 3
</code></pre>
<p>简洁地讲,扩展操作符可以将 arguments 展开成独立的参数。</p>
<h3>3.2 Rest 参数</h3>
<p>还是上栗子:</p>
<pre><code>function func(firstArg, ...restArgs) {
console.log(Array.isArray(restArgs));
console.log(firstArg, restArgs);
}
func(1, 2, 3);
</code></pre>
<p>执行结果是:</p>
<pre><code>true
1 [2, 3]
</code></pre>
<p>从上面的结果可以看出,Rest 参数表示除了明确指定剩下的参数集合,类型是 Array。</p>
<h3>3.3 默认参数</h3>
<p>栗子:</p>
<pre><code>function func(firstArg = 0, secondArg = 1) {
console.log(arguments[0], arguments[1]);
console.log(firstArg, secondArg);
}
func(99);
</code></pre>
<p>执行结果是:</p>
<pre><code>99 undefined
99 1
</code></pre>
<p>可见,默认参数对 arguments 没有影响,arguments 还是仅仅表示调用函数时所传入的所有参数。</p>
<h3>3.4 arguments 转数组</h3>
<p><code>Array.from()</code> 是个非常推荐的方法,其可以将所有类数组对象转换成数组。</p>
<h2>4. 数组与类数组对象</h2>
<p>数组具有一个基本特征:索引。这是一般对象所没有的。</p>
<pre><code>const obj = { 0: "a", 1: "b" };
const arr = [ "a", "b" ];
</code></pre>
<p>我们利用 <code>obj[0]</code>、<code>arr[0]</code> 都能取得自己想要的数据,但取得数据的方式确实不同的。<code>obj[0]</code> 是利用对象的键值对存取数据,而 <code>arr[0]</code> 却是利用数组的索引。事实上,Object 与 Array 的唯一区别就是 Object 的属性是 string,而 Array 的索引是 number。</p>
<p>下面看看类数组对象。</p>
<p>伪数组的特性就是长得像数组,包含一组数据以及拥有一个 length 属性,但是没有任何 Array 的方法。再具体的说,length 属性是个非负整数,上限是 JavaScript 中能精确表达的最大数字;另外,类数组对象的 length 值无法自动改变。</p>
<p>如何自己创建一个类数组对象?</p>
<pre><code>function Foo() {}
Foo.prototype = Object.create(Array.prototype);
const foo = new Foo();
foo.push('A');
console.log(foo, foo.length);
console.log("foo is an array? " + Array.isArray(foo));
</code></pre>
<p>执行结果是:</p>
<pre><code>["A"] 1
foo is an array? false
</code></pre>
<p>也就是说 Foo 的示例拥有 Array 的所有方法,但类型不是 Array。</p>
<p>如果不需要 Array 的所有方法,只需要部分怎么办呢?</p>
<pre><code>function Bar() {}
Bar.prototype.push = Array.prototype.push;
const bar = new Bar();
bar.push('A');
bar.push('B');
console.log(bar);
</code></pre>
<p>执行结果是:</p>
<pre><code>Bar {0: "A", 1: "B", length: 2}
</code></pre>
<p>参考:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=%2BLHh8jEdhLii0xb%2F6utm2g%3D%3D.bb4u%2FudtTPpB0bi9fviNTvDPchCoREtnVHDd%2ByOJSst75%2Bv2XCV6HES2ao%2BQ0Edgk638pzptn8EzjYFujGy4zQ%3D%3D" rel="nofollow">JavaScript中的数组与伪数组的区别</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=RXb%2FWF0Zu6fFjpCDWPOOUw%3D%3D.GQzcfUDiG%2BG8a9e9kUAgHCWj7g%2F1k%2FagzP%2FR8NX6%2F61t%2FtyvM%2BjEQsu3L5bMTIs4KS2Q52IwVDPRK0g%2B5yLb%2BiP25eyCh70iSUkyc2OfzXumnB0wClFjwGti%2B44nwqvP" rel="nofollow">MDN arguments</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=X0vuiQfPLzqEotTlmOHSOQ%3D%3D.Q51810eyRPhCiM8dDHHzNtLhLurRu1pmXw%2FFdWUwy%2Fq7MNb8ijddlJENwnGNp41vOAXBmNBaZ3JlGkxolmdiVhJl94C28Apx4BwoFx9Q0ZoKBaKpo2TXuRXtRCY0puUxV6Kr63167KAg%2BqhAmh9pQA%3D%3D" rel="nofollow">Avoid modifying or passing arguments into other functions — it kills optimization</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=cLg2wYuQgty1EN3LStvgkg%3D%3D.E76JdwKyYbnT8pzcgAB88YGm4jpPUzJNi9Y%2BrRsSYPkjeB2xADK0yuji7cYHg2S3qna4njI%2Fkj0xzs0K2Ibe0R8yicriGsiZRzWvce8G%2FuqTGd1%2B9giA%2FGZuodCQw7jS" rel="nofollow">Optimization killers</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=jOik7FkI8vjowJovfTz%2Fjw%3D%3D.okpWOpDqtdZxc5kJNK2IEb5KifhkaLA96iz2FGaic1IexY5CJ2FS6jnuBvmmHwtZoNNTHUwo7QKmn%2FV3BDKIPjOIjh6eTP9CesfEt%2FQOoxlNcRXJBQ8v2r6imAcxttMfTyWe3jLfvYzdOzbrm2lBHA%3D%3D" rel="nofollow">Why isn't a function's arguments object an array in Javascript?</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=FPdvz%2FQ7CsGDPSAeJ7%2FqfQ%3D%3D.83Ax1Jt6TyHDeIuzSwUm3QqxvH0DvHid2LiXQt3PnbkK4v0DfSbmv%2Bh9lnj30NRCZWls%2F5oazk919iglwJWSkEnUq1Xhufx9eB5JH1c8Wgg%3D" rel="nofollow">arguments 对象</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=JRf2I3G1PoRGwYmBqXVwwQ%3D%3D.KYhjy%2FTP227wHFh%2F7I3r91%2Bp3BBRa%2Bvfnceed57N%2FIU2Mgu2QqM3riMPoRHKUhXFaZsJ0assP44h%2B4tuZtW5hwAa6Oydyoyq7Lppun79vvpliMr3FeFnH43rs%2B5LHeEpqzR54xDGeuRErYsEHN30Yg%3D%3D" rel="nofollow">Advanced Javascript: Objects, Arrays, and Array-Like objects</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=AReYU7ietpCkl%2F2JYa44Rw%3D%3D.Zl6sgEzf9lEH9O35NRXda0DiMyeq7ppeVarnj5TRyPI%3D" rel="nofollow">JavaScript 特殊对象 Array-Like Objects 详解</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=DcqvZ09fbObZb9lxH7iJWQ%3D%3D.altGbkD4qafWzKsCksVjtoHQC%2BXNdOc3tP7tzDlSqnDf9MqfkEZvTJFHeWGgzpO56WLdEuha%2F%2Fo9rLinRFXBcKN8aoEpA%2BYXuOiIWqh8HZrgFjLLR3OXT89ctcdqzOeVoCkcHjADPrWzQtP1KzUVuw%3D%3D" rel="nofollow">What is a good way create a Javascript array-like object?</a></p></li>
</ol>
《人件》读书笔记
https://segmentfault.com/a/1190000007062816
2016-10-02T16:05:16+08:00
2016-10-02T16:05:16+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
1
<p><img src="http://i.imgur.com/bp0tbD4.jpg" alt="" title=""></p>
<p><img src="http://i.imgur.com/Yj47qhQ.jpg" alt="" title=""></p>
<p><img src="http://i.imgur.com/vhOKMzf.jpg" alt="" title=""></p>
<p><img src="http://i.imgur.com/4VwKAc8.jpg" alt="" title=""></p>
<p><img src="http://i.imgur.com/F0E6gaF.jpg" alt="" title=""></p>
<p><img src="http://i.imgur.com/BiuZhs6.png" alt="" title=""></p>
<p>同样的,对脑图感兴趣可以私戳~</p>
web 安全入门
https://segmentfault.com/a/1190000007028595
2016-09-28T10:53:35+08:00
2016-09-28T10:53:35+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
6
<p>搞 Web 开发离不开安全这个话题,确保网站或者网页应用的安全性,是每个开发人员都应该了解的事。本篇主要简单介绍在 Web 领域几种常见的攻击手段。</p>
<h2>1. Cross Site Script(XSS, 跨站脚本攻击)</h2>
<p>首先插播一句,为毛叫 XSS,缩写明显是 CSS 啊?没错,为了防止与我们熟悉的 CSS(Cascading Style Sheets)混淆,所以干脆更名为 XSS。</p>
<p>那 XSS 是什么呢?一言蔽之,XSS 就是攻击者在 Web 页面中插入恶意脚本,当用户浏览页面时,促使脚本执行,从而达到攻击目的。XSS 的特点就是想尽一切办法在目标网站上执行第三方脚本。</p>
<p><img src="/img/remote/1460000007028598?w=555&h=397" alt="" title=""></p>
<p>( 图片来源:<a href="https://link.segmentfault.com/?enc=VXCHNcqyd4RZwrlECDBhZQ%3D%3D.iQowizrqq%2FvXJer9cGMfSGrAzXPPnw5NRsks6aongszTlrf8nVj6b%2FDCnQdbnEiU" rel="nofollow">XSS Tutorial</a> )</p>
<p>举个例子。原有的网站有个将数据库中的数据显示到页面的上功能,<code>document.write("data from server")</code>。但如果服务器没有验证数据类型,直接接受任何数据时,攻击者可以会将 <code><script src='http:bad-script.js'></scirpt></code> 当做一个数据写入数据库。当其他用户请求这个数据时,网站原有的脚本就会执行 <code>document.write("<script src='http://www.evil.com/bad-script.js'></scirpt>")</code>,这样,便会执行 <code>bad-script.js</code>。如果攻击者在这段第三方的脚本中写入恶意脚本,那么普通用户便会受到攻击。</p>
<p>XSS 主要有三种类型:</p>
<ul>
<li><p>存储型 XSS: 注入的脚本永久的存在于目标服务器上,每当受害者向服务器请求此数据时就会重新唤醒攻击脚本;</p></li>
<li><p>反射型 XSS: 当用受害者被引诱点击一个恶意链接,提交一个伪造的表单,恶意代码便会和正常返回数据一起作为响应发送到受害者的浏览器,从而骗过了浏览器,使之误以为恶意脚本来自于可信的服务器,以至于让恶意脚本得以执行。</p></li>
</ul>
<p><img src="/img/remote/1460000007326542?w=408&h=274" alt="" title=""></p>
<p>( 图片来源: <a href="https://link.segmentfault.com/?enc=702aQ7Pzq67RAeBv3r1POg%3D%3D.2sXUDOrNQ%2BC5Vv23y3rOpQJ8jlIGyY6BzHRRe80wePF11OItziuLDjHDZGT7NpbZqSy9ZlO1KQJV%2BQY2i3sAgw%3D%3D" rel="nofollow">Cross-Site Scripting (XSS)</a> )</p>
<ul><li><p>DOM 型 XSS: 有点类似于存储型 XSS,但存储型 XSS 是将恶意脚本作为数据存储在服务器中,每个调用数据的用户都会受到攻击。但 DOM 型 XSS 则是一个本地的行为,更多是本地更新 DOM 时导致了恶意脚本执行。</p></li></ul>
<p>那么如何防御 XSS 攻击呢?</p>
<ul>
<li><p>从客户端和服务器端双重验证所有的输入数据,这一般能阻挡大部分注入的脚本</p></li>
<li><p>对所有的数据进行适当的编码</p></li>
<li><p>设置 HTTP Header: "X-XSS-Protection: 1"</p></li>
</ul>
<h2>2. SQL Injection (SQL 注入)</h2>
<p>所谓 SQL 注入,就是通过客户端的输入把 SQL 命令注入到一个应用的数据库中,从而得以执行恶意 SQL 语句。</p>
<p>先看个例子。</p>
<pre><code class="sql">uname = request.POST['username']
password = request.POST['password']
sql = "SELECT all FROM users WHERE username='" + uname + "' AND password='" + password + "'"
database.execute(sql)</code></pre>
<p>上面这段程序直接将客户端传过来的数据写入到数据库。试想一下,如果用户传入的 <code>password</code> 值是: "password’ OR 1=1",那么 sql 语句便会变成:</p>
<pre><code>sql = "SELECT all FROM users WHERE username='username' AND password='password' OR 1=1"</code></pre>
<p>那么,这句 sql 无论 <code>username</code> 和 <code>password</code> 是什么都会执行,从而将所有用户的信息取出来。</p>
<p>那么怎么预防 sql 的问题呢?</p>
<p>想要提出解决方案,先看看 sql 注入得以施行的因素:</p>
<ul>
<li><p>网页应用使用 SQL 来控制数据库</p></li>
<li><p>用户传入的数据直接被写入数据库</p></li>
</ul>
<p>根据 <a href="https://link.segmentfault.com/?enc=riWV6TruwsIc7yOh1JAraw%3D%3D.%2BCQ%2BfBUrj8fMAWvks6DhSy9FoYlcZC0xceyu%2FS7mi56AxzyOl%2BUwNsE5ZmFWn6L9" rel="nofollow">OWASP</a>,下面看看具体的预防措施。</p>
<ul>
<li><p>Prepared Statements (with Parameterized Queries): 参数化的查询语句可以强制应用开发者首先定义所有的 sql 代码,之后再将每个参数传递给查询语句</p></li>
<li><p>Stored Procedures: 使用语言自带的存储程序,而不是自己直接操纵数据库</p></li>
<li><p>White List Input Validation: 验证用户的输入</p></li>
<li><p>Escaping All User Supplied Input: 对用户提供的所有的输入都进行编码</p></li>
</ul>
<h2>3. Distributed Denial of Service (DDoS, 分布式拒绝服务)</h2>
<p>DoS 攻击就是通过大量恶意流量占用带宽和计算资源以达到瘫痪对方网络的目的。</p>
<p>举个简单的例子,老郑家面馆生意红火,突然有一天一群小混混进了点,霸占了座位,只闲聊不点菜,结果坐在店里的人不吃面,想吃面的人进不来,导致老郑无法向正常客户服务。</p>
<p>而 DDoS 攻击就是将多个计算机联合起来一同向目标发起攻击,从而成倍地提高拒绝服务攻击的威力。</p>
<p><img src="/img/remote/1460000007028599?w=1134&h=768" alt="" title=""></p>
<p>(图片来源于: <a href="https://link.segmentfault.com/?enc=3hZeUahUSFWfy%2FYZxgMeug%3D%3D.GAAN%2BfKvB6Ot91uiSl4qpEy0nB1GDvJoyreCRx0CLv6nv%2F32uzEyfudGehQLHSFP6tBSeR2vheyNhCrQM8Abg54zcA%2BFBRRrSTfM%2FW5YT8tJipWwMgug4FF0M08DmncF" rel="nofollow">DDoSCoin - An Incentive to Launch DDoS Attacks?</a>)</p>
<p>一般 DDoS 攻击有两个目的:</p>
<ul>
<li><p>敲诈勒索,逼你花钱买平安</p></li>
<li><p>打击竞争对手</p></li>
</ul>
<p>在技术角度上,DDoS攻击可以针对网络通讯协议的各层,手段大致有:TCP类的SYN Flood、ACK Flood,UDP类的Fraggle、Trinoo,DNS Query Flood,ICMP Flood,Slowloris类、各种社工方式等等,这些技术这里不做详细解释。但是一般会根据攻击目标的情况,针对性的把技术手法混合,以达到最低的成本最难防御的目的,并且可以进行合理的节奏控制,以及隐藏保护攻击资源。</p>
<p>阿里巴巴的安全团队在实战中发现,DDoS 防御产品的核心是检测技术和清洗技术。检测技术就是检测网站是否正在遭受 DDoS 攻击,而清洗技术就是清洗掉异常流量。而检测技术的核心在于对业务深刻的理解,才能快速精确判断出是否真的发生了 DDoS 攻击。清洗技术对检测来讲,不同的业务场景下要求的粒度不一样。</p>
<h2>4. Cross Site Request Forgery (CSRF, 跨站请求伪造)</h2>
<p>简单来说,CSRF 就是网站 A 对用户建立信任关系后,在网站 B 上利用这种信任关系,跨站点向网站 A 发起一些伪造的用户操作请求,以达到攻击的目的。</p>
<p>举个例子。网站 A 是一家银行的网站,一个转账接口是 "http://www.bankA.com/transfer?toID=12345678&cash=1000"。toID 表示转账的目标账户,cash 表示转账数目。当然这个接口没法随便调用,只有在已经验证的情况下才能够被调用。</p>
<p>此时,攻击者建立了一个 B 网站,里面放了一段隐藏的代码,用来调用转账的接口。当受害者先成功登录了 A 网站,短时间内不需要再次验证,这个时候又访问了网站 B,B 里面隐藏的恶意代码就能够成功执行。</p>
<p>那怎么预防 CSRF 攻击呢?<a href="https://link.segmentfault.com/?enc=n92ELY1dQxEexdpWVruJoQ%3D%3D.uW%2BSCT4lNdYCbQgqVw1JrQObvqF5KXV%2FhLtNO9wbxNde7qjH3LvSS8weHzztuQjS" rel="nofollow">OWASP</a> 推荐了两种检查方式来作为防御手段。</p>
<ul>
<li><p>检查标准头部,确认请求是否同源: 检查 source origin 和 target origin,然后比较两个值是否匹配</p></li>
<li>
<p>检查 CSRF Token: 主要有四种推荐的方式</p>
<ul>
<li><p>Synchronizer Tokens: 在表单里隐藏一个随机变化的 token,每当用户提交表单时,将这个 token 提交到后台进行验证,如果验证通过则可以继续执行操作。这种情况有效的主要原因是网站 B 拿不到网站 A 表单里的 token;</p></li>
<li><p>Double Cookie Defense: 当向服务器发出请求时,生成一个随机值,将这个随机值既放在 cookie 中,也放在请求的参数中,服务器同时验证这两个值是否匹配;</p></li>
<li><p>Encrypted Token Pattern: 对 token 进行加密</p></li>
<li><p>Custom Header: 使用自定义请求头部,这个方式依赖于同源策略。其中最适合的自定义头部便是: "X-Requested-With: XMLHttpRequest"</p></li>
</ul>
</li>
</ul>
<p>参考:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=0zchrxHRT0THMFT3xGasCQ%3D%3D.WoJJeGMsKCjfLHtAknJobFnDWShyLvNhsvzs%2F9L%2Fr9b2VDt%2ByWuYlNwRydTWlpva4MlwvyTul3mV2GpGX4W%2BEXAcDov8SaxFJMYeirz5Cs8%3D" rel="nofollow">Cross-site scripting -- MDN</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=I0vcpb%2BgOIgcQr2%2FtMkvTA%3D%3D.zQz%2BFliPiEI6%2FYPRHT%2Bf%2BVJEa7ejkw6qICmqJSCzIPB4Zt%2BVDBVzumW%2BlRU%2F3TgW" rel="nofollow">XSS Tutorial</a></p></li>
<li><p>微信公众号【给产品经理讲技术】 -- Web安全之CSRF攻击</p></li>
<li><p><a href="https://link.segmentfault.com/?enc=fWW9qPQBGQzXoOYL9TbTRQ%3D%3D.Tf4%2Bxt1Y6Ms%2Boqr6NWCKJBe30u32Wv0qGUyDSFcONuNmAoJsFN9V2o3owT8NAEWA0yFxcAYGAR7xHW9tSNmQ3uty6DQl%2FpVltkt4cchMCoE%3D" rel="nofollow">Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet</a>_Prevention_Cheat_Sheet)</p></li>
<li><p><a href="https://link.segmentfault.com/?enc=klO0QbTFUlx1rarLfBK5Gw%3D%3D.J2z0NCAXgyiEUrbaIYatpdhbndT2lNmfcl5aFjWer11XvrYeCA9a%2F0hf4tfNIr6ZXhcyya2RvEXVTSZ56Sbgmg%3D%3D" rel="nofollow">SQL Injection (SQLi)</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=GiQV52OTmDQN5t5RbtuYfw%3D%3D.lMZsCDlUH%2FifMAxvoJF%2BZ392Cr3GtIfYAGfWI6jRa0KhvnfZTeasjCj8NxBxOnuz" rel="nofollow">SQL Injection</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=3a%2FQAgnqgRZoUWkpOMYM8g%3D%3D.TCoOlfhAbDHqyEuQrNGPhZWfYMpXvD6gBRz1Vjav0XeRZz3gPydd2%2BqHgtQmd3HGya4TXyx2tMgcOrXlMUCCn513jV875NemRkOcsd2pQ%2BM%3D" rel="nofollow">SQL Injection Prevention Cheat Sheet</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=PiG2aWNdW2MdAhyvYfQOaw%3D%3D.6Q2vosa6P23YC%2Bbb1V4UQdFisyT84HbBHuoYYjPBow6pUXpN%2BJEdZJiiXM%2BI6hab6egm9sc8%2BjmBegpUWI0J2fE0o6Dayfk%2FjynHfk%2B%2BUx5R0jMFChmG5%2FfuSfFu%2Bgiy" rel="nofollow">DDoS,并没有想象中那么可怕</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=MseDXXUTihSsWrnmewzKNw%3D%3D.vdElp%2F9JPpAPaYhp1cDiAieYeiSgtEdkgO0xQS%2Flm405G39Ciqz9n3i4KOcRM8hnSB%2FKs8uAINk5Uv5b%2Fk8oxSUzuFzmKyABTxe2OAaBAGRkbqTus4xRi3sD1KNtT6eQza4Qwt3wErEv2MlyIW%2Fd8wge6DMlv02VoWN6psffJtt21r6O%2FCA2r76kZvjZFpLP2QSKztygMKRsJWyzZLtT2x2nQ3vfU5yxWkOFcIAVS3MqN2sTU9PDWgENA9S5RutrVLq2pzRoQwO7RX%2BCrJtAoDYpAT5WFG06%2FTLjyW9PC1ZkOxzi2Zo9yeJtKdBcM389" rel="nofollow">互联网黑市分析:DDoS 启示录</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=y8X0N4SvLik8wUmLBfmHtA%3D%3D.bH7Tg01z3h31uq5Ppszm3Jct6jD5I4QL1A%2FPl%2FBPBBo4FI%2BObL48qWsInhwKuOTnStxl%2FSGNbBUGReAFWRuiEiZVp1O7wATtz0pt4n6lmE24oaKl4RqGEtDK3fka%2Bxez" rel="nofollow">DDoS,网络安全世界里的暗黑杀手</a></p></li>
</ol>
《人月神话》读书笔记
https://segmentfault.com/a/1190000006936355
2016-09-19T09:19:07+08:00
2016-09-19T09:19:07+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
1
<h2>人月神话</h2>
<p><img src="/img/remote/1460000006936336?w=1822&h=3686" alt="" title=""></p>
<p>(如需高清无码大图可以私信)</p>
<h3>没有银弹</h3>
<h4>软件活动包括:</h4>
<ul>
<li><p>1、根本任务:打造由抽象软件实体构成的复杂概念结构</p></li>
<li><p>2、次要任务:使用编程语言表达这些抽象实体,在空间和时间限制内将它们映射成机器语言</p></li>
</ul>
<h4>软件开发困难:</h4>
<ul>
<li><p>1、根本的:软件特性中固有的困难</p></li>
<li><p>2、次要的:出现在目前生产上的,但并非那些与生俱来的困难</p></li>
</ul>
<h4>软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证</h4>
<h4>软件系统中无法规避的内在特性:</h4>
<ul>
<li><p>1、复杂度</p></li>
<li><p>2、一致性</p></li>
<li><p>3、可变性</p></li>
<li><p>4、不可见性</p></li>
</ul>
<h4>以往解决次要困难的一些突破:</h4>
<ul>
<li><p>1、高级语言</p></li>
<li><p>2、分时</p></li>
<li><p>3、统一编程环境</p></li>
</ul>
<h4>银弹的希望:</h4>
<ul>
<li><p>1、高级编程语言</p></li>
<li><p>2、面向对象编程</p></li>
<li><p>3、人工智能</p></li>
<li><p>4、专家系统</p></li>
<li><p>5、“自动”编程</p></li>
<li><p>6、图形化编程</p></li>
<li><p>7、程序验证</p></li>
<li><p>8、环境和工具</p></li>
<li><p>9、工作站</p></li>
</ul>
<h4>针对概念上根本问题的颇具前途的方法:</h4>
<ul>
<li><p>1、购买和自行开发</p></li>
<li><p>2、需求精炼和快速原型</p></li>
<li><p>3、增量开发-增长,而非搭建系统</p></li>
<li><p>4、卓越的设计人员</p></li>
</ul>
<h3>整体部分</h3>
<h4>剔除bug的设计</h4>
<ul>
<li><p>1、防范bug的定义</p></li>
<li><p>2、测试规格说明</p></li>
<li>
<p>3、自顶向下的设计:设计是一系列精化的步骤</p>
<ul>
<li><p>S1、勾画能得到主要结果的,但比较粗略的任务定义和大概的解决方案</p></li>
<li><p>S2、对该定义和方案进行细致的检查,以判断结果与期望之间的差距。同时,将上述步骤的解决方案在更细的步骤中进行分解</p></li>
</ul>
</li>
<li><p>4、结构化编程</p></li>
</ul>
<h4>构件单元调试</h4>
<ul>
<li><p>本机调试</p></li>
<li><p>内存转储</p></li>
<li><p>快照</p></li>
<li><p>交互式调试</p></li>
<li><p>测试用例</p></li>
</ul>
<h4>系统集成调试</h4>
<ul>
<li><p>1、使用经过调试的构件单元</p></li>
<li>
<p>2、搭建充分的测试平台:供调试使用的所有程序和数据</p>
<ul>
<li><p>1、伪构件</p></li>
<li><p>2、微缩文件</p></li>
</ul>
</li>
<li><p>3、控制变更</p></li>
<li><p>4、一次添加一个控件</p></li>
<li><p>5、阶段(量子)化、定期变更</p></li>
</ul>
<h3>削足适履</h3>
<h4>规模控制</h4>
<ul>
<li>
<p>对项目经理而言,规模控制是技术以及管理工作的一部分</p>
<ul>
<li><p>S1、研究用户和应用,设置系统的规模</p></li>
<li><p>S2、将系统划分为若干部分,并设定每个部分的规模目标</p></li>
</ul>
</li>
<li>
<p>几个道理:</p>
<ul>
<li><p>1、和制订驻留空间预算一样,应该制订总体规模的预算;和制订规模预算一样,应该制订后台存储访问的预算</p></li>
<li><p>2、在指明模块有多大的同时,确切定义模块的功能</p></li>
<li><p>3、培养开发人员从系统整体出发、面向用户的态度是软件编程管理人员最重要的职能</p></li>
</ul>
</li>
</ul>
<h4>空间技能</h4>
<ul>
<li><p>1、用功能交换尺寸</p></li>
<li><p>2、时间-空间的折衷</p></li>
</ul>
<h4>数据的表现形式是编程的根本</h4>
<ul><li><p>创造出自精湛的技艺,技艺的改进的结果往往是战略上的突破,战略上的突破常来自于数据或表的重新表达,这是程序的核心所在</p></li></ul>
<h3>画蛇添足</h3>
<h4>结构师的交互准则和机制</h4>
<ul>
<li><p>1、牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,不能支配</p></li>
<li><p>2、时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任何能达到目标的方法</p></li>
<li><p>3、对上述建议保持低调和平静</p></li>
<li><p>4、准备放弃坚持所作的改进建议</p></li>
</ul>
<h4>自律-开发第二系统所带来的后果</h4>
<ul>
<li><p>产生原因:设计第一个系统时结构师会面对不断产生的润色功能,这些功能被搁置,成为下一个项目的内容。做下一个项目时,结构师信心十足,会向系统添加大量修饰功能和想法,然而很多是画蛇添足之举</p></li>
<li><p>结构师无法跳过第二个系统,但他可以有意识关注那个系统的特殊危险,运用特别的自我约束准则来避免那些功能上的修饰;根据系统基本理念及目的变更,舍弃一些功能</p></li>
<li><p>项目经理为了避免画蛇添足,必须坚持至少拥有两个系统以上开发经验结构师的决定</p></li>
</ul>
<h3>焦油坑</h3>
<h4>编程系统产品</h4>
<ul><li></ul>
<h4>职业的乐趣</h4>
<ul>
<li><p>1、创建事物</p></li>
<li><p>2、开发对他人有用的东西</p></li>
<li><p>3、组装的魔力</p></li>
<li><p>4、学习的乐趣</p></li>
<li><p>5、工作的介质易于驾驭</p></li>
</ul>
<h4>职业的苦恼</h4>
<ul>
<li><p>1、追求完美</p></li>
<li><p>2、无法控制的目标、资源和信息</p></li>
<li><p>3、琐碎的BUG</p></li>
<li><p>4、易于陈旧</p></li>
</ul>
<h3>人月神话</h3>
<h4>项目滞后的最主要原因:</h4>
<p>缺乏合理的时间进度</p>
<ul>
<li><p>1、缺乏有效的估算技术</p></li>
<li><p>2、误以为人和月可以互换</p></li>
<li><p>3、难以持续耐心地估算</p></li>
<li><p>4、对进度缺少跟踪和监督</p></li>
<li><p>5、单纯增加人力只会火上浇油</p></li>
</ul>
<h4>乐观主义:所有的</h4>
<p>编程人员都是乐观主义者</p>
<ul><li><p>原因:介质易于驾驭,我们期待在实现过程中不会碰到困难;然而我们的构思是有缺陷的</p></li></ul>
<h4>人月</h4>
<ul>
<li><p>用人月作为衡量一项工作的规模是一个危险和带有欺骗性的神话。它暗示着人员数量和时间可以相互替换。实际上,添加人手需要更多的交流成本,反而延长了时间进度。</p></li>
<li><p>Brooks法则:向进度落后的项目中增加人手,只会使进度更加落后</p></li>
</ul>
<h4>软件任务进度安排</h4>
<ul>
<li><p>1/3 计划</p></li>
<li><p>1/6 编码</p></li>
<li><p>1/4 构件测试和早期系统测试</p></li>
<li><p>1/4 系统测试(不为系统测试安排足够的时间简直就是一场灾难)</p></li>
</ul>
<h3>贯彻执行</h3>
<h4>文档化的规格说明-手册</h4>
<ul><li><p>手册是产品的外部规格说明,它描述和规定了用户所见的每一个细节;同样,它也是结构师主要的工作产物</p></li></ul>
<h4>形式化定义</h4>
<h4>会议和大会</h4>
<ul>
<li>
<p>周例会:每周半天,所有结构师、硬件和软件实现人员代表和市场计划人员参与,由首席系统结构师主持</p>
<ul>
<li><p>在会议之前,任何人可以提出问题和意见,以书面形式</p></li>
<li><p>对详细的变更建议作出决策</p></li>
<li>
<p>卓有成效的原因</p>
<ul>
<li><p>1、数月内,相同小组的结构师、用户和实现人员每周交流一次,因此大家对项目内容比较熟悉,不需要安排额外时间进行培训</p></li>
<li><p>2、上述小组十分睿智和敏锐,深刻理解所面对的问题,并且与产品密切相关。没有人是“顾问”的角色,每个人都要承担义务</p></li>
<li><p>3、当问题出现时,在界限的内部和外部同时寻求解决方案</p></li>
<li><p>4、正式的书面建议集中了注意力,强制了决策的制定,避免了会议草稿纪要方式的不一致</p></li>
<li><p>5、清晰地授予首席结构师决策的权力,避免了妥协和拖延</p></li>
</ul>
</li>
</ul>
</li>
<li><p>年度大会:随着时间推移,一些决定没有很好地贯彻,这些问题周例会没有重新考虑,慢慢的,小问题就会积累。为了解决这些积累的问题,举行年度大会,一般持续两周</p></li>
</ul>
<h3>提纲挈领</h3>
<h4>为什么要有正式的文档</h4>
<ul>
<li><p>1、书面决策是必要的</p></li>
<li><p>2、文档能够作为同其他人的沟通渠道</p></li>
<li><p>3、项目经理的文档可以作为数据基础和检查列表</p></li>
</ul>
<h3>祸起萧墙</h3>
<h4>里程碑还是沉重的负担</h4>
<ul><li><p>根据进度表来控制项目,进度表上的每一件事被称为“里程碑”。好的里程碑对团队来说实际上是一项服务,可以用来向项目经理提出合理要求的一项服务,而不确切的里程碑是难以处理的负担。</p></li></ul>
<h4>“其他的部分反正会落后”</h4>
<ul><li><p>必须关系每一天的滞后,它们是大灾祸的基本组成元素</p></li></ul>
<h4>地毯的下面</h4>
<ul><li>
<p>一线经理发现问题后,并不会立即向老板汇报,而是自己想办法解决。因此,所以污垢都被隐藏在地毯之下。两个把污垢展现给老板的方法:</p>
<ul>
<li><p>1、减少角色冲突:老板要区别行动信息和状态信息</p></li>
<li><p>2、猛地拉开地毯:不论协作与否,了解状态真相的评审机制是必要的</p></li>
</ul>
</li></ul>
<h3>胸有成竹</h3>
<h4>Portman的数据</h4>
<ul><li><p>Portman发现他的编程队伍落后进度大约1/2,每项工作花费的时间大约是估计的两倍</p></li></ul>
<h4>Aron的数据</h4>
<h4>Harr的数据</h4>
<h4>OS/360的数据</h4>
<h4>Corbato的数据</h4>
<ul>
<li><p>对常用编程语言而言,生产率似乎是固定的,这个固定的生产率包括了编程中需要注释、并可能存在错误的情况</p></li>
<li><p>使用适当的高级语言,编程的效率可以提高5倍</p></li>
</ul>
<h3>干将莫邪</h3>
<h4>目标机器</h4>
<ul><li><p>目标机器是软件所服务的对象,程序必须在该机器上进行最后的测试</p></li></ul>
<h4>辅助机器和数据服务</h4>
<ul><li><p>辅助机器是那些在开发系统中提供服务的机器</p></li></ul>
<h4>高级语言和交互式编程</h4>
<ul><li><p>使用高级语言的主要原因是生产率和调试速度</p></li></ul>
<h3>未雨绸缪</h3>
<h4>为舍弃而计划</h4>
<h4>唯一不变的就是变化本身</h4>
<h4>为变更计划系统</h4>
<ul>
<li><p>细致的模块化、可拓展的函数、精确完整的模块间接口设计、完备的文档,可能还有调用队列和表驱动的一些技术</p></li>
<li><p>最重要的措施是使用高级语言和自文档技术,以减少变更引起的错误</p></li>
</ul>
<h4>为变更计划组织架构</h4>
<ul><li><p>设计人员不愿意为设计书写文档的原因,不仅仅是惰性或时间压力。相反,设计人员通常不愿意提交尝试性的设计决策,再为它们进行辩解</p></li></ul>
<h4>前进两步,后退一步</h4>
<ul><li><p>软件维护主要包含对设计缺陷的修复,而缺陷修复总会以20%-50%的几率引入新的bug</p></li></ul>
<h4>前进一步,后退一步</h4>
<ul><li><p>所有修改都倾向于破坏系统的架构,增加系统的混乱程度。随着时间的推移,系统变得越来越无序,修复工作迟早失去根基。每一步前进都伴随着一步后退</p></li></ul>
<h3>为什么巴比伦塔</h3>
<p>会失败</p>
<h4>巴比伦塔的管理教训</h4>
<ul>
<li>
<p>他们具有的条件</p>
<ul>
<li><p>清晰的目标</p></li>
<li><p>人力</p></li>
<li><p>材料</p></li>
<li><p>足够的时间</p></li>
<li><p>足够的技术</p></li>
</ul>
</li>
<li>
<p>他们失败的因素</p>
<ul>
<li><p>交流</p></li>
<li><p>组织</p></li>
</ul>
</li>
</ul>
<h4>大型编程项目中的交流(沟通途径)</h4>
<ul>
<li><p>1、非正式的途径:清晰定义小组内部的相互关系和充分利用电话,能鼓励大量的电话沟通,从而达到对所写文档的共同理解</p></li>
<li><p>2、会议:常规项目会议,团队一个接一个地进行简要的技术陈述。这种方式非常有用,能澄清成百上千的细小问题</p></li>
<li><p>3、工作手册:在项目的开始阶段,应该准备正式的项目工作手册。</p></li>
</ul>
<h4>项目工作手册</h4>
<ul>
<li><p>是什么:对项目必须产出的一系列文档进行组织的一种结构。项目所有的文档都必须是该结构的一部分</p></li>
<li><p>为什么:技术说明必不可少;控制信息发布</p></li>
<li><p>处理机制:每间办公室应保留一份工作手册的拷贝。工作手册的实时性很关键,所以采用活页夹的方式,仅仅更换变更页</p></li>
</ul>
<h4>大型编程项目的组织架构</h4>
<ul>
<li><p>团队组织的目的是减少不必要的交流和合作的数量</p></li>
<li><p>减少交流的方法是人力划分和限定职责范围</p></li>
<li>
<p>树状编程队伍,为使它行之有效,每棵子树必须具备的基本要素:</p>
<ul>
<li><p>1、任务</p></li>
<li><p>2、产品负责人:组建团队,划分工作及制定进度表,要求必要的资源,确保进度目标的实现</p></li>
<li><p>3、技术主管和结构师:构思设计,识别系统的子部分,提供设计的一致性和完整性;控制系统复杂程度;提供问题解决方案</p></li>
<li><p>4、进度</p></li>
<li><p>5、人力的划分</p></li>
<li><p>6、各部分之间的接口定义</p></li>
</ul>
</li>
<li><p>技术作为总指挥,产品负责人充当其左右手。这是对小型团队最好的选择</p></li>
</ul>
<h3>另外一面</h3>
<h4>需要什么样的文档</h4>
<ul>
<li>
<p>使用程序</p>
<ul>
<li><p>1、目的:主要功能是什么?开发程序原因是什么?</p></li>
<li><p>2、环境:程序运行在什么样的机器、硬件配置和操作系统上?</p></li>
<li><p>3、范围:输入的有效范围是什么?允许显示的合法范围是什么?</p></li>
<li><p>4、实现功能和使用的算法。精确地阐述它做了什么</p></li>
<li><p>5、输入-输出格式。必须是确切和完整的</p></li>
<li><p>6、操作指令。包括控制台及输出内容中正常和异常结束的行为</p></li>
<li><p>7、选项。用户的功能选项有哪些?如何在选项之间进行挑选?</p></li>
<li><p>8、运行时间。在指定的配置下,解决特定规模问题所需要的时间?</p></li>
<li><p>9、精度和校验。期望结果的的精确程度?如何进行精度的检测?</p></li>
</ul>
</li>
<li>
<p>验证程序</p>
<ul>
<li><p>1、针对遇到的大多数常规数据和程序主要功能进行测试的用例</p></li>
<li><p>2、数量相对较少的合法数据测试用例</p></li>
<li><p>3、数量相对较少的非法数据测试用例</p></li>
</ul>
</li>
<li>
<p>修改程序</p>
<ul>
<li><p>1、流程图或子系统的结构图</p></li>
<li><p>2、对所用算法的完整描述,或者对文档中类似描述的引用</p></li>
<li><p>3、对所有文件规划的理解</p></li>
<li><p>4、数据流的概要描述-从磁盘或磁带中,获取数据或程序处理的序列-以及在每个处理过程中完成的操作</p></li>
<li><p>初始设计中,对已预见修改的讨论;特性、功能回调的位置以及出口;原作者对可能扩充的地方以及可能处理方案的一些意见。另外,对隐藏缺陷的观察也同样很有价值</p></li>
</ul>
</li>
</ul>
<h4>流程图</h4>
<ul><li><p>一页纸的流程图,成为表达程序结构、阶段或步骤的一种非常基本的图示</p></li></ul>
<h4>自文档化的程序</h4>
<ul><li>
<p>将文档整合到源代码</p>
<ul>
<li><p>方法1、借助那些出于语言的要求而必须存在的语句,来附加尽可能多的“文档”信息</p></li>
<li><p>方法2、尽可能地使用空格和一致的格式提高程序的可读性,表现从属和嵌套关系</p></li>
<li><p>3、以段落的形式,向程序中插入必要的记叙性文字</p></li>
</ul>
</li></ul>
<h3>贵族专制、民主政治</h3>
<p>和系统设计</p>
<h4>概念一致性</h4>
<ul><li><p>在系统设计中,概念完整性是最重要的考虑因素。即为了反应一系列连贯的设计思路,宁可省略一些不规则的特性和改进,也不提倡独立和无法整合的系统</p></li></ul>
<h4>获得概念的完整性:目标是易用性,易用性需要设计的一致性和概念上的完整性</h4>
<ul>
<li><p>在语义上,应具有同样的相似性</p></li>
<li><p>在语法上,每个部分应使用相同的技巧</p></li>
<li><p>每个部分必须反映相同的原理、原则和一致的折衷机制</p></li>
</ul>
<h4>贵族专制和民主政治</h4>
<ul>
<li><p>贵族:结构师;人民:实现人员</p></li>
<li><p>结构师和实现人员都有创意,但创意要符合系统的概念完整性</p></li>
<li><p>只能存在少量结构师,他们处于贵族专制的地位。为了系统概念完整性,他们必须控制概念</p></li>
<li><p>外部技术说明与具体实现都富有创造性。</p></li>
<li><p>外部的体系结构实际上增强了而不是限制实现小组的创造性</p></li>
</ul>
<h3>外科手术队伍</h3>
<h4>问题</h4>
<ul><li><p>精干队伍效率非常高,但开发大型系统时仍然远远不够,只能需要大量人手。这两者矛盾需要调和</p></li></ul>
<h4>Mills的建议</h4>
<ul><li>
<p>10人的编程队伍角色分工-Mills建议项目队伍以类似外科手术的方式组建,每一部分由一个团队解决。即一个人进行问题分解,其他人给予其所需的支持</p>
<ul>
<li>
<p>外科医生:首席程序员,极高的天分,十年的经验,及其他知识</p>
<ul>
<li><p>定义功能和性能技术说明书</p></li>
<li><p>设计程序</p></li>
<li><p>编制源代码</p></li>
<li><p>测试以及书写技术文档</p></li>
</ul>
</li>
<li>
<p>副手:外科医生后备,能完成一部分工作,但经验较少</p>
<ul><li><p>设计的思考者、讨论者和评估人员</p></li></ul>
</li>
<li><p>管理员:外科医生是老板,在人员、加薪方面具有决定权,但不能在这些事物上浪费时间,需要有人管理</p></li>
<li><p>编辑:外科医生负责产生文档,编辑需要根据外科医生草稿或口述进行分析和重新组织</p></li>
<li><p>两个秘书:管理员和编辑各需要一个</p></li>
<li><p>程序职员:维护编程产品库中所有团队的技术记录</p></li>
<li><p>工具维护人员:保证所有基本服务的可靠性</p></li>
<li><p>测试人员:外科医生需要大量测试用例进行测试</p></li>
<li><p>语言专家:寻找一种简洁、有效的使用语言的方法解决复杂问题</p></li>
</ul>
</li></ul>
<h4>如何运作:“两人队伍”与“外科医生-副手”的区别</h4>
<ul>
<li><p>1、传统团队中每人负责一部分任务;外科手术团队中,外科医生和副手都了解所有的设计和全部代码</p></li>
<li><p>2、传统队伍人人平等,出现问题需要讨论和妥协;外科手术团队中由外科医生当方面决定</p></li>
</ul>
<h4>团队的扩建</h4>
<ul><li><p>扩建过程的成功依赖于:每个部分的概念完整性得到了彻底的提高</p></li></ul>
WebSocket 与 Socket.IO
https://segmentfault.com/a/1190000006899960
2016-09-13T23:07:28+08:00
2016-09-13T23:07:28+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
5
<h2>WebSocket 与 Socket.IO</h2>
<p>最近小组在做一个智慧交通的项目,其中有个 “分享屏幕” 的功能,即一个 client 能够将自己当前的页面分享到另外一个 client,针对这个需求,我们利用了 WebSocket 技术,具体说是 Socket.IO。</p>
<h3>1. 什么是 WebSocket</h3>
<p>提到 WebSocket,我首先会想到 “及时通讯” 和 “推送” 这类词。在 WebSocket 以前,很多网站通过其他方式来推送信息,下面我们先看看以前的推送方式,这样,有比较才能看出 WebSocket 的优势。</p>
<h4>1.1 (短)轮询(Polling)</h4>
<p><img src="/img/remote/1460000006899963?w=339&h=167" alt="" title=""></p>
<p>这种方式下,client 每隔一段时间都会向 server 发送 http 请求,服务器收到请求后,将最新的数据发回给 client。一开始必须通过提交表单的形式,这样的后果就是传输很多冗余的数据,浪费了带宽。后来 Ajax 出现,减少了传输数据量。</p>
<p>如图所示,在 client 向 server 发送一个请求活动结束后,server 中的数据发生了改变,所以 client 向 server 发送的第二次请求中,server 会将最新的数据返回给 client。</p>
<p>但这种方式也存在弊端。比如在某个时间段 server 没有更新数据,但 client 仍然每隔一段时间发送请求来询问,所以这段时间内的询问都是无效的,这样浪费了网络带宽。将发送请求的间隔时间加大会缓解这种浪费,但如果 server 更新数据很快时,这样又不能满足数据的实时性。</p>
<h4>1.2 Comet</h4>
<p>鉴于(短)轮询的弊端,一种基于 HTTP 长连接的 “服务器推” 的技术被 hack 了出来,这种技术被命名为 Comet。其与(短)轮询主要区别就是,在轮询方式下,要想取得数据,必须首先发送请求,在实时性要求较高的情况下,只能增加向 server 请求的频率;而 Comet 则不同,client 与 server 端保持一个长连接,只有数据发生改变时,server 才主动将数据推送给 client。Comet 又可以被细分为两种实现方式,一种是长轮询机制,一种是流技术。</p>
<h5>1.2.1 长轮询(Long-polling)</h5>
<p><img src="/img/remote/1460000006899964?w=339&h=167" alt="" title=""></p>
<p>client 向 server 发出请求,server 接收到请求后,server 并不一定立即发送回应给 client,而是看数据是否更新,如果数据已经更新了的话,那就立即将数据返回给 client;但如果数据没有更新,那就把这个请求保持住,等待有新的数据到来时,才将数据返回给 client。</p>
<p>当然了,如果 server 的数据长时间没有更新,一段时间后,请求便会超时,client 收到超时信息后,再立即发送一个新的请求给 server。</p>
<p>如图所示,在长轮询机制下,client 向 server 发送了请求后,server会等数据更新完才会将数据返回,而不是像(短)轮询一样不管数据有没有更新然后立即返回。</p>
<p>这种方式也有弊端。当 server 向 client 发送数据后,必须等待下一次请求才能将新的数据发送出去,这样 client 接收到新数据的间隔最短时间便是 2 * RTT(往返时间),这样便无法应对 server 端数据更新频率较快的情况。</p>
<h5>1.2.2 流技术(Http Streaming)</h5>
<p><img src="/img/remote/1460000006899965?w=339&h=167" alt="" title=""></p>
<p>流技术基于 Iframe。Iframe 是 HTML 标记,这个标记的 src 属性会保持对指定 server 的长连接请求,server 就可以不断地向 client 返回数据。</p>
<p>可以看出,流技术与长轮询的区别是长轮询本质上还是一种轮询方式,只不过连接的时间有所增加,想要向 server 获取新的数据,client 只能一遍遍的发送请求;而流技术是一直保持连接,不需要 client 请求,当数据发生改变时,server 自动的将数据发送给 client。</p>
<p>如图所示,client 与 server 建立连接之后,便不会断开。当数据发生变化,server 便将数据发送给 client。</p>
<p>但这种方式有一个明显的不足之处,网页会一直显示未加载完成的状态,虽然我没有强迫症,但这点还是难以忍受。</p>
<h4>1.3 WebSocket</h4>
<p>写到现在,大家会发现,前人推出那么多的解决方案,想要解决的唯一的问题便是<strong>怎么让 server 将最新的数据以最快的速度发送给 client</strong>。但 HTTP 是个懒惰的协议,server 只有收到请求才会做出回应,否则什么事都不干。因此,为了彻底解决这个 server 主动向 client 发送数据的问题,W3C 在 HTML5 中提供了一种 client 与 server 间进行全双工通讯的网络技术 WebSocket。WebSocket 是一个全新的、独立的协议,基于 TCP 协议,与 HTTP 协议兼容却不会融入 HTTP 协议,仅仅作为 HTML5 的一部分。</p>
<p>那 WebSocket 与 HTTP 什么关系呢?简单来说,WebSocket 是一种协议,是一种与 HTTP 同等的网络协议,两者都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。</p>
<h3>WebSocket 原理</h3>
<p><img src="/img/remote/1460000006899966?w=929&h=837" alt="" title=""></p>
<p>相比于传统 HTTP 的每次“请求-应答”都要 client 与 server 建立连接的模式,WebSocket 是一种长连接的模式。具体什么意思呢?就是一旦 WebSocket 连接建立后,除非 client 或者 server 中有一端主动断开连接,否则每次数据传输之前都不需要 HTTP 那样请求数据。从上面的图可以看出,client 第一次需要与 server 建立连接,当 server 确认连接之后,两者便一直处于连接状态。直到一方断开连接,WebSocket 连接才断开。</p>
<p>下面我们从报文层面谈一下 WebSocket 与 HTTP 的差异。</p>
<p><img src="/img/remote/1460000006899967?w=232&h=217" alt="" title=""></p>
<p>首先,client 发起 WebSocket 连接,报文类似于 HTTP,但主要有几点不一样的地方:</p>
<ul>
<li><p>"Upgrade: websocket": 表明这是一个 WebSocket 类型请求,意在告诉 server 需要将通信协议切换到 WebSocket</p></li>
<li><p>"Sec-WebSocket-Key: <em>*</em>": 是 client 发送的一个 base64 编码的密文,要求 server 必须返回一个对应加密的 "Sec-WebSocket-Accept" 应答,否则 client 会抛出 "Error during WebSocket handshake" 错误,并关闭连接</p></li>
</ul>
<p>server 收到报文后,如果支持 WebSocket 协议,那么就会将自己的通信协议切换到 WebSocket,返回以下信息:</p>
<ul>
<li><p>"HTTP/1.1 101 WebSocket Protocol Handshake":返回的状态码为 101,表示同意 client 的协议转换请求</p></li>
<li><p>"Upgrade: websocket"</p></li>
<li><p>"Connection: Upgrade"</p></li>
<li><p>"Sec-WebSocket-Accept: <em>*</em>"</p></li>
<li><p>...</p></li>
</ul>
<p>以上都是利用 HTTP 协议完成的。这样,经过“请求-相应”的过程, server 与 client 的 WebSocket 连接握手成功,后续便可以进行 TCP 通讯了,也就没有 HTTP 什么事了。可以查阅<a href="https://link.segmentfault.com/?enc=iTUl2xhVDsA0i7rYzBah2g%3D%3D.RDV%2FRh%2F%2FzUN%2Bovu9Dxufn%2BbafIPAGsVlTS14eeh4uwCIwFsmlCAo7uCGXfLtDlgM" rel="nofollow">WebSocket 协议栈</a>了解 WebSocket 的 client 与 server 更详细的交互数据格式。</p>
<h3>WebSocket 与 Socket</h3>
<p>网络应用中,两个应用程序同时需要向对方发送消息的能力(即全双工通信),所利用到的技术就是 socket,其能够提供端对端的通信。对于程序员而言,其需要在 A 端创建一个 socket 实例,并为这个实例提供其所要连接的 B 端的 IP 地址和端口号,而在 B 端创建另一个 socket 实例,并且绑定本地端口号来进行监听。当 A 和 B 建立连接后,双方就建立了一个端对端的 TCP 连接,从而可以进行双向通信。</p>
<p>WebSocekt 是 HTML5 规范中的一部分,其借鉴了 socket 的思想,为 client 和 server 之间提供了类似的双向通信机制。同时,WebSocket 又是一种新的应用层协议,包含一套标准的 API;而 socket 并不是一个协议,而是一组接口,其主要方便大家直接使用更底层的协议(比如 TCP 或 UDP)</p>
<p><img src="/img/remote/1460000006899968?w=545&h=478" alt="" title=""></p>
<h3>什么是 Socket.IO</h3>
<p><a href="https://link.segmentfault.com/?enc=1Vi5XEGpo5u78OKW2s5CdQ%3D%3D.BI27AR7ivwj0lHrgLwVElSwAo96fjm%2F7Ikr4dDtJDkw%3D" rel="nofollow">Socket.IO</a> 是一个封装了 Websocket、基于 Node 的 JavaScript 框架,包含 client 的 JavaScript 和 server 的 Node。其屏蔽了所有底层细节,让顶层调用非常简单。</p>
<p>另外,Socket.IO 还有一个非常重要的好处。其不仅支持 WebSocket,还支持许多种轮询机制以及其他实时通信方式,并封装了通用的接口。这些方式包含 Adobe Flash Socket、Ajax 长轮询、Ajax multipart streaming 、持久 Iframe、JSONP 轮询等。换句话说,当 Socket.IO 检测到当前环境不支持 WebSocket 时,能够自动地选择最佳的方式来实现网络的实时通信。</p>
<p>参考:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=g1X29PP1V8svIxCuofDnpQ%3D%3D.7kJQeVCXmxpWd%2F5n%2FCHuokbxmeTzbqNqbHaHVwrp53xFzOHufntPwS1nEbwDeNOU" rel="nofollow">Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE</a></p></li>
<li><p>微信公众号: 【龙果】 --- 《WebSocket --web持久连接神器》</p></li>
<li><p><a href="https://link.segmentfault.com/?enc=lv0QQzO7dqKxxRTB3NmVsA%3D%3D.hgoEK%2F4HPBmAFeT02dY%2Bnern0jOT4R9RkRu75ZLoYRJw5F7fq5kXiWM5JjZdEtoMJyEH22E1sjfTFffcP5KmP1%2FT2X4efNJaM5oPV7EJ%2BPo%3D" rel="nofollow">WebSocket详解(一):初步认识WebSocket技术</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=mAyMXCF1tfjVcgrl%2FYCRGg%3D%3D.eAeXQeyj7sSXhOaxpn6gCDXvucySjomlnUZEBkkCwYLNItQh2WXtl7FjuB9RuwKL" rel="nofollow">Socket 与 WebSocket</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=qF9wBcWxG%2F2nYGn64j14ng%3D%3D.AHpjoH%2F2w2z%2FB9OcxyNXvEVxzHzOPRMM%2BRHWhdIkrWIFSqCaP2ZxG7%2FEecuF2qB6%2Fsm0BYiF7hiHYaVNmcVSE%2F%2BE3drCjrNQOGNEXwitku8rRpTzNjoWsUi%2FaIuCjsAv" rel="nofollow">Differences between socket.io and websockets</a></p></li>
</ol>
JavaScript 事件详解
https://segmentfault.com/a/1190000005016813
2016-04-27T11:54:32+08:00
2016-04-27T11:54:32+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
4
<h2>JavaScript 事件解读</h2>
<h3>1. 事件基本概念</h3>
<p><strong>事件</strong>是指在文档或者浏览器中发生的一些特定交互瞬间,比如打开某一个网页,浏览器加载完成后会触发 <code>load</code> 事件,当鼠标悬浮于某一个元素上时会触发 <code>hover</code> 事件,当鼠标点击某一个元素时会触发 <code>click</code> 事件等等。</p>
<p><strong>事件处理</strong>就是当事件被触发后,浏览器响应这个事件的行为,而这个行为所对应的代码即为<strong>事件处理程序</strong>。</p>
<h3>2. 事件操作:监听与移除监听</h3>
<h4>2.1 监听事件</h4>
<p>浏览器会根据一些事件作出相对应的事件处理,事件处理的前提是需要监听事件,监听事件的方法主要有以下三种:</p>
<h5>2.1.1 HTML 内联属性</h5>
<p>即在 HTML 元素里直接填写与事件相关的属性,属性值为事件处理程序。示例如下:</p>
<pre><code><button onclick="console.log('You clicked me!');"></button>
</code></pre>
<p><code>onclick</code> 对应着 <code>click</code> 事件,所以当按钮被点击后,便会执行事件处理程序,即控制台输出 <code>You clicked me!</code>。</p>
<p>不过我们需要指出的是,这种方式将 HTML 代码与 JavaScript 代码耦合在一起,不利于代码的维护,所以应该尽量避免使用这样的方式。</p>
<h5>2.1.2 DOM 属性绑定</h5>
<p>通过直接设置某个 DOM 节点的属性来指定事件和事件处理程序,上代码:</p>
<pre><code>const btn = document.getElementById("btn");
btn.onclick = function(e) {
console.log("You clicked me!");
};
</code></pre>
<p>上面示例中,首先获得 btn 这个对象,通过给这个对象添加 <code>onclick</code> 属性的方式来监听 <code>click</code> 事件,这个属性值对应的就是事件处理程序。这段程序也被称作 DOM 0 级事件处理程序。</p>
<h5>2.1.3 事件监听函数</h5>
<p>标准的事件监听函数如下:</p>
<pre><code>const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
console.log("You clicked me!");
}, false);
</code></pre>
<p>上面的示例表示先获得表示节点的 btn 对象,然后在这个对象上面添加了一个事件监听器,当监听到 <code>click</code> 事件发生时,则调用回调函数,即在控制台输出 <code>You clicked me!</code>。<code>addEventListener</code> 函数包含了三个参数 false,第三个参数的含义在后面的事件触发三个阶段之后再讲解。这段程序也被称作 DOM 2 级事件处理程序。IE9+、FireFox、Safari、Chrome 和 Opera 都是支持 DOM 2 级事件处理程序的,对于 IE8 及以下版本,则用 <code>attacEvent()</code> 函数绑定事件。</p>
<p>所以我们可以写一段具有兼容性的代码:</p>
<pre><code>function addEventHandler(obj, eventName, handler) {
if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
else if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
}
else {
obj["on" + eventName] = handler;
}
}
</code></pre>
<h4>2.2 移除事件监听</h4>
<p>在为某个元素绑定了一个事件后,如果想接触绑定,则需要用到 <code>removeEventListener</code> 方法。看如下例子:</p>
<pre><code>const handler = function() {
// handler logic
}
const btn = document.getElementById("btn");
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);
</code></pre>
<p>需要注意的是,绑定事件的回调函数不能是匿名函数,必须是一个已经被声明的函数,因为解除事件绑定时需要传递这个回调函数的引用。</p>
<p>同样,IE8 及以下版本也不支持上面的方法,而是用 <code>detachEvent</code> 代替。</p>
<pre><code>const handler = function() {
// handler logic
}
const btn = document.getElementById("btn");
btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);
</code></pre>
<p>同样,可以写一段具有兼容性的删除事件函数:</p>
<pre><code>function removeEventHandler(obj, eventName, handler) {
if (document.removeEventListener) {
obj.removeEventListener(eventName, handler, false);
}
else if (document.detachEvent) {
obj,detachEvent("on" + eventName, handler);
}
else {
obj["on" + eventName] = null;
}
}
</code></pre>
<h3>3. 事件触发过程</h3>
<p>事件流描述了页面接收事件的顺序。现代浏览器(指 IE6-IE8 除外的浏览器,包括 IE9+、FireFox、Safari、Chrome 和 Opera 等)事件流包含三个过程,分别是捕获阶段、目标阶段和冒泡阶段,下图形象地说明这个过程:</p>
<p><img src="/img/bVvdpE" alt="图片描述" title="图片描述"></p>
<p>下面就详细地讲解这三个过程。</p>
<h4>3.1 捕获阶段</h4>
<p>当我们对 DOM 元素进行操作时,比如鼠标点击、悬浮等,就会有一个事件传输到这个 DOM 元素,这个事件从 Window 开始,依次经过 docuemnt、html、body,再不断经过子节点直到到达目标元素,从 Window 到达目标元素父节点的过程称为<strong>捕获阶段</strong>,注意此时还未到达目标节点。</p>
<h4>3.2 目标阶段</h4>
<p>捕获阶段结束时,事件到达了目标节点的父节点,最终到达目标节点,并在目标节点上触发了这个事件,这就是<strong>目标阶段</strong>。</p>
<p>需要注意的是,事件触发的目标节点为最底层的节点。比如下面的例子:</p>
<pre><code><div>
<p>你猜,目标在这里还是<span>那里</span>。</p>
</div>
</code></pre>
<p>当我们点击“那里”的时候,目标节点是<code><span></span></code>,点击“这里”的时候,目标节点是<code><p></p></code>,而当我们点击<code><p></p></code>区域之外,<code><div></div></code>区域之内时,目标节点就是<code><div></div></code>。</p>
<h4>3.3 冒泡阶段</h4>
<p>当事件到达目标节点之后,就会沿着原路返回,这个过程有点类似水泡从水底浮出水面的过程,所以称这个过程为<strong>冒泡阶段</strong>。</p>
<p>针对这个过程,wilsonpage 做了一个 <a href="https://link.segmentfault.com/?enc=8YLTOqiaflNRoyYMACpXcw%3D%3D.Tr7BsHcdZE4MNXuKdoXnHBjH297plzzKBsVGq85DLwwfqg6TkVg2isbIy42EQiGD" rel="nofollow">DEMO</a>,可以非常直观地查看这个过程。</p>
<p>现在再看 <code>addEventListener(eventName, handler, useCapture)</code> 函数。第三个参数是 useCapture,代表是否在捕获阶段进行事件处理, 如果是 false, 则在冒泡阶段进行事件处理,如果是 true,在捕获阶段进行事件处理,默认是 false。这么设计的主要原因是当年微软和 netscape 之间的浏览器战争打得火热,netscape 主张捕获方式,微软主张冒泡方式,W3C 采用了折中的方式,即先捕获再冒泡。</p>
<h3>4、事件委托</h3>
<p>上面我们讲了事件的冒泡机制,我们可以利用这一特性来提高页面性能,事件委托便事件冒泡是最典型的应用之一。</p>
<p>何谓“委托”?在现实中,当我们不想做某件事时,便“委托”给他人,让他人代为完成。JavaScript 中,事件的委托表示给元素的父级或者祖级,甚至页面,由他们来绑定事件,然后利用事件冒泡的基本原理,通过事件目标对象进行检测,然后执行相关操作。看下面例子:</p>
<pre><code>// HTML
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
// JavaScript
var list = document.getElementById("list");
list.addEventListener("click", function(e) {
console.log(e.target);
});
</code></pre>
<p>上面的例子中,5 个列表项的点击事件均委托给了父元素 <code><ul id="list"></ul></code>。</p>
<p>先看看事件委托的可行性。有人会问,当事件不是加在某个元素上的,如何在这个元素上触发事件呢?我们就是利用事件冒泡的机制,事件流到达目标元素后会向上冒泡,此时父元素接收到事件流便会执行事件执行程序。有人又会问,被委托的父元素下面如果有很多子元素,怎么知道事件流来自于哪个子元素呢?这个我们可以从事件对象中的 <code>target</code> 属性获得。事件对象下面会详细讲解。</p>
<p>我们再来看看为什么需要事件委托。</p>
<ul>
<li><p>减少事件绑定。上面的例子中,也可以分别给每个列表项绑定事件,但利用事件委托的方式不仅省去了一一绑定的麻烦,也提升了网页的性能,因为每绑定一个事件便会增加内存使用。</p></li>
<li><p>可以动态监听绑定。上面的例子中,我们对 5 个列表项进行了事件监听,当删除一个列表项时不需要单独删除这个列表项所绑定的事件,而增加一个列表项时也不需要单独为新增项绑定事件。</p></li>
</ul>
<p>看了上面的例子和解释,我们可以看出事件委托的核心就是监听一个 DOM 中更高层、更不具体的元素,等到事件冒泡到这个不具体元素时,通过 event 对象的 target 属性来获取触发事件的具体元素。</p>
<h3>5、阻止事件冒泡</h3>
<p>事件委托是事件冒泡的一个应用,但有时候我们并不希望事件冒泡。比如下面的例子:</p>
<pre><code>const ele = document.getElementById("ele");
ele.addEventListener("click", function() {
console.log("ele-click");
}, false);
document.addEventListener("click", function() {
console.log("document-click");
}, false);
</code></pre>
<p>我们本意是当点击 ele 元素区域时显示 "ele-click",点击其他区域时显示 "document-click"。但是我们发现点击 ele 元素区域时会依次显示 "ele-click" "document-click"。那是因为绑定在 ele 上的事件冒泡到了 document 上。想要解决这个问题,只需要加一行代码:</p>
<pre><code>const ele = document.getElementById("ele");
ele.addEventListener("click", function(e) {
console.log("ele-click");
e.stopPropagation(); // 阻止事件冒泡
}, false);
document.addEventListener("click", function(e) {
console.log("document-click");
}, false);
</code></pre>
<p>我们还能用 <code>e.cancelBubble = true</code> 来替代 <code>e.stopPropagation()</code>。网上的说法是 <code>cancelBubble</code> 仅仅适用于 IE,而 <code>stopPropagation</code> 适用于其他浏览器。但根据我实验的结果,现代浏览器(IE9 及 以上、Chrome、FF 等)均同时支持这两种写法。为了保险起见,我们可以采用以下代码:</p>
<pre><code>function preventBubble(e) {
if (!e) {
const e = window.event;
}
e.cancelBubble = true;
if (e.stopPropagation) {
e.stopPropagation();
}
}
</code></pre>
<h3>6、event 对象</h3>
<p>Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。当一个事件被触发的时候,就会创建一个事件对象。</p>
<p>我们用下面的代码打印出事件对象:</p>
<pre><code><div id="list">
<li>Item 1</li>
<li>Item 2</li>
</div>
<script>
var list = document.getElementById("list");
list.addEventListener("click", function(e) {
console.log(e);
});
</script>
</code></pre>
<p>chrome 49 的运行结果如下:</p>
<p><img src="/img/bVvdpF" alt="图片描述" title="图片描述"></p>
<p>下面介绍一些比较常用的属性和方法。</p>
<h4>target、 srcElement、 currentTarget 和 relatedTarget、fromElement、 toElement</h4>
<ul>
<li><p>target 与 srcElement 完全相同;</p></li>
<li><p>target 指触发事件的元素, currentTarget 指事件所绑定的元素;</p></li>
<li><p>relatedTarget: 与事件的目标节点相关的节点。对于 mouseover 事件来说,该属性是鼠标指针移到目标节点上时所离开的那个节点。对于 mouseout 事件来说,该属性是离开目标时,鼠标指针进入的节点。<br>对于其他类型的事件来说,这个属性没有用;</p></li>
<li><p>fromElement 和 toElement 仅仅对于 mouseover 和 mouseout 事件有效。</p></li>
</ul>
<p>以上面的例子说明,当点击 <code><li>Item 1</li></code> 时,target 就是 <code><li>Item 1</li></code> 元素,而 currentTarget 是 <code><div id="list"></div></code>。</p>
<h4>clientX/Y、 screenX/Y、 pageX/Y、 offsetX/Y</h4>
<p>上图:</p>
<p><img src="http://i.imgur.com/RYJisO4.png" alt="" title=""></p>
<ul>
<li><p>offsetX/Y: 点击位置相对于所处元素左上角的位置;</p></li>
<li><p>clientX/Y: 点击位置相对于浏览器内容区域左上角的位置;</p></li>
<li><p>screenX/Y: 点击位置相对于屏幕左上角的位置;</p></li>
<li><p>pageX/Y: 点击位置相对整张页面左上角的位置;</p></li>
<li><p>pageX/Y 与 clientX/Y 一般情况下会相同,只有出现滚动条时才不一样。</p></li>
</ul>
<h4>altKey、 ctrlKey、 shiftKey</h4>
<ul>
<li><p>altKey: 返回当事件被触发时,"ALT" 是否被按下;</p></li>
<li><p>ctrlKey: 返回当事件被触发时,"CTRL" 键是否被按下;</p></li>
<li><p>shiftKey: 返回当事件被触发时,"SHIFT" 键是否被按下;</p></li>
</ul>
<h4>其他属性</h4>
<ul>
<li><p>type: 返回当前 Event 对象表示的事件的名称</p></li>
<li><p>bubbles: 返回布尔值,指示事件是否是起泡事件类型;</p></li>
<li><p>cancelable: 返回布尔值,指示事件是否可拥可取消的默认动作;</p></li>
<li><p>eventPhase: 返回事件传播的当前阶段,有三个值: Event.CAPTURING_PHASE、 Event.AT_TARGET、 Event.BUBBLING_PHASE,对应的值为 1、2、3,分别表示捕获阶段、正常事件派发和起泡阶段;</p></li>
<li><p>path: 冒泡阶段经过的节点;</p></li>
</ul>
<h4>方法</h4>
<ul>
<li><p>preventDefault(): 通知浏览器不要执行与事件关联的默认动作;</p></li>
<li><p>stopPropagation(): 阻止冒泡;</p></li>
</ul>
<p>参考及拓展阅读:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=4yOo5a80en1FuBknP0ZsFw%3D%3D.ZiOWIXbMQVvFWbdLFBZWoCbOgL3iFCaF1STEDp0QqVDGwIJUcr8%2BnkY5cf22pkQxWRHNMYvnnxWj6FOA4k5NrQ%3D%3D" rel="nofollow">HTML DOM Event 对象</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=idWbCHJyIi5fPu9LlP499g%3D%3D.NKlIjXXnnKTgaQ9mnjAmPnTjjinyHJx85ONqQLaCZWnKueM0ZopKkyWM%2BXWODWcSSkWbSMGRhYPkn39JLL0%2F1Q%3D%3D" rel="nofollow">最详细的JavaScript和事件解读</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=jaMBrm7G19yQDmp%2BX30glw%3D%3D.hxiwHuuP4%2FaXLsFxI%2FvVtpIBmMiJpvUZfmXpXjnRqBet0j2B%2B7zWRMZ%2FdjmpRabdqMAvQ4%2FcbE7A75Z33b2Msw%3D%3D" rel="nofollow">JavaScript Events</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=DS%2FE1v07g4kx%2BR92hQqF%2Fw%3D%3D.O6Kqu2ScNH04nmkN%2BmE5Wtb2T2Zr8CCdNAJ8cbd%2FhAsxl3l0Gscuu59eCrICMYMmRCWu5owjaUU%2F%2BJO8gpC0RewAuzw6mn1zwAQ882Ccy3s%3D" rel="nofollow">[解惑]JavaScript事件机制</a></p></li>
</ol>
JavaScript this 讲解
https://segmentfault.com/a/1190000004537183
2016-03-04T10:48:15+08:00
2016-03-04T10:48:15+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
2
<h2>精确把握 JavaScript 中的 this</h2>
<p>this 是 JavaScript 中的一个关键字,当一个函数被调用时,除了传入函数的显式参数以外,名为 this 的隐式参数也被传入了函数。this 参数指向了一个自动生成的内部对象,这个内部对象被称为<strong>函数上下文</strong>。与其他面向对象的语言不同的是, JavaScript 中的 this 依赖于函数的调用方式。所以,想要明白 this 的指向问题,还必须先研究函数在 JavaScript 中是如何被调用的。</p>
<h3>调用方式 1、作为函数进行调用</h3>
<p>这说法有点奇怪,函数当然是被作为函数进行调用的,但我们说一个函数“作为函数”被调用,只是为了区别于其他的调用方式。先看一个简单的例子:</p>
<pre><code>function func1() {
console.log(this === window); // true
}
func1();
var func2 = function () {
console.log(this === window); // true
}
func2();
function func3() {
"use strict";
console.log(this); // undefined
}
func3();
</code></pre>
<p>上面例子中的第一第二个 this 在非严格模式下指向全局上下文,即 window 对象,第三个在严格模式下则为 undefined。</p>
<p>所以,当函数被作为函数进行调用时,在非严格模式下,函数的上下文是 window 对象,而在严格模式下,函数上下文为 undefined。</p>
<h3>调用方式 2、作为方法进行调用</h3>
<p>当一个函数被赋值给一个对象的一个属性,并使用引用该函数的这个属性进行调用函数时,那么函数就是作为该对象的一个方法进行调用的。看下面的例子:</p>
<pre><code>var obj = {
func: function() {
console.log(this === obj); // true
}
};
obj.func();
</code></pre>
<p>上面的例子中,函数 func 的调用对象为 obj,所以函数上下文便是 obj。由此可见,将函数作为对象的一个方法进行调用时,该对象就是函数上下文,并且在函数内部可以用 this 来访问这个对象。</p>
<p>此时,我们再看一下第一种调用方式,即“作为函数”调用。“作为函数”进行调用的函数是定义在 window 上的,调用时也不需要 window 的引用,其实方式 1 中例子中的 <code>func1()</code> 就是 <code>window.func1()</code>,所以例子中的函数上下文便是 this。</p>
<h3>调用方式 3、作为构造器进行调用</h3>
<p>将函数作为构造器时,函数的声明与其他调用方式的函数声明一致。将函数作为构造器调用的例子如下:</p>
<pre><code>function Student() {
this.getContext = function() {
return this;
}
}
var stu = new Student();
console.log(stu.getContext() === stu); // true
</code></pre>
<p>将函数作为构造器调用时,便会通过这个函数生成一个新对象,这时,this 指向这个新创建的对象。</p>
<p>从上面几种调用方式来看,函数调用方式之间的主要差异是:作为 this 参数传递给执行函数的上下文对象之间的区别。作为方法进行调用,该上下文是方法的拥有者;作为全局函数调用,其上下文永远是 window (也就是说,该函数是 window 的一个方法);作为构造器进行调用时,其上下文对象则是新创建的对象实例。</p>
<p>下面的一种调用方式可以显式地指定上下文。</p>
<h3>调用方式 4、使用 apply() 和 call() 方法进行调用</h3>
<p>JavaScript 的每个函数都有 apply() 和 call() 函数,可以利用任何一个函数都可以显式指定任何一个对象作为其函数上下文。</p>
<p>通过 apply() 方法来调用函数,我们要给 apply() 传入两个参数:一个作为函数上下文对象,另一个作为函数参数所组成的数组。call() 方法的使用方式类似,唯一不同的是给函数传入的参数是一个参数列表,而不是单个数组。</p>
<pre><code>function func() {
var result = 0;
for(var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
this.result = result;
}
var obj1 = {};
var obj2 = {};
func.apply(obj1, [1, 2, 3]);
func.call(obj2, 4, 5, 6);
console.log(obj1.result === 6); // true
console.log(obj2.result === 15); // true
</code></pre>
<p>在上面的代码中,<code>func.apply(obj1, [1, 2, 3]);</code> 将函数的上下文定义为 obj1,并且传入 1、2、3 三个参数,<code>func.call(obj2, 4, 5, 6);</code> 将函数的上下文定义为 obj2,并且传入 4、5、6 三个参数。</p>
<p>那 apply 和 call 基本相同,那么我们该用哪一个呢?其实 apply 和 call 的区别仅仅在于调用时传入的参数不同,其他完全一样。所以,在选择时,主要看传入的参数。如果已知参数已经在数组里了则用 apply 即可,或者参数是动态生成的,可以把参数 push 进一个数组,然后再用 apply 调用。当参数数量已知,或者在参数里有很多无关的值则用 call 方法调用。</p>
<h3>其他补充</h3>
<h4>利用 bind() 改变函数上下文</h4>
<p>先看下面例子:</p>
<pre><code>var obj1 = {
a: 1
};
var obj2 = {
a: 2,
func: function() {
console.log(this.a);
}.bind(obj1)
};
obj2.func(); // 1
</code></pre>
<p>ECMAScript 5 引入了 <code>Function.prototype.bind</code>,其会创建一个绑定函数,当调用这个绑定函数时,函数上下文将会是 bind() 方法的第一个参数。上面的例子中,将 obj1 设置为函数上下文,所以利用 func 来调用函数时,函数的上下文为 obj1,而不是它的调用者 obj2。</p>
<h4>利用 Array 的 5 个方法改变函数上下文</h4>
<p>5 个方法分别是:</p>
<ul>
<li><p><code>Array.prototype.every(callbackfn [, thisArg ])</code></p></li>
<li><p><code>Array.prototype.some(callbackfn [, thisArg ])</code></p></li>
<li><p><code>Array.prototype.forEach(callbackfn [, thisArg ])</code></p></li>
<li><p><code>Array.prototype.map(callbackfn [, thisArg ])</code></p></li>
<li><p><code>Array.prototype.filter(callbackfn [, thisArg ])</code></p></li>
</ul>
<p>当调用以上 5 个方法时,传入的参数除了回调函数以外,还可以传入另外一个可选地参数,即函数上下文,代表回调函数中的函数上下文。如果省略该参数,则 callback 被调用时的 this 值,在非严格模式下为全局对象,在严格模式下传入 undefined。看下面的例子:</p>
<pre><code>var arr = ["segmentfault"];
var obj = {};
arr.forEach(function(ele, ind) {
console.log(this === window); // true
});
arr.forEach(function(ele, ind) {
console.log(this === obj); // true
}, obj);
</code></pre>
<h3>测试</h3>
<p>在网上找了几个代码小例子,来测试对上面内容的理解,答案在最下面。</p>
<h4>1、</h4>
<pre><code>if (true) {
// this
}
</code></pre>
<h4>2、</h4>
<pre><code>var obj = {
someData: "a string"
};
function myFun() {
// this
}
obj.staticFunction = myFun;
obj.staticFunction();
</code></pre>
<h4>3、</h4>
<pre><code>var obj = {
myMethod : function () {
// this
}
};
var myFun = obj.myMethod;
myFun();
</code></pre>
<h4>4、</h4>
<pre><code>function myFun() {
// this
}
var obj = {
someData: "a string"
};
myFun.call(obj);
</code></pre>
<h4>答案</h4>
<ul>
<li><p>1、window</p></li>
<li><p>2、obj</p></li>
<li><p>3、window</p></li>
<li><p>4、obj</p></li>
</ul>
<p>注:以上代码均在浏览器环境中测试。</p>
Markdown 标记示例
https://segmentfault.com/a/1190000003930804
2015-10-30T15:31:03+08:00
2015-10-30T15:31:03+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
0
<h2>Markdown 标记示例</h2>
<p>Markdown 是一种轻量级的标记语言,其用简单的标记语法便可达到排版的目的,其可以使我们更加专注于内容的编写,而不需过多关注排版。本文主要整理了 Markdown 中的常用的标记语法,以便自己与他人以后查用。</p>
<h3>段落元素</h3>
<h4>1、段落与换行</h4>
<p>Markdown 中的段落指连续的一段文字,编写时段落之间用至少一个<strong>空行</strong>隔开,段落内多个空格将被视为一个空格,段首不支持缩进。</p>
<p>如果想要在显示时显示多个空行,可以插入 <code><br/></code> 来实现,注意的是,插入的 <code><br/></code> 应与前后的段落中间至少空一行。</p>
<h4>2、标题</h4>
<p>Markdown 支持两种类型的标题。</p>
<pre><code>//类型 1
这是一级标题
==========
这是二级标题
----------
//类型 2
# 这是一级标题
## 这是二级标题
...
###### 这是六级标题
</code></pre>
<p>从上面可以看出类型 1 是在标题下面插入 <code>=</code> 或者 <code>-</code> 来标识标题等级,但局限是其只能表示两个级别的标题。</p>
<p>类型 2 是在标题前面插入 1 - 6 个 # ,表示 6 个等级的标题,这是比较推荐的用法。</p>
<h4>3、引用</h4>
<p>Markdown 中使用 <code>></code> 来引用。我们可以在一段文字中的每行开头加上 <code>></code> 来表示一段引用文字,也可以只在一段文字的开头插入一个 <code>></code> 来表示,如下面的 1、2 两种方式:</p>
<pre><code>//方式 1
> 这是一句话
> 这是第二句话
//方式 2
> 这是一句话
这是第二句话</code></pre>
<p>Markdown 支持使用不同数量的 <code>></code> 表示嵌套引用。</p>
<pre><code>> 这是外层的引用
> > 这是内层的引用</code></pre>
<h4>4、无序列表</h4>
<p>无序列表使用 <code>-</code>、 <code>+</code> 或 <code>*</code> 来作为标记。</p>
<pre><code>- 第一项
- 第二项
- 第三项</code></pre>
<p>上面的 <code>-</code> 可以用 <code>+</code>、 <code>*</code>替换。需要注意的是,<code>-</code> 等符号与后面的文字至少空一格空格。</p>
<h4>5、有序列表</h4>
<p>有序列表使用数字和紧挨着的点号表示。</p>
<pre><code>1. 第一项
2. 第二项
3. 第三项</code></pre>
<p>同无序列表一样,标记符号与后面的文字至少空一格空格。但编辑时的数字对显示无影响。</p>
<pre><code>2. 第一项
6. 第二项
1. 第三项</code></pre>
<p>上面的例子与前一个显示的结果完全一致,但建议编辑时按照数字顺序。</p>
<h5>列表</h5>
<ul>
<li><p>有序列表和无序列表的每一项中均可嵌套其他列表;</p></li>
<li><p>在列表项之间要插入段落时,这时需要将列表项之间的段落缩进 4 个空格;</p></li>
<li><p>使用 <code>1\. </code> 来输出 <code>1. </code>;</p></li>
</ul>
<h4>6、代码区块</h4>
<p>缩进 4 个空格,需要注意的是,每行代码都需要至少缩进 4 个空格,不能像段落一样采用首行标记的偷懒写法,一个代码区会一直持续到没有缩进 4 个空格的那一行。</p>
<p>也可以用一对三个连续的撇号 <code> ` </code> 来包裹代码段。</p>
<h4>7、分割线</h4>
<p>使用三个及以上的 <code>*</code>、 <code>-</code> 或 <code>_</code>来表示一个分割线,符号不能混用,符号之间可以插入多个空格。需要注意的是,使用 <code>-</code> 来插入分割线时需要与上一个段落至少空一行,否则 Markdown 会将上一行文字解释为二级标题。</p>
<h4>8、表格</h4>
<p>表格是 Markdown 比较复杂的一种表示。</p>
<pre><code>| Table | Col1 | Col2 |
| ----- |:----:| ----:|
| Row1 | 1-1 | 1-2 |
| Row2 | 2-1 | 2-2 |
| Row3 | 3-1 | 3-2 |
</code></pre>
<p>上面第二行中的点代表对齐方式,分别是默认(居右)、居中、居左。</p>
<hr>
<h3>行内元素</h3>
<h4>9、超链接</h4>
<p>Markdown 中有三种方式实现超链接。</p>
<pre><code>//方式 1
[百度](http://www.baidu.com)
//方式 2
[百度][Baidu-url]</code></pre>
<p>方式 1 较为常用,也可以为链接的文字加上提示文字,只要在括号中超链接加上空格后添加提示内容即可。</p>
<pre><code>[百度](http://www.baidu.com "这是提示文字")
</code></pre>
<p>方式 2 由链接文字和链接地址组成,不同的是两者均由 <code>[]</code> 包裹。链接地址的格式为:</p>
<ul>
<li><p>方括号,里面输入链接地址;</p></li>
<li><p>紧接着是一个冒号;</p></li>
<li><p>冒号后面至少一个空格;</p></li>
<li><p>链接地址;</p></li>
<li><p>若有提示文字,空格后用引号或者括号包裹提示文字。</p></li>
</ul>
<p>下面是完整示例:</p>
<pre><code> [百度][Baidu-url]
[Baidu-url]: http://www.baidu.com "这是提示文字"</code></pre>
<p>第三种方式是用 <code><></code> 来包裹 URL。</p>
<pre><code>
//方式 3
<http://www.baidu.com>
</code></pre>
<h4>10、加粗和斜体</h4>
<p>Markdown 使用 <code>*</code> 和 <code>_</code> 来表示粗体和斜体。</p>
<pre><code>//加粗
**这是加粗文字**
__这也是加粗文字__
//斜体
*这是斜体文字*
_这也是斜体文字_
</code></pre>
<p>被偶数个 <code>*</code> 或 <code>_</code> 包裹的文字显示加粗效果,被奇数个包裹的为倾斜效果。</p>
<p>需要注意的是,<code>*</code> 和 <code>-</code> 要成对出现,不能混合使用,也不能只出现一个。同时,标识符号要与标识的文字紧挨着,符号与符号之间、符号文字之间不能有任何空格。</p>
<h4>11、代码</h4>
<p>使用 <code> ` </code> (撇号) 来包裹一小段代码。</p>
<pre><code>`Hello world.`
</code></pre>
<p>若想在代码中添加撇号,可以使用多个撇号包裹里面需要添加的撇号,但注意里面的连续的撇号数量不能超过外面的数量。</p>
<pre><code>//显示一个撇号
`` ` ``
//显示两个撇号
``` `` ```
</code></pre>
<h4>12、图片</h4>
<p>图片的插入方式跟超链接前两种插入方式类似。</p>
<pre><code>//方式 1
![如果图片不能显示,就显示这段文字](图片 url)
//方式 2
![如果图片不能显示,就显示这段文字][Image-url]</code></pre>
<h3>反斜杠 <code>\</code>
</h3>
<p>我们经常需要在文章中插入一些特殊符号,而这些符号恰好是前面所讲的标识符号,可以在特殊符号前插入 <code>\</code> 来直接显示符号,而不让 Markdown 来将其解释为标识符号。</p>
<p>Markdown 支持以下这些符号前插入 <code>\</code> 而显示其本来样子:</p>
<pre><code> \ 反斜线
` 反引号
* 星号
_ 底线
{} 花括号
[] 方括号
() 括弧
# 井字号
+ 加号
- 减号
. 英文句点
! 惊叹号</code></pre>
<h3>工具</h3>
<ul>
<li><p>Windows 环境下,推荐 Markdownpad,自带图床功能;</p></li>
<li><p>Mac 环境下,推荐 Mou;</p></li>
<li><p>笔记软件: 为知笔记较好的支持 markdown,且支持 Windows、Mac、web、ios、android 等各个平台。</p></li>
</ul>
<p>参考资料:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=JK2VZTp4fHxMlE6A6eukmg%3D%3D.76UxSn3R9oFyoNRmGRTOebFypzkA4P92xThJxxUUW6hvTghZOBX%2FPHhr2rZwueZX4SXA6zXnAumQAYXyII0%2BeA%3D%3D" rel="nofollow">http://daringfireball.net/projects/markdown/syntax</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=wKryl81cyiL35KrGTxYLcA%3D%3D.3PQT1OQ18eryE0urgkFBZiPkhVBJaslJqVf3IQRkettmhVo0pigwMhNeEc2FF%2FGn" rel="nofollow">http://www.jianshu.com/p/1e402922ee32/</a></p></li>
</ol>
从输入 URL 到页面加载完成的过程中都发生了什么
https://segmentfault.com/a/1190000003925803
2015-10-29T15:59:27+08:00
2015-10-29T15:59:27+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
3
<h2>根据 URL 请求页面过程</h2>
<p>说实话,这类文章网上一抓一大把,而我仍想写这篇博客,一方面是想再仔细缕一下这个过程,另一方面是希望用清晰的语言和结构来解释,也算是小小地挑战一下自己。</p>
<h3>过程概述</h3>
<ol>
<li><p>浏览器查找域名对应的 IP 地址;</p></li>
<li><p>浏览器根据 IP 地址与服务器建立 socket 连接;</p></li>
<li><p>浏览器与服务器通信: 浏览器请求,服务器处理请求;</p></li>
<li><p>浏览器与服务器断开连接。</p></li>
</ol>
<p>天啦撸,结束了?也太简单了吧。。。各位看官,不急,都说了是概述,且向下看。</p>
<h3>根据域名查找 IP 地址</h3>
<h4>概念解释</h4>
<ul>
<li><p>IP 地址:IP 协议为互联网上的每一个网络和每一台主机分配的一个逻辑地址。IP 地址如同门牌号码,通过 IP 地址才能确定一台主机位置。服务器本质也是一台主机,想要访问某个服务器,必须先知道它的 IP 地址;</p></li>
<li><p>域名( DN ):IP 地址由四个数字组成,中间用点号连接,在使用过程中难记忆且易输入错误,所以用我们熟悉的字母和数字组合来代替纯数字的 IP 地址,比如我们只会记住 www.baidu.com(百度域名) 而不是 220.181.112.244(百度的其中一个 IP 地址);</p></li>
<li><p>DNS: 每个域名都对应一个或多个提供相同服务服务器的 IP 地址,只有知道服务器 IP 地址才能建立连接,所以需要通过 DNS 把域名解析成一个 IP 地址。</p></li>
</ul>
<p>知道了上面的概念,大概就知道了想要获得服务器的门牌号码,需要先将域名转换成 IP 地址。转换过程如下(以查询 www.baidu.com 的 IP 地址为例,其中2、3、4步均在上一步未查询成功的情况下进行):</p>
<h4>查找过程</h4>
<ol>
<li><p>浏览器搜索自己的 DNS 缓存(维护一张域名与 IP 地址的对应表);</p></li>
<li><p>搜索操作系统中的 DNS 缓存(维护一张域名与 IP 地址的对应表);</p></li>
<li><p>搜索操作系统的 hosts 文件( Windows 环境下,维护一张域名与 IP 地址的对应表);</p></li>
<li>
<p>操作系统将域名发送至 LDNS(本地区域名服务器,如果你在学校接入互联网,则 LDNS 服务器就在学校,如果通过电信接入互联网,则 LDNS 服务器就在你当地的电信那里。)LDNS 查询 自己的 DNS 缓存(一般查找成功率在 80% 左右),查找成功则返回结果,失败则发起一个迭代 DNS 解析请求;</p>
<ol>
<li><p>LDNS 向 Root Name Server (根域名服务器,其虽然没有每个域名的的具体信息,但存储了负责每个域,如 com、net、org等的解析的顶级域名服务器的地址)发起请求,此处,Root Name Server 返回 com 域的顶级域名服务器的地址;</p></li>
<li><p>LDNS 向 com 域的顶级域名服务器发起请求,返回 baidu.com 域名服务器地址;</p></li>
<li><p>LDNS 向 baidu.com 域名服务器发起请求,得到 www.baidu.com 的 IP 地址;</p></li>
</ol>
</li>
<li><p>LDNS 将得到的 IP 地址返回给操作系统,同时自己也将 IP 地址缓存起来;</p></li>
<li><p>操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来;</p></li>
<li><p>至此,浏览器已经得到了域名对应的 IP 地址。</p></li>
</ol>
<h4>补充说明</h4>
<ul>
<li><p>域名与 URL 是两个概念:域名是一台或一组服务器的名称,用来确定服务器在 Internet 上的位置;URL 是统一资源定位符,用来确定某一个文件的具体位置,例如,segmentfault.com 是 SF 的域名,根据这个域名可以找到 SF 的服务器,<a>segmentfault.com/a/1190000003829539</a> 是 URL ,可以根据这个 URL 定位我写的第一篇博客;</p></li>
<li><p>IP 地址与域名不是一一对应的关系:可以把多个提供相同服务的服务器 IP 设置为同一个域名,但在同一时刻一个域名只能解析出一个 IP地址;同时,一个 IP 地址可以绑定多个域名,数量不限;</p></li>
</ul>
<h3>建立连接--三次握手</h3>
<p>知道了服务器的 IP 地址,下面便开始与服务器建立连接了。</p>
<p>通俗地讲,通信连接的建立需要经历以下三个过程:</p>
<ol>
<li><p>主机向服务器发送一个建立连接的请求(<strong>您好,我想认识您</strong>);</p></li>
<li><p>服务器接到请求后发送同意连接的信号(<strong>好的,很高兴认识您</strong>);</p></li>
<li><p>主机接到同意连接的信号后,再次向服务器发送了确认信号(<strong>我也很高兴认识您</strong>),自此,主机与服务器两者建立了连接。</p></li>
</ol>
<h4>补充说明</h4>
<ul><li><p>TCP 协议:三次握手的过程采用 TCP 协议,其可以保证信息传输的可靠性,三次握手过程中,若一方收不到确认信号,协议会要求重新发送信号。</p></li></ul>
<h3>网页请求与显示</h3>
<p>当服务器与主机建立了连接之后,下面主机便与服务器进行通信。网页请求是一个单向请求的过程,即是一个主机向服务器请求数据,服务器返回相应的数据的过程。</p>
<ol>
<li><p>浏览器根据 URL 内容生成 HTTP 请求,请求中包含请求文件的位置、请求文件的方式等等;</p></li>
<li><p>服务器接到请求后,会根据 HTTP 请求中的内容来决定如何获取相应的 HTML 文件;</p></li>
<li><p>服务器将得到的 HTML 文件发送给浏览器;</p></li>
<li><p>在浏览器还没有完全接收 HTML 文件时便开始渲染、显示网页;</p></li>
<li><p>在执行 HTML 中代码时,根据需要,浏览器会继续请求图片、CSS、JavsScript等文件,过程同请求 HTML ;</p></li>
</ol>
<h3>断开连接--四次挥手</h3>
<ol>
<li><p>主机向服务器发送一个断开连接的请求(<strong>不早了,我该走了</strong>);</p></li>
<li><p>服务器接到请求后发送确认收到请求的信号(<strong>知道了</strong>);</p></li>
<li><p>服务器向主机发送断开通知(<strong>我也该走了</strong>);</p></li>
<li><p>主机接到断开通知后断开连接并反馈一个确认信号(<strong>嗯,好的</strong>),服务器收到确认信号后断开连接;</p></li>
</ol>
<h4>补充说明</h4>
<ul>
<li><p>为什么服务器在接到断开请求时不立即同意断开:当服务器收到断开连接的请求时,可能仍然有数据未发送完毕,所有服务器先发送确认信号,等所有数据发送完毕后再同意断开。</p></li>
<li><p>第四次握手后,主机发送确认信号后并没有立即断开连接,而是等待了 2 个报文传送周期,原因是:如果第四次握手的确认信息丢失,服务器将会重新发送第三次握手的断开连接的信号,而服务器发觉丢包与重新发送的断开连接到达主机的时间正好为 2 个报文传输周期。</p></li>
</ul>
CSS 引入方式
https://segmentfault.com/a/1190000003866058
2015-10-16T13:29:49+08:00
2015-10-16T13:29:49+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
5
<h2>HTML 中引入 CSS 的方式</h2>
<p>有 4 种方式可以在 HTML 中引入 CSS。其中有 2 种方式是在 HTML 文件中直接添加 CSS 代码,另外两种是引入 外部 CSS 文件。下面我们就来看看这些方式和它们的优缺点。</p>
<h3>内联方式</h3>
<p>内联方式指的是直接在 HTML 标签中的 <code>style</code> 属性中添加 CSS。</p>
<p>示例:</p>
<pre><code><div style="background: red"></div>
</code></pre>
<p>这通常是个很糟糕的书写方式,它只能改变当前标签的样式,如果想要多个 <code><div></code> 拥有相同的样式,你不得不重复地为每个 <code><div></code> 添加相同的样式,如果想要修改一种样式,又不得不修改所有的 style 中的代码。很显然,内联方式引入 CSS 代码会导致 HTML 代码变得冗长,且使得网页难以维护。</p>
<h3>嵌入方式</h3>
<p>嵌入方式指的是在 HTML 头部中的 <code><style></code> 标签下书写 CSS 代码。</p>
<p>示例:</p>
<pre><code><head>
<style>
.content {
background: red;
}
</style>
</head>
</code></pre>
<p>嵌入方式的 CSS 只对当前的网页有效。因为 CSS 代码是在 HTML 文件中,所以会使得代码比较集中,当我们写模板网页时这通常比较有利。因为查看模板代码的人可以一目了然地查看 HTML 结构和 CSS 样式。因为嵌入的 CSS 只对当前页面有效,所以当多个页面需要引入相同的 CSS 代码时,这样写会导致代码冗余,也不利于维护。</p>
<h3>链接方式</h3>
<p>链接方式指的是使用 HTML 头部的 <code><head></code> 标签引入外部的 CSS 文件。</p>
<p>示例:</p>
<pre><code><head>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
</code></pre>
<p>这是最常见的也是最推荐的引入 CSS 的方式。使用这种方式,所有的 CSS 代码只存在于单独的 CSS 文件中,所以具有良好的可维护性。并且所有的 CSS 代码只存在于 CSS 文件中,CSS 文件会在第一次加载时引入,以后切换页面时只需加载 HTML 文件即可。</p>
<h3>导入方式</h3>
<p>导入方式指的是使用 CSS 规则引入外部 CSS 文件。</p>
<p>示例:</p>
<pre><code><style>
@import url(style.css);
</style>
</code></pre>
<h3>比较链接方式和导入方式</h3>
<p>链接方式(下面用 link 代替)和导入方式(下面用 <a href="/u/import">@import</a> 代替)都是引入外部的 CSS 文件的方式,下面我们来比较这两种方式,并且说明为什么不推荐使用 @import。</p>
<ul>
<li><p>link 属于 HTML,通过 <code><link></code> 标签中的 href 属性来引入外部文件,而 <a href="/u/import">@import</a> 属于 CSS,所以导入语句应写在 CSS 中,要注意的是导入语句应写在样式表的开头,否则无法正确导入外部文件;</p></li>
<li><p><a href="/u/import">@import</a> 是 CSS2.1 才出现的概念,所以如果浏览器版本较低,无法正确导入外部样式文件;</p></li>
<li><p>当 HTML 文件被加载时,link 引用的文件会同时被加载,而 <a href="/u/import">@import</a> 引用的文件则会等页面全部下载完毕再被加载;</p></li>
</ul>
<hr>
<p><strong>小结</strong>:我们应尽量使用 <code><link></code> 标签导入外部 CSS 文件,避免或者少用使用其他三种方式。</p>
<hr>
<hr>
<p>参考资料:</p>
<ol>
<li><p><a href="https://link.segmentfault.com/?enc=QqItJVxqMDlU9WYSvqqBcQ%3D%3D.UTjtwpvEt2IYs9ob%2BNRSvoZuAI5ysgN96m%2F0cyxrEQkFGcj4%2FokZf6FtYtoZzlDl1VHWdF9H87tQNdQcFOL%2FYg%3D%3D" rel="nofollow"></a><a href="https://link.segmentfault.com/?enc=Ahjn7RDNeNqCFDcUm281Uw%3D%3D.zeoWklH0q4qR8M3%2By1jeDLrTZK0zTJn3rBapeOSt7T%2FokYuTSpuKPTewyCXau15CNeiTAPgIpYZaGt1ehuXDLw%3D%3D" rel="nofollow">http://www.stevesouders.com/blog/2009/04/09/dont-use-import</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=YYvdh7wxpH5b%2F5WxkWKxUQ%3D%3D.b3YY6SZDWTk3GutaKViRbg81zgBTiXPnwCZOSB%2BYppNn9P%2FuP2iEYy3kJuM2xgKwkU3ncEU1nLkeialHP7yHa7R1QT6A%2F5IUP9KcmoRHkvSF55G2tJVGBOoYKD5vC18W" rel="nofollow"></a><a href="https://link.segmentfault.com/?enc=iazlwYQudFyKwLvS7gc5aw%3D%3D.N5%2BNkcmxjIGfXpDX87kR3AZ7%2BaVXcpTUUNmG9FGqU26UJcDbeCMXHJukByDqP7YOwdCW52e5pEaz4DOEsK%2Btg0olvC%2BYYok2XAxScgn1Y3%2FVKKCUTfvF57l%2Fyxy1VN1Q" rel="nofollow">http://matthewjamestaylor.com/blog/adding-css-to-html-with-link-embed-inline-and-import</a></p></li>
</ol>
CSS 样式优先级
https://segmentfault.com/a/1190000003860309
2015-10-15T11:14:59+08:00
2015-10-15T11:14:59+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
13
<h2>CSS 样式优先级</h2>
<p>当创建的样式表越来越复杂时,一个标签的样式将会受到越来越多的影响,这种影响可能来自周围的标签,也可能来自其自身。下面我们从这两方面去看看 CSS 样式的优先级。</p>
<h3>CSS 的继承性</h3>
<p>CSS 的继承特性指的是应用在一个标签上的那些 CSS 属性被传到其子标签上。看下面的 HTML 结构:</p>
<pre><code><div>
<p></p>
</div>
</code></pre>
<p>如果 <code><div></code> 有个属性 <code>color: red</code>,则这个属性将被 <code><p></code> 继承,即 <code><p></code> 也拥有属性 <code>color: red</code>。</p>
<p>由上可见,当网页比较复杂, HTML 结构嵌套较深时,一个标签的样式将深受其祖先标签样式的影响。影响的规则是:</p>
<p><strong>CSS 优先规则1:</strong> 最近的祖先样式比其他祖先样式优先级高。</p>
<p>例1:</p>
<pre><code>
<!-- 类名为 son 的 div 的 color 为 blue -->
<div style="color: red">
<div style="color: blue">
<div class="son"></div>
</div>
</div>
</code></pre>
<p>如果我们把一个标签从祖先那里继承来的而自身没有的属性叫做“祖先样式”,那么“直接样式”就是一个标签直接拥有的属性。又有如下规则:</p>
<p><strong>CSS 优先规则2:</strong>“直接样式”比“祖先样式”优先级高。</p>
<p>例2:</p>
<pre><code><!-- 类名为 son 的 div 的 color 为 blue -->
<div style="color: red">
<div class="son" style="color: blue"></div>
</div>
</code></pre>
<h3>选择器的优先级</h3>
<p>上面讨论了一个标签从祖先继承来的属性,现在讨论标签自有的属性。在讨论 CSS 优先级之前,先说说 CSS 7 种基础的选择器:</p>
<ul>
<li><p>ID 选择器, 如 #id{}</p></li>
<li><p>类选择器, 如 .class{}</p></li>
<li><p>属性选择器, 如 a[href="segmentfault.com"]{}</p></li>
<li><p>伪类选择器, 如 :hover{}</p></li>
<li><p>伪元素选择器, 如 ::before{}</p></li>
<li><p>标签选择器, 如 span{}</p></li>
<li><p>通配选择器, 如 *{}</p></li>
</ul>
<p><strong>CSS 优先规则3:</strong>优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器</p>
<p>例3:</p>
<pre><code>// HTML
<div class="content-class" id="content-id" style="color: black"></div>
// CSS
#content-id {
color: red;
}
.content-class {
color: blue;
}
div {
color: grey;
}
<div> 最终的 color 为 black,因为内联样式比其他选择器的优先级高。
</code></pre>
<p>所有 CSS 的选择符由上述 7 种基础的选择器或者组合而成,组合的方式有 3 种:</p>
<ul>
<li><p>后代选择符: .father .child{}</p></li>
<li><p>子选择符: .father > .child{}</p></li>
<li><p>相邻选择符: .bro1 + .bro2{}</p></li>
</ul>
<p>当一个标签同时被多个选择符选中,我们便需要确定这些选择符的优先级。我们有如下规则:</p>
<p><strong>CSS 优先规则4:</strong>计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照“就近原则”来判断。</p>
<p>例4:</p>
<pre><code>// HTML
<div id="con-id">
<span class="con-span"></span>
</div>
// CSS
#con-id span {
color: red;
}
div .con-span {
color: blue;
}
由规则 4 可见,<span> 的 color 为 red。
</code></pre>
<p>如果外部样式表和内部样式表中的样式发生冲突会出现什么情况呢?这与样式表在 HTML 文件中所处的位置有关。样式被应用的位置越在下面则优先级越高,其实这仍然可以用规则 4 来解释。</p>
<p>例5:</p>
<pre><code>// HTML
<link rel="stylesheet" type="text/css" href="style-link.css">
<style type="text/css">
@import url(style-import.css);
div {
background: blue;
}
</style>
<div></div>
// style-link.css
div {
background: lime;
}
// style-import.css
div {
background: grey;
}
从顺序上看,内部样式在最下面,被最晚引用,所以 <div> 的背景色为 blue。
</code></pre>
<p>上面代码中,<code>@import</code> 语句必须出现在内部样式之前,否则文件引入无效。当然不推荐使用 <code>@import</code> 的方式引用外部样式文件,原因见另一篇博客:<a href="http://segmentfault.com/a/1190000003866058">CSS 引入方式</a>。</p>
<p>CSS 还提供了一种可以完全忽略以上规则的方法,当你一定、必须确保某一个特定的属性要显示时,可以使用这个技术。</p>
<p><strong>CSS 优先规则5:</strong>属性后插有 <code>!important</code> 的属性拥有最高优先级。若同时插有 <code>!important</code>,则再利用规则 3、4 判断优先级。</p>
<p>例6:</p>
<pre><code>// HTML
<div class="father">
<p class="son"></p>
</div>
// CSS
p {
background: red !important;
}
.father .son {
background: blue;
}
虽然 .father .son 拥有更高的权值,但选择器 p 中的 background 属性被插入了 !important,
所以 <p> 的 background 为 red。
</code></pre>
<h3>错误的说法</h3>
<p>在学习过程中,你可能发现给选择器加权值的说法,即 ID 选择器权值为 100,类选择器权值为 10,标签选择器权值为 1,当一个选择器由多个 ID 选择器、类选择器或标签选择器组成时,则将所有权值相加,然后再比较权值。这种说法其实是有问题的。比如一个由 11 个类选择器组成的选择器和一个由 1 个 ID 选择器组成的选择器指向同一个标签,按理说 110 > 100,应该应用前者的样式,然而事实是应用后者的样式。错误的原因是:<strong>选择器的权值不能进位</strong>。还是拿刚刚的例子说明。11 个类选择器组成的选择器的总权值为 110,但因为 11 个均为类选择器,所以其实总权值最多不能超过 100, 你可以理解为 99.99,所以最终应用后者样式。</p>
Javascript 中的 Array 操作
https://segmentfault.com/a/1190000003832912
2015-10-10T09:25:00+08:00
2015-10-10T09:25:00+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
3
<h2>Javascript 中的 Array 操作</h2>
<p>在各种语言中,数组总是一个比较重要的数据结构,Javascript 中的 Array 也不例外。Javascript 中的 Array 提供了一系列方法可以更好地让我们操作 Array 中的元素,下面我们就来看看这些操作方法。</p>
<h3>Array 之基本操作</h3>
<p>在基本操作部分,我们约定,加粗的方法可以改变数组本身,其余方法不改变。</p>
<h4>1、 Array 创建</h4>
<ol>
<li><p>利用字面量创建 Array (推荐): <code>var arr = []</code>;</p></li>
<li><p>利用 Array 对象创建 Array: <code>var arr = new Array([length]);</code>//可以指定数组长度,注意不是数组上限;</p></li>
<li><p>利用 Array 对象创建 Array: <code>var arr = new Array(value1, value2, value3, …);</code><br>注意:Javascript 中 Array 中的元素不要求类型一致;</p></li>
</ol>
<h4>2、 Array 访问元素</h4>
<ol>
<li><p><code>var value = arr[index];</code></p></li>
<li><p><code>Array.slice(start[, end]);</code>//从 start 位置开始截取原数组至 end (不包括 end 位置),返回截取的数组。若省略 end ,则截取 start 位置后所有元素;</p></li>
</ol>
<h4>3、 Array 添加与删除元素</h4>
<h5>在数组末尾添加或删除元素:</h5>
<ol>
<li><p><strong><code>Array.push(value1, value2, vlaue3, …);</code></strong>//将一个或多个元素附加在数组末尾,并返回新数组长度;</p></li>
<li><p><strong><code>Array.pop();</code></strong>//移除数组最后一个值,并返回该元素值;</p></li>
</ol>
<h5>在数组开始添加或删除元素:</h5>
<ol>
<li><p><strong><code>Array.unshif(value1, value2, value3, …);</code></strong>//将一个或多个元素附加在数组开头,原数组元素依次后移,并返回新数组长度;</p></li>
<li><p><strong><code>Array.shift();</code></strong>//移除数组第一个值,并返回该元素值;</p></li>
</ol>
<h5>在任意位置添加或删除元素:</h5>
<ol>
<li><p><strong><code>Array.splice(pos, 0, value1, value2, value3, ...);</code></strong>//从数组 pos 位置开始删除 0 个元素,并在删除的位置插入value1, vlaue2, vlaue3…,并返回一个空数组;</p></li>
<li><p><strong><code>Array.splice(pos, count);</code></strong>//从数组 pos 位置开始删除 count 个元素,</p></li>
</ol>
<h4>4、 Array 合并</h4>
<ol><li><p><code>Array.concat(para);</code>// para 可以为数组或元素值,也可以为两者组合,将所有元素连接成一个数组,返回连接好的数组;</p></li></ol>
<h4>5、 Array 字符串化</h4>
<ol>
<li><p><code>Array.join(sperator);</code>//返回由数组元素组成并由 seperator 分隔的字符串。若 seperator 为空,则默认以 “,” 连接;</p></li>
<li><p><code>Array.toString()</code>和<code>Array.toLocaleString();</code>//返回由数组元素组成并由“,”分隔的字符串(不常用);两种方法的区别在于 toLocaleString() 会转变为本地环境字符串(如Date.toLocalString() 会转化为当地时间格式的字符串),toString() 则转为传统字符串; Array 的这两种方法用法无区别;</p></li>
</ol>
<h4>6、 Array 排序</h4>
<ol>
<li><p><strong><code>Array.reverse();</code></strong>//颠倒数组中的元素;</p></li>
<li><p><strong><code>Array.sort();</code></strong>//按字符编码进行排序,支持自定义排序函数;</p></li>
</ol>
<h4>7、 Array 查找</h4>
<ol>
<li><p><code>indexOf()</code>//返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1。</p></li>
<li><p><code>lastIndexOf()</code>//返回在数组中搜索到的与给定参数相等的元素的最后(最大)索引。</p></li>
</ol>
<h4>8、 其他函数</h4>
<ol><li><p><code>Array.isArray();</code>//判断某个值是否为数组;</p></li></ol>
<h3>Array 之高级操作</h3>
<p>首先介绍 Array 的 5 种迭代函数。这 5 种函数的特点是都有一个 callback 函数,函数会为每一个数组元素调用一次 callback 函数。可选参数是 thisArg, 作为 callback被调用时的 this 值,如果没有传入 thisArg 值,则在非严格模式下将会是全局对象,严格模式下是 undefined。其中,callback 函数中包含三个参数,分别是当前元素的值、当前元素索引和被遍历的数组。这 5 种函数遍历的元素个数在第一次调用 callback 函数时就已经确定,即以后即使添加元素也不会改变遍历次数,未访问到且被删除的值不会被 callback 访问到,未访问到且被修改的元素被修改后,传递给 callback 的值是被修改后的值。</p>
<h4>1、 <code>Array.forEach()</code>
</h4>
<p>遍历并为每一项元素执行一遍 callback 函数。是 5 个函数中唯一一个没有返回值的函数。</p>
<pre><code>var arr = [1, 2, 3];
arr.forEach(function(ele) {
console.log(ele);
});
</code></pre>
<p>运行结果:</p>
<pre><code>1
2
3
</code></pre>
<h4>2、 <code>Array.map()</code>
</h4>
<p>遍历并处理所有元素,返回由每个 callback 函数返回值组成的新数组。</p>
<pre><code>var arr = [1, 2, 3];
var newArr = arr.map(function(ele) {
return "element" + ele;
});
console.log(newArr);
console.log(arr);
</code></pre>
<p>运行结果:</p>
<pre><code>["element1", "element2", "element3"]
[1, 2, 3]
</code></pre>
<h4>3、 <code>Array.filter()</code>
</h4>
<p>遍历所有元素,过滤掉不符合条件(即使得 callback 返回 false)的元素。</p>
<pre><code>var arr = [1, 2, 3, 11, 12, 13];
var newArr = arr.filter(function(ele) {
return ele > 10;
});
console.log(newArr);
console.log(arr);
</code></pre>
<p>运行结果:</p>
<pre><code>
[11, 12, 13]
[1, 2, 3, 11, 12, 13]
</code></pre>
<h4>4、 <code>Array.every()</code>
</h4>
<p>遍历所有元素,当所有元素使得 callback 返回 true,则返回 true,否则返回 false。</p>
<pre><code>var arr1 = [9, 12, 13];
var result1 = arr1.every(function(ele) {
return ele > 10;
});
var arr2 = [11, 12, 13];
var result2 = arr2.every(function(ele) {
return ele > 10;
});
console.log(arr1);
console.log("result1:" + result1);
console.log(arr2);
console.log("result2:" + result2);
</code></pre>
<p>运行结果:</p>
<pre><code>[9, 12, 13]
result1:false
[11, 12, 13]
result2:true
</code></pre>
<h4>5、 <code>Array.some()</code>
</h4>
<p>遍历所有元素,只要找到一个元素使得 callback 返回 true,则返回 true,否则返回 false。</p>
<pre><code>var arr1 = [1,3,5,7,9];
var result1 = arr1.some(function(ele) {
return ele > 10;
});
var arr2 = [1,2,3,11];
var result2 = arr2.some(function(ele) {
return ele > 10;
});
console.log(arr1);
console.log("result1:" + result1);
console.log(arr2);
console.log("result2:" + result2);
</code></pre>
<p>运行结果:</p>
<pre><code>[1, 3, 5, 7, 9]
result1:false
[1, 2, 3, 11]
result2:true
</code></pre>
<hr>
<h4>6、 <code>Array.reduce()</code>
</h4>
<p>将数组中的元素从左到右进行缩减,最终缩减为一个值。</p>
<p><code>Array.reduce()</code>语法:</p>
<pre><code>Array.reduce(function(previousValue, currentValue, index, array){
}, [initialValue]);
</code></pre>
<p>callback 函数的几个参数含义:</p>
<ul>
<li><p>previousValue:上一次执行 callback 函数后返回的值</p></li>
<li><p>currentValue:当前数组元素值</p></li>
<li><p>index:当前数组元素索引</p></li>
<li><p>array:遍历的数组对象</p></li>
</ul>
<p>第一次调用 callback 函数时,如果 initialValue 有值,则 previousValue 为 initialValue,<br>currentValue 为数组第一个元素。否则 previousValue 为数组第一个元素,currentValue 为数组第二个元素。</p>
<pre><code>var arr = ["a", "b", "c"];
arr.reduce(function(pre, cur, ind, array) {
console.log(ind);
});
</code></pre>
<p>运行结果:</p>
<pre><code>1
2
</code></pre>
<p>再看另一个有 initialValue 的例子:</p>
<pre><code>var arr = ["a", "b", "c"];
arr.reduce(function(pre, cur, ind, array) {
console.log(ind);
}, "z");
</code></pre>
<p>运行结果是:</p>
<pre><code>0
1
2
</code></pre>
<hr>
<hr>
<p>随着 ES6 的诞生,Array 出现了更多的方法。考虑到浏览器的兼容性,在使用下面的方法时需要注意所使用的浏览器是否支持。</p>
<h4>7、<code>Array.from()</code>
</h4>
<p>将一个类数组对象或可迭代对象转换成真实的数组。</p>
<ul>
<li><p>类数组对象: 拥有一个 length 属性和若干索引属性的任意对象</p></li>
<li><p>可迭代对象: 你可以从它身上迭代出若干个元素的对象,比如有 Map 和 Set 等(Map 与 Set 均为 ES6 中新的对象)</p></li>
</ul>
<pre><code>var arr = Array.from("Hello");
console.log(arr);
</code></pre>
<p>运行结果:</p>
<pre><code>[ "H", "e", "l", "l", "o" ]
</code></pre>
<h4>8、<code>Array.values()</code>
</h4>
<p>将一个数组转换成一个可迭代的对象,该对象包含数组每个索引的值。</p>
<pre><code>var arr = ['a', 'b', 'c'];
var obj = arr.values();
for (let letter of obj) {
console.log(letter);
}
</code></pre>
<p>运行结果:</p>
<pre><code>a
b
c</code></pre>
JavaScript 中 console 的用法
https://segmentfault.com/a/1190000003832065
2015-10-09T09:36:53+08:00
2015-10-09T09:36:53+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
20
<h2>Javascript 中 console 的用法</h2>
<p>在调试 JS 代码时,很多人倾向于使用 <code>alert()</code> 或者 <code>console.log()</code> 方法来输出信息,正如某些 Java 程序员喜欢在调试代码时使用 <code>System.out.println()</code> 输出信息一样。但与 Java 输出不一样的是, console 对象拥有多种方法可以更好的呈现信息,从而给代码调试带来方便。根据常用程度,列出以下几种 console 对象的方法:</p>
<ul>
<li><p><code>console.log()</code></p></li>
<li><p><code>console.debug()</code>、<code>console.info()</code>、<code>console.warn()</code> 与 <code>console.error()</code></p></li>
<li><p><code>console.table()</code></p></li>
<li><p><code>console.time()</code> 与 <code>console.timeEnd()</code></p></li>
<li><p><code>console.assert()</code></p></li>
<li><p><code>console.count()</code></p></li>
<li><p><code>console.group</code>、<code>console.groupEnd()</code> 与 <code>console.groupCollapsed()</code></p></li>
</ul>
<p>以下示例的运行环境是 Chrome 43。</p>
<h3>console.log()</h3>
<p>先来谈谈我们最熟悉也最常用的 <code>console.log()</code> 方法。</p>
<p>我们最常用的做法是通过它来输出一个变量或者输出一个字符串。比如下面:</p>
<pre><code>console.log("Hello China!");
var str = "Hello world!";
console.log(str);
</code></pre>
<p>运行结果如下:</p>
<pre><code>Hello China!
Hello world!
</code></pre>
<p>但我们也可以这样用 <code>console.log()</code> :</p>
<pre><code>// 代码段 1
var str1 = "hello";
var str2 = "world";
console.log(str1, str2);
// 代码段 2
console.log("%d年%d月%d日", 2015, 09, 22);
</code></pre>
<p>控制台会输出:</p>
<pre><code>hello world
2015年9月22日
</code></pre>
<p>代码片段 1 显示,<code>console.log()</code> 的参数可以有多个,输出的结果以一个空格隔开。</p>
<p>代码片段 2 显示,<code>console.log()</code> 可以使用 C 语言 <code>printf()</code> 风格的占位符,不过其支持的占位符种类较少,只支持字符串(%s)、整数(%d或%i)、浮点数(%f)和对象(%o)。</p>
<h3>console.debug()、 console.info()、 console.warn() 与 console.error()</h3>
<p>这四个方法的使用方法跟 <code>console.log()</code> 一模一样,差别在于输出的颜色与图标不同。下面是示例:</p>
<pre><code>console.log("log");
console.debug("debug");
console.info("info");
console.warn("warn");
console.error("error");
</code></pre>
<p>运行结果如下:</p>
<p><img src="http://i.imgur.com/vJJoNFM.png" alt="" title=""></p>
<h3>console.table()</h3>
<p>我们看下面一个变量:</p>
<pre><code>
var people = {
"person1": {"fname": "san", "lname": "zhang"},
"person2": {"fname": "si", "lname": "li"},
"person3": {"fname": "wu", "lname": "wang"}
};
</code></pre>
<p>我们用 <code>console.log()</code> 将之在 Chrome 的控制台中输出:</p>
<p><img src="http://i.imgur.com/zvnvFOx.png" alt="" title=""></p>
<p>再用 <code>console.table()</code> 输出:</p>
<p><img src="http://i.imgur.com/IivvkKr.png" alt="" title=""></p>
<p>所以从上面两种输出我们可以看出,当输出类似于这种两层嵌套的对象时,我们可以选择 <code>console.table()</code> 以表格的形式输出。当然,嵌套三层及以上的也会以表格形式输出,但限于表格只能显示二维信息的特点,其会在嵌套三层或以上的地方会显示 "Object" 字符串。</p>
<h3>console.time() 与 console.timeEnd()</h3>
<p>在调试时,我们经常需要知道一段代码执行时间,我们可以使用这两行代码来实现。看下面一段代码:</p>
<pre><code>
console.time("for-test");
var arr = [];
for(var i = 0; i < 100000; i++) {
arr.push({"key": i});
}
console.timeEnd("for-test");
</code></pre>
<p>输出为:</p>
<pre><code>for-test: 176.152ms
</code></pre>
<p>从上面的例子可以看出,我们用 <code>console.time()</code> 和 <code>console.timeEnd()</code> 包围要测试运行时间的代码,这两个方法的参数保持一致,以便正确识别和匹配代码开始和结束的位置。</p>
<h3>console.assert()</h3>
<p><code>console.assert()</code> 类似于单元测试中的断言,当表达式为 false 时,输出错误信息。示例如下:</p>
<pre><code>var arr = [1, 2, 3];
console.assert(arr.length === 4);
</code></pre>
<p>输出结果如下:</p>
<p><img src="http://i.imgur.com/1qwQFNl.png" alt="" title=""></p>
<h3>console.count()</h3>
<p>调试代码时,我们经常需要知道一段代码被执行了多少次,我们可以使用 <code>console.count()</code> 来方便的达到我们的目的。示例如下:</p>
<pre><code>function func() {
console.count("label");
}
for(var i = 0; i < 3; i++) {
func();
}
</code></pre>
<p>运行结果为:</p>
<pre><code>label: 1
label: 2
label: 3
</code></pre>
<h3>console.group()、 console.groupEnd() 与 console.groupCollapsed()</h3>
<p>一般的 <code>console.log()</code> 方法的输出没有层级关系,在需要一些显示层级关系的输出中显得苍白无力,使用 <code>console.group()</code> 可以达到我们的目的。示例代码如下:</p>
<pre><code>console.group("1");
console.log("1-1");
console.log("1-2");
console.log("1-3");
console.groupEnd();
console.group("2");
console.log("2-1");
console.log("2-2");
console.log("2-3");
console.groupEnd();
</code></pre>
<p>运行结果为:</p>
<p><img src="http://i.imgur.com/t1DjKDc.png" alt="" title=""></p>
<p>把 "group" 换成 "groupCollapsed",则默认为折叠运行结果。</p>
<p>由于本文只列出部分方法,查看完整方法请移步阮一峰前辈的<a href="https://link.segmentfault.com/?enc=spMUF4F0dkit89lKj8beGQ%3D%3D.m0wNWCJ2zO%2FWt83VMo8SfdsZT73z6YbSnISOhcpS8zL6FwcueXyJkNzrUwFhYzdzJ4INA8Q%2BZOu8B61e2bW4ig%3D%3D" rel="nofollow">博客</a>。</p>
JavaScript 中的 for 循环
https://segmentfault.com/a/1190000003829539
2015-10-08T15:51:42+08:00
2015-10-08T15:51:42+08:00
CompileYouth
https://segmentfault.com/u/compileyouth
6
<p>在<a href="https://link.segmentfault.com/?enc=irC4GfEayTtGQPl3EFMD5A%3D%3D.fYGt8Cv787AjZC%2BHH7OhXrxYtPKIBc6VTfIKgz0ZnOReKtE1WBPioR%2BsIXgHAVvB" rel="nofollow">ECMAScript5(简称 ES5)</a>中,有三种 for 循环,分别是:</p>
<ul>
<li><p>简单<code>for</code>循环</p></li>
<li><p><code>for-in</code></p></li>
<li><p><code>forEach</code></p></li>
</ul>
<p>在2015年6月份发布的<a href="https://link.segmentfault.com/?enc=LFK31JECG%2FSEM8lJweFzhQ%3D%3D.dtmNHVVvDoqfexlX5j%2BYBxshOCHEf4klZX0w13kRRqE2vA3SPOU75mhsrlP3IrlCs4dwr8l9OjxZXJau5dplNA%3D%3D" rel="nofollow">ECMAScript6(简称 ES6)</a>中,新增了一种循环,是:</p>
<ul><li><p><code>for-of</code></p></li></ul>
<p>下面我们就来看看这 4 种for循环。</p>
<h2>简单 for 循环</h2>
<p>下面先来看看大家最常见的一种写法:</p>
<pre><code>var arr = [1, 2, 3];
for(var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
</code></pre>
<p>当数组长度在循环过程中不会改变时,我们应将数组长度用变量存储起来,这样会获得更好的效率,下面是改进的写法:</p>
<pre><code>var arr = [1, 2, 3];
for(var i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]);
}
</code></pre>
<h2>for-in</h2>
<p>通常情况下,我们可以用 <code>for-in</code> 来遍历一遍数组的内容,代码如下:</p>
<pre><code>
var arr = [1, 2, 3];
var index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
</code></pre>
<p>一般情况下,运行结果如下:</p>
<pre><code>arr[0] = 1
arr[1] = 2
arr[2] = 3
</code></pre>
<p>但这么做往往会出现问题。</p>
<h3>
<code>for-in</code> 的真相</h3>
<p><strong><code>for-in</code> 循环遍历的是对象的属性,而不是数组的索引。</strong>因此, <code>for-in</code> 遍历的对象便不局限于数组,还可以遍历对象。例子如下:</p>
<pre><code>var person = {
fname: "san",
lname: "zhang",
age: 99
};
var info;
for(info in person) {
console.log("person[" + info + "] = " + person[info]);
}
</code></pre>
<p>结果如下:</p>
<pre><code>
person[fname] = san
person[lname] = zhang
person[age] = 99
</code></pre>
<p>需要注意的是, <code>for-in</code> 遍历属性的顺序并不确定,即输出的结果顺序与属性在对象中的顺序无关,也与属性的字母顺序无关,与其他任何顺序也无关。</p>
<h3>Array 的真相</h3>
<p><strong>Array在 Javascript 中是一个对象, Array 的索引是属性名。</strong>事实上, Javascript 中的 “array” 有些误导性, Javascript 中的 Array 并不像大部分其他语言的数组。首先, Javascript 中的 Array 在内存上并不连续,其次, Array 的索引并不是指偏移量。实际上, Array 的索引也不是 Number 类型,而是 String 类型的。我们可以正确使用如 <code>arr[0]</code> 的写法的原因是语言可以自动将 Number 类型的 0 转换成 String 类型的 "0" 。所以,在 Javascript 中从来就没有 Array 的索引,而只有类似 "0" 、 "1" 等等的属性。有趣的是,每个 Array 对象都有一个 length 的属性,导致其表现地更像其他语言的数组。但为什么在遍历 Array 对象的时候没有输出 length 这一条属性呢?那是因为 <code>for-in</code> 只能遍历“可枚举的属性”, length 属于不可枚举属性,实际上, Array 对象还有许多其他不可枚举的属性。</p>
<p>现在,我们再回过头来看看用 <code>for-in</code> 来循环数组的例子,我们修改一下前面遍历数组的例子:</p>
<pre><code>var arr = [1, 2, 3];
arr.name = "Hello world";
var index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
</code></pre>
<p>运行结果是:</p>
<pre><code>arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world
</code></pre>
<p>我们看到 <code>for-in</code> 循环访问了我们新增的 "name" 属性,因为 <code>for-in</code> 遍历了对象的所有属性,而不仅仅是“索引”。同时需要注意的是,此处输出的索引值,即 "0"、 "1"、 "2"不是 Number 类型的,而是 String 类型的,因为其就是作为属性输出,而不是索引。那是不是说不在我们的 Array 对象中添加新的属性,我们就可以只输出数组中的内容了呢?答案是否定的。因为 <code>for-in</code> 不仅仅遍历 array 自身的属性,其还遍历 array 原型链上的所有可枚举的属性。下面我们看个例子:</p>
<pre><code>Array.prototype.fatherName = "Father";
var arr = [1, 2, 3];
arr.name = "Hello world";
var index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
</code></pre>
<p>运行结果是:</p>
<pre><code>
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world
arr[fatherName] = Father
</code></pre>
<p>写到这里,我们可以发现 <code>for-in</code> 并不适合用来遍历 Array 中的元素,其更适合遍历对象中的属性,这也是其被创造出来的初衷。却有一种情况例外,就是<strong>稀疏数组</strong>。考虑下面的例子:</p>
<pre><code>var key;
var arr = [];
arr[0] = "a";
arr[100] = "b";
arr[10000] = "c";
for(key in arr) {
if(arr.hasOwnProperty(key) &&
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
console.log(arr[key]);
}
}
</code></pre>
<p><code>for-in</code> 只会遍历存在的实体,上面的例子中, <code>for-in</code> 遍历了3次(遍历属性分别为"0"、 "100"、 "10000"的元素,普通 for 循环则会遍历 10001 次)。所以,只要处理得当, <code>for-in</code> 在遍历 Array 中元素也能发挥巨大作用。</p>
<p>为了避免重复劳动,我们可以包装一下上面的代码:</p>
<pre><code>function arrayHasOwnIndex(array, prop) {
return array.hasOwnProperty(prop) &&
/^0$|^[1-9]\d*$/.test(prop) &&
prop <= 4294967294; // 2^32 - 2
}
</code></pre>
<p>使用示例如下:</p>
<pre><code>for (key in arr) {
if (arrayHasOwnIndex(arr, key)) {
console.log(arr[key]);
}
}
</code></pre>
<h3>for-in 性能</h3>
<p>正如上面所说,每次迭代操作会同时搜索实例或者原型属性, for-in 循环的每次迭代都会产生更多开销,因此要比其他循环类型慢,一般速度为其他类型循环的 1/7。因此,除非明确需要迭代一个属性数量未知的对象,否则应避免使用 for-in 循环。如果需要遍历一个数量有限的已知属性列表,使用其他循环会更快,比如下面的例子:</p>
<pre><code>var obj = {
"prop1": "value1",
"prop2": "value2"
};
var props = ["prop1", "prop2"];
for(var i = 0; i < props.length; i++) {
console.log(obj[props[i]]);
}
</code></pre>
<p>上面代码中,将对象的属性都存入一个数组中,相对于 for-in 查找每一个属性,该代码只关注给定的属性,节省了循环的开销和时间。</p>
<h2>forEach</h2>
<p>在 ES5 中,引入了新的循环,即 <code>forEach</code> 循环。</p>
<pre><code>var arr = [1, 2, 3];
arr.forEach(function(data) {
console.log(data);
});
</code></pre>
<p>运行结果:</p>
<pre><code>
1
2
3
</code></pre>
<p>forEach 方法为数组中含有有效值的每一项执行一次 callback 函数,那些已删除(使用 delete 方法等情况)或者从未赋值的项将被跳过(不包括那些值为 undefined 或 null 的项)。 callback 函数会被依次传入三个参数:</p>
<ul>
<li><p>数组当前项的值;</p></li>
<li><p>数组当前项的索引;</p></li>
<li><p>数组对象本身;</p></li>
</ul>
<p>需要注意的是,forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。</p>
<pre><code>var arr = [];
arr[0] = "a";
arr[3] = "b";
arr[10] = "c";
arr.name = "Hello world";
arr.forEach(function(data, index, array) {
console.log(data, index, array);
});
</code></pre>
<p>运行结果:</p>
<pre><code>a 0 ["a", 3: "b", 10: "c", name: "Hello world"]
b 3 ["a", 3: "b", 10: "c", name: "Hello world"]
c 10 ["a", 3: "b", 10: "c", name: "Hello world"]
</code></pre>
<p>这里的 index 是 Number 类型,并且也不会像 <code>for-in</code> 一样遍历原型链上的属性。</p>
<p>所以,使用 forEach 时,我们不需要专门地声明 index 和遍历的元素,因为这些都作为回调函数的参数。</p>
<p>另外,forEach 将会遍历数组中的所有元素,但是 ES5 定义了一些其他有用的方法,下面是一部分:</p>
<ul>
<li><p><code>every</code>: 循环在第一次 <code>return fasle</code> 后返回</p></li>
<li><p><code>some</code>: 循环在第一次 <code>return true</code> 后返回</p></li>
<li><p><code>filter</code>: 返回一个新的数组,该数组内的元素满足回调函数</p></li>
<li><p><code>map</code>: 将原数组中的元素处理后再返回</p></li>
<li><p><code>reduce</code>: 对数组中的元素依次处理,将上次处理结果作为下次处理的输入,最后得到最终结果。</p></li>
</ul>
<p>以上函数的具体信息请参考我的另一篇<a href="http://segmentfault.com/a/1190000003832912#articleHeader11">博客</a>。</p>
<h3>forEach 性能探究</h3>
<p>下面我们来比较 forEach 和普通 for 循环的执行效率。<br>我们看下面一段代码:</p>
<pre><code>var arr = [];
for(var i = 0; i < 100000; i++) {
arr.push({"key": i});
}
function func() {
console.time("for");
for(var i = 0, len = arr.length; i < len; i++) {
arr[i].key;
}
console.timeEnd("for");
console.time("forEach");
arr.forEach(function(single) {
single.key;
});
console.timeEnd("forEach");
}
func();
</code></pre>
<p>运行结果是:</p>
<pre><code>//chrome 43.0
for: 1.000ms
forEach: 5.545ms
//firefox 40.0.3
for: 计时器开始
for: 27.89ms
forEach: 计时器开始
forEach: 1.82ms
//Edge
for: 17.086ms
forEach: 6.41ms
//IE 10
for: 16.525ms
forEach: 6.282ms
//IE 9
for: 21ms
forEach: 8ms
//IE 8 与更低版本不支持forEach
</code></pre>
<p>多次执行的结果与上面类似,所以我们可以看出,forEach 在大多数浏览器中效率比较稳定且比普通 for 循环的效率要高。但我们看出了一个例外,即在 chrome 中,for 循环不仅比其他浏览器中的 for 循环更加高效,甚至比 forEach 还要高效。因为没有找到合理的解释,我暂时只能猜测,待找到解释后更新。我的猜测是 for 循环是种非常常见的循环,所以 chrome 的引擎对 for 循环做了特殊的优化。</p>
<h2>for-of</h2>
<p>先来看个例子:</p>
<pre><code>var arr = ['a', 'b', 'c'];
for(var data of arr) {
console.log(data);
}
</code></pre>
<p>运行结果是:</p>
<pre><code>a
b
c
</code></pre>
<h3>为什么要引进 for-of?</h3>
<p>要回答这个问题,我们先来看看ES6之前的 3 种 for 循环有什么缺陷:</p>
<ul>
<li><p>forEach 不能 break 和 return;</p></li>
<li><p>for-in 缺点更加明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。而且,遍历数组元素的顺序可能是随机的。</p></li>
</ul>
<p>所以,鉴于以上种种缺陷,我们需要改进原先的 for 循环。但 ES6 不会破坏你已经写好的 JS 代码。目前,成千上万的 Web 网站依赖 for-in 循环,其中一些网站甚至将其用于数组遍历。如果想通过修正 for-in 循环增加数组遍历支持会让这一切变得更加混乱,因此,标准委员会在 ES6 中增加了一种新的循环语法来解决目前的问题,即 for-of 。</p>
<p>那 <code>for-of</code> 到底可以干什么呢?</p>
<ul>
<li><p>跟 forEach 相比,可以正确响应 break, continue, return。</p></li>
<li><p><code>for-of</code> 循环不仅支持数组,还支持大多数类数组对象,例如 DOM nodelist 对象。</p></li>
<li><p><code>for-of</code> 循环也支持字符串遍历,它将字符串视为一系列 Unicode 字符来进行遍历。</p></li>
<li><p><code>for-of</code> 也支持 Map 和 Set (两者均为 ES6 中新增的类型)对象遍历。</p></li>
</ul>
<p>总结一下,<code>for-of</code> 循环有以下几个特征:</p>
<ul>
<li><p>这是最简洁、最直接的遍历数组元素的语法。</p></li>
<li><p>这个方法避开了 <code>for-in</code> 循环的所有缺陷。</p></li>
<li><p>与 forEach 不同的是,它可以正确响应 break、continue 和 return 语句。</p></li>
<li><p>其不仅可以遍历数组,还可以遍历类数组对象和其他可迭代对象。</p></li>
</ul>
<p>但需要注意的是,for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用<br><code>for-in</code> 循环(这也是它的本职工作)。</p>