不必硬背,彻底理解JavaScript中的this指向!(上)

简介: 1. 执行上下文提到 this,还得从执行上下文说起。在执行上下文中,包含了变量环境、词法环境、外部环境、this:

1. 执行上下文


提到 this,还得从执行上下文说起。在执行上下文中,包含了变量环境、词法环境、外部环境、this:


网络异常,图片无法展示
|


实际上,this 是和执行上下文绑定的,也就是说每个执行上下文都有一个this,下面就来看看执行上下文的相关概念。


(1)执行上下文概念

执行上下文是评估和执行 JavaScript 代码的环境的抽象概念,当 JavaSciprt 代码在运行时,其运行在执行上下文中。JavaScript 中有三种执行上下文类型:


  • 全局执行上下文: 任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。
  • 函数执行上下文: 当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。
  • eval函数执行上下文: 执行在eval函数中的代码会有属于它自己的执行上下文。

由于eval函数执行上下文用的不多,所以这里只介绍全局执行上下文和函数执行上下文:


  • 在全局执行上下文中,this 是指向 window 对象的;
  • 在函数执行上下文中,默认情况下调用一个函数,其执行上下文的 this 也是指向 window 的。


(2)执⾏上下⽂栈

我们知道,浏览器中的JavaScript解释器是单线程的,也就是说浏览器同⼀时间只能做⼀个事情。代码中只有⼀个全局执⾏上下⽂和⽆数个函数执⾏上下⽂,这些组成了执⾏上下⽂栈(Execution Stack)。 ⼀个函数的执⾏上下⽂,在函数执⾏完毕后,会被移出执⾏上下⽂栈。看下面的例子:


function c(){
  console.log('ok');
}
function a(){
  function b(){
    c();
  }
  b();
}
a();
复制代码


这段代码的执⾏上下⽂栈是这样的:

网络异常,图片无法展示
|


2. 函数的 this 指向


this 是 JavaScript 的一个关键字,多数情况下 this 指向调用它的对象。

这句话有两个意思,首先 this 指向的应该是一个对象(函数执行上下文对象)。其次,这个对象指向的是调用它的对象,如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)。


其实,this 是在函数被调用时确定的,它的指向取决于函数调用的地方,而不是它被声明的地方(除箭头函数外)。当函数被调用时,会创建一个执行上下文,它包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this 就是这个记录的一个属性,它会在函数执行的过程中被用到。


this 在函数的指向绑定形式有四种:默认绑定、隐式绑定、显式绑定、new绑定


(1)默认绑定(全局环境)

函数在浏览器全局环境中直接使用不带任何修饰的函数引用进行调用,非严格模式下 this 指向 window;在 use strict 指明严格模式的情况下就是 undefined(严格模式不允许 this 指向全局对象)。


注意: 在浏览器环境下,全局对象是window;在Node.js环境下,全局对象是global。

function fn1 () {
    console.log(this)
}
function fn2 () {
    'use strict'
    console.log(this)
}
fn1() // window
fn2() // undefined
复制代码


需要注意一种情况,来看代码:

var num = 1
var foo = {
    num: 10,
    fn: function() {
       console.log(this)
       console.log(this.num)
    }
}
var fn1 = foo.fn
fn1()
复制代码


这里会输出 Window 和 1。这里 this 仍然指向 window。虽然 fn 函数在 foo 对象中作为方法被引用,但是在赋值给 fn1 之后,fn1 的执行仍然是在 window 全局环境中。因此输出 window1,它们相当于:


console.log(window)
console.log(window.num)
复制代码


(2)隐式绑定(上下文对象)

如果函数在某个上下文对象中调用,那么 this 绑定的是那个上下文对象。来看一个例子:


var a = 'hello'
var obj = {
    a: 'world',
    fn: function() {
        console.log(this.a)
    }
}
obj.fn()
复制代码


在上述代码中,最后会输出"world"。这里fn方法是作为对象的属性调用的,此时fn方法执行时,this会指向obj对象。也就是说,此时this指向的是调用这个方法的对象。

那如果嵌套了多层对象,this的指向又是怎样的呢?下面来看一个例子:


const obj1 = {
    text: 1,
    fn: function() {
        return this.text
    }
}
const obj2 = {
    text: 2,
    fn: function() {
        return obj1.fn()
    }
}
const obj3 = {
    text: 3,
    fn: function() {
        var fn = obj1.fn
        return fn()
    }
}
console.log(obj1.fn())
console.log(obj2.fn())
console.log(obj3.fn())
复制代码


