Эта статья была впервые опубликована на:4ark.what/post/how-OB…
предыстория истории
На следующий день после выхода в интернет босс сообщил о проблеме в группе. Когда обновление, которое он только что опубликовал, создавало карту обмена, небольшой программный код в нижней части карты был потерян. Однако другие мелкие партнеры сказали, что она работает. обычно на своих мобильных телефонах. На самом деле начальник тоже сказал, что кроме этой динамики все остальное в норме.
Это означает, что для воспроизведения этой ошибки требуется определенная динамическая карта + определенное оборудование.К счастью, мобильный телефон дамы, сидящей напротив меня, такой же, как у большого парня, и ошибка также может быть воспроизведена, избегая социального страха, который у меня есть. пойти к большому парню, чтобы одолжить его Смущение тестирования мобильного телефона.
Позвольте мне сначала объяснить предысторию проекта.Это проект апплета WeChat, в котором функция создания карты обмена используетwxml2canvasОднако в настоящее время библиотека находится в аварийном состоянии. ОШИБКА, упомянутая выше, связана с этой библиотекой. В этой статье рассказывается о процессе устранения ОШИБКИ и о том, как найти соответствующую информацию в спецификации ECMAScript.Object.keys()
Каноническое определение порядка возврата и, наконец, то, как свойства объектов обрабатываются в движке V8.
Надеюсь, что прочитав эту статью, вы больше не будете путаться в том, что не понимаетеObject.keys()
Ошибки в порядке вывода приводят к необъяснимым багам.
TL;DR
Эта статья очень длинная, если вы не хотите читать всю статью, можете прочитать это резюме, если планируете читать всю статью, то можете вообще пропустить этот абзац.
Если чтение аннотации не помогает вам понять, вы можете перейти к соответствующей главе для подробного чтения.
Резюме:
- Как появился этот баг?
-
wxml2canvas
При рисовании он будет основываться наsorted
Объект проходит по своим ключам, ключ объекта — это верхнее значение узла, а значение — элемент узла, тут проблема, автор библиотеки ошибочно подумал, чтоObject.keys()
всегда будет возвращаться в том порядке, в котором свойства были фактически созданы,Однако, когда ключ является положительным целым числом, порядок возврата не соответствует первоначальным ожиданиям, и порядок отрисовки будет нарушен, что приведет к генерации этой ОШИБКИ. - Исходный код:src/index.js#L1146а такжеsrc/index.js#L829
- Как исправить эту ошибку
- Поскольку ключ объекта является числом, ключ может быть целым числом или числом с плавающей запятой. Но ожидаемое поведение - это надежда
Object.keys()
Возвращается в том порядке, в котором свойства были фактически созданы, просто приведите все ключи к числам с плавающей запятой.
-
Object.keys()
В каком порядке возвращаются значения?
-
Object.keys()
Порядок возврата такой же, как и порядок обхода свойств объекта, а вызов[[OwnPropertyKeys]]()
внутренний метод. - согласно сСпецификация ECMAScript, при выводе ключей будетСначала отсортируйте все ключи типа индекса массива (положительное целое число) от меньшего к большему, а затем отсортируйте все ключи строкового типа (включая отрицательные числа и числа с плавающей запятой) в порядке их фактического создания..
- Как V8 обрабатывает свойства объекта внутри?
- Когда V8 хранит атрибуты объекта, для повышения эффективности доступа он будет разделен наОбщие свойства (свойства)а такжеСортировка атрибутов (элементов)
- Сортировка атрибутов (элементов), который является атрибутом типа индекса массива (то есть положительного целочисленного типа).
- Общие свойства (свойства), который является атрибутом типа string (включая отрицательные числа и числа с плавающей запятой).
- Оба вышеупомянутых свойства хранятся в линейной структуре, называемойбыстрый атрибут.
- Однако у каждого запроса есть уровень косвенности, который влияет на эффективность, поэтому в V8 вводитсясвойства объекта.
- V8 будет связывать скрытый класс с каждым объектом для записи формы объекта.Объекты с одинаковой формой будут иметь один и тот же скрытый класс.
- Когда объект добавляет или удаляет атрибуты, новый соответствующийскрытый класс, и снова подключитесь.
-
Свойства объектабудет частичноОбщие свойстваОн размещается непосредственно на первом слое объекта, поэтому эффективность доступа к нему самая высокая.
- когдаОбщие свойстваколичествоМеньше, чем количество свойств при инициализации объектачас,Общие свойстваПрямо какСвойства объектахранить.
- несмотря на то чтобыстрый атрибутСкорость доступа высока, но эффективность выполнения очень низкая при добавлении или удалении из линейной структуры, поэтому, если атрибутов слишком много или при добавлении и удалении атрибутов,Общие свойстваОт линейного хранилища до словарного хранилища — этомедленное свойство.
Взгляните на эти две картинки, чтобы понять:
Общие свойства V8 и свойства сортировки
Свойства объекта V8, быстрые свойства и медленные свойства
Источник изображения: «Иллюстрированный Google V8» — Geek Time
Как исправить эту ошибку
Поскольку для воспроизведения проблемы используется конкретное динамическое + конкретное устройство, легко исключить сетевую причину путемwxml2canvas
Выведите список нарисованных узлов, и вы также можете увидеть узлы, связанные с кодом апплета.
теперь, когдаwxml2canvas
Узел, получивший код апплета, но не нарисованный, то проблема естественно кроется вwxml2canvas
Внутренне, но неудивительно, что меня много раз трахали за это с тех пор, как я присоединился к проекту.wxml2canvas
Проблемы разные и скальп немеет.Если есть шанс, то эту библиотеку надо заменить.Но так как уже много страниц опирается на эту библиотеку, то мы можем только кусать пулю сейчас.
В первую очередь есть подозрение на неправильное координатное положение узла кода апплета, путем сравнения выясняется, что положение не сильно отличается, и причина исключается.
Затем сравните порядок отрисовки всех узлов и обнаружили необычную точку.На мобильном телефоне, который воспроизводит ошибку, время рисования узла кода апплета относительно рано, но поскольку он находится внизу карты, при нормальных обстоятельствах , он должен быть относительно отсталым.
Итак, просмотрев соответствующий код, я нашел загадку:
При рисовании путем обходаsorted
Объекты рисуются сверху вниз и слева направо, но, сравниваяObject.keys()
, и обнаружил, что их вывод отличается, тогда я понимаю, что происходит.
Давайте сначала поговорим об этомsorted
Объект, представляющий собой массив элементов, ключ которых является верхним значением узла, а значения — одним и тем же верхним значением (той же строкой).
Вот код для его генерации:
Проблема возникает, как упоминалось ранееObject.keys()
Вот, давайте сначала посмотрим 🌰:
const sorted = {}
sorted[300] = {}
sorted[200] = {}
sorted[100] = {}
console.log(Object.keys(sorted)) // 输出什么呢?
Я полагаю, что большинство студентов знают, что ответ: ['100', '200', '300'].
Что делать, если есть числа с плавающей запятой?
const sorted = {}
sorted[300] = {}
sorted[100] = {}
sorted[200] = {}
sorted[50.5] = {}
console.log(Object.keys(sorted)) // 这次又输出什么呢?
Будут ли некоторые студенты думать, что ответ: ['50,5', '100', '200', '300']?
Но правильный ответ должен быть: ['100', '200', '300','50,5'].
Так что думаю разумноwxml2canvas
АвторObject.keys
Он будет возвращен в соответствии с порядком ключей от меньшего к большему, поэтому он удовлетворяет логике рисования сверху вниз. Но он не учел число с плавающей запятой, поэтому, когда верхнее значение узла является целым числом, оно будет отрисовано раньше, чем другие узлы, верхнее значение которых является числом с плавающей запятой, что приводит к тому, что предыдущий узел перезаписывается, когда более поздний узел нарисован.
Итак, когда я изменил код на этот, код апплета общей карты отрисовался нормально:
Object
.keys(sorted)
+ .sort((a, b)=> a - b)
.forEach((top, topIndex) => {
// do something
}
Хорошо, займись делом.
тестовая леди: Подожди! воздействовать на другие места.
Я видел это, конечно. Итак, после повторного сравнения оказывается, что в большинстве случаев верхнее значение будет числом с плавающей запятой, а код карточного апплета этого жука просто по совпадению является целым числом, что приводит к неправильному порядку отрисовки.
я только что нашелwxml2canvas
Первоначальная логика заключалась в том, чтобыsorted
Нарисован порядок создания, но случай, когда ключ является целым числом, не рассматривается.
Итак, наконец, решите проблему, изменив ее следующим образом:
_sortListByTop (list = []) {
let sorted = {};
// 粗略地认为2px相差的元素在同一行
list.forEach((item, index) => {
- let top = item.top;
+ let top = item.top.toFixed(6); // 强制添加小数点,将整数转为浮点数
if (!sorted[top]) {
if (sorted[top - 2]) {
top = top - 2;
}else if (sorted[top - 1]) {
top = top - 1;
} else if (sorted[top + 1]) {
top = top + 1;
} else if (sorted[top + 2]) {
top = top + 2;
} else {
sorted[top] = [];
}
}
sorted[top].push(item);
});
return sorted;
}
Очевидно, потому чтоwxml2canvas
авторыObject.keys()
Непонятен механизм возврата заказа, что и приводит к такому багу.
Я не знаю, есть ли другие студенты, которые совершили ту же ошибку.Чтобы избежать повторения такой ситуации, очень важно дать глубокое и всестороннее введение.Object.keys()
исполнительный механизм.
Так что следуйте за мной, чтобы узнать.
Глубокое понимание Object.keys()
Некоторые ученики могут сказать:Object.keys()
Это не новый API, просто погуглите его, зачем писать статью, чтобы представить его?
Ведь можно быстро узнать через поисковикиObject.keys()
Какой порядок возврата , но многие из них только поверхностные, и я даже встречал такой односторонний ответ: числа впереди, а строки сзади.
Итак, на этот раз я хочу попытаться отследить источник, получить информацию из первых рук и легко поверить, что информация, передаваемая из уст в уста, скорее всего, будет односторонней или даже ложной.
PS: На самом деле, не только технологии, мы должны сохранять такое же отношение к другим вещам, которые мы не понимаем.
Давайте сначала посмотрим наMDNпримерно наObject.keys()
описание:
Object.keys()
Метод возвращает массив собственных перечислимых свойств данного объекта в том порядке, в котором имена свойств возвращаются в обычном цикле по объекту.
эммм... не говорит нам напрямую, каков порядок вывода, но мы можем посмотреть на приведенное вышеPolyfillКак пишется:
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}
})()
};
На самом деле, используяfor...in
пройти, и тогда мы можем посмотреть наfor...in, но это не говорит нам, каков порядок.
Поскольку MDN нет, мы можем напрямую посмотреть спецификацию ECMAScript. Обычно MDN прикрепляет ссылку на спецификацию об этом API. Мы напрямую нажимаем на самую последнюю (Living Standard). Следующее касается Object.keysканоническое определение:
When the keys function is called with argument O, the following steps are taken:
- Let obj be ? ToObject(O).
- Let nameList be ? EnumerableOwnPropertyNames(obj, key).
- Return CreateArrayFromList(nameList).
Список свойств объекта через EnumerableOwnPropertyNames
приобрел, вот и всеканоническое определение:
The abstract operation EnumerableOwnPropertyNames takes arguments O (an Object) and kind (key, value, or key+value). It performs the following steps when called:
Let ownKeys be ? O.[OwnPropertyKeys].
Let properties be a new empty List.
For each element key of ownKeys, do a. If Type(key) is String, then
- Let desc be ? O.[GetOwnProperty].
- If desc is not undefined and desc.[[Enumerable]] is true, then a. If kind is key, append key to properties.
b. Else, 1. Let value be ? Get(O, key). 2. If kind is value, append value to properties. 3. Else i. Assert: kind is key+value. ii. Let entry be ! CreateArrayFromList(« key, value »). iii. Append entry to properties.
Return properties.
Стучите по доске! Вот деталь, пожалуйста, обратите больше внимания на студентов, они будут сдавать тест позже.
Мы продолжаем исследовать,OwnPropertyKeys
наконец вернулсяOrdinaryOwnPropertyKeys
:
The [[OwnPropertyKeys]] internal method of an ordinary object O takes no arguments. It performs the following steps when called:
- Return ! OrdinaryOwnPropertyKeys(O).
Главное событие здесь, о том, как сортируются ключиOrdinaryOwnPropertyKeys
изопределениесередина:
The abstract operation OrdinaryOwnPropertyKeys takes argument O (an Object). It performs the following steps when called:
- Let keys be a new empty List.
- For each own property key P of O such that P is an array index, in ascending numeric index order, do a. Add P as the last element of keys.
- For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do a. Add P as the last element of keys.
- For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do a. Add P as the last element of keys.
- Return keys.
На данный момент мы уже знаем ответ, который хотим, вот краткое изложение:
- Создайте пустой список для хранения ключей
- Вседействительные индексы массиваХранить в порядке возрастания
- положить всеИндекс строкового типаХранится в порядке возрастания по времени создания атрибута
- положить все
Symbol
индекс типаХранится в порядке возрастания по времени создания атрибута - ключи возврата
Кстати, здесь также исправлено распространенное недоразумение: в некоторых ответах говорится, что все ключи, атрибуты которых являются числовыми типами, сортируются от меньшего к большему."правильный индекс массива", то есть толькоположительное числоТолько негативные номера или номера с плавающей точкой рассматриваются как строки.
PS: Строго говоря, свойства объекта не имеют числового типа, будь то числа или строки, они будут рассматриваться как строки.
Давайте объединим приведенные выше спецификации, чтобы подумать о том, что выведет следующий код:
const testObj = {}
testObj[-1] = ''
testObj[1] = ''
testObj[1.1] = ''
testObj['2'] = ''
testObj['c'] = ''
testObj['b'] = ''
testObj['a'] = ''
testObj[Symbol(1)] = ''
testObj[Symbol('a')] = ''
testObj[Symbol('b')] = ''
testObj['d'] = ''
console.log(Object.keys(testObj))
После тщательного размышления проверьте правильность своего ответа здесь:
Посмотреть результаты 👈
['1', '2', '-1', '1.1', 'c', 'b', 'a', 'd']
Соответствует ли оно тому, что вы себе представляли? Вы можете задаться вопросом, почему бы и нетSymbol
Типы.
Вы еще помните место, где вы постучали по доске, чтобы ваши одноклассники обратили внимание, потому что вEnumerableOwnPropertyNames
Спецификация предусматривает, что возвращаемое значение должно содержать только строковые атрибуты (выше говорилось, что числа на самом деле являются строками).
Таким образом, свойство Symbol не будет возвращено, вы можете видетьMDNпримерно наObject.getOwnPropertyNames()
описание.
Если вы хотите вернуть свойство Symbol, вы можете использоватьObject.getOwnPropertySymbols().
Прочитав определение спецификации ECMAScript, я считаю, что вы не ошибетесь снова.Object.keys()
Выходной порядок. Но вам интересно, как обрабатывать свойства объекта V8 для вас, следующий раздел, о котором мы что-то скажем.
Как V8 обрабатывает свойства объекта
В официальном блоге V8 есть статья《Быстрые свойства в V8》(китайский перевод), который очень подробно объясняет нам, как V8 внутренне обрабатывает свойства объектов JavaScript, настоятельно рекомендуется к прочтению.
Кроме того, рекомендую курс на Geek Time»Графический Google V8(ведь эта статья позаимствовала картинки внутри, так что мне стыдно ее рекомендовать).
Содержание этого раздела в основном относится к этим двум местам, и мы обобщим их ниже.
Прежде всего, чтобы повысить эффективность доступа к атрибутам объекта, V8 делит атрибуты на два типа:
-
Сортировка атрибутов (элементов), который является атрибутом, соответствующим типу индекса массива (то есть положительному целому числу).
-
Общие свойства (свойства), который является атрибутом типа string (включая отрицательные числа и числа с плавающей запятой).
всеСортировать имуществоВсе они хранятся в линейной структуре. Характеристика линейной структуры заключается в том, что она поддерживает произвольный доступ через индекс, поэтому она может ускорить скорость доступа. Атрибуты, хранящиеся в линейной структуре, называютсябыстрый атрибут.
Общие свойстваОн также будет храниться в другой линейной структуре, вы можете увидеть следующую картинку, чтобы понять:
Свойства сортировки V8 и общие свойства
ноОбщие свойстваНеобходимо выполнить некоторую дополнительную обработку, здесь мы сначала представим, чтоскрытый класс.
Поскольку JavaScript может изменять свойства объекта во время выполнения, запросы будут выполняться медленнее. Вы можете оглянуться назад на рисунок выше. Каждый раз, когда вы обращаетесь к свойству, вам нужно пройти еще один уровень доступа и такие вещи, как статические языки C++. необходимо определить структуру (форму) объекта перед объявлением объекта.После компиляции форма каждого объекта фиксируется, поэтому естественно будет быстрее, потому что смещение атрибута известно при доступе.
Идея, принятая в V8, состоит в том, чтобы применить этот механизм к объектам JavaScript, поэтому он вводитскрытый классМеханизм, вы можете просто понятьскрытый классЭто описание формы объекта, включая положение, соответствующее каждому атрибуту, чтобы запрос был намного быстрее.
оскрытый классЕще несколько моментов, которые нужно добавить:
- Первое поле объекта указывает на егоскрытый класс.
- Если два объекта имеют одинаковую форму, они будут иметь одинаковыескрытый класс.
- Когда объект добавляет или удаляет атрибуты, новый соответствующийскрытый класс, и перенаправить на него.
- В V8 есть механизм дерева преобразования для создания скрытых классов, но в этой статье мы не будем вдаваться в подробности, вы можете прочитать, если вам интересноздесь.
После объяснения скрытого класса давайте вернемся и поговорим о нем.Общие свойства, через приведенную выше картинку мы можем легко найти проблему, то есть каждый раз, когда происходит доступ к атрибуту, для доступа к нему необходимо пройти через уровень косвенности, что, несомненно, снижает эффективность доступа.Чтобы решить эту проблему, V8 представил еще один, называемыйСвойства объекта, как следует из названия, он будет хранить некоторые атрибуты непосредственно в первом слое объекта, и доступ к нему будет самым быстрым, как показано на следующем рисунке:
Свойства в объектах V8
Но будьте осторожны,Свойства объектаТолько хранитьОбщие свойства, свойство сортировки остается неизменным. И количество необходимых обычных свойствменьше, чемОпределенная сумма будет сохранена непосредственноСвойства объекта, какая это сумма?
Ответ зависит отразмер объекта при инициализации.
PS: В некоторых статьях говорится, что атрибуты в объекте будут храниться только в том случае, если атрибутов меньше 10.Не вводите в заблуждение.
КромеСвойства объекта,быстрый атрибутКроме того, есть ещемедленное свойство.
почему бы не бытьмедленное свойствоШерстяная ткань?быстрый атрибутНесмотря на быстрый доступ, если большое количество свойств должно быть добавлено или удалено из объекта, могут потребоваться значительные затраты времени и памяти на обслуживание.скрытый класс, так что вКогда атрибутов слишком много или при многократном добавлении и удалении атрибутовбудуОбщие свойстваСпособ хранения изменен с линейной структуры на словарную, т. е. сведен кмедленное свойство, в то время как из-замедленное свойствоинформация больше не будет храниться вскрытый класс, поэтому его доступ будет меньшебыстрый атрибутМедленнее, но может эффективно добавлять и удалять свойства. Следующий рисунок может помочь понять:
Медленные свойства V8
Написав здесь, я чувствую, что очень хорошо разбираюсь в быстрых и медленных свойствах V8, что почти потрясающе.
Но когда я вижу этот код:
function toFastProperties(obj) {
/*jshint -W027*/
function f() {}
f.prototype = obj;
ASSERT("%HasFastProperties", true, obj);
return f;
eval(obj);
}
Моё настроение такое:
О том, как этот код заставляет V8 использовать объектыбыстрый атрибутВы можете увидеть эту статью:Включить «быстрый» режим для свойств объекта V8.
Также взгляните на этот код:to-fast-properties/index.js.
напиши в конце
Когда вы сталкиваетесь с простой ошибкой во время разработки, вы обычно можете использовать поисковые системы для быстрого решения проблемы, но если вы программируете только для Google, может быть трудно добиться прогресса в технологии, поэтому мы должны не только решить проблему, но и понять, в чем причина этой проблемы?
Настоятельно рекомендуется, чтобы каждый разработчик JavaScript имел некоторые знания о V8 или других движках JavaScript, независимо от того, какой путь вы используете (действительно без рекламы), это гарантирует, что у нас возникнут проблемы при написании кода JavaScript.
Наконец, эта статья ограничена, и некоторые детали неизбежно будут упущены.Настоятельно рекомендуется, чтобы заинтересованные углубленные студенты расширили чтение списка ниже.
Спасибо за прочтение.
дальнейшее чтение
- Fast properties in V8
- «Иллюстрированный Google V8» — Geek Time
- How is data stored in V8 JS engine memory?
- Быстрые и медленные свойства и быстрые и медленные массивы в V8
- Включить «быстрый» режим для свойств объекта V8
- ECMAScript® 2015 Language Specification
- Гарантирует ли JavaScript порядок свойств объекта?