why this resolves to undefined not window/global-env in react component method if it not bind this in constructor - reactjs

Just described as title.
I got answers that how to fix the situation - this refers to undefined.
But no answer tells me why this refers to undefined.
In my opinion, this will refer to window/global-env if it isn't specified implicitly or explicitly.
Of course ,this will be undefined when in strict-mode.

This is determined at runtime and depending on the code, it can be something different.
this is
determined at runtime, when a function is envoked
determined by how a function is invoked, not where the function is
defined
a reference to an object.
will always be an object
global (this) not available in strict mode
Example 1: this = window
var name = 'Global';
var callName1 = function() {
var name = 'Peter';
console.log('--- From callName1 ----');
console.log(this.name);
//console.log(this);
callName2();
}
var callName2 = function() {
var name = 'Jane';
console.log('--- From callName2 ----');
console.log(this.name);
//console.log(this);
}
callName1();
var execute = function(fn) {
var name = 'Mary';
console.log('--- From execute ----');
console.log(this.name);
//console.log(this);
}
execute(callName2);
Example 2: not available in strict mode
'use strict';
var name = 'Global';
var callName1 = function() {
var name = 'Peter';
console.log('--- From callName1 ----');
console.log(this.name);
console.log(this);
}
callName1();
Example 3: examining this with method invocation
var name = 'global';
var obj = {
name: 'James Obj1',
func: function() {
console.log('--- From func ----');
console.log(this.name);
console.log(this); // this reference obj1
}
}
obj.func()
var obj2 = {
name: 'Jame Obj2',
func: obj.func // this reference obj2, but the function is defined in obj1
}
obj2.func()
var obj3 = {
name: 'Kane Obj3',
obj4: {
name: 'Mary Obj4',
func: function () {
console.log('--- From obj4 ----');
console.log(this.name);
console.log(this); // this reference obj4
}
}
}
obj3.obj4.func()
With () => {} function this - is lexically bound. It means that it uses this from the code that contains the arrow function.

use code below to verify method is in strict mode or not.
Include code below in method.
Got a type error, that means , method is in strict mode, which resolve the confusion.
var obj2 = { get x() { return 17; } };
obj2.x = 5;

Related

JavaScript array default assignment syntax confusion

