JavaScript 是一種動態類型語言,也就是說,變量的類型沒有限制,變量可以隨時更改類型。
數據類型
JavaScript 語言的每一個值,都屬於某一種數據類型。JavaScript 的數據類型,共有六種。(ES6 又新增了第七種 Symbol 類型的值)
- 原始類型:數值(number):整數和小數(比如1和3.14)
- 原始類型:字符串(string):文本(比如Hello World)。
- 原始類型:布爾值(boolean):表示真僞的兩個特殊值,即true(真)和false(假)
- 合成類型:對象(object):各種值組成的集合
- undefined:表示“未定義”或不存在,即由於目前沒有定義,所以此處暫時沒有任何值
- null:表示空值,即此處的值爲空。
只是聲明變量而沒有賦值,則該變量的值是undefined。undefined是一個特殊的值,表示“無定義”。
變量提升
:JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然後再一行一行地運行。這造成的結果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)
函數
函數是處理數據的方法,但是JavaScript 語言將函數看作一種值, 把它當成一種數據類型,可以賦值給變量,與其它值(數值、字符串、布爾值等等)地位相同。凡是可以使用值的地方,就能使用函數。由於函數與其他數據類型地位平等,所以在 JavaScript 語言中又稱函數爲第一等公民。
JavaScript 引擎將函數名視同變量名,所以採用function命令聲明函數時,整個函數會像變量聲明一樣,被提升到代碼頭部。
function命令聲明的代碼區塊,就是一個函數
function f1() {}
f1.name // "f1"
var f2 = function () {};
f2.name // "f2"
var f3 = function myName() {};
f3.name // 'myName'
作用域
JavaScript 只有兩種作用域:一種是全局作用域,變量在整個程序中一直存在,所有地方都可以讀取;另一種是函數作用域,變量只在函數內部存在。
ES6 又新增了塊級作用域,
- 全局變量(global variable) 函數外部聲明的變量,它可以在函數內部讀取。
- 局部變量”(local variable) 在函數內部定義的變量,外部無法讀取, 會在該作用域內覆蓋同名全局變量
閉包
閉包(closure) 是 JavaScript 語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。
函數內部聲明的變量,函數外是無法讀取, 只有通過變通方法才能實現。那就是在函數的內部,再定義一個函數
閉包的最大用處有兩個,
- 可以讀取函數內部的變量,封裝對象的私有屬性和私有方法
- 讓這些變量始終保持在內存中,即閉包可以使得它誕生環境一直存在。
注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。因此不能濫用閉包,否則會造成網頁的性能問題。
function f1() {
var n = 999;
function f2() {
console.log("函數f2就是閉包,可以讀取變量n:" + n );
}
return f2;
}
立即調用的函數表達式(IIFE)
圓括號()是一種運算符,跟在函數名之後,表示調用該函數
但是想在定義函數之後立即調用該函數。在函數的定義之後加上圓括號會報錯,原因是會出現歧義:function這個關鍵字即可以當作語句,也可以當作表達式
而JavaScript 引擎規定,如果function關鍵字出現在行首,一律解釋成語句。因此,JavaScript 引擎看到行首是function關鍵字之後,認爲這一段都是函數的定義,不應該以圓括號結尾,所以就報錯了。
解決方法就是不要讓function出現在行首,讓引擎將其理解成一個表達式。最簡單的處理,就是將其放在一個圓括號裏面
// 語句
function f() {}
// 表達式
var f = function f() {}
//錯誤寫法
function(){ /* code */ }();// SyntaxError: Unexpected token (
//IIFE正確寫法
(function(){ /* code */ }());
(function(){ /* code */ })();
//避免污染全局變量
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
通常情況下,只對匿名函數使用這種“立即執行的函數表達式”。它的目的有兩個:
- 一是不必爲函數命名,避免了污染全局變量;
- 二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量。
對象(object)
對象是 JavaScript 語言的核心概念,也是最重要的數據類型。
對象就是一組“鍵值對”(key-value)的集合,
對象的每一個鍵名又稱爲“屬性”(property),
如果一個屬性的值爲函數,通常把這個屬性稱爲“方法”,它可以像函數那樣調用。
var obj = {
//obj對象的屬性foo
'foo': 'Hello',
//obj對象的方法p
p: function (x) {
return 2 * x;
}
};
對象採用大括號表示, JavaScript 引擎會產生歧義: 是對象還是代碼塊
爲了避免這種歧義,JavaScript 引擎的做法是,如果遇到這種情況,無法確定是對象還是代碼塊,一律解釋爲代碼塊。
如果要解釋爲對象,最好在大括號前加上圓括號。因爲圓括號的裏面只能是表達式
{ foo: 123 } // 代碼塊 標籤foo指向表達式123
({ foo: 123 }) // 對象 一個包含foo屬性的對象
嚴格模式
早期的 JavaScript 語言有很多設計不合理的地方,但是爲了兼容以前的代碼,又不能改變老的語法,只能不斷添加新的語法,引導程序員使用新語法。
嚴格模式是從 ES5 進入標準的,主要目的有以下幾個。
- 明確禁止一些不合理、不嚴謹的語法,減少 JavaScript 語言的一些怪異行爲。
- 增加更多報錯的場合,消除代碼運行的一些不安全之處,保證代碼運行的安全。
- 提高編譯器效率,增加運行速度。
- 爲未來新版本的JavaScript 語法做好鋪墊。
//老版本的引擎會把它當作一行普通字符串,加以忽略。新版本的引擎就會進入嚴格模式。
'use strict';
//嚴格模式可以用於整個腳本,也可以只用於單個函數。
- 顯式報錯
- 只讀屬性不可寫
設置字符串的length屬性,會報錯 - 只有取值器(getter)、沒有存值器(setter)的屬性賦值,會報錯
- 禁止擴展的對象不可擴展
- eval、arguments 不可用作標識名
- 函數不能有重名的參數
- 禁止八進制的前綴0表示法
- 只讀屬性不可寫
- 增強的安全措施
- 全局變量顯式聲明
- 禁止 this 關鍵字指向全局對象
- 函數內部不得使用fn.caller、fn.arguments,否則會報錯。這意味着不能在函數內部得到調用棧了
- 函數內部使用arguments.callee、arguments.caller將會報錯。
- 禁止刪除變量
- 靜態綁定
某些情況下,只允許靜態綁定。也就是說,屬性和方法到底歸屬哪個對象,必須在編譯階段就確定。- 禁止使用 with 語句
- 創設 eval 作用域
- arguments 不再追蹤參數的變化
- 向下一個版本的 JavaScript 過渡
- 非函數代碼塊不得聲明函數
- 嚴格模式新增了一些保留字(implements、interface、let、package、private、protected、public、static、yield等)