JS语法: 由 ++[[]][+[]]+[+[]] = 10引发的问题

简介:

JS语法: 由 ++[[]][+[]]+[+[]] = 10引发的问题

解释:为什么 ++[[]][+[]]+[+[]] = 10

[0]是一个带有0成员的数组,[0][0]是取它的第1个成员,所以必是0。

用了[0][0] = '1'虽然改了第1成员的值,但下一个[0][0]是独立的取成员值的表达式,所以得到0数字值。

[] = 1是右值不是iterable(可迭代的)造成的错误,这应该是”解构赋值”造成的错误,以不同的浏览器调试:

 
  1. // Chrome 
  2. TypeError: undefined is not a function 
  3.   
  4. // Firefox 
  5. TypeError: 1 is not iterable 
  6.   
  7. // Safari 
  8. TypeError: [] is not a function. (In '[]''[]' is undefined)  

数组解构赋值的话,右值必需是iterable(可迭代的),下面的例子的错误与[] = 1是一样错误,所以应该会先检查右值是否为iterable时,先抛出类型错误:

 
  1. [] = {}; 
  2. [] = undefined; 
  3. [] = null;  

最后的,[] = '1'不会有错误,是因为字符串是属于iterable(可迭代的)。

JS的{} + {}与{} + []的结果是什么?

ToPrimitive内部运算

因此,加号运算符只能使用于原始数据类型,那么对于对象类型的值,要如何转换为原始数据类型?下面说明是如何转换为原始数据类型的。

在ECMAScript 6th Edition #7.1.1,有一个抽象的ToPrimitive运算,它会用于对象转换为原始数据类型,这个运算不只会用在加号运算符,也会用在关系比较或值相等比较的运算中。下面有关于ToPrimitive的说明语法:

ToPrimitive(input, PreferredType?)input代表代入的值,而PreferredType可以是数字(Number)或字符串(String)其中一种,这会代表”优先的”、”首选的”的要进行转换到哪一种原始类型,转换的步骤会依这里的值而有所不同。但如果没有提供这个值也就是预设情况,则会设置转换的hint值为”default”。这个首选的转换原始类型的指示(hint值),是在作内部转换时由JS视情况自动加上的,一般情况就是预设值。

而在JS的Object原型的设计中,都一定会有两个valueOf与toString方法,所以这两个方法在所有对象里面都会有,不过它们在转换e有可能会交换被调用的顺序。

当PreferredType为数字(Number)时

当PreferredType为数字(Number)时,input为要被转换的值,以下是转换这个input值的步骤:

  1. 如果input是原始数据类型,则直接返回input。
  2. 否则,如果input是个对象时,则调用对象的valueOf()方法,如果能得到原始数据类型的值,则返回这个值。
  3. 否则,如果input是个对象时,调用对象的toString()方法,如果能得到原始数据类型的值,则返回这个值。
  4. 否则,抛出TypeError错误。

当PreferredType为字符串(String)时

上面的步骤2与3对调.

PreferredType没提供时,也就是hint为”default”时

与PreferredType为数字(Number)时的步骤相同。

数字其实是预设的首选类型,也就是说在一般情况下,加号运算中的对象要作转型时,都是先调用valueOf再调用toString。

但这有两个异常,一个是Date对象,另一是Symbol对象,它们覆盖了原来的PreferredType行为,Date对象的预设首选类型是字符串(String)。

因此你会看到在一些教程文件上会区分为两大类对象,一类是 Date 对象,另一类叫 非Date(non-date) 对象。因为这两大类的对象在进行转换为原始数据类型时,首选类型恰好相反。

模拟代码说明

 
  1. a + b: 
  2.     pa = ToPrimitive(a) 
  3.     pb = ToPrimitive(b) 
  4.   
  5.     if(pa is string || pb is string) 
  6.        return concat(ToString(pa), ToString(pb)) 
  7.     else 
  8.        return add(ToNumber(pa), ToNumber(pb))  

JS对于Object与Array的设计

