若是面試問你JavaScript的數據類型有哪些?你能夠信誓旦旦的說出Null, Undefined, Boolean, String, Number,Symbol以及Object七種數據類型,問到它們的區別是什麼,你也能說出一二,可是你知道JavaScript的包裝類型嗎?拆箱和裝箱又是?Symbol數據類型有哪些特性?你在何時用到了Symbol數據類型?隱式類型轉換規則有哪些?判斷JavaScript數據類型的方法有哪些?優缺點是?enmmmm...vue
這篇文章會對上述問題做出解答,並會擴展一些那些咱們須要知道但卻沒有關注到的知識,讓咱們開始學習之旅吧~jquery
(在es10中加入了第七種原始類型BigInt,現已被最新Chrome支持)git
1. 原始類型的值是不可變的,引用類型的值是可變的github
// 原始類型
var name = 'muzishuiji';
name.subStr(1, 3); // uzi
name.slice(2); // zishuiji
name.toUpperCase(); // MUZISHUIJI
console.log(name); // muzishuiji
// 引用類型
var obj = {
name: 'sss'
}
obj.name = 'muzishuiji'
obj.age = 22;
console.log(obj); // {name: "muzishuiji", age: 22}
複製代碼
2. 原始類型的變量是存放在棧區的,引用類型的變量是在堆內存中申請地址存放變量值,而後在棧內存中存放該變量在內存中的地址.*web
原始類型面試
var name = 'muzishuiji';
var age = 22;
var job = 'teacher';
複製代碼
存儲結構以下圖:微信
引用類型koa
var obj1 = {name:'muzishuiji'};
var obj2 = {name:'wangming'};
var person3 = {name:'xuliu'};
複製代碼
存儲結構以下圖:異步
3. 原始類型的比較是值的比較,引用類型類型的比較是變量值所在地址的比較:函數
原始類型的變量在棧中存放的就是對應的變量值, 而引用類型在棧中存放的是變量值所在的地址.
// 原始類型的比較
var a = 'muzishuiji';
var b = 'muzishuiji';
console.log(a === b); // true
// 引用類型的比較
var obj1 = { name: 'muzishuiji' };
var obj2 = { name: 'muzishuiji' };
console.log(obj1 === obj2); // false
複製代碼
Symbol類型是ES6新引入的一種數據類型,它接收一個可選的字符串做爲描述.當參數爲對象時,將調用對象的toString()方法, 使用示例以下:
let s1 = Symbol(); // Symbol()
let s2 = Symbol('muzishuiji'); // Symbol(muzishuiji)
let s3 = Symbol(['sss','aaa']); // Symbol(sss, aaa)
let s4 = Symbol({name:'muzishuiji'}); // Symbol([object Object])
複製代碼
使用Symbol()建立的變量使獨一無二的,所以,比較兩個Symbol()建立的變量老是返回false.
let s5 = Symbol();
let s6 = Symbol();
console.log(s5 === s6); // false
let s7 = Symbol('muzishuiji');
let s8 = Symbol('muzishuiji');
console.log(s7 === s8); // false
複製代碼
js提供了Symbol.for(key)來建立兩個相等的變量,使用給定的key搜索現有的Symbol,若是找到則返回該Symbol,不然將使用給定的key在全局Symbol註冊表中建立一個新的Symbol.
let s1 = Symbol.for('muzishuiji');
let s2 = Symbol.for('muzishuiji');
console.log(s1 === s2); // true
複製代碼
使用new 操做符來建立Symbol變量會報錯,由於Symbol()返回的不是一個變量,而是一個Symbol類型的值,因此禁止把它當作構造函數使用.
new Symbol(); // Uncaught TypeError: Symbol is not a constructor
複製代碼
這個時候你不會不會有些奇怪,平時咱們使用new 操做符來調用一個普通的函數(不是嚴格意義上的構造函數),並不會給咱們拋出這樣的錯誤:
function Person() {
console.log('muzishuiji');
}
var person1 = new Person(); // muzishuiji, 並無報錯
複製代碼
那麼Symbol函數是怎麼知道我是用來new 操做符,並給我拋出錯誤呢?我作了如下實驗:
function Person() {
if(this instanceof Person) {
throw Error("Person is not a constructor"); // Uncaught TypeError: Symbol is not a constructor
}
}
var person1 = new Person();
複製代碼
是的,報錯了,可見Symbol函數是經過判斷當前對象是否是Symbol的實例來判斷咱們有沒有使用new 操做符(js中的new操做符的原理).
使用Symbol建立的屬性名是不可枚舉的,使用for...in, Object.keys(), Object.getOwnPropertyNames()等方法是沒法獲取的.能夠調用Object.getOwnPropertySymbols()和Reflect.ownKeys()來獲取對象的Symbol屬性.
var obj = {
name:'muzishuiji',
[Symbol('age')]: 18
}
Object.getOwnPropertyNames(obj); // ["name"]
Object.keys(obj); // ["name"]
for (var i in obj) {
console.log(i); // name
}
Object.getOwnPropertySymbols(obj) // [Symbol(age)]
Reflect.ownKeys(obj) // ["name", Symbol(name)]
複製代碼
有時候咱們想給一個對象添加屬性名,但又擔憂和別的同事重名,咱們能夠這樣:
const symKey1 = Symbol('name');
var obj = {
name: '1223'
}
obj[symKey1] = '1478'
複製代碼
Symbol建立的變量獨一無二的特性有效避免了屬性污染.
使用Symbol定義類的私有屬性或方法
(function(){
var AGE_SY = Symbol()
var GET_NAME = Symbol()
class Animal {
constructor(name, age) {
this.name = name
this[AGE_SY] = age
}
[GET_NAME]() {
console.log(this.name)
}
}
})()
var animal1 = new Animal('muzishuiji', 18);
// 咱們不能直接獲取到內部定義的變量AGE_SY和GET_NAME ,因此也就不能直接訪問Animal類的AGE_SY屬性和GET_NAME方法
// 但這裏的私有屬性不是嚴格意義上的的私有屬性,由於咱們仍然能夠經過這樣的操做來訪問
animal1[Object.getOwnPropertySymbols(animal1)[0]]; // 18
複製代碼
建立常量
// 一般咱們會這樣,咱們須要根據不一樣的傳入值作不一樣的處理
const TYPE_ONE = 'red'
const TYPE_TWO = 'green'
const TYPE_THERE = 'blue'
function handleSome(resource) {
switch(resource.type) {
case TYPE_AUDIO:
// do something
break;
case TYPE_VIDEO:
// do something
break;
break
// do something
break;
}
}
handleSome('red')
// 使用Symbol咱們能夠這樣,沒必要費勁心思思考枚舉值用什麼
const TYPE_ONE = Symbol()
const TYPE_TWO = Symbol()
const TYPE_THERE = Symbol()
function handleSome(resource) {
switch(resource.type) {
case TYPE_AUDIO:
// do something
break;
case TYPE_VIDEO:
// do something
break;
break
// do something
break;
}
}
handleSome(TYPE_ONE) // 這樣就能夠處理對應TYPE_ONE的代碼邏輯啦
複製代碼
ECMAScript還提供了3個特殊的引用類型: Boolean, Number和String.這些類型具備與各自的基本類型類似的特殊行爲.實際上,每當讀取一個基本類型值的時候,後臺就會建立一個對應的基本包裝類型的對象,從而讓咱們可以調用一些方法來操做這些數據.
var s1 = 'some text';
var s2 = s1.substring(2); // "me text"
複製代碼
事實上,s1是基本類型不是對象,從邏輯上講它是沒有方法的,它是因此調用substring方法,是由於後臺幫咱們作了裝箱的操做,當第二行代碼訪問s1時,訪問過程處於一種讀取模式,也就是要從內存中讀取這個字符串的值,而在讀取模式訪問字符串時,後臺會自動完成下列操做:
(1) 建立String類型的一個實例;
(2) 在實例上調用指定的方法;
(3) 銷燬這個實例;
翻譯成代碼是這樣的:
// 紅寶書上傳入的是字符串"some text",我以爲其實就是傳入的s1的值建立了一個臨時的包裝類型的變量.
var tempS1 = new String(s1);
var s2 = tempS1.sunString();
tempS1 = null;
複製代碼
上面三個步驟也分別適用於Boolean和Number類型對應的布爾值和數值.
引用類型與基本包裝類型的主要區別就是對象的生存期,使用new操做符建立的引用類型的實例,在執行流離開當前做用域以前都一直保存在內存中.而自動建立的基本類型的對象,則只存在於一行代碼的執行瞬間,而後當即被銷燬,這意味着咱們不能在運行時爲基本類型值添加屬性和方法.來看下面的例子:
var s1 = 'some text';
s1.color = 'red'
alert(s1.color); // undefined
複製代碼
在嘗試訪問s1的color屬性的時候,第二行建立的String對象已經被銷燬了,第三行代碼又建立新的String對象,而該對象沒有color屬性.
能夠顯式地調用Boolean,Number和String來建立基本包裝類型的對象,不過,應該在必要的狀況下這樣作,由於這種作法很容易讓人分不清本身是在處理基本類型仍是引用類型的值.
裝箱的操做也就是上面2.4.1介紹的基本操做類型在調用相關方法時後臺爲咱們執行的操做.
從引用類型到基本類型的轉換,也就是拆箱的過程當中,會遵循ECMAScript規範規定的toPrimitive原則,通常會調用引用類型的valueOf和toString方法,你也能夠直接重寫toPeimitive方法。通常會根據想要轉換的目標數據類型, string or number,來執行相應的轉換操做.
// 自定義valueOf和toString, 返回對應值
const obj = {
valueOf: () => { return 123; },
toString: () => { return 'muzishuiji'; }
}
console.log(obj - 1); // 目標類型number, 結果: 122
console.log(obj + '11'); // 目標類型string, 結果: "12311"
// 自定義toPrimitive
const obj1 = {
[Symbol.toPrimitive]: () => { return 123; }
}
console.log(obj1 - 1); // 目標類型number, 結果: 122
console.log(obj1 + '11'); // 目標類型string, 結果: "12311"
// 自定義valueOf和toString, 返回不能被正常轉換的值
const obj2 = {
valueOf: () => { return {}; },
toString: () => { return {}; }
}
console.log(obj2 - 1); // Uncaught TypeError: Cannot convert object to primitive value
console.log(obj2 + '11'); // Uncaught TypeError: Cannot convert object to primitive value
複製代碼
和手動建立包裝類型同樣,咱們也能夠經過手動調用包裝類型或者引用類型的valueOf或toString,實現拆箱操做:
var num =new Number("123");
console.log(num.valueOf(), typeof num.valueOf()); // 123 "number"
console.log(num.toString(), typeof num.toString()); // "123" "string"
const obj = {
valueOf: () => { return 123; },
toString: () => { return 'muzishuiji'; }
}
obj.toString(); // 'muzishuiji'
obj.valueOf(); // 123
複製代碼
咱們都知道JavaScript是一種弱類型的語言,js聲明變量並無預先肯定的類型,變量的類型就是其值的類型,也就是說咱們能夠經過賦值來隨意的修改變量的類型,從新賦值的過程其實就是在後臺爲咱們執行了強制類型轉換的操做.這一特性使js的編碼變得更靈活,但同時也帶來了代碼的不穩定性和不可預測性,因此熟知JavaScript的類型轉換規則,可讓必定程度上避免寫出意外的bug.
JavaScript的類型轉換分爲強制類型轉換和隱式類型轉換.
ToPrimitive(obj,type);
複製代碼
ToPrimitive方法接收兩個參數,須要轉換的對象和指望轉換成的數據類型,第二個參數可選.
先調用obj的toString方法,若是爲原始值,則返回,不然進行第2步;
調用obj的valueOf方法,若是爲原始值,則返回,不然進行第3步;
不然,拋出錯誤.
先調用obj的valueOf方法,若是爲原始值,則返回,不然進行第2步;
調用obj的toString方法,若是爲原始值,則返回,不然進行第3步;
不然,拋出錯誤.
Date數據類型特殊說明:對於Date數據類型,咱們更多指望得到的是其轉爲時間後的字符串,而非毫秒值(時間戳),若是爲number,則會取到對應的毫秒值,顯然字符串使用更多。 其餘類型對象按照取值的類型操做便可。
注意, 隱式類型某個引用類型轉換爲原始值就是在後臺調用ToPrimitive方法, 轉換邏輯就和type參數爲空時的轉換邏輯一致.
Object.prototype.toString()
方法返回該對象的字符串表示
每一個對象都有一個 toString()
方法,當對象被表示爲文本值時或者當以指望字符串的方式引用對象時,該方法被自動調用。
Object.prototype.valueOf()
方法返回指定對象的原始值。
JavaScript 調用 valueOf() 方法用來把對象轉換成原始類型的值(數值、字符串和布爾值)。可是和toString
方法同樣,咱們不多須要本身調用這些函數,這些方法通常都會在發生數據類型轉換的時候被 JavaScript 自動調用。
不一樣內置對象的 valueOf 實現:
代碼展現以下:
var str = "123";
str.valueOf(); // "123"
var num = 456;
num.valueOf(); // 456
var date = new Date();
date.valueOf(); // 1567998675017
var arr = [1,2,3,4];
arr.valueOf(); // [1,2,3,4]
var obj = new Object({valueOf:()=>{
return 'muzishuiji'
}})
console.log(obj.valueOf()); // "muzishuiji"
複製代碼
Number運算符轉換規則:
若是要調用Number方法轉換對象,則會調用ToPrimitive轉換,type指定爲number
代碼示例:
Number(null); // 0
Number(undefined); // NaN
Number('123'); // 123
Number('456abc'); // 456
Number([1,2,3]); // NaN
Number({}); // NaN
Number(new Date()); // 1568000206474
複製代碼
String 運算符轉換規則
若是要調用String方法轉換對象,則會調用ToPrimitive轉換,type指定爲string
代碼示例:
String(null); // 'null'
String(undefined); // 'undefined'
String(true) // 'true'
String(1) // '1'
String(0) // '0'
String(Infinity) // 'Infinity'
String(-Infinity) // '-Infinity'
String({}) // '[object Object]'
String([1,[2,3]]) // '1,2,3'
String(['koala',1]) //koala,1
複製代碼
ToBoolean 運算符轉換規則
除了下述 6 個值轉換結果爲 false,其餘所有爲true:
須要說明的一點是 new Boolaen(false)的轉換結果也是true, 由於經過Boolaen方法建立的是一個值爲false的變量.
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
typeof new Boolean(false) // "object"
複製代碼
除加法運算符之外的運算符,如*, / , - 運算符都會默認將符號兩側的數據轉換成數值在進行計算.
'12' + 2; // '123'
'12' + true; // '12true'
'12' + false; // '12false'
'12' + ['1', '2']; // '121,2 '
'12' + {}; "12[object Object]"
12 + null; // 12
12 + undefined; // NaN
12 + '3'; // '123'
12 + true; // 13
12 + false; // 12
12+['3','4']; // '123,4'
12 + {}; // '12[object Object]'
複製代碼
總結規律以下:
運用排除法可得,使用加法運算符時的隱式類型轉換就是: 除了Number類型 + (Null, Undefined, Boolean,Number)會作加法運算,其餘狀況下都是作字符串拼接操做.
在if語句和邏輯語句中,若是隻有單個變量,會先將變量轉換爲Boolean值,只有如下狀況會被轉成false,其他會被轉換成true.
null
undefined
''
NaN
0
false
複製代碼
使用 == 時會發生隱式類型轉換,致使意外的bug出現,咱們若是須要作比較運算最好使用 === 嚴格等於運算符.
null == undefined; // true
NaN == NaN; // false
true == 1; // true
true == 'sss'; // false
true == ['44']; // false
true == {}; // false
false == 0; // true
false == 'sss'; // false
false == ['44']; // false
false == {}; // false
'123' == 123; // true
'' == 0; // true
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true
{} == '1' // Uncaught SyntaxError: Unexpected token ==
複製代碼
總結規律以下:
true只有和1比較會返回true, false只有和0比較會返回true
String和Number比較,現將String轉換爲Number
原始類型和引用類型比較
當原始類型和引用類型作比較時,對象類型會依照ToPrimitive
規則轉換爲原始類型, {}放在運算符左側會報錯.
typeof
多用於判斷一個變量屬於哪一個原始類型:
typeof 'muzishuiji' // string
typeof 123 // number
typeof true // boolean
typeof Symbol() // symbol
typeof undefined // undefined
typeof function() {} // function
複製代碼
typeof
不能準確判斷引用類型的數據類型:
typeof [] // object
typeof {} // object
typeof new Date() // object
typeof /^\d*$/; // object
typeof null; // object, 衆所周知的JavaScript的一個bug
複製代碼
instanceof
操做符能夠判斷引用類型具體是什麼類型的變量,其主要原理就是監測構造函數的prototype
是否出如今被檢測對象的原型鏈上.
var a = {}
a instanceof Object // true
[] instanceof Array // true
new Date() instanceof Date // true
new RegExp() instanceof RegExp // true
var b = function() {}
b instanceof Function // true
複製代碼
可是有些狀況下,instanceof
獲得的結果也不許確,
[] instanceof Object // true
var b = function() {}
b instanceof Object // true
複製代碼
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('seymoe') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
複製代碼
咱們可使用這個方法返回傳入值的準確類型,Object.prototype.toString
方法返回的是該函數執行是this指向對象的數據類型,看下面的例子:
var tempFun = Object.prototype.toString;
var aaa = [];
aaa.tempFun = tempFun;
aaa.tempFun(); // "[object Array]"
var reg = new RegExp();
reg.tempFun = tempFun;
reg.tempFun(); // "[object RegExp]"
複製代碼
因此call
函數爲咱們綁定了Object.prototype.toString
函數執行時候的this
,調用Object.prototype.toString.call(obj);
就會返回obj的數據類型了(^_^).
const classType = {};
const typeArray = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol"];
typeArray.forEach(type => {
classType["[object " + type + "]"] = type.toLowerCase();
})
function getType(obj) {
if ( obj == null ) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
classType[Object.prototype.toString.call(obj) ] || "object" :
typeof obj;
}
複製代碼
原始類型直接使用typeof
, 引用類型使用Object.prototype.toString.call
取得類型, classType
來將對應類型的小寫形式存起來,將Object.prototype.toString.call
返回的多餘的內容過濾掉,只留下對應類型的小寫形式返回.
在借鑑了前輩們的分享才得以完成這篇JavaScript變量與數據類型的總結,在此過程當中,我加入了本身的理解和擴展,用比較淺顯的語言來闡述JavaScript的一些概念,以及一些規則對應的原理.若是又發現不對的地方或者解釋不到位的地方,歡迎在下方評論或者加微信lj_de_wei_xin
與我交流~