What does 'this' refer to in this function? - javascript

The entire code snipped is:
var observer = {
addSubscriber: function(callback) {
this.subscribers[this.subscribers.length] = callback;
},
removeSubscriber: function(callback) {
for (var i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === callback) {
delete(this.subscribers[i]);
}
}
},
publish: function(what) {
for (var i = 0; i < this.subscribers.length; i++) {
if (typeof this.subscribers[i] === 'function') {
this.subscribers[i](what);
}
}
},
make: function(o) { // turns an object into a publisher
for(var i in this) {
o[i] = this[i];
o.subscribers = [];
}
}
};

It depends on how it is called. I see it is part of an object literal called observer.
observer.make(o) would mean this == observer.
observer.make.call(otherObj, o) would mean this == otherObj.
new observer.make(o) would make a new object to be this
So it would do something like this.
var model = {
name: 'bike',
id: 4,
gears: 7
};
observer.make(model);
//now model has methods from observer
model.addSubscriber(someListener);
model.publish('gearsChanged');

"this" refers to "observer" assuming that is the object in which it was invoked (and in 99% of cases it is);
so: observer.addSubscriber
in the method addSubscriber, "this" will refer to "observer".
When you have objects within objects (or nodes) it can be confusing to resolve "this":
observer = {
node: $("myDiv"),
callIt: function(){
// note "this.node" - node belongs to observer
this.node.onclick = function(){
// "this" now refers to the "node" object
// onclick was invoked by node
}
}
}

this, is how you refere at the scope of a function. it's the function itsel.!!! this example in prototypejs framework is quite handy.
http://api.prototypejs.org/language/function/prototype/bind/
for example if you the following code.
function foo(){
//here this is foo
var x = {}; //object
var me = this;
var img = new Image();
img.load = function(){
//but here this is img.load.. is the scope of the function =)
// if you want to use the x object you have to assing this FOO a global variable is why you use me = this;
me //is foo :P
}
}

Related

Accessing object properties in listener method

Let's say I have the following code:
var Obj = function() {
this.property = 1;
this.arr = [...] // array containing elements we want to add event listeners to
for (...) {
this.arr[i].addEventListener("click", this.listener, false);
}
}
Obj.prototype.listener = function() {
console.log( this.property ); // DOES NOT WORK! *this* does not point to Obj.
}
var a = new Obj();
How do I access object properties (and methods) within a listener? I would assume I'd need to pass it as a parameter? Is the way I'm going about this structurally wrong?
When the function is called as an event listener, the context (this) is changed to something other that the object itself.
To resolve this, manually bind the context to the object instance in the constructor using bind(). This way, this will always point to the object instance, independent of the calling context:
var Obj = function() {
this.property = 'foo';
this.listener = this.listener.bind(this);
}
Obj.prototype.listener = function() {
console.log(this.property);
}
var a = new Obj();
a.listener.call({});
As suggested by #Tushar, you can use Function.prototype.bind() and pass this.property as parameter
<body>
click
<script>
var Obj = function() {
var obj = this;
this.property = 1;
this.arr = [document.body];
for (var i = 0; i < obj.arr.length; i++) {
obj.arr[i].addEventListener("click"
, obj.listener.bind(obj.arr[i], obj.property), false);
}
}
// note order of parameters; e.g., `prop`, `e`
Obj.prototype.listener = function(prop, e) {
console.log(prop, e); // `1`, `event` object
}
var a = new Obj();
</script>
</body>

Using call to pass context

I'm trying to use call to pass the context of the Utilities object so I can access its members (allData array, etc) within the myTest function.
I'm getting error:
ReferenceError: allData is not defined
This tells me the context is lost and I guess I'm not binding the context correctly. How do I do this?
var Utilities = {
allData : [],
storage : [],
apiRequest : function () {
this.allData = ["a","b","c"];
var myTest = this.allData.map.call(this, function (i, el) {
var url = 'testPHP.php/translate_tts?ie=utf-8&tl=zh-CN&q=' + i;
return $.get(url, function (returned_data) {
this.storage.push(returned_data);
});
});
$.when.apply($, myTest).done(function () {
log('done!');
log(this.storage[i]);
});
Reminder
There is only function level context in Javascript, and this is nothing more than a local variable. By default, this is set to the global window object:
function me () { return this; };
me() === window; // true
Or to the object from which the function was invoked:
var o = {};
o.me = me;
o.me() === o; // true
Knowing this, read the following carefully:
var me = o.me;
me === o.me; // true, not a copy
me() === o; // false
me() === window; // true
var p = {};
p.me = o.me;
p.me() === p; // true
o.me() === o; // true
As you can see, this is automatically set at function invocation. This is the default behaviour, but you can also do it yourself using either .call() or .apply() (one shot):
me.call(o) === o; // true
me.apply(o) === o; // true
p.me.call(o) === o; // true
me() === window; // true
And more recently, .bind() (permanent):
me = me.bind(o);
me() === o; // true
me() === window; // false
Your question
I would use .bind():
var Utilities = {
allData : [],
storage : [],
apiRequest : function () {
this.allData = ["a","b","c"];
var myTest = this.allData.map(function (i, el) {
var url = 'testPHP.php/translate_tts?ie=utf-8&tl=zh-CN&q=' + i;
return $.get(url, function (returned_data) {
this.storage.push(returned_data);
}.bind(this));
}.bind(this));
$.when.apply($, myTest).done(function () {
log('done!');
// I've added a loop here
var i, l = arguments.length;
for (i = 0; i < l; i++) {
log(this.storage[i]);
}
}.bind(this));
Great answer above. One thing to emphasize at this point: whenever you bind the object at initialization it will be bound and cannot be call/apply'd using another context anymore. IMO it's up to you whether to use call/apply (at runtime) OR .bind (permanently).
Going from here:
I'm trying to use call to pass the context of the Utilities object so I can access its members (allData array, etc) within the myTest function.
var OtherTest = function() {
this.dataStorage.push(["ok"]);
console.log(arguments);
}
var Utilities = {
dataStorage: [],
allData: [],
apiRequest: function() {
this.allData = ["a","b","c"];
OtherTest.apply(this,arguments);
}
}
Utilities.apiRequest('hu','hu');
console.log(Utilities.dataStorage[0]);
Since this is a reference to the object, it can be mutated at any time after initialization making it easy to use call/apply to pass the context which is the Utilities Object in this case.

JavaScript Event implementation to Closure based Object

I have a Object based on some closure, and want to implement event scheme here:
var class1 = function(val1)
{
var val = val1;
//------ want to call a method of Object of class1--------
var self = this;
setTimeout(function()
{
self.onEvent();
}, 1000);
//----------------
return {
f1: function()
{
return val;
},
onEvent: function()
{
console.log('not implemented yet. Override');
}
};
};
var obj1 = class1(5);
console.log(obj1.f1()); //5
obj1.onEvent(); //not implemented yet. Override
obj1.onEvent = function()
{
console.log('event fired');
}
got error, and I know the reason, and I need a solution:
5
not implemented yet. Override
/....../app.js:9
self.onEvent();
^
TypeError: Object #<Object> has no method 'onEvent'
It is possible if this bind with addEventListener scheme like this:
(The idea based on
Implementing events in my own object
)
var class2 = function()
{
var _this = this;
_this.events = {};
var fireEvent = function(name, args)
{
if (!_this.events.hasOwnProperty(name)) return;
if (!args || !args.length) args = [];
var evs = _this.events[name];
var l = evs.length;
for (var i = 0; i < l; i++)
{
evs[i].apply(null, args);
}
};
setTimeout(function()
{
fireEvent('testEvent', ['hello'])
}, 1000);
return {
addEventListener: function(name, handler)
{
if (_this.events.hasOwnProperty(name))
_this.events[name].push(handler);
else
_this.events[name] = [handler];
}
};
};
var obj2 = class2();
obj2.addEventListener('testEvent',
function(data)
{
console.log('event fired: ' + data);
});
event fired: hello
However, I prefer not to use addEventListener but .onEvent() scheme.
Is it possible? Perhaps it is possible using call/apply.
Thanks for your advice.
In your first block of code, you are returning an object, which is different from this or self.
You don't necessarily have to return this in your constructors but you should assign your functions on the returned object. If you create a variable for the object you want to return, you can use it in your setTimeout callback like so:
var class1 = function(val1)
{
var val = val1;
var obj = {
f1: function()
{
return val;
},
onEvent: function()
{
console.log('not implemented yet. Override');
}
};
setTimeout(function()
{
obj.onEvent();
}, 1000);
return obj;
};
For extra style points, you might want to capitalize the name of your constructors (and perhaps use new to instantiate them to make things clearer to your readers).

How to override Function.length to return the length of an array, in strict mode

I am looking for a way to override the .length property of an Object in JavaScript.
I currently have a wrapper on parent.properties.objects array
(parent is used to for the code to be more readable in context)
This is the basic structure:
(parent variable is defined in namespace and intialized)
var parent = function () {
this.properties = {
objects: [];
};
};
wrapper
(function () {
"use strict";
objects = function () {
If no argument is passed, assume get
if (arguments.length === 0) {
var _objects = parent.properties.objects;
return _objects;
modify or filter objects
} else if (arguments.length > 0) {
...
}
};
this creates a object.prototype (not [prototype]) variable and adds the method length()
objects.prototype.length = function () {
var length = parent.properties.objects.length;
return length;
}
Error
objects.prototype.__proto__.length = function () {
var length = parent.properties.objects.length;
return length;
}
parent.objects = objects;
})();
Assuming I've understood your question correctly, the following code might help you:
function MyObject() {
this.myActualData = [];
}
Object.defineProperty(MyObject.prototype, 'length', {get: function() {
return this.myActualData.length;
}});
And here's an example of it in use:
var x = new MyObject();
x.myActualData.push("Hello");
x.myActualData.push("World");
x.length; // is 2
Note: this will only work on ecmascript 5 and above browsers.

Javascript, value types as reference

I've some scenarios where i need to pass value type as reference without changed the processing function.
Example Numeric Types (var limit)
var limit = 0; // Need to be the reference type
var multiCallback = new MultiCallback(limit, function(){});
for (element in myObject)
{
limit++;
element.DoSomething(multiCallback.callback);
}
function MultiCallback(limit, func)
{
var calls = 0;
function callback()
{
if (++calls == limit)
{
func();
}
}
return {
callback : callback
}
}
Examble Function Types
var resizeCallback = function(){};
$(window).resize(resizeCallback);
function showPage()
{
resizeCallback = resizePage();
}
function showLoader()
{
resizeCallback = resizeLoader();
}
is there a solution
Changing the value of a variable will never update the previous value of the variable.
For functions, you can do something like this:
var current = function() { ... };
function resizeCallback() {
return current.apply(this, arguments);
}
// Updating current will work:
current = function() { ... something ... };
For other values (primitives and objects), the closest thing is to pass an object:
var limit = {value: 0};
function MultiCallback(limit, func) {
....
if (limit.value == ++calls) ...
}
// Changing limit:
limit.value = 1;
There is no pass by reference in javascript (assigning arguments is not visible to the caller). What you are doing in your function example is modifying a global variable.
You can wrap it with an object and mutations of that object are visible to the caller:
var limit = {
value: 0
};

Categories

Resources