装箱操作: 把基本数据类型转换为对应的引用类型的操作
拆箱操作: 把引用类型转换为基本数据类型的操作
上面两个概念只是简洁的实质总结,可能看完后大家心里会产生疑问,到底怎么样叫装箱操作和拆箱操作呢?下面我们一起带着这个疑问往下看~~
装箱操作
首先我们要知道在js中有三个基本包装类型:
- Number
- String
- Boolean
下面看一个例子:
var str="hello world";
var strRes=str.split(" ");
console.log(strRes) //["hello", "world"]
如上面代码所示,变量str是一个基本类型值,不是一个对象,就不存在方法,但上面代码却显示可以正常调用方法。实际上这一切都是js内部做了以下处理:
- (1)创建String类型的一个实例;
- (2)在实例上调用指定的方法;
- (3)销毁这个实例;
转换为对应代码就是:
var str=new String("hello world");
var strRes=str.split(" ");
str=null;
其实,说白了就是临时创建了一个对象,然后去调用方法。下面这句话引用自《javascript高级程序设计》一书中
每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。
拆箱操作
拆箱操作中主要有两个方法:valueOf()方法和toString()方法。这两个方法主要用来检测你返回的是不是一个基本类型的值。一般是先用valueOf()来检测,如果返回的不是一个基本类型的值,是对象自身,则会继续用toString()来检测,如果检测结果不是一个基本类型的值,则会报错(Uncaught SyntaxError: Invalid or unexpected token)。以下是两个方法的具体描述,引用自MDN。
valueOf
- valueOf() 方法返回指定对象的原始值。
- JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
- 默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。
- JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同
toString
- toString() 方法返回一个表示该对象的字符串。
- 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
- 默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。
下面通过代码来分析具体什么叫做拆箱操作:
[]+[] //""
{}+{} //"[object Object][object Object]"
[]+{} //"[object Object]"
{}+[] //0
如上面代码所示,右边注释的是其对应的结果,下面逐个分析其中的原理:
- (1)[]+[],[]自身是一个空数组,即是一个对象,[]会先被valueOf()检测返回自身,还是[],然后使用toString()检测返回空字符"",实际最终是""+"",所以最终结果还是一个""。
- (2){}+{},在js中{}可以表示一个代码块,也可以表示一个对象。在此处作为一个对象来运算,({}).valueOf()检测结果为自身,继续检测,({}).toString()检测结果为"[object Object]",所以{}+{}相当于"[object Object]"+"[object Object]",故结果为"[object Object][object Object]"。
- (3)[]+{},从上面分析可以知道,这个相当于""+"[object Object]",所以结果为"[object Object]"。
- (4){}+[],上面三个或许大多数人都能明白,但这个估计就会有人有疑问了,为什么会是0呢?首先,这里的{}被当做了代码块,由于编译原理底层一些机制,会涉及到词法分析、语法分析、语义分析、代码生成这些知识,这里+[]相当于+"",因为运算符+的原因,会将+""隐式转换为+0,所以结果最终为0。
相信大家到这里应该基本明白什么是装箱操作,什么是拆箱操作了吧!
拓展
通过上面的学习,基本原理大家应该都已经知道了,在这留一个例题,大家可以将自己的分析或想法写在评论区,看看是不是真正的理解了其中的知识,例题如下(结果直接告诉大家了,具体还是需要自己去一步一步分析,才能真正掌握理解,我相信大家都是很厉害的哦!!!):
([][[]]+[])[+!![]]+([]+{})[!+[]+!![]] //"nb"
!! 一般用来将后面的表达式强制转换为布尔类型的数据(boolean),只能是true or false.
例:!![] //true,[]自身就是对象,会先转换成true,![]会将其转换为false,!![]再将其转换为true,实际上就是自身的布尔值.
————————————————
版权声明:本文为CSDN博主「DLGDark」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DLGDark/article/details/100836377
上面拓展中的例题分析:
知识点一: javascript字符串下标获取元素(涉及装箱)
js我们一般都是通过字面量的方式创建
String
var str = "abc"
当操作字符串时会转换为字符串对象var str = new String('abc');
js中String类的实例,是一个类数组。
所以你可以通过str[0]获取一个String对象中键名为0的值。
而charAt是String类的一个api。
两者区别:
下标不存在时,str[index] 会返回 undefined (未定义), str.charAt(index)会返回""(空字符串)
字符串是一个类似数组的对象,有
length
属性,可以通过数值键(0,1)取值;str[i]
与str.charAt(i)
本质上没有区别,只是str.charAt(i)
更标准而已
知识点二:运算符优先级
下面的表将所有运算符按照优先级的不同从高(20)到低(1)排列。
优先级 运算类型 关联性 运算符 20 圆括号
n/a(不相关) ( … )
19 成员访问
从左到右 … . …
需计算的成员访问
从左到右 … [ … ]
new
(带参数列表)n/a new … ( … )
函数调用 从左到右 … ( … )
可选链(Optional chaining) 从左到右 ?.
18 new (无参数列表) 从右到左 new …
17 后置递增(运算符在后) n/a
… ++
后置递减(运算符在后) … --
16 逻辑非 从右到左 ! …
按位非 ~ …
一元加法 + …
一元减法 - …
前置递增 ++ …
前置递减 -- …
typeof typeof …
void void …
delete delete …
await await …
15 幂 从右到左 … ** …
14 乘法 从左到右
… * …
除法 … / …
取模 … % …
13 加法 从左到右
… + …
减法 … - …
12 按位左移 从左到右 … << …
按位右移 … >> …
无符号右移 … >>> …
11 小于 从左到右 … < …
小于等于 … <= …
大于 … > …
大于等于 … >= …
in … in …
instanceof … instanceof …
10 等号 从左到右
… == …
非等号 … != …
全等号 … === …
非全等号 … !== …
9 按位与 从左到右 … & …
8 按位异或 从左到右 … ^ …
7 按位或 从左到右 … | …
6 逻辑与 从左到右 … && …
5 逻辑或 从左到右 … || …
4 条件运算符 从右到左 … ? … : …
3 赋值 从右到左 … = …
… += …
… -= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
… |= …
2 yield 从右到左 yield …
yield* yield* …
1 展开运算符 n/a ...
…0 逗号 从左到右 … , …
知识点三:+号的运算逻辑 (涉及拆箱)
进行 + 号运算时会将 + 号两侧的操作对象转化为原始对象(此过程实际上由javaScript引擎内部的抽象操作 ToPrimitive(input [, PreferredType]) 函数执行,当某个对象出现在了需要原始类型才能进行操作的上下文时,JavaScript 会自动调用 ToPrimitive 函数将对象转化为原始类型)。常见的 + 号运算逻辑有以下两种:
(1)字符串连接符+ :会把其他数据类型调用String()方法转成字符串然后拼接;
(2)算数运算符+ :会把其他数据类型调用Number()方法转成数字然后做加法计算;
+ 号被当做 字符串连接符 时的情况:
- + 号两侧的操作数至少一个为字符串;
- 其中一个为非数值类型或非字符串类型,且调用toPrimitive(input,Number)方法得到String类型结果
+ 号被当做 算术运算符 时的情况也分为两种:
- + 号两侧的操作数均为数字
- 其中一个为非数值类型或非字符串类型,且调用toPrimitive(input,Number)方法得到Number类型结果;
知识点四:逻辑非隐式转换
逻辑非:将其他数据类型使用Boolean()转成布尔类型 ( !val 的运算逻辑是:先将 val 转换为布尔型,再取反)
以下八种情况转换为布尔型会得到false:
- 0
- -0
- NaN
- undefined
- null
- ""(空字符串)
- false
- document.all()
除以上八种情况之外所有数据都会得到true。
因此对表达式:
([][[]]+[])[+!![]]+([]+{})[!+[]+!![]]
分析如下:
(1)圆括号优先级最高,因此先计算圆括号中的表达式,先看第一个圆括号中的表达式:
([][[]]+[]) == "undefined"
// 根据运算符优先级先运算 + 号两侧操作数,
// 先看左侧的操作数,从左至右,[]即为空数组,其后跟随[[]],则外层[]表示需计算的成员访问,因为[]为空数组,因此访问其成员得到的结果为 undefined,
// + 号右侧的[]通过toPrimitive(input,Number)方法得到的计算结果为 [].valueOf().toString() == '',
//此时的计算表达式变为:undefined+'',根据 + 号运算规则,此时为字符串拼接操作,
//因此 ([][[]]+[]) 结果为:"undefined"
再看第二个圆括号中的表达式:
([]+{}) == "[object Object]"
//根据 + 号的运算规则,+ 号两侧的操作数通过toPrimitive(input,Number)方法得到的结果分别为 [].valueOf().toString() == "", ({}).valueOf().toString() == "[object Object]"
//因此 ([]+{}) 结果为 "[object Object]"
此时表达式已转化为:
"undefined"[+!![]]+"[object Object]"[!+[]+!![]]
(2)根据运算符优先级可知,上面的表达式即为:
"undefined"[+!![]] + "[object Object]"[!+[]+!![]]
则接下来继续计算中间 + 号两侧的操作数,首先看左边的操作数:
[+!![]] == [1];
"undefined"[+!![]] == "undefined"[1] == 'n';
//表达式中外层的[]表示需计算的成员访问,而[]内部的表达式 +!![] 则首先进行两次逻辑非的运算,得到 +true,根据 + 号运算规则其结果为 +true == 1,
//因此 "undefined"[+!![]] 即为 "undefined"[1],根据知识点一可知其结果为 'n',
//即 "undefined"[+!![]] 结果为 'n'
根据运算符优先级和左边操作数的计算规则,可得出右边的操作数结果为:
!+[]+!![] == 2;
"[object Object]"[!+[]+!![]] == "[object Object]"[2] == "b"
所以,最终的表达式结果为:
"n" + "b" == "nb"