【學習筆記】JavaScript 基礎 - 數據類型、運算符、類型轉換

數據類型

原始(Primitive)類型

  • 原始(Primitive)值通常叫作棧數據(一旦開了個房間、不可能在這個房間裏對其進行修改)數組

  • 原始類型存儲的都是值,是沒有函數能夠調用的,如 undefined.toString() 會報錯。通常咱們看到的 '1'.toString() 能夠調用是由於實際上它已經被強制轉換成了 String 類型也就是對象類型,因此能夠調用 toString 函數 image.png安全

  • 在 JS 中,存在着如下幾種原始值,分別是:markdown

    number(typeof undefined === "undefined")
    string(typeof '' === 'string')
    boolean(typeof true === 'boolean')
    null(typeof null === 'object')
    undefined(typeof null === 'undefined')
    symbol(typeof Symbol() === 'symbol')
    bigInt(typeof 10n === 'bigint')(沒有正式發佈但即將被加入標準的原始類型)函數

  • undefined 和 null 的區別post

    • null 有值,不過這個值是空值,null 表示爲空,表明此處不該該有值的存在,一個對象能夠是 null,表明是個空對象,null 通常用做佔位
    • undefined 是未定義,表示『不存在』,徹底沒有值的意思,JavaScript 是一⻔動態類型語言,成員除了表示存在的空值外,還有可能根本就不存在(由於存不存在只在運行期才知道),這就是 undefined 的意義所在
  • 除了會在必要的狀況下類型轉換之外,原始類型還有一些坑性能

    • typeof null 會輸出 object,這是 JS 存在的一個悠久 Bug,在 JS 的最第一版本中使用的是 32 位系統,爲了性能考慮使用低位存儲變量的類型信息,000 開頭表明是對象,然而 null 表示爲全零,因此將它錯誤的判斷爲 object
    • JS 的 number 類型是浮點類型,在使用中會可能會遇到某些 Bug,如 0.1 + 0.2 !== 0.3,此處詳見分析篇:爲何 0.1 + 0.2 !== 0.3

引用類型

  • 通常叫作堆數據,包括:ui

    對象(Object)(typeof {} === 'object') 數組(Array)(typeof [] === 'object')
    函數(Function)(typeof function(){} === 'function')spa

  • 引用類型原始類型的區別指針

    • 由於原始值是存放在棧裏的,而引用值是存放在堆裏的,原始值不能夠被改變,引用值能夠被改變
    • 原始值的賦值是把值的內容賦值一份給另外一個變量,棧內存一旦被賦值了就不能夠改變,即便給 num 從新賦值爲 234,也是在棧裏面從新開闢了一塊空間賦值 234,而後把 num 指向了這個空間,前面那個存放 123 的空間還存在
    • 但引用值卻不是這樣:引用值的變量名存在棧裏,可是值倒是存在堆裏,棧裏的變量名只是個指針並指向了一個堆空間,這個堆空間存的是一開始賦的值,當 arr1 = arr 時,實際上是把 arr1 指向了和 arr 指向的同一個堆空間,這樣當改變 arr 的內容時,其實就是改變了這個堆空間的內容,天然一樣指向這個堆空間的 arr1 的值也隨着改變
      // num的改變對num1徹底沒有影響
      var num = 123, num1 = num;
      num = 234;
      console.log(num) // 234
      console.log(num1) // 123
      
      // 只是改變了arr的值,可是arr1也跟着改變了
      var arr = [1,2,3], arr1 = arr;
      arr.push(4)
      console.log(arr) // [1,2,3,4]
      console.log(arr1) // [1,2,3,4]
      複製代碼

    再來看個函數參數是對象的狀況code

    function test(person) {
      person.age = 26
      person = {
        name: 'yyy',
        age: 30
      }
      return person
    }
    const p1 = {
      name: 'yck',
      age: 25
    }
    const p2 = test(p1)
    console.log(p1) // {name: "yck", age: 26}
    console.log(p2) // {name: "yyy", age: 30}
    複製代碼

    (1)上面代碼中,首先函數傳參是傳遞對象指針的副本
    (2)到函數內部修改參數的屬性這步,當前 p1 的值也被修改了
    (3)可是當從新爲 person 分配了一個對象時就出現了分歧,請看下圖,因此最 後 person 擁有了一個新的地址(指針),即和 p1 沒有任何關係,致使了 最終兩個變量的值是不相同的 image.png

  • 原始數據類型的值直接存放在棧中,對象爲引用數據類型,它們的引用變量存儲在棧中,指向於存儲在堆中的實際對象。沒法直接操縱堆中的數據,即沒法直接操縱對象,但可經過棧中對對象的引用來操做對象,就像經過遙控機操做電視機同樣,區別在於這個電視機自己並無控制按鈕

