This article is essentially a translated version of 《深入理解javascript原型和闭包》by 王福朋
1. Everything is Object
Except following value types, all other types in JS is object type.
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
e.x.
console.log(typeof function () {}); //function
console.log(typeof [1, 'a', true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
var fn = function () { };
console.log(fn instanceof Object); // true
Definition: Object is a collection of several fields(attributes).
let obj = {
a: 10,
b: function (x) {
console.log(x);
},
c: {
name: 'Joey',
age: 24
}
};
In the example above, a, b, c
are three fields. refer to a value type, a function(object), a object with two fields.
2. Relationship between function and object
Definition: function is a special type of object, and all objects are created by function.
Explicit:
function fn() {
this.name = 'Joey';
this.age = 24;
}
let obj1 = new fn();
Implicit:
let a = { name: 'Joey', age: 24};
let b = [5, 'x', true];
is basically:
let a = new Object();
obj.name = 'Joey';
obj.age = 24;
let b = new Array();
b[0] = 5;
b[1] = 'x';
b[2] = true;
However, the Object
and Array
in the above code are all functions.
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
3. Prototype
Definition: every function has a default field: prototype, prototype is a Object. with a field called constructor by default, pointing to the function itself.
The prototype in Object class has several other fields, like shown below (hasOwnProperty, isPrototypeOf…etc).
More fields can also be added into the protytype of a function.
function fn() {
fn.prototype.name = 'Joey';
fn.prototype.getAge = () => {
return 24;
}
}
Attributes in the prototype of a function can be referenced by Object created with this function.
function Fn() { }
Fn.prototype.name = 'Joey';
Fn.prototype.getYear = function () {
return 1988;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
Every object has a hiden field
__proto__
, which holds a reference to theprototype
field of the creation function of this object. This__proto__
is called a implicit prototype.
fn.__proto__ === Fn.prototype
4. Implicit prototype
let obj = {};
console.log(obj.__proto__);
The output of the above code is the same as Object.prototype
Every object has a
__proto__
field, which refer to the prototype of the creation function.
The prototype of self defined function is essentially an object, with
fn.prototype.__proto__
field refer toObject.prototype
.
Object.prototype
is a object itself, but a special case happens here:Object.prototype.__proto__
is null.
Two types of creating function:
function add(x, y) {
return x + y;
}
let add1 = new Function('x', 'y', 'return x + y;');
All functions are created by Function, which means
add.__proto__ === Function.prototype
5. instanceof: proto-chain
When applying
typeof
primitive to reference types, return values consist of object/function only. If want to know the exact type of an instance,instanceof
should be applied.
The
instanceof
operator tests to see if theprototype
property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value.
console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Function instanceof Function); // true
Object.__proto__ === Function.prototype
Function.__proto__.__proto__ === Object.prototype
Function.__proto__ === Function.prototype
6. inheritance
The inheritance in JS is represented by proto-chain.
function Foo() {}
let f1 = new Foo();
f1.a = 10; // basic field
Foo.prototype.a = 100; // proto-chain
Foo.prototype.b = 200; // proto-chain
console.log(f1.a); // 10
console.log(f1.b); // 200
Proto-chain: when accessing the field of an object, first retrieve in the basic fields in the object. If cannot find in basic fields, the field should be retrived alongside the proto-chain.
How to identify whether an field to be basic field or found in the proto-chain? We should use
hasOwnProperty
method.
let fn = function(){}
fn.prototype.a = 3;
let f1 = new fn();
console.log(f1.hasOwnProperty('a')); // false
f1.a = 10;
console.log(f1.hasOwnProperty('a')); // true
Code below shows how to find basic fields of an object.
for (item in f1) {
if (f1.hasOwnProperty(item)) {
console.log(item);
}
}
7. Flexibility of prototype
The prototype in JS is very flexible:
- The object fields can be added or modofied at any time.
In JQuery, the object holds barely anything while created, but added with code processing.
- The inherited items can be modified(override) if not applicable or inappropriate.
let obj = { a: 10, b: 20};
console.log(obj.toString()); // [object Object]
let arr = [1, 2, true];
console.log(arr.toString()); // 1, 2, true
function Foo() {}
let f1 = new Foo();
Foo.prototype.toString = function() {
return 'Joey';
};
console.log(f1.toString()); // Joey
Additional methods can be added, e.x.:
Judge the existence of method before adding prototype method to a internal function, and add if not exist.
8 & 9. Execution context
Execution context: Before execution of the code, prepare the variables to be used with undefined or assign with value.
- Preparation state with variable declaration (without value assignment)
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(a); // undefined
var a;
console.log(a); // undefined
var a = 10;
console.log(a); // Uncaught ReferenceError: a is not defined
let a = 10;
Tips to understand above behavior (variable hoisting):
All declarations (function, var, let, const and class) are hoisted in JavaScript, while the
var
declarations are initialized withundefined
, butlet
andconst
declarations remain uninitialized.Let declared variables will only get initialized when their lexical binding (assignment) is evaluated during runtime by the JavaScript engine. This means you can’t access the variable before the engine evaluates its value at the place it was declared in the source code. This is what we call “Temporal Dead Zone”, A time span between variable creation and its initialization where they can’t be accessed.
- Preparation state with variable declaration and value assignment
- Preparation state with variable declaration and value assignment
- function declaration
console.log(f1); // ƒ f1(){ return a;} function f1(){ return a;}
- funciton expression
console.log(f1); // undefined var f1 = function (){ return a;}
function declaration assigned the value in preparation state, however, function expression is the same as the var
declared variables.
In the preparation state, JS engine finished:
-
var
declared varibles and function expressions declaration, default value as undefined - value assignment to
this
- value assignment to
function declaration
Every time a function is invoked, a new execution context is created.
e.x. Code belows shows in this invocation, the
arguments
is assigned with[10]
, andx
is assigned with10
.
10. Execution context stack
When executing global code, it generates a execution context. Every time, an invocation to a function also creates a execution context, and when the function completes execution , this execution context and all data will be removed.The engine always have one and only one activating execution context.
var a = 10, fn, bar = function (x) {
var b = 5;
fn(x + b);
}
fn = function (y) {
var c = 5;
console.log(y + c);
}
bar(10);
Global execution context:
a | 10 |
---|---|
fn | function |
bar | function |
this | window |
bar- execution context:
b | undefined |
---|---|
x | 10 |
arguments | [10] |
this | window |
fn- execution context
c | undefined |
---|---|
y | 15 |
arguments | [15] |
this | window |
11. this
The value of
this
is determined by when the function is called, but not declared. The value ofthis
is part of the execution context.
Here are several cases:
- constructor function
Constructor is function to
new
an object. Strictly speaking, every function can be used tonew
objects. However, some function are born to initialize objects, while others not. By code style, the fucntion name of a constructor function should in capital. e.x. Object, Array, Function.
// Foo is used as constructor
function Foo() {
this.name = 'Joey';
this.age = 24;
console.log(this); // Foo {name: 'Joey', age: 24}
}
let f1 = new Foo();
console.log(f1.name); // Joey
console.log(f1.age); // 24
// Foo is used as normal function
function Foo() {
this.name = 'Joey';
this.age = 24;
console.log(this); // Window {0: Window, window: Window, self: Window, document: document, name: 'Joey', location: Location, …}
}
Foo();
- function is used as a field of object
// 2.1 function is called as a field of an object
let obj = {
x: 10,
fn: function() {
console.log(this); // {x: 10, fn: ƒ}
console.log(this.x); // 10
}
};
obj.fn();
// 2.2 function is called by another reference variable
let obj = {
x: 10,
fn: function() {
console.log(this); // Window {0: Window, window: Window, self: Window, document: document, name: 'Joey', location: Location, …}
console.log(this.x); // undefined
}
};
let fn1 = obj.fn;
fn1();
- function is called via
call
orapply
let obj = {
x: 10
};
let fn = function() {
console.log(this); // {x: 10}
console.log(this.x); // 10
};
fn.call(obj);
- global & invoke normal functions
// under global setting, `this` is equal to window
console.log(this === window); // true
// when normal function is invoked, `this` is equal to window
var x = 10;
let fn = function() {
console.log(this); // Window {0: Window, window: Window, self: Window, document: document, name: 'Joey', location: Location, …}
console.log(this.x); // 10
};
fn();
// function declared inside another function is still a normal function , `this` is equal to window
let obj = {
x: 10,
fn: function() {
function f() {
console.log(this); // Window {0: Window, window: Window, self: Window, document: document, name: 'Joey', location: Location, …}
console.log(this.x); // undefined
}
f();
}
};
obj.fn();
-
Reuse of
this
in consecutive assignmentjQuery.extend = jQuery.fn.extend = function() { ... target = this; // `this` refers to jQuery when executing jQuery.extend // `this` refers to jQuery.fn when executing jQuery.fn.extend ... }
-
this
in the prototype or proto-chain,this
refers to the invoked object
function Fn() {
this.name = 'Joey';
this.age = 24;
}
Fn.prototype.getName = function() {
console.log(this.name); // `this` refers to the invoked object
}
let f1 = new Fn();
f1.getName(); // Joey
f1.name = 'Harris';
f1.getName(); // Harris
12. Scope
Every function has its own scope, which is identified when the function is created.
var a = 10, b = 20; // global scope
function fn() {
var a = 100, c = 300; // fn scope
console.log(a); // 100
function bar() {
var a = 1000, d = 4000; // bar scope
}
}
var a = 10, b = 20; // global scope
function fn() {
var c = 300; // fn scope
console.log(a); // 10
function bar() {
var a = 1000, d = 4000; // bar scope
}
}
Scope can be used to separate variables, under different scope, the variable declaration will not have confliction.
Scope will have subordinate relationships, which are identified by the scope creation. e.x.:
bar
function is declared withinfn
function, thenfn
scope is the upper level ofbar
scope, the variables declared infn
scope can be used inbar
scope.
The outermost level of jQuery is an automatically executed anonymous function.
In this way, the varibles declared in jQuery can be isolated in a separate scope, without interfering with outer JavaScript code variables.
13. Scope and Context
var a = 10, b = 20; // global scope
function fn(x) {
var a = 100, c = 300; // fn scope
function bar(x) {
var a = 1000, d = 4000; // bar scope
}
bar(100);
bar(200);
}
fn(10);
14. From [free variable] to [scope-chain]
Definition: A free variable is a variable used but not declared in a scope. e.x.
x
is a free variable regarding tofn
scope in the code below.
var x = 10;
function fn() {
var b = 20;
console.log(x + b); // x is a free variable here
}
The value of a free variable should be read from the scope that created the current scope, or any upper-level scope along the scope-chain.
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
(function() {
f(); // 10
})();
}
show(fn);
An exmple of scope-chain:
15. Closure
Closure corresponds to two situations:
- function as a return value
- function as a passing argument
- function as a return value
function fn() {
var max = 10;
return function bar(x) {
if (x > max) {
console.log(max + " " + x); // 10 15
}
}
}
var f1 = fn();
f1(15);
- function as a passing argument
var max = 10, fn = function(x) {
if (x > max) {
console.log(max + " " + x); // 10 15
}
};
(function(f) {
var max = 100;
f(15);
})(fn);
In some cases, when a function call completes, its execution context is not destroyed, this is caused by closure.
function fn(x) {
var max = 10;
return function bar(x) {
if (x > max) {
console.log(max + " " + x); // 10 15
}
}
};
var f1 = fn(), max = 100;
f1(15);
function fn(x) {
// var max = 10;
return function bar(x) {
console.log(max + " " + x); // 100 15
}
};
var f1 = fn(), max = 100;
f1(15);
fn()
execution context will be in active status, because the closure needs variables in the execution context.
In the example below, when f2()
is invoked, the execution context of fn(5)
still exists in memory. This is an example of Multiple execution contexts exist in a single scope.
Reference:
[1]. https://www.cnblogs.com/wangfupeng1988/p/3977924.html
[2]. https://blog.bitsrc.io/hoisting-in-modern-javascript-let-const-and-var-b290405adfda