功能/对象属性 - 刺穿面纱

时间:2011-07-06 00:39:07

标签: javascript

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

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

5 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

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

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

方式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 (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对象并使用它将extend函数附加到函数:

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 

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

方式6:向函数原型添加'extend'函数:

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 

这项技术的一大优势在于它可以非常轻松地为函数添加新属性,以及完全OO。此外,它非常友好。然而,缺点是它不是未来的证据。如果未来的浏览器曾向Function原型添加本机“扩展”功能,这可能会破坏您的代码。

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

此语法的一个优点是它允许公共和私有变量。例如,这就是你将'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());

答案 2 :(得分:2)

var f = function() {
    this.x = '1';
}

var eff = new f();
eff.s = '2';
alert(eff.s);
alert(eff.x);

我不确定我是否回答了你想知道的一切,但我认为你正在寻找new来创建一个新的对象实例。

答案 3 :(得分:1)

> var f = function() {
>     this.x = '1';
>     alert(this.s); // undefined }

没有理由使用声明会做得更好的表达式。如果使用表达式,则在执行语句之前该函数将不可用。使用声明,该功能将在执行开始时立即可用,无论它在程序中的何处。

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

> f.s = '2';

函数是对象,因此上面将向 f 函数对象添加 s 属性。

> f();

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

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

即访问 f 属性,该属性已创建并分配了上述值。

> alert(f.x); // undefined

是。但是打电话给 f

alert( x ) // 1

因为f()执行以下行:

  this.x = '1';

是全局/窗口对象,因此添加属性 x 并赋值为1.

答案 4 :(得分:1)

正如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