JS由Number與new Number的區別引發的思考


在回答園子問題的時候發現了不少新東西,寫下來分享一下 ==

 

下面的圖就是此篇的概覽,另外文章的解釋不包括ES6新增的Symbol,話說這貨有包裝類型,但是不能new...

基於JS是面向對象的,所以我們稱呼function為“方法”,等同於“函數”。

 

1.Number與Number Object ——原始類型與包裝類型(primitive VS wrapper object)

  ECMAScript定義了7種數據類型:6種原始類型(ES6新增Symbol)以及Object。原始類型(primitive)不是對象,包裝類型對象(wrapper object)屬於Object的范疇,除了null和undefined原始類型都有對應的包裝類型,我們不談Symbol(分明是不夠理解 ==):

  ● String for the string primitive.

  ● Number for the number primitive.

  ● Boolean for the boolean primitive.

  ● Symbol for the symbol primitive.

  原始類型不是對象並且沒有方法,為什么"test".length怎么可以使用,不是原始類型沒有方法嗎?這里有個機制:http://es5.github.io/#x10.4.3(step 3 的ToObject是核心),下文會提到,了解更多傳送門 -> Does javascript autobox?

 

2.類型檢測

  JS檢測數據類型的方式有很多,這里主要說三種:typeof、instanceof、Object.prototype.toString。

  [1].typeof:主要用於原始類型的檢測,可檢測原始類型、方法/函數、對象,遵循(出於兼容考慮,直到ES6調用 typeof null 仍然返回object):

 

類型 結構
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol (ECMAScript 6 新增) "symbol"
宿主對象(JS環境提供的,比如瀏覽器) Implementation-dependent
函數對象 (implements [[Call]] in ECMA-262 terms) "function"
任何其他對象 "object"

 

   [2].instanceof:主要用於檢測自定義對象,用來判斷某個構造方法/構造函數(右側)的prototype屬性所指向的對象是否存在於另外一個要檢測對象(左側)的原型鏈上。額外補點說明:1.跨Frame/多窗口間不要用instanceof檢測類型(可以用[3].Object.prototype.toString);2.instanceof的結果不是一成不變的,它可能會受到完全重寫prototype的影響:

 1 function Func(){}
 2 
 3 var f1 = new Func();
 4 console.log('---------- f1 ---------------');
 5 console.log(f1 instanceof Func);
 6 console.log(f1.__proto__);
 7 console.log(f1.constructor);
 8 
 9 console.log('----- Func原型被重寫后的f1 -----');
10 Func.prototype = {};
11 
12 console.log(f1 instanceof Func); 
13 console.log(f1.__proto__); // 重寫prototype不影響已創建對象,對象的[[prototype]]在函數對象構造時已經確定 -> http://es5.github.io/#x13.2.2
14 console.log(f1.constructor);
15 
16 
17 console.log('---------- f2 ---------------');
18 
19 var f2 = new Func();
20 console.log(f2 instanceof Func);
21 console.log(f2.__proto__);
22 console.log(f2.constructor);
23 
24 console.warn('所以我們應當避免完全重寫,推薦在prototype上擴展');

 

  [3].Object.prototype.toString.apply() / Object.prototype.toString.call():

   ES5對Object.prototype.toString的描述對照中文如下:

15.2.4.2 Object.prototype.toString ( )

toString方法被調用時,會執行下面的操作步驟:

  1. 如果this的值為undefined,則返回"[object Undefined]".
  2. 如果this的值為null,則返回"[object Null]".
  3. O成為調用ToObject(this)的結果.
  4. class成為O的內部屬性[[Class]]的值.
  5. 返回三個字符串"[object ", class, 以及 "]"連接后的新字符串.

  注意上面step 3 ToObject,所以不要試圖用方法[3]判斷原始類型,no zuo no die ( why you try )。

  幾種類型判斷的使用場景,試試看:

 1 var primitive_number = 1;
 2 var numberObj = new Number(1);
 3 
 4 console.log(typeof primitive_number); // typeof適用於原始類型,primitive_number是原始類型number
 5 console.log(typeof numberObj); // numberObj是object類型
 6 
 7 
 8 console.log(primitive_number instanceof Object);
 9 console.log(numberObj instanceof Object); // instanceof適用於對象類型/自定義對象類型
10 
11 console.log(primitive_number instanceof Number);
12 console.log(numberObj instanceof Number); // instanceof適用於對象類型/自定義對象類型
13 
14 console.log(Object.prototype.toString.apply(primitive_number)); // 問題出現了,此方法不要對原始類型調用
15 console.log(Object.prototype.toString.apply(numberObj));

 

3.構造函數不只用來創建對象

  對各種包裝類型對象(wrapper object)來說,不同的方式(是否使用new操作符)調用構造方法將會返回不同的結果。

  在ES5 Constructor Properties of the Global Object(構造器(類)屬性的全局對象,比如Object、Function、Array、String、Boolean、Number、Date、RegExp、Error... )的規范中,每一小節都有專門描述兩種方式調用構造函數的返回結果:

  ● The Xxx Constructor Called as a Function [未使用new操作符]

  (像調用普通函數一樣調用構造函數:會執行類型轉換並返回ToXxx(value)后的值)

  ● The Xxx Constructor [使用new操作符]

  (當使用new 構造函數方式調用:實例化新創建的對象)

  拿Number Objects來舉個栗子:

