Попытка понять источник underscore.js - вызовите и примените используемый в библиотеке - PullRequest
7 голосов
/ 15 января 2012

В потрясающей библиотеке Underscore.js Джереми Ашкенаса я попытался понять одну вещь в исходном файле. Я не понимаю этого:

var slice = Array.prototype.slice;
args = slice.call(arguments, 2);

Так что:

args = Array.prototype.slice.call(arguments, 2);

.call или .apply - методы функций. Но здесь, к каким функциям относится .call? Первый параметр должен быть контекстом, но arguments является контекстом? Вторым параметром должны быть параметры, передаваемые в функции. Вот они номер 2. Что это значит? Иногда в библиотеке он использует 1 или 0. Это число параметров, передаваемых в функции?

_.bind = function bind(func, context) {
    var bound, args;
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError;
    args = slice.call(arguments, 2);
    return bound = function() {
      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
      ctor.prototype = func.prototype;
      var self = new ctor;
      var result = func.apply(self, args.concat(slice.call(arguments)));
      if (Object(result) === result) return result;
      return self;
    };
  };

Вопрос 2: Я не совсем понимаю логику этой функции. Нужна помощь, чтобы понять. Пример должен быть очень полезным.

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    return _.map(obj, function(value) {
      return (method.call ? method || value : value[method]).apply(value, args);
    });
  };

Спасибо за помощь.

1 Ответ

7 голосов
/ 15 января 2012

Функция "slice" в прототипе Array ожидает, что this будет ссылаться на массив, с которым она должна работать.Другими словами, если у вас есть реальный массив:

var myArray = [1, 2, 3];

и вы вызываете slice():

var sliced = myArray.slice(1);

Тогда в этом вызове slice(), this относится кмассив "myArray".Как отмечает Райнос в комментарии:

myArray.slice(1)

совпадает с

myArray.slice.call(myArray, 1);

Таким образом, когда вы используете call() для вызова функции и передачи ее arguments в качестве контекстаобъект, код slice() работает на arguments.Другие параметры, передаваемые через .call(), являются просто параметром или параметрами самого slice().В моем примере выше обратите внимание, что я передал 1. функции.

Теперь, что касается второго вопроса, эта функция .invoke() сначала изолирует аргументы, переданные после первых двух.Это означает, что когда вы используете _.invoke(), вы передаете ему два или более аргумента: первый - это список для обработки, второй - метод, и (необязательные) последующие аргументы передаются методудля каждого элемента списка.

Этот вызов _.map() сложен (и на самом деле я думаю, что в нем есть небольшая чепуха).Он выполняет итерации по списку, вызывая функцию для каждого значения в списке.Что эта функция делает, чтобы сначала определить, является ли параметр «method» действительно функцией.Если это так, то она вызывает эту функцию через .apply() с элементом списка в качестве контекста.Если «метод» является , а не функцией, то он предполагает, что это имя свойства каждого элемента списка и что свойства являются функциями.

Так, например, с простым спискомэто довольно просто:

var myList = [1, 2, 3];
var result = _.invoke(myList, function(n) { return this * n; }, 2);

Это даст результат [2, 4, 6], потому что функция, которую я передал, умножает свой объект контекста (this) на переданный параметр, и я передал 2 в вызове _.invoke().

С более сложным списком я могу использовать второй вариант _.invoke() и вызвать метод для каждого объекта в списке:

var getName = function(prefix) { return prefix + " " + this.name; };
var list = [
  { name: "Bob", getName: getName },
  { name: "Sam", getName: getName },
  { name: "Lou", getName: getName }
];

var result = _.invoke(list, "getName", "Congressman");

, который вызовет "getNamemsgstr "работать с каждым объектом в списке и возвращать список из результатов.Эффектом будет список ["Congressman Bob", "Congressman Sam", "Congressman Lou"].

Теперь об этой ерунде.В коде для _.invoke():

return _.map(obj, function(value) {
  return (method.call ? method || value : value[method]).apply(value, args);
});

Это подвыражение method || value будет всегда возвращать значение "метода" или, по крайней мере, почти всегда исключать какой-то экзотический прием.Если method.call является правдивым, то ссылка на method также должна быть правдивой.Кроме того, если бы это был мой код, я бы осмотрел method за пределами _.map() обратного вызова, чтобы решение не приходилось принимать снова и снова.Может быть что-то вроде:

return _.map(obj, method.call ?
  function(value) { method.apply(value, args); } :
  function(value) { value[method].apply(value, args); }
);
...