javascript中的this指向(四)

828 阅读5分钟

「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」。

在上两篇文章中,我们介绍了this指向的四个规则。除了这四种规则,还有一些javascript的内置函数或者第三方库中的内置函数,它们的this指向是什么样的呢?以及一个函数调用位置如果应用了多条规则,谁的优先级更高呢?这篇文章我们具体总结下。

内置函数的this绑定分析

有时候,我们会调用一些javascript的内置函数或者一些第三方库中的内置函数,例如setTimeout、数组的forEach、元素div的点击事件等。这些内置函数会要求我们传入另外一个函数。我们并不会显示得调用这些函数,而是javascript内部或者第三方库内部帮助我们执行。

那么这些函数中的this是如何绑定的呢?

setTimeout

setTimeout(function () {
  console.log(this);
}, 2000)

我们可以看到setTimeout中的this指向的是window

image.png 我们可以猜测setTimeout内部进行了默认绑定。

function mySetTimeout(fn, duration) {
  fn()
}

mySetTimeout(function () {
  console.log(this);
}, 2000)

如果想要修改this指向,可以使用call方法。

function mySetTimeout(fn, duration) {
  // fn()
  fn.call('abc')
}

此时,this就指向了abc。

image.png

监听点击

我们创建一个盒子,并给这个盒子绑定点击事件。

const boxBiv = document.querySelector('.box')
boxBiv.onclick = function() {
  console.log(this);
}

点击这个盒子,我们看到this指向的是这个盒子。

image.png 我们也可以通过addEventListener来给盒子注册点击事件,与上面不同的是,addEventListener对同一个事件可以注册多次。

boxBiv.addEventListener('click', function() {
  console.log(this);
})
boxBiv.addEventListener('click', function() {
  console.log(this);
})
boxBiv.addEventListener('click', function() {
  console.log(this);
})

点击这个盒子,我们可以看到this也是指向的是这个盒子。

image.png 我们可以猜测addEventListener内部实现了fn.call(boxBiv)使得this指向的是这个盒子。

数组.forEach/map/filter/find

var names = ['abc', 'cba', 'nba']
names.forEach(function(item) {
  console.log(item,this);
})
names.map(function(item) {
  console.log(item, this);
})

我们可以看到这些数组方法中的this指向的是window。

image.png 我们可以在这些方法中传入第二个参数修改this指向

names.forEach(function(item) {
  console.log(item,this);
}, 'abc')
names.map(function(item) {
  console.log(item, this);
}, 'cba')

image.png

规则优先级

默认绑定的优先级最低

默认绑定的优先级是最低的,存在其他规则时就会通过其他规则的方式来绑定this。

显示绑定高于隐式绑定

call/apply的显示绑定高于隐式绑定

var obj = {
  name: 'obj',
  foo: function() {
    console.log(this);
  }
}

// obj.foo()
// 1. call和apply的显示绑定高于隐式绑定
obj.foo.call('abc')
obj.foo.apply('abc')

image.png bind的优先级高于隐式绑定

var bar = obj.foo.bind('aaa')
bar()

image.png 更明显的比较

function foo() {
  console.log(this);
}

var obj = {
  name: 'obj',
  foo: foo.bind('aaa')
}

obj.foo()

image.png

new高于隐式绑定

var obj = {
  name: 'obj',
  foo: function() {
    console.log(this);
  }
}

// new的优先级高于隐式绑定
var r = new obj.foo()

image.png

new优先级高于显示绑定

new关键字不能和call/apply一起来使用,所以不存在谁的优先级更高。new绑定可以和bind一起使用,new绑定优先级更高。

// new的优先级高于bind
function foo() {
  console.log(this);
}
var bar = foo.bind('aaa')
var obj = new bar()

image.png 总结下this绑定规则的优先级:

new绑定 > 显示绑定(call/apply/bind)> 隐式绑定(obj.foo())> 默认绑定(独立函数调用)

特殊绑定

忽略显示绑定

call/apply/bind: 当传入null/undefined时,这个显示绑定会被忽略,会使用默认绑定,自动将this绑定为全局对象。

function foo () {
  console.log(this);
}

foo.call('abc')
foo.call({})

// call/apply/bind:当传入null/undefined时,自动将this绑定成全局对象
foo.apply(null)
foo.apply(undefined)

var bar = foo.bind(null)
bar()

image.png

间接函数引用

var obj1 = {
  name: 'obj1',
  foo: function() {
    console.log(this);
  }
}

var obj2 = {
  name: 'obj2'
}

obj2.bar = obj1.foo
obj2.bar()

判断以上代码,我们知道obj2调用了foo函数,进行了隐式绑定,所以this指向的是obj2。

image.png 那么,下面的代码this指向什么呢?

var obj1 = {
  name: 'obj1',
  foo: function() {
    console.log(this);
  }
}

var obj2 = {
  name: 'obj2'
};

/* obj2.bar = obj1.foo
obj2.bar() */

// {}后面不能省略;因为下面的代码会把上面的代码看成一个整体
(obj2.bar = obj1.foo)()

image.png 创建一个函数的间接引用,这种情况使用默认绑定规则

赋值(obj2.bar = obj1.foo)的结果是foo函数。foo函数被直接调用,那么是默认绑定。

箭头函数中的this指向

箭头函数是ES6之后增加的一种编写函数的方法,它比函数表达式更加简洁。

箭头函数不会绑定this、arguments属性

箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)

箭头函数不使用this的四种标准规则,也就是箭头函数不绑定this,而是根据外层作用域来决定this

var name = 'haha'
var foo = () => {
  console.log(this);
}

foo()

foo是个箭头函数,调用foo时,箭头函数不绑定this,根据外层作用域来决定this,而它的外层作用域是全局,所以this指向window。

image.png 我们更改下foo的调用方式。

var obj = {foo: foo}
obj.foo()
foo.call('abc')

foo无论怎么调用(通过obj调用、call、apply调用),都是箭头函数调用。箭头函数不绑定this,根据外层作用域来决定this。

image.png

应用场景

我们使用setTimeout模拟网络请求,将结果放到data属性中。

var obj = {
  data: [],
  getData: function () {
    setTimeout(function () {
      var result = ['abc', 'cba', 'nba']
      console.log(this);
      this.data = result
    }, 2000)
  }
}

obj.getData()

上述代码中setTimeout中的this指向是window,并不是obj。

在箭头函数之前,我们使用_this来获取obj。

var obj = {
  data: [],
  getData: function () {
+    var _this = this
    setTimeout(function () {
      var result = ['abc', 'cba', 'nba']
      // this.data = result
+      _this.data = result
    }, 2000)
  }
}

obj.getData()

因为obj调用了getData函数,进行了隐式绑定,所以_this指向的是obj。

有了箭头函数,箭头函数里面的this由外层作用域决定。此时箭头函数的外层作用域是getData,而obj调用了getData,是隐式绑定,所以this引用就会从上层作用域中找到对应的this。

var obj = {
  data: [],
  getData: function () {
    // var _this = this
   /*  setTimeout(function () {
      var result = ['abc', 'cba', 'nba']
      this.data = result
      // _this.data = result
    }, 2000) */

    setTimeout(() => {
      var result = ['abc', 'cba', 'nba']
      this.data = result
    }, 2000)
  }
}