理清楚this以及call,apply,bind的区别和手写

151 阅读3分钟

前言

this是面试中常考的一道题目.对很多面试小白是一道有难度的题目,今天我们来好好聊聊this的问题.

this的指向问题

说到this就不得不说this的指向问题了,this的指向问题遵循以下的规律:

默认绑定:函数被独立调用, this指向window

隐式绑定:函数被某个对象调用, this指向该对象

隐式丢失:函数被一连串的对象调用,this指向最近调用的对象

显示绑定:call apply bind

var name = 'global name';
var foo = function() {
  console.log(this.name);
}
var Person = function(name) {
  this.name = name;
}
var obj = {
  name: 'obj name',
  foo: foo
}
var obj1 = {
  a:2
  fn:foo
}
var obj2 = {
  a:3
  fm:obj1
}

当调用foo()时,独立调用,触发默认绑定,foo中的this指向全局window,因为全局有var name = 'global name';所以会打印global name.若是obj.foo(),触发隐式绑定,this指向obj,会打印obj name;obj2.fm.fn()会打印2,this指向最近调用的对象

显示绑定就是直接通过call,apply,bind方法改变this指向

let obj={
     a:1
}
function foo() {
  console.log(this.a);
}
foo.call(obj)//a

image.png

call apply bind的区别

在上述代码中foo.call(obj),call帮助foo调用了,如果foo中有参数,call apply bind就应该帮忙接收参数并帮助调用foo.

call函数中接收的第一个参数应该是将this绑定的目标对象,后面的是参数,参数的接收是零散的

例如:

function greet(greeting, punctuation) {
    console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

greet.call(person, "Hello", "!"); // 输出: Hello, Alice!

apply和call的区别就是接收的参数是用数组的包裹的

例如:

function greet(greeting, punctuation) {
    console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

greet.apply(person, ["Hi", "!"]); // 输出: Hi, Alice!

bind接收参数的方式和call一样是零散接收的,但bind返回的是一个函数,所以我们要再调用一下

例如:

function greet(greeting, punctuation) {
   console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };
const boundGreet = greet.bind(person, "Hello"); 

boundGreet("!"); // 输出: Hello, Alice!

手写call apply bind

call:

Function.prototype.mycall = function (context, ...args) {
    if (typeof(this) !== 'function') {
      throw new TypeError('Error')
    }
    context = context || window
    const key = Symbol('fn')
    context[key] = this
    const res = context[key](...args)
    delete context[key]
    return res
  }

首先就要判断调用call的对象的数据类型,如果不是一个函数直接抛出错误,第5行是为了防止不传this要指向的对象,如果不传默认指向window;call的实现原理是借用了this的隐式绑定规则.所以在要指向的对象上挂一个fn属性并调用它,为了防止使用时遇上正好有一个fn,所以使用了symbol('fn')做key.再将fn调用后,this就指向了目标对象上了,最后再删除fn.如果this函数中有返回值帮它返回,所以return res

apply:

  Function.prototype.myapply = function (context, args) {
    if (typeof(this) !== 'function') {
      throw new TypeError('Error')
    }
    context = context || window
    const key = Symbol('fn')
    context[key] = this
    const res = context[key](...args)
    delete context[key]
    return res
  }

bind:

  Function.prototype.mybind = function (context, ...args) {
    if (typeof(this) !== 'function') {
      throw new TypeError('Error')
    }
    context = context || window
    const self = this
    return function Fn(...arg) {  
      if (this.__proto__ === Fn.prototype) { // 被 new 调用 返回 foo 的实例
        return new self(...args, ...arg)
      } else {
        return self.apply(context, [...args,...arg])
      }
    }
  }

bind要注意const boundGreet=foo.bind(obj,1,2)和boundGreet("!")都可以接收参数,优先接收前者情况;第8行是为了防止return出的函数体被new了,导致无法将this指向目标对象,指向window;如果被new调用,第八行的this就指向new出来的实例对象,就会有this.proto === Fn.prototype