Co dzieje się w tym kodzie, gdy obiekty Number przechowują właściwości i zwiększają liczbę?

246

Niedawny tweet zawierał ten fragment kodu JavaScript.

Czy ktoś może wyjaśnić, co się w nim dzieje krok po kroku?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

W szczególności nie jest dla mnie jasne:

  • dlaczego wynikiem dis.call(5)jest Numberjakaś [[PrimitiveValue]]właściwość, ale wyniki five++i five * 5wydają się być zwykłymi liczbami 5i 25(nie Numbers)
  • dlaczego five.wtfwłaściwość znika po five++zwiększeniu
  • dlaczego five.wtfwłaściwość nie jest już nawet możliwa do ustawienia po five++inkrementie, mimo że five.wtf = 'potato?'przypisanie najwyraźniej ustawia wartość.
Nathan Long
źródło
6
Haha, widziałem ten tweet !! To dziwne, myślę, że ponieważ pomnożymy go, nie wpływa to ++
całkowicie
8
Której linii nie zrozumiałeś? Prea Postprzyrost? Po pomnożeniu, znowu jest to Numberobiekt, który nie ma wtfwłaściwości ... Ale wciąż jest i objectdlatego może mieć properties..
Rayon
25
Podczas rozmowy telefonicznej disz dis.call(5)tego owija numer prymitywnego 5do obiektu typu Number, który zawiera 5, tak, że ten obiekt może być zwrócony jako thisobiekt. W ++konwertuje go z powrotem do pierwotnej liczby, które nie mogą zawierać właściwości, więc wtfzaczyna być niezdefiniowana.
GSerg
5
@Rayon ... i to się zmieniło wraz z ES6 . W związku z tym cała ta sprawa przestanie się dziać.
GSerg

Odpowiedzi:

278

OP tutaj. Zabawnie jest to zobaczyć w przepełnieniu stosu :)

Przed przejściem przez zachowanie ważne jest wyjaśnienie kilku rzeczy:

  1. Wartość liczbowa i obiekta = 3 liczbowy ( vs a = new Number(3)) są bardzo różne. Jeden jest prymitywny, drugi jest przedmiotem. Nie możesz przypisywać atrybutów do prymitywów, ale możesz do obiektów.

  2. Przymus między nimi jest niejawny.

    Na przykład:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
  3. Każde wyrażenie ma wartość zwracaną. Gdy REPL odczytuje i wykonuje wyrażenie, wyświetla się to. Zwracane wartości często nie oznaczają tego, co myślisz, i sugerują rzeczy, które po prostu nie są prawdą.

Ok, zaczynamy.

Oryginalny obraz kodu JavaScript

Przysięga.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Zdefiniuj funkcję disi wywołaj ją za pomocą 5. Spowoduje to wykonanie funkcji 5jako jako kontekst ( this). Tutaj jest on wymuszany z wartości Number na obiekt Number. Bardzo ważne jest, aby pamiętać, że gdybyśmy byli w trybie ścisłym, tak by się nie stało .

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Teraz ustawiamy atrybut five.wtfna 'potato', a mając pięć jako obiekt, na pewno akceptuje proste przypisanie .

> five * 5
25
> five.wtf
'potato'

Ze fivejako przedmiot, to zapewnić to może jeszcze wykonywać proste operacje arytmetyczne. To może. Czy jego atrybuty nadal się utrzymują? Tak.

Zakręt.

> five++
5
> five.wtf
undefined

Teraz sprawdzamy five++. Sztuczka z przyrostem postfiksowym polega na tym, że całe wyrażenie będzie oceniać względem oryginalnej wartości, a następnie zwiększać wartość. Wygląda na fiveto, że wciąż jest pięć, ale tak naprawdę wyrażenie zostało ocenione na pięć, a następnie ustawione fivena 6.

Nie tylko został fiveustawiony na 6, ale został zmuszony z powrotem do wartości liczbowej i wszystkie atrybuty zostały utracone. Ponieważ prymitywy nie mogą przechowywać atrybutów, five.wtfjest niezdefiniowane.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

