欢迎来到思维库

思维库

全面分析toString与valueOf,并随手解决掉几道大厂必备面试题

时间:2025-11-05 15:57:52 出处:应用开发阅读(143)

基本上,全面所有JS数据类型都拥有这两个方法,分析null除外。并必备它们俩是随手位于原型链上的方法,也是解决为了解决javascript值运算与显示的问题。

valueOf 和 toString 几乎都是掉道大厂在出现操作符(+-*/==><)时被调用(隐式转换)。

toString

返回一个表示该对象的面试字符串,当对象表示为文本值或以期望的全面字符串方式被引用时,toString方法被自动调用。分析

1. 手动调用看看什么效果

嗯,并必备跟介绍的随手一样,没骗人,解决全部都转成了字符串。掉道大厂

比较特殊的面试地方就是,表示对象的全面时候,变成[object Object],表示数组的时候,就变成数组内容以逗号连接的字符串,相当于Array.join(,)。 

let a = {}  let b = [1, 2, 3]  let c = 123  let d = function(){ console.log(fn) } console.log(a.toString())   // [object Object]  console.log(b.toString())   // 1,2,3  console.log(c.toString())   // 123  console.log(d.toString())   // function(){ console.log(fn) } 

2. 最精准的类型判断

这种属于更精确的源码下载判断方式,在某种场合会比使用 typeof & instanceof 来的更高效和准确些。 

toString.call(()=>{})       // [object Function]  toString.call({})           // [object Object]  toString.call([])           // [object Array]  toString.call()           // [object String]  toString.call(22)           // [object Number]  toString.call(undefined)    // [object undefined]  toString.call(null)         // [object null]  toString.call(new Date)     // [object Date]  toString.call(Math)         // [object Math]  toString.call(window)       // [object Window] 

3. 什么时候会自动调用呢

使用操作符的时候,如果其中一边为对象,则会先调用toSting方法,也就是隐式转换,然后再进行操作。 

