callapplybind和new的实现

43次阅读

共计 4211 个字符,预计需要花费 11 分钟才能阅读完成。

call

首先我们先看看 call 的用法(一个 call)

call 原理:让前面的函数执行,并改变 this 指向,如果第一个参数没传或者为 null、undefined,那么 this 为 window, 如果传了那么 this 就是第一个参数

 function fn1(n,m) {console.log('我是 fn1',n,m);// 我是 fn1 1 2
    }
    function fn2() {console.log('我是 fn2');
    }
    fn1.myCall(fn2,1,2) 

上面的例子很明白了,那么我们实现一下 call 方法

Function.prototype.myCall = function (context) {// 如果传了参数那么直接转换成对象(容错处理,如果是数字下面就会报错,1.fn 报错),如果没传就是 window
        context = context ? Object(context) : window;
 // 改变 this 指向,如果直接 this=context js 的语法不支持,所以给 context 增加一个 fn 的属性和 this 共用一个地址,并且让他执行就等于让调用 call 的函数执行并且让 context 作为他的 this
        context.fn = this;
        let ary = [];
        for (let i = 1; i < arguments.length; i++) {ary.push(arguments[i])
        }
        // 利用字符串拼接转换把数组转换成字符串依次传入
        let r = eval('context.fn(' + ary + ')');
        delete  context.fn;
         return r;
    };
两个及以上 call 的分析
 function fn1(n, m) {console.log(this);
        console.log('我是 fn1', n, m);
    }

    function fn2(n,m) {console.log('我是 fn2',n,m);// 我是 fn2 2 undefined
    }
    
 fn1.myCall.myCall(fn2, 1, 2)
两个 myCall 或者多个的情况下是调用类上的 myCall 方法,然后将第一个参数传递进去(这里是 fn2),那么 myCall(fn2)执行时把 myCall 的 this 指向变成 fn2,并执行 myCall 方法,而 myCall 执行时就相当于 fn2.myCall(1,2), 这里 fn2 的 this 就是对象 1, 而 1 之后的作为参数传递进去就会得到以上结果(我是 fn2 2 undefined)

apply

apply 和 call 方法很类似,唯一的区别就是参数必须为数组
用法:

 function fn1(n, m) {console.log('我是 fn1', n, m);// 我是 fn1 1 2
    }

    function fn2(n,m) {console.log('我是 fn2',n,m);
    }
    
fn1.myApply(fn2,[1,2])

实现

// 这里指定为数组就直接将使用形参接收即可
  Function.prototype.myApply = function (context,ary) {context = context ? Object(context) : window;
        context.fn = this;
        // 判断是否传递参数,没传的话直接返回执行后的函数
        if(!ary){return context.fn()
        }
        let r = eval('context.fn(' + ary + ')');
        return r;
        delete  context.fn;
    };

new

关键字 new 在调用构造函数的时候实际进行了如下步骤:
1. 创建了一个对象
2.this 指向实例
3. 执行构造函数中的代码,为对象添加属性
4. 返回新对象
!!!如果类返回的是引用数据类型则直接返回 return 后的引用数据类型值,如果返回基本数据类型那么对返回值没有影响
现在我们看看以下原生 new 的例子

 function Animal(type, a) {
        this.type = type;
        this.a = a;
       // return {name: '孟宇航'}
    }

    Animal.prototype.say = function () {console.log('say');
    };

    let animal = new Animal('动物', '猫');
    console.log(animal);
    // 打印的内容如下:Animal {type: "动物", a: "猫"}
        __propto__: say:f()
        constructor: ƒ Animal(type, a)

那么我们把 new 改造成这样的来实现相同的功能:

let animal = myNew(Animal,'动物', '猫');
  // 首先创建一个简单的类
    function Animal(type, a) {
        this.type = type;
        this.a = a
        // return {name:'孟'}
    }

    Animal.prototype.say = function () {console.log('say');
    };

    function myNew() {
        // 删除并拿到 arguments 的第一项
        let c = [].shift.call(arguments);
        //new 返回对象
        let obj = {};
        // 指向类的公有属性
        obj.__proto__ = c.prototype;
        // 将这个类执行并且以 obj 为 this,传递已经截取除第一项的参数
        let r = c.apply(obj, arguments);
        // 判断如果返回的是引用数据类型直接返回否则返回原来值
        return r instanceof Object ? r : obj
    }
    let animal = myNew(Animal, '动物', 'mao');
    console.log(animal);

这样我们就实现了和 new 相同的功能

bind

bind 的使用:

1.bind 返回一个函数
2.bind 改变调用者的 this 指向
3.bind 函数可以被 new
没有参数传递的应用及实现

应用:

let obj={name:'myh'};

  function fn() {console.log(this.name); //myh
  }
  let bindFn = fn.bind(obj)
  bindFn()

实现不传参的 bind

Function.prototype.myBind=function(context){// 利用闭包保存调用者,这里是 fn, 如果不写这一步的话那么 bindFn()时 this 就是 window
      let that = this;
      return function () {
   // 使用 apply 是因为方便后面对传参的处理
          that.apply(context)
      }
  };

上面已经实现了不传参的情景,那么我们再来看看传递参数的情景

传递参数下的 bind 实现

先来看原生的例子

 let obj={name:'myh'};
  function fn(age,animal) {console.log(this.name+'养了一只'+age+'岁的'+animal);
      //myh 养了一只 3 岁的小猫咪
  }
  let bindFn = fn.bind(obj,3);
  bindFn('小猫咪')

传递参数的实现

  Function.prototype.myBind=function(context){
      let that = this;
      let bindArgs = [].slice.call(arguments,1);//[3]
      return function () {let args = [].slice.call(arguments);//["小猫咪"]
          that.apply(context,bindArgs.concat(args))
      }
  };
使用 new 情况下的 bind

我们先看以下代码分析一下, 被 new 过的函数的 this 指向实例

  let obj={name:'myh'};
  function fn() {
  // 按照我们上面的实现这里的 this 指向 obj,如果是 new 过的函数那么我们需要改变一下这里的 this,使 this 指向实例
        console.log(this);
        this.say= '说话'
  }
  let bindFn = fn.bind(obj);
  let newBindFn = new bindFn();
  console.log(newBindFn);

实现被 new 过的函数 this 指向

  function fn() {console.log(this);
        this.say = '说话'
    }
Function.prototype.myBind = function (context) {
        let that = this;
        let bindArgs = [].slice.call(arguments, 1);
        function boundFn() {let args = [].slice.call(arguments);
     // 判断是否是 boundFn 类的实例
            return that.apply(this instanceof boundFn ? this : context, bindArgs.concat(args))
        }
        return boundFn
    };
    
 let bindFn = fn.myBind(obj);
    let newBindFn = new bindFn();
    console.log(newBindFn);

原型方法的继承

以上方法如果在 fn 函数的原型上加一个方法,那么该实例的__proto__并没有指向类的原型,所以修改如下:

 let obj = {name: 'myh'};

    function fn() {this.say = '说话'}

    Function.prototype.myBind = function (context) {
        let that = this;
        let bindArgs = [].slice.call(arguments, 1);

        function Fn() {}// 创建一个中间类
        function boundFn() {let args = [].slice.call(arguments);
            return that.apply(this instanceof boundFn ? this : context, bindArgs.concat(args))
        }
        Fn.prototype = this.prototype;
        //prototype 为一个对象不能直接赋值这样会使用同一个引用地址
        boundFn.prototype = new Fn();
        return boundFn
    };
    fn.prototype.flag = '哺乳类';
    let bindFn = fn.myBind(obj);
    let newBindFn = new bindFn();
    console.log(newBindFn);

正文完
 0