> http://speakingjs.com/es5/ch01.html
[TOC]
# 第一章 javascript基礎知識
本章講述的是“基本的JavaScript”,我選擇了一個JavaScript子集,這個名字盡可能簡潔,同時也能讓你變得富有成效。當你開始學習JavaScript,我建議你先用它程序編程,然后再移動到其余的語言。這樣,你不必馬上學習所有的東西,因為這可能會讓人困惑。
## 背景
本節給出了JavaScript的一些背景知識,以幫助您理解它為什么是現在這個樣子。
### JavaScript與ECMAScript
ECMAScript是JavaScript的官方名稱。一個JavaScript的商標(最初由Sun持有,現在由甲骨文),那就需要新的名字。
目前,Mozilla是為數不多的幾家允許正式使用JavaScript的公司之一,因為它很久以前就收到了許可證。
對于常見用法,本規則適用:
* JavaScript 指的是javascript編程語言。
* ECMAScript的語言規范使用的名稱。因此,當指的是語言的版本,有人說ECMAScript。JavaScript的當前版本是ECMAScript ECMAScript 5;6是目前正在開發 。
### 語言的影響與本質
JavaScript的創造者,Brendan Eich,別無選擇,只能很快的創造語言(否則,網景公司就要采用其他更爛的語言)。他從幾種編程語言借來的語法:Java (語法, 原始值和引用值), Scheme and AWK (頭等函數[^first-class-functions]), Self (原型式繼承), and Perl and Python (字符串、數組和正則表達式).
JavaScript沒有異常處理直到ECMAScript 3,這解釋了為什么該語言經常自動轉換值并且失敗的靜默處理:它最初不拋出異常。
一方面,JavaScript有怪癖和丟失了相當多的功能(塊作用域的變量,模塊,支持子類化,等)。另一方面,它有幾個功能強大的功能,讓你解決這些問題。在其他語言中,你學習語言特征。在JavaScript中,你經常學習模式.
考慮到它的影響,JavaScript使編程風格成為函數式編程(高階函數,內置映射,減少等)和面向對象編程(對象,繼承)的混合并不奇怪。
## 語法
本節解釋JavaScript的基本語法規則。
### 語法的概述
語法的一些例子:
```js
// Two slashes start single-line comments
var x; // declaring a variable
x = 3 + y; // assigning a value to the variable `x`
foo(x, y); // calling function `foo` with parameters `x` and `y`
obj.bar(3); // calling method `bar` of object `obj`
// A conditional statement
if (x === 0) { // Is `x` equal to zero?
x = 123;
}
// Defining function `baz` with parameters `a` and `b`
function baz(a, b) {
return a + b;
}
```
注意等號的兩種不同用法:
* 一個單一的等號(=)是用來給變量賦值的。
* 一個三等號(= =)用于比較兩個值(見[相等運算符](###))。
### 語句與表達式
要理解JavaScript語法,你應該知道,它有兩個主要的語法范疇:語句和表達式:
* 語句“do things.”程序是一系列語句。下面是聲明的一個例子,它聲明(創建)變量:
`var foo;`
* 表達式產生值。它們是函數參數,賦值的右邊,這里是表達式的一個例子:
`3 * 7`
語句和表達式之間的區別最好的說明是JavaScript有兩種不同的方法," if-then-else"示例,用語句表達:
```js
var x;
if (y >= 0) {
x = y;
} else {
x = -y;
}
```
或作為一個表達式:
```js
var x = y >= 0 ? y : -y;
```
您可以使用后者作為函數參數(但不是前者):
```js
myFunction(y >= 0 ? y : -y)
```
最后,無論JavaScript在哪里期望一個語句,你也可以使用一個表達式;例如:
```js
foo(7, 1);
```
整行是一個語句(一個所謂的表達式語句),但函數調用`foo(7,1)`是一個表達式。
### 分號
分號在JavaScript中是可選的。不過,我建議總是包括它們,因為否則JavaScript可以猜錯語句的結束位置。詳細說明在[自動分號插入](###)。
分號終止語句,但不是用來終止塊的。有一個例子,在一個塊之后你會看到一個分號:函數表達式是用一個塊結束的表達式。如果這樣的表達式在語句中最后出現,則后跟分號:
```js
// Pattern: var _ = ___;
var x = 3 * 7;
var f = function () { }; // function expr. inside var decl.
```
### 注釋
JavaScript有兩種注釋:單行注釋和多行注釋。
* 單行注釋:以`//`開始,到行結束終止。
```js
x++; // single-line comment
```
* 多行注釋:以`/*`和`*/`分隔:
```js
/*This is
a multiline
comment.
*/
```
## 變量和賦值
JavaScript中的變量在使用前聲明
```js
var foo; // declare variable `foo`
```
### 賦值
你可以聲明一個變量,同時賦值:
```js
var foo = 6;
```
你也可以對現有的變量賦值:
```js
foo = 4; // change variable `foo`
```
### 復合賦值運算符
有復合賦值運算符,如`+=`。以下兩個賦值效果是一樣的:
```js
x += 1;
x = x + 1;
```
### 標識符和變量名
標識符是在JavaScript中扮演各種語法角色的名稱。例如,變量名是標識符。
**標識符是區分大小寫的。**
粗略地說,一個標識符的第一個字符可以是任何Unicode字母,一個美元符號($),或一個下劃線(_)。隨后的字符可以是任何Unicode數字。因此,以下是所有合法標識符:
```js
arg0
_tmp
$elem
π
```
下列標識符是保留字,它們是語法的一部分,不能用作變量名(包括函數名和參數名):
| arguments | break | case | catch |
|:-|:-|:-|:-|
| class |const | continue | debugger |
| default | delete | do | else |
| enum |export | extends | false |
| finally | for | function | if |
| implements |import | in | instanceof |
| interface | let | new | null |
| package |private | protected | public |
| return |static | super | switch |
| this | throw | true | try |
| typeof | var | void | while |
以下三個標識符不是保留字,但你應該把它們當作是javascript的保留字:
```
Infinity
NaN
undefined
```
最后,你也應該遠離標準的全局變量的名稱(見[23章](###))。你可以將它們用于局部變量而不破壞任何東西,但你的代碼仍然會變得混亂。
## 值
JavaScript有許多我們期望從編程語言中得到的值:booleans, numbers, strings, arrays, 等等。JavaScript中的所有值都具有屬性[^properties]。每個屬性都有一個鍵(或名稱)和一個值。你可以想到像記錄字段的屬性。您使用點(.)運算符讀取屬性:
```js
value.propKey
```
例如,string“ABC”具有屬性length:
```js
> var str = 'abc';
> str.length
3
```
前面也可以寫為:
```js
> 'abc'.length
3
```
點運算符也用于將值賦給屬性:
```js
> var obj = {}; // empty object
> obj.foo = 123; // create property `foo`, set it to 123
123
> obj.foo
123
```
你可以用它來調用方法:
```js
> 'hello'.toUpperCase()
'HELLO'
```
在前面的示例中,我們調用了值'hello'的方法`touppercase()`。
### 原始值與對象
JavaScript中值之間的區別有點特別:
* 原始值有:booleans, numbers, strings, null, and undefined.
* 其他所有的值是對象。
兩者之間的一個主要區別是如何比較,每個對象都有一個唯一的身份,只有(嚴格)等于自己:
```js
> var obj1 = {}; // an empty object
> var obj2 = {}; // another empty object
> obj1 === obj2
false
> obj1 === obj1
true
```
與此相反,所有被賦值相同值的原始值都被認為是相同的:
```js
> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true
```
接下來的兩個部分將更詳細地解釋原始值和對象。
### 原始值
以下都是原始值:
- Booleans: true, false (see [Booleans](###))
- Numbers: 1736, 1.351 (see [Numbers](###))
- Strings: 'abc', "abc" (see [Strings](###))
- Two “nonvalues”: undefined, null (see [undefined and null](###))
原始值有以下特點:
**通過值相比**
比較“內容”:
```js
> 3 === 3
true
> 'abc' === 'abc'
true
```
**永遠不變的**
原始值的屬性不能更改、添加或刪除:
```js
> var str = 'abc';
> str.length = 1; // try to change property `length`
> str.length // ? no effect
3
> str.foo = 3; // try to create property `foo`
> str.foo // ? no effect, unknown property
undefined
```
(讀取未知屬性總是返回undefined。)
### 對象
所有的非原始值是對象。最常見的對象是:
* 普通對象,可以由對象字面量 (見[單個對象](###)):
```js
{
firstName: 'Jane',
lastName: 'Doe'
}
```
上面的對象有兩個屬性:
firstName的值是“Jane”,lastName屬性的值是“Doe”。
* 數組,可以由數組文本來創建(見[數組](###)):
```js
[ 'apple', 'banana', 'cherry' ]
```
上面的數組有三個元素,可以通過訪問數字指標。例如,“apple”的index為0。
* 正則表達式,正則表達式,其可以通過正則表達式文本(見正則表達式)來創建(見[正則表達式](###)):
```js
/^a+b+$/
```
對象有以下特點:
**通過引用相比**
身份標識進行比較,每個值都有自己的身份標識:
```js
> ({} === {}) // two different empty objects
false
> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
```
**默認可以被改變**
通常可以自由地更改、添加和移除屬性(參見[單個對象](###)):
```js
> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123
```
### undefined和null
大多數編程語言都有表示缺少信息的值。JavaScript有兩個這樣的"nonvalues",`undefined` 和`null`:
* `undefined`的意思是“沒有值”。未初始化的變量是undefined的:
```js
> var foo;
> foo
undefined
```
缺少參數未定義:
```js
> function f(x) { return x }
> f()
undefined
```
如果您讀取一個不存在的屬性,您將得到`undefined`:
```js
> var obj = {}; // empty object
> obj.foo
undefined
```
* `null`的意思是“no object.”。每當你預期需要一個對象時,它作為nonvalue (參數,鏈中的對象,等)。
| <span style="color:#C67171">警告</span> |
|--------|
| undefined和null沒有屬性,甚至沒有標準方法如toString()。 |
#### 檢查 undefined 或 null
函數通常允許您通過`undefined`或`null`指示丟失的值。你可以通過一個明確的檢查做同樣的檢查:
```js
if (x === undefined || x === null) {
...
}
```
您還可以利用這一事實,即`undefined`和`null` 被認為是`false`:
| <span style="color:#C67171">警告</span> |
|--------|
| false,0,NaN和 '' 也被認為是`false`(見[Truthy和Falsy]())。 |
### 使用 typeof和 instanceof分類
有兩個進行區分值的操作符:`typeof`運算主要用于原始值,而的`instanceof`用于對象。
`typeof`是這樣運算的:
```js
typeof value
```
它返回一個字符串,描述值的“類型”。以下是一些例子:
```js
> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {} // empty object literal
'object'
> typeof [] // empty array literal
'object'
```
下表列出了所有結果類型:
| 操作對象 | 結果 |
|--------|--------|
| undefined | 'undefined' |
| null | 'object' |
| Boolean value | 'boolean' |
| Number value | 'number' |
| String value | 'string' |
| Function | 'function' |
| All other normal values | 'object' |
| (引擎創造的值) | JavaScript引擎允許創建針對的typeof返回任意的字符串(從該表中列出的所有結果不同)值。 |
`typeof`運算返回`null`“object”是一個錯誤,無法修復,因為它會破壞現有代碼。這并不意味著`null`是一個對象。
instanceof是這樣的:
`value instanceof Constr`
如果'value'是已經由'Constr'構造方法創建的一個實例對象,則返回true(請參見[構造函數:對象的工廠](###))。這里有些例子:
```js
> var b = new Bar(); // object created by constructor Bar
> b instanceof Bar
true
> {} instanceof Object
true
> [] instanceof Array
true
> [] instanceof Object // Array is a subconstructor of Object
true
> undefined instanceof Object
false
> null instanceof Object
false
```
## 布爾值
原始布爾類型包括值true和false。下面的運算符會產生布爾值:
* 二進制邏輯運算符:`&&`(與),`||`(或)
* 前綴邏輯運算符:`!`(非)
* 比較運算符:
相等運算符: `===, !==, ==, !=`
排序運算符(字符串和數字):`>, >=, <, <=`
### 真值(Truthy)與假值(Falsy)
每當JavaScript期望一個布爾值(例如,if語句的條件)時,可以使用任何值進行轉換。該值將被解釋為是`true`或者`false`。下列值被解釋為`false`:
* undefined,null
* 布爾值:false
* 數值:0,NaN
* 字符串:''
所有其他值(包括所有對象!)被認為是true 。 被解釋為false值被稱為false ,被解釋為true值被稱為true的。調用Boolean()可以將其參數轉換為布爾值。您可以使用它來測試一個值如何被解釋執行為布爾型:
```js
> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({}) // 空對象
true
> Boolean([]) // 空數組
true
```
### 二元邏輯運算符
JavaScript中的二元邏輯運算符會造成**`短路`**。也就是說,如果第一個操作數足以確定結果,則不計算第二個操作數。例如,下面的表達式,不會執行到`函數foo()`:
```js
false && foo()
true || foo()
```
此外,二元邏輯運算符返回它們的操作數之一,不一定是布爾值。可以查看下面的例子確定:
**與 (&&)**
如果第一個操作數是false的,直接就會返回該值,否則,返回第二個操作數:
```js
> NaN && 'abc'
NaN
> 123 && 'abc'
'abc'
```
**或 (||)**
如果第一個操作數為true,返回該值,否則,就返回第二個操作數:
```js
> 'abc' || 123
'abc'
> '' || 123
123
```
### 相等運算符
JavaScript有兩種相等類型:
* 常規的,或者“比較寬松的”,相等:== 和 !=
* 嚴格的相等:=== 和 !==
常規的相等一般就是值相等(細節在 [Normal( Lenient )Equality(==,!=)](###)中解釋 ),但是往往會導致隱藏錯誤存在。 因此,**建議始終使用嚴格的相等(===)**。
## 數值
在JavaScript中所有的數字都是浮點:
```js
> 1 === 1.0
true
```
以下為特殊的數值:
**NaN (“非數值”)**
```js
> 1 === 1.0
true
```
**無窮(Infinity)**
```js
> 3 / 0
Infinity
> Math.pow(2, 1024) // 數值太大
Infinity
```
`Infinity`于任何其他數(除NaN)。同樣,`Infinity`遠小于任何其他號碼(除NaN)。這使得這些數字可用作為默認值(例如,當您正在尋找一個最小值或最大值)。
## 運算符
JavaScript具有以下算術運算符 (參見 [算術運算符](###)):
- 加號: number1 + number2
- 減法: number1 - number2
- 乘法: number1 * number2
- 除法: number1 / number2
- 余數: number1 % number2
- 自增: ++variable , variable++
- 自減: - --variable - variable--
- 否定: -value
- 轉換為數字: +value
全局對象Math (見[Math](###))通過函數提供更多的算術運算 。
JavaScript還具有用于按位運算的運算符(例如,按位運算符;請參見[按位運算符](###))。
## 字符串
字符串可以通過字符串文字直接創建。這些文字是由單或雙引號分隔。反斜杠(\)轉義字符,并產生一些控制字符。這里有些例子:
```js
'abc'
"abc"
'Did she say "Hello"?'
"Did she say \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLine 2' // newline
'Backlash: \\'
```
字符串中的單個字符可以通過方括號訪問:
```js
> var str = 'abc';
> str[1]
'b'
```
字符串的屬性`length`計算字符串中的字符數:
```js
> 'abc'.length
3
```
與所有原始類型一樣,字符串是不可變的;如果要更改現有字符串,則需要創建新字符串。
### 字符串運算符
字符串通過加號( + )運算符連接,如果其中一個操作數是字符串,則將其他操作數轉換為字符串:
```js
> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'
```
要在多個步驟中串聯字符串,請使用+=運算符:
```js
> var str = '';
> str += 'Multiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Multiple pieces are concatenated.'
```
### 字符串方法
字符串有很多有用的方法(參見 [String Prototype Methods](###))。 這里有些例子:
```js
> 'abc'.slice(1) // copy a substring
'bc'
> 'abc'.slice(1, 2)
'b'
> '\t xyz '.trim() // trim whitespace
'xyz'
> 'mj?lnir'.toUpperCase()
'MJ?LNIR'
> 'abc'.indexOf('b') // find a string
1
> 'abc'.indexOf('x')
-1
```
## 語句
JavaScript中的條件和循環在以下部分中介紹。
### 條件
if語句具有 根據布爾條件執行 的then子句和 可選的else子句:
```js
if (myvar === 0) {
// then
}
if (myvar === 0) {
// then
} else {
// else
}
if (myvar === 0) {
// then
} else if (myvar === 1) {
// else-if
} else if (myvar === 2) {
// else-if
} else {
// else
}
```
我建議總是使用大括號(它們表示零個或多個語句的塊)。 但是如果一個子句只是一個單獨的語句不是非要這么做(對于for和while的控制流語句一樣):
```js
if (x < 0) return -x;
```
以下是一個switch語句。 fruit的值決定了哪個case執行:
```js
switch (fruit) {
case 'banana':
// ...
break;
case 'apple':
// ...
break;
default: // all other cases
// ...
}
```
case后的“操作數”可以是任何表達式;**通過===來比較switch的參數**。
### 循環
for循環具有以下格式:
```js
for ([[?init?]]; [[?condition?]]; [[?post_iteration?]])
?statement?
```
`init` 再這個循環開始之前會被執行,`condition`會在每個循環迭代前被判斷檢查,如果它變為false,則循環終止。`post_iteration`在每個循環迭代后執行。
這個例子在控制臺打印arr數組的所有元素:
```js
for (var i=0; i < arr.length; i++) {
console.log(arr[i]);
}
```
`while`循環條件語句,只要在條件成立時,會繼續進行循環迭代。
```js
// Same as for loop above:
var i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
```
`do-while`循環在條件成立時,會繼續進行循環迭代。當條件依據循環體改變,但是至少會執行一次循環體:
```js
do {
// ...
} while (condition);
```
在所有的循環語句中:
1. `break` 會離開循環。
2. `continue` 開始一個新的循環迭代。
## 函數
定義函數的一種方法是通過**函數聲明**
```js
function add(param1, param2) {
return param1 + param2;
}
```
上面的代碼定義了一個函數`add`,有兩個參數,`param1`和`param2`,并返回參數的總和。需要這樣調用函數:
```js
> add(6, 1)
7
> add('a', 'b')
'ab'
```
另一種定義`add()`的方式,是給變量`add`賦值一個**函數表達式**:
```js
var add = function (param1, param2) {
return param1 + param2;
};
```
函數表達式產生了一個值,因此可以直接將函數作為參數傳遞給其他函數:
```js
someOtherFunction(function (p1, p2) { ... });
```
### 函數聲明被提升
函數聲明會被全部移動到當前作用域范圍的開始處。這允許您引用后來聲明的函數:
```js
function foo() {
bar(); // OK, bar is hoisted(bar 被提升到此處)
function bar() {
...
}
}
```
注意:`var`的聲明也會被提升(查看[Variables Are Hoisted](###)),但是通過`var`賦值的函數表達式卻不會:
```js
function foo() {
bar(); // Not OK, bar is still undefined
var bar = function () {
// ...
};
}
```
### 特殊變量arguments
你可以用任意數量的參數調用JavaScript中的任何函數,JS語言永遠不會抱怨。然而,它將通過特殊變量`arguments`使所有參數可用。`arguments`看起來像數組,但沒有數組方法:
```js
> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0] // read element at index 0
'a'
```
### 過多或過少的Arguments
讓我們用下面的函數來探討如何用JavaScript處理`過多或過少的Arguments`(函數`toarray()`查看[將參數轉換為數組](###)):
```js
function f(x, y) {
console.log(x, y);
return toArray(arguments);
}
```
多余的參數將被忽略(除了`arguments`,arguments對象代表實參。):
```js
> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]
```
缺少參數將得到`undefined`:
```js
> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]
```
### 可選參數
以下是為參數分配默認值的常見形式:
```js
function pair(x, y) {
x = x || 0; // (1)
y = y || 0;
return [ x, y ];
}
```
在第一行,如果`x`是真值(不是`null`,`undefined`,等),操作符`||` 返回`x`,否則,返回第二個操作數:
```js
> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]
```
### 強制數量
如果你想強制參數數量(一個特定數量的參數),你可以檢查`arguments.length`:
```js
function pair(x, y) {
if (arguments.length !== 2) {
throw new Error('Need exactly 2 arguments');
}
...
}
```
### 將參數轉換為數組
參數不是數組,只是類似數組(見[Array-Like Objects and Generic Methods](###))。它有`length`屬性,并且您可以通過在方括號內的索引訪問其元素。但是,您不能刪除元素或在其上調用任何數組的方法。因此,你有時需要將參數轉換為一個數組,這就是下面的函數的作用([Array-Like Objects and Generic Methods](###)進行了解釋):
```js
function toArray(arrayLikeObject) {
return Array.prototype.slice.call(arrayLikeObject);
}
```
## 異常處理
處理異常的最常見的方式(見[14章](###))如下:
```js
function getPerson(id) {
if (id < 0) {
throw new Error('ID must not be negative: '+id);
}
return { id: id }; // normally: retrieved from database
}
function getPersons(ids) {
var result = [];
ids.forEach(function (id) {
try {
var person = getPerson(id);
result.push(person);
} catch (exception) {
console.log(exception);
}
});
return result;
}
```
`try`子句包圍主要代碼,如果在`try`子句中拋出異常,則執行`catch`子句。使用前面的代碼:
```js
> getPersons([2, -5, 137])
[Error: ID must not be negative: -5]
[ { id: 2 }, { id: 137 } ]
```
## 嚴格模式
嚴格模式(見[Strict Mode](###))使更多的警告,讓JavaScript成為更干凈,好用的語言(非嚴格模式有時被稱為“懶散”模式)。要啟用它,首先在JavaScript文件或`<script>`標簽中鍵入以下行:
~~~
'use strict';
~~~
您也可以在每個函數中開啟嚴格模式:
```js
function functionInStrictMode() {
'use strict';
...
}
```
## 變量作用域和閉包
在JavaScript中,使用變量之前先用`var`聲明變量:
```js
> var x;
> x
undefined
> y
ReferenceError: y is not defined
```
可以用單個var語句聲明和初始化多個變量:
```js
var x = 1, y = 2, z = 3;
```
但是,我建議每個變量使用一個語句(查看:[Syntax](###))。因此,我會重寫前面的語句:
```js
var x = 1;
var y = 2;
var z = 3;
```
由于變量提升(見[Variables Are Hoisted](###))的,通常最好在函數的開頭聲明變量。
### 變量的作用域
變量的作用域始終是在整個函數內的(相對于當前塊)。例如:
```js
function foo() {
var x = -512;
if (x < 0) { // (1)
var tmp = -x;
...
}
console.log(tmp); // 512
}
```
我們可以看到,變量`tmp`不限制在開始`(1)`的塊內,它一直存在直到函數的結束。
### 變量被提升
每一個變量聲明會被提升:聲明移動到函數的開始處,但賦值部分依然在原處。例如,在下面的函數中考慮行`(1)`中的變量聲明:
```js
function foo() {
console.log(tmp); // undefined
if (false) {
var tmp = 3; // (1)
}
}
```
在內部,上述的函數是這樣執行的:
```js
function foo() {
var tmp; // hoisted declaration(聲明被提升)
console.log(tmp);
if (false) {
tmp = 3; // assignment stays put (賦值停留原處)
}
}
```
### 閉包
每一個函數都會保持連接到包裹它的外部函數中的變量,即使在它被創建的作用域之外。例如:
```js
function createIncrementor(start) {
return function () { // (1)
start++;
return start;
}
}
```
開始`(1)`的函數離開了創建它的上下文,但保持連接到`start`的live版本:
```js
> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8
```
閉包是函數加上與其包圍作用域的變量的連接。因此,`createincrementor()`的返回是一個閉包。
### IIFE(立即執行函數表達式):引入新的作用域
IIFE(Immediately-Invoked Function Expression)
有時,您希望引入一個新的變量范圍,例如,以防止變量成為全局變量。在JavaScript中,不能使用塊來執行,必須使用函數。但有一個模式,用一種類似塊的方式使用函數。被稱為IIFE ([立即調用函數表達式](http://benalman.com/news/2010/11/immediately-invoked-function-expression/),發音為“iffy”):
```js
(function () { // open IIFE
var tmp = ...; // not a global variable
}()); // close IIFE
```
一定要按上面的示例(除了注釋之外)輸入。一個`IIFE`是一個函數表達式,在定義之后就被立即調用。在函數中,一個新的作用域存在,阻止了`tmp`成為全局變量。查看:[Introducing a New Scope via an IIFE](###),獲取更多的`IIFEs`信息。
>IIFE使用案例:不經意的通過閉包共享。
閉包保持它們與外部變量的連接,這有時不是你想要的:
```js
var result = [];
for (var i=0; i < 5; i++) {
result.push(function () { return i }); // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)
```
返回的值`(1)`始終是`i`的當前值,而不是函數創建時的值。循環結束后,`i`值為5,這就是數組中的所有函數返回值。如果你想在行`(1)`的函數接收我的當前值的快照,你可以使用一個IIFE:
```js
for (var i=0; i < 5; i++) {
(function () {
var i2 = i; // copy current i
result.push(function () { return i2 });
}());
}
```
## 對象和構造函數
本節介紹了JavaScript的兩種基本面向對象機制:單個對象和構造函數(這是對象的工廠,類似于其他語言的類)。
### 單一對象
像所有值一樣,對象具有屬性。實際上,您可以考慮一個對象是一組屬性,其中每個屬性是一個(鍵,值)對。鍵是一個字符串,這個值是任何JavaScript值,在JavaScript中,可以通過對象文字直接創建普通對象:
```js
'use strict';
var jane = {
name: 'Jane',
describe: function () {
return 'Person named '+this.name;
}
};
```
前面的對象具有屬性`name`和`describe`。您可以讀取(“get”)和寫入(“set”)屬性:
```js
> jane.name // get
'Jane'
> jane.name = 'John'; // set
> jane.newProperty = 'abc'; // property created automatically
```
值為函數的屬性,如`describe`被稱為方法。他們用`this`引用被調用的對象:
```js
> jane.describe() // call method
'Person named John'
> jane.name = 'Jane';
> jane.describe()
'Person named Jane'
```
可以使用`in`來檢查某個屬性是否存在:
```js
> 'newProperty' in jane
true
> 'foo' in jane
false
```
如果讀取不存在的屬性,則將得到`undefined`。因此,上述的兩次檢查也可以這樣做[^2]:
```language
> jane.newProperty !== undefined
true
> jane.foo !== undefined
false
```
`delete`操作符可以刪除一個屬性:
```js
> delete jane.newProperty
true
> 'newProperty' in jane
false
```
### 任意屬性名
屬性鍵可以是任意字符串。到目前為止,我們已經看到了對象字面量的屬性名和在點運算符之后。但是,只有當它們是標識符時,才可以使用它們(查看[標識符與變量名](###))。
如果你想使用其他字符串作為鍵,你必須引用它們在一個對象字面和使用方括號獲取和設置屬性:
```js
> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> obj['not an identifier'] = 456;
```
方括號還允許您計算屬性的鍵:
```js
> var obj = { hello: 'world' };
> var x = 'hello';
> obj[x]
'world'
> obj['hel'+'lo']
'world'
```
### 提取方法
如果你提取了一個方法,它失去了它與對象的連接。就其本身而言,函數不再是一個方法,`this`變為`undefined`(在嚴格的模式下)。
舉個例子,讓我們回到先前的對象`jane`:
```js
'use strict';
var jane = {
name: 'Jane',
describe: function () {
return 'Person named '+this.name;
}
};
```
我們想從`jane`中提取方法`describe`,把它放到一個變量`func`中,并且調用它。然而,這不起作用:
```js
> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undefined
```
解決的方法是采用所有函數都有的`bind()`方法。用它創建一個新函數,這個函數的`this`總是具有給定值:
```js
> var func2 = jane.describe.bind(jane);
> func2()
'Person named Jane'
```
### 方法中的函數
每個函數都有自己的特殊變量`this`。這是不方便的,如果你在函數內嵌套一個函數,因為你不能從函數中訪問方法的`this`。下面是一個例子,我們調用`forEach`使用一個函數來遍歷數組:
```js
var jane = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
logHiToFriends: function () {
'use strict';
this.friends.forEach(function (friend) {
// `this` is undefined here
console.log(this.name+' says hi to '+friend);
});
}
}
```
調用`logHiToFriends`會產生一個錯誤:
```js
> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined
```
讓我們看看兩種修復方法。首先,我們可以把`this`存儲在一個不同的變量:
```js
logHiToFriends: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' says hi to '+friend);
});
}
```
或者,`forEach`有第二參數允許你為`this`提供值:
```js
logHiToFriends: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' says hi to '+friend);
}, this);
}
```
函數表達式經常用作JavaScript中函數調用的參數。當你從這些函數表達式中引用(refer to)`this`時,一定要小心。
### 構造函數:對象工廠
現在為止,你可能認為JavaScript對象只是從字符串到值的映射,JavaScript對象字面表示了一個概念,就好像其他語言的映射/字典字面量。然而,JavaScript對象也支持一個真正面向對象的功能:繼承。本節不完全解釋JavaScript的繼承如何工作,但為了讓你了解,向您展示了一個簡單的模式。如果你想知道更多的信息,查看 [第17章](###)。
除了是“真正的”函數和方法外,函數在JavaScript中扮演另一個角色:如果通過`new`操作符調用函數對象,它們就成為對象的構造函數工廠。因此,構造函數是對其他語言類的粗略模擬。按照慣例,構造函數的名稱以大寫字母開頭。
例如:
```js
// Set up instance data
function Point(x, y) {
this.x = x;
this.y = y;
}
// Methods
Point.prototype.dist = function () {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
```
我們可以看到構造函數有兩個部分。
首先,函數`Point`設置了實例的數據。
其次,屬性`Point.prototype`包含對象的方法。
前者的數據是每個實例特定的,而后者的數據是所有實例共享的。
為了使用`Point`,我們通過`new`操作符去調用它:
```js
> var p = new Point(3, 5);
> p.x
3
> p.dist()
5.830951894845301
```
`p`是一個`Point`的一個實例:
```js
> p instanceof Point
true
```
## 數組
數組是可以通過在0開始的整數索引訪問的元素序列。
### 數組字面量
數組字面量很方便創建數組:
```js
> var arr = [ 'a', 'b', 'c' ];
```
上述的數組有三個元素:字符串“a”、“B”和“c”。您可以通過整數索引訪問它們:
```js
> arr[0]
'a'
> arr[0] = 'x';
> arr
[ 'x', 'b', 'c' ]
```
`length`屬性表示數組有多少個元素。可以使用它來添加元素或者移除元素:
```js
> var arr = ['a', 'b'];
> arr.length
2
> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3
> arr.length = 1;
> arr
[ 'a' ]
```
`in`操作符也可以對數組使用:
```js
> var arr = [ 'a', 'b', 'c' ];
> 1 in arr // is there an element at index 1?
true
> 5 in arr // is there an element at index 5?
false
```
注意:數組是對象,因此可以具有對象屬性:
```js
> var arr = [];
> arr.foo = 123;
> arr.foo
123
```
### 數組方法
數組有很多方法(見[數組原型方法](##))。這里有幾個例子:
```js
> var arr = [ 'a', 'b', 'c' ];
> arr.slice(1, 2) // copy elements
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]
> arr.push('x') // append an element
4
> arr
[ 'a', 'b', 'c', 'x' ]
> arr.pop() // remove last element
'x'
> arr
[ 'a', 'b', 'c' ]
> arr.shift() // remove first element
'a'
> arr
[ 'b', 'c' ]
> arr.unshift('x') // prepend an element
3
> arr
[ 'x', 'b', 'c' ]
> arr.indexOf('b') // find the index of an element
1
> arr.indexOf('y')
-1
> arr.join('-') // all elements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'
```
### 遍歷數組
有幾種遍歷元素的數組方法(參見[迭代(無損)](###))。兩個最重要的是`forEach`和`map`。
foreach遍歷數組,傳遞了當前元素和它的索引:
```js
[ 'a', 'b', 'c' ].forEach(
function (elem, index) { // (1)
console.log(index + '. ' + elem);
});
```
注意,行`(1)`中的函數可以忽略參數。它可以,例如,只有參數`elem`元素。
`map`通過將函數應用于現有數組的每個元素,來創建一個新數組:
```js
> [1,2,3].map(function (x) { return x*x })
[ 1, 4, 9 ]
```
## 正則表達式
JavaScript有內置支持正則表達式(第19章教程,詳細解釋他們是如何工作的)。他們以斜線(/)分隔:
```js
/^abc$/
/[A-Za-z0-9]+/
```
### test()方法:匹配嗎
```js
> /^a+b+$/.test('aaab')
true
> /^a+b+$/.test('aaa')
false
```
### exec()方法:匹配以及捕獲分組
```js
> /a(b+)a/.exec('_abbba_aba_')
[ 'abbba', 'bbb' ]
```
返回的數組中,索引為0的項包含了完整匹配,索引為1的項為第一組的捕獲,等等。有一個辦法(討論[RegExp.prototype.exec: Capture Groups](###))來多次調用此方法獲取所有匹配。
### replace()方法:搜索和替換
```js
> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]')
'[a] [bbb]'
```
`replace`的第一個參數必須是帶有`/g`標志的正則表達式。否則,只有在第一次發生時被替換(也就是替換一次,后面滿足條件的字符就不替換了)。還有一個辦法(討論[String.prototype.replace: Search and Replace](###))使用函數計算替換。
## Math
Math (查看 [第21章](###)) 是一個具有算術函數的對象。下面的示例:
```js
> Math.abs(-2)
2
> Math.pow(3, 2) // 3 to the power of 2
9
> Math.max(2, -1, 5)
5
> Math.round(1.9)
2
> Math.PI // pre-defined constant for π
3.141592653589793
> Math.cos(Math.PI) // compute the cosine for 180°
-1
```
## 標準庫的其他功能
JavaScript的標準庫比較簡樸,但有更多的東西可以使用:
Date ([第20章](###))
主要功能是解析和創建日期字符串并訪問日期(年、小時等)的日期的構造函數。
JSON ([第22章](###))
具有解析和生成JSON數據的函數的對象。
console**.* methods** (查看[Console的接口](###))
這些瀏覽器的特定方法不是該語言的一部分,但其中的一些方法可以在Node.js使用。
***
[^first-class-functions]: 在計算機科學中,如果一門編程語言把函數看做頭等公民就可以這門語言支持頭等函數。具體也就是說,函數能像參數那樣被傳遞到另一個函數、從另一個函數那像值一樣被返回出來、函數可以賦值給變量或者存在數據結構中。
[^2]:注意: 此檢查將報告不存在的屬性,但存在`undefined`的值。
- 本書簡介
- 前言
- 關于這本書你需要知道些什么
- 如何閱讀本書
- 目錄
- I. JavaScript的快速入門
- 第1章 基礎的JavaScript
- II. 背景知識
- 第2章 為什么選擇JavaScript?
- 第3章 JavaScript的性質
- 第4章 JavaScript是如何創造出來的
- 第5章 標準化:ECMAScript
- 第6章 JavaScript的歷史里程碑
- III. 深入JavaScript
- 第7章 JavaScript語法
- 第8章 值
- 第9章 運算符
- 第10章 布爾值
- 第11章 數字
- 第12章 字符串
- 第13章 語句
- 第14章 異常捕獲
- 第15章 函數
- 第16章 變量:作用域、環境和閉包
- 第17章 對象和繼承
- 第18章 數組
- 第19章 正則表達式
- 第20章 Date
- 第21章 Math
- 第22章 JSON
- 第23章 標準全局變量
- 第24章 編碼和JavaScript
- 第25章 ECMAScript 5中的新功能
- IV. 技巧、工具和類庫
- 第26章 元代碼樣式指南
- 第27章 調試的語言機制
- 第28章 子類化內置構造函數
- 第29章 JSDoc:生成API文檔
- 第30章 類庫
- 第31章 模塊系統和包管理器
- 第32章 其他工具
- 第33章 接下來該做什么
- 著作權