1

I believe this question relates to this: How does the init function work in plugins? and whoever can answer this can probably answer that.

I noticed something about jQuery, if I call my plugin like:

$('.view_title_images').prodigal({width: 500});
$('.glglg').prodigal({ width: 600 });

And then, in my init function I extend with:

options = $.extend({}, options, opts); 

and add that to each element: $(this).data('prodigal', options) in the selector. I get the correct width value for each element (500 for one and 600 for the other) later on when I call another function, open on the click of the element.

However if I do:

options = $.extend(options, opts);

For both selectors, despite being called separately, I get 600. I test this by doing, in my open function:

console.log($(this).data('prodigal'));

I know that not extending to an empty object will override the object for that selector/global object but why is this happening on the data of each selector?

Community
  • 1
  • 1
Sammaye
  • 43,242
  • 7
  • 104
  • 146

2 Answers2

1

First of all, it's an object, not an array. And you most likely don't want to extend the preexisting object, but instead create a new one that is based on the defaults, and then override those with the passed options, something like this:

options = $.extend({}, defaults, opts);

What $.extend does is that it will extend the first argument (an array or an object) with the rest of the arguments. Passing a preexisting object as the first argument will therefor not create a clone, but change the original.

In the example above, by passing a new object ({}) we instead create a clone of the second argument that we then override with the third.

Changing this would solve a lot, but you can still run into race-conditions since it will still share the same options object between instances. So, what if I want to change an option for just one or two of those instances?

The solution is simple, just move that line into the .each loop and every instance of your plugin will have its own options object.

Here's a test case on jsFiddle.

mekwall
  • 28,614
  • 6
  • 75
  • 77
  • "Otherwise you will just be changing the preexisting options." What do you mean by that? That is the essence of my question, why am I seeing the behaviour I am? – Sammaye Dec 10 '12 at 09:09
  • I run my `.each` and every iteration if the `data` for that element is none existant then I write it which means race conditions cannot exist on the global object. When I move it into the `.each` nothing changes...so I don't understand what you mean still. I mean the two calls to the two different selectors are synchronous not async – Sammaye Dec 10 '12 at 09:19
  • @Sammaye Can you add a test case (preferably on [jsFiddle](http://www.jsfiddle.net)) to your question? Might be something else that is wrong, but it's hard to tell merely based on what you've written :) – mekwall Dec 10 '12 at 09:25
  • Check out the console output of that bad boy: http://jsfiddle.net/Sammaye/65XLy/1/ – Sammaye Dec 10 '12 at 09:35
  • @Sammaye Ah. I see now... The problem here is that you aren't creating a new plugin instance. You are using a static object for the plugin logic. Lemme update your example so that it uses instancing instead ;) – mekwall Dec 10 '12 at 09:38
  • Have you got any document that explains how the static global object and the init function actually call in a situation like this? The linked question is heavily related to this. – Sammaye Dec 10 '12 at 09:40
  • I mean even as a static object it should be calling the init function twice right? So even though the static objects options will be over written the data on the object should be consistent with how I called it, it is almost like it is grouping my selector calls but when I put `console.log('in init');` on the first line of the init function it logs twice. – Sammaye Dec 10 '12 at 09:49
  • Poop I think I know why, it is the same reason why this would happen in PHP too. The object isn't being copied to the `data` of the element it is being referenced, hence why it works when copying to an empty object in extend because a new object is being written. This is proven because if you replace `$(this).data('prodigal', options).on('click', open);` with `$(this).data('prodigal', $.extend(options, opts)).on('click', open);` which does write a new object it works...damn dunno how I didn't see that. – Sammaye Dec 10 '12 at 09:55
  • Here's an [updated version](http://jsfiddle.net/mekwall/bvswE/) using instantiation instead. Check out the [jQuery Boilerplate](http://jqueryboilerplate.com/) for a good explanation of this pattern. – mekwall Dec 10 '12 at 09:58
0

Even though @Marcus' answer is a good one and it shows a good setup to plugins that will actually avoid this confusion I thought I would place this answer because it just answers the question a little better.

I am witnessing, like in PHP, a copy on write scenario for memory management here: What is copy-on-write? whereby my init function, as displayed in this fiddle: http://jsfiddle.net/Sammaye/65XLy/1 suffers from the static options object being referenced to each of the calls. So both of:

$('.view_title_images').prodigal({width: 500});
$('.glglg').prodigal({ width: 600 });

Reference the same position in memory for the options object in the plugin since the data assignment is not copy on write safe:

$(this).data('prodigal', options).on('click', open);

This is proven since if you change this line in the fiddle to:

$(this).data('prodigal', $.extend(options, opts)).on('click', open);

It actually works the same as var options = $.extend({}, options, opts); whereby it does copy to a new empty object on extend which in copy on write would trigger a copy.

That is why I am seeing this, because the data in each of the elements is actually a reference to the static objects (plugins) options object.

As an added note I actually found this soon after posting this answer: http://my.opera.com/GreyWyvern/blog/show.dml/1725165 whereby the author states:

There are a few things that trip people up with regards to Javascript. One is the fact that assigning a boolean or string to a variable makes a copy of that value, while assigning an array or an object to a variable makes a reference to the value.

Which explains my problem perfectly.

Community
  • 1
  • 1
Sammaye
  • 43,242
  • 7
  • 104
  • 146