[] == ![] 發生了什麼?

来源:https://www.cnblogs.com/sujinqu/archive/2019/08/27/11421385.html
-Advertisement-
Play Games

記不清在某處看見了這一比較,當時對強制轉換這塊理解的還沒有特別清晰,故有此一文。以為我會以標題的表達式來展開?那你就錯了,下麵直接上[] == ![]是如何轉換的: 1. 因為!運算符的優先順序比較高,所以表達式右側先運行![],得出false,表達式變為[] == false 2. 強制將false ...


記不清在某處看見了這一比較,當時對強制轉換這塊理解的還沒有特別清晰,故有此一文。以為我會以標題的表達式來展開?那你就錯了,下麵直接上[] == []是如何轉換的:

  1. 因為!運算符的優先順序比較高,所以表達式右側先運行[],得出false,表達式變為[] == false
  2. 強制將false轉換為0,表達式變為[] == 0
  3. 將[]強制轉換為原始類型後為"",表達式變為"" == 0
  4. 將""轉換為Number類型,表達式變為0 == 0
  5. 兩側類型相同,直接返回0 === 0的結果true
    - - -

    前言

    本文旨在總結js中強制轉換的規則及觸發強制轉換的幾種場景。ES6標準中定義了六種原始類型,分別是Undefined,Null,String,Number,Boolean,Symbol。本文中的強制轉換指的是在代碼運行時,觸發了數值的隱式轉換,而不是代碼顯示的指定轉換操作。

    原始類型間強制轉換

    發生在原始類型之間的轉換,以個人的理解是其他類型轉換為String,Number或者Boolean類型。

    轉換為String類型

    其他原始類型轉換為String類型通常發生在+兩邊存在字元串時,會將+另一邊的值轉換為String類型。
    考慮如下代碼:

var strAddNum = "test" + 1;
var numAddStr = 1 + "test";
var boolAddStr = true + "test";
var undAddStr = undefined + "";
var nullAddStr = null + "";
console.log(strAddNum);
console.log(numAddStr);
console.log(boolAddStr);
console.log(undAddStr);
console.log(nullAddStr);

代碼傳送門,以上代碼的運行結果均為字元串。其他原始類型轉換為String類型基本是其值的字元串形式,具體如下:

  • Undefined,"undefined"
  • Null,"null"
  • Boolean,"true"或"false"
  • Number,值為NaN,"NaN"
  • Number,值為+0或-0,"0"
  • Number,值為+Infinity,"Infinity"
  • Number,值為-Infinity,"-Infinity"

Number轉為字元串具體可參考ES2018 7.1.12.1章節

註意:Symbol類型無法轉換為String類型。

轉換為Number類型

轉換為Number類型的情況,+-*/%等運算中,除了+之外其他運算均會轉換成Number類型,+運算時需要滿足兩側未出現String類型,該值才會被轉換為Number類型。+運算時情況較為複雜,後面會專門描述其相關轉換規則。考慮如下代碼:

var trueAddTrue = true + true;
var trueAddFalse = true + false;
var trueAdda0 = true + 0;
var nullAddTrue = null + true;
var undefinedAdd0 = undefined + 0;
var strAdd0 = "" + 0;
console.log(trueAddTrue);
console.log(trueAddFalse);
console.log(trueAdda0);
console.log(nullAddTrue);
console.log(undefinedAdd0);
console.log(strAdd0);

