当前位置: 首页 > news >正文

this(执行上下文)

🚩 这个专栏是一个 JS 进阶系列,当前内容为 JS 执行机制,建议按顺序阅读

执行上下文&作用域

词法环境&变量环境

this(上下文对象) 🔹

概述

🌍 前提概要:

在上文 执行上下文&作用域 中,我们已经知道

  • 执行上下文的 thisValuethis 等价
  • 全局执行上下文的 this 指向全局对象

而函数执行上下文中的 thisValue 指向 ,我却一笔带过 ,现在打算放在这篇文章帮它整明白!

💡 我们每次调用函数时,解析器都会将一个 上下文对象 作为 隐含参数 传递进函数。 使用 this 来引用上下文对象,根据函数的 调用方式 不同,this 的值也不同。

函数中的 this

普通函数调用

在普通函数调用中,this 指向全局对象(非严格模式)或 undefined(严格模式)。

🌰举个栗子

let a = {
    b: function () {
        let func = function () {
            console.log(this); //Window
        }
        func(); //普通函数调用
    },

}
a.b(); //打印 Window

方法调用

当函数作为对象的方法调用时,this 指向调用该方法的对象。

🌰举个栗子

const obj = {
    name: 'Alice',
    greet: function() {
        console.log(this.name);
    }
};

obj.greet(); // 输出: Alice

构造函数调用

当函数作为构造函数调用(使用 new 关键字)时,this 指向新创建的对象。

🌰举个栗子

function Person(name) {
    this.name = name;
}

const alice = new Person('Alice');
console.log(alice.name); // 输出: Alice

箭头函数中的 this

箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。这意味着箭头函数中的 this 在定义时就已经确定,不会因为调用方式而改变。

🌰举个栗子

const obj = {
    name: 'Alice',
    greet: function() {
        setTimeout(() => {
            console.log(this.name);
        }, 100);
    }
};

obj.greet(); // 输出: Alice

类中的 this

类本质上是基于构造函数原型继承的语法糖 , 在类的方法中,this 指向类的实例。

🌰举个栗子

class Person {
    constructor(name) {
        this.name = name;
    }

    greet() {
        console.log(this.name);
    }
}

const alice = new Person('Alice');
alice.greet(); // 输出: Alice

使用 callapplybind

call、apply、bind 方法:这三种方法可以明确指定 this 的值 , 即显式设置this

call、apply、bind 原理

call

作用:

call 方法用于调用一个函数,并设置函数执行时的 this 值,同时可以传递参数列表。

语法:

func.call(thisArg, arg1, arg2, ...)

  • thisArg:函数执行时的 this 值。
  • arg1, arg2, ...:传递给函数的参数列表。

原理:

  1. 将函数的 this 绑定到 thisArg
  2. 立即执行函数,并传入参数。

🌰举个例子:

const person1 = {
    name: '张三',
    introduce: function (city, country) {
        console.log(`${this.name} is from ${city}, ${country}`);
    },
};
const person2 = {
    name: '李四',
};
person1.introduce.call(person2, '上海', '中国')

打印:李四 is from 上海, 中国

apply

作用:

apply 方法与 call 类似,用于调用一个函数并设置 this 值,但它的参数是以数组(或类数组对象)的形式传递的。

语法:

func.apply(thisArg, [argsArray])

  • thisArg:函数执行时的 this 值。
  • argsArray:传递给函数的参数数组。

原理:

apply 的原理与 call 几乎相同,唯一的区别是参数传递方式。

🌰仍然是上面那个例子:

参数传递 必须是参数数组

person1.introduce.apply(person2, ['上海', '中国']);

bind

作用:

bind 方法用于创建一个新函数,并将原函数的 this 值绑定到指定的 thisArg。与 callapply 不同,bind 不会立即执行函数,而是返回一个绑定了 this 的新函数。

语法:

func.bind(thisArg, arg1, arg2, ...): newFunc

  • thisArg:新函数执行时的 this 值。
  • arg1, arg2, ...:预先传递给新函数的参数(可选)。
  • return newFunc: 返回一个绑定了 this 的新函数。

原理:

bind 的原理可以理解为:

  1. 返回一个新函数。
    • bind 不会改变原函数,而是返回一个新的函数。
  2. 新函数执行时,this 被绑定到 thisArg
    • 新函数的 this 值会被固定为传入的 第一个参数。例如 f.bind(obj) ,实际上可以理解为 obj.f(),这时,f 函数体内的 this 自然指向的是 obj
  3. 可以预先传递部分参数(柯里化)。
    • 可以在调用新函数时,传入额外的参数,这些参数会在调用时被传递给原函数。
预设参数
案例1:
function multiply(a, b) {
    return a * b;
}
const double = multiply.bind(null, 2); // 固定第一个参数为2
console.log(double(5)); // 输出: 10

