1.1 什麼是型別
原始型別 儲存為簡單資料值。 引用型別 儲存為物件,其本質是指向記憶體位置的引用。
為了讓開發者能夠把原始型別和引用型別按相同的方式處理,JavaScript 花費了很大的努力來保證語言的一致性。
其他程式語言用棧存原始型別,用對儲存引用型別。
而 JavaScript 則完全不同:它使用一個變數物件追蹤變數的生存期。
原始值被直接儲存在變數物件內,而引用值則作為一個指標儲存在變數物件內,該指標指向實際物件在記憶體中的儲存位置。
1.2 原始型別
原始型別代表照原樣儲存的一些簡單資料。
JavaScript 共有5種原始型別:
- boolean 布林,值為
true
orfalse
- number 數字,值為任何整型或浮點數值
- string 字串,值為由單引號或雙引號括住的單個字元或連續字元
- null 空型別,僅有一個值:null
- undefined 未定義,只有一個值:undefined(undefined 會被賦給一個還沒有初始化的變數)
JavaScript 和許多其他語言一樣,原始型別的變數直接儲存原始值(而不是一個指向物件的指標)。
var color1 = "red";
var color2 = color1;
console.log(color1); // "red"
console.log(color2); // "red"
color1 = "blue";
console.log(color1); // "blue"
console.log(color2); // "red"
複製程式碼
鑑別原始型別
鑑別原始型別的最佳方式是使用 typeof
操作符。
console.log(typeof "Nicholas"); // "string"
console.log(typeof 10); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
複製程式碼
至於空型別(null)則有些棘手。
console.log(typeof null); // "object"
複製程式碼
對於 typeof null,結果是"object"。(其實這已被設計和維護 JavaScript 的委員會 TC39 認定是一個錯誤。在邏輯上,你可以認為 null
是一個空的物件指標,所以結果為"object",但這還是很令人困惑。)
判斷一個值是否為空型別(null)的最佳方式是直接和 null
比較:
console.log(value === null); // true or false
複製程式碼
注意:以上這段程式碼使用了三等號(全等 ===),因為三等號(全等)不會將變數強制轉換為另一種型別。
console.log("5" == 5); // true
console.log("5" === 5); // false
console.log(undefined == null); // true
console.log(undefined === null); // false
複製程式碼
原始方法
雖然字串、數字和布林值是原始型別,但是它們也擁有方法(null 和 undefined 沒有方法)。
var name = "Nicholas";
var lowercaseName = name.toLowerCase(); // 轉為小寫
var count = 10;
var fixedCount = count.toFixed(2); // 轉為10.00
var flag = true;
var stringFlag = flag.toString(); // 轉為"true"
console.log("YIBU".charAt(0)); // 輸出"Y"
複製程式碼
儘管原始型別擁有方法,但它們不是物件。JavaScript 使它們看上去像物件一樣,以此來提高語言上的一致性體驗。
1.3 引用型別
引用型別是指 JavaScript 中的物件,同時也是你在該語言中能找到最接近類的東西。
引用值是引用型別的例項,也是物件的同義詞(後面將用物件指代引用值)。
物件是屬性的無序列表。屬性包含鍵(始終是字串)和值。如果一個屬性的值是函式,它就被稱為方法。
除了函式可以執行以外,一個包含陣列的屬性和一個包含函式的屬性沒有什麼區別。
建立物件
有時候,把 JavaScript 物件想象成雜湊表可以幫助你更好地理解物件結構。
JavaScript 有好幾種方法可以建立物件,或者說例項化物件。第一種是使用 new
操作符和建構函式。
建構函式就是通過 new
操作符來建立物件的函式——任何函式都可以是建構函式。根據命名規範,JavaScript 中的建構函式用首字母大寫來跟非建構函式進行區分。
var object = new Object();
複製程式碼
因為引用型別不再變數中直接儲存物件,所以本例中的 object
變數實際上並不包含物件的例項,而是一個指向記憶體中實際物件所在位置的指標(或者說引用)。這是物件和原始值之間的一個基本差別,原始值是直接儲存在變數中。
當你將一個物件賦值給變數時,實際是賦值給這個變數一個指標。這意味著,將一個變數賦值給另外一個變數時,兩個變數各獲得了一份指標的拷貝,指向記憶體中的同一個物件。
var obj1 = new Object();
var obj2 = obj1;
複製程式碼
物件引用解除
JavaScript 語言有垃圾收集的功能,因此當你使用引用型別時無需擔心記憶體分配。但最好在不使用物件時將其引用解除,讓垃圾收集器對那塊記憶體進行釋放。解除引用的最佳手段是將物件變數設定為 null
。
var obj1 = new Object();
// dosomething
obj1 = null; // dereference
複製程式碼
新增刪除屬性
在 JavaScript 中,你可以隨時新增和刪除其屬性。
var obj1 = new Object();
var obj2 = obj1;
obj1.myCustomProperty = "Awsome!";
console.log(obj2.myCustomProperty); // "Awsome!" 因為 obj1 和 obj2 指向同一個物件。
複製程式碼
1.4 內建型別例項化
內建型別如下:
- Array 陣列型別,以數字為索引的一組值的有序列表
- Date 日期和時間型別
- Error 執行期錯誤型別
- Function 函式型別
- Object 通用物件型別
- RegExp 正規表示式型別
可使用 new
來例項化每一個內建引用型別:
var items = new Array();
var now = new Date();
var error = new Error("Something bad happened.");
var func = new Function("console.log('HI');");
var object = new Object();
var re = new RegExp();
複製程式碼
字面形式
內建引用型別有字面形式。字面形式允許你在不需要使用 new
操作符和建構函式顯示建立物件的情況下生成引用值。屬性的鍵可以是識別符號或字串(若含有空格或其他特殊字元)
var book = {
name: "Book_name",
year: 2016
}
複製程式碼
上面程式碼與下面這段程式碼等價:
var book = new Object();
book.name = "Book_name";
book.year = 2016;
複製程式碼
雖然使用字面形式並沒有呼叫 new Object(),但是 JavaScript 引擎背後做的工作和 new Object() 一樣,除了沒有呼叫建構函式。其他引用型別的字面形式也是如此。
1.5 訪問屬性
可通過 .
和 中括號
訪問物件的屬性。
中括號 []
在需要動態決定訪問哪個屬性時,特別有用。因為你可以用變數而不是字串字面形式來指定訪問的屬性。
1.6 鑑別引用型別
函式是最容易鑑別的引用型別,因為對函式使用 typeof
操作符時,返回"function"。
function reflect(value){
return value;
}
console.log(typeof reflect); // "function"
複製程式碼
對其他引用型別的鑑別則較為棘手,因為對於所有非函式的引用型別,typeof
返回 object
。為了更方便地鑑別引用型別,可以使用 JavaScript 的 instanceof
操作符。
var items = [];
var obj = {};
function reflect(value){
return value;
}
console.log(items instanceof Array); // true;
console.log(obj instanceof Object); // true;
console.log(reflect instanceof Function); // true;
複製程式碼
instanceof
操作符可鑑別繼承型別。這意味著所有物件都是 Oject
的例項,因為所有引用型別都繼承自 Object
。
雖然 instanceof 可以鑑別物件型別(如陣列),但是有一個列外。JavaScript 的值可以在同一個網頁的不用框架之間傳來傳去。由於每個網頁擁有它自己的全域性上下文—— Object、Array 以及其他內建型別的版本。所以當你把一個物件(如陣列)從一個框架傳到另外一個框架時,instanceof 就無法識別它。
1.8 原始封裝型別
原始封裝型別有 3
種:String、Number 和 Boolean。
當讀取字串、數字或布林值時,原始封裝型別將被自動建立。
var name = "Nicholas";
var firstChar = name.charAt(0); // "N"
複製程式碼
這在背後發生的事情如下:
var name = "Nichola";
var temp = new String(name);
var firstChar = temp.charAt(0);
temp = null;
複製程式碼
由於第二行把字串當成物件使用,JavaScript 引擎建立了一個字串的實體讓 charAt(0)
可以工作。字串物件的存在僅用於該語句並在隨後銷燬(一種被稱為自動打包的過程)。為了測試這一點,試著給字串新增一個屬性看看它是不是物件。
var name = "Nicholas";
name.last = "Zakas";
console.log(name.last); // undefined;
複製程式碼
下面是在 JavaScript 引擎中實際發生的事情:
var name = "Nicholas";
var temp = new String(name);
temp.last = "Zakas";
temp = null; // temporary object destroyed
var temp = new String(name);
console.log(temp.last);
temp = null;
複製程式碼
新屬性 last
實際上是在一個立刻就被銷燬的臨時物件上而不是字串上新增。之後當你試圖訪問該屬性時,另一個不同的臨時物件被建立,而新屬性並不存在。
雖然原始封裝型別會被自動建立,在這些值上進行 instanceof
檢查對應型別的返回值卻是 false
。
這是因為臨時物件僅在值被讀取時建立。instanceof
操作符並沒有真的讀取任何東西,也就沒有臨時物件的建立。
當然你也可以手動建立原始封裝型別。
var str = new String("me");
str.age = 18;
console.log(typeof str); // object
console.log(str.age); // 18
複製程式碼
如你所見,手動建立原始封裝型別實際會建立出一個 object
。這意味著 typeof
無法鑑別出你實際儲存的資料的型別。
另外,手動建立原始封裝型別和使用原始值是有一定區別的。所以儘量避免使用。
var found = new Boolean(false);
if(found){
console.log("Found"); // 執行到了,儘管物件的值為 false
}
複製程式碼
這是因為一個物件(如 {}
)在條件判斷語句中總被認為是 true
;
1.9 總結
第一章的東西都是我們一些比較熟悉的知識。但是也有一些需要注意的地方:
- 正確區分原始型別和引用型別
- 對於
5
種原始型別都可以用 typeof 來鑑別,而空型別必須直接跟null
進行全等比較。 - 函式也是物件,可用
typeof
鑑別。其它引用型別,可用instanceof
和一個建構函式來鑑別。(當然可以用Object.prototype.toString.call()
鑑別,它會返回 [object Array]之類的)。 - 為了讓原始型別看上去更像引用型別,JavaScript提供了
3
種封裝型別。JavaScript會在背後建立這些物件使得你能夠像使用普通物件那樣使用原始值。但這些臨時物件在使用它們的語句結束時就立刻被銷燬。雖然可手動建立,但不建議。