Notes
-
-
Save DamianMullins/a584d1efae61b8347aa424192fdabbe2 to your computer and use it in GitHub Desktop.
this
is a special identifier keyword that's automatically defined in the scope of every function
Passing context around as an explicit parameter is often messier than passing around a this
context
function foo(num) {
console.log( "foo: " + num );
// keep track of how many times `foo` is called
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// how many times was `foo` called?
console.log( foo.count ); // 0 -- WTF?
- For the
this.count
reference inside of the function,this
is not in fact pointing at all to that function object - Accidentally created a global variable
count
which currently has the value NaN
function foo() {
foo.count = 4; // `foo` refers to itself
}
setTimeout( function(){
// anonymous function (no name), cannot
// refer to itself
}, 10 );
foo
is a reference that can be used to refer to the function from inside itself- The
arguments.callee
reference inside a function also points to the function object of the currently executing function arguments.callee
is deprecated and should not be used
By using the statement foo.call( foo, i );
inside the first function above, we ensure the this
points at the function object (foo
) itself
this
does not, in any way, refer to a function's lexical scope
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); //undefined
- An attempt is made to reference the
bar()
function viathis.bar()
. It is almost certainly an accident that it works - Attempting to use this to create a bridge between the lexical scopes of
foo()
andbar()
, so thatbar()
has access to the variable a in the inner scope offoo()
. No such bridge is possible - You cannot use a this reference to look something up in a lexical scope. It is not possible
this
is not an author-time binding but a runtime binding
this
is contextual based on the conditions of the function's invocation
this
binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called
When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function's execution.
this
is neither a reference to the function itself, nor is it a reference to the function's lexical scope
this
is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called
this
is a binding made for each function invocation, based entirely on its call-site (how the function is called)
The call-site we care about is in the invocation before the currently executing function
function baz() {
// call-stack is: `baz`
// so, our call-site is in the global scope
console.log( "baz" );
bar(); // <-- call-site for `bar`
}
function bar() {
// call-stack is: `baz` -> `bar`
// so, our call-site is in `baz`
console.log( "bar" );
foo(); // <-- call-site for `foo`
}
function foo() {
// call-stack is: `baz` -> `bar` -> `foo`
// so, our call-site is in `bar`
console.log( "foo" );
}
baz(); // <-- call-site for `baz`
- You can visualize a call-stack in your mind by looking at the chain of function calls in order
- If you're trying to diagnose this binding, use the developer tools to get the call-stack, then find the second item from the top, and that will show you the real call-site
The default catch-all rule when none of the other rules apply
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
- Variables declared in the global scope, as var a = 2 is, are synonymous with global-object properties of the same name. They're not copies of each other, they are each other
- When foo() is called, this.a resolves to our global variable
a
- If
strict mode
is in effect, the global object is not eligible for the default binding, so thethis
is instead set toundefined
- The global object is only eligible for the default binding if the contents of
foo()
are not running instrict mode
; thestrict mode
state of the call-site offoo()
is irrelevant.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
- Regardless of whether foo() is initially declared on obj, or is added as a reference later (as this snippet shows), in neither case is the function really "owned" or "contained" by the obj object.
- However, the call-site uses the obj context to reference the function, so you could say that the obj object "owns" or "contains" the function reference at the time the function is called.
- Because obj is the this for the foo() call, this.a is synonymous with obj.a.
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
- Invoking foo with explicit binding by foo.call(..) allows us to force its this to be obj.
- If you pass a simple primitive value (of type string, boolean, or number) as the this binding, the primitive value is wrapped in its object-form (new String(..), new Boolean(..), or new Number(..), respectively). This is often referred to as "boxing".
We create a function bar() which, internally, manually calls foo.call(obj), thereby forcibly invoking foo with obj binding for this. No matter how you later invoke the function bar, it will always manually invoke foo with obj. This binding is both explicit and strong, so we call it hard binding.
Since hard binding is such a common pattern, it's provided with a built-in utility as of ES5: Function.prototype.bind
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
Internally, these various functions almost certainly use explicit binding via call(..) or apply(..), saving you the trouble.
There really is no connection to class-oriented functionality implied by new
usage in JS
When a function is invoked with new in front of it, otherwise known as a constructor call, the following things are done automatically:
- a brand new object is created (aka, constructed) out of thin air
- the newly constructed object is [[Prototype]]-linked
- the newly constructed object is set as the this binding for that function call
- unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
By calling foo(..)
with new
in front of it, we've constructed a new object and set that new object as the this
for the call of foo(..)
. So new is the final way that a function call's this
can be bound. We'll call this new binding
- new binding
- explicit binding
- implicit binding
- default binding
-
Is the function called with new (new binding)? If so, this is the newly constructed object.
var bar = new foo()
-
Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.
var bar = foo.call( obj2 )
-
Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object.
var bar = obj1.foo()
-
Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object.
var bar = foo()
If you pass null or undefined as a this binding parameter to call, apply, or bind, those values are effectively ignored, and instead the default binding rule applies to the invocation
"DMZ" (de-militarized zone) object
var ø = Object.create( null );
Object.create(null)
is similar to { }
, but without the delegation to Object.prototype
, so it's "more empty" than just { }
Arrow-functions are signified not by the function keyword, but by the => so called "fat arrow" operator. Instead of using the four standard this rules, arrow-functions adopt the this binding from the enclosing (function or global) scope
Objects come in two forms: the declarative and the constructed form
They both result in exactly the same sort of object
6 Primary types (language types):
string
number
boolean
null
undefined
object
The first five are simple primitives, and are not objects
"Everything in JavaScript is an object" is a common misstatement
A few special object subtypes (refer to as complex primitives) such as function
and Array
exist
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
// inspect the object sub-type
Object.prototype.toString.call( strObject ); // [object String]
strPrimitive
is not an object, it's a primitive literal and immutable value. To perform operations on it, a String
object is required
The language automatically coerces a "string"
primitive to a String
object when necessary
null
and undefined
have no object wrapper form
Date
values can only be created with their constructed object form, as they have no literal form counter-part
The constructed form does offer, in some cases, more options in creation than the literal form counterpart
The .a
syntax is usually referred to as "property" access, whereas the ["a"]
syntax is usually referred to as "key" access
Can use Object.assign(..)
in es6 to create a shallow clone
Ability to change the value of a property
Modifiy descriptor definition (as long as this is set to true
)
Attempting to modifiy configurable when it is set to false
results in a TypeError
, regardless of strict mode
Changing configurable
to false
is a one way action!
Controls if a property will show up in certain object-property enumerations, such as the for..in
loop
By combining writable:false
and configurable:false
, you can essentially create a constant
Prevent an object from having new properties added to it
Object.preventExtensions(..)
Object.seal(..)
creates a "sealed" object, which means it takes an existing object and essentially calls Object.preventExtensions(..)
on it, but also marks all its existing properties as configurable:false
Object.freeze(..)
creates a frozen object, which means it takes an existing object and essentially calls Object.seal(..)
on it, but it also marks all "data accessor" properties as writable:false
, so that their values cannot be changed
var myObject = {
a: 2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
The in
operator will check to see if the property is in the object, or if it exists at any higher level of the [[Prototype]]
chain object traversal
hasOwnProperty(..)
checks to see if only myObject
has the property or not, and will not consult the [[Prototype]]
chain
propertyIsEnumerable(..)
tests whether the given property name exists directly on the object and is also enumerable:true
Object.keys(..)
returns an array of all enumerable properties, whereas Object.getOwnPropertyNames(..)
returns an array of all properties, enumerable or not
As contrasted with iterating over an array's indices in a numerically ordered way (for
loop or other iterators), the order of iteration over an object's properties is not guaranteed and may vary between different JS engines