call、 apply、bind 的使用和实现

91 阅读2分钟

Call

使用一个指定的this值,和单独给出的一个或多个参数来调用一个函数,达成可以改变当前函数的this指向,并且让当前函数执行

function fun() {
  console.log(this.name, arguments)
}
let obj = { name: 'clying' }
fun.call(obj, 'deng', 'car')

实现

给函数原型添加mycall方法,创建一个上下文对象context,如果传入的对象不存在时,将指向全局window。通过给context添加fn属性,context的fn引用调用该方法的函数fun,并执行fun。执行完成之后删除该属性fn。

设置fn主要是为了实现函数执行这一步

Function.prototype.mycall = function (context, ...args) {
  context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  let r = context.fn(...args)
  delete context.fn
  return r
}

Apply

pply方法接收的是一个包含多个参数的数组。

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
fun.apply(obj, [22, 1])

实现

实现方法与call类似,不过在接收参数时,可以使用一个args作为传入的第二个参数。直接判断如果未传入第二个参数,直接执行函数;否则执行函数。

Function.prototype.myapply = function (context, args) {
 context = (context == null || context == undefined) ? window : new Object(context)
  context.fn = this
  if(!args) return context.fn()
  let r = context.fn(...args)
  delete context.fn
  return r
}

Bind

创建一个新的函数,不自动执行

这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

function fun() {
  console.log(this.name, arguments);
}
let obj = {
  name: 'clying'
}
let b = fun.bind(obj,2)
b(3)
// clying Arguments(2) [2, 3]

实现

使用apply实现:

Function.prototype.myBind = function () {
                // console.log(context)
                // 取出第一个参数
                let outContext = arguments[0];
                // 取出剩余参数,Array.from() 第一个用途:将类数组对象转换成数组。
                let outArgs = Array.from(arguments).slice(1);
                let outThis = this; // 存外部this
                return function cb() {
                    // 获取内部参数
                    let inArgs = Array.from(arguments);
                    return outThis.apply(outContext, outArgs.concat(inArgs));
                };
            };
            var obj = { name: 1, age: 2 };
            var name = "Leo",
                age = 18;
            function Fn(height, Gender) {
                console.log(
                    "name:",
                    this.name,
                    "age:",
                    this.age,
                    "height:",
                    height,
                    "Gender:",
                    Gender
                );
            }

            Fn(); // name: Leo age: 18 height: undefined Gender: undefined
            var fn1 = Fn.myBind(obj, "80cm");
            fn1(); // name: 1 age: 2 height: 80cm Gender: undefined
            fn1("男"); // 1 age: 2 height: 80cm Gender: 男

            let f1 = Fn.myBind(obj, "80cm")
            new f1("60cm");

            console.log("--------------bind----------------");
            Fn(); // name: Leo age: 18 height: undefined Gender: undefined
            var fn1 = Fn.bind(obj, "80cm");
            fn1(); // name: 1 age: 2 height: 80cm Gender: undefined
            fn1("男"); // 1 age: 2 height: 80cm Gender: 男
            let f2 = Fn.bind(obj, "88cm")
            new f2("69cm");

但是bind在使用new运算符构造绑定函数的时候,会忽略传入的thisArg。

上面这种实现可以看出,此时new后,还是把thisArg传到了构造函数里

同时为了保持构造函数的原型继承问题,需要把继承prototype

所以这里需要进行判断,是否进行了new

判断是否是new绑定的方式有两种

1、new.target

2、instanceof

Function.prototype.myBind = function () {
                // console.log(context)
                // 取出第一个参数
                let outContext = arguments[0];
                // 取出剩余参数,Array.from() 第一个用途:将类数组对象转换成数组。
                let outArgs = Array.from(arguments).slice(1);
                let outThis = this; // 存外部this
                let cb = function  () {
                    // 获取内部参数
                    const isNew = typeof new.target !== 'undefined' // 判断函数是否被new过
                    let inArgs = Array.from(arguments);
                    return outThis.apply(isNew ? this : outContext, outArgs.concat(inArgs));
                } 
                cb.prototype = outThis.prototype// 继承构造函数原型
                return  cb
            };