深入this关键字

2021/12/6 JavaScriptthis关键字

# 为什么需要this?

没有this,会让编写代码变得很不方便。

# this的指向

# 在全局作用域中

  • 在浏览器环境中,this指向window
  • 在node环境中,this指向一个空对象

# 在函数中

在执行上下文中记录着函数的调用栈、AO对象等;

this 也是其中的一条记录。

在函数执行时才动态绑定this,所以this 指向什么跟函数所处位置没有关系,而跟函数被调用的方式有关

# this的绑定规则

# 默认绑定

函数被调用时,没有绑定任何对象。只是独立的函数调用。

这时 this 指向 window

# 隐式绑定

函数是通过某个对象进行调用的,object对象会被js引擎绑定到fn函数中的this里面。this 指向的就是当前的对象。

# 显示绑定

使用 call apply bind可以指定 this 的绑定对象,可以很明确的绑定this,这个规则称之为显示绑定。

直接独立调用和call/apply/bind调用的不同在于 this 绑定的不同,

# call、apply和bind的不同

call和apply传参的方式不同,call是剩余参数

call(thisArg,item1,item2)
1

apply是通过数组

apply(thisArg,[item1,item2])
1

bind可以将函数绑定this对象,并返回一个函数

function foo(){
    console.log(this)
}
var newFoo = foo.bind("aaa")

newFoo() //this指向aaa
1
2
3
4
5
6

# new绑定

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

通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器创建出来的对象。

this 指向的就是创建出来的对象

function Person(name,age){
    this.name = name
    this.age = age
}

var okarin = new Person("okarin",19)

console.log(okarin.name,okarin.age)
1
2
3
4
5
6
7
8

# 内置函数的this绑定

# setTimeout
setTimeout(function(){
    console.log(this) //window
},2000)
1
2
3

setTimeout 内部是独立的调用函数,所以在setTimeout内部普通函数this指向的是window

# onclick
const boxDiv =document.querySelector('.box')
boxDiv.onclick = function(){
    console.log(this)
}
1
2
3
4

在这里this会指向box本身,所以内部是一个类似的隐式绑定。将foo绑定到了对象内部的一个属性。

# 数组中的高阶函数
var names = ['abc','cba','nba']
names.forEach(function(item){
    console.log(item,this) //window
})
1
2
3
4

在不传递this参数的情况下,this指向window。

而数组中的高阶函数支持传递一个参数 ( thisArg ) 来指定this

# 规则优先级

# 默认规则的优先级最低

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

# 显示绑定高于隐式绑定

var obj = {
    name:'abc',
    foo:function(){
        console.log(this)
    }
}
obj.foo() //obj
obj.foo.call("aaa")//aaa
1
2
3
4
5
6
7
8

call 和 apply 以及 bind 的优先级是高于隐式绑定的。

# new绑定的优先级高于隐式绑定

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

var f = new obj.foo()//foo {}
1
2
3
4
5
6
7
8

指向的是函数创建的对象,所以new绑定的优先级高于隐式绑定。

# new绑定的优先级高于显示绑定

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

var bar  = foo.bind("aaa")
bar() // "aaa"
var obj = new bar() //foo{}
1
2
3
4
5
6
7

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

# this规则之外

# 忽略显示绑定

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

foo.apply("aaa") //aaa
foo.apply(null) //window
foo.apply(undefined)//window
1
2
3
4
5
6
7

当我们使用显示绑定传入的是 null 或 undefind 时,函数内部会自动将this绑定成全局对象。

# 间接函数引用

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

var obj2 = {
    name: "obj2"
}

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

;(obj2.bar = obj1.foo)() //window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# this在箭头函数

# 箭头函数

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

  • 箭头函数不会绑定this、argment属性。
  • 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
() => {}

var foo = () => {}

var nums = [10,20,45,78]
nums.forEach(()=>{
     
})
1
2
3
4
5
6
7
8
# 箭头函数的简写

如果参数只有一个:小括号可以省略

nums.forEach(item =>{
    console.log(item)
})
1
2
3

函数执行体只有一行代码:大括号可以省略,并且会把这行代码的执行结果作为返回值。

nums.filter(item => item % 2 === 0 )
1

只有一行代码并且返回一个对象:在对象外面添加一个小括号

var bar = () => ({name:'okarin',age:18})
1
# 箭头函数的this

箭头函数不使用this的(也就是不绑定this),而是根据外层作用域来决定this。

一个模拟网络请求的案例:

  • 这里使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
  • 需要拿到obj对象,设置data;
  • 但是直接拿到的this是window,需要在外层定义:var _this = this
  • 在setTimeout的回调函数中使用_this就代表了obj对象
var obj = {
    data:[],
    getData:function(){
        var _this = this
        setTimeout(function () {
            var result = ['abc','cba','nba']
            _this.data = result
        },200);
    }
}

obj.getData()

1
2
3
4
5
6
7
8
9
10
11
12
13

上面的代码在ES6之前是我们最常用的方式,从ES6开始,使用箭头函数:

  • 为什么在setTimeout的回调函数中可以直接使用this呢?
  • 因为箭头函数并不绑定this对象,那么this引用就会从上层作用域中找到对应的this
var obj = {
    data:[],
    getData:function(){
        setTimeout(()=> {
            var result = ['abc','cba','nba']
            this.data = result
        },200);
    }
}

obj.getData()
1
2
3
4
5
6
7
8
9
10
11

思考:如果getData也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?

  • 答案是window;
  • 依然是不断的从上层作用域找,那么找到了全局作用域;
  • 在全局作用域内,this代表的就是window
var obj = {
  data: [],
  getData: () => {
    setTimeout(() => {
      console.log(this); // window
    }, 1000);
  }
}

obj.getData();
1
2
3
4
5
6
7
8
9
10

# this面试题

  • 面试题一
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
}
sayName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 面试题二
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 这里的foo2是一个箭头函数,对象里面的箭头函数的上层作用域不是对象,在此处是全局作用域
person1.foo2.call(person2);//window 箭头函数不遵循优先级规则

person1.foo3()();//window foo3()会返回一个函数,对函数的直接调用是独立函数调用
person1.foo3.call(person2)();//window 在这里外层函数的this是person2 但是最后是一个独立函数调用
person1.foo3().call(person2);//person2 这里是显示绑定

person1.foo4()();//person1 箭头函数不绑定this 上层作用域是隐式绑定的person1
person1.foo4.call(person2)(); //person2 foo4上层作用域是显示绑定的person2
person1.foo4().call(person2); //person1 this不遵循规则 上层作用域是隐式绑定的person1
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
  • 面试题三
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 这里的箭头函数的上层作用域是一个函数,所以this是person1
person1.foo2.call(person2) //person1 箭头函数call无效

person1.foo3()() //独立调用 window
person1.foo3.call(person2)() //window 独立调用
person1.foo3().call(person2) // person2 这里是显示绑定

person1.foo4()() //箭头函数上层作用域是foo4 是person1
person1.foo4.call(person2)()//person2 foo4上层作用域是显示绑定的person2
person1.foo4().call(person2) //person1 this不遵循规则 上层作用域是隐式绑定的person1
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
40
  • 面试题四
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 上层函数的this是obj
person1.obj.foo2.call(person2)()//person2
person1.obj.foo2().call(person2)//obj this不遵循规则 上层作用域是隐式绑定的obj
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
Last Updated: 2021/12/19上午12:27:30