Closure in JS(闭包)

A Simple Demo

1
2
3
4
5
6
7
8
function A() {
function B(){
console.log("Hello closure!");
}
return B;
}
var C = A();
C();//Hello closure!
  • 定义普通函数A,
  • 在函数A中定义普通函数B,
  • 函数A返回普通函数B。
  • 在全局中定义C为A执行的结果
  • 执行C

函数A的内部函数B被函数A外的一个变量C所引用

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包

A Less Simple Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
function A() {
var count = 0;
function B() {
count++;
console.log(count);
}
return B;
}

var C = A();
C();//1
C();//2
C();//3

在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC回收,否则一直保存在内存中

B在A中被定义,因此B依赖于A,而外部变量C引用了B,所以A间接被C引用。

A High-level Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function (document) {
varviewport;
varobj = {
init: function(id) {
viewport = document.querySelector('#'+ id);
},
addChild: function(child) {
viewport.appendChild(child);
},
removeChild: function(child) {
viewport.removeChild(child);
}
}
window.jView = obj;
})(document);

(function(){})为一个匿名函数,而()执行。

全局对象window引用了obj,而obj依赖于匿名函数,且操作其viewport,故形成闭包。

让你分分钟理解 JavaScript 闭包

n>

分开来看,fn是用户传的函数,这个函数内部调用了resolve函数后,就会把promise实例上的cbs全部执行一遍,那么cbs数组中的函数从哪来呢?

1
2
3
4
5
6
7
8
9
10
Promise.prototype.then = function(onResolved) {
return new Promise((resolve)=>{
const result = onResolved(this.data);
if(result instanceof Promise) {
result.then(resolve);
} else {
resolve(result)
}
})
}

异步操作封装成Promise,实现对Ajax的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function pajax({url=null, method='GET', dataType:'JSON', async='true'}) {
return new Promise((resolve, reject)=>{
let xhr = new XMLHttpRequest();
xhr.open(method, url, async);
xhr.responseType = dataType;
xhr.onreadystatechange = () =>{
if(xhr.readyState===4 && xhr.status===200) {
let result = xhr.responseText;
resolve(result);
}
}
xhr.onerror = (err) =>{
reject(err);
}
xhr.send();
})
}

pajax({
url: './test.json',
}).then(console.log)
.catch(console.err)

Promise then的第二个参数和catch的区别

主要区别就是,如果在then的第一个函数里抛出了异常,后面的catch能捕获到,而then的第二个函数捕获不到。

Promise的catch是个语法糖,还是通过then来处理的,类似于:

1
2
3
Promise.prototype.catch = function(fn) {
return this.then(null, fn);
}

捕获错误信息的时候会就近原则。

js single thread and event-loop

Pros and Cons for single threaded javasript

由浏览器决定,JavaScript主要用途是操作DOM,决定了它只能是单线程,否则会带来复杂的同步问题。

Pros:

  1. 适合高并发
  2. 适合I/O密集型应用

Cons:

  1. 不适合CPU密集型应用。如果有长时间运行的计算(大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起。

大纲:

  1. 基本知识点,宏任务、微任务…
  2. 事件循环机制过程

浏览器中的事件循环

JavaScript代码执行过程中,除了依靠函数调用栈来搞定函数的执行顺序,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,成为事件循环过程。一个线程中,事件循环是唯一的,但任务队列可以拥有多个。任务队列又分为macro-task和micro-task,在最新标准中称为task和jobs。

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

event-loop

macro-task大概包括:

  • script(整体代码)
  • setTimeOut
  • setInterval
  • setImmediate
  • I/O
  • UI render
  • DOM

micro-task大概包括:

  • process.nextTick
  • Promise
  • Async/Await(Promise)
  • MutationObserver(h5新特性)

几个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')

依次输出为:

script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout

  1. 首先全局匿名函数最先执行,输出”script start”
  2. 然后遇到async1()函数,调用,将该函数压入执行栈,接着执行 “await async2()”。这里await关键字的作用就是await下面的代码只有当await后面的promise返回结果后才可以执行。而await async2() 语句就像执行普通函数一样执行async2(),进入async2输出”async2 end”。
  3. await关键字下面的语句相当于.then(),加入micro-task队列,那么async1函数执行结束,弹出执行栈。
  4. 遇到setTimeout(),加入macro-task队列。
  5. 接着执行Promise中的executor函数,然后.then()被加入micro-task队列。
  6. 然后执行最后的输出,当前宏任务结束。
  7. 此时事件循环机制开始工作:然后从micro-task队列中依次执行微任务。

而当await函数后面跟的是一个异步函数的调用(这里我们强调返回值为异步才为异步函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)

new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})

console.log('script end')

在最新的chrome v8引擎中执行为:

