aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html')
-rw-r--r--files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html959
1 files changed, 959 insertions, 0 deletions
diff --git a/files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html b/files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html
new file mode 100644
index 0000000000..0f423505da
--- /dev/null
+++ b/files/zh-cn/web/javascript/a_re-introduction_to_javascript/index.html
@@ -0,0 +1,959 @@
+---
+title: 重新介绍 JavaScript(JS 教程)
+slug: Web/JavaScript/A_re-introduction_to_JavaScript
+tags:
+ - JavaScript
+ - 指南
+ - 教程
+ - 进阶
+ - 高级
+translation_of: Web/JavaScript/A_re-introduction_to_JavaScript
+---
+<div>{{jsSidebar}}</div>
+
+<p>为什么会有这一篇“重新介绍”呢?因为 {{Glossary("JavaScript")}} 堪称<a href="http://javascript.crockford.com/javascript.html">世界上被人误解最深的编程语言</a>。虽然常被嘲为“玩具语言”,但在它看似简洁的外衣下,还隐藏着强大的语言特性。 JavaScript 目前广泛应用于众多知名应用中,对于网页和移动开发者来说,深入理解 JavaScript 就尤为必要。</p>
+
+<p>我们有必要先从这门语言的历史谈起。在1995 年 Netscape 一位名为 Brendan Eich 的工程师创造了 JavaScript,随后在 1996 年初,JavaScript 首先被应用于 Netscape 2 浏览器上。最初的 JavaScript 名为 LiveScript,但是因为一个糟糕的营销策略而被重新命名,该策略企图利用Sun Microsystem的Java语言的流行性,将它的名字从最初的 LiveScript 更改为 JavaScript——尽管两者之间并没有什么共同点。这便是之后混淆产生的根源。</p>
+
+<p>几个月后,Microsoft 随 IE 3 发布推出了一个与之基本兼容的语言 JScript。又过了几个月,Netscape 将 JavaScript 提交至 <a href="http://www.ecma-international.org">Ecma International</a>(一个欧洲标准化组织), {{Glossary("ECMAScript")}} 标准第一版便在 1997 年诞生了,随后在 1999 年以 <a href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript 第三版</a>的形式进行了更新,从那之后这个标准没有发生过大的改动。由于委员会在语言特性的讨论上发生分歧,ECMAScript 第四版尚未推出便被废除,但随后于 2009 年 12 月发布的 ECMAScript 第五版引入了第四版草案加入的许多特性。第六版标准已经于 2015 年 6 月发布。</p>
+
+<div class="note">
+<p><strong>注意:</strong> 由于这种用法更常见,从这里开始,我们将使用 JavaScript 来指代 ECMAScript 。</p>
+</div>
+
+<p>与大多数编程语言不同,JavaScript 没有输入或输出的概念。它是一个在宿主环境(host environment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境提供的。浏览器是最常见的宿主环境,但在非常多的其他程序中也包含 JavaScript 解释器,如 Adobe Acrobat、Adobe Photoshop、SVG 图像、Yahoo! 的 Widget 引擎,<a href="http://nodejs.org">Node.js</a> 之类的服务器端环境,NoSQL 数据库(如开源的 <a href="http://couchdb.apache.org">Apache CouchDB</a>)、嵌入式计算机,以及包括 <a href="http://www.gnome.org">GNOME</a> (注:GNU/Linux 上最流行的 GUI 之一)在内的桌面环境等等。</p>
+
+<h2 id="概览">概览</h2>
+
+<p>JavaScript 是一种多范式的动态语言,它包含类型、运算符、标准内置( built-in)对象和方法。它的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。JavaScript 通过原型链而不是类来支持面向对象编程(有关 ES6 类的内容参考这里{{jsxref("Classes")}},有关对象原型参考见此<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">继承与原型链</a>)。JavaScript同样支持函数式编程——因为它们也是对象,函数也可以被保存在变量中,并且像其他对象一样被传递。</p>
+
+<p>先从任何编程语言都不可缺少的组成部分——“类型”开始。JavaScript 程序可以修改值(value),这些值都有各自的类型。JavaScript 中的类型包括:</p>
+
+<ul>
+ <li>{{jsxref("Number")}}(数字)</li>
+ <li>{{jsxref("String")}}(字符串)</li>
+ <li>{{jsxref("Boolean")}}(布尔)</li>
+ <li>{{jsxref("Function")}}(函数)</li>
+ <li>{{jsxref("Object")}}(对象)</li>
+ <li>{{jsxref("Symbol")}}(ES2015 新增)</li>
+</ul>
+
+<p>…哦,还有看上去有些…奇怪的 {{jsxref("undefined")}}(未定义)类型和 {{jsxref("null")}}(空)类型。此外还有{{jsxref("Array")}}(数组)类型,以及分别用于表示日期和正则表达式的 {{jsxref("Date")}}(日期)和 {{jsxref("RegExp")}}(正则表达式),这三种类型都是特殊的对象。严格意义上说,Function(函数)也是一种特殊的对象。所以准确来说,JavaScript 中的类型应该包括这些:</p>
+
+<ul>
+ <li>{{jsxref("Number")}}(数字)</li>
+ <li>{{jsxref("String")}}(字符串)</li>
+ <li>{{jsxref("Boolean")}}(布尔)</li>
+ <li>{{jsxref("Symbol")}}(符号)(ES2015 新增)</li>
+ <li>{{jsxref("Object")}}(对象)
+ <ul>
+ <li>{{jsxref("Function")}}(函数)</li>
+ <li>{{jsxref("Array")}}(数组)</li>
+ <li>{{jsxref("Date")}}(日期)</li>
+ <li>{{jsxref("RegExp")}}(正则表达式)</li>
+ </ul>
+ </li>
+ <li>{{jsxref("null")}}(空)</li>
+ <li>{{jsxref("undefined")}}(未定义)</li>
+</ul>
+
+<p>JavaScript 还有一种内置的 {{jsxref("Error")}}(错误)类型。但是,如果我们继续使用上面的分类,事情便容易得多;所以,现在,我们先讨论上面这些类型。</p>
+
+<h2 id="数字">数字</h2>
+
+<p>根据语言规范,JavaScript 采用“遵循 IEEE 754 标准的双精度 64 位格式”("double-precision 64-bit format IEEE 754 values")表示数字。——在JavaScript(除了{{jsxref("BigInt")}})当中,<strong>并不存在整数/整型(Integer)。</strong>因此在处理如下的场景时候,您一定要小心:</p>
+
+<pre class="notranslate">console.log(3 / 2); // 1.5,<em>not</em> 1
+console.log(Math.floor(3 / 2)); // 1
+</pre>
+
+<p>一个看上去是整数的东西,其实都是浮点数。</p>
+
+<p>当然,您也需要小心这种情况:</p>
+
+<pre class="brush: js notranslate">0.1 + 0.2 = 0.30000000000000004
+</pre>
+
+<p>在具体实现时,整数值通常被视为32位整型变量,在个别实现(如某些浏览器)中也以32位整型变量的形式进行存储,直到它被用于执行某些32位整型不支持的操作,这是为了便于进行位操作。</p>
+
+<p>JavaScript 支持标准的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators">算术运算符</a>,包括加法、减法、取模(或取余)等等。还有一个之前没有提及的内置对象 {{jsxref("Math")}}(数学对象),用以处理更多的高级数学函数和常数:</p>
+
+<pre class="brush: js notranslate">Math.sin(3.5);
+var circumference = 2 * Math.PI * r;
+</pre>
+
+<p>你可以使用内置函数 {{jsxref("Global_Objects/parseInt", "parseInt()")}} 将字符串转换为整型。该函数的第二个可选参数表示字符串所表示数字的基(进制):</p>
+
+<pre class="brush: js notranslate">parseInt("123", 10); // 123
+parseInt("010", 10); // 10
+</pre>
+
+<p>一些老版本的浏览器会将首字符为“0”的字符串当做八进制数字,2013 年以前的 JavaScript 实现会返回一个意外的结果:</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">parseInt("010"); // 8
+parseInt("0x10"); // 16</code></pre>
+
+<p>这是因为字符串以数字 0 开头,{{jsxref("Global_Objects/parseInt", "parseInt()")}}函数会把这样的字符串视作八进制数字;同理,0x开头的字符串则视为十六进制数字。</p>
+
+<p>如果想把一个二进制数字字符串转换成整数值,只要把第二个参数设置为 2 就可以了:</p>
+
+<pre class="brush: js notranslate">parseInt("11", 2); // 3
+</pre>
+
+<p>JavaScript 还有一个类似的内置函数 {{jsxref("Global_Objects/parseFloat", "parseFloat()")}},用以解析浮点数字符串,与{{jsxref("Global_Objects/parseInt", "parseInt()")}}不同的地方是,<code>parseFloat()</code> 只应用于解析十进制数字。</p>
+
+<p>一元运算符 + 也可以把数字字符串转换成数值:</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">+ "42"; // 42
++ "010"; // 10
++ "0x10"; // 16</code></pre>
+
+<p>如果给定的字符串不存在数值形式,函数会返回一个特殊的值 {{jsxref("NaN")}}(Not a Number 的缩写):</p>
+
+<pre class="brush: js notranslate">parseInt("hello", 10); // NaN
+</pre>
+
+<p>要小心NaN:如果把 <code>NaN</code> 作为参数进行任何数学运算,结果也会是 <code>NaN</code>:</p>
+
+<pre class="brush: js notranslate">NaN + 5; //NaN
+</pre>
+
+<p>可以使用内置函数 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/isNaN"><code>isNaN()</code></a> 来判断一个变量是否为 <code>NaN</code>:</p>
+
+<pre class="brush: js notranslate">isNaN(NaN); // true
+</pre>
+
+<p>JavaScript 还有两个特殊值:<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Infinity"><code>Infinity</code></a>(正无穷)和 <code>-Infinity</code>(负无穷):</p>
+
+<pre class="brush: js notranslate">1 / 0; // Infinity
+-1 / 0; // -Infinity
+</pre>
+
+<p>可以使用内置函数 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/isFinite"><code>isFinite()</code></a> 来判断一个变量是否是一个有穷数, 如果类型为<code>Infinity</code>, <code>-Infinity</code> 或 <code>NaN则返回false</code>:</p>
+
+<pre class="brush: js notranslate">isFinite(1/0); // false
+isFinite(Infinity); // false
+isFinite(-Infinity); // false
+isFinite(NaN); // false
+
+isFinite(0); // true
+isFinite(2e64); // true
+
+isFinite("0"); // true
+// 如果是纯数值类型的检测,则返回 false:
+Number.isFinite("0"); // false</pre>
+
+<div class="note"><strong>备注:</strong> {{jsxref("Global_Objects/parseInt", "parseInt()")}} 和 {{jsxref("Global_Objects/parseFloat", "parseFloat()")}} 函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。但是运算符 "+"对字符串的转换方式与之不同, 只要字符串含有无法被解析成数字的字符,该字符串就将被转换成 <code>NaN</code>。可分别使用这两种方法解析“10.2abc”这一字符串,并比较得到的结果,来理解这两种方法的区别。</div>
+
+<h2 id="字符串">字符串</h2>
+
+<p>JavaScript 中的字符串是一串<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Values,_variables,_and_literals#Unicode.E7.BC.96.E7.A0.81">Unicode 字符</a>序列。这对于那些需要和多语种网页打交道的开发者来说是个好消息。更准确地说,它们是一串UTF-16编码单元的序列,每一个编码单元由一个 16 位二进制数表示。每一个Unicode字符由一个或两个编码单元来表示。</p>
+
+<p>如果想表示一个单独的字符,只需使用长度为 1 的字符串。</p>
+
+<p>通过访问字符串的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/length"><code>length</code></a>(编码单元的个数)属性,可以得到它的长度。</p>
+
+<pre class="brush: js notranslate">"hello".length; // 5
+</pre>
+
+<p>这是我们第一次碰到 JavaScript 对象。我们有没有提过你可以像  {{jsxref("Object", "object", "", 1)}}  一样使用字符串?是的,字符串也有 {{jsxref("String", "methods", "#Methods", 1)}}(方法)能让你操作字符串和获取字符串的信息。</p>
+
+<pre class="brush: js notranslate">"hello".charAt(0); // "h"
+"hello, world".replace("world", "mars"); // "hello, mars"
+"hello".toUpperCase(); // "HELLO"
+</pre>
+
+<h2 id="其他类型">其他类型</h2>
+
+<p>与其他类型不同,JavaScript 中的 {{jsxref("null")}} 表示一个空值(non-value),必须使用 null 关键字才能访问,{{jsxref("undefined")}} 是一个“undefined(未定义)”类型的对象,表示一个未初始化的值,也就是还没有被分配的值。我们之后再具体讨论变量,但有一点可以先简单说明一下,JavaScript 允许声明变量但不对其赋值,一个未被赋值的变量就是 <code>undefined</code> 类型。还有一点需要说明的是,<code>undefined</code> 实际上是一个不允许修改的常量。</p>
+
+<p>JavaScript 包含布尔类型,这个类型的变量有两个可能的值,分别是 <code>true</code> 和 <code>false</code>(两者都是关键字)。根据具体需要,JavaScript 按照如下规则将变量转换成布尔类型:</p>
+
+<ol>
+ <li><code>false</code>、<code>0</code>、空字符串(<code>""</code>)、<code>NaN</code>、<code>null</code> 和 <code>undefined</code> 被转换为 <code>false</code></li>
+ <li>所有其他值被转换为 <code>true</code></li>
+</ol>
+
+<p>也可以使用 <code>Boolean()</code> 函数进行显式转换:</p>
+
+<pre class="brush: js notranslate">Boolean(''); // false
+Boolean(234); // true
+</pre>
+
+<p>不过一般没必要这么做,因为 JavaScript 会在需要一个布尔变量时隐式完成这个转换操作(比如在 <code>if</code> 条件语句中)。所以,有时我们可以把转换成布尔值后的变量分别称为 真值(true values)——即值为 true  和 假值(false values)——即值为 false;也可以分别称为“真的”(truthy)和“假的”(falsy)。</p>
+
+<p>JavaScript 支持包括 <code>&amp;&amp;</code>(逻辑与)、<code>||</code> (逻辑或)和<code>!</code>(逻辑非)在内的一些逻辑运算符。下面会有所提到。</p>
+
+<h2 id="变量">变量</h2>
+
+<p>在 JavaScript 中声明一个新变量的方法是使用关键字 <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let">let</a></code> 、<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const">const</a></code> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/var"><code>var</code></a>:</p>
+
+<p><code><strong>let</strong></code> 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">let a;
+let name = 'Simon';</code></pre>
+
+<p>下面是使用  <code><strong>let</strong></code> 声明变量作用域的例子:</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">// myLetVariable 在这里 *不能* 被引用
+
+for (let myLetVariable = 0; myLetVariable &lt; 5; myLetVariable++) {
+ // myLetVariable 只能在这里引用
+}
+
+// myLetVariable 在这里 *不能* 被引用</code></pre>
+
+<p><code><strong>const</strong></code> 允许声明一个不可变的常量。这个常量在定义域内总是可见的。</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">const Pi = 3.14; // 设置 Pi 的值
+Pi = 1; // 将会抛出一个错误因为你改变了一个常量的值。</code></pre>
+
+<p><code><strong>var</strong></code> 是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。这是因为它是传统上在 JavaScript 声明变量的唯一方法。使用 <strong><code>var</code></strong> 声明的变量在它所声明的整个函数都是可见的。</p>
+
+<pre class="brush: js notranslate">var a;
+var name = "simon";
+</pre>
+
+<p>一个使用  <strong><code>var</code> </strong>声明变量的语句块的例子:</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">// myVarVariable在这里 *能* 被引用
+
+for (var myVarVariable = 0; myVarVariable &lt; 5; myVarVariable++) {
+ // myVarVariable 整个</code>函数<code class="language-js">中都能被引用
+}
+
+// myVarVariable 在这里 *能* 被引用</code></pre>
+
+<p>如果声明了一个变量却没有对其赋值,那么这个变量的类型就是 <code>undefined</code>。</p>
+
+<p>JavaScript 与其他语言的(如 Java)的重要区别是在 JavaScript 中语句块(blocks)是没有作用域的,只有函数有作用域。因此如果在一个复合语句中(如 if 控制结构中)使用 var 声明一个变量,那么它的作用域是整个函数(复合语句在函数中)。 但是从 ECMAScript Edition 6 开始将有所不同的, <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let">let</a></code> 和 <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const">const</a></code> 关键字允许你创建块作用域的变量。</p>
+
+<h2 id="运算符">运算符</h2>
+
+<p>JavaScript的算术操作符包括 <code>+</code>、<code>-</code>、<code>*</code>、<code>/</code> 和 <code>%</code> ——求余(<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#求余_%28%29">与模运算相同</a>)。赋值使用 <code>=</code> 运算符,此外还有一些复合运算符,如 <code>+=</code> 和 <code>-=</code>,它们等价于 <code>x = x <em>operator</em> y</code>。</p>
+
+<pre class="brush: js notranslate">x += 5; // 等价于 x = x + 5;
+</pre>
+
+<p>可以使用 <code>++</code> 和 <code>--</code> 分别实现变量的自增和自减。两者都可以作为前缀或后缀操作符使用。</p>
+
+<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#.E5.8A.A0.E6.B3.95_(.2B)"><code>+</code> 操作符</a>还可以用来连接字符串:</p>
+
+<pre class="brush: js notranslate">"hello" + " world"; // hello world
+</pre>
+
+<p>如果你用一个字符串加上一个数字(或其他值),那么操作数都会被首先转换为字符串。如下所示:</p>
+
+<pre class="brush: js notranslate">"3" + 4 + 5; // 345
+3 + 4 + "5"; // 75
+</pre>
+
+<p>这里不难看出一个实用的技巧——通过与空字符串相加,可以将某个变量快速转换成字符串类型。</p>
+
+<p>JavaScript 中的<a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Comparison_Operators">比较操作</a>使用 <code>&lt;</code>、<code>&gt;</code>、<code>&lt;=</code> 和 <code>&gt;=</code>,这些运算符对于数字和字符串都通用。相等的比较稍微复杂一些。由两个“<code>=</code>(等号)”组成的相等运算符有类型自适应的功能,具体例子如下:</p>
+
+<pre class="brush: js notranslate">123 == "123" // true
+1 == true; // true
+</pre>
+
+<p>如果在比较前不需要自动类型转换,应该使用由三个“<code>=</code>(等号)”组成的相等运算符:</p>
+
+<pre class="brush: js notranslate">1 === true; //false
+123 === "123"; // false
+</pre>
+
+<p>JavaScript 还支持 <code>!=</code> 和 <code>!==</code> 两种不等运算符,具体区别与两种相等运算符的区别类似。</p>
+
+<p>JavaScript 还提供了 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators">位操作符</a>。</p>
+
+<h2 id="控制结构">控制结构</h2>
+
+<p>JavaScript 的控制结构与其他类 C 语言类似。可以使用 <code>if</code> 和 <code>else</code> 来定义条件语句,还可以连起来使用:</p>
+
+<pre class="brush: js notranslate">var name = "kittens";
+if (name == "puppies") {
+ name += "!";
+} else if (name == "kittens") {
+ name += "!!";
+} else {
+ name = "!" + name;
+}
+name == "kittens!!"; // true
+</pre>
+
+<p>JavaScript 支持 <code>while</code> 循环和 <code>do-while</code> 循环。前者适合常见的基本循环操作,如果需要循环体至少被执行一次则可以使用 <code>do-while</code>:</p>
+
+<pre class="brush: js notranslate">while (true) {
+ // 一个无限循环!
+}
+
+var input;
+do {
+ input = get_input();
+} while (inputIsNotValid(input))
+</pre>
+
+<p>JavaScript 的 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Statements/for">for</a></code> 循环与 C 和 Java 中的相同:使用时可以在一行代码中提供控制信息。</p>
+
+<pre class="brush: js notranslate">for (var i = 0; i &lt; 5; i++) {
+ // 将会执行五次
+}
+</pre>
+
+<p>JavaScript 也还包括其他两种重要的 for 循环: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of"><code>for</code>...<code>of</code></a></p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">for (let value of array) {
+ // do something with value
+}</code></pre>
+
+<p>和 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in"><code>for</code>...<code>in</code></a> :</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">for (let property in object) {
+ // do something with object property
+}</code></pre>
+
+<p><code>&amp;&amp;</code> 和 <code>||</code> 运算符使用短路逻辑(short-circuit logic),是否会执行第二个语句(操作数)取决于第一个操作数的结果。在需要访问某个对象的属性时,使用这个特性可以事先检测该对象是否为空:</p>
+
+<pre class="brush: js notranslate">var name = o &amp;&amp; o.getName();
+</pre>
+
+<p>或用于缓存值(当错误值无效时):</p>
+
+<pre class="brush: js notranslate">var name = cachedName || (cachedName = getName());
+</pre>
+
+<p>类似地,JavaScript 也有一个用于条件表达式的三元操作符:</p>
+
+<pre class="brush: js notranslate">var allowed = (age &gt; 18) ? "yes" : "no";
+</pre>
+
+<p>在需要多重分支时可以使用  <code>基于一个数字或字符串的switch</code> 语句:</p>
+
+<pre class="brush: js notranslate">switch(action) {
+ case 'draw':
+ drawIt();
+ break;
+ case 'eat':
+ eatIt();
+ break;
+ default:
+ doNothing();
+}
+</pre>
+
+<p>如果你不使用 <code>break</code> 语句,JavaScript 解释器将会执行之后 <code>case</code> 中的代码。除非是为了调试,一般你并不需要这个特性,所以大多数时候不要忘了加上 <code>break。</code></p>
+
+<pre class="brush: js notranslate">switch(a) {
+ case 1: // 继续向下
+ case 2:
+ eatIt();
+ break;
+ default:
+ doNothing();
+}
+</pre>
+
+<p><code>default</code> 语句是可选的。<code>switch</code> 和 <code>case</code> 都可以使用需要运算才能得到结果的表达式;在 <code>switch</code> 的表达式和 <code>case</code> 的表达式是使用 <code>===</code> 严格相等运算符进行比较的:</p>
+
+<pre class="brush: js notranslate">switch(1 + 3){
+ case 2 + 2:
+ yay();
+ break;
+ default:
+ neverhappens();
+}
+</pre>
+
+<h2 id="对象">对象</h2>
+
+<p>JavaScript 中的对象,Object,可以简单理解成“名称-值”对(而不是键值对:现在,ES 2015 的映射表(Map),比对象更接近键值对),不难联想 JavaScript 中的对象与下面这些概念类似:</p>
+
+<ul>
+ <li>Python 中的字典(Dictionary)</li>
+ <li>Perl 和 Ruby 中的散列/哈希(Hash)</li>
+ <li>C/C++ 中的散列表(Hash table)</li>
+ <li>Java 中的散列映射表(HashMap)</li>
+ <li>PHP 中的关联数组(Associative array)</li>
+</ul>
+
+<p>这样的数据结构设计合理,能应付各类复杂需求,所以被各类编程语言广泛采用。正因为 JavaScript 中的一切(除了核心类型,core object)都是对象,所以 JavaScript 程序必然与大量的散列表查找操作有着千丝万缕的联系,而散列表擅长的正是高速查找。</p>
+
+<p>“名称”部分是一个 JavaScript 字符串,“值”部分可以是任何 JavaScript 的数据类型——包括对象。这使用户可以根据具体需求,创建出相当复杂的数据结构。</p>
+
+<p>有两种简单方法可以创建一个空对象:</p>
+
+<pre class="brush: js notranslate">var obj = new Object();
+</pre>
+
+<p>和:</p>
+
+<pre class="brush: js notranslate">var obj = {};
+</pre>
+
+<p>这两种方法在语义上是相同的。第二种更方便的方法叫作“对象字面量(object literal)”法。这种也是 JSON 格式的核心语法,一般我们优先选择第二种方法。</p>
+
+<p>“对象字面量”也可以用来在对象实例中定义一个对象:</p>
+
+<pre class="brush: js notranslate">var obj = {
+ name: "Carrot",
+ _for: "Max",//'for' 是保留字之一,使用'_for'代替
+ details: {
+ color: "orange",
+ size: 12
+ }
+}
+</pre>
+
+<p>对象的属性可以通过链式(chain)表示方法进行访问:</p>
+
+<pre class="brush: js notranslate">obj.details.color; // orange
+obj["details"]["size"]; // 12
+</pre>
+
+<p>下面的例子创建了一个对象原型,<code><strong>Person</strong></code>,和这个原型的实例,<strong><code>You</code></strong>。</p>
+
+<pre class="brush: js notranslate">function Person(name, age) {
+ this.name = name;
+ this.age = age;
+}
+
+// 定义一个对象
+var You = new Person('You', 24);
+// 我们创建了一个新的 Person,名称是 "You"
+// ("You" 是第一个参数, 24 是第二个参数..)
+</pre>
+
+<p>完成创建后,对象属性可以通过如下两种方式进行赋值和访问:</p>
+
+<pre class="brush: js notranslate">// 点表示法(dot notation)
+obj.name = 'Simon';
+var name = obj.name;
+</pre>
+
+<p>和:</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">// 括号表示法(bracket notation)
+obj['name'] = 'Simon';
+var name = obj['name'];
+// can use a variable to define a key
+var user = prompt('what is your key?')
+obj[user] = prompt('what is its value?')</code></pre>
+
+<p>这两种方法在语义上也是相同的。第二种方法的优点在于属性的名称被看作一个字符串,这就意味着它可以在运行时被计算,缺点在于这样的代码有可能无法在后期被解释器优化。它也可以被用来访问某些以<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords">预留关键字</a>作为名称的属性的值:</p>
+
+<pre class="brush: js notranslate">obj.for = 'Simon'; // 语法错误,因为 for 是一个预留关键字
+obj["for"] = 'Simon'; // 工作正常
+</pre>
+
+<div class="note">
+<p><strong>注意:</strong>从 ECMAScript 5 开始,预留关键字可以作为对象的属性名(reserved words may be used as object property names "in the buff")。 这意味着当定义对象字面量时不需要用双引号了。参见 ES5 <a href="http://es5.github.io/#x7.6.1">Spec</a>.</p>
+</div>
+
+<p>关于对象和原型的详情参见: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype">Object.prototype</a>. 解释对象原型和对象原型链可以参见:<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">继承与原型链</a>。</p>
+
+<div class="note">
+<p><strong>注意:</strong>从 ECMAScript 2015 开始,对象键可以在创建时使用括号表示法由变量定义。{[phoneType]: 12345} 可以用来替换 <code>var userPhone = {}; userPhone[phoneType] = 12345</code> .</p>
+</div>
+
+<h2 id="数组">数组</h2>
+
+<p>JavaScript 中的数组是一种特殊的对象。它的工作原理与普通对象类似(以数字为属性名,但只能通过<code>[]</code> 来访问),但数组还有一个特殊的属性——<code>length</code>(长度)属性。这个属性的值通常比数组最大索引大 1。</p>
+
+<p>创建数组的传统方法是:</p>
+
+<pre class="brush: js notranslate">var a = new Array();
+a[0] = "dog";
+a[1] = "cat";
+a[2] = "hen";
+a.length; // 3
+</pre>
+
+<p>使用数组字面量(array literal)法更加方便:</p>
+
+<pre class="brush: js notranslate">var a = ["dog", "cat", "hen"];
+a.length; // 3
+</pre>
+
+<p>注意,<code>Array.length </code>并不总是等于数组中元素的个数,如下所示:</p>
+
+<pre class="brush: js notranslate">var a = ["dog", "cat", "hen"];
+a[100] = "fox";
+a.length; // 101
+</pre>
+
+<p>记住:数组的长度是比数组最大索引值多一的数。</p>
+
+<p>如果试图访问一个不存在的数组索引,会得到 <code>undefined</code>:</p>
+
+<pre class="brush: js notranslate">typeof(a[90]); // undefined
+</pre>
+
+<p>可以通过如下方式遍历一个数组:</p>
+
+<pre class="brush: js notranslate">for (var i = 0; i &lt; a.length; i++) {
+ // Do something with a[i]
+}
+</pre>
+
+<p>ES2015 引入了更加简洁的 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of"><code>for</code>...<code>of</code></a> 循环,可以用它来遍历可迭代对象,例如数组:</p>
+
+<pre class="brush: js notranslate">for (const currentValue of a) {
+ // Do something with currentValue
+}
+</pre>
+
+<p>遍历数组的另一种方法是使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...in"><code>for...in</code></a> 循环, 然而这并不是遍历数组元素而是数组的索引。注意,如果哪个家伙直接向 <code>Array.prototype</code> 添加了新的属性,使用这样的循环这些属性也同样会被遍历。所以并不推荐使用这种方法遍历数组:</p>
+
+<pre class="brush: js notranslate">for (var i in a) {
+ // 操作 a[i]
+}
+</pre>
+
+<p>ECMAScript 5 增加了另一个遍历数组的方法,<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach">forEach()</a></code>:</p>
+
+<pre class="brush: js notranslate">["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
+ // 操作 currentValue 或者 array[index]
+});
+</pre>
+
+<p>如果想在数组后追加元素,只需要:</p>
+
+<pre class="brush: js notranslate">a.push(item);</pre>
+
+<p>除了 <code>forEach()</code> 和 <code>push()</code>,Array(数组)类还自带了许多方法。建议查看 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array">Array 方法的完整文档</a>。</p>
+
+<table>
+ <thead>
+ <tr>
+ <th scope="col">方法名称</th>
+ <th scope="col">描述</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>a.toString()</code></td>
+ <td>返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。</td>
+ </tr>
+ <tr>
+ <td><code>a.toLocaleString()</code></td>
+ <td>根据宿主环境的区域设置,返回一个包含数组中所有元素的字符串,每个元素通过逗号分隔。</td>
+ </tr>
+ <tr>
+ <td><code>a.concat(item1[, item2[, ...[, itemN]]])</code></td>
+ <td>返回一个数组,这个数组包含原先 <code>a</code> 和 <code>item1、item2、……、itemN</code> 中的所有元素。</td>
+ </tr>
+ <tr>
+ <td><code>a.join(sep)</code></td>
+ <td>返回一个包含数组中所有元素的字符串,每个元素通过指定的 <code>sep</code> 分隔。</td>
+ </tr>
+ <tr>
+ <td><code>a.pop()</code></td>
+ <td>删除并返回数组中的最后一个元素。</td>
+ </tr>
+ <tr>
+ <td><code>a.push(item1, ..., itemN)</code></td>
+ <td>将 <code>item1、item2、……、itemN</code> 追加至数组 <code>a</code>。</td>
+ </tr>
+ <tr>
+ <td><code>a.reverse()</code></td>
+ <td>数组逆序(会更改原数组 <code>a</code>)。</td>
+ </tr>
+ <tr>
+ <td><code>a.shift()</code></td>
+ <td>删除并返回数组中第一个元素。</td>
+ </tr>
+ <tr>
+ <td><code>a.slice(start, end)</code></td>
+ <td>返回子数组,以 <code>a[start]</code> 开头,以 <code>a[end]</code> 前一个元素结尾。</td>
+ </tr>
+ <tr>
+ <td><code>a.sort([cmpfn])</code></td>
+ <td>
+ <p>依据可选的比较函数 <code>cmpfn</code> 进行排序,如果未指定比较函数,则按字符顺序比较(即使被比较元素是数字)。</p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>a.splice(start, delcount[, item1[, ...[, itemN]]])</code></td>
+ <td>
+ <p>从 <code>start</code> 开始,删除 <code>delcount</code> 个元素,然后插入所有的 <code>item</code>。</p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>a.unshift(item1[, item2[, ...[, itemN]]])</code></td>
+ <td>
+ <p>将 <code>item</code> 插入数组头部,返回数组新长度(考虑 <code>undefined</code>)。</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="函数">函数</h2>
+
+<p>学习 JavaScript 最重要的就是要理解对象和函数两个部分。最简单的函数就像下面这个这么简单:</p>
+
+<pre class="brush: js notranslate">function add(x, y) {
+ var total = x + y;
+ return total;
+}
+</pre>
+
+<p>这个例子包括你需要了解的关于基本函数的所有部分。一个 JavaScript 函数可以包含 0 个或多个已命名的变量。函数体中的表达式数量也没有限制。你可以声明函数自己的局部变量。<code>return</code> 语句在返回一个值并结束函数。如果没有使用 <code>return</code> 语句,或者一个没有值的 <code>return</code> 语句,JavaScript 会返回 <code>undefined</code>。</p>
+
+<p>已命名的参数更像是一个指示而没有其他作用。如果调用函数时没有提供足够的参数,缺少的参数会被 <code>undefined</code> 替代。</p>
+
+<pre class="brush: js notranslate">add(); // NaN
+// 不能在 undefined 对象上进行加法操作
+</pre>
+
+<p>你还可以传入多于函数本身需要参数个数的参数:</p>
+
+<pre class="brush: js notranslate">add(2, 3, 4); // 5
+ // 将前两个值相加,4 被忽略了
+</pre>
+
+<p>这看上去有点蠢。函数实际上是访问了函数体中一个名为 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments"><code>arguments</code></a> 的内部对象,这个对象就如同一个类似于数组的对象一样,包括了所有被传入的参数。让我们重写一下上面的函数,使它可以接收任意个数的参数:</p>
+
+<pre class="brush: js notranslate">function add() {
+ var sum = 0;
+ for (var i = 0, j = arguments.length; i &lt; j; i++) {
+ sum += arguments[i];
+ }
+ return sum;
+}
+
+add(2, 3, 4, 5); // 14
+</pre>
+
+<p>这跟直接写成 <code>2 + 3 + 4 + 5</code> 也没什么区别。我们还是创建一个求平均数的函数吧:</p>
+
+<pre class="brush: js notranslate">function avg() {
+ var sum = 0;
+ for (var i = 0, j = arguments.length; i &lt; j; i++) {
+ sum += arguments[i];
+ }
+ return sum / arguments.length;
+}
+avg(2, 3, 4, 5); // 3.5</pre>
+
+<p>这个就有用多了,但是却有些冗长。为了使代码变短一些,我们可以使用<a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters">剩余参数</a>来替换arguments的使用。在这方法中,我们可以传递任意数量的参数到函数中同时尽量减少我们的代码。这个<strong>剩余参数操作符</strong>在函数中以:<strong>...variable</strong> 的形式被使用,它将包含在调用函数时使用的未捕获整个参数列表到这个变量中。我们同样也可以将 <strong>for</strong> 循环替换为 <strong>for...of </strong> 循环来返回我们变量的值。</p>
+
+<pre class="brush: js notranslate">function avg(...args) {
+ var sum = 0;
+ for (let value of args) {
+ sum += value;
+ }
+ return sum / args.length;
+}
+
+avg(2, 3, 4, 5); // 3.5
+</pre>
+
+<div class="blockIndicator note">
+<p>在上面这段代码中,所有被传入该函数的参数都被变量 <strong>args </strong>所持有。</p>
+
+<p>需要注意的是,无论“剩余参数操作符”被放置到函数声明的哪里,它都会把除了自己之前的所有参数存储起来。比如函数:function avg(<strong>firstValue</strong>, ...args) 会把传入函数的第一个值存入 <strong>firstValue</strong>,其他的参数存入 <strong>args</strong>。这是虽然一个很有用的语言特性,却也会带来新的问题。<code>avg()</code> 函数只接受逗号分开的参数列表 -- 但是如果你想要获取一个数组的平均值怎么办?一种方法是将函数按照如下方式重写:</p>
+</div>
+
+<pre class="brush: js notranslate">function avgArray(arr) {
+ var sum = 0;
+ for (var i = 0, j = arr.length; i &lt; j; i++) {
+ sum += arr[i];
+ }
+ return sum / arr.length;
+}
+avgArray([2, 3, 4, 5]); // 3.5
+</pre>
+
+<p>但如果能重用我们已经创建的那个函数不是更好吗?幸运的是 JavaScript 允许你通过任意函数对象的 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply">apply()</a></code> 方法来传递给它一个数组作为参数列表。</p>
+
+<pre class="brush: js notranslate">avg.apply(null, [2, 3, 4, 5]); // 3.5
+</pre>
+
+<p>传给 <code>apply()</code> 的第二个参数是一个数组,它将被当作 <code>avg()</code> 的参数列表使用,至于第一个参数 <code>null</code>,我们将在后面讨论。这也正说明了一个事实——函数也是对象。</p>
+
+<div class="blockIndicator note">
+<p>通过使用<a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax">展开语法</a>,你也可以获得同样的效果。</p>
+
+<p>比如说:<code>avg(...numbers)</code></p>
+</div>
+
+<p>JavaScript 允许你创建匿名函数:</p>
+
+<pre class="brush: js notranslate">var avg = function() {
+ var sum = 0;
+ for (var i = 0, j = arguments.length; i &lt; j; i++) {
+ sum += arguments[i];
+ }
+ return sum / arguments.length;
+};
+</pre>
+
+<p>这个函数在语义上与 <code>function avg()</code> 相同。你可以在代码中的任何地方定义这个函数,就像写普通的表达式一样。基于这个特性,有人发明出一些有趣的技巧。与 C 中的块级作用域类似,下面这个例子隐藏了局部变量:</p>
+
+<pre class="brush: js notranslate">var a = 1;
+var b = 2;
+(function() {
+ var b = 3;
+ a += b;
+})();
+
+a; // 4
+b; // 2
+</pre>
+
+<p>JavaScript 允许以递归方式调用函数。递归在处理树形结构(比如浏览器 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model">DOM</a>)时非常有用。</p>
+
+<pre class="brush: js notranslate">function countChars(elm) {
+ if (elm.nodeType == 3) { // 文本节点
+ return elm.nodeValue.length;
+ }
+ var count = 0;
+ for (var i = 0, child; child = elm.childNodes[i]; i++) {
+ count += countChars(child);
+ }
+ return count;
+}
+</pre>
+
+<p>这里需要说明一个潜在问题——既然匿名函数没有名字,那该怎么递归调用它呢?在这一点上,JavaScript 允许你命名这个函数表达式。你可以命名立即调用的函数表达式(IIFE——Immediately Invoked Function Expression),如下所示:</p>
+
+<pre class="brush: js notranslate">var charsInBody = (function counter(elm) {
+ if (elm.nodeType == 3) { // 文本节点
+ return elm.nodeValue.length;
+ }
+ var count = 0;
+ for (var i = 0, child; child = elm.childNodes[i]; i++) {
+ count += counter(child);
+ }
+ return count;
+})(document.body);
+</pre>
+
+<p>如上所提供的函数表达式的名称的作用域仅仅是该函数自身。这允许引擎去做更多的优化,并且这种实现更可读、友好。该名称也显示在调试器和一些堆栈跟踪中,节省了调试时的时间。</p>
+
+<p>需要注意的是 JavaScript 函数是它们本身的对象——就和 JavaScript 其他一切一样——你可以给它们添加属性或者更改它们的属性,这与前面的对象部分一样。</p>
+
+<h2 id="自定义对象">自定义对象</h2>
+
+<div class="note"><strong>备注:</strong>关于 JavaScript 中面向对象编程更详细的信息,请参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript">JavaScript 面向对象简介</a>。</div>
+
+<p>在经典的面向对象语言中,对象是指数据和在这些数据上进行的操作的集合。与 C++ 和 Java 不同,JavaScript 是一种基于原型的编程语言,并没有 class 语句,而是把函数用作类。那么让我们来定义一个人名对象,这个对象包括人的姓和名两个域(field)。名字的表示有两种方法:“名 姓(First Last)”或“姓, 名(Last, First)”。使用我们前面讨论过的函数和对象概念,可以像这样完成定义:</p>
+
+<pre class="brush: js notranslate">function makePerson(first, last) {
+ return {
+ first: first,
+ last: last
+ };
+}
+function personFullName(person) {
+ return person.first + ' ' + person.last;
+}
+function personFullNameReversed(person) {
+ return person.last + ', ' + person.first;
+}
+
+var s = makePerson('Simon', 'Willison');
+personFullName(s); // "Simon Willison"
+personFullNameReversed(s); // "Willison, Simon"</pre>
+
+<p>上面的写法虽然可以满足要求,但是看起来很麻烦,因为需要在全局命名空间中写很多函数。既然函数本身就是对象,如果需要使一个函数隶属于一个对象,那么不难得到:</p>
+
+<pre class="brush: js notranslate">function makePerson(first, last) {
+ return {
+ first: first,
+ last: last,
+ fullName: function() {
+ return this.first + ' ' + this.last;
+ },
+ fullNameReversed: function() {
+ return this.last + ', ' + this.first;
+ }
+ }
+}
+s = makePerson("Simon", "Willison");
+s.fullName(); // "Simon Willison"
+s.fullNameReversed(); // Willison, Simon</pre>
+
+<p>上面的代码里有一些我们之前没有见过的东西:关键字 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this"><code>this</code></a>。当使用在函数中时,<code>this</code> 指代当前的对象,也就是调用了函数的对象。如果在一个对象上使用<a href="https://developer.mozilla.org/en/JavaScript/Reference/Operators/Member_Operators">点或者方括号</a>来访问属性或方法,这个对象就成了 <code>this</code>。如果并没有使用“点”运算符调用某个对象,那么 <code>this</code> 将指向全局对象(global object)。这是一个经常出错的地方。例如:</p>
+
+<pre class="brush: js notranslate">s = makePerson("Simon", "Willison");
+var fullName = s.fullName;
+fullName(); // undefined undefined
+</pre>
+
+<p>当我们调用 <code>fullName()</code> 时,<code>this</code> 实际上是指向全局对象的,并没有名为 <code>first</code> 或 <code>last</code> 的全局变量,所以它们两个的返回值都会是 <code>undefined</code>。</p>
+
+<p>下面使用关键字 <code>this</code> 改进已有的 <code>makePerson</code>函数:</p>
+
+<pre class="brush: js notranslate">function Person(first, last) {
+ this.first = first;
+ this.last = last;
+ this.fullName = function() {
+ return this.first + ' ' + this.last;
+ }
+ this.fullNameReversed = function() {
+ return this.last + ', ' + this.first;
+ }
+}
+var s = new Person("Simon", "Willison");
+</pre>
+
+<p>我们引入了另外一个关键字:<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new"><code>new</code></a>,它和 <code>this</code> 密切相关。它的作用是创建一个崭新的空对象,然后使用指向那个对象的 <code>this</code> 调用特定的函数。注意,含有 <code>this</code> 的特定函数不会返回任何值,只会修改 <code>this</code> 对象本身。<code>new</code> 关键字将生成的 <code>this</code> 对象返回给调用方,而被 <code>new</code> 调用的函数称为构造函数。习惯的做法是将这些函数的首字母大写,这样用 <code>new</code> 调用他们的时候就容易识别了。</p>
+
+<p>不过,这个改进的函数还是和上一个例子一样,在单独调用<code>fullName()</code> 时,会产生相同的问题。</p>
+
+<p>我们的 Person 对象现在已经相当完善了,但还有一些不太好的地方。每次我们创建一个 Person 对象的时候,我们都在其中创建了两个新的函数对象——如果这个代码可以共享不是更好吗?</p>
+
+<pre class="brush: js notranslate">function personFullName() {
+ return this.first + ' ' + this.last;
+}
+function personFullNameReversed() {
+ return this.last + ', ' + this.first;
+}
+function Person(first, last) {
+ this.first = first;
+ this.last = last;
+ this.fullName = personFullName;
+ this.fullNameReversed = personFullNameReversed;
+}
+</pre>
+
+<p>这种写法的好处是,我们只需要创建一次方法函数,在构造函数中引用它们。那是否还有更好的方法呢?答案是肯定的。</p>
+
+<pre class="brush: js notranslate">function Person(first, last) {
+ this.first = first;
+ this.last = last;
+}
+Person.prototype.fullName = function() {
+ return this.first + ' ' + this.last;
+}
+Person.prototype.fullNameReversed = function() {
+ return this.last + ', ' + this.first;
+}
+</pre>
+
+<p><code>Person.prototype</code> 是一个可以被<code>Person</code>的所有实例共享的对象。它是一个名叫原型链(prototype chain)的查询链的一部分:当你试图访问 <code>Person </code>某个实例(例如上个例子中的s)一个没有定义的属性时,解释器会首先检查这个 <code>Person.prototype</code> 来判断是否存在这样一个属性。所以,任何分配给 <code>Person.prototype</code> 的东西对通过 <code>this</code> 对象构造的实例都是可用的。</p>
+
+<p>这个特性功能十分强大,JavaScript 允许你在程序中的任何时候修改原型(prototype)中的一些东西,也就是说你可以在运行时(runtime)给已存在的对象添加额外的方法:</p>
+
+<pre class="brush: js notranslate">s = new Person("Simon", "Willison");
+s.firstNameCaps(); // TypeError on line 1: s.firstNameCaps is not a function
+
+Person.prototype.firstNameCaps = function() {
+ return this.first.toUpperCase()
+}
+s.firstNameCaps(); // SIMON
+</pre>
+
+<p>有趣的是,你还可以给 JavaScript 的内置函数原型(prototype)添加东西。让我们给 <code>String</code> 添加一个方法用来返回逆序的字符串:</p>
+
+<pre class="brush: js notranslate">var s = "Simon";
+s.reversed(); // TypeError on line 1: s.reversed is not a function
+
+String.prototype.reversed = function() {
+ var r = "";
+ for (var i = this.length - 1; i &gt;= 0; i--) {
+ r += this[i];
+ }
+ return r;
+}
+s.reversed(); // nomiS
+</pre>
+
+<p>定义新方法也可以在字符串字面量上用(string literal)。</p>
+
+<pre class="brush: js notranslate">"This can now be reversed".reversed(); // desrever eb won nac sihT
+</pre>
+
+<p>正如我前面提到的,原型组成链的一部分。那条链的根节点是 <code>Object.prototype</code>,它包括 <code>toString()</code> 方法——将对象转换成字符串时调用的方法。这对于调试我们的 <code>Person</code> 对象很有用:</p>
+
+<pre class="brush: js notranslate">var s = new Person("Simon", "Willison");
+s; // [object Object]
+
+Person.prototype.toString = function() {
+ return '&lt;Person: ' + this.fullName() + '&gt;';
+}
+s.toString(); // &lt;Person: Simon Willison&gt;
+</pre>
+
+<p>你是否还记得之前我们说的 <code>avg.apply()</code> 中的第一个参数 <code>null</code>?现在我们可以回头看看这个东西了。<code>apply()</code> 的第一个参数应该是一个被当作 <code>this</code> 来看待的对象。下面是一个 <code>new</code> 方法的简单实现:</p>
+
+<pre class="brush: js notranslate">function trivialNew(constructor, ...args) {
+ var o = {}; // 创建一个对象
+ constructor.apply(o, args);
+ return o;
+}
+</pre>
+
+<p>这并不是 <code>new</code> 的完整实现,因为它没有创建原型(prototype)链。想举例说明 new 的实现有些困难,因为你不会经常用到这个,但是适当了解一下还是很有用的。在这一小段代码里,<code>...args</code>(包括省略号)叫作<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/rest_parameters">剩余参数(rest arguments)</a>。如名所示,这个东西包含了剩下的参数。</p>
+
+<p>因此,调用</p>
+
+<pre class="brush: js notranslate">var bill = trivialNew(Person, "William", "Orange");</pre>
+
+<p>可认为和调用如下语句是等效的</p>
+
+<pre class="brush: js notranslate">var bill = new Person("William", "Orange");</pre>
+
+<p><code>apply()</code> 有一个姐妹函数,名叫 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call"><code>call</code></a>,它也可以允许你设置 <code>this</code>,但它带有一个扩展的参数列表而不是一个数组。</p>
+
+<pre class="brush: js notranslate">function lastNameCaps() {
+ return this.last.toUpperCase();
+}
+var s = new Person("Simon", "Willison");
+lastNameCaps.call(s);
+// 和以下方式等价
+s.lastNameCaps = lastNameCaps;
+s.lastNameCaps();
+</pre>
+
+<h3 id="内部函数">内部函数</h3>
+
+<p>JavaScript 允许在一个函数内部定义函数,这一点我们在之前的 <code>makePerson()</code> 例子中也见过。关于 JavaScript 中的嵌套函数,一个很重要的细节是,它们可以访问父函数作用域中的变量:</p>
+
+<pre class="brush: js notranslate">function parentFunc() {
+ var a = 1;
+
+ function nestedFunc() {
+ var b = 4; // parentFunc 无法访问 b
+ return a + b;
+ }
+ return nestedFunc(); // 5
+}</pre>
+
+<p>如果某个函数依赖于其他的一两个函数,而这一两个函数对你其余的代码没有用处,你可以将它们嵌套在会被调用的那个函数内部,这样做可以减少全局作用域下的函数的数量,这有利于编写易于维护的代码。</p>
+
+<p>这也是一个减少使用全局变量的好方法。当编写复杂代码时,程序员往往试图使用全局变量,将值共享给多个函数,但这样做会使代码很难维护。内部函数可以共享父函数的变量,所以你可以使用这个特性把一些函数捆绑在一起,这样可以有效地防止“污染”你的全局命名空间——你可以称它为“局部全局(local global)”。虽然这种方法应该谨慎使用,但它确实很有用,应该掌握。</p>
+
+<h2 id="闭包">闭包</h2>
+
+<p>闭包是 JavaScript 中最强大的抽象概念之一——但它也是最容易造成困惑的。它究竟是做什么的呢?</p>
+
+<pre class="brush: js notranslate">function makeAdder(a) {
+ return function(b) {
+ return a + b;
+ }
+}
+var add5 = makeAdder(5);
+var add20 = makeAdder(20);
+add5(6); // ?
+add20(7); // ?
+</pre>
+
+<p><code>makeAdder</code> 这个名字本身,便应该能说明函数是用来做什么的:它会用一个参数来创建一个新的“adder”函数,再用另一个参数来调用被创建的函数时,<code>makeAdder</code> 会将一前一后两个参数相加。</p>
+
+<p>从被创建的函数的视角来看的话,这两个参数的来源问题会更显而易见:新函数自带一个参数——在新函数被创建时,便钦定、钦点了前一个参数(如上方代码中的 a、5 和 20,参考 <code>makeAdder</code> 的结构,它应当位于新函数外部);新函数被调用时,又接收了后一个参数(如上方代码中的 b、6 和 7,位于新函数内部)。最终,新函数被调用的时候,前一个参数便会和由外层函数传入的后一个参数相加。</p>
+
+<p>这里发生的事情和前面介绍过的内嵌函数十分相似:一个函数被定义在了另外一个函数的内部,内部函数可以访问外部函数的变量。唯一的不同是,外部函数已经返回了,那么常识告诉我们局部变量“应该”不再存在。但是它们却仍然存在——否则 <code>adder</code> 函数将不能工作。也就是说,这里存在 <code>makeAdder</code> 的局部变量的两个不同的“副本”——一个是 <code>a</code> 等于 5,另一个是 <code>a</code> 等于 20。那些函数的运行结果就如下所示:</p>
+
+<pre class="brush: js notranslate">x(6); // 返回 11
+y(7); // 返回 27
+</pre>
+
+<p>下面来说说,到底发生了什么了不得的事情。每当 JavaScript 执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它使用一切被传入函数的变量进行初始化(初始化后,它包含一切被传入函数的变量)。这与那些保存的所有全局变量和函数的全局对象(global object)相类似,但仍有一些很重要的区别:第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(如浏览器的 <code>window</code> 对象)不同的是,你不能从 JavaScript 代码中直接访问作用域对象,也没有 可以遍历当前作用域对象中的属性 的方法。</p>
+
+<p>所以,当调用 <code>makeAdder</code> 时,解释器创建了一个作用域对象,它带有一个属性:<code>a</code>,这个属性被当作参数传入 <code>makeAdder</code> 函数。然后 <code>makeAdder</code> 返回一个新创建的函数(暂记为 <code>adder</code>)。通常,JavaScript 的垃圾回收器会在这时回收 <code>makeAdder</code> 创建的作用域对象(暂记为 <code>b</code>),但是,<code>makeAdder</code> 的返回值,新函数 <code>adder</code>,拥有一个指向作用域对象 <code>b</code> 的引用。最终,作用域对象 <code>b</code> 不会被垃圾回收器回收,直到没有任何引用指向新函数 <code>adder</code>。</p>
+
+<p>作用域对象组成了一个名为作用域链(scope chain)的(调用)链。它和 JavaScript 的对象系统使用的原型(prototype)链相类似。</p>
+
+<p>一个<strong>闭包</strong>,就是 一个函数 与其 被创建时所带有的作用域对象 的组合。闭包允许你保存状态——所以,它们可以用来代替对象。<a href="http://stackoverflow.com/questions/111102/how-do-javascript-closures-work">这个 StackOverflow 帖子里</a>有一些关于闭包的详细介绍。</p>