Function/object properties - piercing the veil
<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>
I seem able to ke开发者_Go百科ep properties in an object instantiated with a function but I can't access them from the function, nor can I access function variables from the outside either... is there some special trick to pierce the veil?
this
refers to the context in which the function was called, not the function itself.
You're looking for arguments.callee
, which refers to the currently executing function.
First of all, it's important to realise that standard function properties (arguments, name, caller & length) cannot be overwritten. So, forget about adding a property with that name.
Adding your own custom properties to a function can be done in different ways that should work in every browser.
Way 1 : adding properties while running the function :
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);
Output :
doSomething.name :
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
Way 1 (alternate syntax) :
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);
Output :
doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John
Way 1 (second alternate syntax) :
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);
Output :
doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John
A problem with this strategy is that you need to run your function at least once to assign the properties. For many functions, that's obviously now what you want. So let's consider the other options.
Way 2 : adding properties after defining the function :
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);
Output :
doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John
Now, you don't need to run your function first before you're able to access your properties. However, a disadvantage is that your properties feel disconnected from your function.
Way 3 : wrap your function in anonymous function :
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);
Output :
doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
Wrapping your function in an anonymous function, you can collect your attributes into an object and use a loop to add those attributes one-by-one within the anonymous function. That way, your attributes feel more connected to your function. This technique is also very useful for when your attributes need to be copied from an existing object. A disadvantage, however, is that you can only add multiple attributes at the same time when you define your function. Also, it doesn't exactly result in DRY code if adding properties to a function is something you want to do often.
Way 4 : add an 'extend' function to your function, that adds the properties of an object to itself one by one :
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);
Output :
doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
This way, you can extend multiple properties and/or copy properties from another project at any time. Again, however, your code isn't DRY if this is something you do more often.
Way 5 : Make a generic 'extend' function :
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);
Output :
doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
A genetic extend function allows for a more dry approach, allowing you to add the object or any project to any other object.
Way 6 : Create an extendableFunction object and use it to attach an extend function to a function :
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);
Output :
doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
Rather than using a generic 'extend' function, this technique allows you to generate functions that have an 'extend' method attached to it.
Way 6 : Add an 'extend' function to the Function prototype :
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);
Output :
doSomething.name :
doSomething.name2 : John
doSomething() : Beep
doSomething.name :
doSomething.name2 : John
A great advantage to this technique is that it makes adding new properties to a function very easy and DRY as well as completely OO. Also, it's pretty memory friendly. A downside, however, is that it's not very future proof. In case future browsers ever add a native 'extend' function to the Function prototype, this that could break your code.
Way 7 : Run a function recursively once and then return it :
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);
Output :
doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John
Run a function once and have it test whether one of its properties is set. If not set, set the properties and return itself. If set, execute the function. If you include an 'extend' function as one of the properties, you can later execute that to add new properties.
In spite of all these options, I would nevertheless recommend against adding properties to a function. It's better to add properties to objects.
Personally, I prefer the singleton classes with the following syntax.
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;
}
}
})();
An advantage to this syntax is that it allows for both public and private variables. For example, this is how you make the 'data' variable private :
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;
}
}
})();
But you want multiple datastore instances, you say? No problem!
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;
}
}
})();
})();
Finally, you can seperate the instance and singleton properties and use a prototype for the instance's public methods. That results in the following syntax :
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; }
};
})();
With this syntax, you can have :
- multiple instances of an object
- private variables
- class variables
You use it like this :
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());
var f = function() {
this.x = '1';
}
var eff = new f();
eff.s = '2';
alert(eff.s);
alert(eff.x);
Not sure I'm answering everything you want to know, but I think you're looking for new
to create a new instance of the object.
> var f = function() {
> this.x = '1';
> alert(this.s); // undefined }
There is no reason to use an expression where a declaration will do a better job. If an expression is used, the function will not be available until the statement is executed. Using a declaration, the function will be available as soon as execution begins, regardless of where it is in the program.
A function's this value is set when the function is called, it's value depends on the call (ignoring the ES 5 bind method).
> f.s = '2';
Functions are objects, so the above will add an s property to the f function object.
> f();
Calling f without a qualified path means that within the function, this will reference the global object. So the line alert(this.s)
will return undefined since there is no s property of the global object.
alert(f.s); // the value's there
That is accessing the s property of f which was created and assigned a value above.
> alert(f.x); // undefined
Yes. But having called f:
alert( x ) // 1
since when f()
executes the line:
this.x = '1';
and this is the global/window object, so a property x is added and assigned a value of 1.
As pointed out by SLaks you can use arguments.callee
to refer to the currently running function.
var foo = function() {
return arguments.callee.x || 'foo';
}
foo(); // returns 'foo'
foo.x = 'bar';
foo(); // returns 'bar'
However, (at least according to my copy of The Definitive Guide) using arguments.callee
with strict mode in ECMAScript 5 will raise a TypeError
. An alternative is to use named rather than anonymous functions:
function foo() {
return foo.x || 'foo';
}
foo(); // returns 'foo'
foo.x = 'bar';
foo(); // returns 'bar'
Note, however, that the reference to foo.x
in the function body means that if you re-assign foo to something else the reference will point to the new object - see this question.
精彩评论