How Ext.apply works, and how to avoid a big headache

Ext.apply is one of those magic Ext JS methods which copies the essence of one object onto another. You usually call it like this:

Ext.apply(receivingObject, sendingObject, defaults)

Where defaults are optional. If you supply defaults, Ext.apply actually does this:

Ext.apply(receivingObject, defaults);
Ext.apply(receivingObject, sendingObject);

In other words, the order of precedence of the three arguments goes like this: any properties in receivingObject which are also present in defaults will be overwritten by the property in defaults. After that has happened, any properties which are present receivingObject (after defaults have been applied) and also present in sendingObject will be overwritten by the sendingObject value. More graphically:

Ext.apply({a: 'receiver'}, {a: 'sender'}, {a: 'default'}); // = {a: 'sender'}

For me, this was slightly unexpected as I expected the default options to have the lowest priority – that is the default option would only be copied across if it was not present in either the receiving or the sending objects, so watch out for that.

Anyway that’s all well and good once you know how it works inside, but while watching an otherwise excellent screencast from Jay Garcia (see, something odd happened. The example he gave went like this (commented lines signify the output returned by Firebug):

var obj1 = {x: 'x string', y: 'y string'}
// = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// = {a: 'a string', b: 4289, c: function(){}}

var obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
obj3 === obj2 
// true - obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'fwaifewfaije', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}

So basically he set up obj1 and obj2 with non-conflicting properties, then merged them with some defaults to create obj3. In this case the defaults didn’t conflict with the properties from obj1 or obj2, so obj3 is essentially a straightforward combination of obj1 and obj2, plus a default pxyz value.

What he did then however was to create obj4 as a combination of obj2 and obj3, along with a default value for the ‘a’ property, which was a property of obj2 and obj3. Crucially, obj4’s ‘a’ property was set to the default value, which as we’ve seen from how Ext.apply works above, should never happen (it should be set to the default value but then immediately set back again on the second internal Ext.apply call).

So what gives? Well, it turns out this is because when calling:

obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})

obj3 and obj2 are the exact same object, as Ext.apply returns the first argument after the apply process has taken place. So in the next call:

obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})

obj3 and obj2 are in fact both references to the same object, which is causing the unexpected default value. We can show this by manually creating a new obj3 with the exact same properties, and running the example again:

var obj1 = {x: 'x string', y: 'y string'}
// obj1 = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// obj2 = {a: 'a string', b: 4289, c: function(){}}

var obj3 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj2 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj3 === obj2 => false... obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}

This time around we get what we expect – the default value is not applied because it is already present in obj2. Here, obj2 is not the same object as obj3, even though their properties are identical.

I’m not completely certain why two references to the same object cause this behaviour, but the code above does appear to demonstrate that this is what is happening (you can just copy/paste each example into Firebug to reproduce it).

Moral of the story? Well, now you have a more detailed understanding of Ext.apply, and hopefully you’ll be on your guard about referencing the same object by two different variables when performing this type of operation. I know I will be 😉

11 Responses to How Ext.apply works, and how to avoid a big headache

  1. Jay Garcia says:

    Hi Ed,Thank you for this great writeup. It’s nice to see more content on Ext JS.

  2. Martin says:

    Excellent post. When I first came across the apply() issue I thought I was going mad. Turns out I’m not (yet).

  3. Roman Rozinov says:

    Thanks bro for heads up.

  4. Anonymous says:

    I'm on Ext 2.2When I run this exampleobj4 has c: function(){} apply should not copy function, right?

  5. Wilson says:

    That is because of a trick in the 2-step process. As you mentioned, obj3 = Ext.apply(obj3, obj2, {pxyz: ‘soifje’}) is actually accomplished by:
    temp = Ext.apply(obj3, {pxyz: ‘soifje’});
    obj3 = Ext.apply(obj3, obj2);
    You expect obj2 to restore obj3 back to what it was — but obj2 and obj3 refers the same object, so after the 1 step both have been changed (well, actually there is only one object and you can understand obj2/obj3 as its aliases).

  6. Mikkel says:

    Seems straigth forward to me.

    Ext.Apply doens’t check for the presence of a given value, so during default assignment it just moves “a” to obj3, however, since obj2 is refering to same object, when you get to the second step obj2 value isn’t the original value, and thus the “unexpected” happened.

  7. Steven says:

    Crisp sharp article, saved me a lot of time. Thanks!

  8. Bill says:

    Ext.apply has always bothered me, as it’s usage is rather confusing to me. What it is attempting to do is really more straightforward than the syntax allows.
    The objective really is to copy properties from one object to another. Throwing in the defaults is a bit of a red herring, conceptually.

    I use a home grown function called extend that does the same thing, but with any number of objects. For example, if I call extend(obj1, obj2), properties from obj2 are copied to obj1. If I call extend(obj1, obj2, obj3), properties are copied from obj2 to obj1, then from obj3 to obj1. I can list any number of object here and it will process them down the line. If I want to inject defaults into the mix, I just include them as obj2: extend(obj1, defObj, obj3).

    If I want to leave obj1 unchanged, I can simply call extend with an empty object as the first parameter: var newObj = extend({}, ob1, defObj, obj3) – or, I can call my other method, called descend, that does this under the hood: var newObj = descend(obj1, defObj, obj3).

    Make life much easier and there’s no confusion as to which gets applied first or when – it’s just left to right, plain and simple.

  9. James says:

    Why would it be unexpected to have the defaults applied first? Defaults are what you start with and ultimately what you end with if nothing changes. Thus, defaults should always be applied first, then overridden with any custom settings, otherwise you have to implement needless logic to determine which value to apply. If I understand Bill’s comment, he has the right approach, applying properties from N objects in increasing order of precedence. Default options DO have the lowest priority – that’s why you apply them first.

  10. abhay says:

    Excellent Example

  11. abhay says:

    How can I pass and receive extra arguments in a ‘click’ handler. Let say ::

    myBtn.on(click, this.myCallBackFunction);

    myCallBackFunction : function(args) {

    alert (‘extra arguments’ + args);

    I have to receive some custom argument in my callBackFunction, is it possible in ExtJs.

    Please reply…. want to learn a lot in ExtJs.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: