Добавление пользовательских свойств в функцию
Поиск подходящего ответа оказался трудным из-за существования многих других проблем, связанных с моими ключевыми словами, поэтому я спрошу это здесь.
Как мы знаем, функции в javascript являются объектами, и они имеют свои собственные свойства и методы (точнее, экземпляры функций, унаследованные от Function.prototype).
Я думал о добавлении пользовательских свойств для одной функции (метода), давайте пропустим "почему?" часть и перейти прямо к коду:
var something = {
myMethod: function () {
if (something.myMethod.someProperty === undefined) {
something.myMethod.someProperty = "test";
}
console.log(something.myMethod);
}
}
При проверке с помощью DOM-обозревателя Firebug свойство определяется так, как ожидается. Однако, поскольку я не считаю себя экспертом по JavaScript, у меня есть следующие вопросы:
- Можно ли считать этот метод "надлежащим" и соответствующим стандартам? Он работает в Firefox, но есть много вещей, которые работают, как и ожидалось, в веб-браузерах, и это ни в коем случае не стандарты
- Является ли этот вид изменения объектов путем добавления новых свойств к ним хорошей практикой?
9 ответов
Немного трудно дать очень значимый ответ на ваш вопрос, потому что вы как бы сказали: "Вот мое решение, все в порядке?" не объясняя, какую проблему вы пытаетесь решить (вы даже прямо сказали, что не собираетесь объяснять "почему"). Ваш код выглядит как допустимый JavaScript, который будет работать, но он также выглядит как неоптимальный способ работы.
Если вы объясните, чего вы на самом деле хотите достичь, вы можете получить несколько хороших советов о том, как лучше структурировать ваш код. Тем не менее, я дам вам какой-то ответ:
Можно ли считать этот метод "надлежащим" и соответствующим стандартам? Он работает в Firefox, но есть много вещей, которые работают, как и ожидалось, в веб-браузерах, и это ни в коем случае не стандарты
Функции - это объекты (как вы сказали), и, таким образом, к ним можно добавить свойства. На самом деле это не проблема стандартов, так как это основная часть JavaScript, которую поддерживают все браузеры.
Является ли этот вид изменения объектов путем добавления новых свойств к ним хорошей практикой?
Это ваш объект, вы можете добавить любые свойства, которые вам нравятся. Суть объектов в том, что они обладают свойствами, которыми вы можете манипулировать. Я не могу предусмотреть способ использования объектов, который не включает их изменение, включая добавление, удаление и обновление свойств и методов.
Сказав это, для меня не имеет смысла добавлять свойства к myMethod
функции, было бы более обычным, чтобы добавить другие свойства к вашему something
объект (ваш myMethod
Функция при правильном вызове будет иметь доступ к другим свойствам something
через this
ключевое слово).
Если вы используете функцию в качестве конструктора, обычно имеет смысл добавлять методы к связанному прототипу и добавлять (не методические) свойства к каждому экземпляру, но вы можете сделать один или оба других способа, когда это необходимо. (Отметив, что "метод" - это, по сути, просто свойство, которое ссылается на функцию.)
Показанный вами код не добавляет свойства, он проверяет, someProperty
свойство уже существует, и если это так, присваивает ему новое значение.
Вы могли бы извлечь выгоду из чтения некоторых статей, таких как эти в MDN:
First of all, it's important to realise that standard function properties (arguments, name, caller & length) cannot be overwritten. So, forget about adding a property with that name.
Adding your own custom properties to a function can be done in different ways that should work in every browser.
Adding your own custom properties to a function
Way 1: adding properties while running the function:
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
Way 1 (alternate syntax):
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
Way 1 (second alternate syntax):
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
A problem with this strategy is that you need to run your function at least once to assign the properties. For many functions, that's obviously not what you want. So let's consider the other options.
Way 2: adding properties after defining the function:
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
Now, you don't need to run your function first before you're able to access your properties. However, a disadvantage is that your properties feel disconnected from your function.
Way 3: wrap your function in anonymous function:
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
Wrapping your function in an anonymous function, you can collect your attributes into an object and use a loop to add those attributes one-by-one within the anonymous function. That way, your attributes feel more connected to your function. This technique is also very useful for when your attributes need to be copied from an existing object. A disadvantage, however, is that you can only add multiple attributes at the same time when you define your function. Also, it doesn't exactly result in DRY code if adding properties to a function is something you want to do often.
Way 4: add an 'extend' function to your function, that adds the properties of an object to itself one by one:
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
This way, you can extend multiple properties and/or copy properties from another project at any time. Again, however, your code isn't DRY if this is something you do more often.
Way 5: Make a generic 'extend' function:
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
A genetic extend function allows for a more DRY approach, allowing you to add the object or any project to any other object.
Way 6: Create an extendableFunction object and use it to attach an extend function to a function:
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
Вместо того, чтобы использовать универсальную функцию "extends", этот метод позволяет вам генерировать функции, к которым прикреплен метод "extension".
Способ 7: добавить функцию 'extension' в прототип функции:
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
Большим преимуществом этого метода является то, что он делает добавление новых свойств в функцию очень простым и СУХИМЫМ, а также полностью ОО. Кроме того, это довольно дружелюбно для памяти. Недостатком, однако, является то, что это не очень будущее. В случае, если будущие браузеры когда-либо добавят встроенную функцию "расширения" в прототип функции, это может нарушить ваш код.
Способ 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
Запустите функцию один раз и проверьте, установлено ли одно из ее свойств. Если не установлено, установите свойства и верните себя. Если установлено, выполните функцию. Если в качестве одного из свойств вы добавите функцию '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());
"Некромантия" здесь, но я думаю, что на каждый великий вопрос нужны простые ответы:
Да и да *
Прикрепляя свойства к функции, вы очищаете область, улучшаете читаемость и добавляете логическую сплоченность. Дополнительным преимуществом является то, что вы документируете отношения между функцией и переменными. Я думаю, что это превосходный дизайн, гораздо лучше, чем добавление переменных в область видимости.
Создал несколько забавных примеров здесь и здесь. ЗДЕСЬ И ЗДЕСЬ
* Я думаю, стоит отметить, что вы, вероятно, не увидите это очень часто. большинство разработчиков, вероятно, не осознают, что это возможно. Некоторые люди без ума от каждого падения производительности... "движки JavaScript оптимизируются на основе" формы "объекта"..." бла-бла-бла... но я думаю, что вы можете следовать правилу, которое у вас есть для объектов, и вы все будет хорошо.
Присоединение свойств к функциям - прекрасный (возможно, медленный / хакерский) способ перегрузки ()
оператор, который, в свою очередь, обычно используется для реализации функторов: типы объектов, у которых есть одно действительно важное задание, и все другие его функциональные возможности (если они есть) - всего лишь кучка помощников. Вы также можете интерпретировать эти функторы как, по сути, "сохраняющую состояние" функцию, где состояние является публичным (например, большинство встроенных функций имеют частное состояние, то есть состояние из локальной области видимости).
Этот 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
,
Я понимаю, что опоздал на это на годы, но подумал, что добавлю этот пример - requirejs устанавливает свойство под названием "amd" в функции define(), что очень удобно, поскольку шаблон UMD использует его для определения того, что определение () Функция, которая находится в области видимости, на самом деле является функцией AMD define().
RequireJS источник: http://requirejs.org/docs/release/2.1.9/comments/require.js
Шаблон UMD, показывающий это использование: https://github.com/umdjs/umd/blob/master/amdWeb.js
Возможное дополнение к Джону Слегерсу отличный ответ
Возможно ли, что после Джона Слегерса:
Способ 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", так и свойство функции, прямо в объявлении функции. Таким образом избегая его быть "отключенным". Оставил внутреннюю работу функции по умолчанию (простой цикл), чтобы показать, что она все еще работает. Нет?
Я согласен, что это сложный вопрос, который может иметь несколько ответов, поэтому я предпочитаю привести другой пример:
Давайте предположим, что 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])])
Вполне допустимо добавлять свойства или методы к функциональному объекту. Это делается довольно часто. Объект jQuery/$ является примером этого. Это функция с несколькими прикрепленными методами.
Когда свойства добавляются в конструктор, они называются "статическими" свойствами и могут быть вызваны без экземпляра класса. например, Object.create.
У меня недостаточно представителей, чтобы написать комментарий, поэтому я скажу здесь: обычно считается плохой практикой расширять прототипы встроенных объектов, особенно если ваш код должен играть с кодом других людей. Это может иметь непредсказуемые последствия, которые трудно отследить.
test = (function() {
var a = function() {
console.log("test is ok");
};
a.prop = "property is ok";
a.method = function(x, y) {
return x + y;
}
return a
})()
test();
console.log(test.prop);
console.log(test.method(3, 4));
В качестве альтернативы вам нужно использовать геттеры и сеттеры
var person = {
firstName: 'Jimmy',
lastName: 'Smith',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName(name) {
var words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
}
console.log(person.firstName);
console.log(person.lastName);
console.log(person.fullName);
person.fullName = "Tom Jones";
console.log(person.fullName);
Если вы просто хотите добавить пользовательские свойства в функцию, вам нужно добавить эти свойства только в Function.prototype. Например:
Function.prototype.SomeNewProperty = function () {//Do something awesome here...}