JavaScript 類型的那些事

作者:有贊技術(shù)團(tuán)隊(duì)
原文地址:http://tech.youzan.com/javascript-type/

概述

JavaScript的類型判斷是前端工程師們每天代碼中必備的部分邀摆,每天肯定會(huì)寫(xiě)上個(gè)很多遍if (a === 'xxx')if (typeof a === 'object')類似的類型判斷語(yǔ)句初肉,所以掌握J(rèn)avaScript中類型判斷也是前端必備技能,以下會(huì)從JavaScript的類型痕寓,類型判斷以及一些內(nèi)部實(shí)現(xiàn)來(lái)讓你深入了解JavaScript類型的那些事。

類型

JavaScript中類型主要包括了primitiveobject類型,其中primitive類型包括了:nullundefinedboolean疑务、number沾凄、stringsymbol(es6)。其他所有的都為object類型知允。

類型判斷

類型檢測(cè)主要包括了:typeof撒蟀、instanceoftoString的三種方式來(lái)判斷變量的類型。

typeof

typeof接受一個(gè)值并返回它的類型温鸽,它有兩種可能的語(yǔ)法:

  • typeof x
  • typeof(x)

當(dāng)在primitive類型上使用typeof檢測(cè)變量類型時(shí)保屯,我們總能得到我們想要的結(jié)果手负,比如:

typeof 1; // "number"  
typeof ""; // "string"  
typeof true; // "boolean"  
typeof bla; // "undefined"  
typeof undefined; // "undefined"

而當(dāng)在object類型上使用typeof檢測(cè)時(shí),有時(shí)可能并不能得到你想要的結(jié)果姑尺,比如:

typeof []; // "object"  
typeof null; // "object"  
typeof /regex/ // "object"  
typeof new String(""); // "object"  
typeof function(){}; // "function"

這里的[]返回的確卻是object竟终,這可能并不是你想要的,因?yàn)閿?shù)組是一個(gè)特殊的對(duì)象切蟋,有時(shí)候這可能并不是你想要的結(jié)果统捶。

對(duì)于這里的null返回的確卻是object,wtf柄粹,有些人說(shuō)null被認(rèn)為是沒(méi)有一個(gè)對(duì)象喘鸟。

當(dāng)你對(duì)于typeof檢測(cè)數(shù)據(jù)類型不確定時(shí),請(qǐng)謹(jǐn)慎使用驻右。

toString

typeof的問(wèn)題主要在于不能告訴你過(guò)多的對(duì)象信息什黑,除了函數(shù)之外:

typeof {key:'val'}; // Object is object  
typeof [1,2]; // Array is object  
typeof new Date; // Date object  

toString不管是對(duì)于object類型,還是primitive類型堪夭,都能得到你想要的結(jié)果:

var toClass = {}.toString;

console.log(toClass.call(123));  
console.log(toClass.call(true));  
console.log(toClass.call(Symbol('foo')));  
console.log(toClass.call('some string'));  
console.log(toClass.call([1, 2]));  
console.log(toClass.call(new Date()));  
console.log(toClass.call({  
    a: 'a'
}));

// output
[object Number]
[object Boolean]
[object Symbol]
[object String]
[object Array]
[object Date]
[object Object]

underscore中你會(huì)看到以下代碼:

// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

這里就是使用toString來(lái)判斷變量類型愕把,比如你可以通過(guò)_.isFunction(someFunc)來(lái)判斷someFunc是否為一個(gè)函數(shù)。

從上面的代碼茵瘾,我們可以看到toString是可依賴的礼华,不管是object類型還是primitive類型,它都能告訴我們正確的結(jié)果拗秘。但它只可以用于判斷內(nèi)置的數(shù)據(jù)類型圣絮,對(duì)于我們自己構(gòu)造的對(duì)象,它還是不能給出我們想要的結(jié)果雕旨,比如下面的代碼:

function Person() {  
}

var a = new Person();  
// [object Object]
console.log({}.toString.call(a));  
console.log(a instanceof Person);  

