Добавление пользовательских свойств в функцию - PullRequest
50 голосов
/ 21 декабря 2011

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

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

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

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

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

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

Ответы [ 9 ]

95 голосов
/ 23 декабря 2013

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

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


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

Способ 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: добавить функцию 'extension' в вашу функцию, которая добавляет свойства объекта к себе один за другим:

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 (Array.isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            obj[i] = args[i];
        }
    }
    return obj;
}

var doSomething = 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 

Генетическая функция расширения позволяет использовать более СУХОЙ подход, позволяя вам добавить объект или любой проект к любому другому объекту.


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

var extendableFunction = (function() {
    var extend = function(args) {
        if (Array.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 

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


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

Function.prototype.extend = function(args) {
    if (Array.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 

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


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

var doSomething = (function f(arg1) {
    if(f.name2 === undefined) {
        f.name = 'Tom';
        f.name2 = 'John';
        f.extend = function(args) {
            if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
                for (i in args) {
                    this[i] = args[i];
                }
            }
            return this;
        };
        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 

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


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

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

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

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:

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;
            }
        }
    })();
})();

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

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());
20 голосов
/ 21 декабря 2011

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

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

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

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

Является ли этот вид изменения объектов путем добавления новых свойств к ним хорошей практикой?

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

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

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

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

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

17 голосов
/ 18 июля 2014

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

Да и Да *

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

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


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

2 голосов
/ 09 мая 2014

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

Этот 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.

1 голос
/ 29 ноября 2013

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

RequireJS источник: http://requirejs.org/docs/release/2.1.9/comments/require.js

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

0 голосов
/ 06 ноября 2018

Возможное дополнение к Джону Слегерсу отличный ответ

Возможно ли, что после Джона Слегерса:

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

Добавление Путь 2,5

function doSomething() {
    doSomething.prop = "Bundy";
    doSomething.doSomethingElse = function() {
        alert("Why Hello There! ;)");

    };

    let num = 3;
    while(num > 0) {
        alert(num);
        num--;  
    }
}

sayHi();
sayHi.doSomethingElse();
alert(doSomething.prop);

var ref = doSomething;

ref();
ref.doSomethingElse();
alert(ref.prop);

Для полноты добавляем свойство «variable» и свойство function, прямо в объявлении функции. Таким образом избегая его быть "отключенным". Оставьте внутреннюю работу функции по умолчанию (простой цикл), чтобы показать, что она все еще работает. Нет * * 1016

0 голосов
/ 27 октября 2017

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

Предположим, у нас есть 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])])
0 голосов
/ 29 ноября 2016

Вполне допустимо добавлять свойства или методы к функциональному объекту.Это делается довольно часто.Объект jQuery / $ является примером этого.Это функция с несколькими присоединенными методами.

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

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

0 голосов
/ 22 февраля 2015

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

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