你可能還不知道的關於JavaScript類型的細節?

類型

七種類型:javascript

  • Undefined
  • Null
  •  Boolean
  •  String
  • Number
  • Symbol
  • Object

Undefined、Null

閱讀問題:爲何有的編程規範要求使用void 0 代替undefined?html

Undefined類型表示未定義,它的類型只有一個值即undefined,任何變量在賦值前都是Undefined類型,它的值是undefined。因爲JS中的undefined是一個變量,而並不是是一個關鍵字,這是JavaScript語言公認的設計失誤之一。所以,爲了不無心中被篡改,通常用void 0來獲取undefined的值。java

Null類型也只有一個值爲null,null是JavaScript的關鍵字,可使用null關鍵字來獲取null值。express

關於void運算符,有兩種寫法:編程

  • 1.void expression
  • 2.void (expression)

void運算符的操做數能夠是任意類型。該運算符指定要計算一個表達式可是不論該表達式原來是否有本身的返回值,其返回值都爲undefined。其做用以下:函數

做用一:返回undefined,(對於爲何不直接使用undefined,是由於undefined不是關鍵字,意味着它隨時可能被篡改爲其餘值。。。)。性能

做用二:防止沒必要要的行爲,以下代碼:this

<a href='javascript:void(0)'>阻止默認行爲跳轉</a>

例子:編碼

function func(){
    return this;
}
console.log(void func()); //undefined

從上所述就可以說明爲何有的編程規範要求用void 0代替undefined。spa

 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
相關文章
相關標籤/搜索