《JavaScript語言入門教程》記錄整理:入門和數據類型

本系列基於阮一峰老師的《JavaScrip語言入門教程》或《JavaScript教程》記錄整理,教程採用知識共享 署名-相同方式共享 3.0協議。這幾乎是學習js最好的教程之一(去掉之一都不過分)

最好的教程而阮一峰老師又採用開源方式共享出來,之所以重新記錄一遍,一是強迫自己重新認真讀一遍學一遍;二是對其中知識點有個自己的記錄,加深自己的理解;三是感謝這麼好的教程,希望更多人閱讀了解

入門篇

js介紹

  1. JavaScript 是一種輕量級的腳本語言和嵌入式(embedded)語言,其只能通過宿主環境(host,瀏覽器或node環境)提供I/O操作
  2. 語法角度,JS是一種”對象模型”語言。支援函數式編程、”面向對象”編程、過程式編程等
  3. js核心語法包括:基本的語法構造(比如操作符、控制結構、語句)和標準庫(就是一系列具有各種功能的對象比如ArrayDateMath等)。然後就是宿主提供的API(比如瀏覽器提供的BOM、DOM和Web互聯網相關的類;Node環境提供文件操作API、網路通訊API等)
  4. JavaScript 的所有值都是對象
  5. js可以採用事件驅動(event-driven)和非阻塞式(non-blocking)設計,實現高並發、多任務處理

歷史

  1. 1995年,Brendan Eich 只用了10天,完成了js的第一版,其設計借鑒了C、java、Scheme、Awk、Self、Perl、Python等多種語言,同時也留下了各種設計缺陷(導致常常需要學習各種解決問題的模式)
  2. JavaScript與Java是兩種不同的語言,兩者的關係僅僅是js的基本語法和對象體系最開始是想要模仿Java,而後又與當時Java的公司Sun有合作,也藉助Java的聲勢,從而後來改名叫JavaScript
  3. ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現。ECMAScript 只用來標準化 JavaScript 的基本語法結構。但其他標準如 DOM 的標準就是由 W3C組織(World Wide Web Consortium)制定的
  4. 2007年,ECMAScript 4.0版草案發布,但是過於激進,導致後面中止了 ECMAScript 4.0 ,將其中一小部分功能發布為ECMAScript 3.1。之後又將其改名為 ECMAScript 5。
  5. 2015年6月,ECMAScript 6 正式發布,且更名為「ECMAScript 2015」。之後每年發布一個ECMAScript版本
  6. 周邊大事記

基本語法

  1. js執行單位是行(line),一行一行地執行,一般,一行就是一個語句
  2. 語句(statement)執行某種操作、表達式(expression)用於得到返回值。凡是 JavaScript 語言中預期為值的地方,都可以使用表達式(這一點使js某些使用很靈活)
  3. 語句以分號結尾,一個分號表示一個語句結束。多個語句可以寫在一行內。分號前無內容,表示空語句。表達式不需要分號結尾
  4. 變數是對「值」的具名引用,即為”值”取名。變數的名字就是變數名。如下使用var聲明一個變數a,並賦值1
var a=1;
  1. 只聲明變數而不賦值,則該變數的值是undefined,表示”無定義”。同時變數賦值時不寫var也有效,但不建議。變數必須先聲明再使用,否則會報錯not defined
  2. 一條var命令可以聲明多個變數。js是動態類型語言,變數類型可以隨時更改。
