头图

this对象在js中是很自由的存在。this一定是指向一个对象,但具体是哪个对象,就不一定了。

一、this指向的困惑

有一个令人困惑的问题,同一个函数,采用不同的方式对它调用,this会产生不同的结果。

示例代码:

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

// 调用方式1:直接调用
foo() // window

// 调用方式2:放到对象中,然后再调用
var obj = {
  name: 'py',
  aaa: foo
}

obj.aaa() // obj对象

// 调用方式3:使用new关键字调用
new foo() // {  }

// 调用方式4:通过apply / call 调用
foo.apply('aaa') // String { "aaa" }对象

分析上面的代码,可以发现:

  • 函数在调用时,JavaScript会默认给this绑定一个值
  • this的绑定和定义的位置(编写的位置)没有关系
  • this的绑定和调用方式以及调用的位置有关系
  • this是在运行时被绑定的;

二、this绑定的规则

那this到底是什么样的绑定规则呢?

1. 默认绑定

独立函数调用时,会使用默认绑定。这种情况下,this指向的是window对象。

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用,示例代码如下:

// 方式1:普通的函数被独立调用
function foo() {
  console.log('foo函数', this)
}

foo() // window

// 方式2:函数定义在对象中,但是独立调用
var obj = {
  name: 'py',
  aaa: function () {
    console.log('在对象内部定义的函数:', this)
  },
}

var bar = obj.aaa
bar() // window

但是,在严格模式下,独立调用函数中的this指向的是undefined

'use strict'

// 方式1:普通的函数被独立调用
function foo() {
  console.log('foo函数', this)
}

foo() // undefined

// 方式2:函数定义在对象中,但是独立调用
var obj = {
  name: 'py',
  aaa: function () {
    console.log('在对象内部定义的函数:', this)
  },
}

var bar = obj.aaa
bar() // undefined

2. 隐式绑定

隐式绑定是通过某个对象进行调用的,也就是它的调用位置中,是通过某个对象发起的函数调用。

示例代码:

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

var obj = {
  name: 'py',
  bar: foo,
}

obj.bar() // obj

隐式绑定有一个前提条件:

  • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
  • 正是通过这个引用,间接的将this绑定到了这个对象上;

3. 显式绑定

显式绑定和隐式绑定的作用类似,都是将函数的this绑定到调用对象上。

但是隐式绑定比较麻烦,需要先在对象内部有一个对函数的引用,比如是一个属性。

相对来说,显式绑定就省事了许多,它通过使用 call()apply()bind() 等方法,明确地指示将函数的this绑定到指定对象上。

// 使用call和apply绑定this

function foo(name, age) {
  console.log(name, age, this)
}

foo.call('call', '张三', 18) // 张三 18 String { "call" }
foo.apply('apply', ['李四', 20]) // 李四 20 String { "apply" }
// 使用bind绑定this

function foo(name, age, address) {
  console.log(name, age, address, this.color)
}

var obj = {
  color: 'red',
}
var bar = foo.bind(obj, '宋江', 30)

bar('山东郓城县') // 宋江 30 山东郓城县 red

三者的区别:

方法名称函数调用时参数格式使用场景说明
call()函数立即执行参数列表经常使用
apply()函数立即执行数组当参数的格式是数组时,推荐使用
bind()函数不会立即执行参数列表开发中较少使用参数是提前设置好的,不可更改

4. new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。

使用new关键字来调用函数时,会执行如下的操作:

  1. 创建一个全新的对象;
  2. 这个新对象会被执行prototype连接;
  3. 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
  4. 执行函数的其他代码;
  5. 如果函数没有返回非空对象,那么就会返回这个新对象;

示例代码如下:

function Foo() {}

var f = new Foo()

console.log(f) // Foo {}

三、this绑定的优先级

如果使用了多种绑定规则,那么哪种绑定的优先级高呢?

1. 默认绑定的优先级最低

毫无疑问,当没有为this绑定任何对象时,系统只能将this指向window对象,所以它的优先级最低。

2. 显示绑定的优先级高于隐式绑定

