Javascript基础语法之instanceof

277 阅读5分钟

1、instanceof操作符可以用来确定一个对象实例的原型链上是否有原型XX。

function Foo () {
}
let f = new Foo();
console.log(f instanceof Foo); // true
var a = "abc";
var b = new String(a);
var c = Object(a);

console.log(typeof a); // "string"
console.log(typeof b); // "object"
console.log(typeof c); // "object"

console.log(b instanceof String); // true
console.log(c instanceof String); // true
[] instanceof Array; // true
[] instanceof Object; // true
Array instanceof Object; // true
Object instanceof Array; // false

Object instanceof Object; // true
Number instanceof Number; // false
Array instanceof Array; // false

Function instanceof Object; // true
Object instanceof Function; // true
Object instanceof Object; // true
Function instanceof Function; // true
String instanceof Function; // true
Array instanceof Function; // true
Number instanceof Function; // true

"" instanceof String; // false
1 instanceof Number; // false
false instanceof Boolean; // false

错误使用的报错 如:

Array instanceof [];
// Uncaught TypeError: Right-hand side of 'instanceof' is not callable
{} instanceof Object;
// VM91:1 Uncaught SyntaxError: Unexpected token 'instanceof'

2、instanceof 的原理

__proto__prototype的区别:

prototype 只在 Function 中有(是函数对象的属性),而 __proto__ 则在Function和Object中都有。


__proto__ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the > object that is used to build __proto__ when you create an object with new:
大致意思是: __proto__ 是真正用来查找原型链去获取方法的对象。prototype是在用new创建对象时用来构建 __proto__ 的对象

( new Foo ).__proto__ === Foo.prototype;
( new Foo ).prototype === undefined;

摘自于Stack Overflow __proto__ VS. prototype in JavaScript


理解原型

  • 无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。对前面的例子而言,Person.prototype.constructor 指向 Person。然后,因构造函数而异,可能会给原型对象添加其他属性和方法。

  • 在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式, 但 Firefox、Safari和 Chrome 会在每个对象上暴露__proto__属性,通过这个属性可以访问对象的原型。在其他实现中,这个特性完全被隐藏了。关键在于理解这一点:实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。

摘自于《JavaScript高级程序设计 第四版》 第八章 8.2.4 原型模式


也就有这样的结论

Array.prototype === [].__proto__ // true
Object.prototype === [].__proto__.__proto__ // true

模拟实现

function instance_of(L, R) { //L 表示左边的object,R 表示右边的constructor
 const R_P = R.prototype;
 L = L.__proto__;
 while (true) { 
   if (L === null) 
     return false; 
   if (R_P === L)
     return true; 
   L = L.__proto__; 
 } 
}

所以可以这样 (平常不要这样干!)

let arr = []; 
arr.__proto__ = Number.prototype; 
console.log(arr instanceof Number) // true
let arr = []; 
arr.__proto__ = String.prototype;
console.log(arr instanceof String) // true

《你不知道的JavaScript上卷》有这么一段

.__proto__ 看起来很像一个属性,但是实际上它更像一个 getter/setter。 .__proto__ 的实现大致上是这样的:

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

日常开发中要注意__proto__这个非标准属性,尽量不要去使用它,从下面的代码可以不难发现上面的instance_of的模拟实现,之所以叫模拟的原因。

let origin = Object.create(null);
origin.a = 1;
let obj = {
  b: 2
}
// obj.__proto__ = origin;
Object.setPrototypeOf(obj, origin)
console.log(obj.__proto__) // undefined
console.log(Object.getPrototypeOf(obj) === origin) // true
console.log(obj.__proto__ === origin) // false

要修改原型尽量使用Object.setPrototypeOf()方法,但是JavaScript高级编程第四版,第八章中有这么一段警告和描述:

警告:Object.setPrototypeOf()可能会严重影响代码性能。Mozilla文档说的很清楚: “在所有浏览器和JavaScript引擎中,修改继承关系的影响都是微妙且深远的。这种影响并不仅是执行 Object.setPrototypeOf()语句那么简单,而是会涉及所有访问了那些修改过[[Prototype]]的对象的代码。”
为了避免使用Object.setPrototypeOf()可能造成的性能下降,可以通过Object.create()来创建一个新对象,同时为其指定原型

let biped = {
 numLegs: 2
};
let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true 

ES6中Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)(instanceof操作符优先使用该Symbol对应的属性)。参考链接
MDN上的解释: Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
大多数 class 没有 Symbol.hasInstance。在这种情况下,是使用 obj instanceof Class 检查 Class.prototype 是否等于 obj 的原型链中的原型之一

1、

class myArr {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof myArr); // true

2、

class MyArr extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}

var a = new MyArr(1, 2, 3);
console.log(a.map(v => v) instanceof MyArr);     // false;
console.log(a.map(v => v) instanceof Array);     // true;

3、

Function[Symbol.hasInstance](Function); // true
Function[Symbol.hasInstance](String); // true
Function[Symbol.hasInstance](Array); // true
Function[Symbol.hasInstance](Number); // true

Array[Symbol.hasInstance]([]); // true
Number[Symbol.hasInstance](1); // false

3、一些用法

1、

function Foo() {} 
let f = new Foo(); 
console.log(Foo[Symbol.hasInstance](f)); // true
console.log(f instanceof Foo); // true

2、检测Symbol

var sym = Symbol("test");
console.log(sym instanceof Symbol); // false

var symObj = Object(sym); // 需要使用封装对象形式的symObj,而不是原生形式的sym的情况很少
console.log(symObj instanceof Symbol); // true
console.log(symObj.valueOf() === sym); // true

3、

try {
  fun();
} catch (error) {
  if (error instanceof TypeError) {
  
  } else if (error instanceof ReferenceError) {
  	
  }
}

4、

function A () {};
var a = new A;
console.log(a instanceof A); // true

Object.defineProperty(A, Symbol.hasInstance, {
    value: function () { return false; }
})
console.log(a instanceof A) // false

5、关于是否是Promise

instanceof 不足以检查 Promise,Promise 值可能是从其他浏览器窗口接收到的,然而接收到的 Promise 可能和当前窗口的框架不同,因此不能识别 Promise 实例。 库或者框架可能会实现自己的 Promise ,不是使用 原生 ES6 Promise 实现,所以无法检查。下面的代码看起来是可行的。

let a = Promise.resolve(1); 
a instanceof Promise; // true

参考is-promise npm地址 github地址
源码里面是这样的

function isPromise(obj) {
  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}

4、小结

  • instanceof操作符可以用来确定一个对象实例的原型链上是否有原型XX
  • instanceof的实现,是通过对象上的一层一层的__proto__去查找最后一个指向的原型对象是啥。es6中是优先使用Symbol.hasInstance去判断某对象是否是构造器的实例。不要随便使用__proto__非标准属性。要操作原型请使用es6提供的方法。使用Object.setPrototypeOf()修改继承关系需要谨慎。

5、参考链接