15.7 Number Objects

15.7.1 The Number Constructor Called as a Function

When Number is called as a function rather than as a constructor, it performs a type conversion.

15.7.1.1 Number ( [ value ] )

Returns a Number value (not a Number object) computed by ToNumber(value) if value was supplied, else returns +0.

15.7.2 The Number Constructor

When Number is called as part of a new expression it is a constructor: it initialises the newly created object.

  Boolean、String的描述也是類似的,對Number、Boolean、String來說:在調用他們的構造函數時,使用了new操作符將會返回對象(不出意外),否則執行相應的類型轉換后返回結果。類型轉換是什么?就是規范中提到的ToBoolean(value)ToNumber(value)、ToString(value),值得注意的是,這些ToXxx方法的返回結果都是原始類型。

  對應規范,我們看看Number(value) 與 new Number(value)分別做了什么:

  Number(value):

  1. 如果參數value未提供(即Number()),返回+0;
  2. 返回ToNumber(value)的計算結果,此結果是一個Number類型(Number原始類型)的值(不是Number對象,即不是Number類型的包裝對象Number object)。

  new Number(value):

  1. 實例化新創建的對象;
  2. 新構造對象的[[Prototype]]內部屬性設置為原生的Number prototype object —— 初始值為Number.prototype(The Number prototype object is itself a Number object (its [[Class]] is "Number") whose value is +0.);
  3. 新構造對象的[[Class]]內部屬性設置為Number
  4. 新構造對象的[[PrimitiveValue]]內部屬性被設置為:如果參數value未提供(即new Number()),則為+0;否則設置為ToNumber(value)的計算結果;
  5. 新構造對象的[[Extensible]]內部屬性設置為true。

  現在我們可以肯定的說:Number(value)返回Number原始類型,new Number(value)返回對象。

 

4.偷梁換柱? →_→ 都是ToObject干的!

  運行下面代碼,看看你在瀏覽器控制台看到了什么:

1 var obj = Object(3);
2 console.log(obj instanceof Number);
3 console.log(obj.__proto__);
4 console.log(Object.prototype.toString.apply(obj));

  欸,發生了什么?看看Object(value)干了些啥:

15.2 Object Objects #

15.2.1 The Object Constructor Called as a Function #

When Object is called as a function rather than as a constructor, it performs a type conversion.

當以方法形式而不是構造函數形式(沒用new操作符)調用Object時,會執行類型轉換。

15.2.1.1 Object ( [ value ] ) #

When the Object function is called with no arguments or with one argument value, the following steps are taken:

調用Object函數時當無參或者參數個數為1時:

  1. If value is null, undefined or not supplied, create and return a new Object object exactly as if the standard built-in Object constructor had been called with the same arguments (15.2.2.1).  如果value是未提供或者是null、undefined,用相同參數創建一個標准的內建Object。

  2. Return ToObject(value).

15.2.2 The Object Constructor #

When Object is called as part of a new expression, it is a constructor that may create an object.

當以new Object形式調用時,它就是一個構造器,並且可能創建一個對象。

15.2.2.1 new Object ( [ value ] ) #

When the Object constructor is called with no arguments or with one argument value, the following steps are taken:

調用Object函數時當無參或者參數個數為1時:

  1. If value is supplied, then  如果提供了value參數

    1. If Type(value) is Object, then  如果參數是object類型

      1. If the value is a native ECMAScript object, do not create a new object but simply return value. 如果參數是本地對象,返回原對象。

      2. If the value is a host object, then actions are taken and a result is returned in an implementation-dependent manner that may depend on the host object. 如果參數是宿主對象,返回結果依賴於宿主對象。

    2. If Type(value) is String, return ToObject(value).

    3. If Type(value) is Boolean, return ToObject(value).

    4. If Type(value) is Number, return ToObject(value).

  2. Asset: The argument value was not supplied or its type was Null or Undefined.

  3. Let obj be a newly created native ECMAScript object.

  4. Set the [[Prototype]] internal property of obj t to the standard built-in Object prototype object (15.2.4).

  5. Set the [[Class]] internal property of obj to "Object".

  6. Set the [[Extensible]] internal property of obj to true.

  7. Set the all the internal methods of obj as specified in 8.12

  8. Return obj.

   Object(3)和new Object(3)的返回結果是一樣的,來看new Object(3):我們提供了參數3因此進入step 1,Type(3)返回Number類型,因此執行ToObject(3)。ToObject依照下表返回結果,所以我們拿到了一個Number Object:

 

 

