您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么理解JavaScript類型轉換”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
實現一個函數,運算結果可以滿足如下預期結果:
add(1)(2) // 3add(1, 2, 3)(10) // 16add(1)(2)(3)(4)(5) // 15
對于一個好奇的切圖仔來說,忍不住動手嘗試了一下,看到題目首先想到的是會用到高階函數以及 Array.prototype.reduce()
。
高階函數(Higher-order function):高階函數的意思是它接收另一個函數作為參數。在 javascript 中,函數是一等公民,允許函數作為參數或者返回值傳遞。
得到了下面這個解法:
function add() {var args = Array.prototype.slice.call(arguments);return function() {var arg2 = Array.prototype.slice.call(arguments);return args.concat(arg2).reduce(function(a, b){return a + b; }); } }
驗證了一下,發現錯了:
add(1)(2) // 3add(1, 2)(3) // 6add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)
上面的解法,只有在 add()()
情形下是正確的。而當鏈式操作的參數多于兩個或者少于兩個的時候,無法返回結果。
而這個也是這題的一個難點所在,add()
的時候,如何既返回一個值又返回一個函數以供后續繼續調用?
后來經過高人指點,通過重寫函數的 valueOf
方法或者 toString
方法,可以得到其中一種解法:
function add () {var args = Array.prototype.slice.call(arguments);var fn = function () {var arg_fn = Array.prototype.slice.call(arguments);return add.apply(null, args.concat(arg_fn)); } fn.valueOf = function () {return args.reduce(function(a, b) {return a + b; }) }return fn; }
嗯?***眼看到這個解法的時候,我是懵逼的。因為我感覺 fn.valueOf()
從頭到尾都沒有被調用過,但是驗證了下結果:
add(1) // 1add(1,2)(3) //6add(1)(2)(3)(4)(5) // 15
神奇的對了!那么玄機必然是在上面的 fn.valueOf = function() {}
內了。為何會是這樣呢?這個方法是在函數的什么時刻執行的?且聽我一步一步道來。
先來簡單了解下這兩個方法:
用 MDN 的話來說,valueOf() 方法返回指定對象的原始值。
JavaScript 調用 valueOf() 方法用來把對象轉換成原始類型的值(數值、字符串和布爾值)。但是我們很少需要自己調用此函數,valueOf 方法一般都會被 JavaScript 自動調用。
記住上面這句話,下面我們會細說所謂的自動調用是什么意思。
toString() 方法返回一個表示該對象的字符串。
每個對象都有一個 toString() 方法,當對象被表示為文本值時或者當以期望字符串的方式引用對象時,該方法被自動調用。
這里先記住,valueOf() 和 toString() 在特定的場合下會自行調用。
好,鋪墊一下,先了解下 javascript 的幾種原始類型,除去 Object 和 Symbol,有如下幾種原始類型:
Number
String
Boolean
Undefined
Null
在 JavaScript 進行對比或者各種運算的時候會把對象轉換成這些類型,從而進行后續的操作,下面逐一說明:
在某個操作或者運算需要字符串而該對象又不是字符串的時候,會觸發該對象的 String 轉換,會將非字符串的類型嘗試自動轉為 String 類型。系統內部會自動調用 toString
函數。舉個例子:
var obj = {name: 'Coco'};var str = '123' + obj;console.log(str); // 123[object Object]
轉換規則:
如果 toString
方法存在并且返回原始類型,返回 toString
的結果。
如果 toString
方法不存在或者返回的不是原始類型,調用 valueOf
方法,如果 valueOf
方法存在,并且返回原始類型數據,返回 valueOf
的結果。
其他情況,拋出錯誤。
上面的例子實際上是:
var obj = {name: 'Coco'};var str = '123' + obj.toString();
其中,obj.toString()
的值為 "[object Object]"
。
假設是數組:
var arr = [1, 2];var str = '123' + arr;console.log(str); // 1231,2
上面 + arr
,由于這里是個字符串加操作,后面的 arr
需要轉化為一個字符串類型,所以其實是調用了 + arr.toString()
。
但是,我們可以自己改寫對象的 toString
,valueOf
方法:
var obj = { toString: function() {console.log('調用了 obj.toString');return {}; }, valueOf: function() {console.log('調用了 obj.valueOf')return '110'; } } alert(obj);// 調用了 obj.toString// 調用了 obj.valueOf// 110
上面 alert(obj + '1')
,obj 會自動調用自己的 obj.toString()
方法轉化為原始類型,如果我們不重寫它的 toString
方法,將輸出 [object Object]1
,這里我們重寫了 toString
,而且返回了一個原始類型字符串 111
,所以最終 alert 出了 1111。
上面的轉化規則寫了,toString
方法需要存在并且返回原始類型,那么如果返回的不是一個原始類型,則會去繼續尋找對象的 valueOf
方法:
下面我們嘗試證明如果在一個對象嘗試轉換為字符串的過程中,如果 toString()
方法不可用的時候,會發生什么。
這個時候系統會再去調用 valueOf()
方法,下面我們改寫對象的 toString
和 valueOf
:
var obj = { toString: function() {console.log('調用了 obj.toString');return {}; }, valueOf: function() {console.log('調用了 obj.valueOf')return '110'; } } alert(obj);// 調用了 obj.toString// 調用了 obj.valueOf// 110
從結果可以看到,當 toString
不可用的時候,系統會再嘗試 valueOf
方法,如果 valueOf
方法存在,并且返回原始類型(String、Number、Boolean)數據,返回valueOf
的結果。
那么如果,toString
和 valueOf
返回的都不是原始類型呢?看下面這個例子:
var obj = { toString: function() {console.log('調用了 obj.toString');return {}; }, valueOf: function() {console.log('調用了 obj.valueOf')return {}; } } alert(obj);// 調用了 obj.toString// 調用了 obj.valueOf// Uncaught TypeError: Cannot convert object to primitive value
可以發現,如果 toString
和 valueOf
方法均不可用的情況下,系統會直接返回一個錯誤。
添加于 2017-03-07:在查證了 ECMAScript5 官方文檔后,發現上面的描述有一點問題,Object 類型轉換為 String 類型的轉換規則遠比上面復雜。轉換規則為:1.設原始值為調用 ToPrimitive 的結果;2.返回 ToString(原始值) 。關于 ToPrimitive 和 ToString 的規則可以看看官方文檔:ECMAScript5 — ToString
上面描述的是 String 類型的轉換,很多時候也會發生 Number 類型的轉換:
調用 Number() 函數,強制進行 Number 類型轉換
調用 Math.sqrt() 這類參數需要 Number 類型的方法
obj == 1
,進行對比的時候
obj + 1
, 進行運算的時候
與 String 類型轉換相似,但是 Number 類型剛好反過來,先查詢自身的 valueOf
方法,再查詢自己 toString
方法:
如果 valueOf
存在,且返回原始類型數據,返回 valueOf
的結果。
如果 toString
存在,且返回原始類型數據,返回 toString
的結果。
其他情況,拋出錯誤。
按照上述步驟,分別嘗試一下:
var obj = { valueOf: function() {console.log('調用 valueOf');return 5; } }console.log(obj + 1);// 調用 valueOf// 6
var obj = { valueOf: function() {console.log('調用 valueOf');return {}; }, toString: function() {console.log('調用 toString');return 10; } }console.log(obj + 1);// 調用 valueOf// 調用 toString// 11
var obj = { valueOf: function() {console.log('調用 valueOf');return {}; }, toString: function() {console.log('調用 toString');return {}; } }console.log(obj + 1);// 調用 valueOf// 調用 toString// Uncaught TypeError: Cannot convert object to primitive value
什么時候會進行布爾轉換呢:
布爾比較時
if(obj) , while(obj) 等判斷時
簡單來說,除了下述 6 個值轉換結果為 false,其他全部為 true:
undefined
null
-0
0或+0
NaN
”(空字符串)
Boolean(undefined) // falseBoolean(null) // falseBoolean(0) // falseBoolean(NaN) // falseBoolean('') // false
好,***回到我們一開始的題目,來講講函數的轉換。
我們定義一個函數如下:
function test() {var a = 1;console.log(1); }
如果我們僅僅是調用 test
而不是 test()
,看看會發生什么?
可以看到,這里把我們定義的 test 函數的重新打印了一遍,其實,這里自行調用了函數的 valueOf
方法:
我們改寫一下 test 函數的 valueOf
方法。
test.valueOf = function() {console.log('調用 valueOf 方法');return 2; } test;// 輸出如下:// 調用 valueOf 方法// 2
與 Number 轉換類似,如果函數的 valueOf
方法返回的不是一個原始類型,會繼續找到它的 toString
方法:
test.valueOf = function() {console.log('調用 valueOf 方法');return {}; } test.toString= function() {console.log('調用 toString 方法');return 3; } test;// 輸出如下:// 調用 valueOf 方法// 調用 toString 方法// 3
再看回我正文開頭那題的答案,正是運用了函數會自行調用 valueOf
方法這個技巧,并改寫了該方法。我們稍作改變,變形如下:
function add () {console.log('進入add');var args = Array.prototype.slice.call(arguments);var fn = function () {var arg_fn = Array.prototype.slice.call(arguments);console.log('調用fn');return add.apply(null, args.concat(arg_fn)); } fn.valueOf = function () {console.log('調用valueOf');return args.reduce(function(a, b) {return a + b; }) }return fn; }
當調用一次 add 的時候,實際是是返回 fn 這個 function,實際是也就是返回 fn.valueOf()
;
add(1);// 輸出如下:// 進入add// 調用valueOf// 1
其實也就是相當于:
[1].reduce(function(a, b) {return a + b; })// 1
當鏈式調用兩次的時候:
add(1)(2);// 輸出如下:// 進入add// 調用fn// 進入add// 調用valueOf// 3
當鏈式調用三次的時候:
add(1)(2)(3);// 輸出如下:// 進入add// 調用fn// 進入add// 調用fn// 進入add// 調用valueOf// 6
可以看到,這里其實有一種循環。只有***一次調用才真正調用到 valueOf
,而之前的操作都是合并參數,遞歸調用本身,由于***一次調用返回的是一個 fn 函數,所以最終調用了函數的 fn.valueOf
,并且利用了 reduce 方法對所有參數求和。
除了改寫 valueOf
方法,也可以改寫 toString
方法,所以,如果你喜歡,下面這樣也可以:
function add () {var args = Array.prototype.slice.call(arguments);var fn = function () {var arg_fn = Array.prototype.slice.call(arguments);return add.apply(null, args.concat(arg_fn)); } fn.toString = function() {return args.reduce(function(a, b) {return a + b; }) }return fn; }
這里有個規律,如果只改寫 valueOf()
或是 toString()
其中一個,會優先調用被改寫了的方法,而如果兩個同時改寫,則會像 Number 類型轉換規則一樣,優先查詢 valueOf()
方法,在 valueOf()
方法返回的是非原始類型的情況下再查詢 toString()
方法。
“怎么理解JavaScript類型轉換”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。