JavaScript深入之 this和apply、call、bind

1,172 阅读2分钟

之前我们说过this是在运行时进行绑定的,并不是在编写时绑定。JavaScript的this总指向一个对象,具体指向哪一个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

this的指向大致可以分为下面几种:

  • 1、作为对象的方法调用,this指向该对象
  • 2、作为普通函数,this指向window
  • 3、构造器调用,this指向返回的这个对象
  • 4、箭头函数,箭头函数的this绑定的是this所在函数定义在哪个对象下,就绑定哪个对象,如果也有嵌套的情况,则this绑定到最近一层对象上

关于箭头函数this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

作为对象的方法调用

var obj = {
  a: 1,
  getA: function() {
    console.log(this === obj)  // true
    console.log(this.a) // 1
  }
}
obj.getA()

构造器调用

var MyClass = function() {
  this.name = 'sven'
}
var myclass = new MyClass()
console.log(myclass.name) // 'sven'

隐式绑定

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用,foo中的this会指向最近一层的obj2

function foo() {
  console.log(this.a)
}
var obj2 = {
  a: 42,
  foo: foo
}
var obj1 = {
  a: 42,
  obj2: obj2
}
obj1.obj2.foo() // 42

改变this的指向

  • 1、使用es6的箭头函数
  • 2、在函数内部使用that = this
  • 3、使用apply,call,bind
  • 4、new实例化一个对象

通过apply和call改变函数的this指向,第一个参数都是要指向的this,如果为null函数体内的this会指向默认宿主对象,在浏览器中则是window。第二个参数,apply是数组或者类数组,而call则是arg1,arg2...这种形式。

如何实现一个call、apply

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

从上面代码可以看出,call改变了this的指向,指向了foo,bar函数执行了。上面我们提到过作为对象的方法调用,this会指向这个对象,因此也可以拿到这个对象的属性。因此我们可以利用这个原理来实现call:

  • 1、将函数设为对象的属性
  • 2、执行该函数
  • 3、删除该属性
Function.prototype.call = function (context) {
  var context = Object(context) || window;  // 第一个参数为null指向window
  context.fn = this;

  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) { // 获取call后面的参数
    args.push('arguments[' + i + ']');
  }

  var result = eval('context.fn(' + args + ')'); // 依次将参数放到方法中执行

  delete context.fn
  return result;
}

如何实现一个bind

bind 会返回一个函数,并不会立即执行 第二个是带参数(第一个参数要指向的this,后面的的参数用来传递)

Function.prototype.bind2 = function (context) {
    var self = this;
    
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);

    var fbound = function () {
        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }
    return fbound;
}