By Philippe Poulard
Not a complete course on Javascript
Just a focus on some of the counterintuitive parts of the language
this
The value of this
depends on how a function is invoked.
function foo() {};
foo(); //=> this === window
var bar = function() {};
bar(); //=> this === window
function foo() {
return this;
}
// invoke as a function
foo(); //=> this === window
var bar = {
'foo': foo
};
// invoke as a method of 'bar'
bar.foo(); //=> this === bar
function Foo() {
this.bar = function() {
return this;
}
}
// exemple 1
var foo = new Foo();
console.log(foo); //=> Foo
console.log(foo.bar() instanceof Foo); //=> true
// exemple 2
var foo1 = Foo();
console.log(typeof foo1); //=> undefined
console.log(typeof window.bar); //=> function
this
function foo() {
console.log(this); //=> this === element
console.log(arguments); //=> 'a', 'b', 'c'
}
var element = document.querySelector('#foo');
element.addEventListener('click', function(e){
console.log(this); //=> element
console.log(arguments); //=> e === Event
foo.call(element, 'a', 'b', 'c');
});
or
foo.apply(element, ['a', 'b', 'c']);
var Foo = function() {
this.counter = 0;
this.inc = function() {
this.counter += 1;
console.log(this);
};
};
var foo = new Foo();
var element = document.querySelector('#foo');
// #1
element.addEventListener('click', foo.inc); //=> this === element
// #2
element.addEventListener('click', function(){ foo.inc(); }); //=> this === foot
// #3
element.addEventListener('click', foo.inc.bind(foo)); //=> this === foo
var obj = {
doIt: function() {},
handle: function() {
var that = this;
document.addEventListener('click', function(e) {
that.doIt();
});
}
}
var obj = {
doIt: function() {},
handle: function() {
document.addEventListener('click', function(e) {
this.doIt();
}.bind(this)
);
}
}
var obj = {
doIt: function() {},
handle: function() {
document.addEventListener('click', (e) => this.doIt());
}
}
What will be print ?
What will be print ?
What will be print ?
What will be print ?
You expect in other language 121
C-family languages have block-level scope.
What will be print ?
Javascript has function-level scope.
Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter.
In JavaScript, a name enters a scope in one of four basic ways:
this
and arguments
.function foo() {}
.var foo;
This code :
function foo() {
bar();
var x = 1;
}
...is interpreted like this :
function foo() {
var x;
bar();
x = 1;
}
The 2 following functions :
function foo() {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function foo() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
...are equivalent
Every function, when invoked, creates a new execution context.
⇒ Allow to create privacy.
var foo = function(){ /* code */ }
foo();
can we define a shorter way ?
function(){/* code */}(); // SyntaxError: Unexpected token (
function foo(){/* code */}(); // SyntaxError …
// no, because it is still a declaration
Curiously, this one works :
function foo(){ /* code */ }( 1 );
because it is really just equivalent to this:
// a function declaration …
function foo(){ /* code */ }
// … followed by a completely unrelated expression:
( 1 );
Crockford recommends this one :
(function(){ /* code */ }());
But this one works just as well :
(function(){ /* code */ })();
When the parser already expect an expression, parens can be omitted
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
This doesn't work like you might think :
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + i );
}, false );
}
Saving state with closure
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
(function( lockedInIndex ){
elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + lockedInIndex + "/" + i );
}, false );
})( i );
}
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
elems[ i ].addEventListener( 'click',
(function( lockedInIndex ){
return function(e){
e.preventDefault();
alert( 'I am link #' + lockedInIndex );
};
})( i ), false );
}
var counter = (function(){
var i = 0;
return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
}());
// `counter` is an object with properties,
// which in this case happen to be methods.
counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.i; // undefined
An easy way to determine whether something is "pass by reference" is whether you can write a "swap" function.
For example, in C, you can do :
void swap(int *i, int *j)
{
int t;
t = *i;
*i = *j;
*j = t;
}
If you can't do the equivalent of that in Javascript, it is not "pass by reference".
There is no "pass by reference" available in JavaScript.
References are passed by value.