2

When instantiating an object in Javascript, I would like to enable the following scenarios:

  • The object can be instantiated without any parameters, and is created with default property values
  • The object can be instantiated with parameter(s) that set the initial values for the object properties

The solution should:

  • not assign properties that have not been defined on the object
  • allow an arbitrary number of properties to be set (none, some, or all)
  • be easy to apply to any object defined by the script, meaning that we have no prior knowledge of what properties can be set.

jQuery is available for use, though pure JS solutions are welcome.

Take the following object constructor:

function User() {
    this.name = null;
    this.email = null;
    this.password = null;
}

The solution should allow me to achieve the following:

new User();
// User { name: null, email: null, password: null }

new User(<parameter(s) that specify a value for name, email and password>);
// User { name: "passed value", email: "passed value", password: "passed value" }

new User(<parameter that specifies a value for name>);
// User { name: "passed value", email: null, password: null }

new User(<parameter(s) that specify values for name and non-existent property age>);
// User { name: "passed value", email: null, password: null }

2 Answers 2

2

Combining $.extend() with a default object makes this really easy:

function User(options) {
    var defaults = {
        name: null,
        email: null,
        password: null
    };
    var options = $.extend({}, defaults, options);
    console.log(options);

}

new User(); 
// null, null, null

new User({name : 'First'});
// First, null, null

new User({name : 'First', email: '[email protected]'});
// First, [email protected], null

new User({name : 'First', email: '[email protected]', password : 'password1'});
// First, [email protected], password1

Here it is working: http://jsfiddle.net/m6KQC/2/

If you don't want to add extra properties passed when the function is called then just use the code below instead of $.extend(). This has the same functionality as your original function:

function setObjectProperties(defaults, options, target) {
    var options = options || {};
    for (var prop in defaults) { 
        target[prop] = (typeof options[prop] === "undefined") ?  defaults[prop] : options[prop];
    }
}

You can wrap this in your original function and use it like this:

setObjectProperties(defaults, options, this);

You can see this alternative working here: http://jsfiddle.net/m6KQC/9/

Sign up to request clarification or add additional context in comments.

7 Comments

The solution should not assign properties that have not been defined on the object. The object needs to define its properties, and must not set properties by parameter that have not been defined. Does this solution achieve that?
@michaelward82 - why would that matter? The variables can only be accessed through the options object so they're not going to overwrite anything else in the function. Also, why would you pass unnecessary variables to the function anyway?
@michaelward82 - I've updated my answer with an alternative if you don't want to add extra properties passed to the function - no jQuery needed.
Thankyou, we're getting closer to a perfect solution :) However, this needs DRYing out. I need this code to exist in a function that I can (at worst) call using a simple one liner from every Object constructor that I have.
The function seems to be achieving a number of the desired effects, except it isn't actually creating or assigning any object properties. Remove the current console.log and log the results of an object creation and it shows the object created has no public properties. Thankyou for your efforts so far, I feel we will have a great answer when we are done. For the time, my answer meets all the requirements but I do think we can do better than my solution.
|
0

Firstly, I would allow the object constructor to take a parameter

function User(params) {
...

To allow an arbitrary number of arguments to be supplied, the params parameter will accept an object literal:

new User({name: 'name 1', id: 1});

Next I pass the params object to a generic function that will handle assignment of properties from the params object to the target object.

function setObjectProperties(params, target) {
}

The setObjectProperties function needs to verify that we have been correctly passed some data. It checks if both params and target are defined:

function setObjectProperties(params, target) {
    if (params !== undefined && target !== undefined) {

    }
}

Once we're confident that we have something to work with, it is time to iterate over the parameters and assign to the object properties.

function setObjectProperties(params, target) {
    if (params !== undefined && target !== undefined) {
        $.each(params, function (i, property) {
            this[i] = property;
        }, target);
    }
}

Using jQuery's $.each function, which iterates over an objects properties without the side effect of iterating over all object properties such as prototypes, etc. This works but mean we need to deal with the side-effect that this gets redefined inside the callback closure of the $.each function. We now no longer know what are original object was.

However, jQuery's $.proxy function is designed to allow us to call a function whilst designating what we would like to use as this:

function setObjectProperties(params, target) {
    if (params !== undefined && target !== undefined) {
        $.each(params, $.proxy(function (key, value) {
                this[key] = value;
        }, target));
    }
}

Here we tell jQuery to set this to the value of the target parameter, which is set to this inside the object constructor, as shown below:

function User(params) {
    this.name = null;
    this.email = null;
    this.password = null;

    setObjectProperties(params, this);
}

One problem still remains, we should check against declared properties on the object to see if we can assign the value passed in:

function setObjectProperties(params, target) {
    if (params !== undefined && target !== undefined) {
        $.each(params, $.proxy(function (key, value) {
            if (this.hasOwnProperty(key)) {
                this[key] = value;
            }
        }, target));
    }
}

Here's the full and final code:

function setObjectProperties(params, target) {
    if (params !== undefined && target !== undefined) {
        $.each(params, $.proxy(function (key, value) {
            if (this.hasOwnProperty(key)) {
                this[key] = value;
            }
        }, target));
    }
}

function User(params) {
    this.name = null;
    this.email = null;
    this.password = null;

    setObjectProperties(params, this);
}

user1 = new User();
// User { name: null, email: null, password: null }

user2 = new User({ name: 'First Last' });
// User { name: "First Last", email: null, password: null }

user3 = new User({ name: 'First Last', age: '400' });
// User { name: "First Last", email: null, password: null }

2 Comments

Why wouldn't you just use $.extend() – or even a simple for...in loop – in place of all that $.each() mess? This is an unnecessarily complicated implementation.
@MattBall - completely argree. Michael - see my answer for a much easier way of doing this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.