JavaScript中的instanceof操作符是什么?

第一次遇到JavaScript中的instanceof关键字时,它可能非常令人困惑,因为人们倾向于认为JavaScript不是面向对象的编程语言。

  • 是什么?
  • 它能解决什么问题?
  • 什么时候合适,什么时候不合适?
277183 次浏览

运算符

左手边(LHS)操作数是被测试到右手边(RHS)操作数的实际对象,右手边(RHS)操作数是类的实际构造函数。基本定义是:

检查当前对象,如果对象是指定的对象类型,则返回true。

下面是一些很好的例子和直接取自Mozilla的开发者网站的例子:

var color1 = new String("green");
color1 instanceof String; // returns true
var color2 = "coral"; //no type specified
color2 instanceof String; // returns false (color2 is not a String object)

值得一提的是,如果对象继承自类的原型,instanceof的值为true:

var p = new Person("Jon");
p instanceof Person

这就是p instanceof Person,因为p继承自Person.prototype

根据指挥官的要求

我添加了一个小示例,其中包含一些示例代码和解释。

当你声明一个变量时,你给它一个特定的类型。

例如:

int i;
float f;
Customer c;

上面显示了一些变量,即ifc。类型是integerfloat和用户定义的Customer数据类型。像上面这样的类型可以适用于任何语言,而不仅仅是JavaScript。然而,在JavaScript中,当你声明一个变量时,你不会显式地定义一个类型,var x, x可以是一个数字/字符串/用户定义的数据类型。因此,instanceof所做的是检查对象是否属于指定的类型,因此从上面获取Customer对象,我们可以这样做:

var c = new Customer();
c instanceof Customer; //Returns true as c is just a customer
c instanceof String; //Returns false as c is not a string, it's a customer silly!

上面我们已经看到c是用类型Customer声明的。我们修改了它,并检查了它的类型是否为Customer。当然,它返回true。然后仍然使用Customer对象检查它是否为String。不,肯定不是String我们新建了一个Customer对象而不是String对象。在这种情况下,它返回false。

真的就是这么简单!

//Vehicle is a function. But by naming conventions
//(first letter is uppercase), it is also an object
//constructor function ("class").
function Vehicle(numWheels) {
this.numWheels = numWheels;
}


//We can create new instances and check their types.
myRoadster = new Vehicle(4);
alert(myRoadster instanceof Vehicle);

我认为值得注意的是,instanceof是在声明对象时使用“new”关键字定义的。在JonH的例子中;

var color1 = new String("green");
color1 instanceof String; // returns true
var color2 = "coral";
color2 instanceof String; // returns false (color2 is not a String object)

他没有提到的是;

var color1 = String("green");
color1 instanceof String; // returns false

指定"new"实际上是将String构造函数的结束状态复制到color1变量中,而不仅仅是将其设置为返回值。我认为这更好地展示了new关键字的功能;

function Test(name){
this.test = function(){
return 'This will only work through the "new" keyword.';
}
return name;
}


var test = new Test('test');
test.test(); // returns 'This will only work through the "new" keyword.'
test // returns the instance object of the Test() function.


var test = Test('test');
test.test(); // throws TypeError: Object #<Test> has no method 'test'
test // returns 'test'

使用"new"将函数内的"this"值赋给已声明的var,而不使用它则赋给返回值。

你可以用它来进行错误处理和调试,就像这样:

try{
somefunction();
}
catch(error){
if (error instanceof TypeError) {
// Handle type Error
} else if (error instanceof ReferenceError) {
// Handle ReferenceError
} else {
// Handle all other error types
}
}

到目前为止,instanceof有一个重要的方面似乎没有在任何注释中提到:继承。由于原型继承,使用instanceof计算的变量对于多个“类型”可能返回true。

例如,让我们定义一个类型和一个子类型:

function Foo(){ //a Foo constructor
//assign some props
return this;
}


function SubFoo(){ //a SubFoo constructor
Foo.call( this ); //inherit static props
//assign some new props
return this;
}


SubFoo.prototype = Object.create(Foo.prototype); // Inherit prototype
SubFoo.prototype.constructor = SubFoo;