let c = [1, 2, 3]  let d = {a:2}  Object.prototype.toString = function(){      console.log(Object)  }  Array.prototype.toString = function(){      console.log(Array)      return this.join(,)   // 返回toString的默认值(下面测试)  }  Number.prototype.toString = function(){      console.log(Number)  }  String.prototype.toString = function(){      console.log(String)  }  console.log(2 + 1)  // 3  console.log(s)    // s  console.log(s+2)  // s2  console.log(c < 2)  // false        (一次 => Array)  console.log(c + c)  // "1,2,31,2,3" (两次 => Array)  console.log(d > d)  // false        (两次 => Object) 

4. 重写toString方法

既然知道了有 toString 这个默认方法,那我们也可以来重写这个方法 

class A {      constructor(count) {          this.count = count      }      toString() {          return 我有这么多钱: + this.count      }  }  let a = new A(100)  console.log(a)              // A {count: 100}  console.log(a.toString())   // 我有这么多钱:100  console.log(a + 1)          // 我有这么多钱:1001 

Nice.

valueOf

返回当前对象的原始值。

具体功能与toString大同小异,同样具有以上的自动调用和重写方法。

这里就没什么好说的了,主要为两者间的区别,有请继续往下看🙊🙊 

let c = [1, 2, 3]  let d = {a:2}  console.log(c.valueOf())    // [1, 2, 3]  console.log(d.valueOf())    // {a:2} 

两者区别

 共同点:在输出对象时会自动调用。  不同点:默认返回值不同,且存在优先级关系。

二者并存的情况下,在数值运算中,优先调用了valueOf,字符串运算中,优先调用了toString。

看代码方可知晓: 

class A {      valueOf() {          return 2      }      toString() {          return 哈哈哈      }  }  let a = new A()  console.log(String(a))  // 哈哈哈   => (toString)  console.log(Number(a))  // 2         => (valueOf)  console.log(a + 22)   // 222     => (valueOf)  console.log(a == 2)     // true      => (valueOf)  console.log(a === 2)    // false     => (严格等于不会触发隐式转换) 

结果给人的感觉是,企商汇如果转换为字符串时调用toString方法,如果是转换为数值时则调用valueOf方法。

但其中的 a + 22 很不和谐,字符串合拼应该是调用toString方法。为了追究真相,我们需要更严谨的实验。

 暂且先把 valueOf 方法去掉  class A {      toString() {          return 哈哈哈      }  }  let a = new A()  console.log(String(a))  // 哈哈哈     => (toString)  console.log(Number(a))  // NaN         => (toString)  console.log(a + 22)   // 哈哈哈22   => (toString)  console.log(a == 2)     // false       => (toString)   去掉 toString 方法看看  class A {      valueOf() {          return 2      }  }  let a = new A()  console.log(String(a))  // [object Object]    => (toString)  console.log(Number(a))  // 2                    => (valueOf)  console.log(a + 22)   // 222                => (valueOf)  console.log(a == 2)     // true                 => (valueOf) 

发现有点不同吧?!它没有像上面 toString 那样统一规整。对于那个 [object Object],我估计是从 Object 那里继承过来的,我们再去掉它看看。 

class A {      valueOf() {          return 2      }  }  let a = new A()  Object.prototype.toString = null;   console.log(String(a))  // 2        => (valueOf)  console.log(Number(a))  // 2        => (valueOf)  console.log(a + 22)   // 222    => (valueOf)  console.log(a == 2)     // true     => (valueOf) 

总结:valueOf偏向于运算,toString偏向于显示。

 在进行对象转换时,将优先调用toString方法,如若没有重写 toString,将调用 valueOf 方法;如果两个方法都没有重写,则按Object的toString输出。  在进行强转字符串类型时,将优先调用 toString 方法,强转为数字时优先调用 valueOf。  使用运算操作符的情况下,valueOf的优先级高于toString。

[Symbol.toPrimitive]

MDN:Symbol.toPrimitive 是亿华云计算一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

是不是有点懵???把它当做一个函数就行了~~

 作用:同valueOf()和toString()一样,但是优先级要高于这两者;  该函数被调用时,会被传递一个字符串参数

    hint

    ,表示当前运算的模式,一共有三种模式:

 string:字符串类型  number:数字类型  default:默认

下面来看看实现吧: 

class A {      constructor(count) {          this.count = count      }      valueOf() {          return 2      }      toString() {          return 哈哈哈      }      // 我在这里      [Symbol.toPrimitive](hint) {          if (hint == "number") {              return 10;          }          if (hint == "string") {              return "Hello Libai";          }          return true;      }  }  const a = new A(10)  console.log(`${a}`)     // Hello Libai => (hint == "string")  console.log(String(a))  // Hello Libai => (hint == "string")  console.log(+a)         // 10            => (hint == "number")  console.log(a * 20)     // 200           => (hint == "number") console.log(a / 20)     // 0.5           => (hint == "number")  console.log(Number(a))  // 10            => (hint == "number")  console.log(a + 22)   // true22      => (hint == "default")  console.log(a == 10)     // false        => (hint == "default") 

比较特殊的是(+)拼接符,这个属于default的模式。

划重点:此方法不兼容IE,尴尬到我不想写出来了~~

面试题分析

以下几道大厂必考的面试题,完美呈现出 toString 与 valueOf 的作用。

1. a===1&&a===2&&a===3 为 true

双等号(==):会触发隐式类型转换,所以可以使用 valueOf 或者 toString 来实现。

每次判断都会触发valueOf方法,同时让value+1,才能使得下次判断成立。 

class A {      constructor(value) {          this.value = value;      }      valueOf() {          return this.value++;      }  } const a = new A(1);  if (a == 1 && a == 2 && a == 3) {      console.log("Hi Libai!");  } 

全等(===):严格等于不会进行隐式转换,这里使用 Object.defineProperty 数据劫持的方法来实现 

let value = 1;  Object.defineProperty(window, a, {      get() {          return value++      }  })  if (a === 1 && a === 2 && a === 3) {      console.log("Hi Libai!")  } 

上面我们就是劫持全局window上面的a,当a每一次做判断的时候都会触发get属性获取值,并且每一次获取值都会触发一次函数实行一次自增,判断三次就自增三次,所以最后会让公式成立。

 注:defineProperty 可参考这篇文章学习,点我进入传送门  自:大厂面试题分享:如何让(a===1&&a===2&&a===3)的值为true?

2. 实现一个无限累加函数

问题:用 JS 实现一个无限累加的函数 add,示例如下: 

add(1); // 1  add(1)(2);  // 3  add(1)(2)(3); // 6  add(1)(2)(3)(4); // 10   // 以此类推  function add(a) {      function sum(b) { // 使用闭包          a = b ? a + b : a; // 累加          return sum;      }      sum.toString = function() { // 只在最后一次调用          return a;      }      return sum; // 返回一个函数  }  add(1)              // 1  add(1)(2)           // 3  add(1)(2)(3)        // 6  add(1)(2)(3)(4)     // 10    add函数内部定义sum函数并返回,实现连续调用  sum函数形成了一个闭包,每次调用进行累加值,再返回当前函数sum  add()每次都会返回一个函数sum,直到最后一个没被调用,默认会触发toString方法,所以我们这里重写toString方法,并返回累计的最终值a

这样说才能理解:

add(10): 执行函数add(10),返回了sum函数,注意这一次没有调用sum,默认执行sum.toString方法。所以输出10;

add(10)(20): 执行函数add(10),返回sum(此时a为10),再执行sum(20),此时a为30,返回sum,最后调用sum.toString()输出30。add(10)(20)...(n)依次类推。

3. 柯里化实现多参累加

这里是上面累加的升级版,实现多参数传递累加。 

add(1)(3,4)(3,5)    // 16  add(2)(2)(3,5)      // 12  function add(){      // 1 把所有参数转换成数组      let args = Array.prototype.slice.call(arguments)      // 2 再次调用add函数,传递合并当前与之前的参数      let fn = function() {          let arg_fn = Array.prototype.slice.call(arguments)          return add.apply(null, args.concat(arg_fn))      }      // 3 最后默认调用,返回合并的值      fn.toString = function() {          return args.reduce(function(a, b) {              return a + b          })      }      return fn  }  // ES6写法  function add () {      let args = [...arguments];      let fn = function(){          return add.apply(null, args.concat([...arguments]))      }       fn.toString = () => args.reduce((a, b) => a + b)      return fn;  } 

分享到:

上一篇:Nginx (engine-x)是一个开源的高性能 HTTP 服务器、反向代理和 IMAP/POP3 代理服务器。nginx 杰出的功能有:稳定、丰富的功能集、简单的配置和低资源消耗。nginx 被用于一些高性能网站并在站长之间变得越来越流行。本教程会从源码构建一个带有 google paespeed 模块的用于 Ubuntu 15.04 的 nginx .deb 安装包。pagespeed 是一个由 google 开发的 web 服务器模块来加速网站响应时间、优化 html 和减少页面加载时间。ngx_pagespeed 的功能如下: 图像优化:去除元数据、动态缩放、重压缩。 CSS 与 JavaScript 压缩、串联、内联、外联。 小资源内联 图像与 JavaScript 延迟加载 HTML 重写 缓存生命期插件前置要求 Ubuntu Server 15.04 64位 root 权限该文我们将要: 安装必备软件包 安装带 ngx_pagespeed 的 nginx 测试安装必备包复制代码代码如下:sudo apt-get install dpkg-dev build-essential zlib1g-dev libpcre3 libpcre3-dev安装带 ngx_pagespeed 的 nginx第一步 - 添加nginx仓库复制代码代码如下: vim /etc/apt/sources.list.d/nginx.list加入下面的行:复制代码代码如下:deb http://nginx.org/packages/ubuntu/ trusty nginx deb-src http://nginx.org/packages/ubuntu/ trusty nginx更新仓库:复制代码代码如下:sudo apt-get update注意:假如你看到信息:GPG error [...] NO_PUBKEY [...] 等等请添加key:复制代码代码如下:sudo sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys KEYNUMBER sudo apt-get update第二步 - 从仓库下载 nginx 1.8复制代码代码如下: sudo su cd ~ mkdir -p ~/new/nginx_source/ cd ~/new/nginx_source/ apt-get source nginx apt-get build-dep nginx第三步 - 下载 Pagespeed复制代码代码如下:cd ~ mkdir -p ~/new/ngx_pagespeed/ cd ~/new/ngx_pagespeed/ ngx_version=1.9.32.3 wget https://github.com/pagespeed/ngx_pagespeed/archive/release-${ngx_version}-beta.zip unzip release-${ngx_version}-beta.zip cd ngx_pagespeed-release-1.9.32.3-beta/ wget https://dl.google.com/dl/page-speed/psol/${ngx_version}.tar.gz tar -xzf 1.9.32.3.tar.gz第四步 - 配置 nginx 来编译 Pagespeed复制代码代码如下:cd ~/new/nginx_source/nginx-1.8.0/debin/ vim rules在两处 CFLAGS .configure 下添加模块: --add-module=../../ngx_pagespeed/ngx_pagespeed-release-1.9.32.3-beta adding pagespeed to nginx adding pagespeed to nginx  第五步 - 打包 nginx 软件包并安装复制代码代码如下:cd ~/new/nginx_source/nginx-1.8.0/ dpkg-buildpackage -bdpkg-buildpackage 会编译 ~/new/ngix_source/ 为 nginx.deb。打包完成后,看一下目录:复制代码代码如下:cd ~/new/ngix_source/ ls nginx builded with pagespeed    接着安装 nginx。复制代码代码如下:dpkg -i nginx_1.8.0-1~trusty_amd64.deb    测试运行 nginx -V 测试 nginx 是否已经自带 ngx_pagespeed。复制代码代码如下:nginx -V   总结稳定、快速、开源的 nginx 支持许多不同的优化模块。这其中之一是 google 开发的‘pagespeed’。不像 apache,nginx 模块不是动态加载的,因此你必须在编译之前就选择好需要的模块。

下一篇:使用苹果U盘启动电脑装Ghost版系统教程(简单快捷的安装方法,让你轻松拥有Ghost版系统)

温馨提示:以上内容和图片整理于网络,仅供参考,希望对您有帮助!如有侵权行为请联系删除!

友情链接: