[17] 原生功能 Natives - 內部特性、封裝、解封裝


keywords:natives,internal [[Class]],box,unboxing

原生功能 ( Natives )

  • JavaScript 有幾個常用的 natives:

    1. String()
    2. Number()
    3. Boolean()
    4. Array()
    5. Object()
    6. Function()
    7. RegExp()
    8. Date()
    9. Error()
    10. Symbol() (ES6新增)
  • 如果是從別的語言 ( C# ) 來到 JavaScript,String() 看起來很像建立字串值的建構器,不過實際建構出來的東西可能與想像的不同
    看看例子:

      let a = new String('xyz');
    
      typeof a; // object
    
      a; // String {'xyz'}  展開後  {0:'x', 1:'y', 2:'z', length:3, [[PrimitiveValue]]: "xyz" }
    
      Object.prototype.toString.call(a); // [object String]  可想像成 a.toString();
    

    建構器形式建立 即 new String('xyz') 是一個字串包裹器物件,包裹了 'xyz' ,而非建立 'xyz' 這個基型值。
    typeof 顯示出這些物件並非自己的特殊型別,恰當的說法是 object 型別的子型別

  • typeof 為 'object' 的那些值( 例如一個陣列 )會額外標示一個有內部的 [[Class]] 特性 ( 想成是一個內部的分類, classification 非傳統類別導向編成方法中的類別 class ),此特性無法直接被取用,可以借取預設的 Object.prototype.toString(..) 方法,以目標值呼叫它來間接顯露此特性

      Object.prototype.toString.call([1,2,3]); // '[object Array]'
    
      Object.prototype.toString.call(function(){}); // '[object Function]'
    
  • 基本型別值( primitive values )Null() 、Undefined() 原生建構器並不存在 (即沒有這種東西 new Null(null); ),但顯露出來的內部 [[Class]] 值就是 'Null' 和 'Undefined'

      Object.prototype.toString.call(null); // '[object null]'
    
      Object.prototype.toString.call(undefined); // '[object undefined]'
    
  • 簡單的基型值,像是 string、number 以及 boolean ,實際上會有封裝 ( boxing ) 的行為涉入

      Object.prototype.toString.call('xyz'); // '[object String]'
    
      Object.prototype.toString.call(99); // '[object Number]'
    
      Object.prototype.toString.call(false); // '[object Boolean]'
    

封裝用的包裹器

  • 基本型別值沒有特性 ( properties ) 或方法 ( methods ),所以要存取 .length 或 .toString() 時會需要包裹基型值的物件包裹器,幸好 JavaScript 會自動封裝 ( box 也就是 包裹 wrap )
    看看例子:

      let a = 'xyz';
    
      a.length; // 3
    
      a.toUpperCase(); // 'XYZ'
    
  • 如果想藉由直接使用物件形式,試著預先最佳化,程式實際上會執行的更慢,因為瀏覽器很久以前就針對如 .length 這類的常見情況進行最佳化了
  • 一般來說沒必要直接使用物件形式,最好是讓封裝需要時隱含地發生。換句話說永遠都不要做 new String('xyz')new Number(99) ,永遠優先選用 'xyz'99 這樣字面形式的基本型別值

物件包裹器的陷阱

  • 如果真的要使用物件包裹器,有幾個陷阱必須特別留意
    看看例子:

      let a = new Boolean(false);
    
      typeof a; // object
    
      a; // Boolean {false}
    
      !!a; // true
    

    🚩問題在於 new Boolean 建立了包 false 值的物件包裹器,但他依然屬於物件型別 ( objects ),而物件本身是 truthy

  • 比較一下物件包裹器與基本型別值

      let a = new String('xyz');
    
      let b = 'xyz;
    
a b

a:

  1. 有 .length 特性
  2. 每個字占有一個索引
  3. 本身就有 [[PrimitiveValue]]: "xyz"

b:

  1. 本身沒有 .length 特性
  2. 需要調用 b.proto ( 可以看到一個物件的原型的一個特性 ) 才能看到原型
  3. 無 [[PrimitiveValue]]: "xyz"
  • 通常不鼓勵直接使用封裝的物件包裹器

解封裝 ( unboxing )

  • 如果有一個物件包裹器,而我們想要取出其底層的基本型別值 ( underlying primitive value ),可以使用 valueOf()

      let a = new String('xyz');
    
      let b = new Number(99);
    
      let c = new Boolean(false);
    
      a; // String {"xyz"}
    
      b; // Number {99}
    
      c; // Boolean {false}
    
      a.valueOf(); // 'xyz'
    
      b.valueOf(); // 99
    
      c.valueOf(); // false
    
  • 解封裝也可能隱含地發生,必須用到物件包裹器的基本型別值時,這個過程為強制轉型,之後會有更詳細的文章介紹

      let a = new String('xyz');
    
      let b = a + '';
    
      a; // String {"xyz"}
    
      b; // 'xyz'
    
      typeof a; // object
    
      typeof b; // string
    
#natives #internal [[Class]] #box #unboxing







你可能感興趣的文章

JS try catch 使用方式

JS try catch 使用方式

JavaScript 數據類型 & 內存

JavaScript 數據類型 & 內存

[JS] 強制轉型及轉換技巧

[JS] 強制轉型及轉換技巧






留言討論