Ponownie próbuję ponownie przypisać atrybut wtfdo five. Zwracana wartość sugeruje, że się trzyma, ale w rzeczywistości tak nie fivejest, ponieważ jest wartością liczbową, a nie obiektem liczbowym. Wyrażenie ocenia na 'potato?', ale kiedy sprawdzimy, widzimy, że nie zostało przypisane.

Prestiż.

> five
6

Od czasu przyrostu postfiksa fivejest 6.

Mateusz
źródło
70
Oglądasz się uważnie?
Waddles,
3
@Nathan Long Cóż, w Javie, którą JavaScript mocno pożyczył na początku, wszystkie prymitywy mają odpowiedniki klasowe. inti Integerna przykład. Zakładam, że jest tak, abyś mógł stworzyć funkcję doSomething(Object)i nadal być w stanie nadać jej prymityw. W tym przypadku prymitywy zostaną przekonwertowane na odpowiadające im klasy. Ale JS tak naprawdę nie dba o typy, więc powodem jest prawdopodobnie coś innego
Suppen,
4
@Eric Pierwszą rzeczą ++jest zastosowanie ToNumber do wartości . Porównaj podobny przypadek z ciągami: jeśli masz x="5", to x++zwraca liczbę 5.
apsillers
2
Naprawdę cała magia się dzieje dis.call(5), przymus sprzeciwu, nigdy bym się tego nie spodziewał.
mcfedr
3
@gman oprócz tego, że istnieje wiele bardziej szczegółowych wpływów, w tym nazywanie dużych fragmentów jego standardowej biblioteki, zachowanie standardowych typów obiektów, a nawet fakt, że używa składni nawiasów klamrowych i średnikowych (nawet jeśli średniki są opcjonalne) wynika z faktu, że został on zaprojektowany tak, aby był znany programistom Java. Tak, jest to mylące dla początkujących. To nie znaczy, że wpływy nie istnieją.
Jules
77

Istnieją dwa różne sposoby przedstawienia liczby:

var a = 5;
var b = new Number(5);

Pierwszy to prymitywny, drugi obiekt. Pod każdym względem oba cele zachowują się tak samo, z wyjątkiem tego, że wyglądają inaczej po wydrukowaniu na konsoli. Jedną ważną różnicą jest to, że jako obiekt new Number(5)akceptuje nowe właściwości tak jak każda zwykła {}, podczas gdy prymityw 5nie:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

Jeśli chodzi o dis.call(5)część początkową , zobacz Jak działa słowo kluczowe „to”? . Powiedzmy, że jako argument callużyto pierwszego argumentu thisi że ta operacja zmusza liczbę do bardziej złożonej Numberpostaci obiektu. * Później ++wymusza powrót do pierwotnej postaci, ponieważ operacja dodawania +powoduje powstanie nowej prymitywy.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

NumberPrzedmiotem przyjmuje nowe właściwości.

> five++

++skutkuje nową prymitywną 6wartością ...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

... który nie ma i nie akceptuje niestandardowych atrybutów.

* Należy zauważyć, że w trybie ścisłymthis argumentu będzie traktowana inaczej i że nie można przekształcić w Number. Zobacz http://es5.github.io/#x10.4.3, aby uzyskać szczegółowe informacje dotyczące implementacji.

deceze
źródło
2
@Pharap na pewno lepiej w C / C ++, gdzie możesz to zrobić #define true false. Lub w Javie, gdzie możesz po prostu przedefiniować, co oznaczają liczby. To są dobre, solidne języki. W rzeczywistości każdy język ma podobne „sztuczki”, w których uzyskuje się wynik, który działa zgodnie z przeznaczeniem, ale może wyglądać dziwnie.
VLAZ
1
@Vld Ok, pozwól mi to przeformułować, dlatego nienawidzę pisania kaczek.
Pharap
59

W świecie JavaScript istnieje przymus - kryminał