我們這時(shí)候就要用到我們下面介紹的instanceof了扮匠。

instanceof

對(duì)于使用構(gòu)造函數(shù)創(chuàng)建的對(duì)象,我們通常使用instanceof來(lái)判斷某一實(shí)例是否屬于某種類型凡涩,例如:a instanceof Person棒搜,其內(nèi)部原理實(shí)際上是判斷Person.prototype是否在a實(shí)例的原型鏈中,其原理可以用下面的函數(shù)來(lái)表達(dá):

function instance_of(V, F) {  
  var O = F.prototype;
  V = V.__proto__;
  while (true) {
    if (V === null)
      return false;
    if (O === V)
      return true;
    V = V.__proto__;
  }
}

// use
function Person() {  
}
var a = new Person();

// true
console.log(instance_of(a, Person));  

類型轉(zhuǎn)換

因?yàn)镴avaScript是動(dòng)態(tài)類型活箕,變量是沒(méi)有類型的力麸,可以隨時(shí)賦予任意值。但是育韩,各種運(yùn)算符或條件判斷中是需要特定類型的克蚂。比如,if判斷時(shí)會(huì)將判斷語(yǔ)句轉(zhuǎn)換為布爾型筋讨。下面就來(lái)深入了解下JavaScript中類型轉(zhuǎn)換埃叭。

ToPrimitive

當(dāng)我們需要將變量轉(zhuǎn)換為原始類型時(shí),就需要用到ToPrimitive悉罕,下面的代碼說(shuō)明了ToPrimitive的內(nèi)部實(shí)現(xiàn)原理:

// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {  
  // Fast case check.
  if (IS_STRING(x)) return x;
  // Normal behavior.
  if (!IS_SPEC_OBJECT(x)) return x;
  if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError(kSymbolToPrimitive);
  if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
  return (hint == NUMBER_HINT) ? DefaultNumber(x) : DefaultString(x);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {  
  if (!IS_SYMBOL_WRAPPER(x)) {
    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (IsPrimitive(v)) return v;
    }

    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (IsPrimitive(s)) return s;
    }
  }
  throw MakeTypeError(kCannotConvertToPrimitive);
}

// ECMA-262, section 8.6.2.6, page 28.
function DefaultString(x) {  
  if (!IS_SYMBOL_WRAPPER(x)) {
    var toString = x.toString;
    if (IS_SPEC_FUNCTION(toString)) {
      var s = %_CallFunction(x, toString);
      if (IsPrimitive(s)) return s;
    }

    var valueOf = x.valueOf;
    if (IS_SPEC_FUNCTION(valueOf)) {
      var v = %_CallFunction(x, valueOf);
      if (IsPrimitive(v)) return v;
    }
  }
  throw MakeTypeError(kCannotConvertToPrimitive);
}

上面代碼的邏輯是這樣的:

  1. 如果變量為字符串赤屋,直接返回立镶;
  2. 如果!IS_SPEC_OBJECT(x),直接返回类早;
  3. 如果IS_SYMBOL_WRAPPER(x)媚媒,則拋出異常;
  4. 否則會(huì)根據(jù)傳入的hint來(lái)調(diào)用DefaultNumberDefaultString莺奔,比如欣范,如果為Date對(duì)象,會(huì)調(diào)用DefaultString
  • DefaultNumber:首先x.valueOf令哟,如果為primitive恼琼,則返回valueOf后的值,否則繼續(xù)調(diào)用x.toString屏富,如果為primitive晴竞,則返回toString后的值,否則拋出異常
  • DefaultString:和DefaultNumber正好相反狠半,先調(diào)用toString噩死,如果不是primitive再調(diào)用valueOf

那講了實(shí)現(xiàn)原理,這個(gè)ToPrimitive有什么用呢神年?實(shí)際很多操作會(huì)調(diào)用ToPrimitive已维,比如相等比較操作已日。在進(jìn)行操作時(shí)會(huì)將左右操作數(shù)轉(zhuǎn)換為primitive垛耳,然后進(jìn)行相加。

