在研究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:
- Let O be ? ToObject(this value).
- Let len be ? ToLength(? Get(O, "length")).
- If separator is undefined, let separator be the single‐element String ",".
- Let sep be ? ToString(separator).
- If len is zero, return the empty String.
- Let element0 be Get(O, "0").
- If element0 is undefined or null, let R be the empty String; otherwise, let R be ? ToString(element0).
- Let k be 1.
- 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. - 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:
- Let array be ? ToObject(this value).
- Let func be ? Get(array, "join").
- If IsCallable(func) is false, let func be the intrinsic function %ObjProto_toString%.
- 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)
- Assert: Type(O) is Object.
- Assert: IsPropertyKey(P) is true.
- 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的說明就好了旨指,多說無益赏酥。這里就不再往上抄了喳整,也不做記錄了。