Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save DamianMullins/a584d1efae61b8347aa424192fdabbe2 to your computer and use it in GitHub Desktop.
Save DamianMullins/a584d1efae61b8347aa424192fdabbe2 to your computer and use it in GitHub Desktop.
You Don't Know JS: this & Object Prototypes - Notes

Chapter 1 - this Or That?

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

Confusions

Itself

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

Its Scope

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 via this.bar(). It is almost certainly an accident that it works
  • Attempting to use this to create a bridge between the lexical scopes of foo() and bar(), so that bar() has access to the variable a in the inner scope of foo(). No such bridge is possible
  • You cannot use a this reference to look something up in a lexical scope. It is not possible

What's this?

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.

Review

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

Chapter 2 - this All Makes Sense Now!

this is a binding made for each function invocation, based entirely on its call-site (how the function is called)

Call-site

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

Nothing But Rules

Default Binding

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 the this is instead set to undefined
  • The global object is only eligible for the default binding if the contents of foo() are not running in strict mode; the strict mode state of the call-site of foo() is irrelevant.

Implicit Binding

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

Explicit Binding

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".

Hard Binding

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

API Call "Contexts"

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.

new Binding

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:

  1. a brand new object is created (aka, constructed) out of thin air
  2. the newly constructed object is [[Prototype]]-linked
  3. the newly constructed object is set as the this binding for that function call
  4. 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

Everything In Order

  • new binding
  • explicit binding
  • implicit binding
  • default binding

Determining this

  1. Is the function called with new (new binding)? If so, this is the newly constructed object.

    var bar = new foo()

  2. 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 )

  3. 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()

  4. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object.

    var bar = foo()

Binding Exceptions

Ignored this

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

Safer this

"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 { }

Lexical this

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

Chapter 3 - Objects

Syntax

Objects come in two forms: the declarative and the constructed form

They both result in exactly the same sort of object

Type

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

Built-in Objects

  • 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

Contents

The .a syntax is usually referred to as "property" access, whereas the ["a"] syntax is usually referred to as "key" access

Duplicating Objects

Can use Object.assign(..) in es6 to create a shallow clone

Property Descriptors

writable

Ability to change the value of a property

Configurable

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!

Enumerable

Controls if a property will show up in certain object-property enumerations, such as the for..in loop

Immutability

Object Constant

By combining writable:false and configurable:false, you can essentially create a constant

Prevent Extensions

Prevent an object from having new properties added to it

Object.preventExtensions(..)

Seal

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

Freeze

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

Existence

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

Enumeration

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

Iteration

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

Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment