【问题标题】:Adding custom properties to a function向函数添加自定义属性
【发布时间】:2012-01-25 04:34:39
【问题描述】:

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

众所周知,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. 这种通过向对象添加新属性来更改对象的做法是一种好习惯吗?

【问题讨论】:

  • 你做的不对。阅读这篇文章:packtpub.com/article/using-prototype-property-in-javascript 您不能像这样将属性 x 添加到函数 fn:fn.x = 'something'。你需要使用原型。
  • 嗯,它有点工作,就像@nnnnnn 的回答一样,看起来它并不合适。您能否更具体地说明“为什么”?
  • @alessioalex - 您绝对可以直接向函数添加属性。它并不总是最合适的方式,但它(故意)是语言的一部分并且工作正常。 (向原型添加属性有不同的目的。)不过感谢该文章链接,我将保存它,以便将其推荐给其他人。
  • 第二,链接已经添加到收藏夹了。

标签: javascript function oop object custom-properties


【解决方案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:创建一个extendableFunction对象并用它来将一个extend函数附加到一个函数上:

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 原型中添加一个“扩展”函数:

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

【讨论】:

  • 帮了我很多忙!感谢您提供有用的信息!
  • 太短了,需要详细说明。
  • Way 1 的输出应该与 Way 1(替代语法) 具有相同的输出。此外,由于这些都是非常有用的示例,我认为值得添加几句话来解释该输出,即 doSomething.name 输出 doSomething 因为 Function.name 是一个内置属性,而 doSomething.name2 不是, 但调用 doSomething() 会覆盖内置的 name 并定义一个新属性 name2
【解决方案2】:

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

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

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

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

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

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

话虽如此,对我来说,将属性添加到 myMethod 函数并没有什么意义,将其他属性添加到您的 something 对象会更常见(您的 myMethod 函数会,如果正确调用,可以通过this 关键字访问something 的其他属性。

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

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

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

【讨论】:

  • 哇哦,我不知道我怎么会犯这样的错误,我用!==而不是===。谢谢您的回答。这个问题只是作为一个理论上的问题出现在我的脑海中,我也看不到它的可能(和可读)用途,只是检查灵活性。
  • @Przemek 你说的对吗?如果您使用!==,则条件永远不会为真,不是吗?
【解决方案3】:

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

是*

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

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


* 我认为值得注意的是,您可能不会经常看到这种情况。大多数开发人员可能没有意识到这是可能的。有些人对每一滴表现都很疯狂..."JavaScript engines optimize based on the 'shape' of an object'..."blah blah blah... 但是我认为你可以遵循你对对象的规则,你会做得很好。

【讨论】:

    【解决方案4】:

    将属性附加到函数是重载() 运算符的一种漂亮(可以说是缓慢/hack-ish)方式,而后者通常用于实现functors:具有一项非常重要的工作,它的所有其他功能(如果有的话)只是一堆帮手。基本上,您还可以将这些函子解释为状态为公共的“有状态”函数(例如,大多数内联函数具有私有状态,即来自本地范围的状态)。

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

    如您所见,这非常适合以翻译为唯一目的的翻译人员。当然,这些对象类型的例子还有很多,但远没有像经典的UserAnimalCar等具有多样化功能的类型那样普遍。对于这些类型,您只想在极少数情况下添加自定义属性。通常,您希望将它们定义为更完整的类,并通过this 访问它们的公共属性,它是prototype

    【讨论】:

      【解决方案5】:

      我意识到我迟到了几年,但我想我会添加这个例子——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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

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

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

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

          【讨论】:

            【解决方案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 定义,所以我们必须将它定义为Object 的新prototype

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

            【讨论】:

              【解决方案9】:

              可能会添加到 John Slegers 的好答案

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

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

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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  猜你喜欢
                  • 2015-03-09
                  • 2018-08-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-04-15
                  相关资源
                  最近更新 更多