【精】從206個console.log()徹底弄懂數據類型轉換的前世此生(下)

前言

你盼世界,我盼望你無bug。Hello 你們好!我是霖呆呆!javascript

那年我十八歲,單純,善良,懵懂,青澀,陽光,可愛...前端

如今的我...在面對JS類型轉換的時候,依舊是...vue

我覺得了解了toString()valueOf()以後,我就是那個最懂你的男人...java

直到我在你的內心看到一個叫作Symbol.toPrimitive的人...面試

這我的,他掌握着你轉換的核心,甚至在必要的時候可以徹底替代toString()valueOf()segmentfault

我奔潰了...發瘋似得去找谷哥和度娘,求他們告訴我打敗Symbol.toPrimitive的法門,最後,他們只說了一句話:數組

"看完霖呆呆的這篇文章再來一波三連就能夠了啊!!!"瀏覽器

😂😂😂函數

抱歉,狗改不了吃屎,呸,秉性難移頑梗不化,忍不住寫了個小短片哈 😄。post

其實也是想要告訴你們,上一篇文章是重點,這篇文章也是重點,因此都要好好看哦。

有不少人以爲花了這麼多的時間和這麼多的精力來看呆呆的這兩篇類型轉換,就只爲了弄懂這一個小小的知識點,感受好虧啊,遠沒有刷一篇各個知識點都覆蓋的面試總結的成就感高。其實我想說,面試總結類的文章當然能夠幫助咱們查漏補缺,可是對於JS基礎知識的掌握我認爲也是十分重要的。就像今天看到Minute老哥的一篇《非科班二本前端大廠面試的心路歷程和總結(騰訊、頭條、阿里、京東) | 掘金技術徵文》文章裏說的同樣:

OK👌,玩歸玩,鬧歸鬧,類型轉換把你教。

來看看經過閱讀你能夠學習到:

  • 重寫toStringvalueOf
  • Symbol.toPrimitive
  • 使用==比較時的類型轉換
  • +、-、*、/、%的類型轉換
  • 幾道大廠的面試題

前期準備

在正式閱讀以前,我推薦你看一下本系列的上一篇《從206個console.log()徹底弄懂數據類型轉換的前世此生(上)》;這樣有利於你閱讀本篇文章。

讓咱們來回顧一下以前提到的toPrimitive執行流程:

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.hasInstanceSymbol.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要轉換成哪一個類型咯?

這麼屌的功能,趕忙來試試:

var b = {
  toString () {
    console.log('toString')
    return '1'
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] (hint) {
    console.log('symbol')
    if (hint === 'string') {
      console.log('string')
      return '1'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'default') {
      console.log('default')
      return 'default'
    }
  }
}
console.log(String(b))
console.log(Number(b))
複製代碼

這道題重寫了toString、valueOf、Symbol.toPrimitive三個屬性,經過上面👆的題目咱們已經知道了只要有Symbol.toPrimitive在,前面兩個屬性就被忽略了,因此咱們不用管它們。

而對於Symbol.toPrimitive,我將三種hint的狀況都寫上了,若是按照個人設想的話,在調用String(b)的時候應該是要打印出string的,調用Number(b)打印出number,結果也正如我所預想的同樣:

'string'
'1'
'number'
1
複製代碼

那麼這裏面的"default"是作什麼的呀?它是何時執行的呢?

開始個人想法是若是沒有if (hint === 'string')這一個判斷的時候,是否是就會執行"default"了呢?

因而我把if (hint === 'string')'number'這兩個判斷的內容給去掉了,發現它仍是不會執行"default"

var b = {
  toString () {
    console.log('toString')
    return '1'
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] (hint) {
    console.log('symbol')
    // if (hint === 'string') {
    // console.log('string')
    // return '1'
    // }
    // if (hint === 'number') {
    // console.log('number')
    // return 1
    // }
    if (hint === 'default') {
      console.log('default')
      return 'default'
    }
  }
}
console.log(String(b))
console.log(Number(b))

// 'symbol'
// 'undefined'
// 'symbol'
// NaN
複製代碼

能夠看到,執行結果居然和題2.2中那個沒有返回值的b有點像。

因此也就是說,這個hint它是在調用Symbol.toPrimitive的時候就已經肯定了的,後面並不會改變。

好比String(b)時傳的是stringNumber(b)時傳的是number

default這個狀況,它涉及到+運算符,在第四節中會說到。

2.4 題目四

小夥子(姑娘),據說你已經掌握Symbol.toPrimitive了?

OK👌,讓咱們來作個題鞏固一下:

class Person {
  constructor (name) {
    this.name = name
  }
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      console.log('default')
      return 'default'
    }
    if (hint === 'string') {
      console.log('string')
      return '1'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
  }
}
let p1 = new Person('p1');
let p2 = new Person('p2');

console.log(String(p1))
console.log(Number(p2))
console.log(p1)
console.log(p2)
複製代碼

我把原來的對象,換成了如今的class,你不用想多,其實用它生成的實例就是一個對象,且能使用Symbol.toPrimitive

因此這裏的結果爲:

'string'
'1'
'number'
1
Person{ name: 'p1' }
Person{ name: 'p2' }
複製代碼

注意:這裏的p一、p2爲何是沒有表現出Symbol.toPrimitive函數的呢?

別忘了《【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)》這裏說的,定義在class中的全部方法都至關因而定義在其原型對象上,也就是Person.prototype上,因此這裏p一、p2雖然是遵循Symbol.toPrimitive,可是使用的倒是它原型鏈上的。

總結-Symbol.toPrimitive

