Подтвердить что ты не робот

Добавление пользовательских свойств к функции

Поиск подходящего ответа оказалось трудным из-за существования многих других проблем, связанных с моими ключевыми словами, поэтому я попрошу об этом здесь.

Как известно, функции в javascript являются объектами, и у них есть свои собственные свойства и методы (точнее, функции istances, унаследованные от Function.prototype).

Я рассматривал возможность добавления пользовательских свойств для одной функции (метода), пропустить "почему?". и перейдите прямо к коду:

var something = {
    myMethod: function () {
        if (something.myMethod.someProperty === undefined) {
            something.myMethod.someProperty = "test";
        }
        console.log(something.myMethod);
    }
}

При проверке с помощью Firebug DOM explorer свойство определяется как ожидаемое. Однако, поскольку я не считаю себя экспертом по javascript, у меня есть следующие вопросы:

  • Можно ли считать этот метод "правильным" и соответствовать стандартам? Он работает в Firefox, но в веб-браузерах есть много вещей, которые ожидаются в браузерах и ни в коем случае не являются стандартами.
  • Является ли этот вид изменяющихся объектов, добавляя к ним новые свойства?
4b9b3361

Ответ 1

Немного сложно дать очень важный ответ на ваш вопрос, потому что вы вроде как сказали: "Вот мое решение, все в порядке?" не объясняя, какую проблему вы пытаетесь решить (вы даже прямо сказали, что не собираетесь объяснять "почему" ). Ваш код выглядит действительным JavaScript, который будет работать, но он также выглядит менее оптимальным способом делать вещи.

Если вы объясните, что вы на самом деле хотите достичь, вы можете получить хорошие предложения по лучшим способам структурирования вашего кода. Тем не менее, я дам вам какой-то ответ:

Можно ли считать этот метод "правильным" и стандартом? Он работает в Firefox, но в веб-браузерах работает так, как ожидалось, и ни в коем случае не являются стандартами.

Функции - это объекты (как вы сказали), и, следовательно, можно добавлять к ним свойства. Это не стандартная проблема как таковая, поскольку она является основной частью JavaScript, поддерживаемой всеми браузерами.

Является ли этот вид изменяющихся объектов, добавляя к ним новые свойства?

Это ваш объект, вы можете добавить любые свойства, которые вам нравятся. Вся цель объектов заключается в том, что у них есть свойства, которыми вы можете манипулировать. Я не могу действительно предусмотреть способ использования объектов, которые не связаны с их изменением, включая добавление, удаление и обновление свойств и методов.

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

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

Указанный код не добавляет свойства, он проверяет, существует ли уже свойство someProperty, и если оно присваивает ему новое значение.

Вам может быть полезно прочитать некоторые статьи, например, в MDN:

Ответ 2

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

Добавление ваших собственных настраиваемых свойств в функцию может быть выполнено различными способами, которые должны работать в каждом браузере.


Добавление собственных пользовательских свойств в функцию

Способ 1: добавление свойств при запуске функции:

var doSomething = function() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Способ 1 (альтернативный синтаксис):

function doSomething() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

Путь 1 (второй альтернативный синтаксис):

