與某些語言把原始類型存儲在棧中,把引用類型存儲在堆中不一樣,JavaScript使用一個變量對象來追蹤變量的生存週期:原始值直接保存在這個對象內,引用類型的指針(指向這個引用類型在內存中的地址)被保存在這個對象內。數組
因爲上面的緣由,當把一個引用類型賦值給一個變量時,只是把它的指針賦值給了這個變量。框架
解除對一個對象的引用的最好的方法是將對象變量賦值爲null
,這樣垃圾收集機制就能更好的處理無用的垃圾了。例如:函數
let pointer = {}; // 建立一個引用了對象的變量 pointer, 稱爲對象變量
// do something with pointer
pointer = null; // 將對象變量賦值爲 null, 它引用的對象會被垃圾收集,那塊內存就能空出來
複製代碼
上面的代碼用一個pointer
變量引用了一個對象,在將pointer
變量設置爲 null
以後,垃圾收集器就能更好的處理那個已經沒有了引用的對象。ui
對於全部的引用類型,使用字面量形式建立的對象並無調用構造函數,可是JavaScript引擎在背後作的工做和調用了構造函數時同樣。例如對於一個普通對象:spa
// 使用對象字面量的方式建立一個對象
let person = {
name: '王大錘',
age: 30
};
/* 等價於下面使用構造函數建立的對象 */
// 使用構造函數建立一個對象
let person = new Object();
person.name = '王大錘';
person.age = 30;
複製代碼
又例如對於一個數組:指針
let arr = [1, 2, 3]; // 使用字面量形式建立一個數組
/* 等價於下面使用構造函數的形式建立數組 */
let arr = new Array(1, 2, 3);
複製代碼
使用Array.isArray
方法鑑定一個變量是否是數組,使用instanceof
操做符也能夠判斷一個變量的值是否是數組的實例,可是若是變量在同一個網頁的不一樣框架之間傳遞,因爲每一個框架都有本身的環境,因此後者可能沒法獲得準確的結果,可是前者始終能獲得正確的結果。code
let arr = [1, 2, 3];
console.log(Array.isArray(arr), arr instanceof Array); // true true
複製代碼
對原始類型的值使用instanceof
操做符判斷其對應的類型,總會返回false
,這是由於原始類型雖然有打包操做,可是在使用instanceof
進行判斷時,打包操做就已經結束了,此時打包出來的臨時對象已經被銷燬,因此結果爲false
對象
let name = 'Jack Ma';
let age = 40;
let flag = false;
console.log(name instanceof String); // false
console.log(age instanceof Number); // false
console.log(flag instanceof Boolean); // false
複製代碼
函數存在一個被稱爲[[call]]
的內部屬性,內部屬性沒法經過代碼訪問,這裏使用雙中括號來標註此類屬性名。[[call]]
屬性是函數的獨有屬性,並且typeof
操做符對具備[[call]]
屬性的對象返回function
,這就是使用typeof
判斷函數類型的原理。排序
函數聲明能夠被提高是由於引擎提早知道了函數的名字;函數表達式是使用匿名函數定義的,變量名只是引用了這個匿名函數,雖然能夠經過變量對函數進行調用,但變量名並非函數的名字,因此沒法進行提高。ip
sort
方法在排序的時候會將對象轉換成字符串而後再比較,因此在不指定比較函數的時候不能對純數字數組進行準確排序。
函數也有length
屬性,表示函數指望的參數個數,也就是函數聲明的形參個數。
函數和方法的區別:其實這兩個名稱指的都是函數,只是當一個函數是一個對象的屬性時,相對於這個對象,函數就被稱爲了方法。
當屬性***第一次***被添加給對象時,JavaScript會調用對象名爲[[put]]
的內部方法來建立這個屬性,並賦值。
當屬性被添加給對象以後,再改變屬性的值時,不會再調用再調用[[put]]
方法了,這時會調用[[set]]
這個內部屬性。
let obj = {}; //定義一個對象,這個對象沒有任何用戶本身建立的屬性
obj.name = 'Jack M'; // 調用了 [[put]] 方法,由於 obj 原本沒有 name 屬性,這裏給它添加了 name 屬性
obj.name = 'Mask'; // 調用了 [[get]] 方法,由於這時已經存在了name屬性,這裏只是從新給屬性賦值
複製代碼
若是想刪除對象的某個屬性,應使用delete
操做符。注意:直接將屬性設置爲null
是沒法刪除這個屬性的,這樣只是給屬性賦了一個新值爲null
:
// 建立一個帶有 name 屬性的對象
let obj = {
name: 'Yuri'
};
// 將 name 屬性值設爲 null,沒法刪除這個屬性,只是給它賦值爲 null, 即 obj.name === null
obj.name = null;
console.log(obj.name === null); // true
// 使用 delete 關鍵詞能夠真正刪除屬性
delete obj.name;
console.log(obj.name); // undefined
console.log('name' in obj); // false
複製代碼
想遍歷對象的屬性時,能夠用兩種方法:
for ... in ...
:迭代對象的可枚舉的***屬性名***,可枚舉屬性是指[[Enumerable]]
值爲 true
的屬性Object.keys(object)
:這個方法返回對象的全部可枚舉屬性的屬性名組成的數組。// 建立一個帶有兩個自有屬性的對象
let obj = {
name: 'Yuri',
age: 40,
speak: 'You will obey ...'
};
for(let propertyName in obj){ // 每次都會將 對象的屬性名賦值給 propertyName
console.log(propertyName);
}
// name
// age
// speak
let allProperties = Object.keys(obj); // 得到 obj 對象的全部可枚舉屬性的屬性名
console.log(allProperties); // ["name", "age", "speak"]
複製代碼
注意:這兩個方法取得的可枚舉屬性是有差異的,for in
方法會遍歷對象的原型鏈,而Object.keys()
方法只會涉及到對象自己的屬性,不會訪問原型鏈。
對象的大部分自帶的屬性的[[Enumerable]]
的值都是false
,即不可遍歷,用實例對象的propertyIsEnumerable
方法能夠判斷一個屬性是否是可枚舉的:
console.log(obj.propertyIsEnumerable('name')); // true
console.log(obj.propertyIsEnumerable('age')); // true
console.log(obj.propertyIsEnumerable('speak')); // true
複製代碼
使用構造函數建立對象時,若是不須要傳遞參數時,能夠不加小括號:
// 定義一個構造函數
function Person(name){
/* xxx */
}
let p1 = new Person();
let p2 = new Person; // 不加小括號
console.log(p1 instanceof Person, p2 instanceof Person); // true true
複製代碼