script start
async2 end
Promise
script end
async2 end1
promise1
promise2
async1 end
setTimeout

在这里我们理解为,进入await标记的代码后,将Promise后的首个链式调用注册为micro-task,然后继续执行,直到当前宏任务完成后,微任务也完成后,最后执行await后面的代码,最后再调用其它宏任务。

一个典型的async await的使用为:

1
2
3
4
async function f() {
await p
console.log('ok')
}

我们可以将其简化理解为promise:

1
2
3
4
5
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}

『RESOLVE(p)』接近于『Promise.resolve(p)』,不过有微妙而重要的区别:p 如果本身已经是 Promise 实例,Promise.resolve 会直接返回 p 而不是产生一个新 promise。

这里我们复习一下Ajax原生请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function search(term, onload, onerror) {
var xhr, results, ur
l;
url = `http://example.com/search?q=${term}`;

xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function(e) {
if(this.status===200) {
results = JSON.parse(this.responseText);
onload(results);
}
};
xhr.onerror = function(e) {
onerror(e);
};

xhr.send();
}

search('Hello World', console.log, console.error);

如果使用Promise对象,可以写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function search(term) {
var url =`http://example.com/search?q=${term}`;
var xhr = new XMLHttpRequest();
var result;
xhr.open('GET', url, true);
var p = new Promise(function(resolve, reject){
xhr.open('GET', url, true); //async or not
xhr.onload = function(e) {
if(this.status===200) {
result = JSON.parse(this.responseText);
onload(results);
}
}
xhr.onerror = function(e) {
reject(e);
}
xhr.send();
});
return p;
}

search("Hello World").then(console.log, console.error)

implement myCall, myApply, myBind

Show the code directly.

myCall:

1
2
3
4
5
6
7
8
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1);
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

myApply:

1
2
3
4
5
6
7
8
Function.prototype.myApply = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = arguments[1];
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

myBind:

1
2
3
4
5
6
7
8
Function.prototype.myBind = function(ctx) {
let that = this;
let args = [...arguments].slice(1);
return function() {//currying
const newArgs = [...arguments];
return that.apply(ctx, args.concat(newArgs));
}
}

On a deeper understanding about proto and prototype

Last a few days, I just looked through some basic rules about prototype and __proto__. But today when I learnt how to implement the function call() by myself, I found that there’s something unclear.

1
2
3
4
5
6
7
8
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1);
let result = ctx.fn(...args);
delete ctx.fn;
return result;
}

How to understand the third line? What does this refer to?

First, let’s recall how we use the function myCall.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
let args = [...arguments].slice(1)
let result = ctx.fn(...args)
delete ctx.fn
return result;
}

function a(x,y) {
return x+y;
}

const obj = {
m: 1,
n: 2,
}

a.myCall(obj, 3,5)
  1. a is a function
  2. a uses its method myCall.

Wait. As far as I’m concerned, only Object can has a method. But how could a function use a method?

So I looked through some information, and a picture can conclude it well.

proto

  1. Everything in JS is Object. Function is an object, Function.prototype is an object. So they all share the same feature: they have a property: __proto__, which is a pointer to its constructor’s prototype.
  2. Function is a special object. Including general properties, it has an extra property: prototype, which has a property named constructor which is the Function itself.

So it’s easy for us to understand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myCall = function(ctx) {
ctx = ctx || window;
ctx.fn = this;
console.log(this===a)//true;
let args = [...arguments].slice(1)
let result = ctx.fn(...args)
delete ctx.fn
return result;
}

function a(x,y) {
return x+y;
}
console.log(a instanceof Function)//true

const obj = {
m: 1,
n: 2,
}

console.log(a.prototype.constructor===a)//true
console.log(a.__proto__===Function.prototype)//true
console.log(a.prototype.__proto__===Object.prototype)//true

a.myCall(obj, 3,5)

So how do we understand the total process of the code?

First we define a method myCall for Function.prototype, then we define a function a, which is inherited from Function.prototype.

In the last line, we make the function a (also an object) call the method myCall. First it look up in a‘s properties, but cannot match, so it backtracks to a.__proto__ AKA Function.prototype. So it finally finds the method, and this refers to the object & function a.

Finally we revise the basic knowledge of prototype chain.

Object.prototype(top of the prototype chain)

Function.prototype inherited from Object.prototype

Function and Object inherited from Function.prototype.

There are some thoughts on the topic which is interestring.

从探究Function.proto===Function.prototype过程中的一些收获

this, apply, call and bind

What this represents

this means where the current property is from.

What this points to

this points to the object which is the last to calls it.

1
2
3
4
5
6
7
8
9
10
var name = "windowsName";
function a() {
var name = "Cherry";

console.log(this.name); // windowsName

console.log("inner:" + this); // inner: Window
}
a();//window.a()
console.log("outer:" + this) // outer: Window
1
2
3
4
5
6
7
8
var name = "windowsName";
var a = {
name: "Cherry",
fn : function () {
console.log(this.name); // Cherry
}
}
a.fn();
1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}

