Оновлення 2017 року: По-перше, для читачів, які приходять сьогодні - ось версія, яка працює з Node 7 (4+):
function enforceFastProperties(o) {
function Sub() {}
Sub.prototype = o;
var receiver = new Sub(); // create an instance
function ic() { return typeof receiver.foo; } // perform access
ic();
ic();
return o;
eval("o" + o); // ensure no dead code elimination
}
Запроваджує одну-дві невеликі оптимізації - все наведене нижче все ще діє.
Давайте спочатку обговоримо, що це робить і чому це швидше, а потім, чому це працює.
Що це робить
Двигун V8 використовує два представлення об'єкта:
- Режим словника - в якому об'єкт зберігається як ключові карти як хеш-карта .
- Швидкий режим - в якому об'єкти зберігаються як структури , в яких немає обчислень, що беруть участь у доступі до власності.
Ось проста демонстрація, яка демонструє різницю швидкості. Тут ми використовуємо delete
оператор, щоб змусити об'єкти у повільному словниковому режимі.
Двигун намагається використовувати швидкий режим, коли це можливо, і зазвичай, коли виконується багато ресурсів, але іноді він переходить у словниковий режим. Перебуваючи в словниковому режимі, існує велика ефективність покарання, тому загалом бажано переводити об'єкти у швидкий режим.
Цей злом призначений для примушування об'єкта до швидкого режиму зі словника.
Чому це швидше
У прототипах JavaScript, як правило, зберігаються функції, які поділяються між багатьма екземплярами і рідко змінюються дуже багато. З цієї причини дуже бажано мати їх у швидкому режимі, щоб уникнути зайвого штрафу щоразу, коли функція викликається.
Для цього - v8 із задоволенням поставить об'єкти, які є .prototype
властивістю функцій у швидкому режимі, оскільки їх буде спільним для кожного об'єкта, створеного за допомогою виклику цієї функції як конструктора. Це взагалі розумна і бажана оптимізація.
Як це працює
Давайте спочатку переглянемо код і розберемо, що робить кожен рядок:
function toFastProperties(obj) {
/*jshint -W027*/ // suppress the "unreachable code" error
function f() {} // declare a new function
f.prototype = obj; // assign obj as its prototype to trigger the optimization
// assert the optimization passes to prevent the code from breaking in the
// future in case this optimization breaks:
ASSERT("%HasFastProperties", true, obj); // requires the "native syntax" flag
return f; // return it
eval(obj); // prevent the function from being optimized through dead code
// elimination or further optimizations. This code is never
// reached but even using eval in unreachable code causes v8
// to not optimize functions.
}
Нам не потрібно знаходити код, щоб стверджувати, що v8 робить цю оптимізацію, ми можемо замість цього прочитати тести v8 unit :
// Adding this many properties makes it slow.
assertFalse(%HasFastProperties(proto));
DoProtoMagic(proto, set__proto__);
// Making it a prototype makes it fast again.
assertTrue(%HasFastProperties(proto));
Читання та запуск цього тесту показує нам, що ця оптимізація справді працює в v8. Однак - було б непогано подивитися, як.
Якщо ми перевіримо, objects.cc
ми можемо знайти таку функцію (L9925):
void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
if (object->IsGlobalObject()) return;
// Make sure prototypes are fast objects and their maps have the bit set
// so they remain fast.
if (!object->HasFastProperties()) {
MigrateSlowToFast(object, 0);
}
}
Тепер JSObject::MigrateSlowToFast
просто явно бере словник і перетворює його у швидкий V8-об’єкт. Це варто прочитати і цікаве уявлення про внутрішні внутрішні об'єкти v8 - але це не тема тут. Я все ще гаряче рекомендую прочитати його тут, оскільки це хороший спосіб дізнатися про v8 об’єкти.
Якщо ми перевіряємо SetPrototype
в objects.cc
, ми можемо бачити , що він викликається в рядку 12231:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
Що, в свою чергу, називається тим FuntionSetPrototype
, з чим ми отримуємо.prototype =
.
Робити __proto__ =
або .setPrototypeOf
б також працювало, але це функції ES6, і Bluebird працює у всіх браузерах з Netscape 7, тому тут не може бути спрощення коду. Наприклад, якщо ми перевіримо, .setPrototypeOf
ми можемо побачити:
// ES6 section 19.1.2.19.
function ObjectSetPrototypeOf(obj, proto) {
CHECK_OBJECT_COERCIBLE(obj, "Object.setPrototypeOf");
if (proto !== null && !IS_SPEC_OBJECT(proto)) {
throw MakeTypeError("proto_object_or_null", [proto]);
}
if (IS_SPEC_OBJECT(obj)) {
%SetPrototype(obj, proto); // MAKE IT FAST
}
return obj;
}
Що безпосередньо ввімкнено Object
:
InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));
Отже - ми пройшли шлях від коду, який Петка написав, до голого металу. Це було приємно.
Відмова від відповідальності:
Пам'ятайте, що це вся деталь реалізації. Такі люди, як Петка, - оптимізація виродків. Завжди пам’ятайте, що передчасна оптимізація - корінь всього зла в 97% часу. Bluebird робить щось дуже базове дуже часто, тому багато отримує від цих хакерських можливостей - бути таким же швидким, як зворотній зв'язок не просто. Вам рідко доводиться робити щось подібне в коді, який не працює в бібліотеці.