![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAnYldHL0FWby9mZvwFN4ETMfdHLkVGepZ2XtxSZ6l2clJ3LcV2Zh1Wa9M3clN2byBXLzN3btgHL9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5SMzIjM0UGM5I2N0AjZjljZyYzX4QzM0ITM1EzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
toString真是一個讓我愛不釋手的方法,雖然它的職能定位很簡單也很清晰,但是有的時候總是老忍不住使用它應用在一些其他的場景中。那麼它能做什麼,能幫助我們簡化或者解決哪些複雜的問題呢?下面就讓我為大家一一道來。
從字面意思來看toString表示的是轉為字元串,問題就出在這,那麼不同的對象他們的行為都是怎樣的呢?先來看一下大概效果,然後再通過他們來做一些神奇的事情。
let a = "book"
let b = 5
let c = false
let d = {
m: 1
}
let e = [1,2,3]
let f = function add(v1, v2) {
return v1 + v2
}
let g = new RegExp("\d", "g")
let h = new Date()
基本列出了絕大部分常用的類型
(其中null和undefined除外,其餘類型類似)
,我們看一下執行toString之後得到的結果:
我們可以看到,除了Object和Array類型,基本都按照定義時的樣子直接輸出了。
就這?也沒啥呀。[打臉]
接下來我們重點讨論一下toString的特殊之處↓↓↓
用于數值類型
對于數值類型來說,toString可以傳入一個參數,用于指定轉換之後的進制表示。
如:将十進制的5轉化為二進制形式的字元串
由此我們可以将十進制的數值轉換為任意合法進制的字元串表示形式。
我們注意到,在數值類型調用toString的時候先定義了一個變量來接收這個數值,然後再進行的轉換,那麼我們可不可以直接調用呢?
很遺憾!這樣是不行的,那是怎麼回事呢?我們又該怎麼解決呢?
原來由于js表示數值方式的關系,所有的數值都會被解析為浮點數,也就是小數,是以5後面的點不是用來調用toString方法的,而是被解析成了小數點,看一下它的行為我們就能了解了。
其實5.被解析成了5.0,隻不過0可以省略,是以上面的代碼就相當于(5.t)oString(),當然就報錯啦。
我們有以下幾種方式來處理這種現象:
- 用括号包裹:(5).toString()
- 寫兩個點:5..toString()
- 中間放一個空格:5 .toString()
用于對象類型
對于一個對象來說,執行完toString之後,我們幾乎擷取不到任何有用的資訊,但是我們可以重寫它的toString方法
(雖然其他類型也可以這樣做,但是沒什麼必要)
,來達到覆寫預設行為的效果,以便輸出我們想要的。
除了我們顯示的調用toString,其實對象也會有一些預設的隐式調用,比如在做相等運算符的時候。
(Tips: 其實這裡在做隐式調用的時候,會優先檢查該對象的valueOf方法是否存在,如果有重寫,那麼會優先調用valueOf并擷取傳回值,然後再做比較,否則才會調用toString方法。)
相信看到這裡,小夥伴們都會想到那個經典的面試題,而且心中應該也已經有了思路,一起看一下:
是不是很容易了解了呢?
通過這個例子,我們了解了它的運作機制,其實一通百通,其他的應用場景大家也應該是能夠信手拈來的了
類似的還有一道題,其實原理也是這樣的。
數組在調用sort方法進行排序的時候,如果不傳入回調函數,預設行為會将每一項執行toString之後再進行比較,這個時候完全就是按照字元編碼的順序進行排列,是以會産生反直覺的結果。
是以,我們同樣可以改寫toString來改變預設行為:
用于函數類型
這個是我比較鐘愛的一種操作,他幫助我解決了很多難題,甚至除了這種方法我沒有想到其他能解決的辦法,請看下面的示例:
//DTtestone.js
function dataView() {
[{
className: "pd-5px",
cols: [{
span: 8,
className: "text-right",
elements: [{
type: "string",
descrip: "報帳金額"
}]
}, {
span: 16,
elements: [{
type: "input",
model: true,
name: "money",
money: allDatas.money,
}, {
type: "string",
descrip: "(元)"
}]
}]
}]
}
乍一看,這個函數沒有什麼特殊之處,隻是感覺有點奇怪,甚至覺得沒有什麼用,因為隻是執行了一個數組,連一個接收值的變量都沒有定義。
是的,函數的toString執行之後傳回的結果就是定義的方法本身。我們把這個函數放在代碼的任意一個地方,不去執行的話,永遠不會報錯。但是一旦執行的話,我們注意allDatas.money這裡,就是抛出未定義的錯誤。
考慮這樣一個場景。我的項目中為了做到元件化管理,我使用了資料視圖來驅動元件的渲染,一個元件會有多種不同的呈現形态,即會對應很多個類似上面代碼中dataView函數中的數組(即資料視圖),甚至更複雜的資料視圖。如果這些資料視圖都寫在一個檔案裡面,那麼管理起來相當麻煩,而且維護成本非常高。
那麼我們能不能把資料視圖與元件剝離開來,作為單獨的子產品進行管理呢?
自然而然的就會想到,可以使用export和import來對這些檔案進行引用,每個檔案都暴露出一個數組(即資料視圖)。這時又會引發另一個問題,那就是這裡面的allDatas是注入到元件裡面的執行個體屬性,直接在資料視圖裡面引用的話,在運作時會直接抛出錯誤。
可是我們總不能每個資料視圖的檔案都引入一次吧,而且我們希望是在元件裡面的資料全部初始化完畢之後再去解析資料視圖,生成最終的狀态。要是再有很多的元件呢?那麼這種情況将變得不可控。
是以我們既想要能在獨立的檔案中導出,又不想每個檔案都引入元件,更不想在運作時報錯,而且要能夠隻在指定的時機進行解析。那麼此時我們就有請超牛氣的toString登場,來幫我們解決這些問題。
此時我們隻需要按照上面的函數方式書寫,然後在導出的時候做一下處理:
//DTtestone.js
export default {
name: 'DTtestone',
dataView: dataView.toString()
}
接下來在掃描檔案的時候,我們再做進一步處理:
//importDataView.js
const elementsDataViews = require.context(
// 視圖目錄的相對路徑
'@/dataViews',
// 是否查詢其子目錄
true,
// 比對資料視圖檔案名的正規表達式
/DT.+.(js)$/
)
elementsDataViews.keys().forEach(fileName => {
const componentConfig = elementsDataViews(fileName)
let { name, dataView } = componentConfig.default
allDataViews[name] = dataView.substring(21, dataView.length - 1)
})
export default allDataViews
其中對擷取到的資料視圖進行dataView.substring(21, dataView.length - 1)就拿到了函數體中的内容。
最後我們在元件檔案裡面引入這些資料視圖,并且可以在适當的時機去執行它們:
//testone.vue
eval(allDataViews[name]);//name為引入的視圖名稱
到此為止,用這種方式就可以解決我們上面遇到的所有問題,甚至能使用目前元件内執行作用域鍊上的所有變量。
vue3提出了一個很重要的概念,也是它非常重要的一個特性,就是組合api。使得代碼組織管理起來非常友善,提升了可讀性與可維護性。
利用上面這種方法,我們甚至可以在代碼塊級别的粒度上高度抽象出來複用性強、耦合度低、易維護的功能。