JS 手写 bind、call、apply

210 阅读2分钟

手写 bind 函数

bind定义:Function.prototype.bind() - JavaScript | MDN (mozilla.org)

注意:绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。

简易版

// 模拟 bind(简易版)
Function.prototype.bind1 = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)
​
    // 获取 this(数组第一项)
    const t = args.shift()
​
    // fn1.bind(...) 中的 fn1
    const self = this
​
    // 返回一个函数
    return function () {
        return self.apply(t, args)
    }
}
​
function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}
​
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
​

完整版:

参考:js 手动实现bind方法,超详细思路分析! - 听风是风 - 博客园 (cnblogs.com)

Function.prototype.bind_ = function () {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    };
    var args = Array.prototype.slice.call(arguments[0], 1);
    var obj = arguments[0]
    var fn = this;
    //创建中介函数(让fn的实例多了一层__proto__,达到修改原型不会影响fn原型的目的)
    var fn_ = function () {};
    var bound = function () {
        var params = Array.prototype.slice.call(arguments);
        // 当成构造函数调用时,构造函数中会创建一个实例对象,函数内部的this指向此实例
        //可通过constructor判断调用方式,为true this指向实例,否则为obj
        fn.apply(this.constructor === fn ? this : obj, args.concat(params));
        console.log(this);
    };
    fn_.prototype = fn.prototype;
    bound.prototype = new fn_();
    return bound;
};
​
// 实际上多了个new 调用时的构造函数的判断,并实现对调用函数的继承
// 可使用寄生组合继承优化Function.prototype.bind_ = function (obj, ...args) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    };
    var fn = this;
    var bound = function () {
        //通过constructor判断调用方式,为true this指向实例,否则为obj
        fn.apply(this.constructor === fn ? this : obj, [...args, ...arguments]);
        console.log(this);
    };
    bound.prototype = Object.create(fn.prototype)
    return bound;
};

完整简化版:

Function.prototype.myBind = function(thisArg, ...arg1) {
    const _this = this;
    return function _a (...arg2) {
        const params = [...arg1, ...arg2];
        if(this instanceof _a) {
            return new _this(...params);
        }
        else {
            return _this.apply(thisArg, params);
        }
    }
}

JS 实现 call 和 apply

参考:www.cnblogs.com/echolun/p/1…

call 和 apply 的区别

除了都能改变this指向并执行函数,call与apply唯一区别在于参数不同

var fn = function (arg1, arg2) {
    // do something
};

fn.call(this, arg1, arg2); // 参数散列
fn.apply(this, [arg1, arg2]) // 参数使用数组包裹

call 和 apply 的使用场景

检验数据类型:

function type(obj) {
    var regexp = /\s(\w+)]/;
    var result =  regexp.exec(Object.prototype.toString.call(obj))[1];
    return result;
};

console.log(type([123]));//Array
console.log(type('123'));//String
console.log(type(123));//Number
console.log(type(null));//Null
console.log(type(undefined));//Undefined

函数arguments类数组操作:

var fn = function () {
    var arr = Array.prototype.slice.call(arguments);
    console.log(arr); //[1, 2, 3, 4]
};
fn(1, 2, 3, 4);

es6实现:

// ES6 call

Function.prototype.call_ = function (obj, ...args) {
    //判断是否为null或者undefined,同时考虑传递参数不是对象情况
    obj = obj ? Object(obj) : window;
    obj.fn = this;

    // 利用拓展运算符直接将arguments转为数组
    // let args = [...arguments].slice(1);
    let result = obj.fn(...args);

    delete obj.fn
    return result;
};

// ES6 apply

Function.prototype.apply_ = function (obj, arr) {
    obj = obj ? Object(obj) : window;
    
    obj.fn = this;

    const result = obj.fn(...(arr || []));

    delete obj.fn
    return result;
};