爲何引用值要放在堆中,而原始值要放在棧中?
(1)堆比棧大,棧比堆的運算速度快;對象是一個複雜的結構且能夠自由擴展,如數組能夠無限擴充、對象能夠自由添加屬性
(2)相對而言原始類型比較穩定且它只佔據很小的內存,不將原始類型放在堆是由於是爲了避免影響棧的效率,且經過引用到堆中查找實際對象是要花費時間的,而這個綜合成本遠大於直接從棧中取得實際值的成本,因此原始類型值直接存放在棧中

運算符

算術運算符(算術運算符的優先級是從左到右的)

  • +:數學上的相加功能、拼接字符串(字符串和任何數據相加都會變成字符串)
  • /*///%:f分別對應數學上的相減、相乘、相除、取餘功能
  • =:賦值運算符,優先級最低
  • ():和數學上同樣,加括號的部分優先級最高
  • ++:自加 1 運算,當寫在變量前時是先自加 1 再執行運算,寫在變量後時是先運算再自加 1
  • --:用法和 ++ 同樣,不過是減法操做
  • +=:讓變量自加多少
  • 相同的還有 -=、/=、*-、%= 等等

比較運算符

  • 比較運算符有 > 、< 、>= 、<= 、!= 、== 不嚴格等於、===嚴格等於
  • 不嚴格等於和嚴格等於的區別:當比較兩個數據時,是否先轉化成同一個類型的數據以後再進行比較。不嚴格等於就是說這兩個數據進行了轉化後值相同則整兩個數據相等;而嚴格等於則是兩個數據不進行數據轉化也相等

注:NaN 不等於任何數據包括它自己,null 和 undefined 就等於它自己

邏輯運算符

  • 邏輯運算符主要是 && 和 ||(與和或)
  • && 的做用:只有是 true 時纔會繼續日後執行,一旦第一個表達式就錯了後面的第二個表達式根本不執行。若表達式的返回結果都是 true 則這裏 && 的返回結果是最後一個正確的表達式的結果
  • || 的做用:只要有一個表達式是 true 則結束,後面的就不走了且返回的結果是這個正確的表達式的結果,若都是 false 則返回結果就是 false
  • 通常來講,&& 有當作短路語句的做用,由於運算符的運算特色,只有第一個條件成立時纔會運行第二個表達式,因此能夠把簡單的 if 語句用 && 來表現出來
  • 通常來講,|| 有當作賦初值的做用,有時但願函數參數有一個初始值,在不使用ECMA6 的語法的狀況下,最好的作法就是利用 || 語句

    注意:這裏有一個缺點,當傳的參數是一個布爾值且傳的是 false,則 || 語句的特色就會忽略掉所傳的這個參數值而去賦成默認的初始值,因此爲了解決這個弊端,就須要利用 ES6 的一些知識

默認爲 false 的值:undefined、null、" "、0、-0、false、NaN

類型轉換

顯示類型轉換

  • typeof 能返回的類型一共有 6 種:numner、string、boolean、undefined、object、function
    • 數組和 null 的都返回 'object'
    • NaN 屬於 number 類型:雖然是非數,可是非數也是數字的一種
  • Number(mix):該方法能夠把其餘類型的數據轉換成數字類型的數據
  • parseInt(string, radix):該方法是將字符串轉換成整型數字類型的
    • 第二個參數 radix 是可選擇的參數
    • 當 string 裏既包括數字又包括其餘字符時會從左到右只會轉換數字部分,遇到其餘非數字的字符就中止,即便後面還有數字也不會繼續轉換
    • 當 radix 不爲空時,該函數可用來做爲進制轉換,radix 做用則是把第一個參數的數字當成幾進制的數字來轉換成十進制(radix 參數的範圍是 2 - 36)
  • parseFloat(string, radix):這個方法和 parseInt 相似,將字符串轉換成浮點類型的數字,一樣是碰到第一個非數字型字符中止,但因爲浮點型數據有小數點,因此它會識別第一個小數點以及後面的數字,但第二個小數點則沒法識別
    • 一旦數字變得足夠大,其字符串表示將以指數形式呈現,以下,此時獲得的是 1
      // String(100000000000000000000000) -> "1e+23"
      parseInt(100000000000000000000000);  // 1
      複製代碼
  • toString(radix):它是對象上的方法,任何數據類型均可使用,轉換成字符串類型
    • 一樣 radix 基底是可選參數,當爲空時僅僅表明將數據轉化成字符串
    • 當寫了 radix 基底時則表明要將這個數字轉化成幾進制的數字型字符串
    • undefiend 和 null 沒有 toString 方法
  • String(mix):把任何類型轉換成字符串類型
  • Boolean(mix):把任何類型轉換成布爾類型

隱式類型轉換

  • isNaN():這個方法能夠檢測數據是否是非數類型,這中間隱含了一個隱式轉換,先將傳的參數調用 Number 方法,再看結果是否是 NaN,該方法能夠檢測 NaN 自己
isNaN(NaN) // true
isNaN('abc') // true
isNaN(123) // false
複製代碼
  • 算術運算符
    • ++ n:先將數據調一遍 Number 後,再自加 n
    • n ++:雖然是執行完後才自加 n,但執行前就調用 Number 進行類型轉換
    • 一樣一目運算符也能夠進行類型轉換:+、-、*、/ 在執行前都會先轉換成數字類型再進行運算
  • 邏輯運算符也會隱式調用類型轉換
    • && 和 || 都是先把表達式調用 Boolean 換成布爾值再進行判斷,不過返回的結果仍是自己表達式的結果
    • !取反操做符返回的結果也是調用 Boolean 方法後的結果
    • 轉 Boolean:在條件判斷時除了 undefinednullfalseNaN''0-0,其餘全部值都轉爲 true,包括全部對象

轉換規則

原始值 轉換爲數值 轉換爲字符串 轉換爲布爾值
number / 0 -> "0",5 -> "5" 除了 0、-0、NaN 都爲 true
string " " -> 0,"1" -> 1,"a" -> NaN / 除了空字符串都爲 true
undefined NaN "undefined" false
null 0 "null" false
[] 0 " " true
[10,20] NaN "10,20" true
{} NaN "[object, Object]" true
{a: 1} NaN "[object, Object]" true
function() {} NaN "function(){}" true
true 1 "true" true
false 0 "false" false
Symbol NaN "function Symbol() { [native code] }" true
Symbol() 拋錯 "Symbol()" true

引用類型轉換爲原始類型

  • 引用類型在轉換類型時會調用內置的 [[ToPrimitive]] 函數
  • ToPrimitive(obj, preferredType):JS 引擎內部轉換爲原始值
  • ToPrimitive(obj, preferredType) 函數接受兩個參數:obj 爲被轉換的對象,preferredType 爲但願轉換成的類型(默認爲空,接受的值爲 Number 或 String)
  • 在執行 ToPrimitive(obj, preferredType) 時若第二個參數爲空且 obj 爲 Date 的實例時,此時 preferredType 會被設置爲 String,其餘狀況下 preferredType 都會被設置爲 Number,若 preferredType 爲 Number,ToPrimitive 執行過程以下:
    • 若 obj 爲原始值,直接返回
    • 不然調用 obj.valueOf(),若執行結果是原始值則返回
    • 不然調用 obj.toString(),若執行結果是原始值則返回
    • 不然拋異常
  • 若 preferredType 爲 String,將上面的第 2 步和第 3 步調換,即:
    • 若 obj 爲原始值,直接返回
    • 不然調用 obj.toString(),若執行結果是原始值則返回
    • 不然調用 obj.valueOf(),若執行結果是原始值則返回
    • 不然拋異常

== 運算規則

一些常規和很是規的轉換狀況

// 常規
"0" == null  // false
"0" == undefined // false
"0" == NaN // false
"0" == 0 // true
"0" == "" // false
false == null // false
false == undefined // false
false == NaN // false
false == {} // false
"" == null // false
"" == undefined // false
"" == NaN // false
"" == {} // false
0 == null // false
0 == undefined // false
0 == NaN // false
0 == {} // false

// 很是規
"0" == false // true
false == 0 // true
false == "" // true
false == [] // true
"" == 0 // true
"" == [] // true
0 == [] // true
複製代碼

對 == 兩邊的值認真推敲,如下兩個原則能夠有效地避免出錯,這時最好用 === 來避免不經意的強制類型轉換
(1)若兩邊的值中有 truefalse,千萬不要使用 ==
(2)若兩邊的值中有 []"" 或者 0,儘可能不要使用 ==

總結

  • undefined == null,結果是 true 且它倆與全部其餘值比較的結果都是 false
  • String == Boolean,須要兩個操做數同時轉爲 Number
  • String/Boolean == Number,須要 String/Boolean 轉爲 Number
  • Object == Primitive,須要 Object 轉爲 Primitive(具體經過 valueOf 和 toString 方法)

JS中 ==、=== 和 Object.is() 的區別

==:等於,===:嚴格等於,Object.is():增強版嚴格等於

const a = 3; 
const b = "3"; 
a == b;    // true
a === b;   // false,由於*a*,*b*的類型不同 
Object.is( a, b );  //false,由於*a*,*b*的類型不同 
複製代碼

=== 這個比較簡單,只須要利用下面的規則來判斷兩個值是否恆等

若類型不一樣,就不相等
若兩個都是數值且是同一個值,那麼相等 有一個是 NaN 就不相等 若兩個都是字符串且每一個位置的字符都同樣,那麼相等;不然不相等 若兩個值都是一樣的 Boolean 值,那麼相等 若兩個值都引用同一個對象或函數,那麼相等,即兩個對象的物理地址也必須保持一致;不然不相等。 若兩個值都是 null 或者都是 undefined,那麼相等

Object.is() 其行爲與 === 基本一致,不過有兩處不一樣:

+0 不等於 -0
NaN 等於自身

+0 === -0 //true
NaN === NaN // false
Object.is(0, -0) // false
Object.is(+0, -0) // false
Object.is(0, +0) // true
Object.is(-0, -0) // true
Object.is(NaN, 0/0) // true
Object.is(NaN, NaN) // true

Object.is('foo', 'foo');     // true
Object.is(window, window);   // true
 
Object.is('foo', 'bar');     // false
Object.is([], []);           // false
 
const foo = { a: 1 };
const bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false
 
Object.is(null, null);       // true
複製代碼

Object.is() 在嚴格等於的基礎上修復了一些特殊狀況下的失誤,具體來講就是 +0-0NaNNaN

function objectIs(x, y) {
    if(x === y) {
        // 運行到 1/x === 1/y 時 x 和 y 都爲 0,但 1/+0 = +Infinity,1/-0 = -Infinity 是不同的
        return x !== 0 || y !== 0 || 1 / x === 1 / y;
    } else {
        // NaN === NaN 是false,在這裏作個攔截,x !== x 必定是 NaN, y 同理
        // 兩個都是 NaN 時返回 true
         return x !== x && y !== y;
    }
}
複製代碼

擴展

爲何會有 BigInt 的提案?

  • JavaScript 中 Number.MAX_SAFE_INTEGER 表示最大安全數字,計算結果是 9007199254740991,即在這個數範圍內不會出現精度丟失(小數除外)
  • 可是一旦超過這個範圍,JS 就會出現計算不許確的狀況,這在大數計算時不得不依靠一些第三方庫進行解決,所以官方提出了 BigInt 來解決此問題
  • bigInt 類型能夠用任意精度表示整數,使用 bigInt 能夠安全地存儲和操做大整數,甚至能夠超過數字的安全整數限制,bigInt 是經過在整數末尾附加 n 或調用構造函數來建立的

{} + [] 的結果是什麼?

詳見:juejin.cn/post/696307…

['1','2','3'].map(parseInt)的返回值是什麼?

詳見:juejin.cn/post/696291…

相關文章
相關標籤/搜索