咱來總結一下哈。

  • 若是重寫了某個對象或者構造函數中的toString、valueOf、Symbol.toPrimitive方法,Symbol.toPrimitive的優先級是最高的
  • 如果Symbol.toPrimitive函數返回的值不是基礎數據類型(也就是原始值),就會報錯
  • Symbol.toPrimitive接收一個字符串參數hint,它表示要轉換到的原始值的預期類型,一共有'number'、'string'、'default'三種選項
  • 使用String()調用時,hint'string';使用Number()時,hint'number'
  • hint參數的值從開始調用的時候就已經肯定了

說實話,這回是真的有些膨脹了,如今無論是toPrimitive的執行機制,仍是Symbol.toPrimitive的自定義咱都給搞懂了。

3. 使用==比較時的類型轉換

上面👆整了這麼多題,你卻是給👴來點實際會考的東西啊。

好哦,其實在實際中咱們被考的比較多的可能就是用==來比較判斷兩個不一樣類型的變量是否相等。

而全等===的狀況比較簡單,通常不太會考,由於全等的條件就是:若是類型相等值也相等才認爲是全等,並不會涉及到類型轉換。

可是==的狀況就相對複雜了,先給你們看幾個比較眼熟的題哈:

console.log([] == ![]) // true
console.log({} == true) // false
console.log({} == "[object Object]") // true
複製代碼

怎樣?這幾題是否是常常看到呀 😁,下面就讓咱們一個一個來看。

首先,咱們仍是得清楚幾個概念,這個是硬性規定的,不看的話咱無法繼續下去啊。

當使用==進行比較的時候,會有如下轉換規則(判斷規則):

  1. 兩邊類型若是相同,值相等則相等,如 2 == 3確定是爲false的了
  2. 比較的雙方都爲基本數據類型:
  • 如果一方爲null、undefined,則另外一方必須爲null或者undefined才爲true,也就是null == undefinedtrue或者null == nulltrue,由於undefined派生於null
  • 其中一方爲String,是的話則把String轉爲Number再來比較
  • 其中一方爲Boolean,是的話則將Boolean轉爲Number再來比較
  1. 比較的一方有引用類型:
  • 將引用類型遵循ToNumber的轉換形式來進行比較(實際上它的hintdefault,也就是toPrimitive(obj, 'default'),可是default的轉換規則和number很像,具體能夠看3.10)
  • 兩方都爲引用類型,則判斷它們是否是指向同一個對象

在一些文章中,會說道:

若是其中一方爲Object,且另外一方爲String、Number或者Symbol,會將Object轉換成字符串,再進行比較

(摘自《神三元-(建議收藏)原生JS靈魂之問, 請問你能接得住幾個?(上)》中的3. == 和 ===有什麼區別?)

這樣認爲其實也能夠,由於想一想toPrimitive(obj, 'number')的過程:

  • 如果輸入值爲引用數據類型,則先調用valueOf()方法
  • 如果valueOf()方法的返回值是基本數據類型則直接返回,若不是則繼續調用toString()
  • 如果調用toString()的返回值是基本數據類型則返回,不然報錯。

能夠看到,首先是會執行valueOf()的,可是引用類型執行valueOf()方法,除了日期類型,其它狀況都是返回它自己,也就是說執行完valueOf()以後,仍是一個引用類型而且是它自己。那麼咱們是否是就能夠將valueOf()這一步給省略掉,認爲它是直接執行toString()的,這樣作起題來也快了不少。

(雖然能夠將它省略,可是你得知道實際是有這麼一步的,這一點咱們在題目3.6會驗證)

爲了方便記憶,我畫了一張後面三個規則的轉換圖,接下來咱們只須要按着這張圖的轉換規則來作題就能夠了 😁。

(爲了能有更好的作題體驗,請你務必要將此圖刻在內心)

3.1 題目一

(理解類型相同null、undefined的狀況)

來點簡單的吧

console.log(1 == 1)
console.log(1 == 2)

console.log(null == 0)
console.log(null == false)
console.log(null == {})

console.log(undefined == 0)
console.log(undefined == false)
console.log(undefined == {})

console.log(null == null)
console.log(undefined == undefined)
console.log(undefined == null)
複製代碼

謹記開頭的轉換規則來作題哦 😁。

因此這裏的答案爲:

console.log(1 == 1) // true
console.log(1 == 2) // false

console.log(null == 0) // false
console.log(null == false) // false
console.log(null == {}) // false

console.log(undefined == 0) // false
console.log(undefined == false) // false
console.log(undefined == {}) // false

console.log(null == null) // true
console.log(undefined == undefined) // true
console.log(undefined == null) // true
複製代碼

能夠看到,undefined、null除了和它自身以及對方相等以外,和其它的比較都爲false

(其實以前我老是覺得null == 0或者null == false是爲true的,由於以前可能會使用!flag這種方式來判斷某個值是否是truly,固然越到後面越知道這種方式實際上是很不嚴謹的哈)

3.2 題目二

(理解一方爲String,另外一方爲Number的狀況)

如果這種狀況的話,會把String轉成Number再來比較:

console.log('11' == 11)
console.log('1a' == 11)
console.log('11n' == 11)

console.log('0x11' == 17)
console.log('false' == 0)
console.log('NaN' == NaN)
複製代碼

這裏可能會有幾個陷阱,你們要當心了。

答案:

console.log('11' == 11) // true
console.log('1a' == 11) // false
console.log('11n' == 11) // false

console.log('0x11' == 17) // true
console.log('false' == 0) // false
console.log('NaN' == NaN) // false
複製代碼
  • '11' == 11沒啥問題,字符串轉爲了數字
  • '1a'轉爲數字以後是NaN
  • '11n'轉爲數字以後也是NaN,可能你們會當作是bigInt類型的,可是注意了這裏是字符串
  • '0x11',以0x開頭的十六進制,因此轉換爲數字以後是17
  • 'false'是一個字符串哦,並非false,因此結果是假值
  • 'NaN'也是字符串,不過這裏要是真的NaN的話,那也是false,由於NaN這個六親不認的連它本身都不全等(也就是NaN===NaN的結果爲false),只有用Object.is(NaN, NaN)纔會被判斷爲true)

