Co to jest operator instanceof w JavaScript?

313

Słowo instanceofkluczowe w JavaScript może być dość mylące, gdy pojawia się po raz pierwszy, ponieważ ludzie myślą, że JavaScript nie jest obiektowym językiem programowania.

  • Co to jest?
  • Jakie problemy rozwiązuje?
  • Kiedy jest to właściwe, a kiedy nie?
Alon Gubkin
źródło
3
Mimo że poniższe odpowiedzi są bardzo przydatne, nie używasz ich zbyt często (a przynajmniej ja nie) w aplikacjach zawierających prawdziwe słowa. Można by zrobić właściwość object.type, która przechowuje ciąg znaków i sprawdza go.
Omar Al-Ithawi,
4
JS nie ma żadnego sensu: "foo" instanceof String=> fałsz, 1 instanceof Number=> fałsz, {} instanceof Object=> fałsz. Powiedz co?
morbusg
12
@morbusg twój komentarz wprowadza w błąd. Pierwsza "foo" instanceof String => falsejest poprawna, ponieważ typeof "foo" == 'string'. new String("foo") instanceof String => true, ponieważ typeof String == 'function'- należy traktować funkcję jak klasę (definicja klasy). Zmienna staje się instanceofjakąś function(klasą), gdy przypisujesz ją jako var v = new AnythingWhatTypeofEqualsFunction(). To samo dotyczy 1. typeof 1 == 'number'- „liczba” nie jest „funkcją” :) Dalej - {} instanceof Objectjest TRUEw węźle i we współczesnych przeglądarkach
fider
1
@fider: To był komentarz do specyfikacji języka, pochodzący od rubyisty.
morbusg,
@ morbusg - ({}) instanceof Objectpowróci true. W rzeczywistości napisany kod spowoduje błąd.
DDM

Odpowiedzi:

279

wystąpienie

Operand po lewej stronie (LHS) to rzeczywisty testowany obiekt na operand po prawej stronie (RHS), który jest rzeczywistym konstruktorem klasy. Podstawowa definicja to:

Checks the current object and returns true if the object
is of the specified object type.

Oto kilka dobrych przykładów i oto przykład zaczerpnięty bezpośrednio ze strony dewelopera Mozilli :

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)

Jedną rzeczą, o której warto wspomnieć, jest instanceofprawda, jeśli obiekt dziedziczy po prototypie klasy:

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

Jest to p instanceof Personprawdą, ponieważ pdziedziczy po Person.prototype.

Na prośbę PO

Dodałem mały przykład z jakimś przykładem kodu i wyjaśnieniem.

Kiedy deklarujesz zmienną, nadajesz jej określony typ.

Na przykład:

int i;
float f;
Customer c;

Powyższy pokazać kilka zmiennych, a mianowicie i, foraz c. Typy są integer, floati zdefiniowane przez użytkownika Customertyp danych. Typy takie jak powyższe mogą być dla dowolnego języka, nie tylko JavaScript. Jednak w JavaScript, kiedy deklarujesz zmienną, nie definiujesz jawnie typu var x, x może być liczbą / ciągiem / typem danych zdefiniowanym przez użytkownika. Więc co instanceofto jest sprawdzanie obiektu, aby zobaczyć, czy jest on określonego typu, więc z góry biorącCustomer obiekt, który możemy zrobić:

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!

Powyżej widzieliśmy, że czostał zadeklarowany z typem Customer. Zmieniliśmy go i sprawdziliśmy, czy jest typu, Customerczy nie. Pewnie, że zwraca wartość true. Następnie nadal za pomocą Customerobiektu sprawdzamy, czy jest to String. Nie, zdecydowanie nie Stringnowy Customerobiekt nie Stringprzedmiot. W takim przypadku zwraca wartość false.

To naprawdę jest takie proste!

