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方法被调用时,会执行下面的操作步骤:
- 如果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
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 anew
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):
- 如果参数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
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时:
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
Object
is called as part of anew
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时:
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