在这个例子中:

  • null 是固定的 this 值,因为我们不需要设置 this
  • 2 是预设的第一个参数(对应原函数的 a),而在调用 double(5) 时,5 会作为第二个参数(对应原函数的 b)传递。
  • double 函数实际上是 multiply 函数的一个“封装”,它将第一个参数固定为 2,而 this 的值被设置为 null,这在这里并不重要,因为 multiply 函数并没有使用 this
案例 2
function f(y, z){
	return this.x + y + z;
}
let m = f.bind({x : 1}, 2);
console.log(m(3));
//6

在这个例子中:

  1. 参数{x : 1}
    • 这里bind方法会把它的第一个实参绑定给f函数体内的 this,所以这里的 this即指向{x : 1}对象
  2. **参数 **2
    • 从第二个参数起,会依次传递给原始函数,这里的第二个参数 2,即是 f 函数的 y参数,
  3. **参数 **3
    • 最后调用 m(3)的时候,这里的 3 便是最后一个参数 z 了,所以执行结果为 1 + 2 + 3 = 6

分步处理参数的过程其实是一个典型的 函数柯里化 的过程(Curry)

案例

分析代码:

let a = {
    b: function () {
        let func = function () {
            console.log(this.c);//undefined
        }
        func(); //普通函数调用
    },
    c : 'Hello!'

}
a.b(); // undefined

undefined的 原因:

😃func 函数是在 a.b() 方法内定义的,调用时是作为一个普通函数调用,而不是作为对象的方法调用。这导致 this 的指向变成了全局对象(在浏览器中是 window),而不是对象 a

🤔在学习完所有this指向的场景后,你能想出不同的方法去使得this.c能正常访问吗?

解决方法

赋值

可以通过赋值的方式将 this 赋值给 that

let a = {
    b: function () {
        let self = this //将 this 赋值给 that
        let func = function () {
            console.log(self.c);//修改成self
        }
        func();
    },
    c: 'Hello!'

}
a.b(); //输出: Hello!
使用箭头函数
let a = {
    b: function () {
        let func = () => {
            console.log(this.c);
        }
        func();
    },
    c: 'Hello!'
}
a.b(); // 输出: Hello!
使用call / apply
let a = {
    b: function () {
        let func = function () {
            console.log(this.c);
        }
        func.call(this); //call
    },
    c: 'Hello!'
}
a.b(); // 输出: Hello!
使用 bind方法
//写法一
let a = {
    b: function() {
        let func = function() {
            console.log(this.c);
        }.bind(this); //返回新函数 覆盖 
        func();
    },
    c: 'Hello!'
}
a.b(); // 输出: Hello!

//写法二
let a = {
	b : function(){
		let func = function(){
			console.log(this.c);
		}
		func.bind(this)(); //立即执行
	},
	c : 'Hello!'
}

a.b(); // 输出: Hello!

相关文章:

  • 1 存储过程学习: 使用DMSQL程序的优点
  • 如果vue加载页面的时候,需要加载很多子vue ,能不能先加载一个,让页面处于能用的状态,多余的在后台加载。
  • Java 基础入门代码示例解析
  • 使用Python调用Jenkins Api之获取构建日志使用说明文档
  • SpringBoot 3.0之后为什么移除了spring.factories
  • 好好学Docker:基于Docker buildx构建多平台镜像【转载】
  • Python爬取微博签到数据(2025年3月更)
  • python的文件上传
  • 单片机 - RAM 与内存、ROM 与硬盘 之间的详细对比总结
  • 【MySQL】JDBC —— Java 连接 MySQL
  • Qt 隐式共享
  • Java「Deque」 方法详解:从入门到实战
  • 信息安全和病毒防护——入侵检测技术
  • selenium基本使用(二)九种定位方法
  • 深度优先搜索(DFS)在排列组合问题中的应用详解:C++实现与优化
  • 第一章,网络发展史///OSI七层模型
  • 【开题报告+论文+源码】基于SpringBoot+Vue的酒店餐饮管理系统设计与实现
  • CTF类题目复现总结-[羊城杯 2020]TCP_IP 1
  • 25. 策略模式
  • Java 编译 API(javax.tools 包)的使用方法及关键点总结,适用于在运行时动态编译 Java 代码
  • 特朗普与普京开始电话会谈,稍后将致电泽连斯基
  • 多名幼师殴打女童被行拘后续,盘锦教育局工作人员:该局将专项整治全市幼儿园
  • 马上评|劳动课该如何找回“存在感”
  • AI快速迭代带来知识焦虑,褚君浩院士提出“四维能力模型”
  • IPO周报|本周2只新股申购,比亚迪、上汽“小伙伴”来了
  • 内蒙古赤峰市城建集团董事长孙广通拟任旗县区党委书记