Pull to refresh

Comments 35

Прототипы и наследования

Забавно видеть полное противоречие вашего текста тексту по приведенной вами же ссылке. Например, функции в js типом данных не являются, а так же нет "простых" и "составных" типов, а есть "примитивы" и "объекты". Формально — в js массив — тоже объект. Да и неформально тоже, если бы язык позволял обращаться к свойствам через точку и для имен, начинающихся с чисел, конструкция была бы валидной
var a = [3,4,5];
a[0] === a.0

Но он не позволяет, но от этого массивы объектами быть не перестали (к слову, вы сами об этом дальше пишите).
Ну и потеряны null и undefined.
жесткими операторами сравнения

Строгими только.
Имейте в виду, что функциональная декларация функций будет доступна и в области видимости выше, так что все это не имеет значения, в каком порядке была объявлена эта функция. Как правило, вы всегда должны объявлять функции в глобальной области видимости, при этом вы должны стараться избегать размещения таких функций внутри условных операторов.

Чего? Я бы отправил личным сообщением как просьбу переписать абзац, однако тут есть жуткий bad practices: "вы всегда должны объявлять функции в глобальной области видимости" — вы никогда и ни при каких обстоятельствах не должны объявлять функции в глобальной области видимости. Даже если совсем совсем прижмет. Очень мало исключений, как правило, библиотеки.
function ruleOfThree (had, got, have) {
  return have * got / had;
}
ruleOfThree(4, 2, 6);
// <- 3

Просто великолепно! Идея понятна, конечно же, но пример отвратный. В данном случае, что функцию a, что функцию ruleOfThree я полезу искать, покуда совершенно не очевидно, что она делает.
Вообще у вас талант давать плохие примеры под комментарием "хорошо".
Например, в секции "комментарии"
хорошо: [… не дублировать комментариями то, что понятно из кода...]
var numeric = /\d+/; // поиск одной или более цифр в строке

Так то это и так понятно.
Вообще в целом: видно, работа проделана большая и хорошая. Не без огрехов, но все равно. Хотя я бы все же рекомендовал в 2016 году использовать исключительно es6 и выше. Гораздо проще и меньше хлопот. Один раз потратить пару часов на настройку бейбела или запускать в новой ноде гораздо проще и выгоднее, чем потом набивать шишки на всплытии переменных и куче других причуд.
Поддерживаю. Некоторые примеры действительно чудовищны — reduce для склейки свойств объектов в массиве просто нечитаем по сравнению с более лаконичным someCollection.map(function(n) {return n.name}).join(", ");, или ещё более прекрасным ES2015-вариантом someCollection.map(n => n.name).join(", ");
зачем это говно мамонта? если есть es6
а все нормальные люди так вообще используют транспилеры с последних стандартов.
Автор зачастую сам себе противоречит, так например призывают везде использовать точку с запятой и тутже ссылка на jQuery codestyle, призыв не расширять стандартные объекты и Array.prototype.sum =…
До листал до середины, почти в каждом примере помеченным «хорошо» показаны bad-practices

[].slice.call(arguments)

Создали новый массив, только ради того чтоб обратится к прототипу, утечка аргументов это главный вырубатель оптимизаторов в v8 и SpiderMonkey, но к сожалению этим пестрит каждая 3я библиотека на npm, благодаря вот таким вот туториалам…
Сложно скопировать в функцию простой шаблон
var args; for(let i = arguments.length, a = args = new Array(i); i--; a[i] = arguments[i]);

Тогда скопируйте этот:
var args = Array.prototype.concat.apply(Array.prototype, arguments);


Создание новых функций для каждого инстанса быстрее чем копирование ссылок из прототипа? да ну?
Все методы, гетеры, сетеры должны быть в прототипе, все изменяемые свойства создаются в конструкторе, прототип обязан содержать свойство constructor — ссылку на свой конструктор.
Сложно запомнить? Используйте class из es2015

Ну и напоследок, функциональные выражения, которые живут дольше 1 вызова, должны быть именованны
someEmitter.on('event', function onSomeEmitterEvent() {
    //some code
});

не раз поможет при отладке
var args = Array.prototype.concat.apply(Array.prototype, arguments);

Тут же ж тоже утечка аргументов. Вообще не понимаю, неужели нельзя делать такой контракт функций, чтобы не было необходимости в arguments? Это ведь очевиднее для использования.
Причина деоптимизации «утечки аргументов» — связь между индексами объекта arguments и переменными в аргументах функции, при изменении одного — меняется другое, и это не ссылачная связь, а скорее биндинг, поэтому на машинном уровне такой код сложно анализировать.
Когда мы передаем объект arguments во вне функции, он передается по ссылке, внешний код может изменить значения объекта.
Поэтому оптимизатор отказывается менять код функции с подобным поведением, а так же функции подобные такой:
function f(arg) {
   arg = arg || DEFAULT; //Присвоили аргументу новое значение
}