验证代码如下:

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

var obj = {
  name: '张三',
  bar: foo,
}

// 隐式绑定
obj.bar() // this -> obj

// 显示绑定
obj.bar.apply('aaa') // this -> String { 'aaa' }
obj.bar.call('aaa') // this -> String { 'aaa' }

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

验证代码如下:

var obj = {
  name: '张三',
  bar: function () {
    console.log('obj对象内部的函数:', this)
  },
}

// 隐式绑定
obj.bar() // this -> obj

// bind绑定
var foo = obj.bar.bind('aaa')

foo() // this -> String { 'aaa' }

4. new绑定的优先级高于隐式绑定

验证代码如下:

var obj = {
  name: '张三',
  foo: function() {
    console.log('obj内部的foo函数:', this);
  }
}

// 隐式绑定
obj.foo() // this -> obj

// new绑定
var bar = new obj.foo() // this -> {}

5. new绑定和显示绑定的优先级谁高呢?

首先,new不可以和apply / call一起使用,所以它们没有比较优先级的必要。

那么,new绑定和bind绑定,它俩谁的优先级高呢?

验证代码如下:

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

var bar = foo.bind('aaa')

// bind绑定
bar() // this -> String { 'aaa' }

// new绑定
new bar() // this -> {}

由此可见,new绑定的优先级高于bind绑定。

6. 对于显示绑定的三个方法,它们的优先级谁高呢?

验证代码如下:

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

var bar = foo.bind('aaa')

// bind绑定
bar() // this -> String { 'aaa' }

// apply绑定
bar.apply('bbb') // this -> String { 'aaa' }

// call绑定
bar.call('bbb') // this -> String { 'aaa' }

可见,bind绑定的优先级高于apply / call绑定。

7. 结论

优先级: new绑定 > bind绑定 > apply / call绑定 > 隐式绑定 > 默认绑定

四、箭头函数没有this

箭头函数是ES6之后增加的一种编写函数的方法,与function函数不同,箭头函数自己就没有this对象

当访问箭头函数中的this时,它会按照作用域,一层一层向上查找,直到作用域顶层。

比如下面的代码:

var obj = {
  getData: function () {
    setTimeout(() => {
      console.log(this)
    }, 2000)
  },
}

obj.getData()

作用域示意图:

代码分析:

  1. 执行第9行的代码 obj.getData()
  2. 执行第3行的定时器代码 setTimeout
  3. 2秒钟后,执行第4行代码,打印this;
  4. 打印时,首先在 setTimeout 作用域内查找,因为是箭头函数,不存在this,发现没有找到;
  5. 然后,按照作用域链向上查找,来到 getData 作用域,那么这个作用域中有this对象吗;
  6. 因为 getData 是一个function函数,并且在第9行执行 obj.getData() 时,通过隐式绑定,将 getData 中的this绑定到了obj对象;
  7. 所以,在 getData 作用域中找到了this,打印出来的就是obj对象;

打印结果:

如果将 getData 改成箭头函数,那么结果如何呢?代码如下:

var obj = {
  getData: () => {
    setTimeout(() => {
      console.log(this)
    }, 2000)
  },
}

obj.getData()

作用域示意图:

代码分析:

  1. 执行第9行的代码 obj.getData()
  2. 执行第3行的定时器代码 setTimeout
  3. 2秒钟后,执行第4行代码,打印this;
  4. 打印时,首先在 setTimeout 作用域内查找,因为是箭头函数,不存在this,发现没有找到;
  5. 然后,按照作用域链向上查找,来到 getData 作用域,那么这个作用域中有this对象吗;
  6. 因为 getData 是一个箭头函数,也不存在this,所以继续向上查找;
  7. 注意,再次向上一层作用域查找时,肯定不会在obj中查找,虽然obj使用了 {} ,但它是用来定义对象的,不是代码块,作用域只存在于代码块中;
  8. 最终,来到了全局作用域,全局作用域中的this对象就是window,所以打印出来的就是window对象;

打印结果:


BigDipper
17 声望0 粉丝