向函数添加自定义属性

IT技术 javascript function oop object custom-properties
2021-01-26 18:05:56

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

正如我们所知,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. 这种通过向对象添加新属性来改变对象的做法是一种好习惯吗?
6个回答

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

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


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

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

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


方式二:定义函数后添加属性:

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.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 

这种技术的一个巨大优势是,它使向函数添加新属性变得非常容易、DRY 以及完全面向对象。此外,它对内存非常友好。然而,一个缺点是它不是非常适合未来的证明。如果未来的浏览器向 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;
        }
    }
})();

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

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());
太短了,需要详细说明。
2021-03-19 18:05:56
帮了我很多!感谢朋友提供有用的信息!
2021-03-22 18:05:56
方式 1的输出应与方式 1(替代语法)具有相同的输出此外,由于这些都是非常有用的例子,我认为这是值得补充的是输出,即解释几句话,即doSomething.name输出doSomething既然Function.name是一个内置的特性,而doSomething.name2不是,但调用doSomething()覆盖内置name和定义了一个新财产name2
2021-04-05 18:05:56

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

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

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

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

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

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

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

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

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

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

哦,哇,我不知道我怎么会犯这样的错误,我使用了!==代替===谢谢您的回答。这个问题只是作为一个理论问题出现在我的脑海中,我也没有看到它可能(和可读)的用途,只是检查灵活性。
2021-03-18 18:05:56
不过,@Przemek 对吗?如果你使用!==条件永远不会为真,不是吗?
2021-03-22 18:05:56

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

是*

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

在此处和此处创建了一些有趣的示例。 这里 和这里


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

将属性附加到函数是一种很好的(可以说是缓慢的/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');

如您所见,这对于以翻译为唯一目的的翻译人员来说是完美的。当然也有论文的对象类型的例子还有许多,但他们远远不是像普通类型的多样化功能,如经典的UserAnimal Car等类型。对于这些类型,您只想在极少数情况下添加自定义属性。通常,您希望将它们定义为更完整的类,并且可以通过它们访问它们的公共属性,this并且它是prototype.

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