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

 

posted @ 2016-03-30 19:21  _better  阅读(6662)  评论(3编辑  收藏  举报