Что делает функция MooTools Function.prototype.overloadSetter()?

Я просматриваю источник MooTools, чтобы попытаться понять его утилиты .implement() и .extend().

Определение каждого относится к функции, определенной следующим образом:

var enumerables = true;
for (var i in {toString: 1}) enumerables = null;
if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];

Function.prototype.overloadSetter = function(usePlural){
    var self = this;
    return function(a, b){
        if (a == null) return this;
        if (usePlural || typeof a != 'string'){
            for (var k in a) self.call(this, k, a[k]);
            if (enumerables) for (var i = enumerables.length; i--;){
                k = enumerables[i];
                if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
            }
        } else {
            self.call(this, a, b);
        }
        return this;
    };
};

Однако мне трудно понять, что он делает.

Можете ли вы объяснить, как эта функция работает и что она делает?

Ответы

Ответ 1

overloadSetter

overloadSetter, вместе с overloadGetter, являются двумя методами декоратора функций. Функция overloadSetter используется для преобразования функций, которые имеют подпись fn(key, value) для функций, которые могут принимать аргументы объекта, например: fn({key: value}).

Чтобы сделать это, overloadSetter должен обернуть исходную функцию. Эта функция-обертка имеет подпись fn(a, b), которая является ярлыком для fn(key, value). Это фактически становится новой перегруженной версией исходной функции.

Прежде всего, эта перегруженная функция проверяет, имеет ли переданный аргумент key (a) тип строки или нет. Если это не строка, функция предполагает, что мы передаем объект. Таким образом, он выполняет итерацию по каждой паре ключ-значение в объекте и применяет к ней исходную функцию. Если это строка, с другой стороны, она просто применяет эту функцию к значениям аргументов a и b.

Пример

Для иллюстрации предположим, что мы имеем следующую функцию:

var fnOrig = function(key, value){
    console.log(key + ': ' + value); 
};

var fnOver = fnOrig.overloadSetter();

fnOver('fruit', 'banana');
fnOver({'fruit': 'banana', 'vegetable': 'carrot'});

В первом вызове функция fnOver вызывается с двумя аргументами, ключом и значением. Когда функция проверяет тип значения аргумента a, он увидит, что это строка. Поэтому он просто вызовет исходную функцию fnOrig: fnOrig.call(this, 'fruit', 'banana'). Выход консоли - 'fruit: banana'.

Для второго вызова функция fnOver вызывается с аргументом объекта. Поскольку мы передали объект вместо строки, fnOver будет перебирать элементы этого объекта и вызывать функцию fnOrig для каждого из них. Таким образом, fnOrig будет вызываться дважды в этом случае: fnOrig.call(this, 'fruit', 'banana') и fnOrig.call(this, 'vegetable', 'carrot'). Выход консоли - 'fruit: banana' и 'vegetable: carrot'.

Дополнительно

Внутри функции-обертки вы увидите, что там проверили значение usePlural. Это аргумент для самого метода overloadSetter. Если вы установите это значение на true, новая функция будет обрабатывать все аргументы как объекты. Это означает, что даже если вы передадите аргумент строкового ключа, он все равно будет обрабатываться как объект.

Другое дело, что код enumerables, который прелюбирует объявление фактического метода, существует там, потому что он исправляет проблему с некоторыми браузерами, где нативные методы Object не перечисляются в циклах for/in, даже если сам объект реализует его собственную версию.

Ответ 2

Часть, которая заставила меня немного почесывать голову, была

var enumerables = true; for (var i in {toString: 1}) enumerables = null;

что оказывается тестом для ошибки DontEnum, которую имеют некоторые браузеры. На первый взгляд кажется, что он должен просто установить enumerables в null, но с ошибкой DontEnum toString будет подавлено (неправильно, поскольку объект prototype.toString имеет флаг DontEnum) и enumerables оставлен как true.

overloadSetter (или, вернее, результирующая функция), то необходимо проверить по одному за семь свойств, на которые влияет ошибка DontEnum, чтобы увидеть, существуют ли они в аргументе объекта.