Class.create and this keyword - javascript

I'm trying to use the Class methods of prototype.js to manage my object hierarchy on my current project, but I have some trouble with the this keyword in conjonction with Class.create.
Here is a piece of old-fashioned plain js code to create inheritance:
var Super1 = function () {
this.fu = "bar";
}
var Sub1 = function () {
this.baz = "bat";
this.f = function (e) {
alert("Sub1:"+this.fu+this.baz);
}.bindAsEventListener(this);
document.observe("click", this.f);
};
Sub1.prototype = new Super1();
new Sub1();
And here is my first attempt at mimicking this with Class.create:
var Super2 = Class.create({
fu: "bar"
});
var Sub2 = Class.create(Super2, {
baz: "bat",
f: function (e) {
alert("Sub2:"+this.fu+this.baz);
}.bindAsEventListener(this),
initialize: function () {
document.observe("click", this.f);
}
});
new Sub2();
So far so good... But of course it doesn't work: f is bound to window, not to the object created with new. The only way I found is:
var Super3 = Class.create({
fu: "bar"
});
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function (e) {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
this.f = this.f.bindAsEventListener(this);
document.observe("click", this.f);
}
});
new Sub3();
But it's really inelegant. How am I supposed to handle this?
edit (to reply to Colin):
I need to bind f itself, so that I can call stopObserving on f (see http://prototypejs.org/api/event/stopObserving)
I still need bindAsEventListener every time I need this inside the listener, since by default this is the element that fires the event (see http://prototypejs.org/api/event/observe)
I'm still waiting for an anwser on the googlegroup :) I kind of posted here to see whether I can get a faster answer via S.O.
I could (and probably should) use bind instead of bindAsEventListener. I used the latter to make it clear that I'm getting a listener. It doesn't change the fact that the binding procedure is inelegant.

You almost got it, just no need to redeclare this.f:
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function () {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
document.observe("click", this.f.bindAsEventListener(this));
// You could also use .bind(), since you don't pass any
// other arguments to f
}
});
Edit: In reply to your comment, you could do something like this (although it is arguably as 'ugly' as your example):
var Sub3 = Class.create(Super3, {
baz: "bat",
initialize: function () {
this.f = function () {
alert("Sub3:"+this.fu+this.baz);
}.bind(this);
document.observe("click", this.f);
}
});
Now you can use stopObserving with this.f. When you need to re-register the listener, you can simply use this.f again, since it'll still be bound to the same instance as was used when initialize was called.

I'm not too clear about this area, but I think the answer is (untested):
var Sub3 = Class.create(Super3, {
baz: "bat",
f: function() {
alert("Sub3:"+this.fu+this.baz);
},
initialize: function () {
document.observe("click", this.f.bind(this));
}
});
Class arranges that initialize is called as an object method of the new object, so 'this' refers to the new object inside 'initialize', so you just need to bind this.f to this.
Incidentally, bindAsEventListener is almost never needed: Event.observe (and Element.observe) call the handler with the event as the first argument anyway.
Incidentally 2, the Prototype & Scriptaculous google group is a very helpful forum.

Related

How to escape "this" problem in a javascript class?

