上一篇講了 JavaScript 中的類型和值,本篇主要關於 類型轉換、變量。數組
須要注意包裝對象、引用類型變動、對象到原始值的類型轉換規則等。函數
運算符相關的隱式類型轉換請查看:post
犀牛書這裏提到了一個 包裝對象 的概念(見 3.6 ),使用包裝對象的概念解釋了 爲何字符串、數字等原始值也能夠擁有各自的屬性。學習
當 JavaScript 要讀取字符串 s 的屬性時,將會經過調用 new String(s) 來 將字符串轉換爲對象(也至關於調用 Object(s) ,對原始值使用 . 操做符讀取屬性會將原始值進行隱式類型轉換,轉換爲對象),而後引用這個對象的屬性,而使用結束後這個對象就會被 銷燬(實現不必定要建立和銷燬,可是能夠用來類比這個過程)。測試
經過包裝對象的概念能夠很容易理解字符串和數字等爲何有屬性,爲何沒法給它們的屬性賦值。ui
var s = 'string';
// 0
console.log(s.indexOf('s'));
// true
console.log(s.__proto__ === String.prototype);
複製代碼
上述代碼能夠看作爲this
var s = 'string';
console.log(new String(s).indexOf('s'));
複製代碼
var s = 'string';
// 可想象爲賦值在包裝對象上,然而這段語句執行完成包裝對象就被銷燬了,因此 s 上找不到 test
s.test = 'test';
// undefined
console.log(s.test);
複製代碼
原始值都是不可變動的,當爲原始值的變量從新賦值時,修改的是新的值。spa
能夠理解爲每一個原始值都是一個獨立的地址,把一個原始值賦值給一個變量會在內存中從新開闢一個空間賦值原始值並把新的地址賦值給這個變量。prototype
var a = 1; // 開闢棧空間 0001 -> 把空間地址賦給變量 a -> 存值 1 到 0001
var b = a; // 開闢棧空間 0002 -> 把空間地址賦給變量 b -> 複製 a 的值 1 -> 0002
// 1 1
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值 得出 1 1
b = 2; // 取出 b 的地址 0002 -> 存值 2 到 0002
// 2 1
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值 得出 2 1
複製代碼
對象類型是可變的,將一個對象類型賦值給一個變量時,實際上 傳遞的是它的引用(指針)。指針
var a = {}; // 開闢棧空間 0001 -> 將空間地址賦給變量 a -> 開闢堆空間存儲 X0001 -> 存值 {} -> 存儲 X0001 地址到 0001
var b = a; // 開闢棧空間 0002 -> 將空間地址賦給變量 b -> 賦值 0001 中的地址,也就是 X0001 到 0002
// {} {}
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值,查出是堆地址時會去堆中取值,得出 {} {}
b.c = 2; // 讀取 b 從堆中取值,修改堆中值爲 {c: 2}
// {c:2} {c:2}
console.log(b, a); // b 和 a 分別去 0002 和 0001 查詢存儲值,查處是堆地址時會去堆中取值,得出 {c: 2} {c: 2}
複製代碼
== 運算符在判斷時會進行 類型轉換,=== 運算符 不會作任何轉換。
一般可使用全局函數 Object、Number、Boolean、String 來進行顯式類型轉換。
然而咱們也能夠 藉助隱式類型轉換作顯示類型轉換。(隱式類型轉換見上一篇)
console.log(+'0'); // 0 經過 + 運算符的隱式轉換將字符串顯式轉換爲數字
複製代碼
注意,使用 Object 對 null、undefined 進行顯示類型轉換時,能夠正常輸出一個空對象而不報錯,至關於沒傳入值。可是當 JavaScript 嘗試對它們轉換爲對象類型時,將會拋出類型錯誤(好比 null.toString 至關於將 null 轉爲對象而後調用他的 toString,然而這將會拋出類型錯誤)。
對象到原始值的轉換相對而言比較複雜。
全部的對象轉換爲布爾型 都爲 true。
對象轉換爲字符串時,將會通過幾個步驟:
使用代碼測試一下
const a = {
toString: function() {
console.log('toString is called');
return null;
}
};
console.log(String(a));
// 'toString is called'
// 'null'
const b = {
toString: function() {
console.log('toString is called');
return {};
},
valueOf: function() {
console.log('valueOf is called');
return false;
}
};
console.log(String(b));
// 'toString is called'
// 'valueOf is called'
// 'false'
const c = {
toString: function() {
console.log('toString is called');
return {};
},
valueOf: function() {
console.log('valueOf is called');
return {};
}
};
console.log(String(c));
// 'toString is called'
// 'valueOf is called'
// TypeError: Cannot convert object to primitive value
複製代碼
使用代碼模擬一下
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Str = obj => {
let primitiveValue;
if ('toString' in obj) {
primitiveValue = obj.toString();
} else if(!('valueOf' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('valueOf' in obj && (!('toString' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.valueOf();
}
if (isPrimitive(primitiveValue)) {
return String(primitiveValue);
} else {
throw new TypeError("Can't convert to primitive");
}
};
複製代碼
能夠拿上面的測試代碼驗證一下是否是同樣的輸出
對象轉換爲數字時,將會通過幾個步驟:(相似字符串的轉換,可是有所差異)。
一樣使用代碼測試一下
const a = {
valueOf: function() {
console.log('valueOf is called');
return null;
}
};
console.log(Number(a));
// 'valueOf is called'
// 0
const b = {
valueOf: function() {
console.log('valueOf is called');
return {};
},
toString: function() {
console.log('toString is called');
return true;
}
};
console.log(Number(b));
// 'valueOf is called'
// 'toString is called'
// 1
const c = {
valueOf: function() {
console.log('valueOf is called');
return {};
},
toString: function() {
console.log('toString is called');
return {};
}
};
console.log(Number(c));
// 'valueOf is called'
// 'toString is called'
// TypeError: Cannot convert object to primitive value
複製代碼
一樣使用代碼模擬一下
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Num = obj => {
let primitiveValue;
if ('valueOf' in obj) {
primitiveValue = obj.valueOf();
} else if(!('toString' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.toString();
}
if (isPrimitive(primitiveValue)) {
return Number(primitiveValue);
} else {
throw new TypeError("Can't convert to primitive");
}
};
複製代碼
在使用 +、==、關係運算符(>、< 等)操做對象時,將會將對象轉換爲原始值,對於 非日期對象,轉換套用上述的數字的轉換方式(先 valueOf 後 toString),而對於 日期對象,轉換套用上述的字符串的轉換方式(先 toString 後 valueOf),可是 不會執行最後的變爲數字或字符串的類型轉換部分。
使用代碼模擬一下非日期對象的轉換
const isPrimitive = v => v == null || v === true || v === false || typeof v === 'number' || typeof v === 'string';
const obj2Primitive = obj => {
let primitiveValue;
if ('valueOf' in obj) {
primitiveValue = obj.valueOf();
} else if(!('toString' in obj)) {
throw new TypeError("Can't find toString and valueOf");
}
if ('toString' in obj && (!('valueOf' in obj) || !isPrimitive(primitiveValue))) {
primitiveValue = obj.toString();
}
if (isPrimitive(primitiveValue)) {
return primitiveValue;
} else {
throw new TypeError("Can't convert to primitive");
}
};
複製代碼
+、== 會在進行上述轉換後再次根據操做數進行二次判斷,並再次將獲得的原始值進行轉換。
null 和 undefined 在進行 == 運算時將不會進行類型轉換,JavaScript 將會直接判斷另外一個操做數是否爲 null 或者 undefined 而後直接返回結果
剩餘的其它操做符轉換類型比較明確,如 - 會將兩個操做數都轉換爲數字(套用到轉換爲數字的轉換規則)
使用 var 對變量進行 重複聲明是無害的,由於全部的變量聲明都會被提高到代碼執行的頂端(聲明實質上會在代碼編譯時執行),因此聲明 1 ~ n 次的效果都是同樣的。
函數在 定義時 就會綁定一個做用域鏈(詞法做用域、靜態做用域相關),如:函數定義區域的做用域 -> 上層函數定義區域的做用域 -> ... -> 全局做用域。
函數在執行時做用域鏈的頂端將會加入它自身的函數做用域(參數、局部變量)。變量的查找將會隨着做用域鏈一直查找直到找到或者到全局做用域仍未找到報錯爲止。
以前有道題目是什麼狀況下 a == 1 && a == 2 爲 true,看到上面的對象到原始值的轉換,應該就能理解了,其實很簡單,藉助 == 操做符的隱式類型轉換就能夠作到。
const a = {
value: 1,
valueOf: function() {
return this.value++;
}
};
// true
console.log(a == 1 && a == 2);
複製代碼
注意對象到原始值的轉換指的是沒有對對象的 toString 和 valueOf 作過變動時的結果。
認真看了上面的轉換規則的確定能看出來,爲啥 [] 轉換爲數字會變成 0 呢,由於先調用 valueOf 然而數組默認的 valueOf 就是它自身,不是原始值,因此只能調用 toString,獲得空字符串,而後再轉成數字,就變成 0 了。
值 | 字符串 | 數字 | 布爾 | 對象 |
---|---|---|---|---|
undefined | 'undefined' | NaN | false | throws TypeError |
null | 'null' | 0 | false | throws TypeError |
true | 'true' | 1 | new Boolean(true) | |
false | 'false' | 0 | new Boolean(false) | |
'' | 0 | false | new String('') | |
' ' | 0 | true | new String(' ') | |
'1.2' | 1.2 | true | new String('1.2') | |
'one' | NaN | true | new String('one') | |
0 | '0' | false | new Number(0) | |
-0 | '0' | false | new Number(-0) | |
NaN | 'NaN' | false | new Number(NaN) | |
Infinity | 'Infinity' | true | new Number(Infinity) | |
-Infinity | '-Infinity' | true | new Number(-Infinity) | |
1.2 | '1.2' | true | new Number(1.2) | |
{} | '[object Object]' | NaN | true | |
[] | '' | 0 | true |