I've got a big Javascript project that I'm trying to refactor into pseudo-classes:
jsFiddle: http://jsfiddle.net/waitinforatrain/7T42w/
var MyNameSpace = {}
MyNameSpace.MyClass = function() {
this.doSomething = function () {
return "hello";
}
this.doSomething2 = function() {
var x = this.doSomething() + " world";
alert(x);
}
this.doSomething2(); //Works fine
$("#mydiv").click ( this.doSomething2 ); //Doesn't work
}
var class = new MyNameSpace.MyClass();
The reason the click event causes an error is that this refers to the #mydiv element.
How am I supposed to design the above so that I can access the element that was clicked but can also call doSomething()?
You need to cache the context reference and wrap the call in a closure:
var MyNameSpace = {}
MyNameSpace.MyClass = function() {
var context = this;
context.doSomething = function () {
return "hello";
}
context.doSomething2 = function() {
var x = context.doSomething() + " world";
alert(x);
}
// You can do this:
context.doSomething2();
// Or this:
$("#mydiv").click(function(e) {
context.doSomething2();
});
}
this.doSomething2 = $.proxy(function() {
var x = this.doSomething() + " world";
alert(x);
}, this);
$.proxy binds the this scope to the context variable inside said function.
Save a reference to this in the outer scope:
MyNameSpace.MyClass = function() {
var that = this;
this.doSomething = function () {
return "hello";
}
this.doSomething2 = function() {
var x = that.doSomething() + " world";
alert(x);
}
this.doSomething2(); //Works fine
$("#mydiv").click ( this.doSomething2 ); //Doesn't work
}
The function assigned to doSomething2 is said to "close over" the variables in its lexical scope and so has access to their values even once MyClass has returned. This allows us to access the doSomething method through the reference to the instance we assigned to that.
Related
Let's assume we have the following code:
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
Let's make two instances of the class:
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
Now if we console.log() the values we see that:
instance1.getValue(); // 10
instance1.getValue2(); // 20
var MyClass = (function(){
var _this;
function MyClass(inputVal){
_this = this;
this.value = inputVal;
}
MyClass.prototype.getValue = function(){
return this.value;
}
MyClass.prototype.getValue2 = function(){
return _this.value;
}
return MyClass;
})();
var instance1 = new MyClass(10);
var instance2 = new MyClass(20);
console.log(instance1.getValue());
console.log(instance1.getValue2());
Why is that happening? It looks obviously that the _this variable gets the latest created instance properties. How to fix that? I need to keep a copy of this. Thanks!
Edit:
Here's the real situation
var HoverEffects = (function(){
var _this;
function HoverEffects($nav){
_this = this;
this._$activeNav = $nav.siblings('.active_nav');
this._$hoverableLis = $nav.find('>li');
this._$activeLi = $nav.find('>li.active');
if(!$nav.length || !this._$hoverableLis.length || !this._$activeNav.length || !this._$activeLi.length) return;
if(this._$activeNav.hasClass('bottom')){
this._$activeNav.align = 'bottom';
this._$activeLi.cssDefault = {
left: this._$activeLi.position().left,
width: this._$activeLi.width()
};
}
else if(this._$activeNav.hasClass('left')){
this._$activeNav.align = 'left';
this._$activeLi.cssDefault = {
top: this._$activeLi.position().top,
height: this._$activeLi.height()
};
}
else{
return;
}
this._$hoverableLis.hover(
function(){
// How to set the correct this inside this function?
if(this._$activeNav.align === 'bottom'){
this._$activeNav.css({
left: $(this).position().left,
width: $(this).width()
});
}
else if(this._$activeNav.align === 'left'){
this._$activeNav.css({
top: $(this).position().top,
height: $(this).height()
});
}
},
function(){
// Same here, wrong this
this._$activeNav.css(this._$activeLi.cssDefault);
}
);
}
return HoverEffects;
})();
var sideNavHoverMagic = new HoverEffects($('#side-navigation'));
var primaryNavHoverMagic = new HoverEffects($('#primary-navigation'));
Why is that happening?
Every time you call new MyClass, _this = this gets run. The second time overrides the first time.
So _this refers to new MyClass(20), which means that when you call getValue2 from any MyClass instance, 20 will be returned because all MyClass instances are referring to the same _this value.
Based on commentary on the Question:
If you're attempting to pass a function bound to the appropriate context there are a variety of ways to make sure that this refers to the right object. Before continuing, please read "How does the 'this' keyword work?", because there's no reason for me to repeat all of it here.
If you're binding event callbacks such as in a constructor:
function Example(something) {
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
The callback will have the wrong this value because it's not being called as this.callback, it's just being called as:
fn = this.callback;
fn(); //no reference to this
You can get around this in a number of ways.
Function.prototype.bind
You can bind the callback for every instance on their respective instance. This is very concise:
function Example(something) {
//generate a new callback function for each instance that will
//always use its respective instance
this.callback = this.callback.bind(this);
something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
this.doStuff();
this.doMoreStuff();
};
that = this
You can create the callback (closure) within the constructor and reference a variable inside the constructor.
function Example(something) {
//every Example object has its own internal "that" object
var that = this;
this.callback = function () {
//this function closes over "that"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
() => {} (Fat Arrow Syntax)
If you're using ES2015 you can use "fat arrow" syntax for creating lambdas that don't create a new context:
function Example(something) {
this.callback = () => {
//the callback function doesn't create a new "this" context
//so it referes to the "this" value from "Example"
//every instance will have its own function rather than
//a shared prototype function.
that.doStuff();
that.doMoreStuff();
}
something.addEventListener(..event.., this.callback, false);
}
I have an Javascript object following the Module Pattern
var foo = (function() {
var obj = (function() {
var $button = $('#myButton');
var init = function() {
$button.hide();
};
return {
init: init
};
})();
return { obj: obj };
})();
If I call foo.obj.init(), the button should be hidden, and this does not occur.
I saw different questions here about the assignment of an element to a variable, but I think that the problem is with the object. Can't I access a private variable from a public method?
From my comment:
Do it as part of init... you can just declare the var in order to isolate the scope, and then modify it to actually set the button as part of init
Example:
var foo = (function() {
var obj = (function() {
var $button; //$('#myButton');
var init = function() {
if (typeof $button === 'undefined') {
// i would probably make the selector an argument to `init`
// if i were you.
$button = $('#myButton');
}
$button.hide();
};
return {
init: init
};
})();
return { obj: obj };
})();
I have the following JavaScript code:
var objSample = {
variable: 10,
func1 : function(){
someJQueryPlugin(1, function(){
this.variable; // this doesn't work, don't have access
});
}
}
I have two questions:
1) How can I create an instance of the variable so I can have two stand alone objects, each one with its own unique variable values?
Example:
var obj1 = new objSample();
obj1.variable = 1;
var obj2 = new objSample();
obj2.variable = 2;
2) How can I have access to the variable inside an anonymous function from a jQuery plugin inside a function in the object. passing this didn't help.
var objSample = function(){
this.variable = 10
this.func1 = function(){
someJQueryPlugin(1, function(){
this.variable; <-- this doesn't work, don't have access
});
}
}
also you can extend constructor with params
var objSample = function(options){
this.variable = options.val
this.func1 = function(){
someJQueryPlugin(1, function(){
this.variable; <-- this doesn't work, don't have access
});
}
}
var obj1 = new objSample ({val:1})
var obj2 = new objSample ({val:2})
and to access this from callbacks in different context, enclose this to some variable.
So final code looks like:
var objSample = function(options){
var self = this;
self.variable = options.val
self.func1 = function(){
someJQueryPlugin(1, function(){
self.variable;
});
}
}
You need to change the code from an object literal to a constructor function and ensure that you reference the right this in the func1 function.
function ObjSample() {
this.variable = 10;
this.func1 = function () {
var _this = this;
someJQueryPlugin(1, function () {
_this.variable;
});
}
}
DEMO
I have this:
function test1()
{
this.count = 0;
this.active = 0;
this.enable = function () {this.active = 1;}
this.disable = function () {this.active = 0;}
this.dodo = function ()
{
$("html").mousemove(function(event) {
// I want to get here the "active" param value;
});
}
this.enable();
this.dodo();
}
instance = new test1();
instance.disable();
Let's say I want to check the active param of the test1 class in the commented place. How can I get it there ?
Thanks!
If you want access to all the member variables of the higher scope, you just need to save the this pointer from that scope into a local variable so you can use it inside the other scope:
function test1() {
this.count = 0;
this.active = 0;
this.enable = function () {this.active = 1;}
this.disable = function () {this.active = 0;}
var self = this;
this.dodo = function () {
$("html").mousemove(function(event) {
// I want to get here the "active" param value;
alert(self.active);
});
}
this.enable();
this.dodo();
}
instance = new test1();
instance.disable();
this.dodo = function ()
{
var active = this.active;
$("html").mousemove(function(event) {
alert(active);
});
}
When you call a function 'this' refers to the object the function was invoked from, or the newly created object when you use it together with the keyword new. For example:
var myObject = {};
myObject.Name = "Luis";
myObject.SayMyName = function() {
alert(this.Name);
};
myObject.SayMyName();
Note in JavaScript there are multiple ways to declare, define, and assign fields and methods to objects, below is the same code written more similarly to what you wrote:
function MyObject() {
this.Name = "Luis";
this.SayMyName = function() {
alert(this.Name);
};
}
var myObject = new MyObject();
myObject.SayMyName();
And yet another way to write the same thing:
var myObject = {
Name: "Luis",
SayMyName: function() {
alert(this.Name);
},
};
myObject.SayMyName();
There are also several different ways to invoke a function.
Im guessing there is no way to get the function caller name in an anonymous function, is there ?
(function()
{
var cls = function()
{
this.foo = function()
{
console.log(arguments.callee.caller); // null
foo1();
}
var foo1 = function()
{
console.log(arguments.callee.caller); // foo
foo2();
}
var foo2 = function()
{
console.log(arguments.callee.caller); // foo1
cls.foo(); // local
}
var cls =
{
foo : function()
{
console.log(arguments.callee.caller); // cls.foo2
}
}
}
return (window.cls = cls);
})();
var c1 = new cls();
c1.foo();
Correct - they're anonymous. If you need to know their names by callee, you'll need to give them a name. Will something like this.foo = function foo() rather than this.foo = function() work for you?
It is possible in recent versions of Chrome and Firefox as follows. I only recommend this for debugging purposes (e.g. javascript tracing in non-production)
var mystery = function() {
var myNameInChrome = /.*Object\.(.*)\s\(/.exec(new Error().stack)[1];
var myNameInFF = new Error().stack.split("#")[0];
}