Nathan, nie masz pojęcia, co odkryłeś.

Badam to od tygodni. Wszystko zaczęło się w burzową noc w październiku ubiegłego roku. Przypadkowo natknąłem się na Numberklasę - to znaczy, dlaczego na świecie JavaScript ma Numberklasę?

Nie byłem przygotowany na to, co zamierzam się potem dowiedzieć.

Okazuje się, że JavaScript, nie mówiąc ci, zmienia twoje liczby na obiekty, a twoje obiekty na liczby tuż pod twoim nosem.

JavaScript miał nadzieję, że nikt go nie złapie, ale ludzie zgłaszali dziwne, nieoczekiwane zachowanie, a teraz dzięki tobie i twojemu pytaniu mam dowody, że potrzebuję szeroko rozwiać tę sprawę.

Właśnie tego się dowiedzieliśmy. Nie wiem, czy powinienem ci to mówić - możesz wyłączyć JavaScript.

> function dis() { return this }
undefined

Kiedy stworzyłeś tę funkcję, prawdopodobnie nie miałeś pojęcia, co będzie dalej. Wszystko wyglądało dobrze i wszystko było w porządku - na razie.

Brak komunikatów o błędach, tylko słowo „niezdefiniowane” w danych wyjściowych konsoli, dokładnie tego, czego można oczekiwać. W końcu była to deklaracja funkcji - nie powinna niczego zwracać.

Ale to był dopiero początek. Co stało się potem, nikt nie mógł przewidzieć.

> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Tak, wiem, spodziewałeś się 5, ale nie to masz, czy to - masz coś innego - coś innego.

To samo przytrafiło mi się.

Nie wiedziałam, co z tym zrobić. Doprowadzało mnie to do szału. Nie mogłem spać, nie mogłem jeść, próbowałem go wypić, ale żadna ilość Mountain Dew nie sprawi, że zapomnę. To po prostu nie miało sensu!

Właśnie wtedy dowiedziałem się, co tak naprawdę się dzieje - był to przymus i działo się to na moich oczach, ale byłem zbyt ślepy, aby to zobaczyć.

Mozilla próbowała go zakopać, umieszczając go tam, gdzie, jak wiedzieli, nikt by nie spojrzał - ich dokumentację .

Po godzinach rekurencyjnego czytania i ponownego czytania oraz ponownego czytania znalazłem to:

„... a pierwotne wartości zostaną przekonwertowane na obiekty”.

Było to jasne, jak można napisać czcionką Open Sans. To była call()funkcja - jak mogłam być tak głupia ?!

Mój numer wcale nie był już liczbą. W momencie, w którym to przekazałem call(), stało się coś innego. Stało się ... przedmiotem.

Na początku nie mogłem w to uwierzyć. Jak to może być prawda? Ale nie mogłem zignorować dowodów, które gromadziły się wokół mnie. Właśnie tam, jeśli tylko spojrzysz:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtfmiał rację. Liczby nie mogą mieć właściwości niestandardowych - wszyscy to wiemy! To pierwsza rzecz, której uczą cię w akademii.

Powinniśmy byli wiedzieć, kiedy zobaczyliśmy wyjście konsoli - nie był to numer, który naszym zdaniem był. To był oszust - przedmiot, który sam się podaje jako nasza słodka niewinna liczba.

To było ... new Number(5).

Oczywiście! To miało doskonały sens. call()miał pracę do wykonania, musiał wywołać funkcję, a żeby to zrobić, musiał się zaludnić this, wiedział, że nie może tego zrobić liczbą - potrzebował przedmiotu i był gotów zrobić wszystko, aby go zdobyć, nawet jeśli to oznaczałoby wymuszenie naszego numeru. Kiedy call()zobaczył numer 5, zobaczył okazję.

To był idealny plan: poczekaj, aż nikt nie będzie patrzył, i zamień nasz numer na obiekt, który wygląda dokładnie tak. Otrzymujemy numer, funkcja zostaje wywołana i nikt nie byłby mądrzejszy.

