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); // trueconsole.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); //Carfunction 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 = 1return 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的原理没有改变。仅此而已。

JS 中的 this相关推荐

  1. 在js中使用HashMap数据结构,在js中使用K,V数据结构

    首先是定义一个HashMap方法,做基类(复制在js中即可,然后引用) //简单的哈希表,begin function HashMap() {/** Map 大小 * */var size = 0;/ ...

  2. [JavaScript] 探索JS中的函数秘密

    函数长啥样? 把一些要重复使用的内容封装到函数内. function foo(title) {console.log(title) } foo('title') foo('dust') foo('he ...

  3. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  4. 在JS中最常看到切最容易迷惑的语法(转)

    发现一篇JS中比较容易迷惑的语法的解释,挺有用的,转载下,与大家分享: js中大括号有四种语义作用 语义1,组织复合语句,这是最常见的 Js代码  if( condition ) { //... }e ...

  5. js去el的map_转:el表达式获取map对象的内容 js中使用el表达式 js 中使用jstl 实现 session.removeattribute...

    原文链接: 总结: el表达式获取map对象的内容 后端: HashMap map1 = new HashMap(); map1.put("key1","lzsb&quo ...

  6. js中substr,substring,indexOf,lastIndexOf的用法

    js中substr,substring,indexOf,lastIndexOf等的用法 1.substr substr(start,length)表示从start位置开始,截取length长度的字符串 ...

  7. js中的各种宽高以及位置总结

    在javascript中操作dom节点让其运动的时候,常常会涉及到各种宽高以及位置坐标等概念,如果不能很好地理解这些属性所代表的意义,就不能理解js的运动原理,同时,由于这些属性概念较多,加上浏览器之 ...

  8. WKWebView Safari调试、JS互调、加载进度条、JS中alert、confirm、prompt

    主要内容 Safari调试 swift/OC与JS互调 增加加载进度条 支持JS中alert.confirm.prompt Safari调试 设置 -> safari --> 高级,开启J ...

  9. 彻底理解js中this

    相关博文:http://blog.csdn.net/libin_1/article/details/49996815 彻底理解js中this的指向,不必硬背. 首先必须要说的是,this的指向在函数定 ...

  10. 彻底理解js中this的指向

    首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然 ...

最新文章

  1. python合并两个有序列表(list)
  2. “nvinfer1::ILogger”: 不能实例化抽象类
  3. 跨平台C++开源码的两种经常使用编译方式
  4. 一、flask的基本使用-flask
  5. Android 屏幕录制GIF脚本
  6. 测试技巧–不编写测试
  7. 常用的几个提高iOS开发效率的开源类库及工具
  8. 【论文】基于特定实体的文本情感分类总结(PART II)
  9. 剑指offer--矩阵中的路径
  10. linux 断开远程vnc,Linux停VNC远程控制的使用方法
  11. 《深入学习VMware vSphere 6》——2.3 在VMware Workstation虚拟机中安装ESXi 6
  12. [ZZ]Sign Up for the First-Ever Appium Roadshow on August 20th in New York City
  13. 【AnySDK】接入必读及常见问题
  14. php写抢红包,红包生成函数(微信抢红包)
  15. Markdown符号:整除、不整除、恰整除、不恰整除
  16. linux 内核协议栈 NAPI机制与处理流程分析(图解)
  17. 【USB笔记】 USB2.0 包格式 握手包Handshake Packets
  18. 51单片机 Proteus仿真 时钟 串口 发送时钟 整点报时
  19. HTML入门级学习附教程链接~
  20. 计算机(本科)课程安排表

热门文章

  1. 【软考高项】01考试简介
  2. 阻焊设计~焊盘阻焊开窗、阻焊桥
  3. python画图goto的意思_在python中怎么实现goto功能
  4. HTML标签——锚点链接小学习(懒癌患者的摸索之路)
  5. 常用开发工具基本操作——CCS, VSCode, CANoe,Matlab,QT
  6. 梯度消失和梯度爆炸问题详解
  7. 如何最快拿到自考本科毕业证?
  8. 组件通信及vuex原理
  9. 【进阶教程】51Sim-One Cloud 2.0 如何导入OpenDRIVE格式地图
  10. (转)幸福的勇气--目录