3.3 題目三

(理解一方爲Boolean的狀況)

這種狀況會將Boolean轉爲Number來比較,而經過上篇咱們知道,BooleanNumber那是至關簡單的,只有兩種狀況:

  • true => 1
  • false => 0

因此若是有一方爲Boolean的時候應該會很好作吧...

console.log(true == 1)
console.log(false == 0)
console.log(true == '1')
console.log(false == '0')

console.log(true == '0')
console.log(true == 'false')
console.log(false == null)
複製代碼

是挺簡單的哈:

console.log(true == 1) // true
console.log(false == 0) // true
console.log(true == '1') // true
console.log(false == '0') // true

console.log(true == '0') // false
console.log(true == 'false') // false
console.log(false == null) // false
複製代碼
  • 前兩個沒啥問題,truefalse轉爲數字就是 01
  • 第三個true轉爲數字爲1,以後另外一邊是字符串1,依靠準則三,一方爲字符串,則將這個字符串轉爲數字而後進行比較,因此結果爲1 == 1的結果,也就是true
  • 第四個和第三個狀況同樣,false轉爲數字0,以後後面的"0"也被轉爲數字0,因此結果爲true
  • 第五個,true被轉換爲了1'0'被轉換爲了0,因此結果爲false
  • 第六個,true被轉換爲了1"false"被轉換爲了NaN,因此結果爲false
  • 第七個,額,這個其實遵循準則一就能夠了,nullfalse自己就是不相等的。

其實這裏不知道有沒有和我同樣對true == '0'有疑問的呢 🤔️?

由於咱們可能見過這麼一段代碼:

if ('0') {
    console.log('我會被執行')
}
複製代碼

這裏if內的內容是會被執行的,由於字符串'0'轉換爲布爾確實是true,那麼我就總會認爲true == '0'是對的。

因此這裏要注意了,'0'確實是會被轉換爲true,也就是:

if (true) {
    console.log('我會被執行')
}
複製代碼

但在這道題中是將它與true來作比較,那麼就要遵循「有布爾先將布爾轉換爲數字」的規則。

因此其實也就是一個轉換順序的問題,true == '0'是先執行的布爾轉數字的。

可是你不要覺得是一個寫法順序的問題 😂,也就是說就算把true'0'換個位置結果也是同樣的:

console.log('0' == true) // false
複製代碼

3.4 題目四

(一方爲對象的狀況)

在第三節的開頭那裏呆呆已經說了,當一方有爲對象的時候,實際是會將對象執行相似ToNumber操做以後再進行比較的,可是又因爲對象的valueOf()基本都是它自己,因此咱們能夠認爲省略了這一步,不過爲了讓你們心服口服,我這裏仍是得來驗證一下:

var b = {
  valueOf: function () {
    console.log('b.valueOf')
    return '1'
  },
  toString: function () {
    console.log('b.toString')
    return '2'
  }
}
var c = {
  valueOf: function () {
    console.log('c.valueOf')
    return {}
  },
  toString: function () {
    console.log('c.toString')
    return '2'
  }
}
console.log(b == 1)
console.log(c == 2)
複製代碼

這道題中,bvalueOf()返回的是一個基本數據類型

cvalueOf()返回的是一個引用類型。

所以結果爲:

'b.valueOf'
true
'c.valueOf'
'c.toString'
true
複製代碼

因此咱們能夠獲得這張圖:

下面作兩道題讓咱們練習一下哈。

3.5 題目五

(一方爲非數組對象的狀況)

console.log({} == true)
console.log({} == false)
console.log({} == 1)
console.log({} == '1')
console.log({} == 0)
console.log({} == Symbol(1))
console.log({} == null)
console.log({} == {})
複製代碼

哇,乍一看感受好多啊,這...我怎麼比的過來。

這時候你只要記得,有一方是Object時,把這個Object轉爲字符串再來比較就能夠了。

而引用類型轉字符串不知道你們還記得嗎?

分爲了數組非數組兩種狀況,大體就是:

  • [] => ''['1, 2'] => '1, 2'
  • 非數組狀況另看

而後咱們再來看看上面那道題👆,{}轉爲字符串實際上是"[object Object]"

因此能夠看出上面的執行結果全爲false

其中可能比較難理解的是:

  • {} == true,轉換過程爲:
{} == true
"[object Object]" == true // 對象轉字符串
"[object Object]" == 1 // 布爾值轉數字(準則四,一方爲布爾,轉換爲數字)
NaN == 1 // 字符串轉數字(準則三,一方爲字符串另外一方爲數字則將字符串轉數字)
// 結果爲false
複製代碼
  • {} == 1,轉換過程爲:
{} == 1
"[object Object]" == 1 // 對象轉字符串
NaN == 1 // 字符串轉數字(準則三,一方爲字符串另外一方爲數字則將字符串轉數字)
// 結果爲 false
複製代碼
  • {} == {}: 這個你就理解爲對象是引用類型,那麼這兩個對象都有本身獨立的堆空間,確定就是不相等的了。

3.6 題目六

(一方爲數組的狀況)

console.log([] == 0)
console.log([1] == 1)
console.log(['1'] == 1)

console.log([] == 1)
console.log(['1', '2'] == 1)
console.log(['1', '2'] == ['1', '2'])

console.log([{}, {}] == '[object Object],[object Object]')
console.log([] == true)
console.log([] == Symbol('1'))
複製代碼

題目解析:

console.log([] == 0)
[] == 0
'' == 0 // []空數組轉爲字符串爲空字符串
0 == 0 // 空字符串轉爲數字爲0
// true

console.log([1] == 1)
[1] == 1
'1' == 1 // [1]非空數組且數組長度爲1,轉換爲字符串爲'1'
1 == 1 // '1'字符串轉換爲數字1
// true

console.log(['1'] == 1) // 轉換過程和上面一個同樣
// true

console.log([] == 1)
[] == 1
'' == 1 // 空數組轉爲字符串爲''
0 == 1 // 空字符串轉爲數字爲0
// false

console.log(['1', '2'] == 1)
['1', '2'] == 1
'1,2' == 1 // ['1', '2']數組轉爲字符串爲'1,2'
NaN == 1 // '1,2'字符串轉爲數字爲NaN

console.log(['1', '2'] == ['1', '2']) // 引用地址不一樣
// false

console.log([{}, {}] == '[object Object][object Object]')
[{}, {}] == '[object Object][object Object]'
// [{},{}]數組中的每一項也就是{}轉爲字符串爲'[object Object]',而後進行拼接
'[object Object],[object Object]' == '[object Object],[object Object]'
// true

console.log([] == true)
[] == true
[] == 1 // 有一項爲布爾,所以將布爾true轉爲數字1
'' == 1 // 有一項爲數組, 所以將[]轉爲空字符串
0 == 1 // 空字符串轉爲數字0
// false

console.log([] == Symbol('1'))
[] == Symbol('1')
'' == Symbol('1')
// false
複製代碼

3.7 題目七

(理解!運算符的轉換)

當咱們使用!的時候,實際上會將!後面的值轉換爲布爾類型來進行比較,這也就是我在題3.1說到過的不嚴謹的狀況。

並且我發現這種轉換是不會通過ToNumber()的,而是直接轉換爲了布爾值,讓咱們來驗證一下:

var b = {
  valueOf: function () {
    console.log('b.valueOf')
    return '1'
  },
  toString: function () {
    console.log('b.toString')
    return '2'
  }
}
console.log(!b == 1)
console.log(!b == 0)
複製代碼

這裏的執行結果是:

false
true
複製代碼

能夠看到,!b它在轉換的過程當中並無通過valueOf或者toString,而是直接轉爲了false

3.8 題目八

再來作幾道題哈:

console.log(!null == !0)
console.log(!undefined == !0)
console.log(!!null == !!0)

console.log(!{} == {})
console.log(!{} == [])
console.log(!{} == [0])
複製代碼

答案:

console.log(!null == !0) // true
console.log(!undefined == !0) // true
console.log(!!null == !!0) // true

console.log(!{} == {}) // false
console.log(!{} == []) // true
console.log(!{} == [0]) // true
複製代碼

能夠看到,剛剛還不相等的null0在分別加上了!以後,就變爲相等了。

前面三個輸出結果應該都沒有什麼問題,來看看後面三個:

!{} == {}

  • 首先執行的是!{},轉換以後爲false
  • 至關於false == {},一方有布爾的狀況,將布爾轉換爲數字,即0 == {}
  • 一方有對象,將對象轉換爲字符串,即0 == '[object Object]'
  • 一方有字符串,將字符串轉換爲數字,即0 == NaN
  • 所以結果爲false

!{} == []

  • 首先執行的仍是!{},轉換以後爲false
  • 至關於false == [],一方有布爾,將布爾轉換爲數字,即0 == []
  • 一方有對象,將對象轉換爲字符串,即0 == '0'
  • 一方有字符串,將字符串轉換爲數字,即0 == 0
  • 所以結果爲true

!{} == [0]的轉換流程和!{} == []同樣。

3.9 題目九

如今你能弄懂開始說的那幾道題了嗎?

讓咱們再來看看,此次確定以爲很簡單:

var b = {
  valueOf() {
    console.log('valueOf')
    return []
  },
  toString () {
    console.log('toString')
    return false
  }
}
console.log(![] == [])
console.log(![] == b)
複製代碼

![] == []

  • 先將![]轉換爲布爾類型,[]true,那麼![]就是false
  • 而後[]轉爲數字是爲00false比較,將false也轉換爲0,因此結果爲true

![] == b

  • 一樣的,![]轉爲了false
  • b會先執行valueOf,而後執行toString,返回的也是false
  • 因此結果爲true

答案:

true
'valueOf'
'toString'
true
複製代碼

3.10 題目十

(理解==比較時對象的Symbol.toPrimitive函數的hint參數)

var b = {
  [Symbol.toPrimitive] (hint) {
    console.log(hint)
    if (hint === 'default') {
      return 2
    }
  }
}
console.log(b == 2)
console.log(b == '2')
複製代碼

經過上面👆幾個案例,咱們均可以看出對象在進行==比較時會通過相似於ToNumber的轉換過程:

  • 調用valueOf()
  • 調用toString()

但其在進行從新Symbol.toPrimitive接收到的參數會是"default",並非"number"

因此這裏的答案爲:

'default'
true
'default'
true
複製代碼

3.11 題目十一

(函數在使用==時的轉換)

函數其實也是一個對象,因此在進行==比較時也和普通對象同樣處理便可。

可是我只想要提醒一點,在進行==比較時要注意是比較函數自己仍是比較函數的返回值

例如在這道題中:

function f () {
  var inner = function () {
    return 1
  }
  inner.valueOf = function () {
    console.log('valueOf')
    return 2
  }
  inner.toString = function () {
    console.log('toString')
    return 3
  }
  return inner
}
console.log(f() == 1)
console.log(f()() == 1)
複製代碼
  • f()表示的是inner這個函數,因此f() == 1至關因而inner == 1,所以此時就涉及到了inner函數的類型轉換,就會觸發inner.valueOf(),返回2,所以第一個是false
  • f()()表示的是inner()調用以後的返回值,也就是1,因此此時是1 == 1進行比較,並不會涉及到inner函數的類型轉換,也就不會觸發inner.valueOf(),所以第二個爲true