To był naprawdę idealny plan, ale jak wszystkie plany, nawet te idealne, była w nim dziura i mieliśmy się w to wpaść.

Widzisz, call()nie rozumiałem, że nie był jedynym w mieście, który mógł zmusić liczby. To był JavaScript - przymus był wszędzie.

call() wziąłem mój numer i nie zamierzałem przestać, dopóki nie zdejmę maski z jego małego oszusta i nie wystawię go całej społeczności Stack Overflow.

Ale jak? Potrzebowałem planu. Pewnie, że wygląda jak liczba, ale wiem, że nie, musi istnieć sposób, aby to udowodnić. Otóż ​​to! To wygląda jak liczba, ale może ona działać jak jeden?

Powiedziałem, fiveże potrzebuję, aby stał się 5 razy większy - nie zapytał dlaczego i nie wyjaśniłem. Zrobiłem wtedy to, co zrobiłby każdy dobry programista: pomnożyłem. Z pewnością nie było mowy, by mógł to zrobić.

> five * 5
25
> five.wtf
'potato'

Cholera! Nie tylko fivepomnażanie wtfbyło w porządku . Cholera, ten facet i jego ziemniak.

Co się do cholery działo? Czy myliłem się co do tej całej sprawy? Czy to fivenaprawdę liczba? Nie, muszę coś przeoczyć, wiem o tym, muszę zapomnieć o czymś tak prostym i podstawowym, że zupełnie tego nie zauważam.

To nie wyglądało dobrze, pisałem tę odpowiedź od wielu godzin i nadal nie byłem bliżej wyrażenia swojej opinii. Nie mogłem tego utrzymać, w końcu ludzie przestaliby czytać, musiałem coś wymyślić i szybko o tym pomyśleć.

Czekaj, to wszystko! fivenie było 25, wynik był 25, 25 to zupełnie inna liczba. Oczywiście, jak mogłem zapomnieć? Liczby są niezmienne. Po pomnożeniu 5 * 5nic nie zostaje przypisane do niczego, tylko tworzysz nowy numer 25.

Tak musi się tutaj dziać. Jakoś, kiedy się mnożę five * 5, fivemusi być zmuszany do uzyskania liczby i ta liczba musi być tą, która jest używana do mnożenia. To wynik tego mnożenia drukowanego na konsoli, a nie sama wartość five. fivenigdy nie zostaje mu przypisane - więc oczywiście to się nie zmienia.

Więc w jaki sposób mogę fiveprzypisać sobie wynik operacji. Mam to. Zanim fivenawet zdążyłem pomyśleć, krzyknąłem „++”.

> five++
5

Aha! Miałem go! Każdy wie, 5 + 1to 6był to dowód, że muszę wystawiać, że fivenie był to numer! To był oszust! Zły oszust, który nie umiał liczyć. I mógłbym to udowodnić. Oto jak działa liczba rzeczywista:

> num = 5
5
> num++
5

Czekać? Co tu się działo? westchnienie tak mnie pochwyciło, fiveże zapomniałem, jak działają operatorzy pocztowi. Kiedy używam ++na końcu five, mówię: zwróć bieżącą wartość, a następnie zwiększ five. Jest to wartość przed operacją, która jest drukowana na konsoli. numbyło w rzeczywistości 6i mogłem to udowodnić:

>num
6

Czas zobaczyć, co fivenaprawdę było:

>five
6

... dokładnie tak powinno być. fivebyło dobre - ale byłem lepszy. Gdyby fivenadal był przedmiotem, oznaczałby, że nadal miałby własność, wtfa ja byłbym skłonny postawić wszystko, czego nie miał.

> five.wtf
undefined

Aha! Miałem rację. Miałem go! fivebył teraz liczbą - nie był już przedmiotem. Wiedziałem, że ten sposób pomnożenia nie uratuje go tym razem. Zobacz five++jest naprawdę five = five + 1. W przeciwieństwie do mnożenia ++operator przypisuje wartość do five. Mówiąc dokładniej, przypisuje mu wyniki, five + 1których tak jak w przypadku mnożenia zwraca nową niezmienną liczbę .

