類型 七種類型: Undefined Null Boolean String Number Symbol Object Undefined、Null 閱讀問題:為什麼有的編程規範要求使用void 0 代替undefined? Undefined類型表示未定義,它的類型只有一個值即undefined, ...
類型
七種類型:
- Undefined
- Null
- Boolean
- String
- Number
- Symbol
- Object
Undefined、Null
閱讀問題:為什麼有的編程規範要求使用void 0 代替undefined?
Undefined類型表示未定義,它的類型只有一個值即undefined,任何變數在賦值前都是Undefined類型,它的值是undefined。由於JS中的undefined是一個變數,而並非是一個關鍵字,這是JavaScript語言公認的設計失誤之一。因此,為了避免無意中被篡改,一般用void 0來獲取undefined的值。
Null類型也只有一個值為null,null是JavaScript的關鍵字,可以使用null關鍵字來獲取null值。
關於void運算符,有兩種寫法:
- 1.void expression
- 2.void (expression)
void運算符的操作數可以是任意類型。該運算符指定要計算一個表達式但是不論該表達式原來是否有自己的返回值,其返回值都為undefined。其作用如下:
作用一:返回undefined,(對於為什麼不直接使用undefined,是因為undefined不是關鍵字,意味著它隨時可能被篡改成其他值。。。)。
作用二:防止不必要的行為,如下代碼:
<a href='javascript:void(0)'>阻止預設行為跳轉</a>
例子:
function func(){ return this; } console.log(void func()); //undefined
從上所述就能夠說明為什麼有的編程規範要求用void 0代替undefined。
String
問題:字元串是否有最大長度?
首先,String有最大長度為2^53-1,但這個所謂的最大長度並不完全是你所理解中的字元數。因為String的意義,並非“字元串“,而是字元串的UTF16編碼。我們字元串的方法charAt、charCodeAt、length等方法都是針對的是UTF16編碼。所以,字元串的最大長度實際上是受字元串的編碼長度影響的。
現行的字元集國際標準,字元是以 Unicode 的方式表示的,每一個 Unicode 的碼點表示一個字元,理論上,Unicode 的範圍是無限的。UTF 是 Unicode 的編碼方式,規定了碼點在電腦中的表示方法,常見的有 UTF16 和 UTF8。 Unicode 的碼點通常用 U+??? 來表示,其中 ??? 是十六進位的碼點值。 0-65536(U+0000 - U+FFFF)的碼點被稱為基本字元區域(BMP)。剩下的字元都放在輔助平面(縮寫SMP),碼點範圍從U+010000一直到U+10FFFF。如下圖所示:
碼點解釋:它從0開始,為每個符號指定一個編號,這叫做"碼點"(code point)。比如,碼點0的符號就是null(表示所有二進位位都是0)。
U+0000 = null
上式中,U+表示緊跟在後面的十六進位數是Unicode的碼點。
詳細的編碼可參考:
Number
JavaScript的Number類型有2^64-2^53+3個值。JS的Number類型基本符合IEEE 754-2008規定的雙精度浮點數規則。但是JS為了表達幾個額外的語言場景(比如不讓除以0出錯,引入了無窮大的概念),規定了幾個例外情況:
- NaN:占用了 9007199254740990,這原本是符合IEEE 規則的數字;
- infinity:正無窮大;
- -infinity:負無窮大。
JS中+0和-0,在除法場合需要特別留意,“忘記檢測除以-0,而得到負無窮大”的情況經常會導致錯誤,而區分+0和-0的方式,正是檢測1/x是infinity和-infinity。
根據雙精度浮點數的定義,Number類型的有效的整數範圍是 -0x1fffffffffffff 至 0x1fffffffffffff,所以Number無法精確表示此範圍外的整數。
為什麼在JavaScript中,0.1+0.2不能=0.3?
console.log( 0.1 + 0.2 == 0.3) //false
左右兩邊不相等的原因就是浮點數運算的結果。浮點數運算的精度問題導致等式左右的結果並不是嚴格相等,而是相差了個微小的值。
所以實際上,這裡錯誤的不是結論,而是比較的方法,正確的比較方法是使用 JavaScript 提供的最小精度值:
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON) //true
檢查等式左右兩邊差的絕對值是否小於最小精度,才是正確的比較浮點數的方法。這段代碼結果就是 true 了。
Symbol
創建symbol的方式如下:
var mysymbol = Symbol('my symbol'); //Symbol(my symbol)
可以使用Symblo.interator定義for...of在對象上的行為:
var obj = { a:1, b:2, [Symbol.iterator]:function(){ var v = 0; return { next:function(){ return {value:v++,done:v>10} } } } }; for(var v of obj){ console.log(v); //0 1 2 3 4 5 6 7 8 9 }
Object
問題描述:為什麼給對象添加的方法能用在基本類型上?
Object 是 JavaScript 中最複雜的類型,也是 JavaScript 的核心機制之一。
在 JavaScript 中,對象的定義是“屬性的集合”。屬性分為數據屬性和訪問器屬性,二者都是 key-value 結構,key 可以是字元串或者 Symbol 類型。
JavaScript 中的幾個基本類型,都在對象類型中有一個“親戚”。它們是:
- Number
- String
- Boolean
- Symbol
我們必須認識到 3 與 new Number(3) 是完全不同的值,它們一個是 Number 類型, 一個是對象類型。
Number、String 和 Boolean,三個構造器是兩用的,當跟 new 搭配時,它們產生對象,當直接調用時,它們表示強制類型轉換。Symbol 函數比較特殊,直接用 new 調用它會拋出錯誤,但它仍然是 Symbol 對象的構造器。
JavaScript 語言設計上試圖模糊對象和基本類型之間的關係,我們日常代碼可以把對象的方法在基本類型上使用,比如:
console.log("abc".charAt(0)); //a
甚至我們在原型上添加方法,都可以應用於基本類型,比如以下代碼,在 Symbol 原型上添加了 hello 方法,在任何 Symbol 類型變數都可以調用。
Symbol.prototype.helloWorld = () => console.log("hello world"); var a = Symbol("a"); console.log(typeof a); //symbol,a 並非對象 a.helloWorld(); //hello world,有效
針對上面的問題,答案就是: .運算符提供了裝箱操作,它會根據基礎類型構造一個臨時對象,使得我們能在基礎類型上調用對應對象的方法。
裝箱轉換
每一種基本類型 Number、String、Boolean、Symbol 在對象中都有對應的類,所謂裝箱轉換,正是把基本類型轉換為對應的對象,它是類型轉換中一種相當重要的種類。
前文提到,全局的 Symbol 函數無法使用 new 來調用,但我們仍可以利用裝箱機制來得到一個 Symbol 對象,我們可以利用一個函數的 call 方法來強迫產生裝箱。
我們定義一個函數,函數裡面只有 return this,然後我們調用函數的 call 方法到一個 Symbol 類型的值上,這樣就會產生一個 symbolObject。
我們可以用 console.log 看一下這個東西的 type of,它的值是 object,我們使用 symbolObject instanceof 可以看到,它是 Symbol 這個類的實例,我們找它的 constructor 也是等於 Symbol 的,所以我們無論從哪個角度看,它都是 Symbol 裝箱過的對象:
var symbolObject = (function(){ return this; }).call(Symbol("a")); console.log(typeof symbolObject); //object console.log(symbolObject instanceof Symbol); //true console.log(symbolObject.constructor == Symbol); //true
裝箱機制會頻繁產生臨時對象,在一些對性能要求較高的場景下,我們應該儘量避免對基本類型做裝箱轉換。
使用內置的 Object 函數,我們可以在 JavaScript 代碼中顯式調用裝箱能力。
var symbolObject = Object(Symbol("a")); console.log(typeof symbolObject); //object console.log(symbolObject instanceof Symbol); //true console.log(symbolObject.constructor == Symbol); //true
每一類裝箱對象皆有私有的 Class 屬性,這些屬性可以用 Object.prototype.toString 獲取:
var symbolObject = Object(Symbol("a")); console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
在 JavaScript 中,沒有任何方法可以更改私有的 Class 屬性,因此 Object.prototype.toString 是可以準確識別對象對應的基本類型的方法,它比 instanceof 更加準確。
但需要註意的是,call 本身會產生裝箱操作,所以需要配合 typeof 來區分基本類型還是對象類型。
在 ES5 開始,[[class]] 私有屬性被 Symbol.toStringTag 代替,Object.prototype.toString 的意義從命名上不再跟 class 相關。我們甚至可以自定義 Object.prototype.toString 的行為,以下代碼展示了使用 Symbol.toStringTag 來自定義 Object.prototype.toString 的行為:
var obj = { [Symbol.toStringTag]:'myobject' } console.log(obj + ''); //[object myobject]
這裡創建了一個新對象,並且給它唯一的一個屬性 Symbol.toStringTag,我們用字元串加法觸發了 Object.prototype.toString 的調用,發現這個屬性最終對 Object.prototype.toString 的結果產生了影響。
註:以下代碼展示了所有具有內置 class 屬性的對象:
var o = new Object; var n = new Number; var s = new String; var b = new Boolean; var d = new Date; var arg = function(){ return arguments }(); var r = new RegExp; var f = new Function; var arr = new Array; var e = new Error; console.log([o, n, s, b, d, arg, r, f, arr, e].map(v => Object.prototype.toString.call(v))); //["[object Object]", "[object Number]", "[object String]", "[object Boolean]", "[object Date]", "[object Arguments]", "[object RegExp]", "[object Function]", "[object Array]", "[object Error]"]
拆箱轉換
在 JavaScript 標準中,規定了 ToPrimitive 函數,它是對象類型到基本類型的轉換(即,拆箱轉換)。
對象到 String 和 Number 的轉換都遵循“先拆箱再轉換”的規則。通過拆箱轉換,把對象變成基本類型,再從基本類型轉換為對應的 String 或者 Number。
拆箱轉換會嘗試調用 valueOf 和 toString 來獲得拆箱後的基本類型。如果 valueOf 和 toString 都不存在,或者沒有返回基本類型,則會產生類型錯誤 TypeError。
var obj = { valueOf:() => { console.log('valueOf'); return {}; }, toString:() => { console.log('toString'); return {}; } } obj * 2; //valueOf //toString //Uncaught TypeError: Cannot convert object to primitive value
從上面結果可以看出,拆箱操作失敗了。
到 String 的拆箱轉換會優先調用 toString。那麼你會看到調用順序就變了,如下代碼:
var obj = { valueOf:() => { console.log('valueOf'); return {}; }, toString:() => { console.log('toString'); return {}; } } String(obj); //toString //valueOf //Uncaught TypeError: Cannot convert object to primitive value
在 ES6 之後,還允許對象通過顯式指定 @@toPrimitive Symbol 來覆蓋原有的行為。
var obj = { valueOf:() => { console.log('valueOf'); return {}; }, toString:() => { console.log('toString'); return {}; }, [Symbol.toPrimitive]: () => { console.log('toPrimitive'); return 'result msg'; } } console.log( obj + ''); //toPrimitive //result msg