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