面试官:[1] == '1'和[1] == 1结果是什么?

1.前言

arduino 复制代码
console.log([1] == 1);          // true 
console.log([1] == '1');        // true 

小伙们,蒙对了么?是不是脑袋里面一个大大的问号。

2.什么是隐式类型转换

上面的面试题虽然简简单单两行,包含的知识点其实就是我们前端程序员都熟悉的隐式类型转换

隐式类型转换是指编译器自动完成类型转换的方式,总是期望转成基本类型的值

3.触发隐式类型转换的场景

  • 算术运算符(+, -, *, /, %)
  • 关系运算符<、>、 <=、 >= 、== 、!=等
  • 逻辑运算符!、&&、||
  • 条件语句if、while、三目运算符
  • 属性键遍历,比如for...in
  • 模板字符串${}

今天重点说一下双等号的基础类型的隐式转换 ,和对象类型的隐式转换

4.基础类型的隐式转换

4.1 ==比较基础类型的隐式转换

js 复制代码
// null 和 undefined
console.log(null == undefined);  // true
console.log(null == 0);         // false
console.log(undefined == '');    // false

// 数字 vs 字符串
console.log(0 == '');           // true ('' → 0)
console.log(1 == '1');          // true ('1' → 1)
console.log(1 == '1.0');        // true
console.log(1 == '1abc');       // false ('1abc' → NaN)

// 布尔值 vs 其他
console.log(true == 1);         // true (true → 1)
console.log(false == 0);        // true (false → 0)
console.log(true == '1');       // true (true→1, '1'→1)
console.log(false == '');       // true (false→0, ''→0)

// 特殊情况
console.log(NaN == NaN);        // false
console.log([] == ![]);         // true (![] → false → 0, [] → '' → 0)

其他能够引发基础类型的隐式转换的场景还有很多,后面继续补充

5. 对象隐式转换规则

对象隐式转换主要三要素: Symbol.toPrimitiveObject.prototype.valueOf()Object.prototype.toString()

  1. 如果对象定义了Symbol.toPrimitive方法,那会优先调用,无视valueOf()和toString()方法
  2. 如果对象未定义Symbol.toPrimitive方法,那会根据期望的基本类型进行隐式转换,如果期望类型是string,就会先调用obj.toString(),如果没有得到原始值,则继续调用obj.valueOf()方法,返回原始值
  3. 如果对象未定义Symbol.toPrimitive方法,那会根据期望的基本类型进行隐式转换,如果期望类型是number,就会先调用obj.valueOf(),如果没有得到原始值,则继续调用obj.toString()方法,返回原始值
  4. 如果对象未定义Symbol.toPrimitive方法,期望值是基础类型(比如:string),同时toString()和valueOf都没有返回原始值,会直接报错
javascript 复制代码
const obj = {
    age: 10,
    valueOf() {
        return this
    },
    toString() {
        return this
    }
}
console.log(obj + '') //Cannot convert object to primitive value

5.1 Symbol.toPrimitive

The Symbol.toPrimitive well-known symbol specifies a method that accepts a preferred type and returns a primitive representation of an object. It is called in priority by all type coercion algorithms.-来自MDN

理解:Symbol.toPrimitive是可以接受编译器指定的期望基本类型并返回对象原始值的方法。在转换基本类型过程中,总是被优先调用。

Symbol.toPrimitive有一个参数hint,hint在规范协议里面默认是'default',有三个可选值:'string'、'number'、'default'。hint会根据期望值去选定参数

5.1.1 触发hint是string的情况

  • window.alert(obj)
  • 模板字符串:${obj}
  • 成为的对象属性:test[obj] = obj1
javascript 复制代码
const obj = {
    [Symbol.toPrimitive](hint){
        if(hint === 'number') {
            return 10
        } else if(hint === 'string') {
            return 'hello'
        }
        return true
    }
}
window.alert(obj) // hello
console.log(`${obj}`) // hello
obj[obj] = 1
console.log(Object.keys(obj)) //['hello']

5.1.2 触发hint是number的情况

  • 一元+、位移运算
  • -减法、*乘法、/除法、关系运算符(<、>、 <=、 >= 、== 、!=)
  • Math.pow、String.prototype.slice等内部方法
javascript 复制代码
const obj = {
    [Symbol.toPrimitive](hint){
        if(hint === 'number') {
            return 10
        } else if(hint === 'string') {
            return 'hello'
        }
        return true
    }
}
console.log('一元+:',+obj) //一元+: 10
console.log('位移:',obj >> 0) //位移: 10

console.log('减法:',5-obj) //减法: -5
console.log('乘法:',5*obj) //乘法: 50
console.log('除法:',5/obj) //除法: 0.5

console.log('大于:',5>obj) //大于: false
console.log('大于等于:',5>=obj) //大于等于: false

console.log('Math.pow:',Math.pow(2,obj)) //Math.pow: 1024
console.log('内部方法:','abcdefghijklmnopq'.slice(0,obj)) //内部方法: abcdefghij

5.1.3 触发hint是default的情况

  • 二元+
  • ==、!=