JonH
źródło
@Alon - dodałem dla ciebie przykład. Patrz wyżej, color1 ma ciąg typu, więc kiedy powiesz, color1 instanceof String;że zwróci true, ponieważ color1 jest ciągiem.
JonH
@Alon - Sprawdza obiekt, aby zobaczyć, jaki to typ obiektu. Rozważ obiekt osoby / klienta. Zatem person p = new person()p jest teraz typem osoby, a nie ciągiem znaków.
JonH
W JavaScript JavaScript może być trudny do zrozumienia, że ​​zmienna może elastycznie mieć inny typ przez cały okres życia. Przykładowy kod: jsfiddle.net/sikusikucom/znSPv
Moey
@ Siku-Siku.Com - Nie widzę w tym żadnej sztuczki var zero = 0; alert(zero); zero = "0"; alert(zero), przeszliśmy z prymitywnej intna prymitywną stringbez żadnych problemów.
JonH
Och, miałem na myśli, że zrozumienie („nie robienie” ) tej „elastyczności” w JavaScript, było trudne / nowe , szczególnie. dla niektórych, którzy są przyzwyczajeni do języka, który wymaga np. jawnego rzutowania w celu zmiany typu zmiennej. Jak zauważyłeś, bardzo łatwo jest zmienić typ z np. Pierwotnego intna pierwotny string.
moey
95

Jest instancja ważna, która nie wydaje się być uwzględniona w żadnym z dotychczasowych komentarzy: dziedziczenie. Zmienna oceniana za pomocą instanceof może zwrócić wartość true dla wielu „typów” z powodu dziedziczenia prototypowego.

Na przykład zdefiniujmy typ i podtyp:

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;

Teraz, gdy mamy już kilka „klas”, wykonajmy kilka instancji i dowiedzmy się, co to są:

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

Widzisz tę ostatnią linię? Wszystkie „nowe” wywołania funkcji zwracają obiekt, który dziedziczy po Object. Dzieje się tak nawet w przypadku skróconego tworzenia obiektów:

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

A co z samymi definicjami „klasowymi”? Jakie są ich wystąpienia?

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

Wydaje mi się, że zrozumienie, że każdy obiekt może być instancją typu WIELOKROTNEGO, jest ważne, ponieważ ty (niepoprawnie) zakładasz, że możesz rozróżnić, powiedzieć i obiekt od funkcji za pomocą instanceof. Jak pokazuje ten ostatni przykład, funkcja jest obiektem.

Jest to również ważne, jeśli używasz wzorców dziedziczenia i chcesz potwierdzić potomstwo obiektu metodami innymi niż pisanie kaczką.

Mam nadzieję, że pomoże każdemu odkrywać instanceof.

webnesto
źródło
Jeszcze bardziej niesamowite jest to, że po odziedziczeniu po prototypie SubFoo.prototype = new Foo();możesz dodać do niego więcej metod, a subfoo instanceof Fooczek nadal będzie pozytywny, a takżesubfoo instanceof SubFoo
Cory Danielson
87

Pozostałe odpowiedzi tutaj są poprawne, ale nie zajmują się tym, jak instanceoffaktycznie działa, co może zainteresować niektórych prawników zajmujących się językiem.

Każdy obiekt w JavaScript ma prototyp, dostępny poprzez __proto__właściwość. Funkcje mają także prototypewłaściwość, która jest inicjałem __proto__dla wszystkich tworzonych przez nich obiektów. Gdy funkcja jest tworzona, otrzymuje unikalny obiekt prototype. instanceofOperator wykorzystuje tę niepowtarzalność dać odpowiedź. Oto, co instanceofmoże wyglądać, jeśli napisałeś to jako funkcję.

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__;
  }
}

Jest to w zasadzie parafrazowanie ECMA-262 wydanie 5.1 (znane również jako ES5), sekcja 15.3.5.3.