var doSomething = function f() {
    f.name = 'Tom';
    f.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

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


Способ 2: добавление свойств после определения функции:

function doSomething() {
    return 'Beep';
};

doSomething.name = 'Tom';
doSomething.name2 = 'John';

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

Теперь вам не нужно запускать свою функцию, прежде чем вы сможете получить доступ к своим свойствам. Однако недостатком является то, что ваши свойства чувствуют себя отключенными от вашей функции.


Способ 3: завершает вашу функцию в анонимной функции:

var doSomething = (function(args) {
    var f = function() {
        return 'Beep';
    };
    for (i in args) {
        f[i] = args[i];
    }
    return f;
}({
    'name': 'Tom',
    'name2': 'John'
}));

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Обернув свою функцию в анонимной функции, вы можете собирать свои атрибуты в объект и использовать цикл для добавления этих атрибутов один за другим в анонимной функции. Таким образом, ваши атрибуты более связаны с вашей функцией. Этот метод также очень полезен, когда ваши атрибуты необходимо скопировать из существующего объекта. Недостатком, однако, является то, что вы можете добавлять только несколько атрибутов одновременно, когда вы определяете свою функцию. Кроме того, это не приводит к DRY-коду, если добавление свойств к функции - это то, что вы хотите часто делать.


Способ 4: добавляет функцию "продлить" к вашей функции, которая добавляет свойства объекта к себе по одному:

var doSomething = function() {
    return 'Beep';
};

doSomething.extend = function(args) {
    for (i in args) {
        this[i] = args[i];
    }
    return this;
}

doSomething.extend({
    'name': 'Tom',
    'name2': 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Таким образом, вы можете в любое время расширить несколько свойств и/или свойства копирования из другого проекта. Опять же, ваш код не является СУХОЙ, если это то, что вы делаете чаще.


Способ 5: Сделать общую функцию "продлить":

var extend = function(obj, args) {
    if (isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            this[i] = args[i];
        }
    }
    return obj;
}

var Job = extend(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Функция генетического расширения позволяет использовать более DRY-подход, позволяя вам добавить объект или любой проект к любому другому объекту.


Способ 6: Создайте объект extendableFunction и используйте его для присоединения функции расширения к функции:

var extendableFunction = (function() {
    var extend = function(args) {
        if (isArray(args) || (args !== null && typeof args === 'object')) {
            for (i in args) {
                this[i] = args[i];
            }
        }
        return this;
    };
    var ef = function(v, obj) {
        v.extend = extend;
        return v.extend(obj);
    };

    ef.create = function(v, args) {
        return new this(v, args);
    };
    return ef;
})();

var doSomething = extendableFunction.create(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Вместо того, чтобы использовать общую функцию 'extend', этот метод позволяет создавать функции, к которым приложен метод 'extend'.


Способ 7: Добавить функцию 'extend' в прототип функции:

Function.prototype.extend = function(args) {
    if (isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            this[i] = args[i];
        }
    }
    return this;
};

var doSomething = function() {
    return 'Beep';
}.extend({
    name : 'Tom',
    name2 : 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

Большим преимуществом этого метода является то, что он делает добавление новых свойств в функцию очень простой и СУХОЙ, а также полностью OO. Кроме того, это довольно хорошо для памяти. Однако недостатком является то, что это не очень перспективное доказательство. В случае, если будущие браузеры когда-либо добавят собственную функцию расширения для прототипа Function, это может сломать ваш код.


Способ 8: Выполнить функцию рекурсивно один раз, а затем вернуть ее:

var doSomething = (function f(arg1) {
    if(f.name2 === undefined) {
        f.name = 'Tom';
        f.name2 = 'John';
        f.extend = function(obj, args) {
            if (isArray(args) || (args !== null && typeof args === 'object')) {
                for (i in args) {
                    this[i] = args[i];
                }
            }
            return obj;
        };
        return f;
    } else {
        return 'Beep';
    }
})();

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

Выход:

doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

Выполнить функцию один раз и проверить, установлено ли одно из ее свойств. Если не задано, установите свойства и верните себя. Если установлено, выполните функцию. Если вы включите функцию "расширить" в качестве одного из свойств, вы можете позже выполнить это, чтобы добавить новые свойства.


Добавление ваших собственных настраиваемых свойств к объекту

Несмотря на все эти варианты, я бы все же рекомендовал не добавлять свойства к функции. Намного лучше добавлять свойства к объектам!

Лично я предпочитаю одиночные классы со следующим синтаксисом.

var keyValueStore = (function() {
    return {
        'data' : {},
        'get' : function(key) { return keyValueStore.data[key]; },
        'set' : function(key, value) { keyValueStore.data[key] = value; },
        'delete' : function(key) { delete keyValueStore.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in keyValueStore.data) l++;
            return l;
        }
    }
})();

Преимущество этого синтаксиса заключается в том, что он позволяет использовать как общедоступные, так и частные переменные. Например, именно так вы делаете переменную 'data' private:

var keyValueStore = (function() {
    var data = {};

    return {
        'get' : function(key) { return data[key]; },
        'set' : function(key, value) { data[key] = value; },
        'delete' : function(key) { delete data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in data) l++;
            return l;
        }
    }
})();

Но вы хотите несколько экземпляров хранилища данных, скажете вы? Нет проблем!

var keyValueStore = (function() {
    var count = -1;

    return (function kvs() {
        count++; 
        return {
            'data' : {},
            'create' : function() { return new kvs(); },
            'count' : function() { return count; },
            'get' : function(key) { return this.data[key]; },
            'set' : function(key, value) { this.data[key] = value; },
            'delete' : function(key) { delete this.data[key]; },
            'getLength' : function() {
                var l = 0;
                for (p in this.data) l++;
                return l;
            }
        }
    })();
})();

Наконец, вы можете отделить свойства экземпляра и singleton и использовать прототип для общедоступных методов экземпляра. Это приводит к следующему синтаксису:

var keyValueStore = (function() {
    var count = 0; // Singleton private properties

    var kvs = function() {
        count++; // Instance private properties
        this.data = {};  // Instance public properties
    };

    kvs.prototype = { // Instance public properties
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

С помощью этого синтаксиса вы можете:

  • несколько экземпляров объекта
  • частные переменные
  • переменные класса

Вы используете его следующим образом:

kvs = keyValueStore.create();
kvs.set('Tom', "Baker");
kvs.set('Daisy', "Hostess");
var profession_of_daisy = kvs.get('Daisy');
kvs.delete('Daisy');
console.log(keyValueStore.count());

Ответ 3

"necromancing" здесь, но я думаю, что каждый большой вопрос требует простых ответов:

Да и Да *

Прикрепляя свойства к функции, вы очищаете область действия, улучшаете читаемость и добавляете логическую связность. Дополнительным преимуществом является то, что вы документируете взаимосвязь между функцией и переменными. Я думаю, что превосходный дизайн, намного лучше, чем добавление переменных в область some examples of attaching properties to instances of functions

Создал несколько интересных примеров здесь и здесь. ЗДЕСЬ И ЗДЕСЬ


& AST; Я думаю, стоит отметить, что вы, вероятно, не увидите этого очень часто. большинство разработчиков, вероятно, не понимают, что это возможно. Некоторые люди сходят с ума от каждой капли производительности... "JavaScript-движки оптимизируются на основе" формы "объекта" ..." blah blah blah...  ut Я думаю, вы можете следовать правилу, которое у вас есть для объектов, и все будет хорошо.

Ответ 4

Я понимаю, что я опаздываю на это, но думал, что добавлю этот пример: requirejs устанавливает свойство функции amd для функции define(), что весьма удобно, поскольку шаблон UMD использует его для обнаружения что функция define(), которая в области видимости является фактически функцией define() AMD.

RequireJS source: http://requirejs.org/docs/release/2.1.9/comments/require.js

Образец UMD, показывающий это использование: https://github.com/umdjs/umd/blob/master/amdWeb.js

Ответ 5

Прикрепление свойств к функциям - прекрасный (возможно, вялый/хак-иш) способ перегрузки оператора (), который, в свою очередь, обычно используется для реализации functors: Типы объектов, которые имеют одну действительно важную работу, и все ее другие функции (если они есть) - это всего лишь куча помощников. Вы могли бы также интерпретировать эти функторы, как, в основном, функцию "stateful", в которой состояние является общедоступным (большинство встроенных функций, например, имеют личное состояние, то есть состояние из локальной области).

Этот JSFiddle демонстрирует, как мы можем использовать функцию с пользовательскими свойствами для функции translator с дополнительными утилитами:

/**
 * Creates a new translator function with some utility methods attached to it.
 */
var createTranslator = function(dict) {
    var translator = function(word) {
        return dict[word];
    };

    translator.isWordDefined = function(word) {
        return dict.hasOwnProperty(word);
    };

    // Add more utilities to translator here...

    return translator;
};


// create dictionary
var en2deDictionary = {
    'banana': 'Banane',
    'apple': 'Apfel'
};

// simple use case:
var translator = createTranslator(en2deDictionary);
var pre = $('<pre>');
$("body").append(pre);

pre.append(translator('banana') + '\n');
pre.append(translator('apple') + '\n');
pre.append(translator.isWordDefined('w00t') + '\n');

Как вы можете видеть, это идеально подходит для переводчика, единственной целью которого является переводить. Конечно, есть много примеров тезисов типов объектов, но они далеко не так распространены, как типы с разнообразной функциональностью, такие как классические типы User, Animal Car и т.д. Для этих типов типов вы хотите добавить только пользовательские свойства в очень немногих случаях. Обычно вы хотите определить их как более полные классы, и их общедоступные свойства достижимы с помощью this и prototype.

Ответ 6

Если вы просто хотите добавить пользовательские свойства к функции, вам нужно добавить эти свойства только в Function.prototype. Например:

Function.prototype.SomeNewProperty = function () {//Do something awesome here...}

Ответ 7

Абсолютно приемлемо добавлять свойства или методы к объекту функции. Это делалось довольно часто. Пример jQuery/$- пример этого. Это функция с множеством приложенных методов.

Когда свойства добавляются в конструктор, они называются "статическими" свойствами и могут быть вызваны без экземпляра класса. например Object.create.

Мне не хватает репутации для написания комментария, поэтому я скажу здесь: обычно считается, что плохая практика расширяет прототипы встроенных объектов, особенно если ваш код должен играть с кодом других людей. Это может иметь непредсказуемые последствия, которые трудно отслеживать.

Ответ 8

Я согласен, что это сложный вопрос, который может иметь несколько ответов, поэтому я предпочитаю сделать другой пример:

Предположим, что есть JavaScript Array, заполненный генератором:

var arr = [...new Array(10).keys()];

то есть

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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

arr = arr.map((value,index) => ++value)

Мы только что сделали value=value+1 и вернемся, так что теперь массив будет выглядеть как

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Хорошо, теперь предполагается, что JavaScript Object похож на

var obj=new Object()

который был определен как предыдущий массив (по какой-то сумасшедшей причине):

arr.forEach((value,index) => obj[value]=value)

то есть.

{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}

В этот момент мы не можем применить один и тот же метод map, так как он не определен для Object, поэтому мы должны определить его как новый prototype для Object:

Object.defineProperty(Object.prototype, 'mapObject', {
      value: function(f, ctx) {
          ctx = ctx || this;
          var self = this, result = {};
          Object.keys(self).forEach(function(k) {
              result[k] = f.call(ctx, self[k], k, self);
          });
          return result;
      }
    });

В этот момент мы могли бы сделать так же, как и для массива:

obj=obj.mapObject((value,key) => ++value )

так что имеем:

{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}

Вы можете видеть, что мы обновили только значения:

[...Object.keys(obj)]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

и мы можем вернуться в выходной массив:

[...Object.keys(obj).map(k=>obj[k])]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Здесь он работает:

// Array.map
var arr = [...new Array(10).keys()];
console.log("array", arr)
arr = arr.map((value, index) => ++value)
console.log("mapped array", arr)
// new property
Object.defineProperty(Object.prototype, 'mapObject', {
  value: function(f, ctx) {
    ctx = ctx || this;
    var self = this,
      result = {};
    Object.keys(self).forEach(function(k) {
      result[k] = f.call(ctx, self[k], k, self);
    });
    return result;
  }
});

// Object.mapObject
var obj = new Object()
arr = [...new Array(10).keys()];
arr.forEach((value, index) => obj[value] = value)
console.log("object", obj)
obj = obj.mapObject((value, key) => ++value)
console.log("mapped object", obj)
console.log("object keys", [...Object.keys(obj)])
console.log("object values", [...Object.keys(obj).map(k => obj[k])])