对于这段代码,最终的三个输出结果:

  • 第一个 console 输出 1 ,这时 this 指向了调用 fn 方法的对象 obj1,所以会输出obj1中的属性 text 的值 1
  • 第二个 console 输出 1,这里调用了 obj2.fn(),最终还是调用 o1.fn(),因此仍然会输出 1
  • 第二个 console 输出 undefined,在进行 var fn = o1.fn 赋值之后,是直接调用的,因此这里的 this 指向 window,答案是 undefined

根据上面例子可以得出结论:如果嵌套了多个对象,那么指向最后一个调用这个方法的对象。


那如何让 console.log(obj2.fn()) 输出 2 呢?可以这样:

const obj1 = {
    text: 1,
    fn: function() {
        return this.text
    }
}
const obj2 = {
    text: 2,
    fn: o1.fn
}
console.log(obj2.fn())
复制代码


还是上面的结论:this 指向最后调用它的对象,在 fn 执行时,挂到 obj2 对象上即可,就相当于提前进行了赋值操作。


(3)显示绑定(apply、call、bind)

显式绑定是指需要引用一个对象时进行强制绑定调用,显式绑定可以使用apply、call、bind方法来绑定this值,使其指向我们指定的对象。


call、apply 和 bind三个方法都可以改变函数 this 指向,但是 call 和 apply 是直接进行函数调用;bind 不会执行函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this 指向,需要我们手动调用。call 和 apply 的区别: call 方法接受的是参数列表,而 apply 方法接受的是一个参数数组。


这三个方法的使用形式如下:

const target = {}
fn.call(target, 'arg1', 'arg2')
fn.apply(target, ['arg1', 'arg2'])
fn.bind(target, 'arg1', 'arg2')()
复制代码


需要注意,如果把 null 或 undefined 作为 this 的绑定对象传入 call、apply、bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:

var a = 'hello'
function fn() {
    console.log(this.a)
}
fn.call(null)   
复制代码


这里会输出 hello,因为将 null 作为 this 传给了 call 方法,这时 this 会使用默认的绑定规则,this指向了全局对象 window,所以输出 window 中 a 的值 hello。

再来看一个例子:

const foo = {
    name: 'hello',
    logName: function() {
        console.log(this.name)
    }
}
const bar = {
    name: 'world'
}
console.log(foo.logName.call(bar))
复制代码


这里将会输出:world。

那如果对一个函数进行多次 bind,那么上下文会是什么呢?

let a = {}
let fn = function () { 
  console.log(this) 
}
fn.bind().bind(a)() 
复制代码


这里会输出 a吗?可以把上述代码转换成另一种形式:

// fn.bind().bind(a) 等于
let fn2 = function fn1() {
  return function() {
    return fn.apply()
  }.apply(a)
}
fn2()
复制代码


可以发现,不管给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window

let a = { 
  name: 'CUGGZ' 
}
function fn() {
  console.log(this.name)
}
fn.bind(a)() // CUGGZ
复制代码


相关文章
|
7月前
|
JavaScript 前端开发
javascript中的this
javascript中的this
|
7月前
|
JavaScript
JS中改变this指向的六种方法
JS中改变this指向的六种方法
|
6月前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【6月更文挑战第15天】JavaScript的`this`根据调用方式变化:非严格模式下直接调用时指向全局对象(浏览器为window),严格模式下为undefined。作为对象方法时,`this`指对象本身。用`new`调用构造函数时,`this`指新实例。`call`,`apply`,`bind`可显式设定`this`值。箭头函数和绑定方法有助于管理复杂场景中的`this`行为。
61 3
|
5月前
|
JavaScript
js 【详解】函数中的 this 指向
js 【详解】函数中的 this 指向
41 0
|
5月前
|
JavaScript 前端开发
|
7月前
|
JavaScript 前端开发
js中改变this指向、动态指定函数 this 值的方法
js中改变this指向、动态指定函数 this 值的方法
|
6月前
|
JavaScript
js -- 函数总结篇,函数提升、动态参数、剩余参数、箭头函数、this指向......
js -- 函数总结篇,函数提升、动态参数、剩余参数、箭头函数、this指向......
|
6月前
|
JavaScript 前端开发
JS中如何使用this方法
JS中如何使用this方法
21 0
|
7月前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
43 1
|
7月前
|
JavaScript 前端开发
深入探索JavaScript:如何改变this的指向
深入探索JavaScript:如何改变this的指向
53 2