Zauważ, że możesz ponownie przypisać dowolny obiekt do prototypewłaściwości funkcji i możesz ponownie przypisać __proto__właściwość obiektu po jego zbudowaniu. To da ci kilka interesujących wyników:

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
Jay Conrod
źródło
5
Warto zauważyć, że bezpośrednie manipulowanie właściwością „ __proto__” nie jest dozwolone w IE. Jeśli dobrze pamiętam, bezpośrednia manipulacja właściwością nie jest również uwzględniona w specyfikacji ECMA, więc prawdopodobnie jest to zły pomysł, aby użyć jej do zadań innych niż do celów akademickich.
webnesto
@webnesto, to prawda, proto nie ma w specyfikacji. Nie wiedziałem, że IE go nie obsługuje. Kiedy mówisz o bezpośredniej manipulacji, masz na myśli, że nie jest on w ogóle narażony na kod JS, czy po prostu nie można go zapisać?
Jay Conrod,
2
Nie w 100% pewny w starszych wersjach. Brzmi jak z tego miejsca ( stackoverflow.com/questions/8413505/proto-for-ie9-or-ie10 ), że w IE9 jest co najmniej czytelny (choć niemodyfikowalny). Warto również zauważyć, że wygląda na to, że przeglądarki całkowicie go upuszczają.
webnesto
4
Zrozumienie domniemanego istnienia właściwości proto jest ważne niezależnie od tego, czy jest ona dostępna dla kodu użytkownika, czy nie. +10 gdybym mógł za zacytowanie specyfikacji, właśnie tego tu szukałem.
Mike Edwards,
3
Aby uzyskać prototypowy link, użyj Object.getPrototypeOf(o), będzie on taki sam, jak __proto__opisujesz, ale jest zgodny z ECMAScript.
froginvasion
45

Myślę, że warto zauważyć, że instanceof jest definiowany przez użycie słowa kluczowego „new” podczas deklarowania obiektu. W przykładzie z 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)

Nie wspomniał o tym;

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

Określenie „new” faktycznie skopiowało stan końcowy funkcji konstruktora String do zmiennej color1 var, zamiast po prostu ustawić wartość zwracaną. Myślę, że to lepiej pokazuje, co robi nowe słowo kluczowe;

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'

Użycie „nowego” przypisuje wartość „this” wewnątrz funkcji do zadeklarowanego var, natomiast nieużywanie go przypisuje wartość zwracaną.

Stephen Belanger
źródło
2
Nie ma sensu korzystać newz żadnego z typów JavaScript, co sprawia, że ​​akceptowana odpowiedź jest bardziej myląca dla początkujących. text = String('test')i options = {}nie będą testowane przez, instanceofale raczej z typeofzamiast.
Ryan,
3
Date.getTime () // fail. tak, nowe jest ważne.
Stephen Belanger
1
Spróbuj uruchomić to w konsoli. Pojawi się błąd, ponieważ getTime () nie istnieje. Musisz użyć nowego.
Stephen Belanger,
8

Możesz go użyć do obsługi błędów i debugowania:

try{
    somefunction();
} 
catch(error){
    if (error instanceof TypeError) {
        // Handle type Error
    } else if (error instanceof ReferenceError) {
        // Handle ReferenceError
    } else {
        // Handle all other error types
    }
}
Tarek Saied
źródło
3
//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);
yfeldblum
źródło
3

Co to jest?

JavaScript jest językiem prototypowym, co oznacza, że ​​używa prototypów do „dziedziczenia”. instanceofoperatora sprawdza, czy funkcją konstruktora prototypepropertype obecny jest w __proto__łańcuchu obiektu. Oznacza to, że wykona następujące czynności (zakładając, że testObj jest obiektem funkcji):

obj instanceof testObj;
  1. Sprawdź, czy prototyp obiektu jest równy prototypowi konstruktora: obj.__proto__ === testObj.prototype >> czy to true instanceofzwróci true.
  2. Wspina się po łańcuchu prototypów. Na przykład: obj.__proto__.__proto__ === testObj.prototype >> jeśli to true instanceofwrócitrue .
  3. Powtórzy krok 2 do momentu sprawdzenia pełnego prototypu obiektu. Jeśli nigdzie na łańcuchu prototypów obiektu jest dopasowane testObj.prototypenastępnie instanceofoperator powróci false.

Przykład:

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

Jakie problemy rozwiązuje?

Rozwiązano problem wygodnego sprawdzania, czy obiekt pochodzi z określonego prototypu. Na przykład, gdy funkcja odbiera obiekt, który może mieć różne prototypy. Następnie, przed użyciem metod z łańcucha prototypów, możemy użyć instanceofoperatora, aby sprawdzić, czy te metody znajdują się na obiekcie.

Przykład:

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);