Wiedziałem, że go mam i po prostu upewniłem się, że nie będzie mógł się z niego wydostać. Miałem jeszcze jeden test w rękawie. Gdybym miał rację i fivenaprawdę był liczbą, to nie zadziałałoby:

> five.wtf = 'potato?'
'potato?'

Tym razem nie zamierza mnie oszukać. Wiedziałem, potato?że zostanie wydrukowany na konsoli, ponieważ to wynik zadania. Prawdziwe pytanie brzmi: czy wtfnadal tam będzie?

> five.wtf
undefined

Tak jak podejrzewałem - nic - ponieważ liczbom nie można przypisać właściwości. Dowiedzieliśmy się, że pierwszy rok w akademii;)

Dzięki Nathan. Dzięki twojej odwadze w zadawaniu tego pytania mogę w końcu odłożyć to wszystko za siebie i przejść do nowej sprawy.

Jak ten o funkcji toValue(). Drogi Boże. Nieee!

Luis Perez
źródło
9
Zapomnij o tym Jake; to Javascript.
Seth
Dlaczego nazywamy funkcje konstruktora „klasami” w JS?
evolutionxbox
27
01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01deklaruje funkcję, disktóra zwraca obiekt kontekstu. Co thisoznacza zmiany w zależności od tego, czy używasz trybu ścisłego, czy nie. Cały przykład ma inne wyniki, jeśli funkcja została zadeklarowana jako:

> function dis() { "use strict"; return this }

Jest to szczegółowo opisane w sekcji 10.4.3 specyfikacji ES5

  1. Jeśli kod funkcji jest ścisłym kodem, ustaw ThisBinding na thisArg.
  2. W przeciwnym razie, jeśli thisArg ma wartość NULL lub jest niezdefiniowany, ustaw ThisBinding na obiekt globalny.
  3. W przeciwnym razie, jeśli Type (thisArg) nie jest Object, ustaw ThisBinding na ToObject (thisArg).

02jest wartością zwracaną deklaracji funkcji. undefinedpowinno być tutaj oczywiste.

03zmienna fivejest inicjalizowana wartością zwracaną dispo wywołaniu w kontekście wartości pierwotnej 5. Ponieważ disnie jest w trybie ścisłym, linia ta jest identyczna z wywołaniem five = Object(5).

04Dziwna Number {[[PrimitiveValue]]: 5}wartość zwracana jest reprezentacją obiektu, który otacza pierwotną wartość5

05właściwość fiveobiektu wtfma przypisaną wartość ciągu'potato'

06 jest wartością zwracaną zadania i powinna być zrozumiała.

07właściwość fiveobiektu wtfjest badana

08jak five.wtfpoprzednio ustawiono 'potato', powraca 'potato'tutaj

09fiveobiekt jest mnożona przez wartość pierwotnej 5. Nie różni się to niczym od żadnego mnożonego obiektu i jest wyjaśnione w sekcji 11.5 specyfikacji ES5 . Na szczególną uwagę zasługuje sposób rzutowania obiektów na wartości liczbowe, co opisano w kilku sekcjach.

9.3 ToNumber :

  1. Niech primValue będzie ToPrimitive (argument wejściowy, wskazówka Liczba).
  2. Return ToNumber (primValue).

9.1 ToPrimitive :

Zwraca domyślną wartość dla obiektu. Domyślna wartość obiektu jest pobierana poprzez wywołanie wewnętrznej metody [[DefaultValue]] obiektu, przekazując opcjonalną wskazówkę PreferredType. Zachowanie metody wewnętrznej [[DefaultValue]] jest zdefiniowane w tej specyfikacji dla wszystkich rodzimych obiektów ECMAScript w 8.12.8 .

8.12.8 [[DefaultValue]] :