結果:

'valueOf'
false
true
複製代碼

總結-使用==比較

作完了這十一道題,相信你對==的比較應該比以前更瞭解了吧 😁,讓咱們來總結一波。

當使用==進行比較的時候,會有如下轉換規則(判斷規則):

  1. 兩邊類型若是相同,值相等則相等,如 2 == 3確定是爲false的了
  2. 比較的雙方都爲基本數據類型:
  • 如果一方爲null、undefined,則另外一方必須爲null或者undefined才爲true,也就是null == undefinedtrue或者null == nulltrue,由於undefined派生於null
  • 其中一方爲String,是的話則把String轉爲Number再來比較
  • 其中一方爲Boolean,是的話則將Boolean轉爲Number再來比較
  1. 比較的一方有引用類型:
  • 將引用類型遵循相似ToNumber的轉換形式來進行比較(也就是toPrimitive(obj, 'defalut')
  • 兩方都爲引用類型,則判斷它們是否是指向同一個對象

當一方有爲對象的時候,實際是會將對象執行ToNumber操做以後再進行比較的,可是又因爲對象的valueOf()基本都是它自己,因此咱們能夠認爲省略了這一步。

這裏我貼上一張流程圖,感受畫的挺不錯的,你們能夠對照着看一下:

(圖片來源:segmentfault.com/a/119000001…)

4. +、-、*、/、%的類型轉換

除了在==的比較中會進行類型轉換以外,其它的運算符號也會有。

好比標題上常見的這五種。

這裏我主要是分兩類來講:

  1. -、*、/、%這四種都會把符號兩邊轉成數字來進行運算
  2. +因爲不只是數字運算符,仍是字符串的鏈接符,因此分爲兩種狀況:
  • 兩端都是數字則進行數字計算
  • 有一端是字符串,就會把另外一端也轉換爲字符串進行鏈接

4.1 題目一

(四種簡單運算符的類型轉換)

先來講說除了+號之外的其它四種運算符的轉換,因爲基本數據類型應該都清楚,因此就不作說明了,這裏主要是想說一下對象運算時的狀況:

var b = {}
console.log(b - '2')
console.log(b * '2')
console.log(b / '2')
console.log(b % '2')
console.log(b - [])
console.log(b - {})
複製代碼

b是一個對象,在進行這類運算的時候,把兩端都轉換爲數字進行計算,而咱們知道對象{}轉爲數字是NaN,因此答案全都是NaN

答案:

NaN
NaN
NaN
NaN
NaN
NaN
複製代碼

4.2 題目二

(四種運算符的實際轉換-重寫toString()valueOf())

咱們將上面👆那道題的b對象重寫一下它們的toString()valueOf()方法,想一想,若是它是遵循ToNumber()轉換的話,那麼如下的結果會是什麼呢?

var b = {
  valueOf () {
    console.log('valueOf')
    return {}
  },
  toString () {
    console.log('toString')
    return 1
  }
}

console.log(b - '2')
console.log(b * '2')
console.log(b / '2')
console.log(b % '2')
console.log(b - [])
console.log(b - {})
複製代碼

在調用b的時候,會先執行valueOf()方法,若是該方法返回的是一個基本數據類型則返回,不然繼續調用toString()方法,很顯然這裏的valueOf()返回的仍是一個引用類型,因此總會調用toString(),所以答案爲:

'valueOf'
'toString'
-1
'valueOf'
'toString'
2
'valueOf'
'toString'
0.5
'valueOf'
'toString'
1
'valueOf'
'toString'
1
'valueOf'
'toString'
NaN
複製代碼

這裏要說一下的是最後兩個輸出結果。

b - []

  • b輸出的是1,由於[]轉換爲數字咱們知道是0,因此結果爲1

b - {}

  • 其實差很少,b1{}轉爲數字爲NaN,因此結果爲NaN

4.3 題目三

(四種運算符的實際轉換-重寫Symbol.toPrimitive)

4.1那道題咱們除了重寫toStringvalueOf咱們還能夠從新什麼呢?

嘻嘻,怎麼能忘了Symbol.toPrimitive,若是從新了它,那你以爲它接收到的hint參數會是什麼呢?

var b = {
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      console.log('default')
      return 'default'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'string') {
      console.log('string')
      return '2'
    }
  }
}

console.log(b - '2')
console.log(b * '2')
console.log(b / '2')
console.log(b % '2')
console.log(b - [])
console.log(b - {})
複製代碼

既然是會把運算符兩邊都轉換爲數字進行計算,那麼hint接收到的確定就是'number'了呀,沒錯,因此這裏b老是會返回1,所以答案爲:

'number'
-1
'number'
2
'number'
0.5
'number'
1
'number'
1
'number'
NaN
複製代碼

yeah~感受沒啥難度。

4.4 題目四

(+號對於對象的轉換)

  • +b的狀況就至關於轉爲數字
  • +號兩邊有值則判斷兩邊值的類型,若兩邊都爲數字則進行數字計算,如有一邊是字符串,就會把另外一邊也轉換爲字符串進行鏈接
var b = {}
console.log(+b)
console.log(b + 1)
console.log(1 + b)
console.log(b + '')
複製代碼

依照着這個規則,咱們能夠得出答案:

NaN
'[object Object]1'
'1[object Object]'
'[object Object]'
複製代碼

4.5 題目五

('+'運算符與String()的區別)

一樣的,咱們給上題加上Symbol.toPrimitive看一下:

var b = {
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      console.log('default')
      return '我是默認'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'string') {
      console.log('string')
      return '2'
    }
  }
}
console.log(+b)
console.log(b + 1)
console.log(1 + b)
console.log(b + '')
console.log(String(b))
複製代碼

由於+b走的是轉換數字的路線,因此它的hint確定就是number

但是對於b + 1這種字符串鏈接的狀況,走的卻不是string,而是default

因此能夠看到答案爲:

var b = {
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      console.log('default')
      return '我是默認'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'string') {
      console.log('string')
      return '2'
    }
  }
}
console.log(+b) // number
console.log(b + 1) // default
console.log(1 + b) // default
console.log(b + '') // default
console.log(String(b)) // string

'number'
1
'default'
'我是默認1'
'default'
'1我是默認'
'default'
'我是默認'
'string'
'2'
複製代碼

能夠看到b + 1String(b)這兩種促發的轉換規則是不同的

  • {} + 1字符串鏈接時hintdefault
  • String({})hintstring

4.6 題目六

鑑於我不知道上面👆的defaultnumber、string有什麼區別,因此我以爲應該要重寫一下toStringvalueOf()來看看會發生什麼。

var b = {
  valueOf () {
    console.log('valueOf')
    return {}
  },
  toString () {
    console.log('toString')
    return 1
  },
}
console.log(+b) // number
console.log(b + 1) // default
console.log(String(b)) // string
複製代碼

此時的結果爲:

'valueOf'
'toString'
1
'valueOf'
'toString'
2
'toString'
'1'
複製代碼

我發現default的轉換方式和number很像,都是先執行判斷有沒有valueOf,有的話執行valueOf,而後判斷valueof後的返回值,如果是引用類型則繼續執行toString。(這點其實在題目3.10中也說到了)

4.7 題目七

(日期對象的數據轉換)

以前咱們有提到過,日期對象的轉換比較特殊。(在引用類型調用valueOf()中)

  • 普通對象轉換的valueOf返回的是它自己,也就是引用類型
  • 日期對象的valueOf返回的是一個數字類型的毫秒數
var date = new Date()

console.log(date.valueOf())
console.log(date.toString())

console.log(+date)
console.log('' + date)
複製代碼

因此咱們能夠看到這裏的答案是:

var date = new Date()

console.log(date.valueOf()) // 1585742078284
console.log(date.toString()) // Wed Apr 01 2020 19:54:38 GMT+0800 (中國標準時間)

console.log(+date) // 1585742078284
console.log('' + date) // Wed Apr 01 2020 19:54:38 GMT+0800 (中國標準時間)
複製代碼

+date是轉換爲數字,因此結果和date.valueOf()結果一致。

可是咱們會發現這裏的'' + date和上面的'' + {}就會有所不一樣了。

雖然一樣都是被轉換爲字符串,可是還記得'' + {}的轉換順序嗎?它的轉換方式是遵循ToNumber的,也就是會先執行valueOf(),再執行toString(),因爲{}.valueOf等於它自己,是引用類型,因此會繼續執行toString()

date進行+號字符串鏈接不會遵循這種轉換規則,而是優先調用toString()

總結-運算符的類型轉換

對於幾種經常使用運算符的類型轉換:

  1. -、*、/、%這四種都會把符號兩邊轉成數字來進行運算
  2. +因爲不只是數字運算符,仍是字符串的鏈接符,因此分爲兩種狀況:
  • 兩端都是數字則進行數字計算(一元正號+b這種狀況至關於轉換爲數字)
  • 有一端是字符串,就會把另外一端也轉換爲字符串進行鏈接

對象的+號類型轉換:

  • 對象在進行+號字符串鏈接的時候,toPrimitive的參數hintdefault,可是default的執行順序和number同樣都是先判斷有沒有valueOf,有的話執行valueOf,而後判斷valueof後的返回值,如果是引用類型則繼續執行toString。(相似題4.54.6)
  • 日期在進行+號字符串鏈接的時候,優先調用toString()方法。(相似題4.7)
  • 一元正號是轉換其餘對象到數值的最快方法,也是最推薦的作法,由於 它不會對數值執行任何多餘操做

mm...

不知道您看到如今還好不?應該還沒炸吧

鋪墊了這麼久,是時候展現正在的技術了!

下面讓咱們來作幾道綜合題檢驗一下[陰笑~]

5. 幾道大廠的面試題

5.1 如下輸出爲?

console.log([] == [])
console.log([] == ![])
console.log({} == 1)
複製代碼
  • ==號兩邊都是引用類型則判斷是否爲同一引用
  • [] == ![]這個在3.9中說的很詳細了
  • {} == 1,簡單來講兩邊都轉換爲數字,{}轉換爲數字爲NaN,因此結果爲false。詳細來講:一方爲對象,將對象轉換爲字符串進行比較,即"[object Object]" == 1;一方有字符串,將字符串轉換爲數字進行比較,即NaN == 1,因此結果爲false

答案爲:

console.log([] == []) // false
console.log([] == ![]) // true
console.log({} == 1) // false
複製代碼

5.2 如下輸出爲?

console.log({} + "" * 1)
console.log({} - [])
console.log({} + [])
console.log([2] - [] + function () {})
複製代碼

{} + "" * 1

  • 運算順序遵循先乘後加,因此先執行"" * 1,結果爲0,由於""轉換爲數字是0
  • 以後執行{} + 0,將{}轉換爲字符串是"[object Object]"0轉換爲字符串是"0"
  • 因此結果爲"[object Object]0"

{} - []

  • -號兩邊轉換爲數字,{}NaN[]0,因此結果爲NaN

{} + []

  • {}轉爲字符串爲"[object Object]"[]轉爲字符串爲"",因此結果爲"[object Object]"

