闹心的变量

数字、字符串、null、对象挤进同一个小盆,刚见面就吵翻了。加号一落下,到底相加还是拼接?JavaScript 老大的类型转换器开始冒烟……

分享
闹心的变量

前言

本文承接上一篇:《JS 的本质?复读机?忍者!》

“躺好吧!” JavaScript 老大拿起一旁的咖啡,对函数说道。

“好!”

JavaScript 老大按下工作台旁的按钮,函数眼中射出两道光芒,投在工作台前的白幕上。工作台一旁出现了一个小盆,里面空空如也。

“来吧,开始执行!” 老大狠狠抿了一口咖啡,按下工作台侧边的按钮。

嘿!你好

工作台上方的白幕上出现了函数保存的第一行代码:

let num1 = 1;

老大大手一挥,造了一个新数据 1,将它分配到变量大厅的 0x007 号单间。与此同时,工作台旁的小盆里出现了一个身影,头上标着 num1,身上写着 0x007

“嘿!小不点,你好呀!” 老大对刚出现的身影挥了挥手。

“你才是小不点!” num1 挥了挥那只小手,呛了老大一句。

老大嘿嘿一笑,摇了摇头。“不管你了,继续执行!”说着,再次按了下按钮。

白幕上出现了第二行代码:

let num2 = 2;

“又是一个赋值操作啊!简单!” 大手一挥,小盆内又蹦出一个身影,头上标着 num2,身上写着 0x008

“嘿!你好呀!” num2 看到 num1 出现后便脱口而出:“天王盖地虎!”

“小鸡炖蘑菇!” num1 相视一笑。把 num2 拉到了身边。

“继续!” 老大没有理会小盆里的 num1num2

融合

let num3 = num1 + num2;

“终于有点难度了。num1num2,过来干活。” 老大朝小盆说道。

“好嘞,有何吩咐?”

“过来吧,让我看看你们的类型。都是数字,OK。” 老大从工作台上拿起累加器,把 num1num2 丢了进去。不一会儿,累加器给出结果 3。老大大手一挥,小盆里又蹦出一个头上标着 num3 的身影。

“快去见见你们的新伙伴吧!” 老大朝着 num1num2 说道。

“天王盖地虎!” num1num2 对新来的小伙伴说道。

“小鸡炖蘑菇!”

“嘿嘿,是我们的人!”

争议

let prefix = "question";

“赋值操作,简单。” 老大刚说完,小盆中又蹦出一个身影,头上标着 prefix

“喂喂,那个新来的,天王盖地虎!” num1prefix 喊道。

“宝塔镇河妖????” prefix 满脸的疑问。

“你别过来!非我族人,哪儿凉快哪儿呆着去!” num1 拉了拉旁边的 num2num3

prefix 还在蒙圈中,下一句代码随之而来:

let question = prefix + num1;

“嘿,prefixnum1 过来,配合一下,求个值!” 老大说道。

“什么!我要和它一起求值?非我族类,弄不了!” num1 恶狠狠地说道,“求值是不可能求值的!这辈子都不会和它一起求值,抛错吧!”

“...” prefix 一脸委屈,继而转为愤怒:“你以为你谁啊!谁愿意与你一起,个渣渣!”

“行了!让你们配合的是我,看把你们能的!” 说着,老大拿出一旁的类型转换器,自言自语道:“+ 运算到底怎么转换来着?让我看看说明书:JavaScript 数据类型和数据结构。”

+ 会先把对象转换为原始值。如果转换后的任一操作数是字符串,就执行字符串拼接;否则执行数值加法。BigInt 与其他数值类型混算时还有额外限制。

“现在一边是字符串,所以要执行字符串拼接。得嘞,num1 你过来,我把你转换成字符串!”

说着,老大拿出 String 转换器,放入 num1String(num1))。一顿操作后,蹦出一个字符串:"1"

“回去吧!接下来没你的事了!” 老大num1 说道。

“接下来就好办了,字符串拼接。” 说着,老大拿出字符串拼接器,放入 prefix 和刚生成的 "1"。不一会儿便生成了值为 "question1"question

“过来!过来!那边那些变量可不咋地!” prefixquestion 说道。

“我刚刚都瞧见了!个渣渣!” 说着 question 走向了 prefix

“对,同类的感觉就是不一样。” prefix 兴奋地说道。

各自为营

let null1 = null, null2 = null;

转眼间,小盆里又出现了两个身影。

“个渣渣?” prefix 对着 null1null2 说道。

“?? 你才渣渣!” null1 有点恼怒。

“哦!不好意思,认错人了!我只是想对对暗号。” prefix 略带歉意地说道。

“天王盖地虎?” prefix 刚说完,num1 紧接着问道。

“宝塔镇河妖?” null1 有点不知所措。

“你别过来!非我族人,哪儿凉快哪儿呆着去!” num1 脱口而出。

null1 终于怒了,大喊道:“水能载舟!”

“也能煮粥!” 一旁的 null2 脱口而出。

“走,我们去那边!一个个都觉得自己了不起,谁还没个暗号!”

看着小盆里快要闹翻了天,老大终于忍不住了:“你们一个个的干嘛呢!要反了不成!”

“非我族类,其心必异!” 变量们突然统一了口径。

“呦呵,这倒挺齐!”

“谁和它们齐了!” 虽然这么说着,但这句依旧齐的不像话。

“停停停!我把这个盆分成八间:NumberStringNullUndefinedBooleanBigIntSymbolObject,这下行了吧!”

“行!”

“你们还学!”

“还学!”

“哼!”

“哎……” 老大拍了拍日渐稀疏的头发,“真是些冤家!”

对象

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,生成了值为 2sum

“这个对象有点厉害,省心!”

质疑

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" 塞到 老大手中。

老大想了想,问道:“你有没有可能给我一个非字符串的原始值?”

“有可能!但原始值转换返回的一定是原始值,你再把它转换成字符串就行。我撤啦!”

“得嘞,又是个大爷!”

尾声

老大勤勤恳恳地继续执行代码,小盆里的变量进进出出,终于到了收尾的时候。

放下手中的工具,老大不禁叹道:“这函数是谁写的?不同类型之间做了这么多隐式转换,读起来也太费劲了。真要写业务代码,还是显式转换更清楚啊!”