Javascript中的this(二)

509 阅读6分钟

接着上一章继续讲,还有两个this的绑定规则就是显式绑定new绑定

显式绑定

顾名思义,显式绑定意思就是明确的将某个对象绑定到this上,我们知道Javascript有三个APIapplycallbind。他们的共同作用就是手动改变this指向。来看栗子:

   function foo() {
     console.log('foo函数调用',this)
   }
   foo()

此时调用foo函数,this指向window对象没毛病。如果我想将this指向为obj对象怎么办,一种方法是在obj对象中添加foo方法,然后通过obj.foo()的方式调用。但是若规定不允许改变obj对象呢?此时apply上场了,我们可以这么做:

   function foo() {
     console.log('foo函数调用',this)
   }
   const obj = {
     name: 'zengge'
   }
   foo.apply(obj)

image.png
看,此时的this是不是指向了obj对象?这就是apply最基础的用法。实际上apply还有第二个参数,可以传递参数到foo函数。

   function foo(name, age) {
     console.log('foo函数调用', this, name, age)
   }
   const obj = {
     name: 'zengge'
   }
   foo.apply(obj, ['zengge', 22])

image.png
这就是apply的用法,callapply唯一的区别就是在第二个参数上,apply是以数组形式传参,而call是一个一个传参。

   function foo(name, age) {
     console.log('foo函数调用', this, name, age)
   }
   const obj = {
     name: 'zengge'
   }
   foo.call(obj, 'zengge', 22)

image.png
bindapplycall的调用时机不同,bind会返回一个函数并且可以选择调用时机,而applycall必须立即调用。

   function foo(name, age) {
     console.log('foo函数调用', this, name, age)
   }
   const obj = {
     name: 'zengge'
   }
   const newFoo = foo.bind(obj, 'zengge', 22)
   newFoo()

image.png foo.bind()执行后会返回一个函数,这个函数可以在你想执行的地方执行。如上图newFoo函数是独立调用,按理说this会执行默认绑定规则,应该指向window对象,但是因为newFoobind返回的函数,返回之前已经将this显式绑定到obj对象上了,所以绑定规则也有优先级:显式绑定大于默认绑定。下文会详细介绍绑定规则的优先级。

new绑定

Javascropt中有一个关键字new,当我们通过new来调用一个函数时,这个时候this是在调用这个函数的时候创建出来的对象,即this的指向是这个函数本身。这个过程称之为new绑定

   function Foo(name, age) {
     this.name = name
     this.age = age
   }
   const foo = new Foo('zengge', 22)
   console.log(foo.name, foo.age)

image.png
那么这个new关键字具体做了什么:

  1. 创建了一个空对象
  2. 将空对象的原型指向了构造函数本身的原型
  3. 将空对象作为构造函数的上下文,即改变了this的指向
  4. 对构造函数有返回值的处理判断 我们重点来讨论第3点,其余几点我们会在以后的面向对象章节中详细讨论。 在new操作符的内部改变this的指向时,实际就是使用了apply,将this指向了创建的那个空对象。即fn(构造函数).apply(obj(创建的空对象), args(传递的参数,若有))

绑定规则的优先级

毫无疑问,默认绑定优先级是最低的,存在其他规则时,就会覆盖默认绑定
隐式绑定显式绑定冲突时,谁的优先级更高呢?答案肯定是显式绑定

   const obj = {
     name: 'zengge',
     foo
   }
   const obj2 = {
     name: 'zengdada'
   }
   function foo() {
     console.log(this)
   }
   obj.foo.apply(obj2)

image.png
那么问题来了new绑定显式绑定的优先级谁高呢?一起来研究一下:

   const obj = {
     name: 'zengge'
   }
   function Foo() {
     console.log(this)
   }
   const bar = foo.bind(obj)
   new bar()

image.png
由此可知,new绑定的优先级大于显式绑定
结论: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

this规则之外

凡是总有个例外,同样这个this绑定也有个例外。如果我们显式地将this指向null或者undefined会怎么样呢?它会变成null或者undefined吗?

   function foo() {
     console.log(this)
   }
   foo.apply(null)
   foo.apply(undefined)

image.png
可见this并没有像我们像的那样指向null或者undefined,而是指向了window对象。这应该是显式绑定apply内部有机制处理,不让this显式绑定成null或者undefined

其他一些函数的this

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

image.png 这里的this比较特殊,this指向全局对象也就是window对象,这是因为setTimeout函数内部改变了传入函数的this的指向。

监听函数
   const div = document.getElementById('box')
   div.onclick = function() {
     console.log(this)
   }

image.png
这里实际上就是将function函数作为div的一个属性进行了隐式绑定。所以this指向的就是div节点。

forEach/map/filtter/reduce/find
   const arr = [1,2,3]
   arr.forEach(function(item) {
     console.log(item, this)
   })

这这里的回调函数function也是在forEach内部执行的,结果跟setTimeout一样,this指向window的对象,特殊的是forEach/map/filtter/reduce/find这些函数还可以手动改变this指向,实际上除了回调函数还有第二个参数,用于改变回调函数中的this指向。

   const obj = {
     name: 'zengge'
   }
   const arr = [1,2,3]
   arr.forEach(function(item) {
     console.log(item, this)
   }, obj)

image.png

箭头函数的this

Javascript中函数有两种写法,一种是普通函数function(){},另一种是箭头函数() => {},我们上面说的都是普通函数中的this指向,那么箭头函数的this指向跟普通函数一样吗?我们来看看:

   const obj2 = {
     name: 'zengdada'
   }
   const obj = {
     name: 'zengge',
     foo
   }
   const foo = () => {
     console.log(this)
   }
   foo()
   obj.foo()
   foo.apply(obj2)

image.png
结果全都是window对象,这是因为箭头函数不会绑定this,它的this指向的是上层作用域。这一特性在Javascript中非常有用,举一个实际开发中的栗子:

   const dataObj = {
     data: [],
     getData(){
       const _this = this
       setTimeout(function() {
        const dataList = ['zengge','zengdada','zyq']
        _this.data = dataList
       }, 2000)
     }
   }
   dataObj.getdata()

setTimeout函数中的this我们上面说过指向的是全局this,也就是window对象。所以我们如果直接使用this的话是不能直接拿到dataObj中的data然后进行赋值的。只能在getData函数中创建一个_this变量指向getData中的this(实际上getData中的this就是指向dataObj对象,因为在外部调用getData时是隐式绑定this指向dataObj对象),随后在setTimeout中将dataList赋值给_this,这样才能正确完成赋值。这也是很多人在开发中用到的操作,但是有了箭头函数就根本没有必要这样创建中间变量来赋值,直接取上层作用域(getData)中的this。

   const dataObj = {
     data: [],
     getData(){
       setTimeout(() => {
        const dataList = ['zengge','zengdada','zyq']
        this.data = dataList
       }, 2000)
     }
   }
   dataObj.getdata()

所以要巧用箭头函数可以让开发变得更加简洁和高效。
还有其他函数就不一一列举了,掌握了以上的规则以及常见函数的this绑定,开发和一般的面试就已经够了,那么this就聊到这了,加油学习~~