聊一聊typeof instanceof 實現原理

前言

最近在回頭看看js基礎,正好對判斷數據類型有些不懂的地方,或者說不太明白typeof和instanceof原理,因此準備研究研究🤭javascript

涉及到原型的能夠看看這篇文章🤭html

淺談JavaScript原型java

數據類型

最新的 ECMAScript 標準定義了 8 種數據類型:c#

可能你們對BigInt原始數據類型比較陌生,它的提出解決了一部分問題,好比大於253 - 1 的整數。這本來是 Javascript中能夠用 Number 表示的最大數字。BigInt 能夠表示任意大的整數。post

瞭解了數據類型後,咱們接下來就來看看如何檢測數據類型吧。性能


檢測數據類型

typeof

typeof 操做符返回一個字符串,表示未經計算的操做數的類型。ui

總結一下可能的返回值:

  • "undefined"
  • "object"
  • "boolean"
  • "number"
  • "bigint"
  • "string"
  • "symbol"
  • "function"

附加信息:

typeof null === 'object';
複製代碼

這可能說是一個JavaScript設計的Bug吧。MDN規範是這麼解釋的:

在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示類型的標籤和實際數據值表示的。對象的類型標籤是 0。因爲 null 表明的是空指針(大多數平臺下值爲 0x00),所以,null 的類型標籤是 0,typeof null 也所以返回 "object"

typeof在判斷object類型的數據的時候,不能準確的告知咱們具體是哪種Object,並且在判斷null的時候,也會上述的附加信息。對於判斷是哪種object的時候,咱們須要用到instanceof這個操做符來判斷,咱們後面會說到。

說一說typeof的原理吧,說到這裏,咱們應該考慮一下,JavaScript是怎麼存儲數據的呢,又或者說,對於一個變量,它的數據類型權衡的標準是什麼呢?

查閱了相關的資料,其實這個是一個歷史遺留的bug,在 javascript 的最第一版本中,使用的 32 位系統,爲了性能考慮使用低位存儲了變量的類型信息:

  • 000:對象
  • 010:浮點數
  • 100:字符串
  • 110:布爾
  • 1:整數

but, 對於 undefinednull 來講,這兩個值的信息存儲是有點特殊的。

null:對應機器碼的 NULL 指針,通常是全零。

undefined:用 −2^30 整數來表示!

因此,typeof 在判斷 null 的時候就出現問題了,因爲 null 的全部機器碼均爲0,所以直接被當作了對象來看待。

因此,彷佛懂了一點皮毛了(●'◡'●)


instanceof

instanceof 運算符用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。

語法

object instanceof constructor
object 某個實例對象
construtor 某個構造函數
複製代碼
// 定義構造函數
function C(){} 
function D(){} 

var o = new C();


o instanceof C; // true,由於 Object.getPrototypeOf(o) === C.prototype


o instanceof D; // false,由於 D.prototype 不在 o 的原型鏈上

o instanceof Object; // true,由於 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false,C.prototype 指向了一個空對象,這個空對象不在 o 的原型鏈上.

D.prototype = new C(); // 繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 由於 C.prototype 如今在 o3 的原型鏈上
複製代碼

須要注意的是,若是表達式 obj instanceof Foo 返回 true,則並不意味着該表達式會永遠返回 true,由於 Foo.prototype 屬性的值有可能會改變,改變以後的值頗有可能不存在於 obj 的原型鏈上,這時原表達式的值就會成爲 false。另一種狀況下,原表達式的值也會改變,就是改變對象 obj 的原型鏈的狀況,雖然在目前的ES規範中,咱們只能讀取對象的原型而不能改變它,但藉助於非標準的 __proto__ 僞屬性,是能夠實現的。好比執行 obj.__proto__ = {} 以後,obj instanceof Foo 就會返回 false 了。

原理淺析

要想理解instanceof原理的話,咱們須要從兩個方面去了解:

  • 語言規範中是如何定義運算符的
  • JavaScript原型繼承機制

這裏,我直接將規範定義翻譯爲 JavaScript 代碼以下:

function new_instance_of(leftVaule, rightVaule) {
    let rightProto = rightVaule.prototype,
        leftVaule = leftVaule.__proto__;
    while (true) {
        if (leftVaule === null) {
            return false;
        }
        if (leftVaule === rightProto) {
            return true;
        }
        leftVaule = leftVaule.__proto__
    }
}
複製代碼