get_O3(e)
{
e.preventDefault();
let station = document.getElementById(e.target.id);
let lon = station.getAttribute('lon');
let lat = station.getAttribute('lat');
let code_station = station.getAttribute('code');
this.get_previsions(lon, lat, "O3").bind(this).then((data) =>
{
console.log(data);
});
}
I have a "this" problem, when i call the function get_previsions i get the error :
Uncaught TypeError: this.get_previsions is not a function.
It might be because of the (e) parameter because when i do a console.log(this) it returns the target. I would like that this == my class.
Thanks for the help
At any given point you can check what the current this reference is pointing to by doing the 4 following rules:
New: Was the function called using new then the this points to the new instance.
Explicit Binding: Was the function called using Function#call, Function#apply or Function#bind
Implicit Binding: Was the function called by its owner? (i.e. owner.foo() or owner["bar"]())
Default Rule: If none of the other rules happen then this is set to the window object if the script is running in "use strict" mode otherwise undefined.
Event-listeners call a function using Explicit binding (callBack.call(target, ...)) so the this reference gets set to the target. To change the this reference you either need to wrap it and call it implicitly or use Function#bind.
Implicit call Example (+ closure):
var something = {
foo: function() {
var self = this;
addEventListener("click", function(e) {
self.bar(e);
});
},
bar: function() {
}
};
Explicit call Example (Function#bind):
var something = {
foo: function() {
addEventListener("click", this.bar.bind(this));
},
bar: function() {
}
};
I'm assuming you have a class defined similar to
class thing {
get_O3(e) { ... },
get_previsions() { ... }
}
There are a few options for you. First option, you can bind all functions to this in the constructor:
class thing {
constructor () {
this.get_03 = this.get03.bind(this);
this.get_previsions = this.get_previsions.bind(this);
}
get_O3(e) { ... },
get_previsions() { ... }
}
This can get awkward, especially if you have many functions. You can write a helper bindAll function, but a less awkward/verbose solution is to use a factory method instead, bypassing this altogether:
function makeThing {
const thing = {
get_O3(e) {
...
thing.get_previsions();
},
get_previsions() { ... }
};
return thing;
}
Eric Elliot on Medium has some good reading on the topic if you want to get more in depth.

Recursive function within Object constructor and TypeError

Given the following <img id="f1" src="img/fish1.gif">, i have created an object constructor as follows ( narrowed down the code for demo purposes):
$(function(){
function fish(i){
this.f = $('#f'+i)[0];
this.d = '-=1px';
this.animateFish = function(){
$(this.f).animate({"left": this.d} ,10, "linear", this.checkCollision);
}
this.checkCollision = function(){
this.animateFish(); //TypeError: this.animateFish is not a function
}
}
var f1 = new fish(1);
f1.animateFish();
})
I am supposed to animate the img (#f1 in this case), check for collision and then recall this.animate which throws the above error,any fix?
Within the callback function of animate(), this will refer to the element being animated, not the fish object. Hence calling this.animateFish() gives you a function not found error.
To fix this, store a reference to the fish object in a variable in scope of the event handler which you can then use within the event handler. Try this:
function fish(i) {
var _this = this;
_this.$f = $('#f' + i);
_this.d = '-=1px';
_this.animateFish = function() {
_this.$f.animate({ "left": _this.d }, 10, "linear", _this.checkCollision);
}
_this.checkCollision = function() {
_this.animateFish();
}
}
Every time you declare a new function () {}, any this inside will refer to the newly created context in the function. In your constructor, you have
this.animateFish = function(){
$(this.f).animate({"left": this.d} ,10, "linear", this.checkCollision);
}
So the this.f, this.d, and this.checkCollision inside the function do not refer to properties on the object you’re constructing, but instead they refer to the new this context created by that function.
As Rory McCrossan’s answer says, you could make a new variable, var _this = this (although I’d prefer var self = this but that’s just my style). However, in ES6 you can now use arrow functions, which do not have their own this context.
function fish(i){
this.f = $('#f'+i)[0];
this.d = '-=1px';
this.animateFish = () => {
$(this.f).animate({"left": this.d} ,10, "linear", this.checkCollision);
}
this.checkCollision = () => {
this.animateFish();
}
}
When you declare a new () => {} function, any this inside will “inherit”, that is, refer to the same this context in which the function was declared. So in my code above, this.f, this.d, this.checkCollision, and this.animateFish refer to properties of the newly constructed object, which is what you intended.
It’s best not to define methods of an object inside the constructor, but instead on the prototype. If you define methods in the constructor, then every time a new fish() is constructed, it will get its own copy of those methods. Instead, you can save space by assigning the methods to the prototype, so that all the fish instances can share the same methods.
There are two ways to do this. One is using the old ES5 technique, by literally accessing the prototype.
function fish(i){
this.f = $('#f'+i)[0];
this.d = '-=1px';
}
fish.prototype.animateFish = function () {
$(this.f).animate({"left": this.d} ,10, "linear", this.checkCollision);
}
fish.prototype.checkCollision = function () {
this.animateFish();
}
That’ll fix the bug in your code at the same time, because the this inside each prototype function will indeed refer to the fish instance. For you to fully understand how this code works, you need to have some familiarity with the prototype chain, which is why I recommend the technique below—it ‘just works’.
The second technique is to use ES6 classes, if you can support it.
class fish {
constructor(i){
this.f = $('#f'+i)[0];
this.d = '-=1px';
}
animateFish() {
$(this.f).animate({"left": this.d} ,10, "linear", this.checkCollision);
}
checkCollision() {
this.animateFish();
}
}
This will also fix your bug, because the this inside each method will refer to the fish instance.
First thing, there is an inifinite loop as animateFish call checkCollision and vice versa.
Secondly, in your animate call yuou have to bind the this when calling checkCollision otherwise the this referred to inside checkCollision will be the DOM object.
function fish(i) {
this.f = $('#hlogo');
this.d = '-=1px';
this.animateFish = function() {
$(this.f).animate({
"left": this.d
}, 10, "linear", this.checkCollision.call(this));
}
this.checkCollision = function() {
this.animateFish();
//TypeError: this.animateFish is not a function
}
}
var f1 = new fish(1);
f1.animateFish();

Crockford's Prototypical Inheritance - Usage

I've been building a small JS framework for use at my job, and I'd like to employ Douglas Crockford's prototypical inheritance patterns. I think I get the general idea of how the prototype object works, but what isn't clear is the way in which I would use this pattern beyond the simplest example.
I'll flesh it out to the point that I understand it.
(function () {
'use strict';
var Vehicles = {};
Vehicles.Vehicle = function () {
this.go = function () {
//go forwards
};
this.stop = function () {
//stop
};
};
Vehicles.Airplane = Object.create(Vehicles.Vehicle());
}());
So now my Vehicles.Airplane object can go() and stop(), but I want more. I want to add takeOff() and land() methods to this object. I could just use ugly dot notation afterwards:
Vehicles.Airplane.takeOff = function () {
//take off stuff
}
But that seems wrong, especially if I were to add many methods or properties. The question asked at here seems to be very similar to mine, but the answer doesn't quite ring true for me. The answer suggests that I should build an object literal before using Object.create, and that I should pass that object literal into the create method. In the example code given, however, it looks like their new object inherits nothing at all now.
What I'm hoping for is some syntax similar to:
Vehicles.Airplane = Object.create(Vehicles.Vehicle({
this.takeOff = function () {
//takeOff stuff
};
this.land = function () {
//land stuff
};
}));
I know this syntax will break terribly with Object.create right now, because of course I'm passing Vehicle.Vehicle a function rather than an object literal. That's beside the point. I'm wondering in what way I should build new properties into an object that inherits from another without having to list them out one at a time with dot notation after the fact.
EDIT:
Bergi, after some anguished thought on the topic, I think I really want to go with what you described as the "Classical Pattern". Here is my first stab at it (now with actual code snippets rather than mocked up hypotheticals - You even get to see my crappy method stubs):
CS.Button = function (o) {
o = o || {};
function init(self) {
self.domNode = dce('a');
self.text = o.text || '';
self.displayType = 'inline-block';
self.disabled = o.disabled || false;
self.domNode.appendChild(ctn(self.text));
if (o.handler) {
self.addListener('click', function () {
o.handler(self);
});
}
}
this.setText = function (newText) {
if (this.domNode.firstChild) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.domNode.appendChild(ctn(newText));
};
init(this);
};
CS.Button.prototype = Object.create(CS.Displayable.prototype, {
constructor: {value: CS.Button, configurable: true}
});
CS.Displayable = function (o) { // o = CS Object
o = o || {};
var f = Object.create(new CS.Element(o));
function init(self) {
if (!self.domAnchor) {
self.domAnchor = self.domNode;
}
if (self.renderTo) {
self.renderTo.appendChild(self.domAnchor);
}
}
//Public Methods
this.addClass = function (newClass) {
if (typeof newClass === 'string') {
this.domNode.className += ' ' + newClass;
}
};
this.addListener = function (event, func, capture) {
if (this.domNode.addEventListener) {
this.domNode.addEventListener(event, func, capture);
} else if (this.domNode.attachEvent) {
this.domNode.attachEvent('on' + event, func);
}
};
this.blur = function () {
this.domNode.blur();
};
this.disable = function () {
this.disabled = true;
};
this.enable = function () {
this.disabled = false;
};
this.focus = function () {
this.domNode.focus();
};
this.getHeight = function () {
return this.domNode.offsetHeight;
};
this.getWidth = function () {
return this.domNode.offsetWidth;
};
this.hide = function () {
this.domNode.style.display = 'none';
};
this.isDisabled = function () {
return this.disabled;
};
this.removeClass = function (classToRemove) {
var classArray = this.domNode.className.split(' ');
classArray.splice(classArray.indexOf(classToRemove), 1);
this.domNode.className = classArray.join(' ');
};
this.removeListener = function () {
//Remove DOM element listener
};
this.show = function () {
this.domNode.style.display = this.displayType;
};
init(this);
};
CS.Displayable.prototype = Object.create(CS.Element.prototype, {
constructor: {value: CS.Displayable, configurable: true}
});
I should be quite clear and say that it's not quite working yet, but mostly I'd like your opinion on whether I'm even on the right track. You mentioned "instance-specific properties and methods" in a comment in your example. Does that mean that my this.setText method and others are wrongly placed, and won't be available to descendant items on the prototype chain?
Also, when used, it seems that the order of declaration now matters (I can't access CS.Displayable.prototype, because (I think) CS.Button is listed first, and CS.Displayable is undefined at the time that I'm trying to reference it). Is that something I'll just have to man up and deal with (put things in order of ancestry in the code rather than my OCD alphabetical order) or is there something I'm overlooking there as well?
Vehicles.Airplane = Object.create(Vehicles.Vehicle());
That line is wrong. You seem to want to use new Vehicles.Vehicle - never call a constructor without new!
Still, I'm not sure which pattern you want to use. Two are coming to my mind:
Classical Pattern
You are using constructor functions just as in standard JS. Inheritance is done by inheriting the prototype objects from each other, and applying the parent constructor on child instances. Your code should then look like this:
Vehicles.Vehicle = function () {
// instance-specific properties and methods,
// initialising
}
Vehicles.Vehicle.prototype.go = function () {
//go forwards
};
Vehicles.Vehicle.prototype.stop = function () {
//stop
};
Vehicles.Airplane = function() {
// Vehicles.Vehicle.apply(this, arguments);
// not needed here as "Vehicle" is empty
// maybe airplane-spefic instance initialisation
}
Vehicles.Airplane.prototype = Object.create(Vehicles.Vehicle.prototype, {
constructor: {value:Vehicles.Airplane, configurable:true}
}); // inheriting from Vehicle prototype, and overwriting constructor property
Vehicles.Airplane.prototype.takeOff = function () {
//take off stuff
};
// usage:
var airplane = new Vehicles.Airplace(params);
Pure Prototypical Pattern
You are using plain objects instead of constructor functions - no initialisation. To create instances, and to set up inheritance, only Object.create is used. It is like having only the prototype objects, and empty constructors. instancof does not work here. The code would look like this:
Vehicles.Vehicle = {
go: function () {
//go forwards
},
stop: function () {
//stop
}
}; // just an object literal
Vehicles.Airplane = Object.create(Vehicles.Vehicle); // a new object inheriting the go & stop methods
Vehicles.Airplane.takeOff = function () {
//take off stuff
};
// usage:
var airplane = Object.create(Vehicles.Airplane);
airplane.prop = params; // maybe also an "init" function, but that seems weird to me
You got Object.create wrong. The first argument should be an object (maybe that's why people suggested you pass a literal).
In your first example, you're actually passing undefined:
Vehicles.Airplane = Object.create(Vehicles.Vehicle()); // the function call will
// return undefined
The following would work, but it's not very Crockford-ish:
Vehicles.Airplane = Object.create(new Vehicles.Vehicle());
The way I believe Crockford would do it (or, at least, wouldn't complain of):
var Vehicles = {};
Vehicles.Vehicle = {
go : function() {
// go stuff
},
stop : function() {
// go stuff
}
};
Vehicles.Airplane = Object.create(Vehicles.Vehicle, {
takeOff : {
value : function() {
// take-off stuff
}
},
land : {
value: function() {
// land stuff
}
}
});
Note that Vehicles.Vehicle is just a literal, which will be used as the prototype for other objects. When we call Object.create, we pass Vehicles.Vehicle as the prototype, and takeOff and land will be own properties of Vehicles.Airplane. You may then call Object.create again, passing Vehicles.Airplane as the prototype, if you want to create e.g. a Boeing.
The own properties passed as the second parameter are packed in an object that contains a representation of their property descriptors. The outer keys are the names of your properties/methods, and each one points to another object containing the actual implementation as the value. You may also include other keys like enumerable; if you don't they'll take the default values. You can read more about descriptors on the MDN page about Object.defineProperty.

Mootools class variable scope

Take this class:
var MyClass = new Class({
Implements: [Events, Options],
initialize: function() {
this.a = 1;
},
myMethod: function() {
var mc = new differentClass({
events: {
onClick: function() {
console.log(this.a); // undefined (should be 1, 2, 3 etc)
this.a ++;
}
}
});
}
});
How do I keep the value of this.a? I am basically trying to draw a line (using canvas) from the last point to the co-ordinates just clicked.
[EDIT]
I dont want to bind this as it's bad apparently and it will over-ride the differentClass options.
several patterns are available.
decorator via .bind()
var mc = new differentClass({
events: {
click: function() {
console.log(this.a);
this.a ++;
}.bind(this) // binds the scope of the function to the upper scope (myclass)
}
});
keeping a reference.
var self = this; // reference parent instance of myClass
var mc = new differentClass({
events: {
click: function() {
console.log(self.a);
self.a ++;
}
}
});
pointing to a myClass method that can deal with it:
handleClick: function() {
this.a++;
},
myMethod: function() {
var mc = new differentClass({
events: {
click: this.handleClick.bind(this)
}
});
}
the 2-nd one - by storing a reference is preferred due to the smaller footprint and universal support whereas .bind is not available in every browser and needs to be shimmed as well as the extra time to curry the function on execution.
self is what you will find in mootools-core itself when possible.
if performance is not at risk, method 3 can probably offer the best readability and code structure. the arguments to the method will remain what the click handler passes, i.e. event and event.target will be the handler.
in pattern #2 with self, this will point to the click handler within the anonymous function (or to the other class, for example), which may be useful as well - rebinding context can be a pain in the neck
You can refer proper context like this:
...
myMethod: function() {
var _this = this;
var mc = new differentClass({
events: {
onClick: function() {
console.log(_this.a);
_this.a ++;
}
}
});
}
...

How do I find the instance to which a method belongs?

// minified base class code (if the uncompressed code is needed, I'll post it)
function Class(){}Class.prototype.construct=function(){};Class.extend=function(c){var a=function(){arguments[0]!==Class&&this.construct.apply(this,arguments)},d=new this(Class),f=this.prototype;for(var e in c){var b=c[e];if(b instanceof Function)b.$=f;d[e]=b}a.prototype=d;a.extend=this.extend;return a};
// custom event class
var Event = Class.extend({
handlers: [],
// stores the event handler
subscribe: function(handler, thisObj) {
this.handlers.push([handler, thisObj]);
},
// calls the event handlers
fire: function() {
for(i in this.handlers) {
var handler = this.handlers[i];
handler[0].apply(handler[1]);
}
}
});
var Class2 = Class.extend({
myEvent: new Event(), // the event
test: function() { // fires the event
this.myEvent.fire(this);
}
});
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
model.myEvent.subscribe(this.handler, this); // subscribe to the event
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
var instance1 = new Class2();
var instance2 = new Class3(instance1);
instance1.test();
The only way to make the event handler code to work with the good 'this' is by adding a new argument ('thisObj') to the 'subscribe' method? Is there a better way to do this?
The behaviour you're getting is due to the fact that when you pass a "method" to a function, the receiving function has no idea it's a method. It's just a block of javascript that needs to get executed.
Prototype gets around this issue with the bind method
http://api.prototypejs.org/language/function/prototype/bind/
You can get similar behaviour (I didn't look at the implementation details of bind) by using a closure.
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
//model.myEvent.subscribe(this.handler, this); // subscribe to the event
var self = this;
model.myEvent.subscribe(function() {self.handler()});
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
Or apply some similar functionality to the subscribe method of your custom event class.
EDITED To reflect CMS's observations. Thanks!

Categories

Resources