JavaScript基礎系列--數據類型及類型判斷

ECMAScript5中有五種基本數據類型:Undefined,Null,Boolean,Number,String,以及一種複雜(引用類型)數據類型:ObjectObject中還細分了不少具體的類型,好比:Array, Function, Date等等;ECMAScript6中又新增了一種Symbol類型。es6

typeof操做符

typeof是操做符,而不是函數,它可能返回:編程

  • "undefined":若是這個值未定義
  • "boolean":若是這個值是布爾值
  • "string":若是這個值是字符串值
  • "number":若是這個值是數值
  • "object":若是這個值是對象或者null
  • "function":若是這個值是函數
  • "symbol":若是這個值是Symbol

函數在ECMAScript中是對象,不是一種數據類型;可是函數卻也有一些特殊的屬性,因此經過typeof來區分對象和函數是有必要的安全

Undefined類型

Undefined類型只有一個值 —— undefined,當一個變量僅聲明而未初始化時,這個變量的值就是undefined(當一個變量未聲明(未定義)時,若是訪問它將會報錯)函數

var a;
let b;
console.log(a,b);//undefined undefined
console.log(c);//ReferenceError: c is not defined

ES6以前,typeof是一個百分之百安全的操做,由於即便是對於未聲明的變量,typeof會返回"undefined",而不會報錯。測試

可是ES6引入了let,const來聲明變量,這兩個命令沒有聲明提高,必定要在聲明語句以後才能使用,不然就會報錯;並且let,const還存在暫時性死區this

只要塊級做用域內存在let或const命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。
ES6 明確規定,若是區塊中存在let或const命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域,凡是在聲明以前就使用這些變量,就會報錯
let tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError: tmp is not defined
  let tmp;
}

上面的代碼中存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。編碼

因此「暫時性死區」也意味着typeof再也不是一個百分之百安全的操做,這樣的設計是爲了讓你們養成良好的編程習慣,變量必定要在聲明以後使用,不然就報錯spa

Null類型

Null類型只有一個值 —— null,從邏輯角度來看,null表示一個空對象指針,這也是用typeof操做符檢測null會返回"object"的緣由。prototype

undefined值是派生自null值的,因此undefined == null會返回true,可是它們二者的用途徹底不一樣。設計

只要意在保存對象的變量尚未真正保存對象,就應該讓該變量保存null值,這樣作不只能夠體現null做爲空指針對象的慣例,還有助於區分nullundefined

Boolean類型

Boolean類型有兩個字面值,表示真的true和表示假的falseECMAScript中全部類型的值都有與這兩個Boolean值等價的值,能夠經過Boolean()函數轉換

數據類型 轉換爲true的值 轉換爲false的值
Boolean true false
String 任何非空字符串 ""空字符串
Number 任何非零數字值(包括無窮大) 0和NaN
Object 任何對象 null
Undefined 不適用 undefined
Symbol 任何 不適用

Number類型

Number類型的數值字面量格式有八進制(嚴格模式下使用八進制將會報錯)、十進制和十六進制,其中八進制字面值的第一位必須爲0,十六進制前兩位必須爲0x。在進行算術計算時,八進制和十六進制都會轉換爲十進制。

Number類型使用IEEE754標準定義的64位浮點數表示,存儲爲8字節,使用時不區分整數與浮點數,1與1.0是同樣的,存儲狀況以下:

clipboard.png

  • 符號位S:第 1 位是正負數符號位(sign),0表明正數,1表明負
  • 指數位E:中間的 11 位存儲指數(exponent),用來表示次方數
  • 尾數位M:最後的 52 位是尾數(mantissa),超出的部分自動進一舍零

其中尾數位即有效數字部分,IEEE754規定,有效數字第一位默認老是1,即有效數字老是1.xx...xx的形式,其中第一位不進行存儲,而xx..xx的部分保存在64位浮點數之中,最長可能爲52位。所以,JavaScript實際提供的有效數字最長爲53個二進制位,一個數字能夠表示爲:

(-1)^符號位 * 1.xx...xx * 2^指數位

