DIY系列

call

/*
** var value = 2;
** var obj = {
**     value: 1
** };
** 
** function bar(name, age) {
**     return {
**         value: this.value,
**         name: name,
**         age: age
**     }
** }
** 
** bar.call2(null); // 2
** 
** bar.call2(obj, 'kevin', 18);
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }
*/
Function.prototype.call2 = function (context) {
    //context undefined、null、'' -> window
    //context true\false -> {}
    context = typeof context == 'boolean' ? {} : (context ? (typeof context == 'object' ? context : {}) : window);
    //临时方法fn,存储bar函数
    context.fn = this;
    //执行该方法,参数不包括obj本身  es6写法... 也可遍历arguments获取
    var result = context.fn(...[...arguments].slice(1));
    //删除该方法
    delete context.fn;
    //返回bar的返回值
    return result;
}
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
31
32
33
34
35
36

此代码也有缺点,修改原对象(添加了一个之前不存在的fn),有可能造成bug,如Object.freeze(context)等

apply

//只需把fn的参数换成arr即可
Function.prototype.apply2 = function (context, arr) {
    context = typeof context == 'boolean' ? {} : (context ? (typeof context == 'object' ? context : {}) : window);
    context.fn = this;
    var result;
    if(arr) {
        result = context.fn(...arr);
    } else {
        result = context.fn();
    }
    delete context.fn;
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

bind

bind的特点:

  • 1.bind() 方法会创建一个新函数。
  • 2.一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。此时的this指向实例对象,而不是context。
/*
** var foo = {
**     value: 1
** };
** 
** function bar(name, age) {
**     console.log(this.value);
**     console.log(name);
**     console.log(age);
** 
** }
** 
** var bindFoo = bar.bind(foo, 'daisy');
** bindFoo('18');
** // 1
** // daisy
** // 18
** bind的时候可以传参,执行bind的时候也可以传参
*/

//利用call和apply 只满足条件1
Function.prototype.bind2 = function (context) {
    context = typeof context == 'boolean' ? {} : (context ? (typeof context == 'object' ? context : {}) : window);
    //bind的时候 取 除context以外的参数
    var args = Array.prototype.slice.call(arguments, 1);
    //执行环境是bar
    var self = this;
    return function () {
        //取执行bin时候的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(context, args.concat(bindArgs));
    }
}
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
31
32
33
// 满足条件1 2 
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("not a function - this is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    // 利用一个中间函数,用于继承bar,且bar不被修改原型
    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

参考 1 2

promise

https://juejin.im/post/5b2f02cd5188252b937548ab https://juejin.im/post/5c2b34a15188257abf1d96eb https://juejin.im/post/5a006ec351882554b836f1fd

https://github.com/Jocs/jocs.github.io/issues/7

instanceof

// 对象a的__proto__属性指向其构造函数的prototype
// L insatanceof R ==> L.__proto__[n个] === R.prototype
function instance_of (L, R) {//L 表示左表达式,R 表示右表达式
    var O = R.prototype;
    let proto = L.__proto__;  // or  L = Object.getProrotypeOf(L)
    while (true) { ·
        if (proto === null) { return false; };
        if (proto === R.prototype) { return true; }  // 这里重点:当 O 严格等于 L 时,返回 true 
        proto = proto.__proto__; 
    } 
}
1
2
3
4
5
6
7
8
9
10
11

new

new 关键字调用函数都的具体过程:

  • 首先创建一个空的对象,空对象的__proto__属性指向构造函数的原型对象
  • 把上面创建的空对象赋值构造函数内部的this,用构造函数内部的方法修改空对象
  • 如果构造函数返回一个非基本类型的值,则返回这个值,否则返回上面创建的对象
function _new(fn, ...arg) {
    var obj = Object.create(fn.prototype);
    var result = fn.apply(obj, arg);
    return Object.prototype.toString.call(result) == '[object Object]' ? result : obj;
}
1
2
3
4
5

1

写一个简单的模板引擎实现

/* let template = '我是,年龄,性别'; let data = { name: '姓名', age: 18 } render(template, data); // 我是姓名,年龄18,性别undefined */

function render(template, data) {
    const reg = /\{\{(\s+)?(\w+)(\s+)?}\}/; // 模板字符串正则  
    if (reg.test(template)) { // 判断模板里是否有模板字符串
        const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
        template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
        return render(template, data); // 递归的渲染并返回渲染后的结构
    }
    return template; // 如果模板没有模板字符串直接返回
}
1
2
3
4
5
6
7
8
9
function render(tpl, data) {
    return tpl.replace(/\{\{(\s+)?(\w+)(\s+)?}\}/g, (match, key) => { return data[key.trim()] });
}
1
2
3
// 利用解构和模板字符串 见https://juejin.cn/post/7095626395669233701
function render(template, data) {
    eval(`var { ${Object.keys(data).join(',')} } = data;`);
    return eval('`' + template + '`');
}
1
2
3
4
5
function render(template, data) {
  // 最简单版
    with(data) {
        return eval('`' + template + '`')
    }
}
1
2
3
4
5
6

compose 实现

举例:

const add = num => num  + 10;
const multiply = num => num * 2;
const foo = compose(multiply, add);
foo(5) => 30;
1
2
3
4

实现:

// 摘自 https://github.com/reactjs/redux/blob/master/src/compose.
export default function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg;
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
1
2
3
4
5
6
7
8
9
10

柯里化

柯里化的一个重要的作用是延迟执行,bind的作用也是延迟执行,所以bind实际上也是一种柯里化。

  1. 实现 add(1)(2, 3)(4)() = 10 的效果

两个关键点:

  • 传入参数时,代码不执行输出结果,而是先收集起来
  • 当传入空的参数时,代表可以进行真正的运算
function currying(fn) {
    var allArgs = [];

    return function next(...args) {
        if (args.length > 0) {
            // 收集参数
            allArgs = allArgs.concat(args);
            return next;
        } else {
            // 触发条件
            fn.apply(null, allArgs);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. 实现 add(1)(2, 3)(4)(5) = 15 的效果。 问题:怎么判断执行的时机?

利用valueOf和toString:js在获取当前变量值的时候,会根据语境,隐式调用valueOf和toString方法进行获取需要的值。

function currying(fn){
    var allArgs = [];

    function next(){
        var args = [].slice.call(arguments);
        allArgs = allArgs.concat(args);
        return next;
    }
    // 字符类型
    next.toString = function(){
        return fn.apply(null, allArgs);
    };
    // 数值类型
    next.valueOf = function(){
        return fn.apply(null, allArgs);
    }

    return next;
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});
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

1 2 3

最近更新: 5/19/2022, 5:01:10 PM