this你了解多少呢

执行上下文

js在执行代码之前,需要经过一系列的"准备",这被称为执行上下文 ,其包含词法环境this 。所有的 js 代码在运行时都是在执行上下文中进行的,每创建一个执行上下文,就会将当前执行上下文放到一个栈顶,这就就是我们常说的执行栈

执行上下文的创建

何时创建执行上下文

JavaScript 中有三种情形会创建新的执行上下文:

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

eval函数执行上下文用的不多,我们只介绍全局执行上下文和函数执行上下文:

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

2. 函数的 this 指向

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

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

其实,this 是在函数被调用时 确定的,它的指向取决于函数调用的地方,而不是它被声明的地 方(除箭头函数外)。

当函数被调用时,会创建一个执行上下文,它包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息,this 就是这个记录的一个属性,它会在函数执行的过程中被用到。

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

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

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

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

javascript 复制代码
function func1 () {
    console.log(this)
}
function func2 () {
    'use strict'
    console.log(this)
}
func1() // window
func2() // undefined

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

javascript 复制代码
var num = 1
var foo = {
    num: 10,
    func: function() {
       console.log(this) // window
       console.log(this.num) // 1
    }
}
var func1 = foo.fn
func1()

这里会输出 Window 和 1。

这里 this 仍然指向 window。虽然 func 函数在 foo 对象中作为方法被引用,但是在赋值给 func1 之后,func1 的执行仍然是在 window 全局环境中。因此输出 window1。*因为最后是window调用的func1,所以this指向window *

它们相当于:

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

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

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

javascript 复制代码
var a = 'hello'

var obj = {
    a: 'world',
    fn: function() {
        console.log(this.a)
    }
}

obj.fn()

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

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

javascript 复制代码
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 呢?可以这样:

javascript 复制代码
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 方法接受的是一个参数数组。

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

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

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

javascript 复制代码
var a = 'hello'

function fn() {
    console.log(this.a)
}

fn.call(null)   

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

再来看一个例子:

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

这里将会输出:world。

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

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

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

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

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

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

(4)new绑定(构造函数)

函数作为构造函数使用 new 调用时, this 绑定的是新创建的构造函数的实例:

javascript 复制代码
function Person(name,age){
  this.name = name;
  this.age = age;
  this.say = function(){
    console.log(this.name + ":" + this.age);
  }
}
var person = new Person("kobe",24);
console.log(person.name); // kobe
console.log(person.age);  // 24
person.say(); // kobe:24

可以看到,在上面代码中,this 就指向了构造函数 Person 的新对象person,所以使用 this 可以获取到 person 对象的属性和方法。

实际上,在使用 new 调用构造函数时,会执行以下操作:

  1. 创建一个新对象;
  2. 构造函数的 prototype 被赋值给这个新对象的 proto
  3. 将新对象赋给当前的 this;
  4. 执行构造函数;

如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象,如果返回的不是对象将被忽略。

总结:

  1. this 是在函数被调用时确定的,它的指向取决于函数调用的地方,而不是它被声明的地方(除箭头函数外)
  2. this 在函数的指向绑定形式有四种:默认绑定、隐式绑定、显式绑定、new绑定
  3. this优先级:new绑定>显示绑定>隐式绑定>默认绑定
相关推荐
李明卫杭州4 分钟前
CSS BFC 完全指南:从原理到实战,彻底搞懂这个"结界"
前端
裕波5 分钟前
AI 正在重写应用开发。Vue 与 Vite,给出新的答案。
javascript·vue.js
Momo__5 分钟前
MDN MCP Server——Mozilla 把 Web 文档接进 AI Agent,从此 LLM 不再瞎编 API
前端·ai编程·mcp
妙码生花5 分钟前
现代前端的极致性能 icon 加载方案(死磕成功版)
前端·vue.js·typescript
掘金者阿豪1 小时前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端2 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid3 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端