66

由于存在与我的关键字相关的许多其他问题,因此很难找到合适的答案,所以我会在这里问这个问题。

众所周知,javascript 中的函数是对象,它们有自己的属性和方法(更准确地说,是函数实例,继承自 Function.prototype)。

我正在考虑为一个函数(方法)添加自定义属性,让我们跳过“为什么?” 部分并直接进入代码:

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

当使用 Firebug 的 DOM 浏览器检查时,该属性按预期定义。但是,由于我不认为自己是 javascript 专家,所以我有以下问题:

  1. 这种方法可以被认为是“适当的”并且符合标准吗?它可以在 Firefox 中运行,但在 Web 浏览器中可以按预期运行很多东西,而且无论如何都不是标准。
  2. 这种通过向对象添加新属性来更改对象是一种好习惯吗?
4

10 回答 10

131

首先,重要的是要认识到标准函数属性(参数、名称、调用者和长度)不能被覆盖。因此,忘记添加具有该名称的属性。

可以通过不同的方式将您自己的自定义属性添加到函数中,这应该适用于每个浏览器。


将您自己的自定义属性添加到函数

方式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 

这样,您可以随时扩展多个属性和/或从另一个项目复制属性。但是,如果这是您经常执行的操作,那么您的代码也不是 DRY。


方式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 

遗传扩展功能允许使用更 DRY 的方法,允许您将对象或任何项目添加到任何其他对象。


方式 6:创建一个可扩展函数对象并使用它将扩展函数附加到函数:

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 

该技术不是使用通用的“扩展”函数,而是允许您生成附加了“扩展”方法的函数。


方式 7:向函数原型添加一个“扩展”函数:

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 

这种技术的一个很大的优势是它使得向函数添加新属性变得非常容易、干燥并且完全面向对象。此外,它对内存非常友好。然而,一个缺点是它不是很有未来的证据。万一未来的浏览器曾经向函数原型添加原生“扩展”函数,这可能会破坏你的代码。


方式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 

运行一次函数并让它测试是否设置了它的一个属性。如果未设置,则设置属性并返回自身。如果设置,则执行该功能。如果您包含“扩展”功能作为属性之一,您可以稍后执行该功能以添加新属性。


将您自己的自定义属性添加到对象

尽管有所有这些选项,但我仍然建议不要向函数添加属性。向对象添加属性要好得多!

就个人而言,我更喜欢具有以下语法的单例类。

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

这种语法的一个优点是它允许公共和私有变量。例如,这是您将“数据”变量设为私有的方式:

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());
于 2013-12-22T21:01:08.390 回答
29

对你的问题给出一个非常有意义的答案有点困难,因为你有点说“这是我的解决方案,可以吗?” 没有解释您要解决的问题(您甚至明确表示您不会解释“为什么”)。您的代码看起来是可以运行的有效 JavaScript,但它看起来也不是最佳的做事方式。

如果你解释了你真正想要实现的目标,你可能会得到一些关于更好地构建代码的好建议。不过,我会给你一些答案:

这种方法可以被认为是“适当的”并且符合标准吗?它可以在 Firefox 中运行,但在 Web 浏览器中可以按预期运行很多东西,而且无论如何都不是标准。

函数是对象(如您所说),因此可以向它们添加属性。这并不是一个真正的标准问题,因为它是所有浏览器都支持的 JavaScript 的核心部分。

这种通过向对象添加新属性来更改对象是一种好习惯吗?

这是你的对象,你可以添加任何你喜欢的属性。对象的全部意义在于它们具有您可以操作的属性。我真的无法设想一种不涉及更改对象的使用方法,包括添加、删除和更新属性和方法。

话虽如此,对我而言,向函数添加属性并没有真正意义,向对象myMethod添加其他属性会更常见(如果正确调用,您的函数将可以通过关键词)。somethingmyMethodsomethingthis

