摘要:这样导致结果不一致,等解析语句为,对空对象强制转为数字类型,即为,将非空字符串转换为数字类型,结果为。综上,右边表达式转换为。
首先从一系列让JavaScript初学者抓狂的运算说起。
1 + {} {} + 1 [] + {} {} + [] [] + [] {} + {}
能全部答对上面的运算结果,不必浪费时间继续阅读本文了。
如果对某一些的结果还不确定,请慢慢往下看。
上面列的所有运算,需要理清以下两点:
+和{}的解析规则;
JavaScript是如何进行类型转换的;
+和{}的解析规则 +符号数字的加法运算,二元运算符
字符串的连接运算,二元运算符
正号,一元运算符,强制转换其他类型的运算元为数字类型
{}符号对象的字面量
区块语句
加法运算规则首先,我们来了解,+符号作为加号二元运算符的运算规则
使用ToPrimitive转换左右运算元为原始数据类型值;
在第1步转换后,如果有运算元出现原始数据类型为“字符串”类型时,则另一运算元强制转换为字符串,然后做字符串连接运算;
其他情况时,所有运算元都转换为原始数据类型为“数字”类型,然后做数字相加运算;
ToPrimitive运算从上图总结ToPrimitive运算的语法说明:
ToPrimitive(input, PreferredType?)
input代表代入的值,而PreferredType可以是数字(Number)或字符串(String)其中一种,这会代表“优先的”、“首选的”的要进行转换到哪一种原始类型,转换的步骤会依这里的值而有所不同。
但如果没有提供这个值也就是预设情况,则会设置转换的hint值为default。这个首选的转换原始类型的指示(hint值),是在作内部转换时由JS视情况自动加上的,一般情况就是预设值。
转换步骤为:
如果input是原始数据类型,则直接返回input。
否则,如果input是个对象时,则调用对象的valueOf()方法,如果能得到原始数据类型的值,则返回这个值。
否则,如果input是个对象时,调用对象的toString()方法,如果能得到原始数据类型的值,则返回这个值。
否则,抛出TypeError错误。
上面的步骤2与3对调,转换步骤为:
如果input是原始数据类型,则直接返回input。
否则,如果input是个对象时,则调用对象的toString方法,如果能得到原始数据类型的值,则返回这个值。
否则,如果input是个对象时,调用对象的valueOf()方法,如果能得到原始数据类型的值,则返回这个值。
否则,抛出TypeError错误。
有几点值得注意:
数字是预设的PreferredType;
在一般情况下,加号运算中的对象要作转型时,都是先调用valueOf再调用toString;
例外:Date 对象、Symbol 对象
Date 对象的预设首选类型是字符串(String);
Symbol 能显示转为字符串 String(Symbol) 和布尔值,不能转为数字;
a + b: pa = ToPrimitive(a) pb = ToPrimitive(b) if(pa is string || pb is string) return concat(ToString(pa), ToString(pb)) else return add(ToNumber(pa), ToNumber(pb))
// ECMA-262, section 9.1, page 30. Use null/undefined for no hint, // (1) for number hint, and (2) for string hint. function ToPrimitive(x, hint) { if (!IS_SPEC_OBJECT(x)) return x; if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT; return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x); }
可以看出,Date类型的对象预设值为字符串(String)。
DefaultNumber和DefaultString方法
// ECMA-262, section 8.6.2.6, page 28. function DefaultString(x) { if (!IS_SYMBOL_WRAPPER(x)) { if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToString); var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = % _CallFunction(x, toString); if (IsPrimitive(s)) return s; } var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = % _CallFunction(x, valueOf); if (IsPrimitive(v)) return v; } } throw MakeTypeError(kCannotConvertToPrimitive); }
// ECMA-262, section 8.6.2.6, page 28. function DefaultNumber(x) { var valueOf = x.valueOf; if (IS_SPEC_FUNCTION(valueOf)) { var v = % _CallFunction(x, valueOf); if (IS_SYMBOL(v)) throw MakeTypeError(kSymbolToNumber); if (IS_SIMD_VALUE(x)) throw MakeTypeError(kSimdToNumber); if (IsPrimitive(v)) return v; } var toString = x.toString; if (IS_SPEC_FUNCTION(toString)) { var s = % _CallFunction(x, toString); if (IsPrimitive(s)) return s; } throw MakeTypeError(kCannotConvertToPrimitive); }
至此,我们弄清楚了ToPrimitive内部运算规则。
原始数据类型转换规则表对于原始数据类型直接的转换规则就不一一解释了,可以参看下表:
解析为数字1与空对象{}进行加运算,按照上面的规则,空对象{}按照valueOf -> toString顺序,valueOf返回对象本身,所以调用toString返回"[object Object]"为字符串,根据规则,有一个为字符串,则进行字符串连接运算,数字1强制转换为字符串"1",最终结果为"1[object Object]"。
{} + 1这个例子有坑,因为对于浏览器引擎来说,它们会认为以花括号开头{的,是一个 区块语句 的开头,而不是一个对象字面量的语句,所以会认为略过第一个{}。所以这个例子相当于+1,加号运算会直接变为一元正号运算,对数字1强制转换为数字类型,结果为1。
[] + {}将[]和{}分别调用ToPrimitive方法,返回""和"[object Object]",然后做字符串拼接得到"[object Object]"。
{} + []开始的{}被解析为区块语句而略过,相当于+[],而[]转为原始数据类型结果为"",强制转换""为数字类型,结果为0;
[] + []将[]和{}分别调用ToPrimitive方法,返回""和"",然后做字符串拼接得到""。
{} + {}这个例子有争议,对于Firefox和Edge浏览器来说,他们一以贯之的将第一个{}作为区块语句略过,而对于V8引擎系列(Chrome、Node.js等)则将第一个{}解析为对象字面量。
这样导致结果不一致,Firefox等解析语句为+{},对空对象{}强制转为数字类型,即为+"[object Object]",将非空字符串转换为数字类型,结果为NaN。
Chrome等解析语句为两个空对象做加运算,结果也比较好理解:"[object Object][object Object]"。
这是一个不严格等于运算,我们来看转换过程。
// 第一步,转换右边,根据上述原始数据类型转换规则表, // 所有对象强制转 Boolean 类型都是 true,所以 ![] 为 false ToPrimitive(![]) >> ToPrimitive(!ToBoolean([])) >> ToPrimitive(!true) >> ToPrimitive(false) >> 0 // 第二步,转换左边 ToPrimitive([]) >> "" // 第三步,判断 "" == 0 >> ToNumber("") == 0 >> 0 == 0 >> return true++[[]][+[]] + [+[]]
进一步,来看这个例子。
很明显,根据运算符优先级,这个表达式可以用+分隔为左右两个部分++[[]][+[]]和[+[]]。
可以看出这是一个数组里面有一个元素+[],而+[]即将[]强制转换为数字类型,所以等于+"",结果为0。
综上,右边表达式转换为[0]。
我们来一步步拆解, 根据对右边表达式的转换,这个表达式可以等同看做++([[]][0]),++后面又可以看做数组去第1个元素,表达式转换为++[]。
但是当我们去浏览器执行++[]时,报错了:Uncaught ReferenceError: Invalid left-hand side expression in prefix operation。
吓得我赶紧去看++的语法,原来++的运算是一种引用运算,即++[]应该转换为:
var ref = [] ref = ref + 1
所以++[]转换的正确姿势为[] + 1。
左右进行相加得到:[] + 1 + [0]。
根据ToPrimitive运算规则,[] + 1 + [0] === "" + 1 + [0] === "1" + [0] === "10"。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/108893.html
摘要:所谓装箱转换,正是把基本类型转换为对应的对象,他是类型转换中一种相当重要的种类。拆箱转换在标准中,规定了函数,它是对象类型到基本类型的转换即,拆箱转换。拆箱转换会尝试调用和来获得拆箱后的基本类型。 JavaScript隐式类型转换 基本数据类型 ECMAScript 一共定义了七种 build-in types,其中六种为 Primitive Value,Null, Undefined...
摘要:在编程语言中,能够表示并操作的值的类型称做数据类型。中的原始类型包括数字,字符串和布尔值。日期与时间语言核心包括构造函数,用来创建表示日期和时间的对象。其规则为如果是布尔值,和分别被转换为和如果是数字值,返回本身。 源代码: https://github.com/RobinQu/Programing-In-Javascript/blob/master/chapters/Javas...
摘要:完整清单是中添加,此处不予介绍布尔值用来表示可能是真或假的值。结果抽象比较运算符在比较它们之前在类型之间进行自动转换。中的隐式转换称为强制类型转换,并在规范中定义。这些内置类型可用于在不同类型之间进行显式转换。 翻译:疯狂的技术宅原文:https://www.valentinog.com/bl... 本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 show...
摘要:原始类型分别有类型类型和类型三种。类型中存在一个特殊的值叫。也可以把其他类型的数据自动转换为类型运算符运算符判断原始类型语法结构变量名称。 数据类型 1.数据类型的概述;在JavaScript代码中,能够表示并且操作值的类型就叫做数据类型数据类型可以分成可变类型和不可变类型,可变类型的值是可以修改的。相反不可变类型的值是不可以修改的。数据类型还有原始类型(原始值)与引用类型(内置对象)...
摘要:下面先看看涉及到的几个函数以及他们的转换规则,这个是需要记忆的内容类型转换需要使用到的函数对于布尔值用到的是对于数值,用到的是当然还有但是对于隐式类型转换的时候,调用的是前者。 javaScript类型转换规则 javaScript的类型转换其实一直是很多前端开发人员很迷的地方,一会儿这里要转换,一会儿那里又要转换,总之就是一个大写的迷,因为它隐式类型转换的地方实在是太多了。 但其实...
摘要:下面分几步来简单的探探不同类型的转换吧以下的内容,都可以从权威指南中找到。其他值转换成在编写代码的过程中,几乎不用考虑它的取值类型。核心内置类,会尝试先于可以理解为对象优先转换成数字例外的是,利用的是转换。 最近在写公司的登录注册模块,遇到类型不同相比较的时候,就心惊胆战,每次都要用浏览器来验证一下,决定乱七八糟的随便写一下,方便日后自己回顾知识~ 弱类型带来的那些让人迷糊的事 弱类型...
阅读 1728·2021-09-23 11:34
阅读 2423·2021-09-22 15:45
阅读 12566·2021-09-22 15:07
阅读 2167·2021-09-02 15:40
阅读 4058·2021-07-29 14:48
阅读 1034·2019-08-30 15:55
阅读 3218·2019-08-30 15:55
阅读 2169·2019-08-30 15:55