var a = 1;
a = 'hello';
  1. 使用var重新聲明一個已存在的變數,是無效的,重新聲明時賦值,相當於重新賦值
  2. 變數提升:js的執行方式是,先解析程式碼,獲取所有被聲明的變數,然後再一行一行地運行,這樣就會導致所有變數的聲明語句,會被提升到程式碼的頭部,這叫做變數提升(hoisting)
  3. 標識符(identifier)指的是識別各種值的合法名稱。比如變數名、函數名。js標識符大小寫敏感。標識符的命名規則是:只能以字母、下劃線(_)和美元符號($)開頭,從第二個字元開始還可以使用數字。
  4. js標識符中的字母指的是Unicode字母。因此中文標識符也可以使用
  5. js中的保留字不能用於標識符,保留字是指js中用來表示特定含義的字元,如return、class、true、false、function等
  6. 注釋:注釋用來對程式碼進行解釋,js引擎執行時會忽略注釋部分。//表示單行注釋。/* */表示多行注釋
  7. js使用大括弧表示”區塊”(block)。對於var命令,js的區塊不構成單獨的作用域(scope)
  8. 條件語句:
  • ifif...else...結構,根據條件的布爾值判斷執行。

  • switch結構判斷表達式的值是否與case相符並執行,如果都不符則執行最後的defaultcasebreak不能少,否則當前case程式碼塊執行完會接著執行下一個case。switch語句部分和case語句部分,都可以使用表達式,這就是js中可以為值的地方,都可以使用表達式的體現,如下:

switch (1 + 3) {
  case 2 + 2:
    f();
    break;
  default:
    neverHappens();
}

switch語句的值和case語句的值,比較時採用的是嚴格相等運算符(===)

  • ?:三元運算符:如下,條件true,執行”表達式1″,否則執行”表達式2″,然後獲取對應返回值
(條件) ? 表達式1 : 表達式2
  1. 循環語句:重複執行某個操作
  • while 循環:循環條件和程式碼塊,條件為真,就循環執行程式碼塊

  • for 循環:可以指定循環的起點、終點和終止條件。分為初始化表達式(initialize)、條件表達式(test)、遞增表達式(increment)

for (初始化表達式; 條件; 遞增表達式) {
  語句
}

所有for循環,都可以改寫成while循環

for語句的無線循環表示:

for ( ; ; ){
  console.log('Hello World');
}
  • do...while 循環:先執行一次循環體,再判斷條件
  • break語句用於跳出程式碼塊或循環。continue語句用於立即終止本輪循環,並開始下一輪循環
  1. js語句的前面可以添加標籤(label),相當於定位符,用於跳轉到程式的任意位置
label:
  語句

數據類型

概述

  1. JavaScript的數據類型有六種(ES6新增了 Symbol 類型)
  • 數值(number):整數和小數(比如1和3.14)
  • 字元串(strin):文本(比如”Hello World”)。
  • 布爾值(boolean):表示真偽的兩個特殊值,即true(真)和false(假)
  • undefined:表示「未定義」或不存在,即由於目前沒有定義,所以此處暫時沒有任何值
  • null:表示空值,即此處的值為空。
  • 對象(object):各種值組成的集合。

數值、字元串、布爾值稱為原始類型(primitive type),是最基本的數據類型。對象稱為合成類型(complex type)。undefinednull兩個特殊值。

對象分為:狹義的對象(object)、數組(array)、函數(function)

  1. 類型判斷

typeof運算符返回一個值的數據類型:

  • 數值、字元串、布爾值分別返回numberstringboolean
  • 函數返回functionundefined返回undefined(可以檢測未聲明的變數),對象返回objectnull返回object
// 檢測未聲明
if (typeof v === "undefined") {
  // ...
}

typeof window // "object"
typeof {} // "object"
typeof [] // "object"

typeof null // "object"
  • instanceof可以區分數組和對象
[] instanceof Array // false
[] instanceof Array // true

null 和 undefined

  1. 兩者含義”類似”,if語句中自動轉為false,相等於運算符(==)兩者比較為true。null表示”空”對象,轉為數值是0undefined表示”無定義”的原始值,轉為數值是NaN
Number(null) // 0
Number(undefined) // NaN
  1. 函數沒有返回值時,默認返回 undefined
  2. 布爾值表示truefalse兩個真假狀態。
  3. 如果 JavaScript 預期某個位置應該是布爾值,則會將該位置上現有的值自動轉為布爾值。

下面六個值會被轉為false,其他的值都是true

  • undefined
  • null
  • false
  • 0
  • NaN
  • “”或”(空字元串)

空數組([])和空對象({})對應的布爾值,都是true