在JS中所设计的Object纯对象类型的valueOf与toString方法,它们的返回如下:

valueOf方法返回值: 对象本身。(所以ToPrimitive最后要返回toString的值了)

toString方法返回值: “[object Object]”字符串值,不同的内建对象的返回值是”[object type]”字符串,”type”指的是对象本身的类型识别,例如Math对象是返回”[object Math]”字符串。但有些内建对象因为覆盖了这个方法,所以直接调用时不是这种值。(注意: 这个返回字符串的前面的”object”开头英文是小写,后面开头英文是大写)

一元正号(+),具有让首选类型(也就是hint)设置为数字(Number)的功能,所以可以强制让对象转为数字类型,一般的对象会转为:

这里首选类型其实本身就是数字,+让toString输出的字符串再强转了一次。

 
  1. > +{} //相当于 +"[object Object]" 
  2. NaN  

当然,对象的这两个方法都可以被覆盖,你可以用下面的代码来观察这两个方法的运行顺序,下面这个都是先调用valueOf的情况:

 
  1. let obj = { 
  2.   valueOf: function () { 
  3.       console.log('valueOf'); 
  4.       return {}; // object 
  5.   }, 
  6.   toString: function () { 
  7.       console.log('toString'); 
  8.       return 'obj'; // string 
  9.   } 
  10. console.log(1 + obj);  //valueOf -> toString -> '1obj' 
  11. console.log(+obj); //valueOf -> toString -> NaN 
  12. console.log('' + obj); //valueOf -> toString -> 'obj'  

实例

基本类型间运算

字符串 + 其他原始类型字符串在加号运算有最高的优先运算

 
  1. '1' + 123 
  2. "1123" 
  3.   
  4. '1' + false 
  5. "1false" 
  6.   
  7. '1' + null 
  8. "1null" 
  9.   
  10. '1' + undefined 
  11. "1undefined"  

数字 + 其他的非字符串的原始数据类型数字为优先

 
  1. > 1 + true //true转为1, false转为0 
  2.   
  3. > 1 + null //null转为0 
  4.   
  5. > 1 + undefined //null转为NaN 
  6. NaN  

数字/字符串以外的原始数据类型作加法运算就是转为数字再运算

 
  1. true + true 
  2.   
  3. true + null 
  4.   
  5. > undefined + null 
  6. NaN  

对象类型间运算

  • 空数组 + 空数组
 
  1. > [] + [] 
  2.  
  3. ""  

两个数组相加,依然按照valueOf -> toString的顺序,但因为valueOf是数组本身,所以会以toString的返回值才是原始数据类型,也就是空字符串,所以这个运算相当于两个空字符串在相加,依照加法运算规则第2步骤,是字符串连接运算(concatenation),两个空字符串连接最后得出一个空字符串。

  • 空对象 + 空对象

特别注意: {} + {}在不同的浏览器有不同结果

如果在第一个(前面)的空对象加上圆括号(()),这样JS就会认为前面是个对象,就可以得出同样的结果:

 
  1. > ({}) + {} 
  2. "[object Object][object Object]"  

注: 上面说的行为这与加号运算的第一个(前面)的对象字面值是不是个空对象无关,就算是里面有值的对象字面,例如{a:1, b:2},也是同样的结果。

  • Date对象
 
  1. > 1 + (new Date()) 
  2.  
  3. "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"  

要得出Date对象中的valueOf返回值,需要使用一元加号(+),来强制转换它为数字类型,例如以下的代码:

 
  1. > +new Date() 
  2.  
  3. 1480180751492  

总结

解构赋值产生的问题

 
  1. > {name: 1}['name'] = '2' 
  2. {name: 1}['name'] = '2' 
  3.           ^^^^^^ 
  4. SyntaxError: Invalid destructuring assignment target  

上述错误。

 
  1. > {name: 1}[name] = '2' 
  2.  
  3. '2'  

{name: 1}[name]相当于{name: 1};[name]。解构赋值成功。

{}问题

 
  1. > var name = 'test' 
  2. > {[name]:1} 
  3. Object {1: 1} 
  4. > {[name]:1};[name] = '1' 
  5. VM174:1 Uncaught SyntaxError: Unexpected token :  

上述错误其实是由于,{[name]:1}中{}是表达式,返回对象;{[name]:1};[name] = ‘1’中{}是语句,语句中不允许”[name]:1“,换而言之语句中允许”{name: 1}”写法。

{} + {}

{} + {}的结果是会因浏览器而有不同结果,Chrome(v55)中是object Object字符串连接,但其它的浏览器则是认为相当于+{}运算,得出NaN数字类型。

{} + []的结果是相当于+[],结果是0数字类型。

Date对象

Date对象上面有提及是首选类型为”字符串”的一种异常的对象,这与其他的对象的行为不同(一般对象会先调用valueOf再调用toString),在进行加号运算时时,它会优先使用toString来进行转换,最后必定是字符串连接运算(concatenation)

 
  1. > 1 + (new Date()) 
  2. "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"  

toString()

Object.prototype.toString()才是用来检测变量本身的类型,typeof是检测基本类型,instanceof是检测是否在原型链上。(注意一下Object.prototype.toString与Number.prototype.toString、Array.prototype.toString不同)

 
  1. > var a = 1 
  2. undefined 
  3. > a.toString() 
  4. '1' 
  5. > Number.prototype.toString.call(a) 
  6. '1' 
  7. > Object.prototype.toString.call([1, 2]) 
  8. '[object Array]' 
  9. > Array.prototype.toString.call([1, 2]) 
  10. '1,2' 
  11. > [1, 2].join() 
  12. '1,2'  

toString方法返回值: “[object Object]”字符串值,不同的内建对象的返回值是”[object type]”字符串,”type”指的是对象本身的类型识别,例如Math对象是返回”[object Math]”字符串。但有些内建对象因为覆盖了这个方法,所以直接调用时不是这种值。(注意: 这个返回字符串的前面的”object”开头英文是小写,后面开头英文是大写。

 
  1. > Object.prototype.toString.call(null
  2. '[object Null]' 
  3. > typeof null 
  4. 'object' 
  5. > Object.prototype.toString.call(1) 
  6. '[object Number]'  

Number()、String()与Boolean()

常被搞混的是直接使用Number()、String()与Boolean()三个强制转换函数的用法,这与包装对象的用法不同,包装对象是必须使用new关键字进行对象实例化的,例如new Number(123),而Number(‘123’)则是强制转换其他类型为数字类型的函数。


本文作者:伯乐在线

来源:51CTO

相关文章
|
2月前
|
JavaScript 前端开发
关于 JavaScript 代码里双重感叹号的语法
关于 JavaScript 代码里双重感叹号的语法
51 1
|
3月前
|
JavaScript 前端开发
JavaScript基础语法(类型转换)
JavaScript基础语法(类型转换)
27 0
|
3月前
|
JavaScript 前端开发 Java
JavaScript基础语法(流程控制语句)
JavaScript基础语法(流程控制语句)
25 0
|
3月前
|
JavaScript 前端开发 Java
JavaScript基础语法(运算符)
JavaScript基础语法(运算符)
32 0
|
3月前
|
JavaScript 前端开发 Java
JavaScript基础语法(变量)
JavaScript基础语法(变量)
48 0
|
3月前
|
JavaScript 前端开发 Java
JavaScript基础语法(输出语句)
JavaScript基础语法(输出语句)
21 0
|
29天前
|
JavaScript 前端开发 Web App开发
JavaScript基础语法(codewhy版本)(一)
JavaScript基础语法(codewhy版本)
88 1
JavaScript基础语法(codewhy版本)(一)
|
1月前
|
JavaScript 前端开发 网络架构
JavaScript的数组教程(最详细,更新至es6新语法)
JavaScript的数组教程(最详细,更新至es6新语法)
|
1月前
|
移动开发 前端开发 JavaScript
Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(下)
Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(下)
|
1月前
|
JavaScript 前端开发 Java
Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(中)
Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(中)