文章加密

;

2024年10月3日 星期四

[TS] any v.s. unknown

unknown

我們不能夠對 unknown 型別的東西進行任何除了讀寫和比較以外的操作

功能一:保證變數的值不會被不小心地操作到

例子:

function compareArrayAtEven(arr1: unknown[], arr2: unknown[]): boolean {
    let diff = arr1.length - arr2.length;
    if(diff > 1 || diff < -1) return false;
    for(let i = 0; i < arr1.length && i < arr2.length; i++) {
        if(arr1[i] !== arr2[i] + 2) return false; // TypeScript 會在這邊報錯
    }
    return true;
}


功能二:負責把關來自外界的輸入

預防各種執行階段中的不可預期現象發生

例子:

export function splitString(arg: unknown): string[] {
    if(typeof arg == 'string') {
        // 檢查完了之後才做我們真正要做的事情,例如:
        return arg.split(',');
        // 此處 TypeScript 會正確地知道 arg 的型別為字串,
        // 因為剛才我們用 typeof 檢查過了。
        // 對於更複雜的型別檢查,可以用 instanceof 關鍵字、
        // 或是使用 TypeScript 的 TypeGuard 來達成,
        // 這是題外話,這邊先不深入討論。
    } else {
        // 否則看要怎麼進行錯誤處理;這邊的方法是傳回預設值
        return [];
        // 或者也可以丟出一個 Error 包含了我們自訂的錯誤訊息
    }
}


養成把對外 API 的參數宣告成 unknown 的好習慣,並且避免使用 TypeScript 中的 as 關鍵字來作型別斷言(取而代之地,應該使用 instanceof 或自訂的 TypeGuard 來確認型別)

any

any 隨便做都行,沒鑑別度

很大的程度上來說,是的;我個人現在是「TypeScript 程式碼中應該要幾乎沒有 any 才對」主張的擁護者。對於很多我接手的程式碼,我都會先搜尋出所有使用到 any 的地方,而它們大多都可以直接換成 unknown 而不用作額外的修改——而如果換成 unknown 之後某個地方因此就出現了編譯錯誤,那幾乎在所有的情況中,那都是突顯出了程式碼具有潛在的異味(bad smell),而釐清為什麼改成 unknown 之後有編譯錯誤、往往能讓程式碼變得更加健全

只有一種情況是我會勉強接受 any 的使用的,那就是當我們引入了一個第三方的型別,我們很清楚其規格、但是我們偏偏又沒有該型別的完整定義檔、而我們自己去寫定義檔又很浪費時間的時候。然而,這樣的使用有幾個前提是應該要遵守的:

  1. 使用了 any 型別的物件應該要充分地被封裝起來,使得使用它的程式碼都非常清楚該變數要怎麼操作。如果有很多程式碼依賴於該物件,那應該要提供一個有良好定義型別的介面來讓其它程式碼間接操作該物件,而不是讓所有程式碼直接取用它。
  2. 對於該物件傳回的值,應該馬上用型別斷言或型別檢查來確定其型別,而不是繼續讓傳回值維持 any 的狀態。

如果沒辦法做到這兩點,那最好還是花一點時間自己把該型別當中會用到的東西宣告一下,這不僅能讓程式碼更有條理,也可以避免很多潛在的手殘可能性。

2024年7月23日 星期二

const numbers = [33, 2, 8]; numbers.sort(); //[2, 33, 8]

quiz:

const numbers = [33, 2, 8]; 

numbers.sort();  //[2, 33, 8]


Solution:

JavaScript is a dynamically typed language, which means that all standard library functionality must, at some point, decide how to work for most, if not all, use-cases.

Always keep in mind that the following array is valid:

const array = ["1", true, 55, 1.421, "foo", {}];


Array.prototype.sort now needs to make a decision on how to handle such scenarios, and the solution is pretty straight-forward:

Convert all values to their string representation (because every value in JavaScript can always be converted to a string!), and then sort them in lexicographic order(字典順序).

Which basically makes sort see the array as this:

const intermediate = ["33", "2", "8"];


And in lexicographic order, no matter how many characters a string has, comparison starts at position 0, and "3" comes before "8".

And in the end, the result is this:

["2", "33", "8"];

console.log(3 > 2 > 1); // false

 console.log(3 > 2 > 1); // false


因為

先比較 3>2 // true

後比較 true > 1

根據之前提及的 coercion變成Boolean(true) > 1 // false



[陷阱] console.log(typeof typeof 1); // string

 console.log(typeof typeof 1); // string


This actually returns "string".

This expression is evaluated from right to left.

The first sub-expression evaluated actually is typeof 1 which will return "number".

Only after that the next sub-expression is evaluated which now is typeof "number" which returns "string".

[陷阱]console.log("This is a string." instanceof String); // false

 console.log("This is a string." instanceof String); // false


This actually returns false.

The reason for that circumstance is that JavaScript distinguishes between primitives and objects.

And "This is a string." is actually a primitive string and not an instance of the object String.

If the code was like this:

new String("This is a string.") instanceof String


you'd actually get the result you would have expected at the beginning.

What instanceof actually does is checking if the String constructor is nested within the prototype chain of the value provided.

In this case, it isn't.

++{String}, prefix-operator, converting the right-side argument to a number

 The plus operator is defined for numbers and strings and as soon as a string is present on either the left or right side, a string concatenation is perfomed.


If we follow the execution path, this is what happens:

// 1st step
'b' + 'a' -> 'ba'
// 2nd step
'ba' + + 'a' // wait a second!

There are two plus operators in this expression. But one of those is actually a prefix-operator, and not a classical plus.
What it does is converting the right-side argument to a number
, but converting 'a' to a number will actually yield NaN!
Let's continue with following the execution path:

// 1st step
'b' + 'a' -> 'ba'
// 2nd step
'ba' + + 'a' -> 'ba' + NaN -> 'baNaN'
// 3rd step
'baNaN' + 'a' -> 'baNaNa'
// 4th step
'baNaNa'.toLowerCase() -> 'banana'

[有未看]the abstract equality comparison algorithm, which is also called the type-coercing equality check.

quiz:

// 1st step
false == '0'

// 2nd step
Number(false) == '0' -> 0 == '0'

// 3rd step 0 == '0' -> 0 == Number('0')

// 4th step
0 == 0 -> 0 === 0 -> true


solution:

there is a hierarchy for type coercion in JavaScript. The coercion process follows a set of rules:

  1. If either operand is a null or undefined, they are considered equal (unless strict equality is being used with the "===" operator).
    先檢查是否null或undefined
  2. If either operand is a boolean, the boolean is converted to a number (false becomes 0, true becomes 1) before comparison.
    布林值轉數字
  3. If one operand is a string and the other is a number, the string is converted to a number before comparison.
    字串和數字比的話,字串轉數字
  4. If one operand is an object and the other is a primitive value, the object is converted to its primitive value before comparison. This can be done using the valueOf() or toString() methods of the object.
  5. If the operands are both strings, they are compared as strings.
後兩個比較深,先跳過