Niech valueOf będzie wynikiem wywołania wewnętrznej metody [[Get]] obiektu O z argumentem „valueOf”.

  1. Jeśli IsCallable (valueOf) ma wartość true,

    1. Niech val będzie wynikiem wywołania wewnętrznej metody [[Call]] valueOf, z O jako tą wartością i pustą listą argumentów.
    2. Jeśli val jest pierwotną wartością, zwróć val.

Jest to ogólny sposób stwierdzenia, że ​​funkcja obiektu valueOfzostaje wywołana, a wartość zwracana z tej funkcji jest używana w równaniu. Jeśli chcesz zmienić valueOffunkcję, możesz zmienić wyniki operacji:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10ponieważ funkcja fives valueOfpozostała niezmieniona, zwraca opakowaną pierwotną wartość 5, aby five * 5oceniać, w 5 * 5którym wyniku25

11właściwość fiveobiektu wtfjest oceniana ponownie, mimo że nie uległa zmianie od momentu przypisania 05.

12 'potato'

13Postfix Przyrost Operator jest wywoływana five, która pobiera wartość liczbową ( 5omówiliśmy jak wcześniej), przechowuje wartość tak, że może zostać zwrócone, dodaje 1się do wartości ( 6) przypisuje wartość fivei zwraca zapisana wartość ( 5)

14 tak jak poprzednio, zwracana wartość jest wartością przed jej zwiększeniem

15dostępna jest wtfwłaściwość pierwotnej wartości ( 6) przechowywanej w zmiennej five. Sekcja 15.7.5 specyfikacji ES5 określa to zachowanie. Liczby pobierają właściwości z Number.prototype.

16 Number.prototypenie ma wtfwłaściwości, więc undefinedjest zwracany

17 five.wtfma przypisaną wartość 'potato?'. Przypisanie jest zdefiniowane w 11.13.1 specyfikacji ES5 . Zasadniczo przypisana wartość jest zwracana, ale nie jest przechowywana.

18 'potato?' został zwrócony przez operatora przypisania

19ponownie five, który ma wartość, 6jest uzyskiwany i ponownie Number.prototypenie ma wtfwłaściwości

20 undefined jak wyjaśniono powyżej

21 five jest dostępny

22 6 jest zwracany jak wyjaśniono w 13

zzzzBov
źródło
17

To całkiem proste.

function dis () { return this; }

Zwraca thiskontekst. Jeśli tak call(5), przekazujesz numer jako obiekt.

callFunkcja nie dostarcza argumentów, pierwszy argument podać jest kontekst this. Zwykle, jeśli chcesz, aby było kontekstowe, dajesz {}tak dis.call({}), co oznacza, thisże funkcja jest pusta this. Jednak jeśli zdasz 5, wydaje się, że zostanie przekonwertowany na obiekt. Zobacz .call

Więc powrót jest object

Gdy to zrobisz five * 5, JavaScript widzi obiekt fivejako typ pierwotny, więc jest równoważny z 5 * 5. Co ciekawe, zrób '5' * 5to, nadal jest równy 25, więc JavaScript wyraźnie rzuca się pod maską. W fivetym wierszu nie wprowadza się żadnych zmian w typie bazowym

Ale kiedy to zrobisz ++, przekształci obiekt w numbertyp pierwotny, usuwając w ten sposób .wtfwłaściwość. Ponieważ wpływasz na typ bazowy

Callum Linington
źródło
Zwrot to liczba .
GSerg
++konwertuje i przypisuje go z powrotem. ++jest równy variable = variable + 1. Więc przegrywaszwtf
Rajesh
*Vs ++nie mylić mnie w ogóle. Posiadanie funkcji, która nie oczekuje żadnych argumentów i zwraca this, posiadanie tej funkcji i tak przyjmuje argument i zwraca coś, co jest, ale nie jest dokładnie argumentem - to nie ma dla mnie sensu.
Nathan Long
Zaktualizowałem @NathanLong, aby pokazać, co dis.call()robi.
Callum Linington
5 ++ zachowuje się tak samo jak w języku C, więc nic dziwnego. thisjest po prostu wskaźnikiem do obiektu, więc typy pierwotne są domyślnie konwertowane. Dlaczego funkcja bez argumentów nie może mieć przynajmniej kontekstu? Pierwszy argument calllub bindsłuży do ustawienia kontekstu. Ponadto funkcje są zamknięciami, co oznacza, że ​​mają dostęp do więcej niż tylkoarguments
Rivenfall
10

