摘要:本文主要講解規(guī)范中的操作符。由上述步驟可知,如果是一個(gè)函數(shù),那么會(huì)重新在綁定的目標(biāo)函數(shù)上執(zhí)行操作。而使用的方式的時(shí)候,給構(gòu)造函數(shù)添加一個(gè)靜態(tài)方法,相當(dāng)于給對(duì)象賦值,賦值操作會(huì)先檢查原型鏈上是否存在同名屬性,所以就會(huì)有賦值失敗的風(fēng)險(xiǎn)。
本文主要講解ECMAScript7規(guī)范中的instanceof操作符。
預(yù)備知識(shí) 有名的Symbols“有名”的Symbols指的是內(nèi)置的符號(hào),它們定義在Symbol對(duì)象上。ECMAScript7中使用了@@name的形式引用這些內(nèi)置的符號(hào),比如下面會(huì)提到的@@hasInstance,其實(shí)就是Symbol.hasInstance。
InstanceofOperator(O, C)O instanceof C在內(nèi)部會(huì)調(diào)用InstanceofOperator(O, C)抽象操作,該抽象操作的步驟如下:
如果C的數(shù)據(jù)類(lèi)型不是對(duì)象,拋出一個(gè)類(lèi)型錯(cuò)誤的異常;
讓instOfHandler等于GetMethod(C, @@hasInstance),大概語(yǔ)義就是獲取對(duì)象C的@@hasInstance屬性的值;
如果instOfHandler的值不是undefined,那么:
返回ToBoolean(? Call(instOfHandler, C, ? O ?))的結(jié)果,大概語(yǔ)義就是執(zhí)行instOfHandler(O),然后把調(diào)用結(jié)果強(qiáng)制轉(zhuǎn)化為布爾類(lèi)型返回。
如果C不能被調(diào)用,拋出一個(gè)類(lèi)型錯(cuò)誤的異常;
返回OrdinaryHasInstance(C, O)的結(jié)果。
OrdinaryHasInstance(C, O)OrdinaryHasInstance(C, O)抽象操作的步驟如下:
如果C不能被調(diào)用,返回false;
如果C有內(nèi)部插槽[[BoundTargetFunction]],那么:
讓BC等于C的內(nèi)部插槽[[BoundTargetFunction]]的值;
返回InstanceofOperator(O, BC)的結(jié)果;
如果O的類(lèi)型不是對(duì)象,返回false;
讓P等于Get(C, "prototype"),大概語(yǔ)義是獲取C.prototype的值;
如果P的數(shù)據(jù)類(lèi)型不是對(duì)象,拋出一個(gè)類(lèi)型錯(cuò)誤的異常;
重復(fù)執(zhí)行下述步驟:
讓O等于O.[[GetPrototypeOf]]()的結(jié)果,大概語(yǔ)義就是獲取O的原型對(duì)象;
如果O等于null,返回false;
如果SameValue(P, O)的結(jié)果是true,返回true。
SameValue抽象操作參見(jiàn)JavaScript中的==,===和Object.js()中的Object.is(),Object.is()使用的就是這個(gè)抽象操作的結(jié)果。
由上述步驟2可知,如果C是一個(gè)bind函數(shù),那么會(huì)重新在C綁定的目標(biāo)函數(shù)上執(zhí)行InstanceofOperator(O, BC)操作。
由上述步驟6可知,會(huì)重復(fù)地獲取對(duì)象O的原型對(duì)象,然后比較該原型對(duì)象和C的prototype屬性是否相等,直到相等返回true,或者O變?yōu)?b>null,也就是遍歷完整個(gè)原型鏈,返回false。
Function.prototype[@@hasInstance](V)由上面的InstanceofOperator(O, C)抽象操作的步驟2和3可以知道,如果C上面定義或繼承了@@ hasInstance屬性的話(huà),會(huì)調(diào)用該屬性的值,而不會(huì)走到步驟4和5。步驟4和5的目的是為了兼容沒(méi)有實(shí)現(xiàn)@@hasInstance方法的瀏覽器。如果一個(gè)函數(shù)沒(méi)有定義或繼承@@hasInstance屬性,那么就會(huì)使用默認(rèn)的instanceof的語(yǔ)義,也就是OrdinaryHasInstance(C, O)抽象操作描述的步驟。
ECMAScript7規(guī)范中,在Function的prototype屬性上定義了@@hasInstance屬性。Function.prototype[@@hasInstance](V)的步驟如下:
讓F等于this值;
返回OrdinaryHasInstance(F, V)的結(jié)果。
所以,你可以看到在默認(rèn)情況下,instanceof的語(yǔ)義是一樣的,都是返回OrdinaryHasInstance(F, V)的結(jié)果。為什么說(shuō)默認(rèn)情況下?因?yàn)槟憧梢愿采wFunction.prototype[@@hasInstance]方法,去自定義instanceof的行為。
例子function A () {} function B () {} var a = new A a.__proto__ === A.prototype // true a.__proto__.__proto__ === Object.prototype // true a.__proto__.__proto__.__proto__ === null // true a instanceof A // true a instanceof B // false
由OrdinaryHasInstance(C, O)的第6步可知:
對(duì)于a instanceof A,P是A.prototype,在第一次循環(huán)的時(shí)候,a的原型對(duì)象a._proto__是A.prototype,也就是步驟中的O是A.prototype,所以返回了true;
對(duì)于a instanceof B,P是B.prototype,在第一次循環(huán)的時(shí)候,a的原型對(duì)象a._proto__是A.prototype,不等于P;執(zhí)行第二次循環(huán),此時(shí)O是a.__proto__.__proto__,也就是Object.prototype,不等于P;執(zhí)行第三次循環(huán),此時(shí)O是a.__proto__.__proto__.__proto__,也就是null,也就是原型鏈都遍歷完了,所以返回了false。
接著上面的例子:
A.prototype.__proto__ = B.prototype a.__proto__ === A.prototype // true a.__proto__.__proto__ === B.prototype // true a.__proto__.__proto__.__proto__ === Object.prototype // true a.__proto__.__proto__.__proto__.__proto__ === null // true a instanceof B // true
在上面的例子中,我們把B.prototype設(shè)置成了a的原型鏈中的一環(huán),這樣a instanceof B在OrdinaryHasInstance(C, O)的第6步的第2次循環(huán)的時(shí)候,返回了true。
由OrdinaryHasInstance(C, O)的第2步,我們知道bind函數(shù)的行為和普通函數(shù)的行為是不一樣的:
function A () {} var B = A.bind() B.prototype === undefined // true var b = new B b instanceof B // true b instanceof A // true
由上面的例子可知,B.prototype是undefined。所以,instanceof作用于bind函數(shù)的返回結(jié)果其實(shí)是作用于綁定的目標(biāo)函數(shù)的返回值,和bind函數(shù)基本上沒(méi)有什么關(guān)系。
由InstanceofOperator(O, C)步驟2和步驟3可知,我們可以通過(guò)@@hasInstance屬性來(lái)自定義instanceof的行為:
function A () {} var a = new A a instanceof A // true A[Symbol.hasInstance] = function () { return false } a instanceof A // ?
在chrome瀏覽器測(cè)試了一下,發(fā)現(xiàn)還是輸出true。然后看了一下ECMAScript6的文檔,ECMAScript6文檔里面還沒(méi)有規(guī)定可以通過(guò)@@hasInstance改變instanceof的行為,所以應(yīng)該是目前chrome瀏覽器還沒(méi)有實(shí)現(xiàn)ECMAScript7中的instanceof操作符的行為。
直到有一天看了MDN上Symbol.hasInstance的兼容性部分,發(fā)現(xiàn)chrome從51版本就開(kāi)始支持Symbol.hasInstance了:
class MyArray { static [Symbol.hasInstance](instance) { return Array.isArray(instance) } } console.log([] instanceof MyArray) // true
那么為什么我那樣寫(xiě)不行呢?直到我發(fā)現(xiàn):
function A () {} var fun = function () {return false} A[Symbol.hasInstance] = fun A[Symbol.hasInstance] === fun // false A[Symbol.hasInstance] === Function.prototype[Symbol.hasInstance] // true A[Symbol.hasInstance] === A.__proto__[Symbol.hasInstance] // true
由上面的代碼可知,A[Symbol.hasInstance]并沒(méi)有賦值成功,而且始終等于Function.prototype[Symbol.hasInstance],也就是始終等于A的原型上的Symbol.hasInstance方法。那是不是因?yàn)樵蜕系耐椒ǎ?/p>
Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance) // Object {writable: false, enumerable: false, configurable: false, value: function}
由上面的代碼可知,Function.prototype上的Symbol.hasInstance的屬性描述符的writable是false,也就是這個(gè)屬性是只讀的,所以在A上面添加Symbol.hasInstance屬性失敗了。但是為啥沒(méi)有失敗的提示呢?
"use strict" function A () {} var fun = function () {return false} A[Symbol.hasInstance] = fun // Uncaught TypeError: Cannot assign to read only property "Symbol(Symbol.hasInstance)" of function "function A() {}"
錯(cuò)誤提示出來(lái)了,所以以后還是盡量使用嚴(yán)格模式。非嚴(yán)格模式下有些操作會(huì)靜默失敗,也就是即使操作失敗了也不會(huì)有任何提示,導(dǎo)致開(kāi)發(fā)人員認(rèn)為操作成功了。
var a = {} a[Symbol.hasInstance] = function () {return true} new Number(3) instanceof a // true
因?yàn)榭梢酝ㄟ^(guò)自定義Symbol.hasInstance方法來(lái)覆蓋默認(rèn)行為,所以用instanceof操作符判斷數(shù)據(jù)類(lèi)型并不一定是可靠的。
還有一個(gè)問(wèn)題:為什么上面MDN文檔的例子可以成功,我最初的例子就不行呢,目的不都是寫(xiě)一個(gè)構(gòu)造函數(shù),然后在構(gòu)造函數(shù)上添加一個(gè)屬性嗎?
個(gè)人分析的結(jié)果是:雖然大家都說(shuō)Class是寫(xiě)構(gòu)造函數(shù)的一個(gè)語(yǔ)法糖,但是其實(shí)還是和使用function的方式有差別的,就比如上面的例子。使用Class的時(shí)候,會(huì)直接在構(gòu)造函數(shù)上添加一個(gè)靜態(tài)屬性,不會(huì)先檢查原型鏈上是否存在同名屬性。而使用function的方式的時(shí)候,給構(gòu)造函數(shù)添加一個(gè)靜態(tài)方法,相當(dāng)于給對(duì)象賦值,賦值操作會(huì)先檢查原型鏈上是否存在同名屬性,所以就會(huì)有賦值失敗的風(fēng)險(xiǎn)。所以,就給構(gòu)造函數(shù)添加Symbol.hasInstance屬性來(lái)說(shuō),Class能做到,使用Function的方式就做不到。
更新于2018/11/20
上面總結(jié)到:
所以,就給構(gòu)造函數(shù)添加Symbol.hasInstance屬性來(lái)說(shuō),Class能做到,使用Function的方式就做不到。
但是,給對(duì)象添加屬性除了直接賦值之外,還可以使用Object.defineProperty方法:
function A () {} var a = new A a instanceof A // true Object.defineProperty(A, Symbol.hasInstance, { value: function () { return false } }) a instanceof A // false
使用Object.defineProperty方法添加或者修改對(duì)象屬性的時(shí)候不會(huì)檢查原型鏈,所以就可以成功了。所以上面的總結(jié)也就不成立了,也就是:
所以,就給構(gòu)造函數(shù)添加Symbol.hasInstance屬性來(lái)說(shuō),Class能做到,使用Function的方式也可以做到。總結(jié)
本文主要講解ECMAScript7規(guī)范中的instanceof操作符,希望大家能有所收獲。如果本文有什么錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,歡迎在評(píng)論區(qū)留言。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/97833.html
摘要:本文將講解我目前所知道的判斷數(shù)據(jù)類(lèi)型的方法。數(shù)據(jù)類(lèi)型一共有種除了之外的種屬于原始數(shù)據(jù)類(lèi)型。等價(jià)于問(wèn)題四的返回值是什么答案。 本文將講解我目前所知道的判斷JavaScript數(shù)據(jù)類(lèi)型的方法。JavaScript數(shù)據(jù)類(lèi)型一共有7種: Undefined Null Boolean String Symbol Number Object 除了Object之外的6種屬于原始數(shù)據(jù)類(lèi)型。有時(shí),我...
摘要:本文將介紹規(guī)范中的抽象操作。它們主要用于規(guī)范的說(shuō)明,不需要被真正地實(shí)現(xiàn)。該抽象操作接受一個(gè)參數(shù)和一個(gè)可選的參數(shù)。根據(jù)規(guī)范中的加法操作,對(duì)于操作,會(huì)調(diào)用和把和轉(zhuǎn)化為原始數(shù)據(jù)類(lèi)型。 本文將介紹ECMAScript7規(guī)范中的ToPrimitive抽象操作。 預(yù)備知識(shí) ECMAScript數(shù)據(jù)類(lèi)型 ECMAScript數(shù)據(jù)類(lèi)型細(xì)分為兩大類(lèi)數(shù)據(jù)類(lèi)型,一種是語(yǔ)言類(lèi)型,一種是規(guī)范類(lèi)型: 語(yǔ)言類(lèi)型...
概述 本文主要講解JavaScript中的三種相等運(yùn)算:==,===和Object.is()。通過(guò)對(duì)比和例子,加深大家的印象,并就個(gè)別例子進(jìn)行詳細(xì)說(shuō)明。 預(yù)備知識(shí) ECMAScript7規(guī)范中的ToPrimitive抽象操作 ===運(yùn)算符 對(duì)于x === y,該運(yùn)算符的比較步驟如下: 如果x的類(lèi)型和y的類(lèi)型不一樣,返回false; 如果x的類(lèi)型是數(shù)字,那么: 如果x是NaN,返回false;...
摘要:本文將介紹一段使用隱式類(lèi)型轉(zhuǎn)換輸出的代碼,并講解具體的轉(zhuǎn)換過(guò)程。代碼轉(zhuǎn)換過(guò)程我們分四部分講解具體的轉(zhuǎn)換過(guò)程,一個(gè)空數(shù)組,緊跟在數(shù)組后面的的語(yǔ)義應(yīng)該是表示屬性操作,類(lèi)似于中的作用,而不是表示數(shù)組。 本文將介紹一段使用JavaScript隱式類(lèi)型轉(zhuǎn)換輸出nb的代碼,并講解具體的轉(zhuǎn)換過(guò)程。 預(yù)備知識(shí) 請(qǐng)先閱讀文章ECMAScript7規(guī)范中的ToPrimitive抽象操作。 代碼 ([][[...
摘要:注意基本變量類(lèi)型不是對(duì)象類(lèi)型,只有基本包裝類(lèi)型才是對(duì)象類(lèi)型。至于顯示的原型,在里用屬性表示,這個(gè)是原型繼承的基礎(chǔ)知識(shí),在這里就不在敘述了。 前言 如果你要開(kāi)發(fā)一個(gè)復(fù)雜的產(chǎn)品,那么肯定少不了使用面向?qū)ο髾C(jī)制,當(dāng)然也避不開(kāi) Javascript 里面的繼承,instanceof 運(yùn)算符是原生 Javascript 語(yǔ)言中用來(lái)判斷實(shí)例繼承的操作符。所以我們有必要深入理解該運(yùn)算符! inst...
閱讀 2162·2021-09-22 15:54
閱讀 1901·2021-09-04 16:40
閱讀 927·2019-08-30 15:56
閱讀 2687·2019-08-30 15:44
閱讀 2215·2019-08-30 13:52
閱讀 1181·2019-08-29 16:35
閱讀 3405·2019-08-29 16:31
閱讀 2625·2019-08-29 13:48