大廠面試題常考:toString與valueOf如何深刻理解?結合大廠面試原題給你們作個分析

最近啊,有同窗來問,數據類型轉換咱們都學過,但是在面試題中遇到了咱們怎麼就總是不會用啊。javascript

講到數據類型轉換,咱們基本上都是講到隱式數據類型轉換和顯式轉換。講到數據類型轉換,咱們就要來聊聊,valueOf 和 toString 這兩個方法了。前端

基本上,全部JS數據類型都擁有這兩個方法,null除外。java

這兩個方法都是在原型鏈上的方法,爲了處理javascript值運算與顯示的問題。面試

valueOf 和 toString 幾乎都是在出現操做符(+-*/==><)時被調用(隱式轉換)。數組

toString

 toString返回一個表示該對象的字符串,當對象表示爲文本值或以指望的字符串方式被引用時,toString方法被自動調用。閉包

  1. 手動調用的效果

運行的效果,所有都轉成了字符串。app

比較特殊的地方就是,在表示對象的時候,就會打印[object Object],表示數組的時候,就打印數組內容以逗號鏈接的字符串,至關於數組方法Array.join(',')方法。函數

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

2. 最精準的類型判斷

 toString 有時候在某種場合會比使用 typeof & instanceof 判斷更高效和準確些,屬於更精確的判斷方式測試

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

3. 何時會自動調用

不少同窗會問了,這個toString方法,何時會自動調用呢?使用操做符的時候,若是其中一邊爲對象,則會先調用toSting方法,也就是隱式轉換,而後再進行操做。this

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

valueOf

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偏向於顯示。

  1. 在進行對象轉換時,將優先調用toString方法,如若沒有重寫 toString,將調用 valueOf 方法;若是兩個方法都沒有重寫,則按Object的toString輸出。
  2. 在進行強轉字符串類型時,將優先調用 toString 方法,強轉爲數字時優先調用 valueOf。
  3. 使用運算操做符的狀況下,valueOf的優先級高於toString。

[Symbol.toPrimitive]

MDN:Symbol.toPrimitive 是一個內置的 Symbol 值,它是做爲對象的函數值屬性存在的,當一個對象轉換爲對應的原始值時,會調用此函數。

看這個解釋基本上不少同窗都是看不懂的,無論你有沒有學過JS。

其實不用想的太複雜,就把他當作是一個函數就能夠了。

  1. 做用:同valueOf()和toString()同樣,可是優先級要高於這二者;

2.該函數被調用時,會被傳遞一個字符串參數hint,表示當前運算的模式

一共有三種模式:

string:字符串類型

number:數字類型

default:默認

下面來看看Symbol.toPrimitive在代碼中的實現吧:

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屬性獲取值,而且每一次獲取值都會觸發一次函數實行一次自增,判斷三次就自增三次,因此最後會讓公式成立。

  1. 如何實現一個無限累加函數

問題:用 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 
  1. add函數內部定義sum函數並返回,實現連續調用
  2. sum函數造成了一個閉包,每次調用進行累加值,再返回當前函數sum
  3. 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; 
    }

好了,今天的內容就分享那麼多,不知道對於 valueOf 和 toString方法你有沒有更加深入的理解了呢?若是今天的分享教程你學到了東西,別忘了關注公衆號【前端研究所】哦!平時也歡迎把你不懂的問題分享到公衆號文章的評論留意,說不定下一次就來說解你的疑問了呢!