轉自原文:https://juejin.im/post/687321524380421326javascript
基本上,全部JS數據類型都擁有這兩個方法,null除外。它們倆是位於原型鏈上的方法,也是爲了解決javascript值運算與顯示的問題。java
valueOf
和 toString
幾乎都是在出現操做符(+-*/==><)
時被調用(隱式轉換)。web
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
方法,也就是隱式轉換
,而後再進行操做。app
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;
}
本文分享自微信公衆號 - JavaScript忍者祕籍(js-obok)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。