代碼傳送門,在運行代碼之前可以先考慮下以上代碼答列印的結果分別是什麼?然後再運行,看是否符合你的預期。其他原始類型轉換為Number類型的具體如下:

  • Undefined,NaN
  • Null, +0
  • Boolaen,值為true,1
  • Boolean,值為false,+0
  • String,不可轉為Number的,NaN
  • String,可轉為Number的就是其對應的Number值(具體可參考ES2018 7.1.3.1

註意:Symbol類型同樣無法轉換為Number類型。

轉換為Boolean類型

轉換為Boolean類型的情況較為簡單,除了以下情況轉換為Boolean類型會是false,其他情況均是true

  • Undefined
  • Null
  • Number,+0,-0,NaN
  • String,長度為0的字元串
    這幾種false的情況在ES標準中有明確規定7.1.2

對象強制轉換為原始類型

ES中將對象轉換為原始類型的演算法,大致可描述為三種情形:

  1. 如果該對象設置了[Symbol.toPrimitive],調用該函數,如果其返回值為非Object類型則返回結果,否則拋出TypeError異常
  2. 若未指定轉換提示則轉換提示為"default"
  3. 若轉換提示為"default",則將其置為"number"
  4. 當指定轉換提示為"number"時先調用該對象的valueOf函數並判斷其結果,如果是原始類型則返回結果,否則調用該對象的toString函數並判斷其返回結果,如果結果為原始類型則返回,否則拋出異常TypeError
  5. 當指定轉換提示為"string"時先調用toString函數並判斷其返回結果,如果是原始類型則返回結果,否則調用該對象的valueOf函數並判斷其返回結果,如果結果為原始類型則返回,否則拋出異常TypeError

上述三種情形中第一種情形優先順序最高,第二三種情形優先順序併列,具體需要根據使用場景判斷是哪一種。其中的指定轉換提示是ES標準內部調用該演算法時指定的。

第一種情形只有Symbol對象和Date對象內置了[Symbol.toPrimitive],且該屬性的writeable為false,enumerable為false,configurable為true
對象轉換為原始類型時發生的強制轉換非特殊情況均為第二種,第三種情況較為少見。在正常編碼工作中應該使用第二種情形就夠用了,第三種情形幾乎不會出現,要瞭解更多細節可查閱ES標準。

var test = {
  [Symbol.toPrimitive]: function(hint) {
     console.log(hint)
  },
  valueOf: function() {
      console.log("valueOf")
  },
  toString: function() {
      console.log("toString")
  }
}
test + "";  //"default"
test * 0;   //"number"
String(test);   //"string"

代碼傳送門上述代碼指定了分別指定了test對象的[Symbol.toPrimitive],valueOf和toString函數,可以觀察到並valueoOf和toString函數均未被調用,指定的[Symbol.toPrimitive]函數可以接受一個提示參數,這個參數就是強制轉換時的強制轉換提示。這樣我們在函數中就可以根據轉換場景的不同分別返回不同的值。

原始類型強制轉換為對象(裝箱)

在開始描述這個問題之前,可以先思考一下,都有哪些場景會是強制的將原始類型轉換為對象,其實這種場景幾乎在js代碼中隨處可見,考慮如下代碼:

var str = "testString";
str.replace("test", "");

如上代碼中定義的str的值並不是一個對象而是一個原始類型String,原始類型顯然是沒有方法可以調用的。

實際上這裡的str在執行str.replace時str其值會被強制轉換為對象,得到一個String類型的實例對象,而該實例的原型上定義了一系列方法,且該實例是無法被獲取的,在執行完這行代碼後,該實例就會被回收,所以這裡的str依然是一個字元串。

考慮如下代碼:

var a = 3;
a.fn = function(){};
a.fn();

強制轉換的幾種場景

在js代碼中會出現強制轉換的場景通常有三種:

  • +運算
  • -,*,/,%運算
  • ==比較
  • 作為判斷條件

    +運算

    一元+運算

    做一元+運算時,均會被強制轉為Number類型,例如

var a = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // number
        if(hint === "number") {
            return 2;
        } else {
            return 9;
        }
    }
};
console.log(+a);   // 2

var b = "3";
console.log(+b);    // 3

代碼傳送門

二元+運算

二元+運算為幾種強制轉換中複雜度僅次於==比較的一種情形,個人總結其轉換步驟如下:

  1. 先將兩側數值強制轉換為原始類型(未指定轉換提示,即轉換提示為hint default);
  2. 若兩側存在String類型,均轉換為String類型,則返回兩側拼接的字元串;
  3. 若第2未返回,則兩側數值強制轉換為Number類型,返回計算結果;
var a = "";
var b = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "default"
        if(hint === "default") {
            return 2;
        } else {
            return 9;
        }
    }
};
var c = a + b;  //這裡b轉換為原始類型返回的是Number類型2,由於a是"",所以b被轉換為"2",後與""拼接返回"2"
console.log(c); // "2"

var d = 3;
var e = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "default"
        if(hint === "default") {
            return 2;
        } else {
            return 9;
        }
    }
};
var f = d + e;  //這裡e轉換為原始類型返回的是Number類型2,由於兩側均沒有String類型,則至第3步,強制轉換為Number後返回兩側相加的結果5
console.log(f); // 5

代碼傳送門

-,*,/,%運算
這幾個運算符這涉及的強制轉換都是轉換為Number類型的,所以這裡只要搞清楚轉換為Number是怎樣的過程就可以了。上文中已經對原始類型轉換為Number類型做了描述,這裡補充一下Object轉換為Number的過程:

  1. 將對象轉換為原始類型,且轉換時會指定轉換提示為"number";
  2. 轉換為原始類型後再根據原始類型轉換為Number類型進行轉換;
var a = 8;
var b = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "number"
        if(hint === "number") {
            return 2;
        } else {
            return 9;
        }
    }
};
console.log(a-b);   //  6
console.log(a/b);   // 4
console.log(a*b);   // 16
console.log(a%b);   // 0