现在我们有了几个“类”,让我们创建一些实例,并找出它们是什么实例:

var
foo = new Foo()
,   subfoo = new SubFoo()
;


alert(
"Q: Is foo an instance of Foo? "
+   "A: " + ( foo instanceof Foo )
); // -> true


alert(
"Q: Is foo an instance of SubFoo? "
+   "A: " + ( foo instanceof SubFoo )
); // -> false


alert(
"Q: Is subfoo an instance of Foo? "
+   "A: " + ( subfoo instanceof Foo )
); // -> true


alert(
"Q: Is subfoo an instance of SubFoo? "
+   "A: " + ( subfoo instanceof SubFoo )
); // -> true


alert(
"Q: Is subfoo an instance of Object? "
+   "A: " + ( subfoo instanceof Object )
); // -> true

看到最后一行了吗?所有对函数的“new”调用都会返回一个继承自object的对象。即使在使用对象创建简写时也是如此:

alert(
"Q: Is {} an instance of Object? "
+   "A: " + ( {} instanceof Object )
); // -> true

那么“类”定义本身呢?它们是什么实例?

alert(
"Q: Is Foo an instance of Object? "
+   "A:" + ( Foo instanceof Object)
); // -> true


alert(
"Q: Is Foo an instance of Function? "
+   "A:" + ( Foo instanceof Function)
); // -> true

我觉得理解任何对象都可以是MULTIPLE类型的实例是很重要的,因为你可以(错误地)假设你可以通过使用instanceof来区分对象和函数。最后一个例子清楚地显示了函数是一个对象。

如果您正在使用任何继承模式,并希望通过鸭子类型以外的方法确认对象的后代,这也很重要。

希望这能帮助大家探索instanceof

这里的其他答案是正确的,但它们没有涉及instanceof实际上是如何工作的,这可能会引起一些语言律师的兴趣。

JavaScript中的每个对象都有一个原型,可以通过__proto__属性访问。函数也有prototype属性,这是由它们创建的任何对象的初始__proto__。当一个函数被创建时,它被赋予一个prototype的唯一对象。instanceof操作符使用这个唯一性来给你一个答案。如果你把instanceof写成一个函数,它可能是这样的。

function instance_of(V, F) {
var O = F.prototype;
V = V.__proto__;
while (true) {
if (V === null)
return false;
if (O === V)
return true;
V = V.__proto__;
}
}

这基本上是对ECMA-262 5.1版(也称为ES5)第15.3.5.3节的解释。

注意,可以将任何对象重新赋值给函数的prototype属性,也可以在构造对象后重新赋值对象的__proto__属性。这会给你一些有趣的结果:

function F() { }
function G() { }
var p = {};
F.prototype = p;
G.prototype = p;
var f = new F();
var g = new G();


f instanceof F;   // returns true
f instanceof G;   // returns true
g instanceof F;   // returns true
g instanceof G;   // returns true


F.prototype = {};
f instanceof F;   // returns false
g.__proto__ = {};
g instanceof G;   // returns false

在“什么时候合适,什么时候不合适?”这个问题上,我的观点是:

instanceof在生产代码中很少有用,但在测试中很有用,你想断言你的代码返回/创建正确类型的对象。通过明确您的代码返回/创建的对象类型,您的测试作为理解和记录代码的工具变得更加强大。

我刚刚发现了一个真实的应用程序,我想现在会更经常地使用它。

如果你使用jQuery事件,有时你想写一个更通用的函数,也可以直接调用(没有事件)。你可以使用instanceof来检查函数的第一个参数是否是jQuery.Event的实例,并做出适当的反应。

var myFunction = function (el) {
if (el instanceof $.Event)
// event specific code
else
// generic code
};


$('button').click(recalc);    // Will execute event specific code
recalc('myParameter');  // Will execute generic code

在我的例子中,函数需要为所有(通过按钮上的单击事件)或仅为一个特定元素计算一些东西。我使用的代码:

var recalc = function (el) {
el = (el == undefined || el instanceof $.Event) ? $('span.allItems') : $(el);
// calculate...
};

instanceof只是isPrototypeOf的语法糖:

function Ctor() {}
var o = new Ctor();


o instanceof Ctor; // true
Ctor.prototype.isPrototypeOf(o); // true


o instanceof Ctor === Ctor.prototype.isPrototypeOf(o); // equivalent

instanceof依赖于对象构造函数的原型。

构造函数只是一个普通的函数。严格来说,它是一个函数对象,因为在Javascript中所有东西都是对象。这个函数对象有一个原型,因为每个函数都有一个原型。

原型只是一个普通的对象,它位于另一个对象的原型链中。这意味着在另一个对象的原型链中使一个对象成为原型:

function f() {} //  ordinary function
var o = {}, // ordinary object
p;


f.prototype = o; // oops, o is a prototype now
p = new f(); // oops, f is a constructor now


o.isPrototypeOf(p); // true
p instanceof f; // true

instanceof操作符应该避免使用,因为它伪造了Javascript中不存在的类。尽管class关键字也不在ES2015中,因为class只是语法糖…但那是另一个故事了。

是什么?

Javascript是一种原型语言,这意味着它使用原型进行“继承”。instanceof操作符测试构造函数的prototype属性类型是否存在于对象的__proto__链中。这意味着它将执行以下操作(假设testObj是一个函数对象):

obj instanceof testObj;
  1. 检查对象的prototype是否等于构造函数的prototype: obj.__proto__ === testObj.prototype >>如果这是true instanceof将返回true
  2. 会顺着原型链往上爬。例如:obj.__proto__.__proto__ === testObj.prototype >>如果这是trueinstanceof将返回true
  3. 将重复步骤2,直到对象的完整原型被检查。如果对象的原型链上没有与testObj.prototype匹配的对象,则instanceof操作符将返回false

例子:

function Person(name) {
this.name = name;
}
var me = new Person('Willem');


console.log(me instanceof Person); // true
// because:  me.__proto__ === Person.prototype  // evaluates true


console.log(me instanceof Object); // true
// because:  me.__proto__.__proto__ === Object.prototype  // evaluates true


console.log(me instanceof Array);  // false
// because: Array is nowhere on the prototype chain

它能解决什么问题?

它解决了方便地检查对象是否来自某个原型的问题。例如,当一个函数接收到一个可以有各种原型的对象时。然后,在使用原型链中的方法之前,可以使用instanceof操作符检查这些方法是否在对象上。

例子:

function Person1 (name) {
this.name = name;
}


function Person2 (name) {
this.name = name;
}


Person1.prototype.talkP1 = function () {
console.log('Person 1 talking');
}


Person2.prototype.talkP2 = function () {
console.log('Person 2 talking');
}




function talk (person) {
if (person instanceof Person1) {
person.talkP1();
}
  

if (person instanceof Person2) {
person.talkP2();
}
  

  

}


const pers1 = new Person1 ('p1');
const pers2 = new Person2 ('p2');


talk(pers1);
talk(pers2);

talk()函数中,首先检查原型是否位于对象上。在此之后,选择适当的方法来执行。不进行此检查可能导致执行一个不存在的方法,从而导致引用错误。

什么时候合适,什么时候不合适?

我们已经讲过了。当您需要在对对象进行操作之前检查对象的原型时,可以使用它。

@SebastianSimon我最后的答案是8岁(那时我跛了),有可能我写了一些牛逼的东西:)

简单地说,目前我使用instanceof的唯一情况是当我使用class实例时,行为取决于我将接收的类,例如。我想区分404是否为ErrorA(资源不存在)或ErrorB(服务未找到)-库响应代码令人困惑,但我的运气是使用不同的错误类抛出。

我绝对(目前)不会使用它来检查反映原语的类型-你不能确定库是否返回'msg'new String('msg')

它们都有属于String类的方法,因为'msg'原语在内部包装为字符串对象。内部是指通过解释器。它们都是Strings,但instanceof操作符在这里似乎不够用——要检查某事物是基元还是类,我将使用typeof &&instanceof -但仅用于从外部JS库返回的东西。

目前TypeScript正在解决这个问题,你不再需要使用typeofinstanceof这样繁琐的检查。