typeof和instanceof原理

目錄

  • JavaScript數據類型
  • typeof
  • instanceof
  • JavaScript原型鏈

JavaScript數據類型

JavaScript有八種內置類型前端

  • 空值(null)
  • 未定義(undefined)
  • 布爾值(boolean)
  • 數字(number)
  • 字符串(string)
  • 對象 (object)
  • 符號(symbol, ES6中新增)
  • 大整數(BigInt, ES2020 引入)
除對象外,其餘統稱爲「基本類型」。
typeof null // 'object'
typeof undefined; // "undefined"
typeof false; // "boolean"
typeof 1; // "number"
typeof '1'; // "string"
typeof {}; // "object" 
typeof []; // "object" 
typeof new Date(); // "object"

typeof Symbol; // "Symbol"
typeof 123n // 'bigint'

這裏的類型值的是值,變量是沒有類型的,變量能夠隨時持有任何類型的值。JavaScript中變量是「弱類型」的,一個變量能夠如今被賦值爲 字符串類型,隨後又被賦值爲數字類型。es6

typeof是一個操做符而不是函數,用來檢測給定變量的數據類型。數組

Symbol 是ES6中引入的一種 原始數據類型,表示獨一無二的值。BigInt(大整數)是 ES2020 引入的一種新的數據類型,用來解決 JavaScript中數字只能到 53 個二進制位(JavaScript 全部數字都保存成 64 位浮點數,大於這個範圍的整數,沒法精確表示的問題。(在日常的開發中,數據的id 通常用 string 表示的緣由)。爲了與 Number 類型區別,BigInt 類型的數據必須添加後綴n。 1234爲普通整數, 1234nBigInt。瞭解更多能夠看 《ES6 入門教程》

typeof null 爲何返回 'object',稍後會從JavaScript數據底層存儲機制來解釋。瀏覽器

還有一種狀況微信

function foo() {};
typeof foo; // 'function'

這樣看來,function 也是JavaScript的一個內置類型。然而查閱規範,就會知道,它其實是 object 的一個"子類型"。具體來講,函數是「可調用對象」,它有一個內部屬性[[call]],該屬性使其能夠被調用。typeof 能夠用來區分函數其餘對象。函數

可是使用 typeof 不能 判斷對象具體是哪一種類型。全部 typeof 返回值爲 "object" 的對象(如數組,正則等)都包含一個內部屬性 [[class]](咱們能夠把它看作一個內部的分類)。這個屬性沒法直接訪問,通常經過 Object.prototype.toString(...)來查看。學習

Object.prototype.toString.call(new Date); // "[object Date]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(/reg/ig); // "[object RegExp]"

instanceof 運算符也經常用來判斷對象類型。用法: 左邊的運算數是一個object,右邊運算數是對象類的名字或者構造函數; 返回truefalsethis

[] instanceof Array; // true
[] instanceof Object; // true
[] instanceof RegExp; // false
new Date instanceof Date; // true

instanceof 的內部機制是:檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。下面會詳解介紹該部分。spa

typeof 原理

typeof原理: 不一樣的對象在底層都表示爲二進制,在Javascript中二進制前(低)三位存儲其類型信息prototype

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

typeof null 爲"object", 緣由是由於 不一樣的對象在底層都表示爲二進制,在Javascript中二進制前(低)三位都爲0的話會被判斷爲Object類型,null的二進制表示全爲0,天然前三位也是0,因此執行typeof時會返回"object"。
一個不恰當的例子,假設全部的Javascript對象都是16位的,也就是有16個0或1組成的序列,猜測以下:

Array: 1000100010001000
null:  0000000000000000

typeof []  // "object"
typeof null // "object"

由於Array和null的前三位都是000。爲何Array的前三位不是100?由於二進制中的「前」通常表明低位, 好比二進制00000011對應十進制數是3,它的前三位是011。

instanceof

要想從根本上理解,須要從兩個方面入手:

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

通俗一些講,instanceof 用來比較一個對象是否爲某一個構造函數的實例。注意,instanceof運算符只能用於對象,不適用原始類型的值。

  1. 判斷某個實例是否屬於某種類型
function Foo() {};
Foo.prototype.message = ...;
const a = new Foo();
  1. 也能夠判斷一個實例是不是其父類型或者祖先類型的實例。
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

JavaScript原型鏈

理解原型

咱們建立的每一個函數都有一個 [[prototype]](原型))屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。那麼 prototype 就是調用 構造函數 而建立的那個對象實例的原型對象。使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。

function Person() {};
Person.prototype.name = 'kangkang';
Person.prototype.sayName = function() {
    console.log(this.name);
}

const person1 = new Person();
person1.sayName(); // 'kangkang'

const person2 = new Person();
person2.sayName(); // 'kangkang'

console.log(person1.sayName === person2.sayName);
// true

構造函數,原型和實例的關係

  • 每一個構造函數都有一個原型對象
  • 原型對象都包含一個指向構造函數指針
  • 實例都包含一個指向原型對象指針

那麼,假如咱們讓原型對象等於另外一個類型實例,結果會怎麼樣?
顯然,此時的原型對象將包含一個指向另外一個原型指針,相應地,另外一個原型中也包含着一個指向指向另外一個構造函數指針。假如另外一個原型又是另外一個類型實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。

上面這段話有點繞,若是想不明白的話,這裏能夠停一下,讀三篇,再結合咱們日常寫代碼使用過程當中的實際場景。

[[prototype]]機制