下面來(lái)個(gè)實(shí)例飘千,({}) + 1(將{}放在括號(hào)中是為了內(nèi)核將其認(rèn)為一個(gè)代碼塊)會(huì)輸出啥堂鲜?可能日常寫(xiě)代碼并不會(huì)這樣寫(xiě),不過(guò)網(wǎng)上出過(guò)類似的面試題护奈。

操作只有左右運(yùn)算符同時(shí)為StringNumber時(shí)會(huì)執(zhí)行對(duì)應(yīng)的%_StringAdd%NumberAdd缔莲,下面看下({}) + 1內(nèi)部會(huì)經(jīng)過(guò)哪些步驟:

  1. {}和1首先會(huì)調(diào)用ToPrimitive
  2. {}會(huì)走到DefaultNumber霉旗,首先會(huì)調(diào)用valueOf痴奏,返回的是Object {},不是primitive類型厌秒,從而繼續(xù)走到toString读拆,返回[object Object],是String類型简僧;
  3. 最后加操作建椰,結(jié)果為[object Object]1雕欺。

再比如岛马,有人問(wèn)你[] + 1輸出啥時(shí)棉姐,你可能知道應(yīng)該怎么去計(jì)算了,先對(duì)[]調(diào)用ToPrimitive啦逆,返回空字符串伞矩,最后結(jié)果為"1"。

除了ToPrimitive之外夏志,還有更細(xì)粒度的ToBoolean乃坤、ToNumberToString,比如在需要布爾型時(shí)沟蔑,會(huì)通過(guò)ToBoolean來(lái)進(jìn)行轉(zhuǎn)換湿诊。看一下源碼瘦材,我們可以很清楚的知道這些布爾型厅须、數(shù)字等之間轉(zhuǎn)換是怎么發(fā)生:

// ECMA-262, section 9.2, page 30
function ToBoolean(x) {  
  if (IS_BOOLEAN(x)) return x;
  // 字符串轉(zhuǎn)布爾型時(shí),如果length不為0就返回true
  if (IS_STRING(x)) return x.length != 0;
  if (x == null) return false;
  // 數(shù)字轉(zhuǎn)布爾型時(shí)食棕,變量不為0或NAN時(shí)返回true
  if (IS_NUMBER(x)) return !((x == 0) || NUMBER_IS_NAN(x));
  return true;
}

// ECMA-262, section 9.3, page 31.
function ToNumber(x) {  
  if (IS_NUMBER(x)) return x;
  // 字符串轉(zhuǎn)數(shù)字調(diào)用StringToNumber
  if (IS_STRING(x)) {
    return %_HasCachedArrayIndex(x) ? %_GetCachedArrayIndex(x)
                                    : %StringToNumber(x);
  }
  // 布爾型轉(zhuǎn)數(shù)字時(shí)true返回1朗和,false返回0
  if (IS_BOOLEAN(x)) return x ? 1 : 0;
  // undefined返回NAN
  if (IS_UNDEFINED(x)) return NAN;
  // Symbol拋出異常,例如:Symbol() + 1
  if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToNumber);
  return (IS_NULL(x)) ? 0 : ToNumber(DefaultNumber(x));
}

// ECMA-262, section 9.8, page 35.
function ToString(x) {  
  if (IS_STRING(x)) return x;
  // 數(shù)字轉(zhuǎn)字符串簿晓,調(diào)用內(nèi)部的_NumberToString
  if (IS_NUMBER(x)) return %_NumberToString(x);
  // 布爾型轉(zhuǎn)字符串眶拉,true返回字符串true
  if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
  // undefined轉(zhuǎn)字符串,返回undefined
  if (IS_UNDEFINED(x)) return 'undefined';
  // Symbol拋出異常
  if (IS_SYMBOL(x)) throw MakeTypeError(kSymbolToString);
  return (IS_NULL(x)) ? 'null' : ToString(DefaultString(x));
}

