4 ms·
While this README is very kindly worded, there are several things to be careful of here: Patching in Object.create with a version that will break third-party c
by jashkenas 13y ago
While this README is very kindly worded, there are several things to be careful of here:
Patching in Object.create with a version that will break third-party code?
https://github.com/javascript/augment/blob/master/lib/augment.js#L25-L27 https://github.com/javascript/augment/blob/master/lib/augmen...
Extending Object.prototype (!!!) inconsistently between old and new browsers?
https://github.com/javascript/augment/blob/master/lib/augment.js#L38-L42 https://github.com/javascript/augment/blob/master/lib/augmen...
Just because this is voted to the top of the HN front page ... apparently doesn't mean you don't still need to watch your step.
If you'd like a safe, small and fast function for conveniently setting up the prototype chain (a.k.a. classical inheritance in JavaScript), that works cross-browser, try this one on for size:
function augment(parent, properties) {
var child = properties.constructor || function() {
return parent.apply(this, arguments);
};
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
for (var key in properties) {
child.prototype[key] = properties[key];
}
return child;
};
... the nasty bit of JavaScript-specific business there being the intermediate Surrogate, so that you don't need a concrete instance of the parent class to be instantiated, in order to set your prototype chain. Used like so:
var Person = augment(Model, {
sortableName: function() {
return this.lastName + ', ' + this.firstName;
}
});
- rubiquity 13y agoI was going to write a reply like this: "Congrats, you just implemented what `extends` does in CoffeeScript!" And then I saw your username. This is still the best implementation of inheritance in JavaScript that I have seen.
- yuchi 13y agoI still don’t understand why no one does `child.__proto__ = parent;` to inherit properties on the constructor chain too.
- nevir 13y agoThere's a bunch of caveats to __proto__: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- deleted 13y ago[deleted]
- jashkenas 13y agoBecause then your JavaScript will break in both old, and even relatively recent versions of Internet Explorer.
- ivan_ah 13y agoCool, this is neat. Here is a fuller usage example: var Rectangle = augment(Object, { constructor: function (width, height) { this.height = height; this.width = width; }, area: function () { return this.width * this.height; } }); var Square = augment(Rectangle, { constructor: function (side) { Rectangle.call(this, side, side); } }); sq = new Square(3) //augment.constructor {height: 3, width: 3, constructor: function, area: function} // it will be a little annoying to see augment.constructor in the console sq.width // 3 sq.area() // 9 Square.prototype // Surrogate {constructor: function, area: function} Square.constructor // function Function() { [native code] } sq instanceof Rectangle //true
- rattray 13y agoWould anyone mind annotating jashkenas' version of this? For someone like me who isn't yet an advanced Javascript user, I feel this could be a terrific, concise learning opportunity, and while I understand more or less what is going on, I have next to no idea as to the why.
- jashkenas 13y agoYou're in luck. I boiled it down from the slightly more comprehensive version that Backbone uses, annotated here: http://backbonejs.org/docs/backbone.html#section-206 http://backbonejs.org/docs/backbone.html#section-206 The main difference being that the Backbone version is also copying "static" properties -- properties defined directly on the parent constructor function -- down into the subclass.
- rattray 13y agoThanks very much for the pointer!
- jtblin 13y agoThis is nice but not quite right, it only works when overwriting the constructor. Every object in Javascript as a constructor property defined var a = {}; a.constructor // display function Object() { [native code] } Therefore the test on `properties.constructor` doesn't work to extend objects without overwriting the constructor e.g. var MyString = augment(String, {}); var myString = new MyString; myString // display Object {} myString.length // display undefined To solve this you need to test if the constructor is not the default one i.e. var obj = {}; var child = properties && properties.constructor && properties.constructor !== obj.constructor ? properties.constructor : function() { return parent.apply(this, arguments); }; Now you can extend objects without providing a new constructor: var MyString = augment(String, {}); var myString = new MyString; myString // display child {constructor: function} myString.length // display 0 Nice little utility other than that.
- aaditmshah 13y agoIn my humble opinion the smallest classical OOP pattern is as follows: function defclass(prototype) { var constructor = prototype.constructor; constructor.prototype = prototype; return constructor; } You may then use it as: var Rectangle = defclass({ constructor: function (width, height) { this.height = height; this.width = width; }, area: function () { return this.width * this.height; } }); The only problem is that this doesn't support inheritance which is why I created `augment`. While creating `augment` I kept the following things in mind: 1. I didn't want to use a `for..in` loop to copy all the methods of the "class". Hence I used a function instead of an object to model the prototype. 2. Using a function instead of an object to model the prototype is similar to the module pattern. In addition you can create private static properties of a class (such as configuration) easily. 3. I wanted `augment` to work for both the [constructor pattern and the prototypal pattern](http://stackoverflow.com/a/16872315/783743 http://stackoverflow.com/a/16872315/783743) which is why `base` may be either a function or an object. 4. I wanted `augment` to be able to create singleton instances (again in the spirit of the prototypal pattern of prototypal inheritance). Hence I simply return the prototype if the "class" doesn't have a constructor. Keeping these points in mind I added 4 more lines to the `defclass` function to create `augment`: function augment(base, body) { var uber = typeof base === "function" ? base.prototype : base; var prototype = Object.create(uber); body.apply(prototype, [].slice.call(arguments, 2).concat(uber)); if (!prototype.hasOwnProperty("constructor")) return prototype; var constructor = prototype.constructor; constructor.prototype = prototype; return constructor; } The above function is essentially the `augment` function in my GitHub repository minus the following features: 1. It's not defined as a method of `Function.prototype` or `Object.prototype`. Hence you can't call it as a method on a function or an object. I added this feature for flexibility of style. In retrospect I believe that it's unnecessary and hence I'll remove it. 2. It won't work in browsers which don't support and haven't patched `Object.create`. I understand that the patch my library provides is incomplete. In my defense I never thought that it would become infamous overnight. I'll fix this as well. 3. It doesn't use `bindable`, `callable` and other functional features I commonly use in my code. When I created `augment` I never expected many people to use it. Hence I put in all the functional features I commonly use in all my programs. 4. It doesn't use the `{}.hasOwnProperty.call` hack. Again I programmed defensively in my library. However I doubt that JavaScript programmers usually create classes which don't inherit from `Object.prototype`. Hence I'll remove that hack as well. In short I'll remove all the unnecessary features from `augment` that I commonly use and deliver the smallest classical OOP library as promised.