How does jquery proxy work - javascript

I am more curious than anything else. How does it pass the context to the function. Does it wrap the function in the object? I am sure there is some simple straightforward code for doing this in js without jquery proxy
function abc(){
console.log(this.name);
}
var obj={name:"Something"};
$.proxy(abc,obj);
How can I do this without jquery proxy?

Without jQuery you may use bind :
var newFunction = abc.bind(obj);
And if you want to be compatible with IE8, you may do
var newFunction = function(){ abc.call(obj) };
Here's how jQuery does it :
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},

Related

jQuery plugin boiler plate - private method with bound scope?

I've been looking at the plugin boiler plate for jQuery plugins, I find it ok but there is one major flaw in the design or maybe just something I can't figure out.
When I author plugins at the moment, it is easy for me to define publicly exposed methods and private methods that only the plugin has access to.
When I tried to do something similar in the boiler plate I was thwarted.
;(function ( $, window, document, undefined ) {
// Create the defaults once
var
pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin ( element, options ) {
this.element = element;
this.settings = $.extend( {}, defaults, options );
this.defaults = defaults;
this.name = pluginName;
this.init();
}
Plugin.prototype.init = function() {
console.log('init')
console.log(this)
this.yourOtherFunction();
}
Plugin.prototype.yourOtherFunction = function () {
console.log('yourOtherFunction')
console.log(this)
this.yourOtherFunction2();
}
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
var privateFunction = function() {
console.log('private')
console.log(this)
}
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[ pluginName ] = function ( options ) {
return this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
};
})( jQuery, window, document );
$(document).defaultPluginName()
Anyway you can see the function 'privateFunction' it's scope is to the window object, but what I want to be able to do is scope it to the Plugin instance, or basically 'this' from the prototype methods.
What I don't want to do, is pass the scope into each private function as a function argument!
So how can I bind the scope?
Console output
init
Plugin { element=document, settings={...}, defaults={...}, more...}
yourOtherFunction
Plugin { element=document, settings={...}, defaults={...}, more...}
private
Window index.html <-- I want Plugin, not window
You are calling privateFunction and then binding this as scope for its result. So use (as said by #Khanh_TO):
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}
Instead of:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
More details:
bind returns a copy of the function on which is called (the result of privateFunction in your case) after applying the scope you've passed in (this in your case). What bind does is something like:
Function.prototype.bind = function(scope) {
var _function = this;
var _args = [];
for (var i = 0, len = arguments.length-1; i < len; i++){ _args[i] = arguments[i+1]; }
return function() {
// returns the same function on which is called (not the same Function object, but
// another with same properties) with 'this' equal to the first parameter and
// the remaining specified parameters as parameters of the function returned
return _function.apply(scope, _args);
}
}
eg. myFunction.bind(newScope, param1, param2, ...) -> returns an anonymous function which in turns returns the function myFunction(param1, param2,....) with set this = newScope.
So, as a proof of concept, also this code would have worked:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.bind(this)();
}
but you should use the first one since the last one does the same thing with extra passages.
Replace:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
With
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}

How to create a Jquery Plugin with Plugin Methods and Maintaining Chainability?

