闹心的变量
数字、字符串、null、对象挤进同一个小盆,刚见面就吵翻了。加号一落下,到底相加还是拼接?JavaScript 老大的类型转换器开始冒烟……
前言
本文承接上一篇:《JS 的本质?复读机?忍者!》。
“躺好吧!” JavaScript 老大拿起一旁的咖啡,对函数说道。
“好!”
JavaScript 老大按下工作台旁的按钮,函数眼中射出两道光芒,投在工作台前的白幕上。工作台一旁出现了一个小盆,里面空空如也。
“来吧,开始执行!” 老大狠狠抿了一口咖啡,按下工作台侧边的按钮。
嘿!你好
工作台上方的白幕上出现了函数保存的第一行代码:
let num1 = 1;
老大大手一挥,造了一个新数据 1,将它分配到变量大厅的 0x007 号单间。与此同时,工作台旁的小盆里出现了一个身影,头上标着 num1,身上写着 0x007。
“嘿!小不点,你好呀!” 老大对刚出现的身影挥了挥手。
“你才是小不点!” num1 挥了挥那只小手,呛了老大一句。
老大嘿嘿一笑,摇了摇头。“不管你了,继续执行!”说着,再次按了下按钮。
白幕上出现了第二行代码:
let num2 = 2;
“又是一个赋值操作啊!简单!” 大手一挥,小盆内又蹦出一个身影,头上标着 num2,身上写着 0x008。
“嘿!你好呀!” num2 看到 num1 出现后便脱口而出:“天王盖地虎!”
“小鸡炖蘑菇!” num1 相视一笑。把 num2 拉到了身边。
“继续!” 老大没有理会小盆里的 num1 和 num2。
融合
let num3 = num1 + num2;
“终于有点难度了。num1、num2,过来干活。” 老大朝小盆说道。
“好嘞,有何吩咐?”
“过来吧,让我看看你们的类型。都是数字,OK。” 老大从工作台上拿起累加器,把 num1 和 num2 丢了进去。不一会儿,累加器给出结果 3。老大大手一挥,小盆里又蹦出一个头上标着 num3 的身影。
“快去见见你们的新伙伴吧!” 老大朝着 num1 与 num2 说道。
“天王盖地虎!” num1 与 num2 对新来的小伙伴说道。
“小鸡炖蘑菇!”
“嘿嘿,是我们的人!”
争议
let prefix = "question";
“赋值操作,简单。” 老大刚说完,小盆中又蹦出一个身影,头上标着 prefix。
“喂喂,那个新来的,天王盖地虎!” num1 对 prefix 喊道。
“宝塔镇河妖????” prefix 满脸的疑问。
“你别过来!非我族人,哪儿凉快哪儿呆着去!” num1 拉了拉旁边的 num2 与 num3。
prefix 还在蒙圈中,下一句代码随之而来:
let question = prefix + num1;
“嘿,prefix 和 num1 过来,配合一下,求个值!” 老大说道。
“什么!我要和它一起求值?非我族类,弄不了!” num1 恶狠狠地说道,“求值是不可能求值的!这辈子都不会和它一起求值,抛错吧!”
“...” prefix 一脸委屈,继而转为愤怒:“你以为你谁啊!谁愿意与你一起,个渣渣!”
“行了!让你们配合的是我,看把你们能的!” 说着,老大拿出一旁的类型转换器,自言自语道:“+ 运算到底怎么转换来着?让我看看说明书:JavaScript 数据类型和数据结构。”
+会先把对象转换为原始值。如果转换后的任一操作数是字符串,就执行字符串拼接;否则执行数值加法。BigInt与其他数值类型混算时还有额外限制。
“现在一边是字符串,所以要执行字符串拼接。得嘞,num1 你过来,我把你转换成字符串!”
说着,老大拿出 String 转换器,放入 num1(String(num1))。一顿操作后,蹦出一个字符串:"1"。
“回去吧!接下来没你的事了!” 老大对 num1 说道。
“接下来就好办了,字符串拼接。” 说着,老大拿出字符串拼接器,放入 prefix 和刚生成的 "1"。不一会儿便生成了值为 "question1" 的 question。
“过来!过来!那边那些变量可不咋地!” prefix 对 question 说道。
“我刚刚都瞧见了!个渣渣!” 说着 question 走向了 prefix。
“对,同类的感觉就是不一样。” prefix 兴奋地说道。
各自为营
let null1 = null, null2 = null;
转眼间,小盆里又出现了两个身影。
“个渣渣?” prefix 对着 null1 与 null2 说道。
“?? 你才渣渣!” null1 有点恼怒。
“哦!不好意思,认错人了!我只是想对对暗号。” prefix 略带歉意地说道。
“天王盖地虎?” prefix 刚说完,num1 紧接着问道。
“宝塔镇河妖?” null1 有点不知所措。
“你别过来!非我族人,哪儿凉快哪儿呆着去!” num1 脱口而出。
null1 终于怒了,大喊道:“水能载舟!”
“也能煮粥!” 一旁的 null2 脱口而出。
“走,我们去那边!一个个都觉得自己了不起,谁还没个暗号!”
看着小盆里快要闹翻了天,老大终于忍不住了:“你们一个个的干嘛呢!要反了不成!”
“非我族类,其心必异!” 变量们突然统一了口径。
“呦呵,这倒挺齐!”
“谁和它们齐了!” 虽然这么说着,但这句依旧齐的不像话。
“停停停!我把这个盆分成八间:Number、String、Null、Undefined、Boolean、BigInt、Symbol 和 Object,这下行了吧!”
“行!”
“你们还学!”
“还学!”
“哼!”
“哎……” 老大拍了拍日渐稀疏的头发,“真是些冤家!”
对象
let obj = {
toString() {
return "this is a string";
},
valueOf() {
return 1;
}
};
随着代码的执行,Object 单间终于迎来一个变量:obj。
let sum = num1 + obj;
“num1 你过来,obj 你也过来。这是个求和操作!”
“这次希望我转换成什么原始值?” obj 问了一句。
“你还懂原始值转换?” 老大一惊。
“对啊,我可是对象,什么都知道,说吧说吧!我还想着装饰下我的房间呢!快点。”
“emmmm,普通对象遇到 + 时会使用 default 提示。你没有定义 Symbol.toPrimitive,所以通常先试 valueOf(),再试 toString()。” 老大看着类型转换说明念了出来。
“了解了,也就是先让我变成原始值,再决定做拼接还是加法,对吧?” obj 问道。
“嗯,是的。”
“好,那我调用 valueOf(),结果是数值 1。现在两个操作数都是数字,可以确定做加法了吧!”
“嗯,是数值!”
“接下来该怎么做,你应该清楚了吧,我先走了!” 说完,obj 留下目瞪口呆的 老大。老大拿着它给出的数值 1,生成了值为 2 的 sum。
“这个对象有点厉害,省心!”
质疑
let mergeStr = question + obj;
“嘿,obj,左边已经是字符串了,这次结果肯定是字符串拼接!” 老大得意地说。
“结果虽然会是字符串,但你仍然得先把我转换成原始值。”
“是啊,左边不是已经有字符串了吗?”
“那也不能跳过原始值转换。这里仍是 default 提示,所以我先调用 valueOf();如果它没有返回原始值,再调用 toString()。”
“哈?你确定?”
“你到底是不是个称职的 JavaScript 解释器啊?按规范来。”
“哦···” 突然的质疑让老大略感惭愧。
“这个给你。” obj 把数值 1 递给 老大,“接下来再判断该做字符串拼接还是数值加法吧,我走了。”
“好,你慢走。” 老大突然有点泄气,但手上的活依旧没停。不一会儿,值为 "question11" 的 mergeStr 出现在小盆中。
顺畅
let mergeStr2 = `${question}${obj}`;
“模板字符串会对插值执行字符串转换。这次提示就是 string,给我一个原始值吧!” 老大自信地对 obj 说道。
“好,既然提示是 string,那就先调用我的 toString()。这个给你!” 说着,obj 将 "this is a string" 塞到 老大手中。
老大想了想,问道:“你有没有可能给我一个非字符串的原始值?”
“有可能!但原始值转换返回的一定是原始值,你再把它转换成字符串就行。我撤啦!”
“得嘞,又是个大爷!”
尾声
老大勤勤恳恳地继续执行代码,小盆里的变量进进出出,终于到了收尾的时候。
放下手中的工具,老大不禁叹道:“这函数是谁写的?不同类型之间做了这么多隐式转换,读起来也太费劲了。真要写业务代码,还是显式转换更清楚啊!”