this
是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。
在JavaScript语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象下运行的,而this就是函数运行时所在的对象(环境)。这本来并不会让我们糊涂,但是JavaScript支持运行环境动态切换,也就是说,this的指向是动态的,很难事先确定到底指向哪个对象,这才是最让我们感到困惑的地方。
总之,this永远指向一个对象,但是this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this最终指向的是那个调用它的对象。
JavaScript 语言之所以有this
的设计,跟内存里面的数据结构有关系。
var obj = { foo: 5 }
上面的代码将一个对象赋值给变量obj,JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 }
,然后再把这个对象的内存地址赋值给变量obj。
也就是说,变量obj是一个地址。后面如果要读取obj.foo
,引擎会先从obj
拿到内存地址,然后再从该地址读出原始的对象,返回它的foo
属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo
属性,实际上是以下面的形式保存的。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
注意,foo
属性的值保存在属性描述对象的value
属性里面。
这样的结构是很清晰的,问题在于属性的值可能是一个函数。
首先我们应该知道,在JS中,数组、函数、对象都是引用类型,在参数传递时也就是引用传递。
var obj = { foo: function () {} }
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo
属性的value
属性。
{
foo: {
[[value]]: 函数的地址
...
}
}
由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
var f = function () {}
var obj = { f: f }
// 单独执行
f()
// obj 环境执行
obj.f()
JavaScript 允许在函数体内部,引用当前环境的其他变量。
var f = function () {
console.log(x)
}
上面代码中,函数体里面使用了变量x,该变量由运行环境提供。
现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this
就出现了,它设计的目的就是在函数体内部,指代函数当前的运行环境。
var x = 1
var f = function () {
console.log(this.x)
}
var obj = {
foo: f,
x: 2
}
// 单独执行
f() // 1
// obj 环境执行
obj.foo() // 2
上面代码中,函数f
在全局环境执行,this.x
指向全局环境的x
。
在obj
环境执行,this.x
指向obj.x
。
函数的不同使用场合,this
有不同的值
这是函数的最通常用法,属于全局性调用,因此this
就代表全局对象。请看下面这段代码,它的运行结果是1。
var x = 1
function test() {
console.log(this.x)
}
test() // 1
作为对象方法的调用
函数还可以作为某个对象的方法调用,这时this
就指这个上级对象。
var obj = {
user:"sun",
fn:function(){
console.log(this.user) //sun
}
}
obj.fn()
obj.fn()
是通过obj
找到fn
,所以就是在obj
环境执行。一旦var fn = obj.fn
,变量fn
就直接指向函数本身,所以fn()
就变成在全局环境执行。
var obj = { user:"sun", fn:function(){ console.log(this.user) //undefined
}
} var fn = obj.fn fn()
所以,this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。
还有一种情况需要注意:
var obj = {
user:"sun",
fn:function(){
console.log(this.user) //sun
}
}
window.obj.fn()
首先,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点obj对象。
但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象。
这里我们补充一下:
情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中默认的this不再是window,而是undefined。
情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数被最外层的对象所调用,this指向的也只是它上一级的对象。
事件绑定共有三种方式:行内绑定、动态绑定、事件监听。
行内绑定的两种情况:
<input type="button" value="按钮" onclick="click()">
<script>
function click(){
this //此函数的运行环境在全局window对象下,因此this指向window
}
</script>
?
<input type="button" value="按钮" onclick="this">
<!-- 运行环境在节点对象中,因此this指向本节点对象 -->
行内绑定事件的语法是在html节点内,以节点属性的方式绑定,属性名是事件名称前面加‘on‘,属性的值则是一段可执行的 JS 代码段,而属性值最常见的就是一个函数调用。
当事件触发时,属性值就会作为JS代码被执行,当前运行环境下没有click
函数,因此浏览器就需要跳出当前运行环境,在整个环境中寻找一个叫click
的函数并执行这个函数,所以函数内部的this就指向了全局对象window。如果不是一个函数调用,直接在当前节点对象环境下使用this,那么显然this就会指向当前节点对象。
动态绑定与事件监听:
<input type="button" value="按钮" id="btn">
<script>
var btn = document.getElementById(‘btn‘)
btn.onclick = function(){
this // this指向本节点对象
}
</script>
因为动态绑定的事件本就是为节点对象的属性(事件名称前面加‘on‘)重新赋值为一个匿名函数,因此函数在执行时就是在节点对象的环境下,this自然就指向了本节点对象。
事件监听中this指向的原理与动态绑定基本一致,所以不再阐述。
所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this
就指这个新对象。
var x = ‘2‘
function Pro(){
this.x = ‘1‘
this.y = function(){}
}
var p = new Pro()
p.x // ‘1‘
x // ‘2‘
这里之所以对象p可以点出函数Pro里面的x是因为new关键字可以改变this的指向,将这个this指向对象p,为什么我说p是对象,因为用了new关键字就是创建一个对象实例,我们这里用变量p创建了一个Pro的实例(相当于复制了一份Pro到对象p里面),此时仅仅只是创建,并没有执行,而调用这个函数Pro的是对象p,那么this指向的自然是对象p,那么为什么对象p中会有x,因为你已经复制了一份Pro函数到对象p中,用了new关键字就等同于复制了一份。
new 一个构造函数并执行函数内部代码的过程就是这个五个步骤,当 JS 引擎指向到第3步的时候,会强制的将this指向新创建出来的这个对象。基本不需要理解,因为这本就是 JS 中的语法规则,记住就可以了。
当this碰到return时:
function Fn() {
this.user = ‘sun‘
return {}
}
var a = new Fn()
console.log(a.user) //undefined
function Fn() {
this.user = ‘sun‘
return function(){}
}
var a = new Fn()
console.log(a.user) //undefined
function Fn() {
this.user = ‘sun‘
return 1
}
var a = new Fn()
console.log(a.user) //sun
function Fn() {
this.user = ‘sun‘
return undefined
}
var a = new Fn()
console.log(a.user) //sun
function Fn() {
this.user = ‘sun‘
return null
}
var a = new Fn()
console.log(a.user) //sun
什么意思呢?如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。
window定时器中的this
var obj = {
fun:function(){
console.log(this)
}
}
?
setInterval(obj.fun,1000) // this指向window对象
setInterval(‘obj.fun()‘,1000) // this指向obj对象
setInterval()
是window对象下内置的一个方法,接受两个参数,第一个参数允许是一个函数或者是一段可执行的 JS 代码,第二个参数则是执行前面函数或者代码的时间间隔;
在上面的代码中,setInterval(obj.fun,1000)
的第一个参数是obj
对象的fun
,因为 JS 中函数可以被当做值来做引用传递,实际就是将这个函数的地址当做参数传递给了 setInterval
方法,换句话说就是 setInterval
的第一参数接受了一个函数,那么此时1000毫秒后,函数的运行就已经是在window对象下了,也就是函数的调用者已经变成了window对象,所以其中的this则指向的全局window对象。
而在 setInterval(‘obj.fun()‘,1000)
中的第一个参数,实际则是传入的一段可执行的 JS 代码。1000毫秒后当 JS 引擎来执行这段代码时,则是通过 obj
对象来找到 fun
函数并调用执行,那么函数的运行环境依然在 对象 obj
内,所以函数内部的this也就指向了 obj
对象。
call和apply的作用一致,区别仅仅在函数参数传递的方式上。这个两个方法的最大作用就是用来强制指定函数调用时this的指向。
以apply()为例:
var x = 0
function test() {
console.log(this.x)
}
var obj = { x : 1 }
test.apply(obj) // 1
但是,apply()
的参数为空时,默认调用全局对象:
var x = 0
function test() {
console.log(this.x)
}
var obj = { x : 1 }
obj.m = test
obj.m.apply() // 0 这时的运行结果为0
,证明this
指的是全局对象。
参考:
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
https://zhuanlan.zhihu.com/p/42145138
https://www.cnblogs.com/pssp/p/5216085.html
原文:https://www.cnblogs.com/sunyudi/p/14183258.html