将自定义属性添加到函数

时间:2011-12-21 10:44:50

标签: javascript function oop object custom-properties

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

正如我们所知,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. 这种改变对象是通过向他们添加新属性来改善它吗?

10 个答案:

答案 0 :(得分:89)

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

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


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

方式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:为您的函数添加一个'extend'函数,它将对象的属性逐个添加到自身:

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 

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


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

这种技术不是使用通用的'extend'函数,而是允许您生成附加了'extend'方法的函数。


方式7:在函数原型中添加'extend'函数:

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 

这项技术的一大优势在于它可以非常轻松地为函数添加新属性,以及完全OO。此外,它非常友好。然而,缺点是它不是未来的证据。如果未来的浏览器曾向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 

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


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

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

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

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

答案 1 :(得分:20)

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

如果您解释了您实际想要实现的目标,那么您可以就更好的方法构建代码获得一些好的建议。不过,我会给你一些答案:

  

这种方法可以被认为是“正确的”并且符合标准吗?它可以在Firefox中运行,但是在Web浏览器中有许多工作正常,并且绝不是标准。

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

  

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

这是你的对象,你可以添加你喜欢的任何属性。对象的重点是它们具有可以操作的属性。我无法真正想到一种使用不涉及改变它们的对象的方法,包括添加,删除和更新属性和方法。

话虽如此,对我来说,向myMethod函数添加属性并没有多大意义,更常见的是将其他属性添加到something对象中({{1}如果正确调用,函数将通过myMethod关键字访问something的其他属性。

如果您使用函数作为构造函数,通常将方法添加到关联的原型并为每个实例添加(非方法)属性是有意义的,但是你可以在适当的时候用其他方式做其中之一或两者。 (注意“方法”本质上只是恰好引用函数的属性。)

您显示的特定代码不会添加属性,它会测试this属性是否已存在,如果存在,则为其指定新值。

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

答案 2 :(得分:17)

“暗示”,但我认为每个好问题都需要简单的答案:

是*

通过将属性附加到函数,可以清理范围,提高可读性并增加逻辑内聚力。另一个好处是您记录了函数和变量之间的关系。我认为这是一个优秀的设计,比在范围上添加变量要好得多 some examples of attaching properties to instances of functions

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


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

答案 3 :(得分:2)

将属性附加到函数是重载()运算符的一种漂亮(可以说是迟缓/黑客)的方式,而这通常用于实现functors:具有一个非常重要的工作的对象类型及其所有其他功能(如果有的话)只是一堆帮助器。你也可以将这些仿函数解释为一个有状态的"状态为public的函数(例如,大多数内联函数具有私有状态,即来自本地范围的状态)。

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

正如您所看到的,这对于翻译的唯一目的是翻译是完美的。当然,还有更多这类对象的例子,但它们远不如具有多样化功能的类型那么常见,例如经典UserAnimal Car等类型。对于那些类型,您只想在极少数情况下添加自定义属性。通常,您希望将这些类定义为更完整的类,并且可以通过this及其prototype访问其公共属性。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:0)

我同意这是一个很难回答的问题,所以我更愿意做一个不同的例子:

我们假设有一个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]

好的,现在应该有一个像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 1}}:

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]

这是在工作:

&#13;
&#13;
// 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])])
&#13;
&#13;
&#13;

答案 8 :(得分:0)

可能为John Slegers带来了很好的答案

约翰·斯莱格斯(John Slegers)之后,难道不是吗?

  

方法2:在定义函数后添加属性

添加 Way 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);

出于完整性考虑,直接在函数声明中同时添加“变量”属性和函数属性。因此避免了它被“断开”。保留该函数的内部默认工作方式(一个简单的循环)以表明它仍然有效。不?

答案 9 :(得分: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和setters

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