首页 > 编程语言 > 详细

JavaScript中的this指向问题

时间:2020-12-24 17:00:38      阅读:52      评论:0      收藏:0      [点我收藏+]

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指向的也只是它上一级的对象。

事件绑定中的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

所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,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() 方法

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

 

JavaScript中的this指向问题

原文:https://www.cnblogs.com/sunyudi/p/14183258.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!