講了這么多原理憔儿,那這個(gè)ToPrimitive有什么卵用呢忆植?這對(duì)于我們了解JavaScript內(nèi)部的隱式轉(zhuǎn)換和一些細(xì)節(jié)是非常有用的,比如:

var a = '[object Object]';  
if (a == {}) {  
    console.log('something');
}

你覺(jué)得會(huì)不會(huì)輸出something呢皿曲,答案是會(huì)的唱逢,所以這也是為什么很多代碼規(guī)范推薦使用===三等了。那這里為什么會(huì)相等呢屋休,是因?yàn)檫M(jìn)行相等操作時(shí)坞古,對(duì){}調(diào)用了ToPrimitive,返回的結(jié)果就是[object Object],也就返回true了毫玖。我們可以看下JavaScript中EQUALS的源碼就一目了然了:

// ECMA-262 Section 11.9.3.
EQUALS = function EQUALS(y) {  
  if (IS_STRING(this) && IS_STRING(y)) return %StringEquals(this, y);
  var x = this;

  while (true) {
    if (IS_NUMBER(x)) {
      while (true) {
        if (IS_NUMBER(y)) return %NumberEquals(x, y);
        if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
        if (IS_SYMBOL(y)) return 1;  // not equal
        if (!IS_SPEC_OBJECT(y)) {
          // String or boolean.
          return %NumberEquals(x, %$toNumber(y));
        }
        y = %$toPrimitive(y, NO_HINT);
      }
    } else if (IS_STRING(x)) {
      // 上面的代碼就是進(jìn)入了這里残腌,對(duì)y調(diào)用了toPrimitive
      while (true) {
        if (IS_STRING(y)) return %StringEquals(x, y);
        if (IS_SYMBOL(y)) return 1;  // not equal
        if (IS_NUMBER(y)) return %NumberEquals(%$toNumber(x), y);
        if (IS_BOOLEAN(y)) return %NumberEquals(%$toNumber(x), %$toNumber(y));
        if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
        y = %$toPrimitive(y, NO_HINT);
      }
    } else if (IS_SYMBOL(x)) {
      if (IS_SYMBOL(y)) return %_ObjectEquals(x, y) ? 0 : 1;
      return 1; // not equal
    } else if (IS_BOOLEAN(x)) {
      if (IS_BOOLEAN(y)) return %_ObjectEquals(x, y) ? 0 : 1;
      if (IS_NULL_OR_UNDEFINED(y)) return 1;
      if (IS_NUMBER(y)) return %NumberEquals(%$toNumber(x), y);
      if (IS_STRING(y)) return %NumberEquals(%$toNumber(x), %$toNumber(y));
      if (IS_SYMBOL(y)) return 1;  // not equal
      // y is object.
      x = %$toNumber(x);
      y = %$toPrimitive(y, NO_HINT);
    } else if (IS_NULL_OR_UNDEFINED(x)) {
      return IS_NULL_OR_UNDEFINED(y) ? 0 : 1;
    } else {
      // x is an object.
      if (IS_SPEC_OBJECT(y)) {
        return %_ObjectEquals(x, y) ? 0 : 1;
      }
      if (IS_NULL_OR_UNDEFINED(y)) return 1;  // not equal
      if (IS_SYMBOL(y)) return 1;  // not equal
      if (IS_BOOLEAN(y)) y = %$toNumber(y);
      x = %$toPrimitive(x, NO_HINT);
    }
  }
}

所以,了解變量如何轉(zhuǎn)換為primitive類型的重要性也就可想而知了奶陈。具體的代碼細(xì)節(jié)可以看這里:runtime.js

ToObject

ToObject顧名思義就是將變量轉(zhuǎn)換為對(duì)象類型附较〕粤#可以看下它是如何將非對(duì)象類型轉(zhuǎn)換為對(duì)象類型:

// ECMA-262, section 9.9, page 36.
function ToObject(x) {  
  if (IS_STRING(x)) return new GlobalString(x);
  if (IS_NUMBER(x)) return new GlobalNumber(x);
  if (IS_BOOLEAN(x)) return new GlobalBoolean(x);
  if (IS_SYMBOL(x)) return %NewSymbolWrapper(x);
  if (IS_NULL_OR_UNDEFINED(x) && !IS_UNDETECTABLE(x)) {
    throw MakeTypeError(kUndefinedOrNullToObject);
  }
  return x;
}

因?yàn)槿粘4a很少用到,就不展開(kāi)了拒课。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徐勃,一起剝皮案震驚了整個(gè)濱河市事示,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌僻肖,老刑警劉巖肖爵,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異臀脏,居然都是意外死亡劝堪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門揉稚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秒啦,“玉大人,你說(shuō)我怎么就攤上這事搀玖〉圯铮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵巷怜,是天一觀的道長(zhǎng)葛超。 經(jīng)常有香客問(wèn)我,道長(zhǎng)延塑,這世上最難降的妖魔是什么绣张? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮关带,結(jié)果婚禮上侥涵,老公的妹妹穿的比我還像新娘。我一直安慰自己宋雏,他們只是感情好芜飘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著磨总,像睡著了一般嗦明。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚪燕,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天娶牌,我揣著相機(jī)與錄音,去河邊找鬼馆纳。 笑死诗良,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲁驶。 我是一名探鬼主播鉴裹,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了径荔?” 一聲冷哼從身側(cè)響起葛作,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猖凛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绪穆,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辨泳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玖院。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菠红。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖难菌,靈堂內(nèi)的尸體忽然破棺而出试溯,到底是詐尸還是另有隱情,我是刑警寧澤郊酒,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布遇绞,位于F島的核電站,受9級(jí)特大地震影響燎窘,放射性物質(zhì)發(fā)生泄漏摹闽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一褐健、第九天 我趴在偏房一處隱蔽的房頂上張望付鹿。 院中可真熱鬧,春花似錦蚜迅、人聲如沸舵匾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坐梯。三九已至,卻和暖如春刹帕,著一層夾襖步出監(jiān)牢的瞬間烛缔,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工轩拨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留践瓷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓亡蓉,卻偏偏與公主長(zhǎng)得像晕翠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 強(qiáng)制轉(zhuǎn)換 強(qiáng)制轉(zhuǎn)換主要指使用Number淋肾、String和Boolean三個(gè)構(gòu)造函數(shù)硫麻,手動(dòng)將各種類型的值,轉(zhuǎn)換成數(shù)字...
    燈火闌珊Zone閱讀 392評(píng)論 0 3
  • 值類型轉(zhuǎn)換將值從一種類型轉(zhuǎn)換為另一種類型通常稱為類型轉(zhuǎn)換樊卓,這是顯示的情況拿愧;隱式的情況稱為強(qiáng)制類型轉(zhuǎn)換。JavaSc...
    xpwei閱讀 3,530評(píng)論 0 5
  • 前端07班 王語(yǔ)句JavaScript程序的執(zhí)行單位為行(line)碌尔,也就是一行一行地執(zhí)行浇辜。一般情況下,每一行就是...
    ea203453e188閱讀 883評(píng)論 0 4
  • 今天是周五也是這個(gè)周最后一天唾戚,晚上回家吃過(guò)晚飯決定我說(shuō)明天寫(xiě)作業(yè)柳洋,今晚帶寶貝去玩會(huì)蹦蹦床,聽(tīng)到后她可開(kāi)心了叹坦。到了那...
    王藝靜媽媽閱讀 169評(píng)論 0 3
  • 昨天早上六點(diǎn)起來(lái)從廣州趕飛機(jī)到南京募书,晚上十二點(diǎn)睡绪囱,今天早上八點(diǎn)起來(lái)從南京到上海,辦完事晚上十一點(diǎn)回到南京南站莹捡。 出...
    李靈軍閱讀 316評(píng)論 1 1