在回答園子問題的時候發現了不少新東西,寫下來分享一下 ==
下面的圖就是此篇的概覽,另外文章的解釋不包括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方法被調用時,會執行下面的操作步驟:
- 如果this的值為undefined,則返回
"[object Undefined]".- 如果this的值為null,則返回
"[object Null]".- 讓O成為調用ToObject(this)的結果.
- 讓class成為O的內部屬性[[Class]]的值.
- 返回三個字符串"[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
Numberis 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
Numberis called as part of anewexpression 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):
- 如果參數value未提供(即Number()),返回+0;
- 返回ToNumber(value)的計算結果,此結果是一個Number類型(Number原始類型)的值(不是Number對象,即不是Number類型的包裝對象Number object)。
new Number(value):
- 實例化新創建的對象;
- 新構造對象的[[Prototype]]內部屬性設置為原生的Number prototype object —— 初始值為
Number.prototype(The Number prototype object is itself a Number object (its [[Class]] is "Number") whose value is +0.); - 新構造對象的[[Class]]內部屬性設置為
Number; - 新構造對象的[[PrimitiveValue]]內部屬性被設置為:如果參數value未提供(即new Number()),則為+0;否則設置為ToNumber(value)的計算結果;
- 新構造對象的[[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
Objectis called as a function rather than as a constructor, it performs a type conversion.當以方法形式而不是構造函數形式(沒用new操作符)調用Object時,會執行類型轉換。
15.2.1.1 Object ( [ value ] ) # Ⓣ
When the
Objectfunction is called with no arguments or with one argument value, the following steps are taken:調用Object函數時當無參或者參數個數為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。
Return ToObject(value).
15.2.2 The Object Constructor # Ⓣ
When
Objectis called as part of anewexpression, it is a constructor that may create an object.當以new Object形式調用時,它就是一個構造器,並且可能創建一個對象。
15.2.2.1 new Object ( [ value ] ) # Ⓣ
When the
Objectconstructor is called with no arguments or with one argument value, the following steps are taken:調用Object函數時當無參或者參數個數為1時:
If value is supplied, then 如果提供了value參數
If Type(value) is Object, then 如果參數是object類型
If the value is a native ECMAScript object, do not create a new object but simply return value. 如果參數是本地對象,返回原對象。
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. 如果參數是宿主對象,返回結果依賴於宿主對象。
Asset: The argument value was not supplied or its type was Null or Undefined.
Let obj be a newly created native ECMAScript object.
Set the [[Prototype]] internal property of obj t to the standard built-in Object prototype object (15.2.4).
Set the [[Class]] internal property of obj to
"Object".Set the [[Extensible]] internal property of obj to true.
Set the all the internal methods of obj as specified in 8.12
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:
If Type(x) is the same as Type(y), then
If Type(x) is Undefined, return true.
If Type(x) is Null, return true.
If Type(x) is Number, then
If x is NaN, return false.
If y is NaN, return false.
If x is the same Number value as y, return true.
If x is +0 and y is −0, return true.
If x is −0 and y is +0, return true.
Return false.
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.
If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
Return true if x and y refer to the same object. Otherwise, return false.
If x is null and y is undefined, return true.
If x is undefined and y is null, return true.
If Type(x) is Number and Type(y) is String,
return the result of the comparison x == ToNumber(y).If Type(x) is String and Type(y) is Number,
return the result of the comparison ToNumber(x) == y.If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
If Type(x) is either String or Number and Type(y) is Object,
return the result of the comparison x == ToPrimitive(y).If Type(x) is Object and Type(y) is either String or Number,
return the result of the comparison ToPrimitive(x) == y.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:
If Type(x) is Undefined, return true.
If Type(x) is Null, return true.
If Type(x) is Number, then
If x is NaN, return false.
If y is NaN, return false.
If x is the same Number value as y, return true.
If x is +0 and y is −0, return true.
If x is −0 and y is +0, return true.
Return false.
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.
If Type(x) is Boolean, return true if x and y are both true or both false; otherwise, return false.
Return true if x and y refer to the same object. Otherwise, return false.
尼瑪,這就是在不停的拿類型比較啊有木有。從這也能看出來為什么推薦使用嚴格相等(===),它沒有隱式轉換並且判斷步驟少得多。
這里也體現了非嚴格相等(==)用了兩種形式的類型轉換:
- 原始類型之間(ToNumber),所以"3" == 3會返回true。
- 對象轉原始類型(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/
[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