數值

  1. js中所有數字都是以64位浮點數存儲,整數也是如此。因此1===1.0,是同一個數
1 === 1.0 // true

JavaScript 語言的底層根本沒有整數,所有數字都是小數(64位浮點數)

  1. 浮點數不是精確的值,因此小數的比較和運算要特別注意
0.1 + 0.2 === 0.3   // false

0.3 / 0.1           // 2.9999999999999996

(0.3 - 0.2) === (0.2 - 0.1)   // false
  1. js浮點數的64個二進位位,從最左邊開始,由如下組成:
  • 第1位:符號位,0表示正數,1表示負數。數值正負
  • 第2位到第12位(共11位):指數部分。數值的大小
  • 第13位到第64位(共52位):小數部分(即有效數字)。數值的精度
  1. 絕對值小於2的53次方的整數,即$-253$到$253$,都可以精確表示
Math.pow(2, 53); // 9007199254740992

Math.pow(2, 53) + 1; // 9007199254740992

Math.pow(2, 53) + 2; // 9007199254740994

Math.pow(2, 53) + 3; // 9007199254740996

Math.pow(2, 53) + 4; // 9007199254740996
  1. 最大值和最小值:Number.MAX_VALUENumber.MIN_VALUE
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
  1. 數值表示法:50(十進位)、0xFF(十六進位)、123e3123e-3(科學計數法)
  2. 當小數點前面的數字多於21位時,或者小數點後的零多於5個時,js會自動將數值轉為科學計數法表示
  3. 使用字面量(literal)表示數值時:
  • 十進位:沒有前導0的數值
  • 八進位:有前綴0o0O的數值,或者有前導0、且只用到0-7的八個阿拉伯數字的數值。
  • 十六進位:有前綴0x0X的數值。
  • 二進位:有前綴0b0B的數值。

js會自動將八進位、十六進位、二進位轉為十進位

  1. js存在2個0:+0-0,兩者是等價的。唯一區別是+0-0當作分母時表達式的返回值不相等。
  2. NaN表示「非數字」(Not a Number),比如字元串解析為數字報錯時會返回NaN。0除以0得到NaN。NaN只是一個特殊值,類型依舊是Number
  3. NaN不等於任何值,包括它本身
NaN === NaN // false

NaN的布爾值為false,NaN與任何數(包括它自己)的運算,得到的都是NaN

  1. Infinity表示「無窮」,表示無法表示正無窮(Infinity)和負無窮(-Infinity);非0數除以0,得到Infinity
    Infinity大於一切數值(除了NaN),-Infinity小於一切數值(除了NaN)

NaN的比較運算會返回false

  1. parseInt()方法將字元串轉為整數。字元串轉為整數時,會一個個字元依次轉換,如果遇到不能轉為數字的字元,就不再進行下去,返回已經轉好的部分。轉換失敗返回NaN

解析科學計數法的數字時會出現奇怪的結果

第二個參數表示解析的值的進位

parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
  1. parseFloat():將一個字元串轉為浮點數,可以對科學計數法字元串正確轉換
  2. isNaN()判斷一個值是否是NaNisNaN()只對數值有效,其他類型值會先轉為數值,再判斷。對於空數組和只有一個數值成員的數組,isNaN()返回false
isNaN('Hello') // true
// 相當於
isNaN(Number('Hello')) // true
  1. 判斷NaN最好的方法是:NaN是唯一不等於自身的值
function myIsNaN(value) {
  return value !== value;
}

如果使用isNaN()判斷,要同時判斷類型:

function myIsNaN(value) {
  return typeof value === 'number' && isNaN(value);
}
  1. isFinite()判斷是否為正常的數值,判斷是參數也會進行類型轉換

字元串

  1. 字元串是放在單引號或雙引號中的零個或多個排在一起的字元。字元串中使用相同的引號要用\反斜杠轉義
  2. 每行的尾部使用反斜杠,可以實現多行字元串(\後面只能有換行符,否則報錯)
var longString = 'Long \
long \
long \
string';

longString // "Long long long string"