console.log(undefined * 0);   //NaN
console.log(null * -1); // 0
console.log(false * -1);    //0 
console.log(true * -1); // -1
console.log("1" * -1);  // -1

代碼傳送門

==比較

==比較的基礎===比較

x === y,其具體比較步驟如下:

  1. 若x和y的類型不一致,返回false;
  2. 若x和y為Number類型,則若x和y中有一個為NaN返回false;若x和y的值相等則返回true;若x是+0,y是-0或者x是-0,y是+0則返回true;其他情況返回false;
  3. 若x和y為Undefined類型,返回true
  4. 若x和y為Null類型,返回true
  5. 若x和y為String類型,其值相同則返回true,否則返回false
  6. 若x和y為Boolean類型,其值均為true或均為false返回true,否則返回false
  7. 若x和y為Symbol類型,其值為同一個Symbol值則返回true,否則返回false
  8. 若x和y為Object類型,其值為同一個對象(其引用地址相同)則返回true,否則返回false

x == y規則

==比較的轉換規則雖然稍微多一點,實際上也就幾條規則,兩側的數值類型符合哪種就按哪種去轉換,只不過有的可能需要轉兩次,具體如下:

  1. 如果兩側類型相等則直接返回===的結果;
  2. 若x為undefined和y為null或x為null和y為undefined,返回true
  3. 若兩側為String類型和Number類型,將String類型轉換為Number類型,繼續用==比較
  4. 若有一側存在Boolean類型,將Boolean類型轉換為Number類型,繼續用==比較
  5. 若兩側為String,Number或Symbol類型和Object類型,將Object類型轉換原始類型,繼續用==比較
  6. 其他返回false

下麵列舉一些可能有點違反直覺的比較

"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
[] == ![];  //true

作為條件判斷

這種情形到沒有太多可說的,基本上就是,除了undefined,null,+0,-0,NaN,""這六個值會被轉為false,其他情況均為true;

出現將不是Boolean類型的值強制轉換的情況為

  1. if(...)
  2. for(...;...;...)第二個條件表達式
  3. while(...)和do...while(...)中的條件表達式
  4. ...?...:...三元表達式中的第一個條件表達式
  5. ||和&&

結論

其實上面描述了這麼多,日常開發環境中用到比較多的應該是作為判斷條件,+,==這三種情況了,這三種中最常見的應該是判斷條件的情況了,這種情況反而是最簡單的一種了。

轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • start with connect by prior 遞歸查詢用法 子句:遍歷起始條件 子句:連接條件。關鍵詞prior,prior跟父節點列parentid放在一起,就是往父結點方向遍歷;prior跟子結點列subid放在一起,則往葉子結點方向遍歷,parentid、subid兩列誰放在 前都無 ...
  • 每一種語言都有它的註釋方式,代碼量少的時候還可以,隨著代碼量越來越多,代碼註釋的重要性也越發凸顯。 在mysql中主要有三種方式: 1、常用的方式,跟在css中那些註釋一樣 :/* 內容 */ 2、兩條橫線,mysql中通用的方式,一般都用這個: -- 內容(最後一條橫線後有空格) 3、 欄位或列的 ...
  • LOAD DATA INFILE 語句用法 參考手冊 本文語句參數使用預設值 PHP: TP框架環境 例子中 數據 和 表結構簡單 參考~ ...
  • 1.使用AlertDialog.Builder 對話框自定義view,並通過setview設置 這裡要想在對話框按鈕的監聽事件中調用xml佈局裡面的控制項,不能直接findViewById,需要這樣寫 對話框.show()函數之後才可以調用, 編程之路,坑多且長,此處留白,未完待續 ...
  • 在我們實際的項目開發中,經常會遇到頁面UI內容過多,導致手機一屏展示不完的情況出現,以Android為例,在Android中遇到這類情況的做法通常就是使用ScrollView將內容包裹起來,如果不做可滑動的處理,Android上的表現為頁面的部分內容無法展示,而在Flutter中,如果內容過多無法展... ...
  • $.each一般用來遍歷一個數組或對象,$.fn.each()就是指jQuery實例可以執行的操作(因為$.fn是jQuery對象的原型) $.each用來遍歷一個數組或對象,並依次執行回掉函數,最後返回傳遞的數組或對象,以支持鏈式操作,可以傳遞三個參數,如下: object 待遍歷的對象或數組 c ...
  • 原文地址:https://rxjs.dev/guide/overview 簡介 RxJS 是組合非同步以及基於事件的使用可觀察者序列的程式類庫。它提供一個核心類型, "Observable" ,附屬類型(Observer,Schedulers,Subjects)並且受到了數組額外操作(map,filt ...
  • 1、比較方法(常用) 2、取整(常用) 3、隨機數(常用) 4、其他(不常用) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...