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中變量是“弱類型”的,一個變量可以現在被賦值為 字符串類型,隨后又被賦值為數字類型。
typeof是一個操作符而不是函數,用來檢測給定變量的數據類型。
Symbol 是ES6中引入的一種原始數據類型,表示獨一無二的值。BigInt(大整數)是 ES2020 引入的一種新的數據類型,用來解決 JavaScript中數字只能到 53 個二進制位(JavaScript 所有數字都保存成 64 位浮點數,大於這個范圍的整數,無法精確表示的問題。(在平常的開發中,數據的id 一般用 string 表示的原因)。為了與 Number 類型區別,BigInt 類型的數據必須添加后綴n。 1234為普通整數,1234n為 BigInt。
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,右邊運算數是對象類的名字或者構造函數; 返回true或false。
[] instanceof Array; // true [] instanceof Object; // true [] instanceof RegExp; // false new Date instanceof Date; // true
instanceof 的內部機制是:檢測構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈上。下面會詳解介紹該部分。
typeof 原理
typeof原理: 不同的對象在底層都表示為二進制,在Javascript中二進制前(低)三位存儲其類型信息。
- 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運算符只能用於對象,不適用原始類型的值。
- 判斷某個實例是否屬於某種類型
function Foo() {}; Foo.prototype.message = ...; const a = new Foo();
- 也可以判斷一個實例是否是其父類型或者祖先類型的實例。
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]]。
原型鏈
-
- function Foo 就是一個方法,比如內置的 Array,String,或者自定義方法。
-
- function Object就是 Object
-
- function Function就是 Function
-
- 以上三個其實都是 function,所以他們的 __proto__都是 Function.prototype
-
- 記住 String, Array, Number, Object, Function這些其實都是 function
東莞vi設計https://www.houdianzi.com/dgvi/ 豌豆資源網站大全https://55wd.com
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 的原型鏈,直到找到 constructor 的 prototype ,如果查找失敗,則會返回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