var f = a.fn;
f();
1
2
3
4
5
6
7
8
9
10
11
var name = "windowsName";

function fn() {
var name = 'Cherry';
innerFunction();
function innerFunction() {
console.log(this.name); // windowsName
}
}

fn()

Methods to change where this points

Arrow Function

this in arrow function points where the function is defined, instead of conducting.

If there is no this in arrow function, we must find the scope chain to find the value. if a non-arrow function contains the arrow function, this in the arrow function is the this in the non-arrow function, otherwise this is undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var name = "windowsName";

var a = {
name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
setTimeout( () => {
this.func1()
},100);
}

};

a.func2() // Cherry

_this = this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "windowsName";

var a = {

name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}

};

a.func2() // Cherry

apply, call, bind

apply & call difference:

1
2
3
fun.apply(thisArg, [argsArray])
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.bind(thisArg[, arg1[, arg2[, ...]]])()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = {
name : "Cherry",

func1: function () {
console.log(this.name)
},

func2: function () {
setTimeout( function () {
this.func1()
}.apply(a),100);
}

};

a.func2() // Cherry

js-new

How to understand javascript new operation?

A easy example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function soldier(ID) {
var temp_obj = {};// ① new helps us a temp_obj
temp_obj.__proto__ = soldier.prototype;//② new helps us bind the prototype
temp_obj.ID = ID;
temp_obj.life = 42;

return temp_obj;//③ new helps us return the temp_obj;
}

soldier.prototype = {//④ new helps us define "prototype"
type: "Jiefangjun",
attack: 5,
walk: function(){},
run: function(){},
attack: function(){},
defend: function(){},
die: function(){},
}

Using new, we can prevent doing the following things:

  • Creating a temporary object (directly using this to visit the temporary object)
  • Binding prototype (new knows where your prototype is)
  • Returning temporary object
  • Giving a name to “prototype”

After we using new :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function soldier(ID) {
this.ID = ID;
this.life = 42;
}

soldier.prototype = {
type: "Jiefangjun",
attack: 5,
walk: function(){},
run: function(){},
attack: function(){},
defend: function(){},
die: function(){},
}

var soldiers = []
for(let i=0; i<100; ++i) {
soldiers.push(new soldier(i))
}

There is a property named constructor:

1
2
3
soldier.prototype = {
constructor: soldier
}

So we need to define the property seperatedly.

Implement a new by self

First let’s do a test:

1
2
3
4
5
6
7
8
9
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('cxk')
console.log(t.name) // 'cxk'
t.sayName() // 'cxk'

Test if we return a value in the function:

1
2
3
4
5
6
function Test(name) {
this.name = name
return 1
}
const t = new Test('cxk')
console.log(t.name) // 'cxk'

So if we return a value in the function, there is no difference.

Test if we return an object in the function:

1
2
3
4
5
6
7
8
function Test(name) {
this.name = name
console.log(this) // Test { name: 'yck' }
return { age: 26 }
}
const t = new Test('yck')
console.log(t) // { age: 26 }
console.log(t.name) // 'undefined'

So if we return an object, the new doesnt effect.

1
2
3
4
5
6
function create(fun, ...args) {
let obj = {};//create a temp_obj
Object.setPrototypeOf(obj, fun.prototype)//bind the temp_obj to prototype
let result = fun.apply(obj, args)//bind obj to construction function
return result instanceof Object ? result: obj;
}

Then we test function create :

1
2
3
4
5
6
7
8
9
10
11
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const a = create(Test, 'cxk', 21)
console.log(a.name) // 'cxk'
console.log(a.age) // 21
a.sayName() // 'cxk'

Currying

How to implement an add funtion?

1
2
3
4
function add(x, y) {
return x+y;
}
add(1, 3)===4//true

How to implement add(1)(3) === 4?

1
2
3
4
5
function add(x) {
return function(y) {
return x+y;
}
}

How to implement add3(2) === 5?

1
2
3
4
5
6
7
8
function currying(fn, ...args1) {
return function(...args2) {
fn(...args1, ...args1);
}
}

var increment = currying(add, 1);
increment(5)===6//true

How to implement infinite arguments?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function trueCurrying(fn, ...args1) {
if(args1.length>=fn.length) return fn(...args1);
return function(...args2) {
return trueCurrying(fn, ...args1, ...args2);
}
}

function triAdd(x, y, z) = {
return x+y+z;
}

var curryAdd = trueCurrying(triAdd);
curryAdd(1,2,3)===6//true;
curryAdd(1,2)(3)===6//true;
curryAdd(1)(2)(3)===6//true;