Tutaj w talk()funkcji najpierw sprawdza się, czy prototyp znajduje się na obiekcie. Następnie wybiera się odpowiednią metodę do wykonania. Nie wykonanie tej kontroli może spowodować wykonanie metody, która nie istnieje, a tym samym błąd odniesienia.

Kiedy jest to właściwe, a kiedy nie?

W pewnym sensie już to omówiliśmy. Użyj go, gdy chcesz sprawdzić prototyp obiektu przed zrobieniem z nim czegoś.

Willem van der Veen
źródło
1
Myślę, że chciałeś napisać coś innego niż „ prototyp funkcji konstruktora pojawia się gdzieś we właściwości prototypu konstruktora
Bergi
Warto wspomnieć, że właściwsze byłoby, gdyby osoby korzystały z interfejsu i nadawały nazwy obu metodom PersonX.prototype.talk, aby ta talkfunkcja mogła po prostu to zrobić person.talk().
Bergi,
Miałeś całkowitą rację, zaktualizowałem go, więc definicja jest lepsza. Dzięki za wskazanie!
Willem van der Veen,
Nie używaj także __proto__w dokumentacji, jest przestarzała - Object.getPrototype()zamiast tego napisz
Bergi,
1

Na pytanie „Kiedy jest to właściwe, a kiedy nie?”, Moje 2 centy:

instanceofjest rzadko przydatny w kodzie produkcyjnym, ale przydatny w testach, w których chcesz stwierdzić, że Twój kod zwraca / tworzy obiekty poprawnych typów. Wyrażając się jasno o rodzajach obiektów, które kod zwraca / tworzy, twoje testy stają się potężniejsze jako narzędzie do zrozumienia i dokumentowania kodu.

Andrew Magee
źródło
1

instanceofjest tylko cukrem syntaktycznym dla 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 zależy tylko od prototypu konstruktora obiektu.

Konstruktor jest po prostu normalną funkcją. Ściśle mówiąc, jest to obiekt funkcji, ponieważ wszystko jest obiektem w JavaScript. I ten obiekt funkcji ma prototyp, ponieważ każda funkcja ma prototyp.

Prototyp to po prostu normalny obiekt, który znajduje się w łańcuchu prototypów innego obiektu. Oznacza to, że będąc w łańcuchu prototypów innego obiektu tworzy obiekt do prototypu:

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

instanceofOperator należy unikać, ponieważ podróbki klas, które nie istnieją w JavaScript. Pomimo tego classsłowa kluczowego nie w ES2015, ponieważ classjest to po prostu cukier składniowy dla ... ale to inna historia.


źródło
1
Pierwszy wiersz jest niepoprawny! Zobacz tutaj na dłużej: „isPrototypeOf () różni się od operatora instanceof W wyrażeniu« Obiekt jest instancją AFunction», łańcuch prototyp przedmiot jest sprawdzany przed AFunction.prototype, a nie przeciwko samej AFunction”.
Yan Foto
1
@ Yan I nadal instanceofpochodzi od isPrototypeOf. Nazywam ten cukier składniowy. A twoje źródło MDN to żart, prawda?
Nie. To nie jest i nie powinien to być żart, ale wciąż zły link. Miałem to zamieścić. Nie chcę wchodzić w szczegóły techniczne, ale instanceof nie robię tego samego, co isPrototypeOf. Otóż ​​to.
Yan Foto
0

Właśnie znalazłem aplikację z prawdziwego świata i myślę, że teraz będzie z niej częściej korzystać.

Jeśli używasz zdarzeń jQuery, czasami chcesz napisać bardziej ogólną funkcję, która może być również wywoływana bezpośrednio (bez zdarzenia). Możesz użyć, instanceofaby sprawdzić, czy pierwszy parametr funkcji jest instancją jQuery.Eventi odpowiednio zareagować.

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

W moim przypadku funkcja potrzebowała obliczyć coś dla wszystkich (poprzez zdarzenie kliknięcia na przycisku) lub tylko dla jednego określonego elementu. Kod, którego użyłem:

var recalc = function (el) { 
    el = (el == undefined || el instanceof $.Event) ? $('span.allItems') : $(el);
    // calculate...
};
jussty
źródło