Wartości pierwotne nie mogą mieć właściwości. Ale gdy próbujesz uzyskać dostęp do właściwości o wartości pierwotnej, przezroczyste przejście do tymczasowego obiektu Number.

Więc:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6
Techniv
źródło
8

Funkcja deklaracji dis. Funkcja zwraca kontekst

function dis() { return this }
undefined

Zadzwoń disz kontekstem 5. Wartości pierwotne są umieszczane w ramkach, gdy są przekazywane jako kontekst w trybie ścisłym ( MDN ). Więc fiveteraz jest obiekt (numer w pudełku).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Deklaruj wtfwłaściwość fivezmiennej

five.wtf = 'potato'
"potato"

Wartość five.wtf

five.wtf
"potato"

fivejest zapakowany 5, więc jest to jednocześnie numer i obiekt (5 * 5 = 25). To się nie zmienia five.

five * 5
25

Wartość five.wtf

five.wtf
"potato"

Rozpakowanie fivetutaj. fiveteraz jest po prostu prymitywny number. Drukuje 5, a następnie dodaje 1do five.

five++
5

fivejest liczbą pierwotną 6, nie ma w niej żadnych właściwości.

five.wtf
undefined

prymitywy nie mogą mieć właściwości, nie można tego ustawić

five.wtf = 'potato?'
"potato?"

nie możesz tego przeczytać, ponieważ nie zostało ustawione

five.wtf
undefined

fiveto 6z powodu wyżej po inkrementacji

five
6
Maxx
źródło
7

Po pierwsze wygląda na to, że jest uruchamiany przez konsolę nodejs.

1.

    function dis() { return this }

tworzy funkcję dis (), ale ponieważ nie została ustawiona jako a, varnie było żadnej wartości do zwrócenia, podobnie undefinedjak wynik, mimo że dis()został zdefiniowany. Z boku thisnie został zwrócony, ponieważ funkcja nie została wykonana.

2)

    five = dis.call(5)

Powrócisz javascript na Numberprzedmiot, ponieważ po prostu ustawić funkcję dis()„s thiswartość prymitywnego pięciu.

3)

   five.wtf = 'potato'

Pierwsze powroty "potato", bo po prostu ustawić właściwość wtfo fivecelu 'potato'. JavaScript zwraca wartość zmiennej można ustawić, dzięki czemu jest łatwy do wielu zmiennych łańcuchowych i ustawić je na taką samą wartość jak ten: a = b = c = 2.

4

    five * 5

Zwraca to, 25ponieważ właśnie pomnożyłeś pierwotną liczbę 5do five. Wartość fivezostała określona przez wartość Numberobiektu.

5

    five.wtf

Wcześniej pomijałem tę linię, ponieważ chciałbym ją tutaj powtórzyć. Zwraca tylko wartość właściwości wtfustawionej powyżej.

6.

    five++

Jak powiedział @Callum, ++skonwertuje typ na numbertę samą wartość z obiektu Number {[[PrimitiveValue]]: 5}}.

Ponieważ fivejest to number, nie możesz już ustawiać dla niego właściwości, dopóki nie zrobisz czegoś takiego:

    five = dis.call(five)
    five.wtf = "potato?"

lub

    five = { value: 6, wtf: "potato?" }

Zauważ też, że drugi sposób będzie Numberdziałał inaczej niż przy użyciu pierwszej metody, ponieważ definiuje obiekt ogólny zamiast obiektu, który został wcześniej utworzony.