也可使用+可以將多個字元串列連接

  1. 反斜杠(\)用來表示一些特殊字元,稱為轉義。如\n換行符;\r:回車鍵;\t:製表符。如果在字元串中需要包含反斜杠,需要\轉義自身。
"你好,反斜杠\\";  // "你好,反斜杠\"
  1. 字元串可以看做字元數組,但僅能使用數組的方括弧運算符獲取字元,而不能進行其他操作
  2. length屬性返回字元串長度
'hello'.length  // 5
  1. js使用Unicode字符集。不僅以Unicode存儲字元,而且可以只用Unicode碼點,如’\u00A9’表示版權符號
var s = '\u00A9';
s // "©"

每個字元在 JavaScript 內部都是以16位(2個位元組)的 UTF-16 格式儲存。js單位字元長度固定為16位長度

js 對 UTF-16 的支援不完整,只支援兩位元組的字元,無法識別四位元組的字元。比如四位元組字元𝌆,瀏覽器可以正確識別是一個字元,但js無法識別,認為是兩個字元。需要特別注意

'𝌆'.length // 2
  1. Base64編碼:對於ASCII 碼0到31的符號無法列印出來,可以使用Base64編碼轉換為可列印的字元;以文本格式傳遞二進位數據,也可以使用Base64編碼
  2. Base64 是一種編碼方法,可以將任意值轉成 0~9、A~Z、a-z、+/這64個字元組成的可列印字元。目的是不出現特殊字元,簡化程式處理。
  • btoa():任意值轉為 Base64 編碼
  • atob()Base64 編碼轉為原來的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
  1. btoa()atob()的Base64編碼解碼不使用非ASCII碼的字元,如btoa('你好')就會報錯。處理辦法是加一個URI轉碼,如下
function b64Encode(str) {
  return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
  return decodeURIComponent(atob(str));
}

b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"

對象

  1. 對象是一組”鍵值對”(key-value)的集合,是一種無序的複合數據集合。

如下,對象用大括弧,鍵值對又叫成員,分為”鍵名”和”鍵值”,對應”成員的名稱”和”成員的值”,鍵名與鍵值用冒號分割,鍵值對逗號分割。

var obj = {
  foo: 'Hello',
  bar: 'World'
};
  1. 對象的所有鍵名都是字元串(ES6中Symbol值也可以作為鍵名)。鍵名要麼符合標識名的規則,要麼使用引號包含。鍵名又稱為”屬性”(property),屬性可以動態創建
  2. “鍵值”可以是任何數據類型,比如
    屬性值可以為函數,這個屬性也可以稱為”方法”,可以像函數一樣調用
    屬性的值還是對象,就可以形成鏈式引用
  3. 對象屬性之間逗號分割,最後一個屬性可以加逗號(trailing comma),也可不加
  4. 不同的變數名指向同一個對象,則它們都是這個對象的引用,即指向同一個記憶體地址。引用只局限於對象,原始類型中,兩個變數就是值的拷貝
  5. 對象採用大括弧表示,則就有可能和程式碼塊的大括弧產生歧義。比如行首是大括弧,如果看做表達式,則是一個對象;如果是語句,則表示一個程式碼區塊。
{ foo: 123 }

遇到大括弧的歧義時,無法確定是對象還是程式碼塊,JavaScript引擎一律解釋為程式碼塊。

可以將大括弧放入圓括弧中,這樣就是表達式

// eval 對字元串求值
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
  1. 使用點運算符或方括弧運算符讀取對象的屬性,同時也可以用來賦值。
    方括弧運算符的鍵名必須有引號,否則被看做變數
var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

數值鍵名不能使用點運算符,使用方括弧運算符時數值鍵名會轉換為字元串
8. Object.keys(obj)查看一個對象所有屬性或鍵名
9. delete用於刪除對象本身的屬性,成功後返回true。且刪除不存在的key也返回true。只有屬性存在且不能刪除時,才返回false。不能刪除繼承的屬性