2й аргумент нативного Function.prototype.apply исключение, так как вызываемая функция на наш arguments гарантировано воздействовать не сможет

Разве здесь [].slice.call(arguments) есть утечка аргументов? slice же тоже ничего не меняет в arguments.

function bad2() {
  return [].call.slice(arguments, 1);
}

Если же вы пытаетесь передать arguments в какую-нибудь другую функцию (например, делаете [].slice.call(arguments, 1)), то Crankshaft вообще откажется оптимизировать вашу функцию.

http://mrale.ph/blog/2015/04/12/jsunderhood.html
благодарю за ссылку, нашел для себе пару новшеств
2й аргумент нативного Function.prototype.apply исключение, так как вызываемая функция на наш arguments гарантировано воздействовать не сможет

Да, про apply вы правы. Забыл про эту особенность
>>связь между индексами объекта arguments и переменными в аргументах функции, при изменении одного — меняется другое

это не так со включенным флагом 'use strict';
Да и вообще сейчас лучше вообще
Array.from(arguments)
использовать. Или даже ...args
«это не так со включенным флагом 'use strict';»
Не поможет

«Или даже ...args»
Поможет

«Да и вообще сейчас лучше вообще Array.from(arguments) использовать.»
Так делать не стоит
Если включен флаг use strict то связи между объектом arguments и аргументами функции нет — откуда утечка?
«Если же вы пытаетесь передать arguments в какую-нибудь другую функцию, то Crankshaft вообще откажется оптимизировать вашу функцию.»
Статье уже год как, думаю это стоит проверить тем более Array.from как раз создан для работы с итерируемыми объектами и от ...args не слишком то отличается.
То есть вы не проверили и советуете только основываясь на своё внутреннее ощущение, что за год в движке уж точно этот момент доработали?

Я вот в свою очередь думаю, что год назад как была эта информация актуальна, так и сейчас актуальна, потому что зачем допиливать оптимизацию устаревшнего arguments, раз уже активно внедряют es2015-es2016 и rest parameters
Нет, я исходил из соображений, что утечка отсутсвует и потому оптимизация должна быть. Теперь обязательно проверю. Тем более интересен тот момент, что Array.from не может менять arguments, так что это не совсем обычная функция.
Итого проверил в node 5.10.1 и chrome 49.0.2623.112 со включенным use strict:
function(...args) {var a = args; return a;} // не оптимизируется 

function() {var a = Array.from(arguments); return a;} // не оптимизируется 

function() {var a = []; for(let el of arguments) a.push(el); return a;} // оптимизируется TurboFan

function() {var a = []; for(var i = 0, l = arguments.length; i < l; i++) a.push(arguments[i]); return a;} // оптимизируется

function() {var a = Array.prototype.concat.apply(Array.prototype, arguments); return a;} // оптимизируется 


Итого оптимизация случается крайне редко, ES6 пока оптимизируется плохо или не оптимизируется.
Так получается, что критически важные по скорости задачи все равно пишутся с использованием классического for, а в остальном нет смысла отказываться от удобных и читаемых конструкций будь то ...args, Array.from или for of.
1. здесь нечего оптимизировать, код инлайнится в простой return args, так как ...args в отличии от arguments просто массив
2. теперь и вы убедились, что ваш совет и вывод про статью были ошибочны
3-5. Невозможно просто заинлайнить, оптимизируется как и ожидалось

Итого. Используйте es6 + babel и никого не слушайте
Ну так тоже не оптимизируется:

function testFunction(...args) {
  const l = args.length;
  var a = new Array(l);
  for(let i = 0; i < l; i++) a[i] = args[i];
  return a;
}


C use strict arguments это тоже просто объект. Проблемы с внутренней кухней оптимизации не предсказуемы. Я думаю как только будет оптимизироваться ...args будет оптимизироваться и Array.form (механизм работы у них схожий), хотя опять же, это все очень не предсказуемо.

Общий вывод поддерживаю — есть смысл пользоваться es6 с бабелем или без, а там где нужна оптимизация переходить к старому доброму for или уже переходить на asmjs и/или glsl (зависит от задач).
Кстати for-of только с 41 версии хрома оптимизируется, раньше тоже не хотел.
Если есть желание, можно проверить еще и на 6.0.0 ноде, где более актуальная версия v8 4.9.385.27 (chrome ~49), против 4.6.85.31 (chrome ~45) в node 5.10.1 (в 5 ветке вообще v8 не менялся с 5.1.0)
Ночные сборки 6.0.0 ноды: https://nodejs.org/download/nightly/
Ну я прямо в браузере проверял так что наверное смысла нет. Скорей всего они эти оптимизации проведут когда полностью заменят кранкшафт на турбофан.
Спасибо за инструкцию как запускать проверку в браузере

Вот еще до кучи вариант со стрелочными функциями (которые, как известно, дешевле и быстрее создаются и вызываются, и вовсе не имеют arguments):
var testFunction = (...args) => {
	let a = Array.from(args);
	args = 4;
	return a;
}