[[prototype]]機制就是存在與對象中的一個內部連接,它會引用其餘對象。
一般來講,這個連接的做用是:若是在對象上沒有找到須要的屬性或者方法引用,引擎就會繼續在 [[ptototype]]關聯的對象上進行查找,同理,若是在後者中也沒有找到須要的引用就會繼續查找它的[[prototype]],以此類推。這一系列對象的連接被稱爲「原型鏈」。

可是哪裏是 [[prototype]]的 」盡頭「呢?

全部普通的 [[prototype]]鏈最終都會執行內置的 Object.prototype。因爲全部的"普通"(內置,不是特定主機的擴展)對象都」源於「(或者說把[[prototype]] 鏈頂端設置爲)這個Object.prototype對象,因此說它包含JavaScript中許多通用的功能。好比說.toString().valueOf()等等

Object.prototype是js原型鏈的最頂端,它的__proto__null(有__proto__屬性,但值是 null,由於這是原型鏈的最頂端);

爲何要這麼設計?

最主要的就是節省內存,若是屬性和方法定義在原型上,那麼全部的實例對象就能共享。

__proto__

絕大多數(不是全部)瀏覽器也支持一種非標準的方法來訪問內部的 [[prototype]]屬性。

function Foo() {};
const a = new Foo();

a.__proto__ === Foo.prototype; // true

這個奇怪的.__proto__屬性「神奇地」引用了內部的[[prototype]]對象。若是你想直接查找(甚至能夠直接經過.__proto__.__proto__ ...來遍歷)原型鏈的話,這個方法很是有用。

.construtor同樣, __proto__實際上並不存在於你正在使用的對象(本例中是 a)。實際上,它和其餘的經常使用函數( .toString()、.isPrototypeOf(...),等等 同樣,存在於內置的 Object.prototype中。(它們是不可枚舉的;

此外,.__proto__看起來很像一個屬性,可是實際上它更像一個 getter/setter
.__proto__的實現大體是這樣的

Object.defineProperty(Object.prototype, "__proto__", {
    get: function() {
        return Object.getPrototypeOf(this);
    },
    // ES6中的Object.setPrototypeOf
    set: function(o) {
        Object.setPrototypeOf(this, o);
        return o;
    }
})

所以,訪問(獲取值) a.__proto__時,其實是調用了 a.__proto__()(調用getter函數)。雖然getter函數存在於Object.prototype對象中,可是 它的 this 指向對象 a,因此和object.getPrototypeOf(a)結果相同。

.__proto__是可設置屬性,以前的代碼中使用ES6的Object.setPrototypeOf(...)進行設置。然而,一般來講你不須要修改已有對象的[[prototype]]

原型鏈

JavaScript原型鏈

    1. function Foo 就是一個方法,好比內置的 Array,String,或者自定義方法。
    1. function Object就是 Object
    1. function Function就是 Function
    1. 以上三個其實都是 function,因此他們的 __proto__都是 Function.prototype
    1. 記住 String, Array, Number, Object, Function這些其實都是 function

function Foo() {};

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true

console.log(Foo instanceof Foo); // false
console.log(Foo instanceof Object); // true
console.log(Foo instanceof Function); // true

你們能夠在控制檯輸出,能夠直觀的看到每一個步驟的輸出,結合instanceof 的規範跟js原型鏈 加深理解。

回過頭來再看instanceof

instanceof的語法:

object instanceof constructor
// 等同於
constructor.prototype.isPrototypeOf(object)
  • object: 要檢測的對象
  • constructor:某個構造函數

instanceof的代碼實現。

function instanceof(L, R) { //L是表達式左邊,R是表達式右邊
    const O = R.prototype;
    L = L.__proto__;
    while(true) {
        if (L === null)
            return false;
        if (L === O) // 這裏重點:當 L 嚴格等於 0 時,返回 true 
            return true;
        L = L.__proto__;
    }
}

instanceof原理: 檢測 constructor.prototype是否存在於參數 object的 原型鏈上。instanceof 查找的過程當中會遍歷object 的原型鏈,直到找到 constructorprototype ,若是查找失敗,則會返回false,告訴咱們,object 並不是是 constructor 的實例。

原型鏈這部分很很差理解,我基本上都是看完過幾天就忘,因此要多看幾遍多理解,花些時間搞明白,搞明白這部分。以後再看相關的東西,就很簡單易懂。這部分是JavaScript很重要的核心。花幾天時間反覆看,弄明白了,之後理解不少問題都是簡單的多。若是你發現我上面哪部分表述的不太準確,記得給我指出來,互相學習。這部分推薦好好看看 《JavaScript高級程序設計(第3版)》第六章的這部分,還有 《你不知道的JavaScript(上卷)》第五章關於這部份內容的講解。

Symbol.hasInstance

對象的Symbol.hasInstance屬性,指向一個內部方法。當其餘對象使用instanceof運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

總結

看完以後,腦子裏能夠把上面的內容串一下;看看下面的幾個問題你是否能夠馬上想出來

  • JavaScript有哪幾種數據類型,都有哪些判斷數據類型的操做,返回值是什麼,原理是什麼
  • typeof null 爲何是 」object「
  • 什麼是原型,哪裏是 [[prototype]]的 」盡頭「,爲何要這麼設計
  • JavaScript原型鏈的核心是什麼
  • instanceof的原理是什麼
  • Symbol.hasInstance又是什麼(或者你本身實現一個instanceof

其餘

最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。
牧碼的星星

相關文章
相關標籤/搜索