Mam nadzieję, że to pomaga, javascript lubi zakładać różne rzeczy, więc może stać się mylący przy przechodzeniu z Numberobiektu na prymityw number. Możesz sprawdzić, co to jest za pomocą typeofsłowa kluczowego, pisząc typ pięć po zainicjowaniu zwraca 'object'i po wykonaniu five++zwraca 'number'.

@deceze bardzo dobrze opisuje różnicę między obiektem Number a liczbą pierwotną.

Patrick Barr
źródło
6

Zakresy JavaScript są wykonane z kontekstów wykonania. Każdy kontekst wykonania ma środowisko leksykalne (wartości o zasięgu zewnętrznym / globalnym), środowisko zmienne (wartości o zasięgu lokalnym) i to powiązanie .

Tego wiązania jest bardzo ważną częścią kontekstu wykonania. Użycie calljest jednym ze sposobów zmiany tego powiązania , a to spowoduje automatyczne utworzenie obiektu do wypełnienia powiązania.

Function.prototype.call () (z MDN)

Składnia
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
Wartość tego pod warunkiem wezwania do zabawy. Zauważ, że może to nie być rzeczywista wartość postrzegana przez metodę: jeśli metoda jest funkcją w trybie trybu nie ścisłego, wartości puste i niezdefiniowane zostaną zastąpione obiektem globalnym, a wartości pierwotne zostaną przekształcone w obiekty . (moje podkreślenie)

Gdy okaże się, że 5 jest przekształcane new Number(5), reszta powinna być raczej oczywista. Zauważ, że inne przykłady również będą działać, o ile są prymitywnymi wartościami.

function primitiveToObject(prim){
  return dis.call(prim);
}
function dis(){ return this; }

//existing example
console.log(primitiveToObject(5));

//Infinity
console.log(primitiveToObject(1/0));

//bool
console.log(primitiveToObject(1>0));

//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />

wprowadź opis zdjęcia tutaj

Travis J
źródło
4

Kilka pojęć wyjaśnia, co się dzieje

5 jest liczbą, pierwotną wartością

Number {[[PrimitiveValue]]: 5} jest instancją Number (nazwijmy to wrapperem obiektów)

Ilekroć uzyskujesz dostęp do właściwości / metody pierwotnej wartości, silnik JS utworzy opakowanie obiektu odpowiedniego typu ( Numberfor 5, Stringfor 'str'i Booleanfor true) i rozwiąże wywołanie dostępu / metody właściwości dla tego opakowania obiektu. Tak się dzieje, kiedy to robisztrue.toString() na przykład .

Podczas wykonywania operacji na obiektach są one konwertowane na prymitywne wartości (za pomocą toStringlub valueOf) w celu rozwiązania tych operacji - na przykład podczas wykonywania

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

stringodbędzie się konkatenacji ciąg mystri obj.toString()i numberobejmie dodawanie 3iobj.valueOf() .

Teraz wszystko razem

five = dis.call(5)

dis.call(5)zachowuje się tak, (5).dis()jakby 5rzeczywiście miał tę metodę dis. W celu rozwiązania wywołania metody tworzone jest opakowanie obiektu, a wywołanie metody jest na nim rozstrzygane. W tym momencie pięć punktów otacza obiekt wokół pierwotnej wartości 5.

five.wtf = 'potato'

Ustawienie właściwości obiektu, nic szczególnego.

five * 5

W rzeczywistości five.valueOf() * 5uzyskuje to pierwotną wartość z opakowania obiektu. fivenadal wskazuje na obiekt początkowy.

five++

To jest faktycznie five = five.valueOf() + 1. Przed tym wierszem fiveotacza obiekt wokół wartości 5, a po tym punkcie fivezawiera wartość pierwotną6 .

five.wtf
five.wtf = 'potato?'
five.wtf

fivenie jest już przedmiotem. Każda z tych linii tworzy nową instancję Number w celu rozwiązania .wtfdostępu do właściwości. Instancje są niezależne, więc ustawienie właściwości na jednej nie będzie widoczne na drugiej. Kod jest całkowicie równoważny z tym:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
Tibos
źródło