-
-
Save aroc/5715154 to your computer and use it in GitHub Desktop.
// Allows you to create a child which is a copy of the parent and changing the child or parent doesn't affect one another. | |
// Calling "this" inside the child won't change attributes of the parent, as it is a copy and not connected through the prototype chain. | |
define(['jquery', 'underscore', 'backbone'], function ($, _, Backbone) { | |
// NOTE: This works in every scenario I've tested it in. However, this code looks bad | |
// and there may very well be a much cleaner + better way to handle this. Or maybe not | |
// because JS prototypes are crazy. | |
function childOfClass (parentFunction, newAttributes) { | |
// Create the new function and ensure to call Backbone's standard constructor | |
// with the context of *this* (this function). | |
// NOTE: We could change this to be the constructor of the parentFunction in order to | |
// have the parentFunction constructor code ran, then add in a call to | |
// this.initialize.apply(this, arguments) to *also* run this function's initialize method upon | |
// object construction. | |
function childFunction () { | |
Backbone.Model.prototype.constructor.apply(this, arguments); | |
} | |
// Set the prototype equal to that of a NEW parentFunction. We do new | |
// parentFUnction instead of just parentFunction or parentFunction.prototype | |
// because this breaks the prototype chain and allows us to inherit a *copy* of the | |
// parentFunction's attributes. | |
childFunction.prototype = new parentFunction(); | |
// After we've set the prototype equal to that of the parentFunction, ensure | |
// the constructor is set to the childFunction function, or the Backbone.Model | |
// constructor code won't get run when the object is created. | |
childFunction.prototype.constructor = childFunction; | |
// Extend the prototype with the new / additional functionality. | |
_.extend(childFunction.prototype, newAttributes); | |
// Return the new function / class. | |
return childFunction; | |
} | |
return childOfClass; | |
}); |
I'm still confused. When you create an instance with the new operator, you don't directly modify the prototype chain at all. This also seems like it's pretty much the exact same as the built-in code that Backbone has for Backbone.Model.extend().
Do you have an example that demonstrates the problem you're trying to solve? I mentioned earlier about using hashes and arrays as default values in a class's prototype.
Example:
MyView = Backbone.View.extend({
stuff: [1,2,3]
});
MyOtherView = MyView.extend({});
myView = new MyView();
otherView = new MyOtherView();
otherView.stuff.push(4);
console.log(myView.stuff);
// -> [1,2,3,4]
Because all objects (arrays and hashes included) are stored by referenced in JavaScript, if you store one in the prototype, then that one object reference is shared across all instances of the prototype. Numbers and strings are not affected in the same way because they are immutable in JavaScript.
I just wasn't sure if that's the issue you were dealing with, but I've dealt with that issue in the past, and the way to solve it is to initialize non-constant value (e.g. arrays/hashes) in your constructor. Based on the description you were giving, I thought perhaps this is what you were dealing with.
Notice the constructor usage instead of defining it in the prototype:
MyView = Backbone.View.extend({
initialize: function() { this.stuff = [1,2,3] }
});
MyOtherView = MyView.extend({});
myView = new MyView();
otherView = new MyOtherView();
otherView.stuff.push(4);
console.log(myView.stuff);
// -> [1,2,3]
The problem I was having was this: I have a UserModel and I have a SignupModel which extends (Backbone extends) the UserModel. I wanted to have the SignupModel make changes to some of the properties it inherited from the UserModel, but doing so updated the UserModel's prototype as well as the SignupModel's - thus any new instances of UserModel now have the changes I made in SignupModel. I was making the changes by referring to "this.X" in the initialize function.
UserModel = Backbone.Model.extend({
validation: 'user'
});
SignupModel = UserModel.extend({
initialize: function (attributes, options) {
this.validation = 'signup';
}
});
Now if I instantiate a SignupModel, the UserModel's "validation" value gets changed - which adversely affects my application. I just basically want a copy of all the functionality UserModel has, then I can make changes as I please.
I tried to find ways to call "this.validation" specifying the SignupModel as the specific context, but I couldn't get anything to work properly outside of the solution I posted above.
With the code you've shown, it's mixing prototype properties and instance properties. UserModel
has a validation
property on its prototype, while SignupModel
does not. Instances of UserModel
do not have an instance property for validation
, instead getting their value from UserModel
's prototype. Instances of SignupModel
do have an instance property for validation
, which means the prototype chain is not referenced.
Here's a fiddle showing what I mean, open the console to see the output.
I forgot to mention the main point, that the fiddle shows that an instance of SignupModel
and instance of UserModel
have independent validation
values. In your code, I'm unsure how setting this.validation
in initialize
has any effect outside of that instance of SignupModel
.
How are you creating SignupModel
instances? Are you using call
, apply
, or _.bind
to create SignupModel
instances? The only way I can think of that would cause assigning to this.validation
to affect UserModel
is by supply UserModel.prototype
as this
via one of those functions. Maybe another possibility is forgetting to invoke with new
.
I'm also not seeing how this would break, unless you're not using the new
keyword. SignupModel.prototype
should inherit validation from UserModel
's prototype. But setting this.validation
in the constructor on SignupModel
should only change the value on that instance, not either of the prototypes.
Are you using call
or apply
anywhere to change the value of this
used when your constructor is called? That's what @kastiglione is getting at.
I've come across my issue. I want SignupModel to inherit values from UserModel, then I want to be able to directly modify those values while keeping a separate copy. This jsfiddle outlines precisely my issue.
@aroc: That's exactly what I was referring to. You're using a Hash/Object in the UserModel prototype, and you're just changing a single value in the child class. Both classes are sharing the same instance of the Hash/Object.
I haven't tried it, but try adding this.validation = $.extend(true, {}, this.validation)
to get a local deep copy of the validation
hash.
http://api.jquery.com/jQuery.extend/#jQuery-extend-deep-target-object1-objectN
Ya @kastiglione that should work. The issue with the previous solutions is that we are needing a deep copy of the validations object.
We tried that here a couple days ago. It does work, but the problem is I need to deep copy every attribute I want to from UserModel over to SignupModel. I'd prefer if I didn't need to go about it that way.
We tried doing a deep copy of the UserModel's prototype, but couldn't manage to get it working properly - it still got overridden.
The issue is that hasOwnProperty will only look at the immediate object and won't navigate the prototype chain. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
hasOwnProperty is only good for telling where the function/object is defined, not if the object will respond to the function. For that I would call it and see if you get 'undefined', unless there is a better way.
Add the following to your fiddle and you'll see it works fine.
alert((new UserModel).validation['user']['type']);
alert((new SignupModel).validation['user']['type']);
Unless I am missing something from your fiddle.
The other thing to be aware of is that it appears Backbone defines it's own extend function. If you are looking at the one in underscore, you are looking in the wrong place. It's at the bottom of the page. http://backbonejs.org/docs/backbone.html
@tylermercier The parts in the gist about hasOwnProperty
were from me, but they're irrelevant. See the last two comments by @ekryski and @aroc. Basically, they want deep copying of a number of properties from the parent's prototype, but they don't want to explicitly do that in each subclass initializer.
I don't totally understand why you need this, but I see what you're doing here. Looks like these are necessary hoops for javascript.
Being curious, I wondered how it could be done in coffeescript, this is the closest I could get.
Which generates: