【问题标题】:Function/object properties - piercing the veil功能/对象属性 - 刺破面纱
【发布时间】:2011-09-29 05:52:30
【问题描述】:
<script type="text/javascript">
var f = function() {
    this.x = '1';
    alert(this.s); // undefined
}
f.s = '2';
f();
alert(f.s); // the value's there
alert(f.x); // undefined
</script>

我似乎能够将属性保留在使用函数实例化的对象中,但我无法从函数中访问它们,也无法从外部访问函数变量...是否有一些特殊的技巧可以戳破面纱?

【问题讨论】:

    标签: javascript


    【解决方案1】:

    this 指的是调用函数的上下文,而不是函数本身。
    您正在寻找arguments.callee,它指的是当前正在执行的函数。

    【讨论】:

    • this 与上下文无关,由调用设置。
    • @Rob:是的;它是调用函数的上下文
    • @SLaks,确实是先生。你有什么不知道的吗?哈哈。这就是我想要的:var f = function() { alert(arguments.callee.s); } f.s = '2'; f();
    • @SLacks - 不,不是。从 global context 调用 someObj.someFn 不会将 someFn 的 this 设置为 global/window 对象,因此 context (即由ES3 和 ES5) 无关紧要。此外,来自其他上下文(例如函数内部)的非限定调用仍会将被调用函数的 this 设置为全局对象。上下文无关紧要,重要的是调用函数的方式。
    • @Rob:context,我不是指 ES5 的执行上下文;相反,我指的是this。有更好的词吗?
    【解决方案2】:

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

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

    方式一:在运行函数时添加属性:

    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 (isArray(args) || (args !== null && typeof args === 'object')) {
            for (i in args) {
                this[i] = args[i];
            }
        }
        return obj;
    }
    
    var Job = 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对象并使用它来将一个扩展函数附加到一个函数上:

    var extendableFunction = (function() {
        var extend = function(args) {
            if (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 
    

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

    方式6:在函数原型中添加一个“扩展”函数:

    Function.prototype.extend = function(args) {
        if (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 
    

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

    方式7:递归运行一次函数,然后返回它:

    var doSomething = (function f(arg1) {
        if(f.name2 === undefined) {
            f.name = 'Tom';
            f.name2 = 'John';
            f.extend = function(obj, args) {
                if (isArray(args) || (args !== null && typeof args === 'object')) {
                    for (i in args) {
                        this[i] = args[i];
                    }
                }
                return obj;
            };
            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());
    

    【讨论】:

    • 哇。这是每个 js 新手都应该阅读的内容!感谢您花时间详细说明该主题
    【解决方案3】:
    var f = function() {
        this.x = '1';
    }
    
    var eff = new f();
    eff.s = '2';
    alert(eff.s);
    alert(eff.x);
    

    不确定我是否回答了您想知道的所有问题,但我认为您正在寻找 new 来创建对象的新实例。

    【讨论】:

    • 这只是故事的一半...它回答了如何从函数外部访问 x 而不是如何从内部访问 s...请参阅:jsfiddle.net/yfVhe
    【解决方案4】:
    > var f = function() {
    >     this.x = '1';
    >     alert(this.s); // undefined }
    

    没有理由使用声明会做得更好的表达式。如果使用表达式,则在执行语句之前,该函数将不可用。使用声明,函数将在执行开始后立即可用,而不管它在程序中的什么位置。

    函数的this值是在函数被调用时设置的,它的值取决于调用(忽略ES 5的bind方法)。

    > f.s = '2';
    

    函数是对象,所以上面会为f函数对象添加一个s属性。

    > f();
    

    在没有限定路径的情况下调用 f 意味着在函数内,this 将引用全局对象。因此,alert(this.s) 行将返回 undefined,因为全局对象没有 s 属性。

    alert(f.s); // the value's there
    

    这是访问 fs 属性,该属性在上面创建并分配了一个值。

    > alert(f.x); // undefined
    

    是的。但是调用了 f:

    alert( x ) // 1
    

    自从f() 执行该行时:

      this.x = '1';
    

    this是全局/窗口对象,所以添加了一个属性x并赋值为1。

    【讨论】:

    • 感谢您花时间解释它。有些事情总是在我的脑海中模糊不清。
    • @ekkis - 弄清楚 this 需要一点时间,但这并不难。只要记住看看函数是如何被调用的:obj.fn() - this 将是 objfn() - this 将是全局的对象(在浏览器中也是 window)。
    【解决方案5】:

    正如SLaks 所指出的,您可以使用arguments.callee 来引用当前正在运行的函数。

    var foo = function() {
      return arguments.callee.x || 'foo';
    }
    foo(); // returns 'foo'
    foo.x = 'bar';
    foo(); // returns 'bar'
    

    但是,(至少根据我的 The Definitive Guide 副本)在 ECMAScript 5 中以严格模式使用 arguments.callee 会引发 TypeError。另一种方法是使用命名函数而不是匿名函数:

    function foo() {
      return foo.x || 'foo';
    }
    foo(); // returns 'foo'
    foo.x = 'bar';
    foo(); // returns 'bar'
    

    但是请注意,函数体中对 foo.x 的引用意味着如果您将 foo 重新分配给其他对象,则该引用将指向新对象 - 请参阅 this question

    【讨论】:

      猜你喜欢
      • 2020-12-18
      • 2022-01-14
      • 1970-01-01
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      • 2021-01-31
      • 1970-01-01
      • 2020-08-24
      相关资源
      最近更新 更多