Результат:
Function is optimized by TurboFan

После работы бабеля получается тоже оптимизированная функция:
Function is optimized

Проверил так же различные варианты стрелочных функций (в том числе и в прототипах), они всегда оптимизировались (по крайней мере не удалось найти вариант, чтобы оптимизации не было)
Например, функцию console.log() не получится сделать так, чтобы можно было обойтись без arguments, ну или max(21, 34, 54, 345, 1) какой-нибудь

Просто сейчас можно перейти на ...rest параметры, и очевиднее, и деоптимизаций не будет, так как Rest parameters создают обычный массив, хоть как его используй

При этом babel при преобразовании в es5 сам оптимизирует этот момент, чтобы деоптимизаций не было
function test(...rest) {
  [].slice.call(rest)
}

Станет:
function test() {
  for (var _len = arguments.length, rest = Array(_len), _key = 0; _key < _len; _key++) {
    rest[_key] = arguments[_key];
  }

  [].slice.call(rest);
}

Поэтому, кстати, в 2016 году куда полезнее учить связке es2015+babel, чем держать в голове все эти особенности с оптимизациями. По крайней мере на странных вещах, вроде arguments, попасться будет сложнее
Вы не вкурсе, в последних версиях бабеля поправили преобразование
[...arguments]

в
[].concat(Array.prototype.slice.call(arguments))

В исходном варианте утечки нет, после бабеля появилась
Проверил на последней версии (или что-то тут не так?):
$ ./node_modules/.bin/babel -V
6.6.5 (babel-core 6.7.4)

На выходе получается правильная версия, без утечки:
$ cat ./src/index.js 
function test(...rest) {
  [].slice.call(rest)
}

$ cat ./lib/index.js 
"use strict";

function test() {
  for (var _len = arguments.length, rest = Array(_len), _key = 0; _key < _len; _key++) {
    rest[_key] = arguments[_key];
  }

  [].slice.call(rest);
}

Try it out выдает тот же результат
Я тут не про рест аргументы, а про спред оператор в литерале массива, имеющий сходный синтаксис
var arr = [1, 2];
console.log([3, 4, 5, ...arr]); // [3, 4, 5, 1, 2]

Вместо arr допускается любой массиво-подобный объект
Тогда о чем и к чему речь, если я предлагаю вовсе отказаться от arguments и заменить его на Rest parameters + babel, чтобы утечек оптимизации не было? А вы предлагает вариант, где снова тянуть за собой arguments, да еще и как оператор spread. Es6 вообще подразумевает, что вы забудете как пишется arguments.

И кстати, Babel тут наоборот правильно всё делает, конструкция [...arguments] и до преобразования не «В исходном варианте утечки нет, после бабеля появилась», а наоборот, потому что это сложение с arguments, что приводит к деоптимизации. И как раз babel не вправе делать из версии с утечкой версию где нет утечки оптимизации (даже если умеет и знает что такая проблема есть), потому что в исходной версии она есть.

И если цель использовать arguments в качестве оператора spread
То в случае перехода на rest параметры:
function test(...args) {
  var newArgs = [...args]
}

babel преобразует его правильным образом:
function test() {
  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  var newArgs = [].concat(args);
}

UFO just landed and posted this here
При всём уважении, рано вам ещё руководства писать. Разработчик проходит три этапа развития: концентрация на синтаксисе языка, концетрация на паттернах, концентрация на архитектуре. Вы сейчас на первом, когда перейдёте на следующий — поменяете своё мнение о предыдущем и будете видеть каждый из 500 ляпов этой статьи. Вот тогда милости просим от вас руководство.
Вопрос на засыпку, по методу массива reduce
if (!Array.prototype.reduce) {
  Array.prototype.reduce = function(callback/*, initialValue*/) {
    'use strict';
    if (this == null) {
      throw new TypeError('Array.prototype.reduce called on null or undefined');
    }
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }
    var t = Object(this), len = t.length >>> 0, k = 0, value;
    if (arguments.length == 2) {
      value = arguments[1];
    } else {
      while (k < len && ! k in t) {
        k++; 
      }
      if (k >= len) {
        throw new TypeError('Reduce of empty array with no initial value');
      }
      value = t[k++];
    }
    for (; k < len; k++) {
      if (k in t) {
        value = callback(value, t[k], k, t);
      }
    }
    return value;
  };
}

Почему вот этот кусок кода:
while (k < len && ! k in t) {
    k++; 
}
if (k >= len) {
    throw new TypeError('Reduce of empty array with no initial value');
}
value = t[k++];

Нельзя было заменить на этот:
if (len == 0) {
    throw new TypeError('Reduce of empty array with no initial value');
}
value = t[k++];

Когда вот в этот цикл тут можно войти, при каких условиях в данной функции:
while (k < len && ! k in t) {
    k++; 
}
Array(7).reduce((a, b)=> a + b)
Sign up to leave a comment.

Articles