JS 中的 this

JS中的this

本文是本人阅读MDN文档和Dmitri Pavlutin的博客后写下的this学习笔记, 主要翻译自Dmitri Pavlutin的博客 Gentle Explanation of “this” in JavaScript,并添加了自己的理解和例子。本文只讨论浏览器环境下的this,且不保证正确。

1. JS中的this是什么

JS中的this是函数调用的上下文。 在我看来,这个定义包括两方面:1.在一个函数中,this是调用这个函数的对象;2.一个函数可能会被以不同的方式调用,不同的调用方式下,this的含义不同,因此,函数中的this的含义可以视为是在运行时确定的。

2. 不同函数调用情形下的this

JS中,函数被调用方式包括(但可能不限于)以下几种:

2.1 作为函数调用(function invocation)

“函数调用”就是“直接”在函数对象后加双引号(双引号中可以有用逗号隔开的参数)调用函数的方式(以“f(a1, a2)”形式最为常见)。直接的意思就是函数变量名前没有“xxx.”的语句,也就是说,函数不作为某个对象的方法被调用。

函数调用情形下,在非严格模式中,函数中的this指向全局对象(在浏览器中就是window对象);在严格模式下,this为undefined。

例如:

var b = 2;
console.log(this); // 全局对象window
var that = this;
function f1(){
  console.log(that === this); // true
  console.log(this.b); //2
}
function f2(){
  'use strict'
  console.log(this); // undefined
}
f1()
f2()
> Window {..., b:1, ...} // var b 被添加为window的属性
> true // 函数调用下,函数内部的this等于全局变量window
> 2 // this.b 即 window.b
> undefined // 严格模式

2.2 作为方法调用(method invocation)

“方法调用”是在一个以属性访问器形式表示的表达式(其计算结果是函数对象)后跟一个开括号(、一个逗号分隔的参数表达式列表和一个右括号)时执行的。最常见的就是直接在对象名后加点再加函数名和括号的形式(即 obj.method(a1, a2)形式)。在方法调用情况下,方法中的this指向拥有方法的对象。例如:

this.a1 = 33
var obj = {
    f: function(num){
        this.a1 = num
    }
}

obj.f(44) //f作为obj的方法被调用,this指向obj,this.a1 即 obj.a1
console.log(obj.a1) //输出44

一个常见的误区是认为,即使将对象的方法抽离对象,其this仍不变。 实际上,以函数调用的形式调用对象方法,this指向全局变量。应时刻注意,js的this代表包含其的函数的调用方法,而非函数的定义方式。闭包返回的函数(即内部函数)中的this也不指向对象本身。例如:

this.a1 = 33 // 全局变量:window.a1
var obj = {
  a1: function(){
    this.a1 = 11;
    return function(){
      this.a1 = 22
    }
  }
}
f = obj.a1 //抽取方法
f() //函数调用,f中的this指向全局变量,所以上面a1方法中的this.a1 = 11 等价于window.a1 = 11
console.log(obj.a1) //这里的a1是obj的方法, 输出函数定义
console.log(window.a1) // 11
f = obj.a1() // f 为返回的匿名函数。此时,a1作为obj的方法进行调用
//这种情况下this为obj(具体阐述见2.2), this.a1 = 11, 即obj.a1 = 11
//也就是说, obj.a1 自此不是方法了,而是一个属性,其值为11
console.log(obj.a1) // 11
f() //以函数调用的形式调用f,f中的this为全局对象
console.log(window.a1) //因此,window.a1 = 22

再举一个例子:

function Pet(type, legs) {
  this.type = type;
  this.legs = legs;

  this.logInfo = function() {
    console.log(this === myCat); 
    console.log(`The ${this.type} has ${this.legs} legs`);
  }
}

const myCat = new Pet('Cat', 4);
setTimeout(myCat.logInfo, 1000); 
//输出:
// false
// ... undefined ... undefined 
//因为,上述操作相当于:
// var f = myCat.logInfo
// setTimeOut(f, 1000)

2.3 作为构造函数调用(constructor invocation)

当new关键字后跟一个计算结果为函数对象的表达式、一个开括号(、一个逗号分隔的参数表达式列表和一个右括号)时,将执行构造函数调用。最常见的形式就是“new Class(a1, a2)”

在此情形下,函数中的this指向新构造的对象。例如:

var a = "b"
var C = function(){
  this.a = "a";
}
C.prototype.b = function(){
  return this.a
}
var obj = new C() // this 指向obj
console.log(obj.a) //a
console.log(obj.b()) //a 虽然b是顺着原型链找到的,但是其调用者仍是obj

注意,“new”后面接方法调用的形式也是构造函数调用。即: "new C.m()"也是构造函数调用。例如:

var C = function(){
  this.a = "a";
}
C.prototype.b = function(){
  this.c = "c"
}
var obj1 = new C() // this 指向obj
var obj2 = new obj1.b() //用C.prototype.b创造了一个新的对象obj2
//obj2没有属性a,只有属性c, 因为构造函数中只设置了c属性。
//C的构造函数并未被调用
//由于obj1.b被作为构造函数调用,其内部的this指向新的对象而不是obj1
//因此,没能给obj1设置c属性
console.log(obj1.a) //a
console.log(obj1.c) //undefined
console.log(obj2.a) //undefined
console.log(obj2.c) //c

有时,我们想要定义一个构造函数,但是定义或使用阶段出现了错误。比如,有人让构造函数返回值,且返回值不是新构造的对象,this也不指向新构建的对象。又比如,调用函数时忘记使用new。例如:

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
}

const car = Vehicle('Car', 4); 
//由于忘记使用new, 函数Viechle被以函数形式调用
//没有新创建对象,构造函数里的this也指向全局对象, 因此给window添加了属性
console.log(car); // undefined
console.log(window.type); //Car

function Vehicle1(type, wheelsCount) {
  this.type = type;
  return this
}
console.log(Vehicle1("Car",5)) //window对象

function Vehicle2(type, wheelsCount) {
  this.type = type;
  return {
  	type: this.type
  }
}
console.log(Vehicle1("Car",5)) //window对象

2.4 箭头函数(arrow function)

箭头函数中的this等于其定义环境的this。如果箭头函数在某个函数中被定义,其this值等于外部函数被执行时的this值。如果箭头函数的定义不在任何函数中,则其this为全局对象。箭头函数一经构建,其this指针就被绑定,且不能被任何方法改变。例如:

var Point = function(x, y){
  this.x = x;
  this.y = y;
  this.getX = ()=>{return this.x;};
  this.getY = ()=>{return this.y;};
}
var p1 = new Point(1,2) //构造函数调用; getX、getY方法被创建
//箭头函数的this被固定为p1
console.log(p1.getX()) // 1
f = p1.getY//将箭头函数抽离
console.log(f())//2 箭头函数的this被永久绑定,不受后续的调用方式影响

var a = 0
var f = ()=>{
  this.a = 1
  return this.a
}
console.log(f()) //1, 箭头函数是在最外围创造的,因此其this为window

在ES5中,如果把类的方法定义为箭头函数,则方法内的指针很可能不为新构建对象。例如:

var a = 11
var C = function(){
  this.a = 1
}

C.prototype.method = ()=>{return this.a;}
var c = new C()
console.log(c.method()) // 11

2.5 非直接调用(indirect invocation)

非直接调用是指以function.call(argthis, args1, arg2, …), function.apply(argthis,[args])的形式调用函数。call和apply的第一个参数是fuction的调用上下文,也就是函数中的this的值。例如:

var obj1 = {
  a:1,
  b:2
}
var obj2 = {
  a:3,
  b:4
}
var f = function(){
  return this.a + this.b;
}
console.log(f.call(obj1))
console.log(f.apply(obj2))

2.6 绑定函数(bound function)

绑定函数是指使用bind(argThis)函数新建一个函数,且将其上下文永久绑定到argThis的形式调用函数。一旦使用bind(argThis)绑定成功,函数的上下文(this)就不能再改变。但是,构造函数调用可以改变经过bind绑定的函数的上下文。例如:

var a = "b"
var obj = {
  a: "a",
  m: function(){
  	console.log(this.a);
  }
}
var f = obj.m.bind(obj);
f(); //a
f.call(window); //a
f.apply(window); //a
f.bond(window)(); //a, 重绑定无效
var obj2 = new f(); //undefined, 因为new 使得this指向新构建的对象,而对象无a属性

3 函数以外的调用

如果一个this出现在函数以外的地方,则其值为window,或为undefined(在严格模式下)

'use strict'
console.log(this) //undefined
console.log(this) //window

4 总结

这已经不是我第一次学习js中的this了。之前学得迷糊的原因是:1. 误以为JS中的this与C++中的this的归属不同,其实二者都指向一个对象。2. 下意识地认为JS中的嵌套函数是在编码完成时就被实例化了,其实应该理解为嵌套函数是在外层函数被调用后产生的。3. 下意识地认为JS函数中的this在函数构建完后就确定了,实际上函数在被调用时,this才被确定。我把后两点归结为对JS即时编译的特点体会不够深。

其实JS中的this的原理很简单。除箭头函数和构造函数以外的函数调用情形下,this的值是函数的调用者,特别是在严格模式下,以函数调用的方式调用函数,函数是被看作没有调用者的,这符合人的观感,也符合编程需求。这些情形下,this的值与函数定义的位置、函数的归属无关。而箭头函数的this则是函数被创建时的上下文,与调用方式无关,且无法改变。构造函数调用使函数的this指向新构造的对象。ES6虽然出了一些关于构建类和对象的新特性,但this的原理没有改变。仅此而已。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值