js對象的toString()方法和valueOf()方法

在研究js的==和===的區(qū)別時譬胎,曾經(jīng)說過秉撇,在js中非原始值對象祝谚,要參加運算需要ToPrimitive(x)轉(zhuǎn)換成原始值類型易阳。
在ToPrimitive(x)時习贫,我們曾得出結(jié)論:

ToPrimitive

  • ToPrimitive(input,hint)轉(zhuǎn)換為原始類型的方法剥啤,根據(jù)hint目標類型進行轉(zhuǎn)換搞动。
  • hint只有兩個值:String和Number
  • 如果沒有傳hint汽摹,Date類型的input的hint默認為String,其他類型的input的hint默認為Number
  • Number 類型先判斷 valueOf()方法的返回值,如果不是惭蟋,再判斷 toString()方法的返回值
  • String 類型先判斷 toString()方法的返回值苗桂,如果不是,再判斷 valueOf()方法的返回值

也就是說告组,js中的對象離不開toString()方法和valueOf()方法煤伟。那到底何時用toString()方法,何時用valueOf()方法呢木缝?怎么用便锨?

//先定義一個用戶User類
class User {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
}

我們先定義一個用戶User類,如果我們不重寫toString()方法和valueOf()方法 我碟,直接輸入下面的值:

var coolcao = new User('coolcao',23);
console.log(coolcao);
console.log(coolcao.toString());
console.log(coolcao.valueOf());
console.log(coolcao + 10);
//輸出
//User { name: 'coolcao', age: 23 }
//[object Object]
//User { name: 'coolcao', age: 23 }
//[object Object]10

直接輸入對象coolcao,我們得到的就是對象的表示形式放案。注意,此時User { name: 'coolcao', age: 23 }是在nodejs環(huán)境下的輸出矫俺,在瀏覽器中輸出的形式可能不同吱殉,但結(jié)論都是一樣的,輸出的就是一個User的實例化對象的表示形式厘托。
coolcao.toString()輸出的是[object Object],而coolcao.valueOf()輸出的是對象本身考婴,還是個對象。
而當使用coolcao+10進行運算的時候催烘,將對象coolcao調(diào)用的toString()方法得到的字符串進行計算的沥阱。
那是不是意味著,我們在使用 + 操作符等對對象進行計算時伊群,會調(diào)用toString()方法呢考杉?
我們重寫一下User的valueOf()方法。

class User {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    valueOf(){
        return this.age;
    }
}

再來看一下上面四個console的輸出:

{ [Number: 23] name: 'coolcao', age: 23 }
[object Object]
23
33

這時舰始,我們發(fā)現(xiàn)崇棠,直接輸出對象的形式也變了,最后使用coolcao+10的時候丸卷,結(jié)果也變了枕稀。
對比兩次結(jié)果,我們可以發(fā)現(xiàn)谜嫉,直接輸出對象萎坷,只是顯示變了,但是輸出的還是對象沐兰,本質(zhì)是沒變的哆档。
唯一變得是,當直接使用對象進行計算的時候住闯,值變了瓜浸,coolcao對象取的是valueOf()返回的值澳淑,進行計算的。
我們再來重寫一下toString()方法:

class User {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    valueOf(){
        return this.age;
    }
    toString(){
        return `[name:${this.name}|age:${this.age}]`;
    }
}

上面打印的結(jié)果:

{ [Number: 23] name: 'coolcao', age: 23 }
[name:coolcao|age:23]
23
33

我們看出插佛,最后計算還是沒有變杠巡,還是取的valueOf()的值。

那是不是我們可以得出結(jié)論雇寇,對于自定義的對象氢拥,在使用對象進行計算時,將使用其valueOf()的返回值呢谢床?

不嚴謹?shù)闹v兄一,可以這么說厘线。

其實识腿,是這樣的,對于非原始類型造壮,進行計算時要轉(zhuǎn)換成原始類型渡讼。轉(zhuǎn)換步驟就是本篇開頭說的步驟方法。
User是自定義的類耳璧,因此使用ToPrimitive()方法轉(zhuǎn)換時成箫,hint默認是Number。將默認調(diào)用DefaultNumber()進行轉(zhuǎn)換旨枯。
即先判斷valueOf()方法的返回值蹬昌。默認情況下(我們不重寫其valueOf方法)valueOf()方法返回對象本身,還是一個object類型攀隔,因此再調(diào)用DefaultString()進行轉(zhuǎn)換皂贩,判斷的是toString(),因此第一個我們沒重寫之前調(diào)用的是toString()返回的結(jié)果進行計算的昆汹。后面我們重寫了valueOf()方法明刷,返回原始類型數(shù)據(jù),就取valueOf的返回結(jié)果進行計算满粗。