delete obj.p // true
  1. in運算符檢查對象是否包含某個屬性。繼承時屬性也會返回true。
  2. hasOwnProperty(key)方法片段是否為對象自身的屬性
  3. for...in循環用於遍歷對象的全部屬性
  • 遍歷的是對象所有可遍歷(enumerable)的屬性,會跳過不可遍歷的屬性。
  • 不僅遍歷對象自身的屬性,還遍歷繼承的屬性。

通常都是遍歷對象自身的屬性,因此可以結合hasOwnProperty方法

var person = { name: '阮一峰老師' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

函數

  1. 函數是一段可以反覆調用的程式碼塊
  2. js中聲明函數的三種方法
  • function 命令——函數的聲明(Function Declaration)

如下,function 函數名(參數1,...){函數體}

function print(s) {
  console.log(s);
}
  • 函數表達式(Function Expression)

變數賦值的方式,這個匿名函數又稱函數表達式

var print = function(s) {
  console.log(s);
};

函數表達式需要在語句的結尾加上分號,表示語句結束。

函數表達式聲明函數時,function後面不帶有函數名。如果加上函數名也僅在函數體內部有效。

  • Function 構造函數

如下,使用Function構造函數時,可以傳遞任意數量的參數,只有最後一個參數會被當做函數體

var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等同於
function add(x, y) {
  return x + y;
}
  1. 函數的重複聲明:如果同一個函數被多次聲明,後面的就會覆蓋前面的聲明。
  2. 函數圓括弧在聲明時用於放入參數;非聲明時函數後緊跟圓括弧,表示調用函數並傳入參數。return語句表示返回後面表達式的值,並退出當前函數
  3. 函數調用自身,就是遞歸(recursion)
  4. js將函數看作一種值。凡是可以使用值的地方,就能使用函數。函數是一個可以執行的值。因為函數與其他數據類型地位平等,所有在js中又稱函數為第一等公民
  5. js將函數名視為變數名,所以同樣有函數名的提升,function聲明函數時會被提升到程式碼頭部。但是賦值語句定義函數,在賦值前調用會報錯(因為是undefined)
  6. 函數的屬性和方法
  • name屬性返回函數的名字
  • length屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數。
    length屬性提供了一種機制,判斷定義時和調用時參數的差異,以便實現面向對象編程的「方法重載」(overload)
  • toString()方法返回字元串,內容是函數的源碼。函數內的注釋也會返回。
    原生函數,toString()方法返回function (){[native code]}
  1. 函數作用域:作用域(scope)指變數存在的範圍。ES5中,JavaScript只有兩種作用域:全局作用域(變數在整個程式中一直存在);函數作用域(變數只在函數內部存在),函數作用域內同樣存在變數提升。ES6新增了塊級作用域
  2. 對於頂層函數(直接在js文件中或<script>標籤中),函數外部聲明的變數就是全局變數(global variable)。函數內部定義的變數,外部無法讀取,稱為”局部變數”(local variable),且函數內部定義的局部變數,在當前函數作用內會覆蓋同名全局變數
  3. 使用var聲明的變數只有在函數內才是局部變數,其他區塊內聲明仍是全局變數(比如ifwhile等塊內)
  4. 函數本身的作用域:函數本身也是一個值。它的作用域與變數一樣,是其聲明時所在的作用域,與其運行時所在的作用域無關函數執行時所在的作用域,是定義時的作用域,而不是調用時所在的作用域
var a = 1;
var x = function () {
  console.log(a); //聲明時所在作用域的變數a
};

function f() {
  var a = 2;
  x();
}
f() // 1

同樣,函數體內部聲明的函數,作用域綁定函數體內部。

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

如上,fon顳部聲明的函數bar,bar的作用域為foo函數內部,當在foo外部取出bar執行時,bar中使用的變數x指向的是foo內部(聲明時所在作用域)的x,而不是foo外部的x。這就是閉包現象

  1. 閉包(closure)就是能夠讀取其他函數內部變數的函數。而js中,只有函數內部的子函數才能讀取內部變數,因此閉包簡單理解就是”定義在一個函數內部的函數”。本質上,閉包用於將函數內部和函數外部連接起來。

理解閉包,要先理解變數的作用域,函數內部可以讀取全局變數

var e = "我是全局變數";

function f1() {
  console.log(e);
}
f1() // "我是全局變數"

但是,函數外部無法讀取函數內部聲明的變數。

function f1() {
  var n = "我是函數內部變數";
}

console.log(n)  // Uncaught ReferenceError: n is not defined

但是,正常無法得到函數內的局部變數,如果想要實現必須通過變通方法:在函數的內部,再定義一個函數。這樣函數內部的子函數就可以使用函數內的局部變數,但是函數內無法使用其子函數內的局部變數。這就是js特有的 “鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變數。

父對象的所有變數,對子對象都是可見的,反之則不成立。

這樣將子函數作為返回值,就可以在函數外部讀取它的內部變數

function f1() {
  var n = "我是函數內部變數";
  function f2() {
  console.log(n); // "我是函數內部變數"
  }
  return f2;
}

var result=f1();
result(); // "我是函數內部變數"

如上,獲取f1的返回值f2函數,而f2可以讀取f1的內部變數,這樣就可以在外部獲取f1內部的變數。閉包就是函數f2

閉包最大的特點,就是它可以”記住”誕生的環境

閉包的用處:

  • 讀取函數內部的變數之外
  • 讓變數始終保持在記憶體中(即閉包可以使得它的誕生環境一直存在)
function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5  // 閉包使得start變數一直在記憶體中
inc() // 6
inc() // 7

閉包使得函數內部環境一直存在,閉包可以看作是函數內部作用域的一個介面

  • 封裝對象的私有屬性和私有方法
function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('張三');
p1.setAge(25);
p1.getAge(); // 25
p1.name;     // "張三"

如上,函數Person的內部變數_age,通過閉包getAge和setAge,變成了返回對象p1的私有變數。

比如下面,就是閉包使用的典型例子。通常其在處理循環頁面dom元素並添加事件方法時需要特別注意

var funs=[];
for(var i=0;i<5;i++){
  funs[i]=function(){
    console.log(i);
  }
}
funs.forEach(function(f){ 
  f(); 
})
// 輸出 5個5,而不是0,1,2,3,4

利用閉包,可實現將變數i的每一個循環值”保存”,供調用時輸出

var funs=[];
for(var i=0;i<5;i++){
  (function(i){
    funs[i]=function(){
      console.log(i);
    }
  })(i);
}
funs.forEach(function(f){ 
  f(); 
})
// 輸出 0,1,2,3,4
  1. 不能濫用閉包,容易產生網頁性能問題。外層函數每次運行,都會生成一個新的閉包,而每個閉包都會保留外層函數的內部變數,記憶體消耗很大。
  2. 函數的參數,通過圓括弧傳遞外部數據。js在調用時允許省略參數(省略的參數變為undefined,但只能省略在後面的參數,靠前的參數不能省略)
  3. 參數傳遞方式:函數參數如果是原始類型的值(數值、字元串、布爾值),參數傳遞就是傳值傳遞(passes by value)。函數參數是複合類型的值(數組、對象、其他函數),傳遞方式是傳址傳遞(pass by reference),此時在函數內部修改參數,會影響原始值
  4. arguments對象包含了函數運行時的所有參數,arguments[0]是第一個參數,arguments[1]是第二個參數,以此類推,arguments只能在函數體內部使用。

argumentslength屬性可以判斷函數調用時的參數個數

arguments是對象,只是很像數組。如果想使用數組方法,需要將arguments轉為數組。下面是轉換為數組的兩種方法:

var args = Array.prototype.slice.call(arguments);

// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}
  1. 立即調用的函數表達式(IIFE):

圓括弧()運算符跟在函數名後面,表示調用該函數,如果在定義函數之後,立即調用該函數,如下當function出現在行首時,js會將其解釋為語句,後面是函數的定義,這是以圓括弧結尾就會報錯。

function(){ /* code */ }();

function出現在行首就是語句。

// 語句
function f() {}

// 表達式
var f = function f() {}

解決辦法就是不要讓function出現在行首,讓js引擎解釋為表達式。這樣就可以在定義函數之後立即運行

比如放圓括弧中:

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

這就是”立即調用的函數表達式”(Immediately-Invoked Function Expression,IIFE)。

IIFE最後的分號是必須的。尤其是兩個IIFE寫在一起時,如果省略分號,連著的兩個IIFE就出出現問題,可能會報錯

如下,兩行之間沒有分號,js將它們連在一起解釋,將第二行解釋為第一行的參數

// 報錯
(function(){ /* code */ }())
(function(){ /* code */ }())

任何讓解釋器以表達式來處理函數定義的方法,都能產生IIFE

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// 甚至
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

通常,只對匿名函數使用”立即執行的函數表達式”。目的:一是不必為函數命名,避免了污染全局變數;二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。

如下,寫法二完全避免了污染全局變數

// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);

// 寫法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

數組

  1. 數組(array)是按次序排列的一組值,位置編號從0開始,用方括弧表示。比如var arr = ['a', 'b', 'c'];
  2. 任何類型的數據都可以放入數組
var arr = [
  {a: 1},
  [1, 2, 3],
  function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}
  1. 數組本質是一種特殊的對象。typeof返回數組的類型是object

數組”對象”的鍵名,是按次序排列的整數。

var arr = ['a', 'b', 'c'];

Object.keys(arr); // ["0", "1", "2"]

js中,對象的鍵名一律為字元串,數組的鍵名也是字元串。對象中數值的鍵名不能使用點結構(object.key),所以數組元素不能使用.獲取

  1. 數組的length屬性,返回數組元素的個數,即數組長度

js使用一個32位整數,保存數組的元素個數。即數組長度最多只有 4294967295 個($2^32 – 1$)個

length屬性是可寫的。減少length值可用於刪除數組元素。length設置為0可用於清空數組。length屬性的值等於最大的數字鍵加1

var arr = [ 'a', 'b', 'c' ];
arr.length // 3

arr.length = 2;
arr // ["a", "b"]
  1. 不推薦使用for...in遍曆數組(會遍歷非數字鍵)。可使用forwhile循環,或forEach方法遍歷
var a = [1, 2, 3];

// for循環
for(var i = 0; i < a.length; i++) {
  console.log(a[i]);
}

// while循環
var i = 0;
while (i < a.length) {
  console.log(a[i]);
  i++;
}

var l = a.length;
while (l--) {
  console.log(a[l]);
}

// forEach方法
a.forEach(function (item) {
  console.log(item);
});
  1. 數組的某個位置是空元素,即兩個逗號之間沒有任何值,稱該數組存在空位(hole)
var a = [1, , 3 , 4];
a.length // 4

a[1] // undefined

delete a[2];
a[2] // undefined
a.length // 4

數組的空位不影響length屬性。數組最後一個元素後面有逗號,不會產生空位。空位返回undefineddelete命令刪除一個數組成員會形成空位,且不會影響length

  1. 類似數組的對象:如果一個對象的所有鍵名都是正整數或零,並且有length屬性,則這個對象語法上稱為”類似數組的對象”(array-like object)。

“類似數組的對象”並不是數組,它們不具備數組特有的方法。

如下,obj就是一個類似數組的對象

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

“類似數組的對象”的根本特徵,就是具有length屬性。只要有length屬性,就可以認為這個對象類似於數組。但這種length屬性不是動態值

典型的”類似數組的對象”是函數的arguments對象,以及大多數DOM元素集,還有字元串。

// arguments對象
function args() { return arguments }
var arrayLike = args('a', 'b');

arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false

// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false

// 字元串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false

數組的slice方法可以將”類似數組的對象”變成真正的數組。

var arr = Array.prototype.slice.call(arrayLike);

除了轉為真正的數組,還可以通過call()把數組的方法放到對象上面,從而讓”類似數組的對象”可以使用數組的方法。

Array.prototype.forEach.call(arrayLike, function (value, index) {
  console.log(index + ' : ' + value);
});

這種方法比直接使用數組原生的forEach要慢,所以可以先轉為真正的數組,在調用方法