this指向之bind、apply、call

187 阅读3分钟

1.区别

image.png

  1. apply、call、bind 三者都是用来改变函数的 this 对象的指向的;
  2. apply、call、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
  3. apply、call 两者都可以利用后续参数传参; 但是传参的方式不一样,apply是数组,call是正常传参形式。
  4. bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用

2. bind

bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

let obj = {};
function test(...params){
  console.log(this === obj);  //true
  console.log('我接收第二个以后的参数:'+params)
}
let result = test.bind(obj,1,'aaaa',[4,5]);
result();

image.png

手写bind

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};
    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

2. apply

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变thi指向一次。

function Person(name,age){
    this.name = name;
    this.age = age;
}
function Student(name,age,grade){
    //apply用法
    Person.apply(this,arguments);//arguments指该函数的所有参数(数组)
    //call用法
    //Person.call(this,name,age);
    this.grade = grade
}
let student = new Student('猪猪侠',12,'一年级')
console.log(student.name);  //猪猪侠

手写apply

Function.prototype.myApply = function(context){
    if(typeof this !== 'function'){
        throw new TypeError('not function')
    }
    context = context || window;
    context.fn = this;
    let result;
    //arguments是JS内置对象,取函数形参arguments[0]指第一形参this指向,arguments[1]剩余参数
    //如果arguments[0]是null、undefind,this指向window
    if(arguments[1]){
        result = context.fn(...arguments[1]); //相当于push(...[1,2,3]),result是数组长度
    }else{
        result = context.fn();
    }
    delete context.fn
    return result
}
let arr1 = [1,2,3];
let arr2 = [];
arr2.push.myApply(arr2,arr1);
console.log(arr2) //[1, 2, 3]

手写apply es6版

Function.prototype.myApply = function (context, args=[]) {
 if(typeof this !== 'function'){
     throw new TypeError('not function')
  }
       context = context || window; //这里默认不传就是给window,也可以用es6给参数设置默认参数
       const key = Symbol(); //给context新增一个独一无二的属性以免覆盖原有属性
       context[key] = this
       let result = context[key](...args);//通过隐式绑定的方式调用函数
       delete context[key];//删除添加的属性
       return result; //返回函数调用的返回值
  }

3. call

call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

手写call

Function.prototype.myCall = function(context){
    if(typeof this!== 'function'){
        throw new TypeError('not function')
    }
    context = context || window;
    context.fn = this;
    let arr = [...arguments].slice(1); //删除第一个参数(用于改变this指向)
    let result = context.fn(...arr);
    delete context.fn
    return result
}

let arr = [];
arr.push.myCall(arr,1,2,8,9);
console.log(arr) //[1, 2, 8, 9]

手写call es6版

Function.prototype.myApply = function (context, ...args) {
 if(typeof this !== 'function'){
     throw new TypeError('not function')
  }
       context = context || window; //这里默认不传就是给window,也可以用es6给参数设置默认参数
       const key = Symbol(); //给context新增一个独一无二的属性以免覆盖原有属性
       context[key] = this
       let result = context[key](...args);//通过隐式绑定的方式调用函数
       delete context[key];//删除添加的属性
       return result; //返回函数调用的返回值
  }