前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

从206个console.log完全弄懂数据类型转换的前世今生下|博客系列

# 1. 重写toString和valueOf

看完了上篇的对象转字符串,不知道你对toPrimititve的转换流程掌握了多少呢?

如果你感觉之前的那些例子还不太具有说明性,也就是说你还是没有感觉到JS确实是按我画的那个流程图来进行转换的话,你可以看看这里。

我们在上篇的6.1中提到过了,大部分的对象都是可以通过原型链查找到Object.prototype上的toString或者valueOf方法,然后使用它们。

但是你想想,如果我这个对象本身就有toString或者valueOf方法的话是不是就可以不用Object.prototype上的了,这其实就是我们常听到的重写。

你也许可以用这样的方式来覆盖原型链上的这两个方法:

let obj = {
    toString () {
        return '1'
    },
    valueOf () {
        return 1
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

甚至你还可以直接修改Object.prototype上的方法:

Object.prototype.toString = function () {
  return 1
}
var b = {}
console.log(String(b)) // '1'
@前端进阶之旅: 代码已经复制到剪贴板

(当然,这种肯定是不推荐的哈,这会影响所有的对象)

# 1.1 题目一

(通过重写toString()和valueOf()来判断我们之前的toPrimitive流程是否正确)

上面👆两个例子我只是想告诉你,既然我们可以重写对象上的toString()和valueOf,那如果我们在重写的函数里面再加上console.log(xxx),不就可以知道对象转原始值的具体过程是不是按我们设想的方式执行下去了吗?

比如这样:

var b = {
  toString () {
    console.log('toString')
    return 1
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
}
console.log(Number(b))
@前端进阶之旅: 代码已经复制到剪贴板

想一下这里的执行结果 🤔️?

既然是用Number()方法来进行转换的话,那也就是执行了伪代码toPrimitive(obj, 'number')了。

那也就是说会先调用valueOf()函数,然后判断这个函数的返回值是否为原始值,再决定是继续调用toString()还是返回。

  • 很显然,由于这里的valueOf()被重写了,所以调用valueOf()之后返回的是一个引用类型[1, 2],所以它会继续执行toString()
  • 也就是执行[1, 2].toString(),但是这时候的toString()也是被重写了的并且返回了数字1,所以我们根本没必要管[1, 2].toString()的结果了,而是直接将1返回。

所以整个过程结束之后,答案为:

'valueof'
'toString'
1
@前端进阶之旅: 代码已经复制到剪贴板

这样看下来流程就很清晰了,它确实是按照我们预期的方向走的。

# 1.2 题目二

如果你理解了上面一题的话,咱再来看看这里:

var b = {
  toString () {
    console.log('toString')
    return { name: 'b' }
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
}
console.log(String(b))
@前端进阶之旅: 代码已经复制到剪贴板

这次我是用的String()方法将b转为字符串。

而且要注意了,重写的toString()和valueOf都是返回的引用数据类型。那你可以想想到最后的结果会是什么吗?

来看看过程分析:

  • 执行重写的toString()方法,返回引用类型{name: 'b'}
  • 继续执行重写的valueOf()方法,返回引用类型[1,2]
  • 哇,很难受,都经过两轮转换了还是引用类型,得抛错了。

没错,这里的转换过程最终是失败了的,因为还记得流程图中,最后一步了若还不是原始值的话,就会抛异常了。

所以结果为:

'toString'
'valueOf'
Cannot convert object to primitive value at String
@前端进阶之旅: 代码已经复制到剪贴板

# 1.3 题目三

(数组在进行ToString时的不同之处)

我们都知道,当数组在进行转字符串的时候,会把里面的每一项都转为字符串然后再进行","拼接返回。

那么为什么会有","拼接这一步呢?难道toString()在调用的时候还会调用join()方法吗?

为了验证我的想法💡,我做了一个实验,重写了一个数组的join()方法:

var arr = [1, 2]
arr['join'] = function () {
  let target = [...this]
  return target.join('~')
}
console.log(String(arr))
@前端进阶之旅: 代码已经复制到剪贴板

重写的join函数中,this表示的就是调用的这个数组arr。

然后将返回值改为"~"拼接,结果答案竟然是:

"1~2"
@前端进阶之旅: 代码已经复制到剪贴板

也就是说在String(arr)的过程中,它确实是隐式调用了join方法。

但是当我们重写了toString()之后,就不会管这个重写的join了:

var arr = [1, 2]
arr['toString'] = function () {
  let target = [...this]
  return target.join('*')
}
arr['join'] = function () {
  let target = [...this]
  return target.join('~')
}
console.log(String(arr)) // "1*2"
@前端进阶之旅: 代码已经复制到剪贴板

可以看出toString()的优先级还是比join()高的。

现在我们又可以得出一个结论:

对象如果是数组的话,当我们不重写其toString()方法,在转换为字符串类型的时候,默认实现就是将调用join()方法的返回值作为toString()的返回值。

# 2. Symbol.toPrimitive

在我正为自己弄懂了toPrimitive而感到骄傲的时候,我得知了一个叫做Symbol.toPrimitive的家伙。

看这家伙的样子,让我想起了以前见到过的一些老大哥:Symbol.hasInstance、Symbol.toStringTag。

他们都有着酷酷的纹身:Symbol,并且之前的老大哥是能够让我们做一些自定义的事情,不知道这家伙是不是和我想的一样,也能够帮助我们重写toPrimitive 🤔️?

了解了事情的真相之后,我知道了自己还是不笨的,给猜对了。

Symbol.toPrimitive就是比重写toString()和valueOf()更屌的一个属性。

如果你在一个对象里重写了它的话,那么甚至都不会执行重写的toString()和valueOf()了。

(Symbol.toPrimitive也被叫做@@toPrimitive)

# 2.1 题目一

(Symbol.toPrimitive的基本使用-返回值为一个原始值)

var b = {
  toString () {
    console.log('toString')
    return { name: 'b' }
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] () {
    console.log('symbol')
    return '1'
  }
}
console.log(String(b))
console.log(Number(b))
@前端进阶之旅: 代码已经复制到剪贴板

这道题中,我把刚刚提到的三个属性都给重写了,你感觉结果会是什么?

😄记住刚刚说的话,Symbol.toPrimitive的优先级是最高的,所以这里只会执行它里面的内容。

因此结果为:

'symbol'
'1'
'symbol'
1
@前端进阶之旅: 代码已经复制到剪贴板

并且大家可以看到,虽然Symbol.toPrimitive的返回值是"1",但是最终的结果String(b)还是字符串,Number(b)还是数字,表明,最后还是会给返回值做一层对应的转换的。

# 2.2 题目二

(Symbol.toPrimitive的返回值为引用类型,或者没有返回值?)

如果它的返回值是引用类型,或者干脆没有返回值,就会继续执行valueOf或者toString吗?

结果并不会…来看看这里,我定义了对象b和c,并且重写了这三个属性:

var b = {
  toString () {
    console.log('b.toString')
    return { name: 'b' }
  },
  valueOf () {
    console.log('b.valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] () {
    console.log('b.symbol')
  }
}
var c = {
  toString () {
    console.log('c.toString')
    return { name: 'c' }
  },
  valueOf () {
    console.log('c.valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] () {
    console.log('c.symbol')
    return [1, 2]
  }
}
console.log(String(b))
console.log(String(c))
@前端进阶之旅: 代码已经复制到剪贴板

执行结果:

'b.symbol'
'undefined'
'c.symbol'
TypeError: Cannot convert object to primitive value
    at String
@前端进阶之旅: 代码已经复制到剪贴板

过程分析:

  • String(b) 过程中打印出了b.symbol,说明还是执行了Symbol.toPrimitive方法的,但是这个方法并没有返回值,且也没有继续执行valueOf()或者toString()了,而是返回了字符串"undefined"
  • String(c)过程中也打印了c.symbol,但是Symbol.toPrimitive的返回值是一个对象,却报错了。

所以从这道题,我们可以看出:

Symbol.toPrimitive它可谓是一夫当关,万夫莫开,只要有它在,就不会继续往下走了,它的返回结果就是作为最终的返回结果。

而且通过String(c)我们可以看出来:如果返回的是一个对象的话,也不会继续执行valueOf()、toString()了,而是判断它的返回值,如果是原始值那就返回,否则就抛出错误。

# 2.3 题目三

(带参数的Symbol.toPrimitive)

你以为Symbol.toPrimitive仅仅是这么简单吗?

No😺,它竟然还能接收参数!!!

它接收一个字符串类型的参数:hint,表示要转换到的原始值的预期类型。

且参数的取值为以下字符串的其中一个:

  • "number"
  • "string"
  • "default"

嗯😺?我一惊,这怎么和之前介绍的toPrimitive那么像啊:

toPrimitive(obj, 'number')
toPrimitive(obj, 'string')
@前端进阶之旅: 代码已经复制到剪贴板

也就是说传入了之后,就是告诉Symbol.toPrimitive要转换成哪个类型咯?

这么屌的功能,赶紧来试试:

← 从206个console.log完全弄懂数据类型转换的前世今生上数组扁平化的六种实现方式 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合