有效數字精度最多隻能到53個二進制位,這意味着,絕對值小於2的53次方的整數,即在-(Math.pow(2, 53) - 1Math.pow(2, 53) - 1範圍內的整數均可以精確表示(安全整數),而不在該範圍內的整數則會喪失精度。

es6中增長了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER來表示安全整數範圍的上下限,還增長了方法Number.isSafeInteger來判斷是否處於安全整數範圍內。

浮點數的最高精度是17位小數,可是計算時的精度遠遠不如整數,浮點數值計算會產生舍入偏差的問題,這是使用基於IEEE754數值的浮點計算的通病,因此永遠不要測試某個特定的浮點數數值

Number類型的數值範圍是Number.MIN_VALUE ~ Number.MAX_VALUE,超出這個範圍的值爲Infinity,可使用isFinite()函數來判斷數值是否在範圍內。

Number類型有一個特殊的值——NaN(Not a Number)

  • 他表示一個原本要返回數值的操做數卻未返回數值的狀況,NaN是數字類型 可是不是數字,他有兩個特色

    • 任何涉及NaN的操做都會返回NaN
    • NaN與任何值都不相等,包括它本身
  • 0處於0返回NaN,其餘的除以0返回infinity-infinity
  • NaN的布爾值是false
  • isNaN()函數能夠判斷傳入的參數是否「不是數值」,判斷前會自動調用Number()進行轉換,轉換後再進行判斷,任何不能轉換爲數值的值都會致使這個函數返回true;這個函數也適用於對象,在基於對象調用它時,會先調用對象的valueOf()方法,而後肯定該方法的返回值是否能夠轉換爲數值。若是不能,則基於這個返回值再調用toString()方法,再測試返回值

有如下幾種方法能夠把非數值轉換爲數值

  • Number()函數

    • 能夠用於任何類型,包括對象
    • 若是是undefined值,返回NaN
    • 對於字符串,數字的前導0會忽略,若是字符串包含了除數值,0x之外的字符將會返回NaN
    • 若是是對象,先調用對象的valueOf()方法,而後肯定該方法的返回值是否能夠轉換爲數值。若是不能,即返回值是NaN,則基於這個返回值再調用toString()方法,再去轉換返回的字符串值
  • parseInt()函數

    • 字符串轉整數
    • 忽略字符串前面的空格,直到找到第一個非空格字符,若是第一個字符不是數字字符或者正負號(小數點不是有效的數字字符),返回NaN。因此轉換空字符串返回NaN
    • 能夠識別並指定進制數,而後按照相應進制數轉換爲相同大小的十進制。(由於ECMAScript 3ECMAScript 5在解析八進制字面量字符時有分歧,ECMAScript 5 中的parseInt()已經不具有解析八進制的能力,前導0會被忽略,因此最好指定進制數,也就是第二個參數)
  • parseFloat()函數

    • 字符串轉浮點數
    • 忽略字符串前面的空格,直到找到第一個非空格字符,若是第一個字符不是浮點數字字符(字符中第一個小數點是有效的,後面的小數點都是無效的)或者正負號,返回NaN
    • 始終忽略前導0,它只解析十進制值,因此它只有一個參數。若是是十六進制會返回0,由於十六進制的0xparseFloat()函數只會解析到0
    • 字符串裏若是是能夠解析爲整數的數,那會返回整數
  • ~符號(按位非)

    • 按位非的本質是 操做數的負值減1
    • 對於NaNInfinity,應用位操做符會被當作0來處理

      console.log(~NaN);// -1
      console.log(~Infinity);// -1
    • 非數值應用位操做符時會先使用Number()函數將該值轉換爲數值,在應用位操做符

      console.log(~"12");// -13
      console.log(~"w12");// -1
    • 若是是對浮點數應用位操做符,將會對其取整(直接把小數點捨去的這種取整),再應用位操做符

      console.log(~1.2);// -2
      console.log(~-1.2);// 0
  • ~~符號(兩次按位非)

    • 執行兩次按位非,能夠實現取整效果,直接把小數點捨去的這種取整
  • ++-- ,能夠轉換數據類型,是按Number()函數來轉換的

    • 先將++/--後面的內容按Number函數轉換,再加減
    var a = "0xf";
    a++;
    console.lgo(a);// 16
  • +- 能夠轉換數據類型,將其餘的轉爲數字類型

    • Number()函數來轉換的
    • +號前面沒有其餘東西時,+字符串會按照Number()函數來轉換字符串,其餘的時候是字符串鏈接
    • -轉換後,會在轉換後的數值前加上負號,因此都是用+好來轉換

String類型

String類型表示由零個或多個16Unicode字符組成的字符序列——字符串,JavaScript建立的時候,Unicode是一個16位字符集,因此JavaScript中的字符都是16位的,採用UCS-2編碼方式(關於Unicode的知識能夠參見)

字符串是不可變的,一旦建立,值就不能改變;要改變就要先銷燬原來的字符串(銷燬過程在後臺完成),再用另外一個包含新值的字符串填充該變量

String類型包含一些特殊的字符字面量,好比\n,\t,\b,\xnn,\unnnn等,他們稱爲轉義序列,能夠出如今字符串的任意位置,將被當作一個字符來解析;可是若是包含雙字節字符,字符串將解析錯誤,length也將返回錯誤的結果

var a = "Look this: \b";
var b = "Look this: \u20BB7";
console.log(a,a.length);//"Look this:  " 12
console.log(b,b.length);//"Look this:  7" 13

爲了解決這個問題,ES6中對字符的Unicode表示法作了改進,只要將Unicode碼點放入大括號,就能正確解讀該字符,可是length屬性仍是返回錯誤,若想要正確的能夠經過Array.form(xx).length

var b = "Look this: \u{20BB7}";
console.log(b,b.length);//"Look this: 𠮷" 13
console.log(Array.form(b).length);// 12

有如下幾種方法能夠把非字符串值轉換爲字符串值

  • toString()方法

    • 數值、布爾值、對象、字符串值都有這個方法
    • nullundefined沒有這個方法
    • 數值的該方法能夠傳入基數參數,而後就能夠輸出相應進制形式表示的字符串值
  • String()方法

    • 將任何類型的值轉換爲字符串(包括nullundefined)
    • 若是要轉換的值有toString方法,就調用這個
    • 若是要轉換的值是null返回"null",是undefined返回"undefined"
  • +符號

    • 將要轉換的值 + "" 便可轉換爲字符串值

Object類型

ECMAScript中的對象是一組數據和功能的集合,Object類型是全部它的實例的基礎,即Object類型所具備的任何屬性和方法也存在於更具體的對象中

每一個Object實例都具備如下屬性和方法

  • constructor:保存着用於建立當前對象的函數(構造函數)

    var s = "cc";
    var b = true;
    console.log(s.constructor);//ƒ String()
    console.log(b.constructor);//ƒ Boolean()
  • hasOwnProperty(propertyName):檢查一個對象自身(不包括原型鏈)是否具備指定名稱的屬性。若是有,返回true,不然返回false,參數必須爲字符串
  • prototypeObject.isPrototypeOf( object ):檢查prototypeObject是否存在於object的原型鏈中
  • propertyIsEnumerable:檢查給定的屬性是否可以用for-in語句來枚舉,參數必須爲字符串

    • 一般,預約義的屬性不是可枚舉的,而用戶定義的屬性老是可枚舉的
    • 用戶自定義的對象,經過原型"繼承"得到的屬性,使用該方法也返回false,能夠理解爲它只是繼承了這個屬性可是沒有真的屬於他本身,因此就不能枚舉
  • toLocalString:返回對象的字符串表示,與執行環境的地區對應
  • toString:返回對象的字符串表示
  • valueOf:返回對象的字符串、數值或布爾值表示

Symbol類型

這個類型表示獨一無二的值,經過Symbol()函數生成,能夠做爲屬性名,凡是屬性名屬於 Symbol 類型,就都是獨一無二的,能夠保證不會與其餘屬性名產生衝突。

Symbol()函數能夠接受一個字符串做爲參數,表示對Symbol實例的描述,主要是爲了在控制檯顯示,或者轉爲字符串時,比較容易區分。若是Symbol() 的參數不是字符串,就會調用toString方法,將其轉爲字符串,而後才生成一個 Symbol 值。注意,Symbol函數的參數只是表示對當前 Symbol 值的描述,所以相同參數的Symbol函數的返回值是不相等的。

let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

Symbol 值不能與其餘類型的值進行運算,會報錯,在模板字符串中也不能使用Symbol 值;

let s = Symbol('My symbol');

"your symbol is " + s
// TypeError: Cannot convert a Symbol value to a string
`your symbol is ${s}`
// TypeError: Cannot convert a Symbol value to a string
s + 1
// TypeError: Cannot convert a Symbol value to a number

可是它能夠顯式轉換爲字符串值、布爾值,可是不能轉換爲數值

let s = Symbol('My symbol');

String(s) // "Symbol(My symbol)"
s.toString() // "Symbol(My symbol)"
Boolean(s) // true
Number(s) // TypeError: Cannot convert a Symbol value to a number

Symbol 值能夠用於對象的屬性名,能夠能保證不會出現同名的屬性,此時不能用點運算符,只能用方括號:

const mySymbol = Symbol();
const a = {};

a.mySymbol = "Hello";
a[mySymbol] = "World!"
console.log(a['mySymbol']); // "Hello"
console.log(a[mySymbol]); // "World!"

由於點運算符後面老是字符串,因此不會讀取mySymbol做爲標識名所指代的那個值,致使a的屬性名其實是一個字符串,而不是一個 Symbol值。

Symbol 做爲屬性名,該屬性不會出如今for...infor...of循環中,也不會被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。可是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,能夠獲取指定對象的全部 Symbol 屬性名。Reflect.ownKeys方法能夠返回全部類型的鍵名,包括常規鍵名和 Symbol 鍵名。

Symbol.for方法能夠作到從新使用同一個Symbol。它接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的 Symbol 值。若是有,就返回這個 Symbol 值,不然就新建並返回一個以該字符串爲名稱的 Symbol

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

Symbol.for()Symbol()這兩種寫法,都會生成新的 Symbol。它們的區別是:前者會被登記在全局環境中供搜索,後者不會

  • Symbol.for()不會每次調用就返回一個新的 Symbol 類型的值,而是會先檢查給定的key是否已經存在,若是不存在纔會新建一個值
  • Symbol()寫法沒有被登記,每次調用都會返回一個新的 Symbol 類型的值,每次的都是不一樣的值
  • Symbol.forSymbol 值登記的名字,是全局環境的,能夠在不一樣的 iframeservice worker 中取到同一個值
Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false

Symbol.keyFor方法返回一個已登記Symbol 類型值的key,也就是返回使用Symbol.for生成的Symbol 類型值的key,若是沒有傳參數則返回"undefined";對於Symbol()生成的Symbol 類型值返回undefined

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

let s2 = Symbol.for();
Symbol.keyFor(s3) // "undefined"

let s3 = Symbol("foo");
Symbol.keyFor(s3) // undefined

數據類型判斷

typeof

typeof 變量 //"undefined"/"boolean"/"number"/"string"/"object"/"function"/"symbol"

typeof能夠正確檢測出Number, String, Boolean, Undefined, Symbol類型,但Array, Null, Date, Reg, Error 所有被檢測爲Object類型,也就是說不能檢測出Object類型下的細分類型

instanceof

變量 instanceof 類型 //true or false

instanceof方法要求開發者明確地確認對象爲某特定類型,它能夠檢測Object類型下的細分類型,便可以正確檢測出Array,Function,Date,Reg,Error類型,以及使用new操做符建立的Number, String, Boolean類型(對於普通的字面量形式的Number, String, Boolean沒法檢測)。

instanceof沒法檢測Null, Undefined, Symbol類型,老是會返回false,因此使用instanceof進行變量檢測時,須要首先判斷是不是Null, Undefined, Symbol類型

instanceof不能跨iframe(每一個頁面的類型原生對象所引用的地址是不同的)

constructor

變量.constructor == 類型 //true or false

constructor原本是原型對象上的屬性,指向其構造函數。可是根據實例對象尋找屬性的順序,若實例對象上沒有實例屬性或方法時,就去原型鏈上尋找,所以,實例對象也是能使用constructor屬性的

constructor能夠正確檢測Null, Undefined以外的全部類型,包括Symbol類型,Null, Undefined沒有constructor屬性

使用constructor不是保險的,由於constructor屬性是能夠被修改的,會致使檢測出的結果不正確

constructor不能跨iframe(每一個頁面的類型原生對象所引用的地址是不同的)

Object.prototype.toString.call

Object.prototype.toString.call(變量) == "[object 類型]" //true or false

Object.prototype.toString.call的行爲:

  • 首先,取得對象的一個內部屬性[[Class]]
  • 而後依據[[Class]]這個屬性,返回一個相似於"[object Array]"的字符串做爲結果
  • 經過call能夠取得任何對象的內部屬性[[Class]],而後把類型檢測轉化爲字符串比較,以達到咱們的目的

這個方法能夠檢測出全部的類型,包括Null,UndefinedObject下的細分類型以及Symbol類型

jQuery$.type的實現

jQuery就是用Object.prototype.toString.call結合typeof實現的

  • 先使用typeof進行檢測
  • typeof返回"object"或者"function"時,再使用Object.prototype.toString.call來檢測

因此除了ObjectFunction,其餘的都是使用typeof進行檢測

相關文章
相關標籤/搜索