5.捆綁在相等性判斷上的類型比較

  ES2015/ES6定義了四種相等性判斷算法: Abstract Equality Comparison(==)、Strict Equality Comparison (===,嚴格相等)、SameValueZero(零值相等)、SameValue(同值相等)。兩等號判斷在比較時進行類型轉換,三等號判斷不會轉換類型,零值相等認為+0和-0相等,同值相等用於JS引擎內部、類似三等號判斷、區別在於對部分數值類型(NaN、+0、-0)比較處理方式不同。

  JS提供了三種值(相等)比較運算符:loose equality(==)、strict equality(===,嚴格相等)、Object.is(ES6新增);

  關於JS的相等性判斷,MDN已經描述的很清楚了(還是中文版)~ JavaScript 中的相等性判斷

  在各種比較方式中都大量用到了一個東西:Type(x),這貨是干嘛的?其實就是typeof x(Within this specification, the notation “Type(x)” is used as shorthand for “the type of x” where “type” refers to the ECMAScript language and specification types defined in this clause.),也就是用來檢測數據類型的(7種數據類型=6種原始類型+Object)。

   來看看ES5規范是如何描述兩等號比較的算法部分的:

11.9.3 The Abstract Equality Comparison Algorithm #

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

    1. If Type(x) is the same as Type(y), then

      1. If Type(x) is Undefined, return true.

      2. If Type(x) is Null, return true.

      3. If Type(x) is Number, then

        1. If x is NaN, return false.

        2. If y is NaN, return false.

        3. If x is the same Number value as y, return true.

        4. If x is +0 and y is 0, return true.

        5. If x is 0 and y is +0, return true.

        6. Return false.

      4. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.

      5. If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.

      6. Return true if x and y refer to the same object. Otherwise, return false.

    2. If x is null and y is undefined, return true.

    3. If x is undefined and y is null, return true.

    4. If Type(x) is Number and Type(y) is String,
      return the result of the comparison x == ToNumber(y).

    5. If Type(x) is String and Type(y) is Number,
      return the result of the comparison ToNumber(x) == y.

    6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

    7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

    8. If Type(x) is either String or Number and Type(y) is Object,
      return the result of the comparison x == ToPrimitive(y).

    9. If Type(x) is Object and Type(y) is either String or Number,
      return the result of the comparison ToPrimitive(x) == y.

    10. Return false.

  再來看看三等號比較的算法部分:

11.9.6 The Strict Equality Comparison Algorithm #

The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:

  1. If Type(x) is different from Type(y), return false.

  2. If Type(x) is Undefined, return true.

  3. If Type(x) is Null, return true.

  4. If Type(x) is Number, then

    1. If x is NaN, return false.

    2. If y is NaN, return false.

    3. If x is the same Number value as y, return true.

    4. If x is +0 and y is 0, return true.

    5. If x is 0 and y is +0, return true.

    6. Return false.

  5. If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise, return false.

  6. If Type(x) is Boolean, return true if x and y are both true or both false; otherwise, return false.

  7. Return true if x and y refer to the same object. Otherwise, return false.

  尼瑪,這就是在不停的拿類型比較啊有木有。從這也能看出來為什么推薦使用嚴格相等(===),它沒有隱式轉換並且判斷步驟少得多。

  這里也體現了非嚴格相等(==)用了兩種形式的類型轉換:

  1. 原始類型之間(ToNumber),所以"3" == 3會返回true。
  2. 對象轉原始類型(ToPrimitive),比較對象和number(原始)類型的時候會執行到step 9,所以new Number(3)==3也會返回true。

  有了規范的說明,我們就可以這么解釋兩個object類型的變量用兩等號比較為什么返回false:類型相同,進入step 1 -> 如果是Undefined、Null,返回true -> 不是Number、String、Boolean(object怎么可能是原始類型),繼續 -> 是同一個對象的引用?返回true;否則,只能返回false了。如果使用三等號判斷,直接到了step 7...

 

6.回到問題

  ● Number(3) 與 new Number(3)

   1.依據上面兩等號比較的算法部分,上面剛解釋new Number(3)==3,而Number(3)將返回number原始類型的3,因為所以...new Number(3) == Number(3)

   2.依據上面三等號比較的算法部分,第一步就返回false了(一個是原始類型,另一個是object).... 所以new Number(3) !== Number(3)

  ● Object(3) 與 Object(3)

    1.兩等號判斷,好吧,Object(3) => new Number(3),原因見上文ToObject。兩個object類型的變量用兩等號比較為什么返回false。(見上問)

    2.如果使用三等號判斷,直接到了step 7。(見上)

  ● new Number(3) 與 Object(3)

    Object(3) => new Number(3),解釋同上。

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

參考:

[1].ES5規范:http://es5.github.io/

[2].ES5規范官網:http://www.ecma-international.org/ecma-262/5.1/

[3].JS數據類型:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_types#Data_structures_and_types

[4].JavaScript的相等性判斷:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness

[5].JavaScript:Object.prototype.toString方法的原理:http://www.cnblogs.com/ziyunfei/archive/2012/11/05/2754156.html

[6].JavaScript面向對象編程(5)重寫prototype造成的混亂:http://blog.csdn.net/zhengwei223/article/details/41785881

[7].重寫prototype原型后哪些東西改變了:http://www.cnblogs.com/web-coding/p/4723381.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM