this 到底指向谁? 箭头函数中的this还能使用吗? 如果能使用又要怎么使用呢? 与普通函数的this指向一致吗?

this到底指向谁?

首先,定义一个函数:

三种不同的调用方式,this的指向也会不同!

  1. 直接调用

foo() // window{}

  1. 通过对象调用
1
2
3
var obj = { name: 'name' }
obj.foo = foo
obj.foo(); // Object{} (obj这个对象)
  1. call/apply/bind

foo.apply(“abc”) // String {‘abc’}

结论:

  1. 函数在调用时,js会默认给this绑定一个值
  2. this的绑定跟它**定义的位置(编写的位置)**没有关系;
  3. this的绑定与调用方式以及调用的位置有关;
  4. this是在运行时被绑定的。

this的绑定规则

默认绑定

  • 普通函数被独立调用

foo() // window{}

  • 函数定义在对象中,但是独立被调用

var bar = obj.foo
bar() // window{}

  • 严格模式下,独立调用的函数中的this指向undefined
1
2
3
4
<script>
"use strict"
...
</script>
  • 高阶函数

function test(fn) {

fn()

}

test(obj.foo) // window{}

即独立调用函数时(非严格模式下)this指向window!

隐式绑定

一般是通过对象来发起调用。

显式绑定

1
2
foo.call(obj)
foo.apply(obj) // foo {name:'name'}
  • 这两种形式的显示绑定区别不大,不会像隐式绑定那样在obj上面添加foo:foo,但是foo的this就指向了obj;
  • 另外,这种形式一般将this绑定在对象身上,如果foo.call(‘abc’),那么也会默认将’abc’创建为对应的包装类型,这里也就是String对象;
  • 如果是绑定在了undefined这种没有对应包装类型的对象身上,那么this就会默认指向window

call / apply函数说明

1
2
3
function test(name, age) {
console.log('参数:', name, age);
}

作用:都可以调取函数并绑定this,传递参数方式不同

  • apply(obj,[argumentsArray])

obj是指this指向的对象;

argumentsArray是指函数的参数,必须要放在数组中进行传递;

1
2
// apply
test.apply('apply', ['chenber', 18]) // 参数: chenber 18
  • <font style="color:#DF2A3F;">call(obj,arg1,arg2,...)</font>

obj是指this指向的对象;

arg1,arg2,...是指函数的参数列表。

1
2
// call
test.call('call', 'chender', 18) // 参数: chender 18

bind

1
2
3
4
5
6
// bind
var bar = test.bind('bind')
bar() // 参数:this,String{'bind}

var bar = test.call('call', 'chender', 18) // 参数: chender 18
bar() // 报错

bind绑定(怪异)函数,是返回绑定过对象的函数,那么在执行的时候this只会指向绑定好的对象;

callapply都是直接执行函数,没有返回值 。

  • bind()的其他参数
1
2
3
var bar = test.call('call', 'chender', 18)
bar() // 参数: chender 18
bar() // 参数: chender 18 (参数一开始就指定好了,修改不了)

❗️❗️❗️此时,会不会有疑问?(bar()函数单独调用,this不应该指向window吗?)

这里就涉及到了this绑定的优先级了!

  1. 默认绑定优先级最低
  2. 显式绑定 > 隐式绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
name: 'chenber',
age: 18,
test: test
}

obj.test.apply('apply', ['1', 19])
// this [String: 'apply']
// 参数: 1 19
var test1 = obj.test.bind('bind', 'chenber', 18)
test1()
// this [String: 'bind']
// 参数: chenber 18
  1. new 绑定 > 隐式绑定
1
2
3
4
5
6
7
8
9
10
var obj = {
name: 'chenber',
foo: function () {
console.log('foo:', this);
console.log('foo:', this === obj);
}
}
new obj.foo()
// foo: foo {}
// foo: false
  1. new > 显式(bind)

❗️newapply / call不可以一起使用,所以没有可比性

newbind可以一起使用

1
2
3
4
5
6
function test() {
console.log('test', this);
}

var testFN = test.bind('abc')
new testFN() // test{}

new绑定

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

  • 创建一个全新的对象;
  • 这个新对象会被执行prototype连接:
  • 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
  • 如果函数没有返回其他对象,表达式会返回这个新对象:

总结(优先级从高到低):

  1. new
  2. bind
  3. apply / call
  4. 隐式
  5. 默认绑定

内置函数的调用绑定

内置函数的this指向需要根据一些经验获取

  1. setTimeOut()
1
2
3
setTimeOut(()=>{
console.log('this', this) //window
},1000)
  1. 按钮的点击监听
1
2
3
4
5
6
7
8
9
10
   var btn = document.querySelector('button')
btn.onclick = function () {
console.log('btn', this); // <button>点击</button>
}
btn.addEventListener("click", () => {
console.log('btn', this); // <button>点击</button>
})
btn.addEventListener("click", () => {
console.log('btn', this); // window
})
  1. forEach

forEach(function(){}, {})

forEach的两个参数:

  1. 回调函数
  2. 回调函数的this绑定对象
1
2
3
4
5
6
7
8
const names = ["abc", "abc", "abc", "abc", "abc"]
names.forEach(function (item) {
console.log('forEach', this); // window
})

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

this 绑定之外的规则

  1. 如果在使用显式绑定时传入null或者undefined,那么就会使用默认绑定规则
1
2
foo.apply(null) // window
foo.apply(undefined) // window

严格模式差异:在严格模式下,绑定null/undefined时会直接使用传入值,this会指向null或undefined本身。

  1. 间接函数引用(知道就行,一般不会出现)
1
2
var obj2 = {};
(obj2.foo = obj.foo)() // window
  1. 箭头函数(补充)
    箭头函数是 es6 新增的一种函数的声明方法。
  • 完整写法
1
2
3
const foo = (name,age)=>{
console.log("=>")
}

❗️注:

- 	箭头函数不会绑定`this` 和 `arguments`(有新的属性进行代替)属性;
- 	箭头函数不能作为构造函数来使用(会抛出错误)
  • 箭头函数的简写
    • 只有一个参数时 可省略()
    • 函数体只有一行语句时,可省略{},但是不能带return 关键字
    • 如果执行体只有返回一个对象,那么需要给这个对象加上()

箭头函数中的 this 使用

箭头函数的作用域没有 this
但是箭头函数中this会向寻找上层作用域中的this,直至找到全局this->window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const test = () => {
console.log('this', this);
}
test() // window
var test1 = test.bind('abc')
test1() // window
var obj = {
name: 'chenber',
foo: () => {
console.log('this', this);
}
}
obj.foo() // window
const test2 = obj.foo.bind('abc')
test2() // window

应用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 模拟网络请求函数
function request(url, callbackFn) {
const results = {
code: 200,
msg: '成功',
data: null
}
callbackFn(results)
}

// 将获取的数据传输给obj的results
var obj = {
results: {},
// 之前的写法:
// getData: function () {
// var _this = this
// request('/test', function (res) {
// _this.results = res
// })
// }

// 使用箭头函数:
getData: function () {
request('/test', (res) => {
this.results = res
})
}
}
obj.getData()

1.6 相关面试题

  1. 面试题一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 面试题一
var name = 'window'
var person = {
name: 'person',
sayName: function () {
console.log(this.name)
}
}
function sayName() {
var sss = person.sayName;
sss(); // 默认绑定 输出:window

person.sayName(); // 隐式绑定 输出:person
(person.sayName)(); // 隐式绑定 输出:person // 此处的;不能省略,因为下面是间接函数引用
(b = person.sayName)() // 默认绑定 输出:window 此处为间接函数引用,相当于独立函数调用,即this指向window
}
  1. 面试题二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 面试题二
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}

var person2 = {
name: 'person2'
}

person1.foo1(); // 隐式绑定 输出:person1
person1.foo1.call(person2); // 显示绑定 输出:person2

person1.foo2(); // 默认绑定 输出:window
person1.foo2.call(person2); // 显示绑定 this->上层作用域 输出:window

// 上层作用域指的是函数定义时的作用域,而不是函数运行时的作用域
// 所以下面这几种情况要注意区分
person1.foo3()(); // 默认绑定 输出:window
person1.foo3.call(person2)(); // 显示绑定 输出:window
person1.foo3().call(person2); // 显示绑定 输出:person2

person1.foo4()(); // 间接函数引用 输出:person1
person1.foo4.call(person2)(); // 间接函数引用 输出:person2
person1.foo4().call(person2); // 显式绑定 输出:person1
  1. 面试题三
    在这里插入图片描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 面试题三
var name = 'window'
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
}
this.foo2 = () => console.log(this.name)
this.foo3 = function () {
return function () {
console.log(this.name)
}
}
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1(); // 隐式绑定 输出:person1
person1.foo1.call(person2); // 显示绑定 输出:person2

person1.foo2(); // 上层作用域查找 输出:person1
person1.foo2.call(person2); // 显示绑定 this->上层作用域 输出:person2

person1.foo3()(); // 默认绑定 输出:window
person1.foo3.call(person2)(); // 显式绑定 输出:window
person1.foo3().call(person2); // 显式绑定 输出:person2

person1.foo4()(); // 隐式绑定 上层作用域查找 输出:person1
person1.foo4.call(person2)(); // 显式绑定 上层作用域查找 输出:person2
person1.foo4().call(person2); // 显式绑定 上层作用域查找 输出:person1
  1. 面试题四
    在这里插入图片描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 面试题四
var name = 'window'
function Person(name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()()// 隐式绑定 window
person1.obj.foo1.call(person2)() // 显式绑定 window
person1.obj.foo1().call(person2) // 显式绑定 person2

person1.obj.foo2()() // 隐式绑定 obj
person1.obj.foo2.call(person2)() // 显式绑定 person2
person1.obj.foo2().call(person2) // 显式绑定 obj