Generator

概念

Generator 函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行)。

1
2
3
4
function* gen(x) {
var y = yield x + 2;
return y;
}

generator函数是一个封装的异步任务。执行:

1
2
3
let g = gen(1);
g.next()// {value: 3, done: false}
g.next()// {value: undefined, done: true}

调用generator函数会返回一个iterator,其next方法会返回一个对象,表示当前阶段的信息,包括value属性(yield语句后面表达式的值),done表示是否执行完毕。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
var fetch = require('node-fetch');

function* gen() {
var url = 'https://api.github.com/user/github';
var result = yield fetch(url);//return a Promise
console.log(result.bio);
}

var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data))

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));
}
}

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 闭包

  • await关键字下面的语句相当于.then(),加入micro-task队列,那么async1函数执行结束,弹出执行栈。
  • 遇到setTimeout(),加入macro-task队列。
  • 接着执行Promise中的executor函数,然后.then()被加入micro-task队列。
  • 然后执行最后的输出,当前宏任务结束。
  • 此时事件循环机制开始工作:然后从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)

    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'

    Inheritance and Prototype in js

    When it comes to inheritance, there is only one data type in js: object. Every object has a private property named \_proto___ which points to its prototype. The top of the prototype is Object. Object.__proto__ === null

    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
    // 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
    let f = function () {
    this.a = 1;
    this.b = 2;
    }
    /* 这么写也一样
    function f() {
    this.a = 1;
    this.b = 2;
    }
    */
    let o = new f(); // {a: 1, b: 2}

    // 在f函数的原型上定义属性
    f.prototype.b = 3;
    f.prototype.c = 4;

    // 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
    // o.[[Prototype]] 有属性 b 和 c
    // (其实就是 o.__proto__ 或者 o.constructor.prototype)
    // o.[[Prototype]].[[Prototype]] 是 Object.prototype.
    // 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
    // 这就是原型链的末尾,即 null,
    // 根据定义,null 就是没有 [[Prototype]]。

    // 综上,整个原型链如下:

    // {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
    1
    2
    3
    4
    5
    6
    function doSomething(){}
    console.log( doSomething.prototype );
    // 和声明函数的方式无关,
    // JavaScript 中的函数永远有一个默认原型属性。
    var doSomething = function(){};
    console.log( doSomething.prototype );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    constructor: ƒ doSomething(),
    __proto__: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),//return a boolean
    isPrototypeOf: ƒ isPrototypeOf(),//return a boolean on whether inherit
    propertyIsEnumerable: ƒ propertyIsEnumerable(),//是否可枚举
    toLocaleString: ƒ toLocaleString(),//可输出日期等
    toString: ƒ toString(),//
    valueOf: ƒ valueOf()
    }
    }

    New

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function foo() {}
    foo.prototype = {
    foo_prop: "foo val"//add a property
    }
    var proto = new foo //proto.__proto__ = foo.prototype
    proto.bar_prop = "bar val"//add a property on proto

    function bar() {}
    bar.prototype = proto//
    var inst = new bar//
    console.log(inst.__proto__ === bar.prototype)
    console.log(inst.__proto__ === proto)
    console.log(bar.prototype === proto)
    console.log(proto.__proto__ === foo.prototype)
    console.log(proto.__proto__ === foo.prototype)
    // inst -> bar.prototype -> proto -> foo.prototype

    Object.create

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function foo(){}
    foo.prototype = {
    foo_prop: "foo val"
    };
    function bar(){}
    var proto = Object.create(
    foo.prototype,
    {
    bar_prop: {
    value: "bar val"
    }
    }
    );
    bar.prototype = proto;
    var inst = new bar;
    console.log(inst.foo_prop);//foo val
    console.log(inst.bar_prop);//bar val

    IE8 unsupported

    Object.setPrototypeOf

    perform badly

    __proto__

    need abandoning

    prototypeObject.getPrototypeOf

    1
    2
    3
    var a1 = new A(); 
    var a2 = new A();
    Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething

    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;