js

js的this指向问题

行行色色的this指向问题

Posted by AzirKxs on 2022-10-24
Estimated Reading Time 9 Minutes
Words 2.3k In Total
Viewed Times

this何时存在?

由[深入理解js - AzirKxs的博客 | 分享与记录](http://8.130.40.222/cn/base05- 深入理解js/)我们可以知道,this是和执行上下文绑定的,也就是说每个执行上文中都有自己对应的this,执行上下文主要分为全局执行上下文,函数执行上下文和eval,eval几乎不用,暂时不做区分。也就是我们所需要探讨的核心就是全局执行上下文中的this和函数执行上下文中的this。

全局执行上下文

废话不多说,直接打印

1
console.log(this); //window

就是这么简单,全局执行上下文中的this就是window

函数执行上下文

我们先做一个简单的推测,既然this是在执行上下文中绑定的,而执行上下文是在代码执行时才有的,那么this的值一定与函数的执行有关!(好像说了句废话…),那么我们该如何确定一个this的值呢?

ECMAScript规范

emmm……我也想从规范的角度去理解this,但是看了两篇规范相关的文章了,还是有点迷糊,总之this的值是有一定的规则的,它是由调用方式和所处环境所决定的

ECMAScript2016规范理解(1)-this | 李冬琳的博客 (ldllidonglin.github.io)

JavaScript深入之从ECMAScript规范解读this · Issue #7 · mqyqingfeng/Blog (github.com)

接下来我简单在做一个介绍,注意一下的内容都只在规范中存在:

确定this值的过程:

1.计算 MemberExpression 的结果赋值给 ref

2.判断 ref 是不是一个 Reference 类型

3.根据规则确定this的值

1
2
3
4
5
如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)

如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)

如果 ref 不是 Reference,那么 this 的值为 undefined

MemberExpression

简单的说,MemberExpession的值就是函数调用时()左边的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo() {
console.log(this)
}

foo(); // MemberExpression 是 foo

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

foo()(); // MemberExpression 是 foo()

var foo = {
bar: function () {
return this;
}
}

foo.bar(); // MemberExpression 是 foo.bar

Reference

Reference由以下三个部分组成

  • base value
  • referenced name
  • strict reference

base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。

1
2
3
4
5
6
7
8
var foo = 1;

// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = {
bar: function () {
return this;
}
};

foo.bar(); // foo

// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};

此外,reference还有两个相关的方法

1.GetBase

获取base value

2.IsPropertyReference

判断base value是否是一个对象

GetValue

GetValue是一个从 Reference 类型获取对应值的方法,调用 GetValue,返回的将是具体的值,而不再是一个 Reference,如果使用了+,||等运算将会调用GetValue方法导致MemberExpression不再是一个reference

根据规则规范判断this

案例1

看下面的例子

1
2
3
4
5
6
7
8
9
10
var value = 1;

var foo = {
value: 2,
bar: function () {
return this.value;
}
}

console.log(foo.bar());
  • 首先判断MemberExpression

根据上面的规则我们可以得知MemberExpression为foo.bar

  • 判断foo.bar是否是一个Reference

foo.bar是一个reference

1
2
3
4
5
barReference ={
base:'foo',
name:'bar',
strict:false
}
  • 判断base value的类型

base value是一个对象

  • 确定this的值

根据规则2.1 this = getBase(foo.bar)也就是bar的base value值为foo

案例2
1
2
3
4
5
6
7
8
9
10
var value = 1;

var foo = {
value: 2,
bar: function () {
return this.value;
}
}

(foo.bar = foo.bar)()
  • 首先判断MemberExpression

根据上面的规则我们可以得知MemberExpression为foo.bar

  • 判断foo.bar = foo.bar是否是一个Reference

因为使用了=运算,调用了GetValue方法,得到的不再是一个reference类型,因此不是一个reference类型

  • 判断this

根据规则3,this为undefined

案例3
1
2
3
4
5
6
'use strict'
function foo(){
console.log(this)
}

foo() //undefined
  • 首先判断MemberExpression

根据上面的规则我们可以得知MemberExpression为foo

  • 判断foo是否是一个Reference

foo是一个reference

1
2
3
4
5
fooReference ={
base:'EnvironmentRecord',
name:'foo',
strict:false
}
  • 判断base类型

base为environmentRecord,遵循规则2

  • 判断this

根据规范,ImplicitThisValue(ref)的值为undefined

注意案例3只有在严格模式下才为undefined,一把模式下为window

this的值

了解了规范,我们可以做一些简单易懂的总结了,this 永远指向最后调用它的那个对象

先来看案例:

1
2
3
4
5
6
7
8
'use strict'
function foo(){
console.log(this)
}

foo() //undefined
this.foo() //window
window.foo() //window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict'
function test(){
console.log(this)
}
var a ={
b: test
}
test() //undefined
a.b() //a对象
var c = a.b
c()// undefined

var d = {
e:a
}
d.e.b() //a对象

不管怎么花式赋值,this的值永远与它最后的调用方式有关!

绑定规则

上面我们从函数的角度判断了this的值,其中只包括了我们没有对this值进行更改的情况(默认绑定,隐式绑定),接下来我们从绑定方式的角度进行一个更详细深入探究。

默认绑定

1
2
3
4
5
function foo(){
console.log(this)
}

foo() //window

隐式绑定

1
2
3
4
5
6
7
8
9
10
11
function foo(){
console.log(this)
}

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

obj.foo() //obj
foo() //window
1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
var obj2 = {
name:'obj2',
obj1:obj1
}
obj2.obj1.foo()

显式绑定

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

 必须在调用的对象内部有一个对函数的引用(比如一个属性);

 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;

 正是通过这个引用,间接的将this绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,就要用到apply和call方法了

非严格模式下,如果显示绑定到null或者undefined,则忽略该显示绑定;严格模式下,this为null或者undefined

call

call方法可以将this绑定到call后面的指定的参数上

1
2
3
4
5
6
7
8
function foo() {
console.log(this)
}

foo() //window
foo.call(window) //window
foo.call('123')//String:{'=123'}
foo.call(123) //Number:{123}
1
2
3
4
5
6
7
8
9
10
'use strict'
function foo() {
console.log(this)
return this
}

foo() //undefined
foo.call(window) //window
console.log(typeof foo.call('123'))//123 string
console.log(typeof foo.call(123)) // 123 number

call方法也可以传递参数,需要将传递的参数根在需要绑定的参数后面

1
2
3
4
5
function foo(id,name,age){
console.log(this,id,name,age);
}

foo.call('123','2019006152','azirkxs',22) //String {'123'} 2019006152 azirkxs 22

apply

apply与call方法的作用一模一样,区别在于call方法传递参数时需要以数组的形式传递

1
2
3
4
5
function foo(id,name,age){
console.log(this,id,name,age);
}

foo.apply('123',['2019006152','azirkxs',22]) //String {'123'} 2019006152 azirkxs 22

bind

如果我们需要将函数一直绑定在某个参数上,不想每次都调用call/applay方法,就可以使用bind方法

1
2
3
4
5
6
7
function foo(id,name,age){
console.log(this,id,name,age);
}

foo = foo.bind('123')
foo('2019006152','azirkxs',22) //String {'123'} '2019006152' 'azirkxs' 22
foo('2019006153','kwx',24) //String {'123'} '2019006153' 'kwx' 24

new绑定

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

 1.创建一个全新的对象;

 2.这个新对象会被执行prototype连接;

 3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);

 4.如果函数没有返回其他对象,表达式会返回这个新对象;

1
2
3
4
5
6
7
function foo(id,name,age){
this.id = id;
this.name = name;
this.age = age;
console.log(this,id,name,age);
}
var obj = new foo('2019006152','kxs',22); //foo {id: '2019006152', name: 'kxs', age: 22} '2019006152' 'kxs' 22

绑定优先级

既然有这么多绑定规则,如果我同时使用了,那么该听谁的呢?以下做一个总结

1.默认规则的优先级最低

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

3.new绑定优先级高于隐式绑定

4.new绑定优先级高于bind

  • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

  • new绑定可以和bind一起使用,new绑定优先级更高

箭头函数

在es6中推出了箭头函数,箭头函数不会绑定this,arguments属性且箭头函数不能作为构造函数来使用

箭头函数并不绑定this对象,this引用就会从上层作用于中找到对应的this

看下面的例子来区分箭头函数与普通函数的this

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
var obj = {
name:'obj',
foo1:function(){
console.log(this)
},
foo2:function(){
return function(){
console.log(this)
}
},
foo3:()=>console.log(this),
foo4:function(){
return ()=>console.log(this)
}
}

var test = {
name:'test'
}

obj.foo1(); //obj
obj.foo1.call(test) //test
obj.foo2()(); //window
obj.foo2().call(test);//test
obj.foo2.call(test)();//window

obj.foo3(); //window
obj.foo3.call(test); //window
obj.foo4()(); //obj
obj.foo4().call(test); //obj
obj.foo4.call(test)(); //test

以上就是关于this的相关总结了!


如果这篇文章对你有帮助,可以bilibili关注一波 ~ !此外,如果你觉得本人的文章侵犯了你的著作权,请联系我删除~谢谢!