将自定义属性添加到函数

Prz*_*mek 53 javascript oop function object custom-properties

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

我们知道,javascript中的函数是对象,它们有自己的属性和方法(更合适的是函数,从Function.prototype继承).

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

var something = {
    myMethod: function () {
        if (something.myMethod.someProperty === undefined) {
            something.myMethod.someProperty = "test";
        }
        console.log(something.myMethod);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用Firebug的DOM资源管理器进行检查时,属性按预期定义.但是,由于我不认为自己是一个JavaScript专家,我有以下问题:

  1. 这种方法可以被认为是"正确的"并且符合标准吗?它适用于Firefox,但在Web浏览器中有许多工作正常,并且不是任何标准.
  2. 通过向它们添加新属性这种改变对象是一种好习惯吗?

Joh*_*ers 100

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

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


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

方法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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

方式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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

方式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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

此策略的一个问题是您需要至少运行一次函数来分配属性.对于许多功能,这显然不是你想要的.所以让我们考虑其他选择.


方式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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

现在,您无需先运行您的功能,然后才能访问您的属性.但是,缺点是您的属性会与您的功能断开连接.


方式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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

将函数包装在匿名函数中,可以将属性收集到对象中,并使用循环在匿名函数中逐个添加这些属性.这样,您的属性就会与您的功能更加相关.当您需要从现有对象复制属性时,此技术也非常有用.但是,缺点是在定义函数时只能同时添加多个属性.此外,如果向函数添加属性是您经常要做的事情,它并不会导致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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

这样,您可以随时从另一个项目扩展多个属性和/或复制属性.但是,如果您经常这样做,那么您的代码也不会是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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

遗传扩展函数允许更多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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

这种技术不是使用通用的"扩展"功能,而是允许您生成附加了"扩展"方法的函数.


方式7:向Function原型添加'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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

这种技术的一个很大的优点是它可以非常容易地为一个函数添加新的属性,以及完全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);
Run Code Online (Sandbox Code Playgroud)

输出:

doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 
Run Code Online (Sandbox Code Playgroud)

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


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

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

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

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;
        }
    }
})();
Run Code Online (Sandbox Code Playgroud)

这种语法的一个优点是它允许公共和私有变量.例如,这就是你将'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;
        }
    }
})();
Run Code Online (Sandbox Code Playgroud)

但是你想要多个数据存储区实例吗?没问题!

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;
            }
        }
    })();
})();
Run Code Online (Sandbox Code Playgroud)

最后,您可以分离实例和单例属性,并使用原型作为实例的公共方法.这导致以下语法:

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; }
    };
})();
Run Code Online (Sandbox Code Playgroud)

使用此语法,您可以:

  • 一个对象的多个实例
  • 私有变量
  • 类变量

你这样使用它:

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());
Run Code Online (Sandbox Code Playgroud)

  • 太短了,需要细化。 (6认同)
  • *方式 1* 的输出应与 *方式 1(备用语法)* 具有相同的输出。另外,由于这些都是非常有用的示例,我认为值得添加一些对该输出的解释,即“doSomething.name”输出“doSomething”,因为“Function.name”是内置属性,而“doSomething”输出“doSomething” .name2 不是,但调用 doSomething() 会覆盖内置的 name 并定义一个新属性 name2 。 (2认同)

nnn*_*nnn 22

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

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

这种方法可以被认为是"正确的"并且符合标准吗?它适用于Firefox,但在Web浏览器中有许多工作正常,并且不是任何标准.

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

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

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

话虽如此,对我来说,向myMethod函数添加属性并没有多大意义,更常见的是为something对象添加其他属性(myMethod如果正确调用,您的函数可以访问其他属性,something通过this关键词).

如果您使用函数作为构造函数,那么将方法添加到关联的原型并向每个实例添加(非方法)属性通常是有意义的,但您可以在适当的时候以其他方式执行.(注意"方法"本质上只是恰好引用函数的属性.)

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

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


Sha*_*mal 17

"坏死"在这里,但我认为每个好问题都需要简单的答案:

是的,是的*

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

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


*我认为值得注意的是,你可能不会经常看到这一点.大多数开发人员可能都没有意识到这是可能的.有些人喜欢吃的性能每一滴...... "的基础上的对象" ......的'形状’的JavaScript引擎优化"等等等等...... UT我认为你可以按照你的对象,你的规则我会做的很好.


Dom*_*omi 6

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

这个 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');
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这对于以翻译为唯一目的的译者来说是完美的。当然,这些类型的对象的例子还有很多,但到目前为止,它们并不像具有多样化功能的类型那样常见,例如 classicUserAnimal Car类型。对于这些类型,您只想在极少数情况下添加自定义属性。this通常,您希望将它们定义为更完整的类,并通过it's访问它们的公共属性prototype