Недавний твит содержал этот фрагмент JavaScript.
Может кто-нибудь объяснить, пожалуйста, что в нем происходит, шаг за шагом?
> 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
В частности, мне не понятно
- почему результатом
dis.call(5)
является aNumber
с каким-то[[PrimitiveValue]]
свойством, но результатомfive++
и,five * 5
кажется, являются просто простые числа5
и25
(неNumber
s) - почему
five.wtf
свойство исчезает послеfive++
приращения - почему
five.wtf
свойство больше даже не может быть установлено послеfive++
приращения, несмотря на то, чтоfive.wtf = 'potato?'
присваивание, очевидно, устанавливает значение.
javascript
Натан Лонг
источник
источник
++
кажется, влияет на базовый типPre
противPost
приращения? После умножения, это сноваNumber
объект, который не обладаетwtf
свойством ... Но, тем не менее, это объект, которыйobject
он может иметьproperties
...dis
с помощьюdis.call(5)
, это оборачивает примитивный номер5
в объект типа,Number
который содержит5
, так что этот объект может быть возвращен какthis
объект. В++
преобразует его обратно в примитивном число , которое не может содержать свойства, такwtf
начинает быть неопределенным.Ответы:
ОП здесь. Забавно видеть это на переполнении стека :)
Прежде чем перейти к поведению, важно уточнить несколько вещей:
Числовое значение и номер объекта (
a = 3
противa = new Number(3)
) очень разные. Один - примитив, другой - объект. Вы не можете назначать атрибуты примитивам, но вы можете назначать объекты.Принуждение между ними подразумевается.
Например:
Каждое выражение имеет возвращаемое значение. Когда REPL читает и выполняет выражение, это то, что он отображает. Возвращаемые значения часто не означают, что вы думаете, и подразумевают вещи, которые просто не соответствуют действительности.
Хорошо, поехали.
Залог.
Определите функцию
dis
и вызовите ее с помощью5
. Это выполнит функцию с5
контекстом (this
). Здесь он приводится от числового значения к числовому объекту. Очень важно отметить, что мы были в строгом режиме этого бы не произошло .Теперь мы устанавливаем атрибут
five.wtf
к'potato'
, и с пятью в качестве объекта, достаточно уверенный он принимает простого присваивания .В
five
качестве объекта я гарантирую, что он все еще может выполнять простые арифметические операции. Это может. Его признаки все еще придерживаются? Да.Поворот.
Сейчас мы проверяем
five++
. Уловка с постфиксным приращением состоит в том, что все выражение будет сравниваться с исходным значением, а затем будет увеличивать значение. Похоже,five
еще пять, но на самом деле выражение оценивается до пяти, а затем установитьfive
в6
.Мало
five
того6
, что был установлен в , но он был приведен обратно в числовое значение, и все атрибуты были потеряны. Поскольку примитивы не могут содержать атрибуты, ониfive.wtf
не определены.Я снова пытаюсь переназначить атрибут
wtf
наfive
. Возвращаемое значение подразумевает, что это придерживается, но это фактически не потому, чтоfive
это числовое значение, а не числовой объект. Выражение оценивается как'potato?'
, но когда мы проверяем, мы видим, что оно не было назначено.Престиж.
С тех пор, как увеличение постфикса,
five
было6
.источник
int
иInteger
, например. Я предполагаю, что это так, что вы можете создать функциюdoSomething(Object)
, и при этом иметь возможность давать ей примитивы. В этом случае примитивы будут преобразованы в соответствующие им классы. Но JS на самом деле не заботится о типах, поэтому причина, вероятно, в чем-то другом++
нужно сделать, это применить ToNumber к значению . Сравните похожий случай со строками: если естьx="5"
, тоx++
возвращает число5
.dis.call(5)
принуждении к объекту, я бы никогда этого не ожидал.Существует два разных способа представления числа:
Первый - примитив, второй - объект. Для всех намерений и целей оба ведут себя одинаково, за исключением того, что они выглядят по-разному при печати на консоли. Одно важное отличие состоит в том, что, как объект,
new Number(5)
принимает новые свойства, как и любой простой{}
, в то время как примитив5
- нет:Что касается начальной
dis.call(5)
части, см. Как работает ключевое слово "this"? , Давайте просто скажем, что первый аргумент tocall
используется в качестве значенияthis
, и что эта операция переводит число в более сложнуюNumber
форму объекта. * Позже++
оно возвращает его обратно в примитивную форму, потому что операция сложения+
приводит к созданию нового примитива.Number
Объект принимает новые свойства.++
приводит к новому примитивному6
значению ...... который не имеет и не принимает пользовательские атрибуты.
* Обратите внимание , что в строгом режиме
this
аргумент будет рассматриваться по- разному , и было бы не быть преобразован вNumber
. См. Http://es5.github.io/#x10.4.3 для подробностей реализации.источник
#define true false
. Или в Java, где вы можете просто переопределить, что означают числа. Это хорошие твердые языки. По правде говоря, у каждого языка есть схожие «хитрости», когда вы получаете результат, который работает как задумано, но может выглядеть странно.В мире JavaScript есть принуждение - детективная история
Натан, ты не представляешь, что ты раскрыл.
Я расследую это уже несколько недель. Все началось бурной ночью в октябре прошлого года. Я случайно наткнулся на
Number
класс - я имею в виду, почему в мире JavaScript былNumber
класс?Я не был готов к тому, что я собирался узнать дальше.
Оказывается, JavaScript, не сказав вам, меняет ваши номера на объекты, а ваши объекты на числа прямо у вас под носом.
JavaScript надеялся, что никто не поймет, но люди сообщают о странном неожиданном поведении, и теперь, благодаря вам и вашему вопросу, у меня есть доказательства того, что мне нужно взорвать эту штуку.
Это то, что мы узнали до сих пор. Я не знаю, должен ли я даже сказать вам это - вы можете отключить JavaScript.
Когда вы создавали эту функцию, вы, вероятно, не знали, что будет дальше. Все выглядело хорошо, и все было хорошо - пока.
Никаких сообщений об ошибках, только слово «undefined» в выводе консоли, именно то, что вы ожидаете. В конце концов, это было объявление функции - оно не должно ничего возвращать.
Но это было только начало. Что произошло дальше, никто не мог предсказать.
Да, я знаю, вы ожидали
5
, но это не то, что вы получили, это было - у вас есть что-то еще - что-то другое.То же самое случилось со мной.
Я не знал, что с этим делать. Это сводило меня с ума. Я не мог спать, я не мог есть, я пытался выпить это, но никакая Горная Роса не заставила бы меня забыть. Это просто не имеет никакого смысла!
Именно тогда я узнал, что на самом деле происходит - это было принуждение, и это происходило прямо перед моими глазами, но я был слишком слеп, чтобы это увидеть.
Мозилла попыталась похоронить его, положив туда, где, как они знали, никто не будет искать, - свою документацию .
После нескольких часов рекурсивного чтения, перечитывания и перечитывания я обнаружил следующее:
Это было прямо здесь, как можно прописать шрифтом Open Sans. Это была
call()
функция - как я мог быть таким глупым ?!Мой номер больше не был номером. В тот момент, когда я это передал
call()
, это стало чем-то другим. Это стало ... объектом.Сначала я не мог в это поверить. Как это могло быть правдой? Но я не мог игнорировать доказательства, которые накапливались вокруг меня. Это прямо здесь, если вы просто посмотрите:
wtf
был прав. Числа не могут иметь пользовательских свойств - мы все это знаем! Это первое, чему вас учат в академии.Мы должны были знать момент, когда мы увидели вывод консоли - это был не тот номер, о котором мы думали. Это был самозванец - объект, выдававший себя за наше милое невинное число.
Это было ...
new Number(5)
.Конечно! Это имело смысл.
call()
у него была работа, он должен был вызывать функцию, и для этого ему нужно было заполнитьthis
, он знал, что не может сделать это с числом - ему нужен объект, и он готов сделать все, чтобы получить его, даже если это означало принуждение нашего номера. Когдаcall()
увидел номер5
, он увидел возможность.Это был идеальный план: подождите, пока никто не посмотрел, и обменяйте наш номер на объект, который выглядит точно так же, как он. Мы получаем число, функция вызывается, и никто не станет мудрее.
Это действительно был идеальный план, но, как и во всех планах, даже идеальных, в нем была дыра, и мы собирались упасть прямо в нее.
Видите,
call()
не понимал только то, что он был не единственным в городе, кто мог принуждать числа. В конце концов, это был JavaScript - принуждение было повсюду.call()
взял мой номер, и я не собирался останавливаться, пока не снял маску с его маленького самозванца и не открыл его всему сообществу переполнения стека.Но как? Мне нужен был план. Конечно, это похоже на число, но я знаю, что это не так, должен быть способ доказать это. Это оно! Это похоже на число, но может ли оно действовать как единое целое?
Я сказал, что
five
мне нужно, чтобы он стал в 5 раз больше - он не спросил почему, а я не объяснил. Затем я сделал то, что сделал бы любой хороший программист: я размножился. Конечно, не было никакого способа, которым он мог фальсифицировать свой выход из этого.Черт побери! Мало того, что
five
умножение просто отличноwtf
было еще там. Черт бы побрал этого парня и его картошку.Что, черт возьми, происходит? Был ли я неправ во всем этом? Это
five
действительно число? Нет, я должен что-то упустить, я знаю это, я должен кое-что забыть, что-то настолько простое и базовое, что я полностью упускаю это из виду.Это выглядело не очень хорошо, я писал этот ответ в течение нескольких часов, и я все еще не был ближе к тому, чтобы высказать свою точку зрения. Я не мог продолжать это, в конце концов, люди перестали читать, мне нужно было что-то придумать, и я должен был быстро обдумать это.
Подождите, вот и все!
five
не было 25, 25 было результатом, 25 было совершенно другим числом. Конечно, как я мог забыть? Числа неизменны. Когда вы умножаете,5 * 5
ничто не присваивается ни для чего, вы просто создаете новый номер25
.Это должно быть то, что здесь происходит. Каким-то образом, когда я умножаю
five * 5
,five
должен быть приведен в число, и это число должно быть тем, которое используется для умножения. Именно результаты этого умножения выводятся на консоль, а не их ценностьfive
.five
никогда ничего не назначается - поэтому, конечно, это не изменится.Итак, как же я могу
five
назначить себе результат операции? Я понял. Прежде чемfive
даже имел возможность подумать, я вопил «++».Ага! Я имел его! Все знают
5 + 1
это6
, это было доказательством мне нужно было выставить , чтоfive
не было рядом! Это был самозванец! Плохой самозванец, который не знал, как считать. И я мог это доказать. Вот как действует действительное число:Подождите? Что здесь происходило? вздох Я так увлекся перебором,
five
что забыл, как работают почтовые операторы. Когда я использую++
в конце слова,five
я возвращаю текущее значение, затем увеличиваюfive
. Это значение перед выполнением операции выводится на консоль.num
был на самом деле6
и я мог это доказатьВремя посмотреть что
five
самом деле было:... это было именно то, что должно быть.
five
было хорошо - но мне было лучше. Если быfive
все еще был объект, это означало бы, что он все еще имел бы свойствоwtf
и я был готов поспорить на все, чего не было.Ага! Я был прав. Я имел его!
five
теперь был числом - это больше не был объект. Я знал, что трюк умножения не спасет его на этот раз. Видитеfive++
это действительноfive = five + 1
. В отличие от умножения,++
оператор присваивает значениеfive
. Более конкретно, он присваивает ему результаты,five + 1
которые, как и в случае умножения, возвращают новое неизменное число .Я знал, что он у меня есть, и просто чтобы убедиться, что он не сможет выбраться из этого. У меня был еще один тест в рукаве. Если бы я был прав, и сейчас
five
был действительно номером, это не сработало:Он не собирался обманывать меня на этот раз. Я знал,
potato?
что будет напечатан на консоли, потому что это вывод задания. Реальный вопрос, всеwtf
еще будет там?Как я и подозревал - ничего - потому что числам не могут быть назначены свойства. Мы узнали, что первый год в академии;)
Спасибо Натан. Благодаря вашей смелости, задавая этот вопрос, я наконец могу оставить все это позади и перейти к новому делу.
Как этот о функции
toValue()
. О Боже мой. Нееет!источник
01
объявляет функцию,dis
которая возвращает объект контекста. Чтоthis
представляет изменения в зависимости от того, используете ли вы строгий режим или нет. Весь пример имеет разные результаты, если функция была объявлена как:Это подробно описано в разделе 10.4.3 в спецификации ES5.
02
Возвращаемое значение объявления функции.undefined
должен быть самоочевидным здесь.03
переменнаяfive
инициализируется с возвращаемым значениемdis
при вызове в контексте примитивного значения5
. Посколькуdis
не в строгом режиме, эта строка идентична вызовуfive = Object(5)
.04
НечетноеNumber {[[PrimitiveValue]]: 5}
возвращаемое значение является представлением объекта, который оборачивает примитивное значение5
05
вfive
объектаwtf
недвижимости присваивается строковое значение'potato'
06
является возвращаемым значением присваивания и должно быть самоочевидным.07
вfive
объектеwtf
недвижимость рассматриваются в08
какfive.wtf
было ранее установлено,'potato'
это возвращает'potato'
сюда09
five
объект умножается на примитивном значение5
. Это ничем не отличается от любого другого умножаемого объекта и объясняется в разделе 11.5 спецификации ES5 . Особого внимания заслуживает то, как объекты приводятся к числовым значениям, что рассматривается в нескольких разделах.9.3 ToNumber :
9.1 ToPrimitive :
8.12.8 [[DefaultValue]] :
Это все окольный способ сказать,
valueOf
что вызывается функция объекта, а возвращаемое значение из этой функции используется в уравнении. Если бы вы изменилиvalueOf
функцию, вы могли бы изменить результаты операции:10
поскольку функцияfive
svalueOf
не изменилась, она возвращает значение обернутого примитива,5
чтобыfive * 5
оценить, к5 * 5
какому результату25
11
вfive
объектеwtf
недвижимость оцениваются снова , несмотря на то , оставались неизменными с момента , когда он был назначен на05
.12
'potato'
13
Postfix оператор инкремента вызываетсяfive
, которая получает числовое значение (5
мы рассмотрели , как раньше), сохраняет значение так , что оно может быть возвращено, добавляет1
к значению (6
) присваивает значениеfive
и возвращает хранимое значение (5
)14
как и раньше, возвращаемое значение является значением до того, как оно было увеличено15
доступ кwtf
свойству примитива value (6
), хранящегося в переменнойfive
. Раздел 15.7.5 спецификации ES5 определяет это поведение. Номера получают свойства отNumber.prototype
.16
Number.prototype
не имеетwtf
свойства, поэтомуundefined
возвращается17
five.wtf
присваивается значение'potato?'
. Назначение определено в 11.13.1 спецификации ES5 . В основном назначенное значение возвращается, но не сохраняется.18
'potato?'
был возвращен оператором присваивания19
сноваfive
, который имеет значение6
доступ к, и сноваNumber.prototype
не имеетwtf
свойства20
undefined
как объяснено выше21
five
доступ22
6
возвращается, как объяснено в13
источник
Это довольно просто.
Это возвращает
this
контекст. Итак, если вы этоcall(5)
сделаете, вы передаете номер как объект.call
Функция не предоставляет аргументы, то первый аргумент вы даете это контекстthis
. Обычно, если вы хотите, чтобы это было в контексте, вы даете это{}
такdis.call({})
, что означает, чтоthis
в функции пустоthis
. Однако, если вы передадите,5
кажется, он будет преобразован в объект. См. ЗвонокТак что возвращение
object
Когда вы это делаете
five * 5
, JavaScript видит объектfive
как примитивный тип, что эквивалентно5 * 5
. Интересно, что'5' * 5
это все равно равняется25
, поэтому JavaScript явно находится под капотом. Вfive
этой строке не производится никаких изменений в базовый типНо когда вы это сделаете,
++
он преобразует объект в примитивныйnumber
тип, удаляя.wtf
свойство. Потому что вы влияете на базовый типисточник
++
преобразует и присваивает его обратно.++
равноvariable = variable + 1
. Итак, вы проигралиwtf
*
Против++
не смущает меня. Наличие функции, которая не ожидает никаких аргументов и возвращает ееthis
, в любом случае эта функция принимает аргумент и возвращает что-то похожее на аргумент, но не совсем - для меня это не имеет смысла.dis.call()
делает.this
это просто указатель на объект, поэтому примитивные типы конвертируются неявно. Почему функция без аргументов не может иметь хотя бы контекст? Первый аргументcall
илиbind
используется для установки контекста. Кроме того, функции являются замыканиями, что означает, что они имеют доступ к чему-то большемуarguments
Примитивные значения не могут иметь свойства. Но когда вы пытаетесь получить доступ к свойству с примитивным значением, оно прозрачно переходит во временный объект Number.
Так:
источник
Объявить функцию
dis
. Функция возвращает свой контекстЗвоните
dis
с контекстом5
. Примитивные значения помещаются в рамки, когда передаются в качестве контекста в строгом режиме ( MDN ). Так чтоfive
теперь это объект (номер в штучной упаковке).Объявить
wtf
свойство поfive
переменнойЗначение
five.wtf
five
в штучной упаковке5
, так что это номер и объект одновременно (5 * 5 = 25). Это не меняетсяfive
.Значение
five.wtf
Распаковка
five
здесь.five
сейчас просто примитивноnumber
. Он печатает5
, а затем добавить1
вfive
.five
6
теперь это примитивное число , в нем нет свойств.примитивы не могут иметь свойства, вы не можете установить это
Вы не можете прочитать это, потому что это не было установлено
five
это6
потому , что пост приращения вышеисточник
Во-первых, похоже, что это выполняется через консоль nodejs.
1.
создает функцию dis (), но поскольку она не была задана как a,
var
не было никакого значения, которое нужно возвращать, поэтомуundefined
вывод был, даже еслиdis()
был определен. На sidenote,this
не был возвращен, потому что функция не была выполнена.2.
Это возвращает в JavaScript
Number
объект , потому что вы просто установите функциюdis()
«sthis
значение примитивных пяти.3.
Первые возвращается ,
"potato"
потому что вы просто установите свойствоwtf
изfive
в'potato'
. Javascript возвращает значение переменной вы установили, что делает его легко для нескольких переменных цепи и установить их на то же значение , как это:a = b = c = 2
.4.
Это возвращается,
25
потому что вы только что умножили примитивное число5
наfive
. Стоимостьfive
была определена стоимостьюNumber
объекта.5.
Я пропустил эту строку раньше, потому что я буду повторять это здесь. Он просто возвращает значение свойства,
wtf
которое вы установили выше.6.
Как сказал @Callum,
++
преобразует типnumber
из того же значения из объектаNumber {[[PrimitiveValue]]: 5}}
.Теперь, потому что
five
этоnumber
, вы не можете устанавливать для него свойства, пока не сделаете что-то вроде этого:или
Также обратите внимание, что второй способ будет вести себя иначе, чем при использовании первого метода, поскольку он определяет общий объект вместо
Number
объекта, который был создан ранее.Надеюсь, это поможет, javascript любит предполагать что-то, поэтому он может запутаться при переходе от
Number
объекта к примитивуnumber
. Вы можете проверить , что что - то типа с помощьюtypeof
ключевого слова, писать TypeOf пять после инициализации он возвращается'object'
, и после того, как вы сделаетеfive++
это возвращается'number'
.@deceze очень хорошо описывает разницу между объектом Number и примитивным числом.
источник
Области применения JavaScript сделаны из контекстов выполнения. Каждый контекст выполнения имеет лексическую среду (внешние / глобальные значения), переменную среду (локальные значения) и привязку this .
Эта привязка является очень важной частью контекста выполнения. Использование
call
- это один из способов изменить эту привязку , и при этом автоматически будет создан объект для заполнения привязки.Function.prototype.call () (из MDN)
Как только становится очевидным, что 5 преобразуется в
new Number(5)
, остальные должны быть достаточно очевидными. Обратите внимание, что другие примеры также будут работать, если они являются примитивными значениями.источник
Пара концепций объясняет, что происходит
5
это число, примитивное значениеNumber {[[PrimitiveValue]]: 5}
является экземпляром Number (назовем его обёрткой объекта)Всякий раз, когда вы обращаетесь к свойству / методу примитивного значения, механизм JS создаст объектную оболочку соответствующего типа (
Number
для5
,String
для'str'
иBoolean
дляtrue
) и разрешит вызов свойства / метода для этой объектной оболочки. Это то, что происходит, когда вы делаете,true.toString()
например.При выполнении операций над объектами они преобразуются в примитивные значения (с помощью
toString
илиvalueOf
) для разрешения этих операций - например, при выполненииstring
будет содержать конкатенацию строкmystr
иobj.toString()
иnumber
будет содержать добавление3
иobj.valueOf()
.Теперь собрать все вместе
dis.call(5)
ведет себя так же, как(5).dis()
если бы на5
самом деле был методdis
. Чтобы разрешить вызов метода, создается обертка объекта и на нем разрешается вызов метода. На данный момент пять указывает на обертку объекта вокруг примитивного значения 5.Установка свойства на объекте, ничего особенного здесь.
Это фактически
five.valueOf() * 5
получение примитивного значения из обёртки объекта.five
все еще указывает на начальный объект.Это на самом деле
five = five.valueOf() + 1
. До этой строкиfive
объект-обертка удерживает значение около 5, а после этой точкиfive
- примитивное значение6
.five
больше не объект. Каждая из этих строк создает новый экземпляр Number для разрешения.wtf
доступа к свойству. Экземпляры независимы, поэтому установка свойства для одного не будет видна для другого. Код полностью эквивалентен этому:источник