javascript 复制代码
const obj = {
    [Symbol.toPrimitive](hint){
        if(hint === 'number') {
            return 10
        } else if(hint === 'string') {
            return 'hello'
        }
        return true
    }
}
console.log(5 + obj) //6
console.log(5 == obj) //false
console.log(0 != obj) // true

首先会返回default时候的原始值,若没有再默认调用vlaueOf(除了Date对象)

javascript 复制代码
const obj = {
    [Symbol.toPrimitive](hint){
        if(hint === 'number') {
            return 10
        } else if(hint === 'string') {
            return 'hello'
        }
        // return true
    }
}
console.log(obj.valueOf()) //NaN
console.log(1+obj) //NaN

5.2 Object.prototype.valueOf

在未定义Symbol.toPrimitive方法时候,期望类型是number,所以会直接执行valueOf()方法

javascript 复制代码
const obj = {
    age: 10,
    name: '小明',
    toString() {
        return this.name
    },
    valueOf() {
        return this.age
    }
}
console.log(+obj) // 10

若是定义的valueOf方法返回的不是原始值,会继续执行toString方法

javascript 复制代码
const obj = {
    age: 10,
    name: '小明',
    toString() {
        return this.name
    },
    valueOf() {
        return this
    }
}
console.log(+obj) // NaN

5.3 Object.prototype.toString

在未定义Symbol.toPrimitive方法时候,期望类型是string,所以会直接执行toString()方法

javascript 复制代码
const obj = {
    age: 10,
    name: '小明',
    toString() {
        return this.name
    },
    valueOf() {
        return this.age
    }
}
console.log(`${obj}`) // 小明
obj[obj] = 1
console.log(Object.keys(obj)) //[ 'age', 'name', 'toString', 'valueOf', '小明' ]

若是定义的toString方法返回的不是原始值,会继续执行valueOf方法

javascript 复制代码
const obj = {
    age: 10,
    name: '小明',
    toString() {
        return this
    },
    valueOf() {
        return this.age
    }
}
console.log(`${obj}`) // 10

若是没有定义toString方法,会继续执行原型上的toString的方法返回原始值

javascript 复制代码
const obj = {
    age: 10,
    name: '小明',
    // toString() {
    //     return this.name
    // },
    valueOf() {
        return this.age
    }
}
console.log(`${obj}`) // [object Object]
//如果将原型上的toString重置,就会去执行valueOf方法
Object.prototype.toString = undefined
console.log(`${obj}`) // 10

6. 特殊对象Date

hint是default的时候,会优先调用toString,,然后在调用valueOf。

hint已经表明期望值是string或者number的时候,还是按照期望的进行转换

sql 复制代码
const date = new Date()
console.log('date:toString',`${date}`) //date:toString Mon Dec 05 2022 21:53:18 GMT+0800 (中国标准时间)
console.log('date:valueOf',+date) //date:valueOf 1670248398283

console.log('date:default',date + 1) //date:default Mon Dec 05 2022 21:53:18 GMT+0800 (中国标准时间)1

7. 总结

看完文章知道这个面试题的原因了么?

lua 复制代码
console.log([1] == 1);          // true [1] -> '1' -> 1
console.log([1] == '1');        // true [1] -> '1'
  • console.log([1] == 1)
    1. 触发对象隐式类型转换的期望值是 number,但是数组继承了 Object.prototype.valueOf 的默认行为,是返回对象本身
    2. 上一步结果不是原始值会继续调用 toString方法,从 [1] 得到字符串类型 '1'
    3. 基础类型的隐式转换,数字与字符串类型进行比较,则字符串类型转数字,从 '1' 转成 数字类型 1
    4. 最后类型相同值相同 1 == 1 ,返回为 true
  • console.log([1] == '1')
    1. 触发对象隐式类型转换的期望值是 number,但是数组继承了 Object.prototype.valueOf 的默认行为,是返回对象本身
    2. 上一步结果不是原始值会继续调用 toString方法,从 [1] 得到字符串类型 '1'
    3. 基础类型的隐式转换,字符串与字符串类型进行比较,类型相同值相同 '1' == '1' ,返回为 true
相关推荐
萌萌哒草头将军3 小时前
尤雨溪宣布 oxfmt 即将发布!比 Prettier 快45倍 🚀🚀🚀
前端·webpack·vite
weixin_405023373 小时前
webpack 学习
前端·学习·webpack
云中雾丽3 小时前
flutter中 Future 详细介绍
前端
求知呀3 小时前
服务器状态管理 Vue Query
前端
在下Z.4 小时前
前端基础--css(1)
前端·css
常在士心4 小时前
Flutter项目支持鸿蒙环境
前端
重生之我要当java大帝4 小时前
java微服务-尚医通-管理平台前端搭建-医院设置管理-4
java·开发语言·前端
用户59561957545234 小时前
Vue-i18n踩坑记录
前端
WindrunnerMax4 小时前
从零实现富文本编辑器#8-浏览器输入模式的非受控DOM行为
前端·前端框架·github