[2] - [] + function () {}

  • -號兩邊轉換爲數字分別爲20,因此[2] - []結果爲2
  • 以後2 + function () {},兩邊轉換爲字符串拼接爲"2function () {}",由於函數是會轉換爲源代碼字符串的。

答案爲:

console.log({} + "" * 1) // "[object Object]0"
console.log({} - []) // NaN
console.log({} + []) // "[object Object]"
console.log([2] - [] + function () {}) // "2function () {}"
複製代碼

5.3 你會幾種讓if(a == 1 && a == 2 && a == 3)條件成立的辦法?

這道題相信你們看的不會少,除了重寫valueOf()你還會哪些解法呢?

解法一:重寫valueOf()

這個解法是利用了:當對象在進行==比較的時候實際是會先執行valueOf(),如果valueOf()的返回值是基本數據類型就返回,不然仍是引用類型的話就會繼續調用toString()返回,而後判斷toString()的返回值,如果返回值爲基本數據類型就返回,不然就報錯。

如今valueOf()每次返回的是一個數字類型,因此會直接返回。

// 1
var a = {
  value: 0,
  valueOf () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

解法二:重寫valueOf()toString()

var a = {
  value: 0,
  valueOf () {
    return {}
  },
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

原理就是解法一的原理,只不過用到了當valueOf()的返回值是引用類型的時候會繼續調用toString()

這裏你甚至均可以不用重寫valueOf(),由於除了日期對象其它對象在調用valueOf()的時候都是返回它自己。

也就是說你也能夠這樣作:

var a = {
  value: 0,
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

解法三:重寫Symbol.toPrimitive

想一想是否是還能夠用Symbol.toPrimitive來解呢?

結合題3.10咱們知道,當對象在進行==比較的時候,Symbol.toPrimitive接收到的參數hint"defalut",那麼咱們只須要這樣重寫:

var a = {
  value: 0,
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      return ++this.value
    }
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

這樣結果也是能夠的。

解法四:定義class並重寫valueOf()

固然你還能夠用class來寫:

class A {
  constructor () {
    this.value = 0
  }
  valueOf () {
    return ++this.value
  }
}
var a = new A()
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

解法五:利用數組轉爲字符串會隱式調用join()

什麼 ? 還有別的解法嗎?並且我看解法五的題目有點沒看懂啊。

讓咱們回過頭去看看題1.3,那裏提到了當數組在進行轉字符串的時候,調用toString()的結果其實就是調用join的結果。

那和這道題有什麼關係?來看看答案:

let a = [1, 2, 3]
a['join'] = function () {
  return this.shift()
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

由於咱們知道,對象若是是數組的話,當咱們不重寫其toString()方法,在轉換爲字符串類型的時候,默認實現就是將調用join()方法的返回值做爲toString()的返回值。

因此這裏咱們重寫了ajoin方法,而此次重寫作了兩件事情:

  1. 將數組a執行a.shift()方法,咱們知道這會影響原數組a的,將第一項去除
  2. 將剛剛去除的第一項返回回去

因此當咱們在執行a == 1這一步的時候,因爲隱式調用了a['join']方法,因此會執行上面👆說的那兩件事情,後面的a == 2a == 3同理。

解法六:定義class繼承Array並重寫join()

對於解法五咱們一樣能夠用class來實現

class A extends Array {
  join = this.shift
}
var a = new A(1, 2, 3)
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
複製代碼

這種寫法比較酷🆒,可是第一次看可能不太能懂。

  • 首先A這個類經過extends繼承於Array,這樣經過new A建立的就是一個數組
  • 而後A重寫了join方法,join = this.shift就至關因而join = function () { return this.shift() }
  • 這樣當每次調用a == xxx的時候,都會隱式調用咱們自定義的join方法,執行和解法五同樣的操做。

5.4 讓if (a === 1 && a === 2 && a === 3)條件成立?

這道題看着和上面那道有點像,不過這裏判斷的條件是全等的。

咱們知道全等的條件:

  1. 左右兩邊的類型要相等,若是類型不相等則直接返回false,這點和==不一樣,==會發生隱式類型轉換
  2. 再判斷值相不相等

而對於上面👆一題的解法咱們都是利用了==會發生隱式類型轉換這一點,顯然若是再用它來解決這道題是不能實現的。

想一想當咱們在進行a === xxx判斷的時候,實際上就是調用了a這個數據而已,也就是說咱們要在調用這個數據以前,作一些事情,來達到咱們的目的。

不知道這樣說有沒有讓你想到些什麼 🤔️?或許你和呆呆同樣會想到Vue大名鼎鼎的數據劫持 😁。

想一想在Vue2.x中不就是利用了Object.defineProperty()方法從新定義data中的全部屬性,那麼在這裏咱們一樣也能夠利用它來劫持a,修改a變量的get屬性。

var value = 1;
Object.defineProperty(window, "a", {
  get () {
    return this.value++;
  }
})
if (a === 1 && a === 2 && a === 3) {
  console.log('成立')
}
複製代碼

這裏實際就作了這麼幾件事情:

  • 使用Object.defineProperty()方法劫持全局變量window上的屬性a
  • 當每次調用a的時候將value自增,並返回自增後的值

(其實我還想着用Proxy來進行數據劫持,代理一下window,將它用new Proxy()處理一下,可是對於window對象好像沒有效果...)

解法二

怎麼辦 😂,一碰到這種題我又想到了數組...

var arr = [1, 2, 3];
Object.defineProperty(window, "a", {
  get () {
    return this.arr.shift()
  }
})
if (a === 1 && a === 2 && a === 3) {
  console.log('成立')
}
複製代碼

中了shift()的毒...固然,這樣也是能夠實現的。

解法三

還有就是EnoYao大佬那裏看來的騷操做:

原文連接:juejin.im/post/5e66dc…

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if (aᅠ == 1 && a == 2 && ᅠa == 3) {
  console.log("成立");
}
複製代碼

說來慚愧...a的先後隱藏的字符我打不來 😂...

5.5 實現如下代碼

現須要實現如下函數:

function f () {
  /* 代碼 */
}

console.log(f(1) == 1)
console.log(f(1)(2) == 3)
console.log(f(1)(2)(3) == 6)
複製代碼

首先看到這道題的時候讓我想到了題目3.11,只不過這裏是有傳參的,而且返回值像是一個累計的過程。

也就是說會收集每次傳遞進來的參數而後進行一個累加並返回(這個很容易想到reduce方法)。

而且f(1)(2)這樣的寫法很像是偏應用,函數返回了一個函數。

那咱們是否是能夠在函數f內用一個變量數組來存放參數集合,而後返回一個函數(我命名爲inner),這個inner函數的做用是收集傳遞進來的參數將它添加到參數集合中。

以後就和3.11很像,在每次進行==比較的時候,f返回的inner函數會進行隱式類型轉換,也就是會調用innervalueOf()toString()方法,那咱們只須要重寫這兩個方法,並返回用reduce累加的參數的和就能夠了。

代碼也很簡單,一塊兒來看看:

function f () {
  let args = [...arguments]
  var add = function () {
    args.push(...arguments)
    return add
  }
  add.valueOf = function () {
    return args.reduce((cur, pre) => {
      return cur + pre
    })
  }
  return add
}
console.log(f(1) == 1)
console.log(f(1)(2) == 3)
console.log(f(1)(2)(3) == 6)
複製代碼

固然,上面👆的valueOf()換成toString()也是能夠的,由於咱們已經知道了,對象==比較時類型轉換的順序其實就是先通過valueOf再到toString

5.6 控制檯輸入{}+[]會怎樣?

氣氛不要這麼凝重嘛...讓咱們最後來看道簡單有趣的題。

這道有趣的題是從LINGLONG的一篇《【js小知識】[]+ {} =?/{} +[] =?(關於加號的隱式類型轉換)》那裏看來的。

(PS: pick一波玲瓏,這位小姐姐的文章寫的都挺好的,不過熱度都不高,你們能夠支持一下呀 😁)

OK👌,來看看題目是這樣的:

在控制檯(好比瀏覽器的控制檯)輸入:

{}+[]
複製代碼

的結果會是什麼 🤔️?

咦~這道題上面不是作過了嗎(題目5.2裏的第三個console.log())?

console.log({}+[]) // "[object Object]"
複製代碼

可是注意這裏的題目,是要在控制檯輸出哦。

此時我把這段代碼在控制檯輸出結果發現答案居然和預期的不同:

{}+[]
0
複製代碼

也就是說{}被忽略了,直接執行了+[],結果爲0

知道緣由的我眼淚掉了下來,原來它和以前提到的1.toString()有點像,也是由於JS對於代碼解析的緣由,在控制檯或者終端中,JS會認爲大括號{}開頭的是一個空的代碼塊,這樣看着裏面沒有內容就會忽略它了。

因此只執行了+[],將其轉換爲0

若是咱們換個順序的話就不會有這種問題:

(圖片來源:juejin.im/post/5e6055…)

爲了證明這一點,咱們能夠把{}當成空對象來調用一些對象的方法,看會有什麼效果:

(控制檯或者終端)

{}.toString()
複製代碼

如今的{}依舊被認爲是代碼塊而不是一個對象,因此會報錯:

Uncaught SyntaxError: Unexpected token '.'
複製代碼

解決辦法能夠用一個()將它擴起來:

({}).toString
複製代碼

不過這東西在實際中用的很少,我能想到的一個就是在項目中(好比我用的vue),而後定義props的時候,若是其中一個屬性的默認值你是想要定義爲一個空對象的話,就會用到:

props: {
    target: {
        type: Object,
        default: () => ({})
    }
}
複製代碼

整完了整完了...啊...

後語

知識無價,支持原創。

參考文章:

你盼世界,我盼望你無bug。這篇文章就介紹到這裏。

能夠看到,當咱們瞭解了類型轉換的原理以後發現並非太難。關鍵就是在於半懂不懂的時候最可怕。就像沒了解以前,我能很快的說出{} == 1的結果是false,可是如今腦殼裏會先轉上一圈:

  • 一方爲對象,將對象轉換爲字符串進行比較,即"[object Object]" == 1
  • 一方有字符串,將字符串轉換爲數字進行比較,即NaN == 1,因此結果爲false

對於一些簡單的題甚至會遲鈍一下...這些都是還不熟形成的,但願你們可以多多練習早日避免呆呆這種的狀態。

用心創做,好好生活。這篇文章出了以後這段時間可能不會再出這種都是題目的文章了,作多了你們也會麻木不想看,因此後面會出一些原理性的文章,敬請期待吧,嘻嘻😁。

若是你以爲文章對你有幫助的話來個贊👍哦,謝謝啦~ 😁。

喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai 或者掃一掃下面的二維碼👇👇👇.

我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉

你的鼓勵就是我持續創做的主要動力 😊.

相關推薦:

《全網最詳bpmn.js教材》

《【建議改爲】讀完這篇你還不懂Babel我給你寄口罩》

《【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)》

《【建議👍】再來40道this面試題酸爽繼續(1.2w字用手整理)》

《【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)》

《【何不三連】作完這48道題完全弄懂JS繼承(1.7w字含辛整理-返璞歸真)》

《【精】從206個console.log()徹底弄懂數據類型轉換的前世此生(上)》

相關文章
相關標籤/搜索