如果您将函数用作构造函数,通常将方法添加到关联的原型并将(非方法)属性添加到每个实例是有意义的,但是您可以在适当的时候以另一种方式或两种方式执行。(请注意,“方法”本质上只是碰巧引用函数的属性。)

您显示的特定代码不会添加属性,它会测试该someProperty属性是否已经存在,如果存在,则为其分配一个新值。

您可能会从阅读 MDN 上的一些文章中受益:

于 2011-12-21T11:08:37.023 回答
19

“死灵法”在这里,但我认为每个伟大的问题都需要简单的答案:

的,是的*

通过将属性附加到函数,您可以清理范围、提高可读性并增加逻辑凝聚力。另一个好处是您可以记录函数和变量之间的关系。我认为这是一个出色的设计,比在范围上添加变量要好得多 将属性附加到函数实例的一些示例

在这里和这里创建了一些有趣的例子。 这里 和这里


*我认为值得注意的是,您可能不会经常看到这种情况。大多数开发人员可能没有意识到这是可能的。有些人对性能的每一滴都感到疯狂…… “JavaScript 引擎基于对象的‘形状’进行优化……”等等等等等等……但我认为你可以遵循对象的规则,而你会好的。

于 2014-07-18T05:35:20.007 回答
4

Attaching properties to functions is a beautiful (arguably sluggish/hack-ish) way of overloading the () operator, which in turn is usually used to implement functors: Object types that have one really important job, and all its other functionality (if there is any) is just a bunch of helpers. You could also interpret these functors as, basically, a "stateful" function where the state is public (most inline functions for example, have private state, that is state from the local scope).

This JSFiddle demonstrates how we can use a function with custom properties for a translator function with additional utilities:

/**
 * 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');

As you can see, this is perfect for a translator whose sole purpose is to translate. Of course there are many more examples of theses types of objects, but they are by far not as common as types with diversified functionality, such as the classic User, Animal Car etc. types. To those sort of types, you only want to add custom properties in very few cases. Usually, you want to define those as more complete classes, and have their public properties reachable through this and it's prototype.

于 2014-05-09T08:22:46.307 回答
2

我意识到我已经晚了几年,但我想我会添加这个例子——requirejs 在define() 函数上设置了一个名为“amd”的属性,这非常方便,因为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

于 2013-11-29T03:49:16.990 回答
0

如果您只想为函数添加自定义属性,则只需将这些属性添加到 Function.prototype。例如:

Function.prototype.SomeNewProperty = function () {//Do something awesome here...}
于 2015-02-22T17:03:58.990 回答
0

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

或者,您必须使用 getter 和 setter

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

于 2020-03-18T21:40:44.563 回答
0

约翰斯莱杰斯的可能补充很好的答案

有没有可能在约翰·斯莱杰斯之后:

方式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);

为了完整起见,直接在函数声明中放入“变量”属性和函数属性。从而避免它被“断开”。离开函数的内部默认工作方式(一个简单的循环)以表明它仍然有效。不?

于 2018-11-06T00:35:14.367 回答
0

向函数对象添加属性或方法是完全可以接受的。它经常做。jQuery/$ 对象就是一个例子。这是一个附加了很多方法的函数。

当属性被添加到构造函数时,它们被称为“静态”属性,并且可以在没有类实例的情况下调用。例如 Object.create。

我没有足够的代表来写评论,所以我会在这里说:扩展内置对象的原型通常被认为是不好的做法,特别是如果您的代码必须与其他人的代码一起使用。它可能会产生难以追踪的不可预测的后果。

于 2016-11-29T18:39:52.767 回答
0

我同意这是一个可能有多个答案的难题,所以我更愿意举一个不同的例子:

假设有一个Array由生成器填充的 JavaScript :

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]

好的,现在应该有一个Object类似的 JavaScript

var obj=new Object()

就像前面的数组一样定义(出于某种疯狂的原因):

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

IE

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

此时我们不能应用相同的map方法,因为它没有为 an 定义,Object所以我们必须将它定义为 an 的新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])])

于 2017-10-27T08:40:32.727 回答