注意:并不是說辈末,我們重寫了valueOf方法,就一定調(diào)用valueOf()的返回值進行計算映皆。而是valueOf返回的值是原始值時才會按照此值進行計算挤聘。
例如,如果我們這么寫:

var coolcao = new User('coolcao',{key:'age',value:22});

由于js語言的靈活性捅彻,我們在實例化User對象時檬洞,可以將age參數(shù)傳一個對象。如果這樣沟饥,valueOf()返回this.age將是一個object類型添怔,還不是原始值湾戳。那么,最后的輸出結(jié)果將是:

User { name: 'coolcao', age: { key: 'age', value: 22 } }
[name:coolcao|age:[object Object]]
{ key: 'age', value: 22 }
[name:coolcao|age:[object Object]]10

再看下面一個例子广料,User的定義還是如上砾脑,重寫了toString()和valueOf()方法后:

var user = new User('coolcao',23);
console.log(user + 1);
console.log([user,1].join());

先猜測一下這兩個輸出什么呢?

24
[name:coolcao|age:23]1

會不會有疑問艾杏,為什么第一個直接使用 + 操作符計算的時候韧衣,取的valueOf()的值,而第二個對數(shù)組進行join()的時候购桑,取得是toString()的值呢畅铭?
我們說過,對對象進行運算操作時,默認先判斷valueOf,如果valueOf()方法返回的是原始類型的值,就使用原始類型的值進行計算,否則將使用toString()方法返回的值進行計算.
那么第二個輸出是什么鬼,按道理講不應該是 字符串 '11' 嘛?難道上面的結(jié)論總結(jié)錯了?
其實原因在于數(shù)組的join方法.
我們看一下join方法的實現(xiàn)說明:

The join method takes one argument, separator, and performs the following steps:

  1. Let O be ? ToObject(this value).
  1. Let len be ? ToLength(? Get(O, "length")).
  2. If separator is undefined, let separator be the single‐element String ",".
  3. Let sep be ? ToString(separator).
  4. If len is zero, return the empty String.
  5. Let element0 be Get(O, "0").
  6. If element0 is undefined or null, let R be the empty String; otherwise, let R be ? ToString(element0).
  7. Let k be 1.
  8. Repeat, while k < len
    a. Let S be the String value produced by concatenating R and sep.
    b. Let element be ? Get(O, ! ToString(k)).
    c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).
    d. Let R be a String value produced by concatenating S and next.
    e. Increase k by 1.
  9. Return R.
    NOTE 2 The join function is intentionally generic; it does not require that its this value be an Array object. Therefore,it can be transferred to other kinds of objects for use as a method.

從上面的步驟中我們看出,數(shù)組的join方法進行計算的時候,要對每個非undefined和非null的項轉(zhuǎn)換成字符串.所以上面例子中輸出的結(jié)果也就不難理解了.

Object.prototype.toString

js的所有對象,都是繼承于Object的勃蜘,因此硕噩,每個對象都會默認有個toString()方法。我們先看一下ECMA-262對toString方法的解釋:

When the toString method is called, the following steps are taken:
1. If the this value is undefined, return "[object Undefined]".
2. If the this value is null, return "[object Null]".
3. Let O be ToObject(this value).
4. Let isArray be ? IsArray(O).
5. If isArray is true, let builtinTag be "Array".
6. Else, if O is an exotic String object, let builtinTag be "String".
7. Else, if O has an [[ParameterMap]] internal slot, let builtinTag be "Arguments". 8. Else, if O has a [[Call]] internal method, let builtinTag be "Function".
9. Else, if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
10. Else, if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
11. Else, if O has a [[NumberData]] internal slot, let builtinTag be "Number".
12. Else, if O has a [[DateValue]] internal slot, let builtinTag be "Date".
13. Else, if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
14. Else, let builtinTag be "Object".
15. Let tag be ? Get(O, @@toStringTag).
16. If Type(tag) is not String, let tag be builtinTag.
17. Return the String that is the result of concatenating "[object ", tag, and "]".
This function is the %ObjProto_toString% intrinsic object.
NOTE Historically, this function was occasionally used to access the String value of the [[Class]] internal slot that was used in previous editions of this speci ication as a nominal type tag for various built‐in objects. The above de inition of toString preserves compatibility for legacy code that uses toString as a test for those speci ic kinds of built‐in objects. It does not provide a reliable type testing mechanism for other kinds of built‐in or program de ined objects. In addition, programs can use @@toStringTag in ways that will invalidate the reliability of such legacy type tests.