從上面的代碼看得出來,instanceof主要的原理就是:

只要右邊的prototype在左邊的原型鏈上及可,也就是返回true。所以,instanceof在查詢的過程當中會遍歷左邊變量的原型鏈,直到找到右邊變量的prototype,若是查找失敗的話,返回false,告訴咱們左邊的變量並不是是右邊變量的實例。

接下來咱們看一看有趣的例子:

function Foo() {}
        console.log(Object instanceof Object)
        console.log(Function instanceof Function)
        console.log(Function instanceof Object)
        console.log(Foo instanceof Object)
        console.log(Foo instanceof Function)
        console.log(Foo instanceof Foo)
複製代碼

JavaScript 的原型繼承原理

關於原型繼承的原理,我簡單用一張圖來表示

這個圖很重要,對於原型鏈不理解的,能夠看看這篇文章:

淺談JavaScript原型

舉個例子分析一個有趣的instanceof例子

Object instanceof Object

由圖可知,Object 的 prototype 屬性是 Object.prototype, 而因爲 Object 自己是一個函數,由 Function 所建立,因此 Object.__proto__ 的值是 Function.prototype,而 Function.prototype 的 __proto__ 屬性是 Object.prototype,因此咱們能夠判斷出,Object instanceof Object 的結果是 true 。用代碼簡單的表示一下
複製代碼
leftValue = Object.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判斷
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
leftValue === rightValue
// 返回 true
複製代碼

剩下的Function instanceof Object等有趣的例子能夠本身手動去實現一下🤭


Object.prototype.toString

ES5 規範中的描述

能夠知道,Object.prototype.toString 最終會返回形式如 [object,class] 的字符串,class 指代的是其檢測出的數據類型,這個是咱們判斷數據類型的關鍵。

var toString=Object.prototype.toString;

console.log(toString.call(und));  // [object Undefined]
console.log(toString.call(nul));  // [object Null]
console.log(toString.call(boo));  // [object Boolean]
console.log(toString.call(num));  // [object Number]
console.log(toString.call(str));  // [object String]
console.log(toString.call(obj));  // [object Object]
console.log(toString.call(arr));  // [object Array]
console.log(toString.call(fun));  // [object Function]
console.log(toString.call(date));  // [object Date]
console.log(toString.call(reg));  // [object RegExp]
console.log(toString.call(err));  // [object Error]
console.log(toString.call(arg));  // [object Arguments]
複製代碼

數據類型檢測終極方法

/** * @desc 數據類型檢測 * @param obj 待檢測的數據 * @return {String} 類型字符串 */
 let type = (obj) => typeof obj !== 'object' ? typeof obj : Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();

複製代碼

數據類型的單獨檢測

/** * @desc 是不是 Undefined 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isUndefined = obj => obj === void 0
/** * @desc 是不是 Null 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isNull = obj => obj === Null
/** * @desc 是不是 Boolean 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isBoolean = obj => typeof(obj) === 'boolean'
/** * @desc 是不是 Number 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isNumber = obj => typeof(obj) === 'number'
/** * @desc 是不是 String 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isString = obj => typeof(obj) === 'string'
/** * @desc 是不是 Object 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
/** * @desc 是不是 Array 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isArray = obj => Object.prototype.toString.call(obj) === '[object Array]'
/** * @desc 是不是 Function 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isFunction = obj => typeof obj === 'function'
/** * @desc 是不是 Date 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]'
/** * @desc 是不是 RegExp 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isRegExp = obj => Object.prototype.toString.call(obj) === '[object RegExp]'
/** * @desc 是不是 Error 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isError = obj => Object.prototype.toString.call(obj) === '[object Error]'
/** * @desc 是不是 Arguments 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isArguments = obj => Object.prototype.toString.call(obj) === '[object Arguments]'

複製代碼

結論

  • 使用 typeof 來判斷基本數據類型是 ok 的,須要注意的是typeof判斷null類型時的問題
  • 判斷一個對象的話具體考慮用instanceof,可是instanceof判斷一個數組的時候,它能夠被instanceof判斷爲Obeject
  • 比較準確的的判斷對象實例的類型,採起Object.prototype.toString.call()方法

參考

v8引擎是如何知道js數據類型的?

typeof的原理?

JavaScript中typeof詳解

JavaScript 數據類型檢測終極解決方案

相關文章
相關標籤/搜索