If I create a function constructor in JavaScript like so:
function Emitter() {
this.events={}
}
and then I add a method to its prototype like so:
Emitter.prototype.on = function(type,listener) {
this.event[type] = this.events[type] | [];
this.event[type].push(listener);
}
When I call the method 'on' an instance of Emitter twice, why does it not just overwrite the original property called greet and assign it the second function? I guess I do not understand the stricture of what is happening in:
this.event[type] = this.events[type] | [];
var emtr = new Emitter();
emtr.on('greet',function(){
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
You should be using || for OR instead of |, this is invalid:
this.event[type] = this.events[type] | [];
Also, you are calling event instead of events. You should have:
this.events[type] = this.events[type] || [];
This way if this.events[type] is not undefined it will stay as is. However if it is undefined is will be assigned as an empty array: []
The code below will successfully add the two functions to emtr.events['greet'] (an array of functions):
function Emitter() {
this.events = {}
}
Emitter.prototype.on = function(type, listener) {
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
var emtr = new Emitter();
emtr.on('greet', function() {
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
console.log(emtr.events['greet'])
So you can call them like so:
emtr.events['greet'][0]();
And
emtr.events['greet'][1]()
If instead, you would like to replace the listener then you shouldn't be using an array. Pointing to a function instead of an array of functions will suffice:
function Emitter() {
this.events = {}
}
Emitter.prototype.on = function(type, listener) {
this.events[type] = listener;
}
var emtr = new Emitter();
emtr.on('greet', function() {
console.log('Hello once');
});
emtr.on('greet', function(){
console.log('Hello twice');
});
emtr.events['greet'](); // notice how the first listener was replaced by the new one
This way you can call your listener with emtr.events['greet']().
While #Ivan is completely correct, he missed your main question of “why?”
The answer is that the logical OR operator in JS also functions as a null coalescing operator when used as part of an assignment statement.
Essentially, in the case
let x = y || “default”;
The right hand side will evaluate to the first “truthy” value and return that. In JS most things evaluate true in a logical operation except values like false, 0, null, and a few others not relevant to this question.
So in your syntax (as corrected by Ivan), you’re telling JS to assign to the events[type] property the first thing that’s true. Either itself (which will evaluate true if it’s not null, unassigned, 0, etc) or else an empty array.
The first time you add an event, it’ll be unassigned and therefore get an empty array added. Subsequently, arrays eval as true, so you’ll just keep re-assigning the property to itself which has no effect.
Make sense?
function Emitter() {
this.events = []
}
Emitter.prototype.on = function (type, listener) {
this.events[type] = this.events[type]||[]
this.events[type].push(listener);
}
Emitter.prototype.emit = function(type){
var listener = this.events[type].pop()
if(this.events[type].length>=0)
listener()
else
console.log('Nothing to emit')
}
var emtr = new Emitter();
emtr.on('greet',function(){
console.log('Hello once');
});
emtr.emit('greet')

ng-if only works when referencing var direct from service, instead of var in controller scope

I am trying to understand why my ng-if statement doesn't work when I reference a local variable in my controller that is assigned to a value from a service, but it works properly if assigned directly to the value from that service.
For example, this works:
<div class="map" ng-if="interactiveMap.mapService.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
with the following controller:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.mapService = map;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
But if I were to assign the "esriLoaded" var to a local var in the scope, like this:
<div class="map" ng-if="interactiveMap.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
Controller here:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.esriLoaded = map.esriLoaded;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
Then it doesn't work. The value for "esriLoaded" is always false (which is the default value for esriLoaded). It's like it isn't updating the value of self.ersiLoaded when the value gets updated in the "map" service. Here is the code for the "map" service, just in case folks need it to answer this question.
angular.module('tamcApp')
.service('map', function (config, esriLoader, esriRegistry, esriMapUtils) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.esriLoaded = false;
self.lazyload = function() {
// Make a call to load Esri JSAPI resources.
// A promise is provided for when the resources have finished loading.
esriLoader.bootstrap({
url: config.globals.esriJS
}).then(function() {
// Set Loaded to be true
self.esriLoaded = true;
// DEFINE CUSTOM BASEMAP USED BY ALL MAPS
esriMapUtils.addCustomBasemap('mcgiStreet', {
urls: ['http://myhost.com/arcgis/rest/services/BaseMap/StreetMap/MapServer'],
title: 'MCGI Street Map',
thumbnailurl: ''
});
});
};
if (!self.esriLoaded) {
self.lazyload();
}
self.getMap = function(id){
return esriRegistry.get(id);
};
});
That is actually not because of angular, but because of JavaScript. map.esriLoaded is a boolean value, a primitive and thus not an object, which leads to your local self.esriLoaded not becoming a reference (as only objects can be referenced), but just a plain copy of the boolean value contained in map.esriLoaded.
A short example to make it more clear:
//Primitive
var a = 5; //primitive
var b = a; //b just copies the value of a
a = 6; //This will change a, but not b
conosle.log(b); //will print 5
//Object
var a = { someValue: 5 }; //a is now a reference to that object
var b = a; //b also becomes a reference to the object above
a.someValue = 1337; //will change the object a is referencing, thus also
//changing the object b is referencing, as its the same object
console.log(b.someValue); //will print 1337

How to extend returned objects in the list returned by $asArray?

I'm having trouble decorate the objects in my list returned by $asArray in angularfire with a new method (not decorating the array itself).
The angularfire documentation seems to suggest that the right way to do this is to override the $$added method in the factory for $FirebaseArray, returning a new object that either encapsulates or extends the snapshot that gets passed in to that method. From the documentation:
// an object to return in our JokeFactory
app.factory("Joke", function($firebaseUtils) {
function Joke(snapshot) {
this.$id = snapshot.name();
this.update(snapshot);
}
Joke.prototype = {
update: function(snapshot) {
// apply changes to this.data instead of directly on `this`
this.data = snapshot.val();
},
makeJoke: function() {
alert("Why did the " + this.animal + " cross the " + this.obstacle + "?");
},
toJSON: function() {
// since we didn't store our data directly on `this`, we need to return
// it in parsed format. We can use the util function to remove $ variables
// and get it ready to ship
return $firebaseUtils.toJSON(this.data);
}
};
return Joke;
});
app.factory("JokeFactory", function($FirebaseArray, Joke) {
return $FirebaseArray.$extendFactory({
// change the added behavior to return Joke objects
$$added: function(snap) {
return new Joke(snap);
},
// override the update behavior to call Joke.update()
$$updated: function(snap) {
this.$getRecord(snap.name()).update(snap);
}
});
});
However, when I do this in my code, nothing ever gets added to the array, although I can see from outputting to the console that it is getting called.
var printMessageObjConstructor = function(snap) {
this.$id = snap.name();
this.snapshot = snap;
this.$update = function(snap) {
this.snapshot = snap;
};
this.printMessage = function() {
return this.author + "'s question is: " + this.body;
};
};
var ref = new Firebase("https://danculley-test.firebaseio.com/questions");
//What Am I Doing Wrong Here?
var arrayFactory = $FirebaseArray.$extendFactory({
$$added: function(snap, prevChild) {
var x = new printMessageObjConstructor(snap);
console.log("I am being called from FirebaseDecoratedCtlOverloadAddedinNewObj.");
return x;
},
$createObject: function(snap) {
return new printMessageObjConstructor(snap);
},
$$updated: function(snap) {
var i = this.$indexFor(snap.name());
var q = this.$list[i];
q.$update(snap);
}
});
var sync = $firebase(ref, {arrayFactory:arrayFactory});
var list = sync.$asArray();
list.$loaded(function(list) {
$scope.questions = list;
});
I've set up a new plunk stripped down to show the issue with a couple other use cases that I've tried. (The actual method I'm adding is more complex and isn't related to the view, but I wanted to do something simple to reproduce the issue.)
I think the issue is that I don't quite understand what exactly $$added is supposed to return, or what additional behavior beside returning the value to be stored $$added is supposed to have. There also doesn't really seem to be an $$added on the prototype or on $FirebaseArray to call as a super to get the default behavior. Can someone point me in the right direction?
UPDATE
For the benefit of others, after reviewing the like that Kato posted, I was able to solve the issue by adding the following, almost all copied directly from the source except for the commented line below.
$$added: function(snap, prevChild) {
var i = this.$indexFor(snap.name());
if( i === -1 ) {
var rec = snap.val();
if( !angular.isObject(rec) ) {
rec = { $value: rec };
}
rec.$id = snap.name();
rec.$priority = snap.getPriority();
$firebaseUtils.applyDefaults(rec, this.$$defaults);
//This is the line that I added to what I copied from the source
angular.extend(rec, printMessageObj);
this._process('child_added', rec, prevChild);
}
}
For the benefit of others, after reviewing the link that Kato posted, I was able to solve the issue by adding the following, almost all copied directly from the source except for the commented line below.
$$added: function(snap, prevChild) {
var i = this.$indexFor(snap.name());
if( i === -1 ) {
var rec = snap.val();
if( !angular.isObject(rec) ) {
rec = { $value: rec };
}
rec.$id = snap.name();
rec.$priority = snap.getPriority();
$firebaseUtils.applyDefaults(rec, this.$$defaults);
//This is the line that I added to what I copied from the source
angular.extend(rec, printMessageObj);
this._process('child_added', rec, prevChild);
}
}

Backbone Model change data in one instance affects another

I just had a weird bug when using Backbone.Model
so I have the model declaration something like:
var MyMode = Backbone.Model.extend({
defaults:{
'someList': []
},
initialize: function(){
_.bindAll(this, 'toString', 'castFromString');
},
toString: function(){
return this.get('hierarchyNameList').join('+');
},
castFromString: function(str){
var strArray = [], index = 0;
if (str) {
strArray = str.split('+');
}
for (index = 0; index < strArray.length; index++){
this.get('hierarchyNameList').push(strArray[index]);
}
}
});
then I tried to test it
(function () {
'use strict';
var assert = function(condition, message) {
if (condition !== true) {
throw message || "Assertion failed";
}
};
var mA = new MyModel({'someList': ['a','b','c']});
var mB = new MyModel();
mB.castFromString('a+b+c');
//I have a added a equals function to array prototype and tested it
assert(mA.get('someList').equals(mB.get('someList'))); //true
var mC = new MyModel(mA.toJSON()); //behaves as expected
var mD = new MyModel(); //for some reason its list already contains the list from B
mD.castFromString(mB.toString()); //since castFromString used push, now B and D both has array of length 6
assert(mC.equals(mA)); //success
assert(mC.equals(mD)); //fail, mc has arrayLength 3, mD has 6
}).call(this);
The actual code is more complicated than this, but I think this is where I am probably doing something wrong, any suggestion on why this would happen? Thanks in advance!
The problem is with your defaults
defaults:{
'someList': []
},
objects in JavaScript are passed by reference not by value. It means that all instances, for which you didn't explicitly specified someList value will share array created in defaults definition above. To avoid it you can define your defaults as a function:
defaults: function () {
return { 'someList': [] };
},
This will create new array for every instance of MyModel, so they won't share the same array.

How to convert object literal to function

Is there any dynamic way to convert/clone this object:
var object = {
a: 2,
b: function(){
return this.a;
}
}
Into this kind of function object:
function object(){};
object.a = 2;
object.b = function(){
return this.a;
};
Is this possible? how can I do so dynamically?
You can just copy everything, though I would use the prototype:
function toClass(obj) {
var func = function () {};
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
func.prototype[i] = obj[i];
}
}
return func;
}
A whole other question is how useful this actually is and whether there is a better solution to the underlying problem.
Fiddle: http://jsfiddle.net/pb8mv/
It is a little bit strange that you need such a thing. If I have to guess, I think that you have an object and you want to extend it. I.e. you want to create function based on that object, so you later create multiple instances of it. That's possible and here it is a little snippet showing how:
var object = {
a: 2,
b: function(){
return this.a;
}
}
var extend = function(obj) {
return function() {
return Object.create(obj);
}
};
var Class1 = extend(object);
var ob1 = Class1();
ob1.a = 10;
var Class2 = extend(object);
var ob2 = Class2();
ob2.a = 23;
console.log(ob1.b(), ob2.b());
The result of the script is
10 23

Resources