I am trying to create a Jquery plugin that maintains chainability and has public methods as specified in Jquery Plugins/Authoring . The complexity is that it is trying to maintain certain vars that I want the public methods to use.
This is my jsfiddle : http://jsfiddle.net/badmash69/9cqcj/2/
javascript code :
(function($){
var methods = {
init : function( options ) {
this.options = options;
}
, add_that: function (elem) {
$(this).append(elem);
return (this);
}
, show_parent: function(){
// this is a simple test to see if the plugin vars are accessible
alert("parent id=" + $(this).parentId)
}
, add_this: function (elem) {
return methods.add_that.apply(this,elem);
}
};
$.fn.test = function (method) {
var args = method;
var argss = Array.prototype.slice.call(args, 1);
return this.each(function(){
var $this = $(this);
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.test' );
}
var element = $(this);
var parentId= element.parent().attr("id")
});
};
})(jQuery);
$('#test').test('add_this',$('<div>Hello World d</div>'));
$('#test').test('show_parent');
​
Html Code
<div id="holder">
<div id="test"></div>
</div>
I cant figure out what I am doping wrong here .
How can I make it work ? I would deeply appreciate any help .
the way that I do this is using the $.data, you can have specific object local vars, "public"/"private" methods, etc. here goes an small example in how I will do it
(function($){
var myTestMethods = function() {
// local variables
var last_added;
// local "private" methods
var init=function(options) {
this.options = options;
last_added = null;
return this;
};
var add_that=function(elem) {
last_added = elem;
this.append(elem);
return this;
};
var show_parent=function() {
alert("parent id=" + this.parent().attr('id'));
}
return { // this are your obj "public" methods
// notice we are not listing add_that method, therefore this method will be a "private" method
init : init,
show_parent: show_parent, // you can publish a private method
get_last_added: function(){
return last_added; // you can access local variables
},
add_this: function (elem) {
return add_that.apply(this, elem); // you can also run local methods
}
}
};
$.fn.test = function (method) {
var obj_data = this.data('myTestData');
if (typeof(obj_data) != "undefined") {
if ( obj_data[method] ) {
return obj_data[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
}else {
$.error( 'Method ' + method + ' does not exist on jQuery.test' );
}
} else {
if (typeof(method) === 'object' || ! method) {
obj_data = myTestMethods();
this.data('myTestData', obj_data);
return obj_data.init.apply(this, arguments);
}
}
};
})(jQuery);
$('#test').test(); //init
$('#test').test('add_this',$('<div>Hello World d</div>'));
$('#test').test('show_parent');
this code has small tests so there may be small bugs, but this will show you the basic idea in how to do what you want.
Take a look at this demo: http://jsfiddle.net/9cqcj/11/
As they suggest, to keep data you should better use .data:
return this.each(function(){
var $this = $(this);
$this.data("parentId",$this.parent().attr("id"));
....
(assuming that you need parentId of each element in set)
Also, you have a problem with calling your methods:
return this.each(function(){
var $this = $(this);
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
Last line, arguments - arguments of function passed to .each is used. In order to get original arguments save them into variable before calling a method:
$.fn.test = function (method) {
var args = arguments;
return this.each(function(){
var $this = $(this);
$this.data("parentId",$this.parent().attr("id"));
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( args , 1 ));
See arguments replaced with args in last line.
Also, when you are using .apply, second parameter should be an array:
return methods.add_that.apply(this, [elem]);
In case like this:
return methods.add_that.apply(this, elem);
You can get unexpected problems. For instance, try to replace elem with simple string "test" and see what you will get in console. Or if you will pass jQuery object, you will get DOM object in called method

Pass javascript function as parameter and evaluate its content as class content

Is it possible to pass a function as parameter to a method and evaluate it's content as if it was part of the class?
function Class()
{
this.Method = function( param )
{
// Call "CallThis"
};
this.CallThis = function()
{
};
}
var c = new Class();
c.Method(
{
evalThisContent: function()
{
this.CallThis();
}
}
);
If I follow your intention:
function Class()
{
this.Method = function( param, name )
{
this[name] = param;
param.call(this);
};
this.CallThis = function()
{
};
}
var c = new Class();
c.Method(function() {
this.CallThis();
}, 'evalThisContent');
It's certainly possible to invoke the function, in this case with
param()
as for "as if it was part of the class", if you mean would it have access to its members through this, no it wouldn't. But you could pass in a reference to this (object of type Class) to the function and it could access its members through this reference.
I've modified the class in the way I think you may have intended, though Zirak seems to have already demonstrated the main idea.
function Class() {
}
Class.prototype.method = function(param) {
if (typeof param === 'object' && param.evalThisContent) {
param.evalThisContent.call(this);
}
};
Class.prototype.callThis = function() {
alert("I'm getting called indirectly!");
};
var c = new Class();
c.method(
{
evalThisContent: function()
{
this.callThis();
}
}
);
If you wish, you could instead add or alter "evalThisContent" dynamically on the prototype, making it available to all objects which may henceforth wish to call it:
Class.prototype.method = function(param) {
if (typeof param === 'object' && param.evalThisContent && !Class.prototype.evalThisContent) {
Class.prototype.evalContent = param.evalThisContent;
}
this.evalContent();
};
This has the advantage of not creating a function into memory each time, nor invoking it in a less than optimal way with call, while call (or apply), as in the first example, has the more commonly useful advantage of allowing each instance of the Class to use its own functions or implementations (or you could use inheritance).

How do I compute a variable in JavaScript if and only if it is used?

This is what I'm doing right now.
var foo = function() {
var x = someComplicatedComputationThatMayTakeMoreTime();
this.foo = function() { return x; };
return x;
}
It works but only if foo is called as a function like so
foo();
But what if I want to call it as a normal variable with a value? I could modify the code to be
var foo = function() {
var x = someComplicatedComputationThatMayTakeMoreTime();
this.foo = x;
return x;
}
That would allow me to only call it once as a function and after that as a regular variable. But it's still not what I want. Plus it gets complicated if it accidentally gets called as a function again, returning an error.
Is this even possible in JavaScript?
BTW, this is for a Chrome/Firefox extension, so IE compatibility does not matter.
Ended up using toString because getters don't allow me to redefine the whole attribute, a function must be associated with it. And toString has cleaner syntax.
How about using toString?
var foo = function() {
function someComplicatedComputationThatMayTakeMoreTime() {
//your calculations
}
return {
toString: function() {
return someComplicatedComputationThatMayTakeMoreTime();
}
}
}
More about Object-to-Primitive Conversions in JavaScript
EDIT based on comment. Use a singleton (I think it's called):
myObject.prop = (function(){
function someComplicatedComputationThatMayTakeMoreTime() {
//your calculations
}
return {
toString: function() {
return someComplicatedComputationThatMayTakeMoreTime();
}
}
})()
If only Internet Explorer didn't exist, you could use getters and setters as described by John Resig in this blog article:
John Resig: JavaScript Getters and Setters
... They allow you to bind special functions to an object that look like normal object properties, but actually execute hidden functions instead.
Using a function is your best option for now, however the new JavaScript standard (ECMAScript 5th Ed.) which is being implemented now by all major browser vendors, gives you a method to create accessor properties, where you can define a property with a get and set functions that will be internally called, without worrying to treat this properties as functions, e.g.:
var obj = {};
Object.defineProperty(obj, 'foo', {
get: function () { // getter logic
return 'foo!';
},
set: function (value) {
// setter logic
}
});
obj.foo; // "foo!", no function call
This new standard will take some time to be implemented for all browsers, (the IE9 preview version really disappointed me), and I wouldn't recommend you to use it for production, unless you have total control on the environment where your application will be used.
What I think you want is a lazily instantiated variable, which can be implemented like this.
var myProperty = null;
function getMyProperty() {
return (myProperty = myProperty || builder());
}
This is not practical on the web because IE does not support it, but you can look at
https://developer.mozilla.org/en/defineGetter for examples how to do this.
There are a couple ways to do it, here is one example:
var data = {};
data.__defineGetter__("prop",
(function () {
var value = null;
return function () {
if (null == value) {
value = getYourValueHere();
}
return value;
};
})());
and now you can use it like:
var a = data.prop;
var b = data.prop;
I would recommend a variation on ChaosPandion's answer, but with a closure.
var myProperty = (function () {
var innerProperty = null;
return function() {
return (innerProperty = innerProperty || someComplicatedComputationThatMayTakeMoreTime());
};
})();
and then use myProperty() every time you need to access the variable.
You could define a JavaScript getter. From the Apple JavaScript Coding Guidelines:
myObject.__defineGetter__( "myGetter", function() { return this.myVariable; } );
var someVariable = myObject.myGetter;
See John Resig's post, JavaScript Getters and Setters, and the Defining Getters and Setters page at the Mozilla Developer Centre for more information.
I would use explicit lazy evaluation. Here's my implementation of it based on Scheme's take:
var delay, lazy, force, promise, promiseForced, promiseRunning;
(function () {
var getValue = function () {
return this.value;
};
var RUNNING = {};
var DelayThunk = function (nullaryFunc) {
this.value = nullaryFunc;
};
DelayThunk.prototype.toString = function () {
return "[object Promise]";
};
DelayThunk.prototype.force = function () {
if (promiseRunning (this)) {
throw new Error ("Circular forcing of a promise.");
}
var nullaryFunc = this.value;
this.value = RUNNING;
this.value = nullaryFunc ();
this.force = getValue;
return this.value;
};
var LazyThunk = function (nullaryFunc) {
DelayThunk.call (this, nullaryFunc);
};
LazyThunk.prototype = new DelayThunk (null);
LazyThunk.prototype.constructor = LazyThunk;
LazyThunk.prototype.force = function () {
var result = DelayThunk.prototype.force.call (this);
while (result instanceof LazyThunk) {
result = DelayThunk.prototype.force.call (result);
}
return force (result);
};
delay = function (nullaryFunc) {
return new DelayThunk (nullaryFunc);
};
lazy = function (nullaryFunc) {
return new LazyThunk (nullaryFunc);
};
force = function (expr) {
if (promise (expr)) {
return expr.force ();
}
return expr;
};
promise = function (expr) {
return expr instanceof DelayThunk;
};
promiseForced = function (expr) {
return expr.force === getValue || !promise (expr);
};
promiseRunning = function (expr) {
return expr.value === RUNNING || !promise (expr);
};
}) ();
Example Syntax:
var x = lazy (function () { return expression; });
var y = force (x);
var z = delay (function () { return expression; });
var w = force (z);
Note values are stored once evaluated, so repeated forcing will not do extra computations.
Example usage:
function makeThunk (x, y, z) {
return lazy (function () {
// lots of work done here
});
}
var thunk = makeThunk (arg1, arg2, arg3);
if (condition) {
output (force (thunk));
output (force (thunk)); // no extra work done; no extra side effects either
}
You can use the javascript Proxy class for creating such functionality.
var object = {};
var handler = {
resolvers: {},
get ( target, property, proxy ) {
if ( ! target.hasOwnProperty( property ) && this.resolvers.hasOwnProperty( property ) ) {
// execute the getter for the property;
target[ property ] = this.resolvers[ property ]();
}
return target[ property ];
},
set ( target, property, value, receiver ) {
// if the value is function set as a resolver
if ( typeof value === 'function' ) {
this.resolvers[property] = value;
// otherwise set value to target
} else {
target.property = value;
}
},
has ( target, property, receiver ) {
//true when proxy handler has either a resolver or target has a value;
return this.resolvers.hasOwnProperty( property ) || target.hasOwnProperty( property );
}
};
var lazyObject = new Proxy( object, handler );
Now you can use it like this:
'exampleField' in lazyObject; //returns false
lazyObject.exampleField = function(){ return 'my value' }; // add a resolver function
'exampleField' in lazyObject; //returns true
lazyObject.exampleField; //executes your resolver function and returns 'my value'
This example is to demonstrate the working. You can change after your needs.
Here is a fiddle with a demonstration

Removing the need for "new"

A nasty gotcha in javascript is forgetting to call new on a function meant to be instantiated, leading to this being bound to a different object (usually the global) instead of a fresh one. One workaround I read about is to check for it explicitly in the function-constructor using the following idiom:
function SomeConstructor(x, y, ...) {
// check if `this` is created with new
if ( !(this instanceof arguments.callee) )
return new SomeConstructor(x, y, ...);
// normal initialization code follows
Now new SomeConstructor(...) and SomeConstructor(...) are equivalent.
I'd like to simplify this by creating a wrapper function factory(fn) that does the first two lines and then delegates to the wrapped function fn. This would be used like:
SomeConstructor = factory(function (x, y, ...) {
// normal initialization code follows
})
My first attempt was:
function factory(fn) {
return function() {
if ( !(this instanceof arguments.callee) ) {
return new arguments.callee.apply(this, arguments);
}
fn.apply(this, arguments);
}
}
but it fails with "Function.prototype.apply called on incompatible [object Object]". The second attempt was:
function factory(fn) {
return function() {
if ( !(this instanceof arguments.callee) ) {
var tmp = new arguments.callee();
arguments.callee.apply(tmp, arguments);
return tmp;
}
fn.apply(this, arguments);
}
}
This sort of works but it may call the wrapped function twice: once with no arguments (to create a new instance) and once with the passed arguments for the actual initialization. Apparently this is fragile and inefficient but I can't figure out a way to do it with a single call. Is this possible ?
EDIT: Based on bobince's approach, here's a similar one that does the trick:
function factory(init) {
var run_init = true;
function constr() {
if ( !(this instanceof constr) ) {
run_init = false;
var tmp = new constr();
run_init = true;
init.apply(tmp, arguments);
return tmp;
}
if (run_init)
init.apply(this, arguments);
}
return constr;
}
As for whether this is something that should be encouraged or not, that's debatable. I come from a Python background and I think of new as just noise (Java) or wart (Javascript), but I may be missing something.
This simply encourages a bad-habit shortcut that relies far too heavily on the implementation of the class to "fix" the calling code.
If this is a problem, don't just let it slide, throw an error message.
You can pass a unique value into the constructor for the first call (with new) that signifies you don't want the initialiser called yet:
var _NOINIT= {};
function factory(init) {
function constr() {
if (!(this instanceof constr)) {
var inst= new constr(_NOINIT);
init.apply(inst, arguments);
return inst;
}
if (arguments[0]!==_NOINIT)
init.apply(this, arguments);
}
return constr;
}
Note I've used a named inline function for the constructor because arguments.callee will be going away in ECMAScript Fifth Edition's ‘strict’ mode.
However if you're using a class factory, I suggest making the initialiser function a member of the class, rather than being passed in. That way, you can subclass a base class and have the subclass inherit the initialiser, which is normal behaviour in class-based languages. eg.:
Function.prototype.makeSubclass= function() {
function constr() {
var that= this;
if (!(this instanceof constr))
that= new constr(_NOINIT);
if (arguments[0]!==_NOINIT && '_init' in that)
that._init.apply(that, arguments);
return that;
}
if (this!==Object)
constr.prototype= new this(_NOINIT);
return constr;
};
var Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
var Point= Shape.makeSubclass();
// inherits initialiser(x, y), as no need for anything else in there
var Circle= Shape.makeSubclass()
Circle.prototype._init= function(x, y, r) {
Shape.prototype._init.call(this, x, y);
this.r= r;
};
Of course you don't have to put that into the Function prototype... it's a matter of taste, really. As is allowing constructors without new.
Personally I prefer to throw an error rather than silently make it work, to try to discourage bare-constructor-calling, since this is a mistake elsewhere and may make the code slightly less clear.
Your "factory" function could be written in this way:
function factory(fn, /* arg1, arg2, ..., argn */) {
var obj = new fn(), // Instantiate using 'new' to preserve the prototype chain
args = Array.prototype.slice.call(arguments, 1); // remove fn argument
fn.apply(obj, args); // apply the constructor again, with the right arguments
return obj;
}
// Test usage:
function SomeConstructor (foo, bar) {
this.foo = foo;
this.bar = bar;
}
SomeConstructor.prototype.test = true;
var o = factory(SomeConstructor, 'foo', 'bar');
// will return: Object foo=foo bar=bar test=true, and
o instanceof SomeConstructor; // true
However, the new operator is not bad, I would not encourage you to avoid it, I would recommend you to stick with a proper naming convention, constructor functions identifiers in PascalCase, all other identifiers in camelCase, and also I highly recommend you to use JsLint it will help you to detect that kind of mistakes early.
I dislike your mixing of arguments.callee and the function's identifier. Also, you are dumbing down the original problem. You should have used apply to begin with so as not to make the helper (factory) function seem even better than it really is.
What should have been done to begin with:
function SomeConstructor(x, y, ...) {
// check if `this` is created with new
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply (this, arguments);
// normal initialization code follows
Another issue with factory is that it defeats the function's length property.
while 'new' is a good thing, and I don't endorse trying to do away with language features, check out this code I played with a while ago: (note, this is not a complete solution for you, but rather something to build into your code)
function proxy(obj)
{
var usingNew = true;
var obj_proxy = function()
{
if (usingNew)
this.constructor_new.apply(this, arguments);
};
obj_proxy.prototype = obj.prototype;
obj_proxy.prototype.constructor_new = obj.prototype.constructor;
obj_proxy.createInstance = function()
{
usingNew = false;
var instance = new obj_proxy();
instance.constructor_new.apply(instance, arguments);
usingNew = true;
return instance;
}
return obj_proxy;
}
to test it out, create a function foo like this:
function foo(a, b) { this.a = a; }
and test it:
var foo1 = proxy(foo);
var test1 = new foo1(1);
alert(test1 instanceof foo);
var test2 = foo1.createInstance(2);
alert(test2 instanceof foo);
EDIT: removed some code not relevant for this.
If you are interested in dealing with the inability to use apply with new, one could use
Function.prototype.New = (function () {
var fs = [];
return function () {
var f = fs [arguments.length];
if (f) {
return f.apply (this, arguments);
}
var argStrs = [];
for (var i = 0; i < arguments.length; ++i) {
argStrs.push ("a[" + i + "]");
}
f = new Function ("var a=arguments;return new this(" + argStrs.join () + ");");
if (arguments.length < 100) {
fs [arguments.length] = f;
}
return f.apply (this, arguments);
};
}) ();
Example:
var foo = Foo.New.apply (null, argArray);
Here is some broilerplate code I've come up with as a code-template for object factory in AngularjS. I've used a Car/CarFactory as an example to illustrate. Makes for simple implementation code in the controller.
<script>
angular.module('app', [])
.factory('CarFactory', function() {
/**
* BroilerPlate Object Instance Factory Definition / Example
*/
this.Car = function(color) {
// initialize instance properties
angular.extend(this, {
color : null,
numberOfDoors : null,
hasFancyRadio : null,
hasLeatherSeats : null
});
// generic setter (with optional default value)
this.set = function(key, value, defaultValue, allowUndefined) {
// by default,
if (typeof allowUndefined === 'undefined') {
// we don't allow setter to accept "undefined" as a value
allowUndefined = false;
}
// if we do not allow undefined values, and..
if (!allowUndefined) {
// if an undefined value was passed in
if (value === undefined) {
// and a default value was specified
if (defaultValue !== undefined) {
// use the specified default value
value = defaultValue;
} else {
// otherwise use the class.prototype.defaults value
value = this.defaults[key];
} // end if/else
} // end if
} // end if
// update
this[key] = value;
// return reference to this object (fluent)
return this;
}; // end this.set()
}; // end this.Car class definition
// instance properties default values
this.Car.prototype.defaults = {
color: 'yellow',
numberOfDoors: 2,
hasLeatherSeats: null
};
// instance factory method / constructor
this.Car.prototype.instance = function(params) {
return new
this.constructor()
.set('color', params.color)
.set('numberOfDoors', params.numberOfDoors)
.set('hasFancyRadio', params.hasFancyRadio)
.set('hasLeatherSeats', params.hasLeatherSeats)
;
};
return new this.Car();
}) // end Factory Definition
.controller('testCtrl', function($scope, CarFactory) {
window.testCtrl = $scope;
// first car, is red, uses class default for:
// numberOfDoors, and hasLeatherSeats
$scope.car1 = CarFactory
.instance({
color: 'red'
})
;
// second car, is blue, has 3 doors,
// uses class default for hasLeatherSeats
$scope.car2 = CarFactory
.instance({
color: 'blue',
numberOfDoors: 3
})
;
// third car, has 4 doors, uses class default for
// color and hasLeatherSeats
$scope.car3 = CarFactory
.instance({
numberOfDoors: 4
})
;
// sets an undefined variable for 'hasFancyRadio',
// explicitly defines "true" as default when value is undefined
$scope.hasFancyRadio = undefined;
$scope.car3.set('hasFancyRadio', $scope.hasFancyRadio, true);
// fourth car, purple, 4 doors,
// uses class default for hasLeatherSeats
$scope.car4 = CarFactory
.instance({
color: 'purple'
numberOfDoors: 4
});
// and then explicitly sets hasLeatherSeats to undefined
$scope.hasLeatherSeats = undefined;
$scope.car4.hasLeatherSeats.set('hasLeatherSeats', $scope.hasLeatherSeats, undefined, true);
// in console, type window.testCtrl to see the resulting objects
});
</script>
the only thing that worked for me involves dumbing down the implementation. it's ugly but works (both with and without operator new):
var new_ = function (cls)
{
var constructors = [
function ()
{
return new cls();
}
, function ($0)
{
return new cls($0);
}
, function ($0, $1)
{
return new cls($0, $1);
}
, function ($0, $1, $2)
{
return new cls($0, $1, $2);
}
, // up to a chosen limit
];
return function ()
{
return constructors[arguments.length].apply(
this
, arguments
);
}
}
edit to react to comments
I have way-below-average tolerance to repetitive code, and this code hurt to write, but the functions in constructors need to be separate if arguments.length is to mean something in the real constructor function. consider a variant with a single new wrapper:
var new_ = function (cls)
{
// arbitrary limit
var constructor = function ($0, $1, $2)
{
return new cls($0, $1, $2);
};
return function ()
{
return constructor.apply(
this
, arguments
);
}
}
var gen = new_(function ()
{
print(
arguments.length
+ " "
+ Array.prototype.toSource.call(arguments)
);
});
gen("foo") // 3 ["foo", undefined, undefined]
gen("foo", "bar") // 3 ["foo", "bar", undefined]
gen("foo", "bar", "baz") // 3 ["foo", "bar", "baz"]
the parameter list can be arbitrarily wide, but arguments.length doesn't lie only in the special case.
I've been using this solution with the upper limit of 10 arguments for a few years, and I don't remember ever running into the limit. the risk that it'll ever happen is rather low: everybody knows that functions with many parameters are a no-no, and javascript has a better interface for the desired functionality: packing parameters into objects.
so, the only limit is the width of the parameter list, and this limit seems to be purely theoretical. other than that, it supports completely arbitrary constructors, so I'd say it's very general.

Categories

Resources