從說明上缭贡,我們可以看出炉擅,toString()默認會返回字符串 "[object ", tag"]"
而tag的值將會有下面幾個值:Undefined,Null,Array,String,Arguments,Boolean,Number,Date,RegExp,Object,Error
這里有一個小技巧阳惹,如果要判斷一個值的具體類型谍失,就可以使用toString()方法進行判斷。例如:

Object.prototype.toString.call([]);     //'[object Array]'
Object.prototype.toString.call(null);   //'[object Null]'
Object.prototype.toString.call(1);      //'[object Number]'
Object.prototype.toString.call(NaN);    //'[object Number]'
Object.prototype.toString.call(true);   //'[object Boolean]'
Object.prototype.toString.call(function(){});   //'[object Function]'

只要判斷toString()返回的字符串即可莹汤。

Array.prototype.toString

When the toString method is called, the following steps are taken:

  1. Let array be ? ToObject(this value).
  1. Let func be ? Get(array, "join").
  2. If IsCallable(func) is false, let func be the intrinsic function %ObjProto_toString%.
  3. Return ? Call(func, array).

NOTE The toString function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

從說明中我們看以看出快鱼,數(shù)組的toString()實現(xiàn)步驟是,先將數(shù)組ToObject()轉(zhuǎn)換成包裝類對象纲岭,這里由于數(shù)組本身就是包裝類抹竹,因此這里返回的是數(shù)組本身。
array = ToObject(this)這里array即是數(shù)組本身荒勇。
然后另func等于Get(array,'join').那這里就要看一下Get()這個方法的實現(xiàn)了柒莉。

Get (O, P)

  1. Assert: Type(O) is Object.
  1. Assert: IsPropertyKey(P) is true.
  2. Return ? O.[[Get]](P, O).

首先斷言O是不是一個對象,這里就是數(shù)組嘛沽翔,當然是兢孝,通過斷言。
然后斷言O有沒有P屬性仅偎,對于數(shù)組Array來說跨蟹,是有join方法的,斷言通過橘沥。繼續(xù)往下走窗轩。
返回的就是調(diào)用O上的P屬性返回的值。即這里Get(array,'join')返回的就是 array.join的值座咆,這里是一個方法痢艺。即 func = array.join
往上看仓洼,func 是數(shù)組的join方法,當然是可執(zhí)行的堤舒,因此第三步中的 IsCallable(func)是true色建,繼續(xù)第四步
最后返回Call(func,array),即執(zhí)行array.join()方法
也就是說舌缤,對于數(shù)組箕戳,如果不重寫其toString()方法,其默認實現(xiàn)就是調(diào)用數(shù)組的 join()方法返回值作為toString()的返回值国撵。

多說無意陵吸,看例子:

> [].toString()
''
> [1,2,3].toString()
'1,2,3'
> [1,2,3].join()
'1,2,3'
> [true,false].join()
'true,false'
> [0,1,'2'].join()
'0,1,2'
> [0,1,'2'].toString()
'0,1,2'

所以,只要看明白ecma規(guī)范中對于各個方法的說明介牙,是很容易理解的壮虫。
所以,像Boolean,Function等等的toString()方法的實現(xiàn)說明耻瑟,看ecma的說明就好了旨指,多說無益赏酥。這里就不再往上抄了喳整,也不做記錄了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裸扶,一起剝皮案震驚了整個濱河市框都,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呵晨,老刑警劉巖魏保,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異摸屠,居然都是意外死亡谓罗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門季二,熙熙樓的掌柜王于貴愁眉苦臉地迎上來檩咱,“玉大人,你說我怎么就攤上這事胯舷】舔牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵桑嘶,是天一觀的道長炊汹。 經(jīng)常有香客問我,道長逃顶,這世上最難降的妖魔是什么讨便? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任充甚,我火速辦了婚禮,結(jié)果婚禮上霸褒,老公的妹妹穿的比我還像新娘津坑。我一直安慰自己,他們只是感情好傲霸,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布疆瑰。 她就那樣靜靜地躺著,像睡著了一般昙啄。 火紅的嫁衣襯著肌膚如雪穆役。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天梳凛,我揣著相機與錄音耿币,去河邊找鬼。 笑死韧拒,一個胖子當著我的面吹牛淹接,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叛溢,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼塑悼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了楷掉?” 一聲冷哼從身側(cè)響起厢蒜,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烹植,沒想到半個月后斑鸦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡草雕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年巷屿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墩虹。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘱巾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出败晴,到底是詐尸還是另有隱情浓冒,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布尖坤,位于F島的核電站稳懒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜场梆,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一墅冷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧或油,春花似錦寞忿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辖佣,卻和暖如春霹抛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卷谈。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工杯拐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人世蔗。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓端逼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親污淋。 傳聞我的和親對象是個殘疾皇子顶滩,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容