Scope of "this" in inner function (CraftyJS scene) - javascript

I'm writing up a small game using CraftyJS. Here's what I want to write:
Crafty.scene('MainMap', function() {
this.player = Crafty.e('Player');
this.player.move(5, 5);
this.game_objects = [this.player];
isOccupied: function(x, y) {
for (var i = 0; i < this.game_objects.length; i++) {
// ...
}
}
if (!this.isOccupied(5, 5)) { ... }
// ...
}
Unfortunately, this doesn't work as expected; it's not an anonymous object, but it's a function. I have to use a different syntax, and pass in my objects, like so:
function isOccupied(x, y, game_objects) { ... }
// Same place as previous call to isOccupied
if (!isOccupied(x, y, this.gameObjects) { ... }
It's clear to me why I have to declare it as function isOccupied and not isOccupied: function (because it's inside a function, not an object) but it's not clear to me what the scope of this is. It doesn't get passed into the function.
Is it possible to somehow keep the objects within some non-global scope, and not need to pass them into isOccupied?

You could assign the parent scope to another variable, so it will be available in your closure. Like so...
Crafty.scene('MainMap', function() {
var self = this;
this.player = Crafty.e('Player');
this.player.move(5, 5);
this.game_objects = [this.player];
function isOccupied (x, y) {
for (var i = 0; i < self.game_objects.length; i++) {
// ...
}
}
}

You have a syntax error in your Crafty scene.
The colon in this part should not be there. In JavaScript, colons are only used in objects.
// Wrong
isOccupied: function(x, y) {
for (var i = 0; i < this.game_objects.length; i++) {
// ...
}
}
// Right
function isOccupied(x, y) {
// ...
}
In your function, this refers to the global object (window).
EDIT:
To fix this, use Function.prototype.bind, like so:
function isOccupied(x, y) {
// ...
}.bind(this);

After doing more research, it appears that this is a well-known issue with the this keyword. It will be fixed in ECMAscript 5.
To summarize: the this keyword gets confused when you have more than one level of nesting:
obj = {
speak: function() {
alert(this); // obj
inner = function() {
alert("inner: " + this); // window
};
}
};
The work-around is to use a scope chain, by assigning a variable to this:
obj = {
speak: function() {
alert(this); // obj
var that = this;
inner = function() {
alert("inner: " + that); // obj instead of window
};
}
};
Unfortunately for me, CraftJS has me inside a function instead of an object. To declare a sub-function, I still must specify it as function isOccupied:
Crafty.scene('MainMap', function() {
self = this; // new
this.player = Crafty.e('Player');
this.player.move(5, 5);
this.game_objects = [this.player];
function isOccupied(x, y) { // game_objects no longer passed in
for (var i = 0; i < self.game_objects.length; i++) { // uses self
// ...
}
}
if (!this.isOccupied(5, 5)) { ... }
// ...
}

Related

Javascript accessing scope by using bind

why this logs 1 and not 2 ? i am trying to pass this scope to initData function but no success. after running init i should trigger iniData but this should refer to the object itself not to a new scope
let startFunction = function (){
let initData = () => {
this.x = 2
}
return {
x:1,
init:initData.bind(this)
};
};
obj=startFunction()
obj.init();
console.log(obj.x); // should be 2
update:
let startFunction = function (){
let initData = function(){
this.x = 2
}
return {
x:1,
init:initData
};
};
obj=startFunction()
obj.init();
console.log(obj.x); // should be 2
and why this works ? shouldn't the function create a new scope ?
There are two issues here. The first issue is that arrow functions are immutably bound to their execution context. Function.prototype.bind has no effect on an arrow function. The second issue here is what this refers to when you call startFunction. When you invoke a function freely, without a binding to a particular object, this refers to the object in which the function is invoked. So this refers to the global object in initData permanently.
So let's think about the value of obj and the global object's x value after each line.
After obj=startFunction:
// global x is undefined
// obj
{
x: 1,
init: initData // with the same binding to the global object
}
After obj.init(), initData sets the global object's x value to 2:
// global x is 2
// obj
{
x: 1,
init: initData // with the same binding to the global object
}
Problem you are facing is the x in the object has nothing to do with the this.x that is being assigned in the init method. You need to set an object and in the init code adjust that object.
let startFunction = function() {
const initData = () => {
data.x = 2
};
var data = {
x: 1,
init: initData,
};
return data;
};
const obj = startFunction()
obj.init();
console.log(obj.x);
Better solution, use a proper class.
class MyClass {
x = 1;
init() {
this.x = 2;
}
}
const obj = new MyClass();
obj.init();
console.log(obj.x);
class MyClass2 {
x = 1;
constructor() {
this.x = 2;
}
}
const obj2 = new MyClass2();
console.log(obj2.x);

Change variable with string instead of just accessing it

I can access a variable easily:
function accessVariable(accessFunction, variable) {
var namespaces = accessFunction.split(".");
for (var b = 0; b < namespaces.length; b++) {
if (namespaces[b] != "") {
try {
variable = variable[namespaces[b]];
} catch {
variable = undefined;
};
};
return variable;
};
But updating this gotten variable is something that I don't know how to do.
It's easiest if you use a separate function to update. This can take the new value as another argument.
Then you stop the iteration before the last item in namespace, and use that as the index to assign to.
function updateVariable(accessFunction, variable, value) {
var namespaces = accessFunction.split(".");
for (var b = 0; b < namespaces.length - 1; b++) {
if (namespaces[b] != "") {
try {
variable = variable[namespaces[b]];
} catch {
variable = undefined;
}
}
}
if (variable) {
variable[namespaces.pop()] = value;
}
}
let obj = {a: {b:[{c: 10}]}};
updateVariable('a.b.0.c', obj, 20);
console.log(obj);
Define a single global-scoped variable, and put your variables there.
var Glob = {}; // globally scoped object
function changeVar(){
Glob.variable1 = 'value1';
}
function SeeVar(){
alert(Glob.variable1); // shows 'value1'
}

functions inside array to ignore the array's scope

I have a constructor and am trying to have an array of functions inside its prototype, but i need the functions to have the scope of the object created by the constructor, and not the array's scope.
I tried using .bind(this) or .bind(_p) but "this" is the node server's scope and _p is just the prototype without the variables.
function BoardModel() {
this.x = 3
this.y = 2
}
_p = BoardModel.prototype;
_p.skillFunctions = [
function(){
console.log(this.x); //undefined
},
function(){
console.log(this.y); //undefined
},
];
Why not use an own method for each property, instead of an array?
function BoardModel() {
this.x = 3
this.y = 2
}
_p = BoardModel.prototype;
_p.skillFunctionsX = function (){
console.log(this.x);
};
_p.skillFunctionsY = function (){
console.log(this.y);
};
var item = new BoardModel;
item.skillFunctionsX();
item.skillFunctionsY();
Arrow functions use this from the enclosing context so how about this (pun intended)?
function BoardModel() {
this.x = 3
this.y = 2
this.skillFunctions = [
() => { console.log(this.x) },
() => { console.log(this.y) },
];
}
let board = new BoardModel()
board.skillFunctions.forEach((skillFunction) => { skillFunction() })

difference between these two ways of creating a 'private' property in Javascript

I am trying to simulate a simple Holder "class" in JavaScript with a "private" property that holds something and "public" getter and setter "methods" to access the value.
The approach exhibited by HolderA below is mentioned e.g. here. The other approach I more or less arrived at by mutation but I guess it must be recognizable as an idiom as well. I like it because it contains no this or prototype stuff and seems very elementary and functional. Is there a difference between the two?
The test code (I run it under nodejs) seems to suggest that the two approaches are identical except that in the first case the objects I get have typeof object whereas in the second function.
var test = function(o) {
var initialValueCorrect = (!(typeof o.getX()==='undefined'))&&(o.getX()===0);
var VALUE_TO_SET = 10;
o.setX(VALUE_TO_SET);
var getSetWorks = o.getX()===VALUE_TO_SET;
var xIsPrivate = (typeof o.x === 'undefined');
var xHasCorrectValue;
if (!xIsPrivate)
xHasCorrectValue = o.x === VALUE_TO_SET;
return {initialValueCorrect: initialValueCorrect,
getSetWorks : getSetWorks,
xIsPrivate: xIsPrivate,
xHasCorrectValue: xHasCorrectValue};
};
var HolderA = (function() {
function foo(x) {
this.getX = function() {
return x;
};
this.setX = function(_x) {
x = _x;
};
};
return foo;
})();
var createHolderB = (function() {
var x;
function foo(_x) {
x = _x;
return foo;
}
foo.getX = function() {
return x;
};
foo.setX = function(_x) {
x = _x;
};
return foo;
})();
var objects = [{object: new HolderA(0), name: "approach with constructor-invocation and 'this'"},
{object: createHolderB(0), name: "approach with normal function invocation and closed variable"}];
for (var i = 0; i<objects.length ; i++) {
var testResult = test(objects[i].object);
console.log('['+objects[i].name+']: the object is a: '+(typeof objects[i].object)
+'\n\n\t\t\t'+JSON.stringify(testResult)+'\n\n\n\n\n');
}
update
As Bergi has pointed out function createHolderB in my code above is plain wrong and only creates a singleton object. So, is not really a "constructor" function. To that end I've now created createHolderC which can be used to really create multiple objects with a hidden private property like this:
var objectC1 = createHolderC()(0);
Now, is there any material difference between HolderA and the createHolderC function or is the difference purely stylistic?
var createHolderC = function () {
return (function() {
var x;
function foo(_x) {
x = _x;
return foo;
};
foo.getX = function() {
return x;
};
foo.setX = function(_x) {
x = _x;
};
return foo;
})();
};
createHolderB does not create new holders like HolderA does. It's essentially a singleton pattern. You might also want to call it a module. Notice that createHolderB() === createHolderB.
createHolderC is still different from HolderA in that it returns function objects, not instances. You may see the differences better when you strip out the unnecessary IEFEs:
function HolderA(x) {
this.getX = function() {
return x;
};
this.setX = function(_x) {
x = _x;
};
// implicit `return this;` when called via `new`
}
function createHolderC() {
var x;
function foo(_x) {
x = _x;
return foo;
};
foo.getX = function() {
return x;
};
foo.setX = function(_x) {
x = _x;
};
return foo;
}
A typical factory would rather look like this:
function createHolderD(x) {
var foo = {};
foo.getX = function() {
return x;
};
foo.setX = function(_x) {
x = _x;
};
return foo;
}
(or even with return {getX: …, setX: …};), the only difference to HolderA is the prototypical inheritance then.
Basically both exhibits the private access behavior to the x variable. But the difference between those two are
Constructor function
var HolderA = (function() {
function foo(x) {
this.getX = function() {
return x;
};
this.setX = function(_x) {
x = _x;
};
};
return foo;
})();
This is a self executing function, where it returns Constructor foo.
That is the reason you are using new while creating this type of holder {object: new HolderA(0)
A function
var createHolderB = (function() {
var x;
function foo(_x) {
x = _x;
return foo;
}
foo.getX = function() {
return x;
};
foo.setX = function(_x) {
x = _x;
};
return foo;
})();
Even this is also a self executing function, but while execution it creates x variable and returns function foo, and foo access x by closure.
You are creating this type of holder just by normal function call object: createHolderB(0).
Both cases are really bad in practice.
If you're making closures (anonymous functions) that hold your "private variable" scope, it means you're creating those functions for every instance of the class. That costs memory and performance. I used to create classes like this, but then once I was making large number of instances and found out that it hits performance hard.
Just as in Python, make private variables private by some convention, like underscore in _name. Then you can break it for debug reasons (because variables in closure scope are not accessible).
This is how I would do it:
function MyClass(arg) {
this._privateVar = arg;
}
MyClass.prototype.getPrivateVar = function() {
return this._privateVar;
}
You can also make getters and setters:
Object.defineProperty(MyClass.prototype, "privateVar", {
get: function() {
return this._privateVar;
}
}
But don't try to push javascript into things it's not designed to, or you'll pay with performance, code readability and debugging complexity. Applies to other languages too.

How can you programmatically add a function to a nested namespace?

Say i have this function that dynamically creates my namespace for me when I just pass it a string, (I'm pretty sure basically what YUI JS library does):
MyObj.namespace('fn.method.name');
would result in
MyObj.fn.method.name = {}
being created - all three levels being equivalent to an empty object.
Now, what I want to do, though, is make the last level, in this case name, set to a function, but without having to redeclare the newly created object.
So instead of doing this:
function fnName() { /* some code here */ }
MyObj.namespace('fn.method.name');
MyObj.fn.method.name = new fnName();
i want to call something like:
MyObj.add('fn.method.name', fnName);
And internally, the add method would programmatically instantiate the passed in function:
MyObj.fn.method.name = new fnName()
In the way I have it implemented, I can create the namespace object and set it to an empty object, however, when I try to instantiate a passed in function and associate that namespace with the passed in function, it never gets added to the namespace. Instead, an empty object is always returned. Any ideas?
edit: Here is the namespace method. this is attached to the base object as a JSON object, so please ignore the formatting:
namespace: function (ns) {
var _ns = ns.split('.'),
i = 0, nsLen = _ns.length,
root = this;
if (_ns[0] === gNS) {
_ns.shift();
nsLen = _ns.length;
}
for (i = 0; i < nsLen; i++) {
// create a property if it doesn't exist
var newNs = _ns[i];
if (typeof root[newNs] === "undefined") {
root[newNs] = {};
}
root = root[newNs];
}
return root;
}
edit2 - removed the passed in fn argument
Were you looking for something like this:
var root = {};
function create(ns, fn) {
var nsArray = ns.split(/\./);
var currentNode = root;
while(nsArray.length > 1) {
var newNS = nsArray.shift();
if(typeof currentNode[newNS] === "undefined") {
currentNode[newNS] = {};
}
currentNode = currentNode[newNS];
}
if(fn) {
currentNode[nsArray.shift()] = fn;
}
else {
currentNode[nsArray.shift()] = {};
}
}
Then:
create("a.b.c");
console.log(root.a);
console.log(root.a.b);
console.log(root.a.b.c);
Gives:
Object { b={...}}
Object { c={...}}
Object {}
And:
create("d.e.f", function() { console.log("o hai"); });
console.log(root.d);
console.log(root.d.e);
console.log(root.d.e.f);
Gives:
Object { e={...}}
Object {}
function()
Calling the function you defined:
root.d.e.f();
Gives:
o hai
Well you haven't given the namespace function but your add function could look something like this:
MyObj.add = function (namespace, value) {
var names = namespace.split('.'), current = this, name;
while (names.length > 1) {
name = names.shift();
current[name] = {};
current = current[name];
}
current[names[0]] = value;
};
This code assigns the value given to the last part of the namespace. You could modify it to current[names[0] = new value(); if you want the object constructed by the passed in function (and you are assuming the constructor function takes no arguments).
function ns() {
var root = window;
for (var i = 0; i < arguments.length; i++) {
var arr = arguments[i].split(/\./);
for (var j = 0; j < arr.length; j++) {
var item = arr[j];
if (typeof item !== 'string') {
root = item;
}
else {
if (!root[item]) {
root[item] = {};
}
root = root[item];
}
}
root = window;
}
}
then you can create using
ns('fn.method.name');
or
ns('fn.method.name','fn.method.secondName');
and call using
fn.method.name
this function creates your namespace on 'window' so alternatively you can use
window.fn.method.name

Categories