一网打尽 this,对执行上下文说 Yes

news2024/11/20 9:43:59

文章目录

    • 全局环境下的 this
    • 上下文对象调用中的 this
    • bind/call/apply 改变 this 指向
    • 构造函数和 this
    • 箭头函数中的 this 指向
    • this 优先级相关
    • 实现一个bind 函数

在这里插入图片描述

✍创作者:全栈弄潮儿
🏡 个人主页: 全栈弄潮儿的个人主页
🏙️ 个人社区,欢迎你的加入:全栈弄潮儿的个人社区
📙 专栏地址,欢迎订阅:前端架构师之路

this 到底指向谁?

全局环境下的this、箭头函数的 this、构造函数的 this、this 的显隐性和优先级,等等,真实环境多样。

一句话总结:

谁调用它,this 就指向谁。

也就是说,this 的指向是在调用时确定的。这么说没有太大的问题,可是并不全面。

事实上,调用函数会创建新的属于函数自身的执行上下文。执行上下文的调用创建阶段会决定 this 的指向。到此,我们可以得出的一个结论:

this 的指向,是在调用函数时根据执行上下文所动态确定的。

  • 在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下 this 绑定到 undefined,否则绑定到全局对象 window/global;
  • 一般构造函数 new 调用,绑定到新创建的对象上;
  • 一般由 call/apply/bind 方法显式调用,绑定到指定参数的对象上;
  • 一般由上下文对象调用,绑定在该对象上;
  • 箭头函数中,根据外层上下文绑定的 this 决定 this 指向。

全局环境下的 this

这种情况相对简单直接,函数在浏览器全局环境中被简单调用,非严格模式下 this 指向 window;在 use strict 指明严格模式的情况下就是 undefined。我们来看例题,请描述打印结果:

function f1 () {
    console.log(this)
}
function f2 () {
    'use strict'
    console.log(this)
}
f1() // window
f2() // undefined

上一题比较基础,再看一道例子:

const foo = {
    bar: 10,
    fn: function() {
       console.log(this)
       console.log(this.bar)
    }
}
var fn1 = foo.fn
fn1()

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

console.log(window)
console.log(window.bar)

还是上面这道例题,如果调用改变为:

const foo = {
    bar: 10,
    fn: function() {
       console.log(this)
       console.log(this.bar)
    }
}
foo.fn()

将会输出:
{bar: 10, fn: ƒ}
10
因为这个时候 this 指向的是最后调用它的对象,在 foo.fn() 语句中 this 指向 foo 对象。请记住:在执行函数时,如果函数中的 this 是被上一级的对象所调用,那么 this 指向的就是上一级的对象;否则指向全局环境。

上下文对象调用中的 this

如上结论,面对下题时我们便不再困惑:

const student = {
    name: 'Lucas',
    fn: function() {
        return this
    }
}
console.log(student.fn() === student)

最终结果将会返回 true。
当存在更复杂的调用关系时:

const person = {
    name: 'Lucas',
    brother: {
        name: 'Mike',
        fn: function() {
            return this.name
        }
    }
}
console.log(person.brother.fn())

在这种嵌套的关系中,this 指向 最后 调用它的对象,因此输出将会是:Mike。再看一道更高阶的题目:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: function() {
        return o1.fn()
    }
}
const o3 = {
    text: 'o3',
    fn: function() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())

答案是:o1、o1、undefined,你答对了吗?
我们来一一分析。
第一个 console 最简单,o1 没有问题。难点在第二个和第三个上面,关键还是看调用 this 的那个函数。
第二个 console 的 o2.fn(),最终还是调用 o1.fn(),因此答案仍然是 o1。
最后一个,在进行 var fn = o1.fn 赋值之后,是“裸奔”调用,因此这里的 this 指向 window,答案当然是 undefined。

如果我们需要让:
console.log(o2.fn())
输出 o2,该怎么做?

一般可能会想到使用 bind/call/apply 来对 this 的指向进行干预,这确实是一种思路。如果不能使用bind/call/apply,有别的方法吗?

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: o1.fn
}

console.log(o2.fn())

还是应用那个重要的结论:this 指向 最后 调用它的对象,在 fn 执行时,挂到 o2 对象上即可,我们提前进行了赋值操作。

bind/call/apply 改变 this 指向

bind/call/apply,他们都是用来改变相关函数 this 指向的,但是 call/apply 是直接进行相关函数调用;bind 不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this 指向,开发者需要手动调用即可。

const target = {}
fn.call(target, ‘arg1’, ‘arg2’)
相当于:
const target = {}
fn.apply(target, [‘arg1’, ‘arg2’])
相当于:
const target = {}
fn.bind(target, ‘arg1’, ‘arg2’)()

我们来看一道例题:

const foo = {
    name: 'lucas',
    logName: function() {
        console.log(this.name)
    }
}
const bar = {
    name: 'mike'
}
console.log(foo.logName.call(bar))

将会输出 mike。

构造函数和 this

function Foo() {
    this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)

答案将会输出 Lucas。

new 操作符调用构造函数,具体做了什么?

  • 创建一个新的对象;
  • 将构造函数的 this 指向这个新对象;
  • 为这个对象添加属性、方法等;
  • 最终返回新对象。

以上过程,也可以用代码表述:

var obj  = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)

如果在构造函数中出现了显式 return 的情况,那么需要注意分为两种场景:

function Foo(){
    this.user = "Lucas"
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.user)

将会输出 undefined,此时 instance 是返回的空对象 o。

function Foo(){
    this.user = "Lucas"
    return 1
}
const instance = new Foo()
console.log(instance.user)

将会输出 Lucas,也就是说此时 instance 是返回的目标对象实例 this。

结论: 如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;如果返回的不是一个对象,那么 this 仍然指向实例。

箭头函数中的 this 指向

箭头函数使用 this 不适用以上标准规则,而是根据外层(函数或者全局)上下文来决定。

const foo = {  
    fn: function () {  
        setTimeout(function() {  
            console.log(this)
        })
    }  
}  
console.log(foo.fn())

这道题中,this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。如果需要 this 指向 foo 这个 object 对象,可以巧用箭头函数解决:

const foo = {  
    fn: function () {  
        setTimeout(() => {  
            console.log(this)
        })
    }  
} 
console.log(foo.fn())

// {fn: ƒ}

单纯箭头函数中的 this 非常简单, 但是综合所有情况,结合this 的优先级考察,这时候 this 指向并不好确定。

this 优先级相关

我们常常把通过 call、apply、bind、new 对 this 绑定的情况称为显式绑定;根据调用关系确定的 this 指向称为隐式绑定。

那么显式绑定和隐式绑定谁的优先级更高呢?

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

const obj1 = {
    a: 1,
    foo: foo
}

const obj2 = {
    a: 2,
    foo: foo
}

obj1.foo.call(obj2)
obj2.foo.call(obj1)

输出分别为 2、1,也就是说 call、apply 的显式绑定一般来说优先级更高

function foo (a) {
    this.a = a
}

const obj1 = {}

var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)

上述代码通过 bind,将 bar 函数中的 this 绑定为 obj1 对象。执行 bar(2) 后,obj1.a 值为 2。即经过 bar(2) 执行后,obj1 对象为:{a: 2}。

当再使用 bar 作为构造函数时:

var baz = new bar(3)
console.log(baz.a)

将会输出 3。bar 函数本身是通过 bind 方法构造的函数,其内部已经对将 this 绑定为 obj1,它再作为构造函数,通过 new 调用时,返回的实例已经与 obj1 解绑。 也就是说:
new 绑定修改了 bind 绑定中的 this,因此 new 绑定的优先级比显式 bind 绑定更高。

我们再看:

function foo() {
    return a => {
        console.log(this.a)
    };
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

const bar = foo.call(obj1)
console.log(bar.call(obj2))

将会输出 2。由于 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。

如果将 foo 完全写成箭头函数的形式:

var a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

将会输出 123。

如果仅仅将上述代码的第一处变量 a 的赋值改为:

const a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

答案将会输出为 undefined,原因是因为使用 const 声明的变量不会挂载到 window 全局对象当中。因此 this 指向 window 时,自然也找不到 a 变量了。

实现一个bind 函数

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function bound () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(context, finalArgs);
    }
}

这样的实现基本满足需求。但是, 如果 bind 返回的函数如果作为构造函数,搭配 new 关键字出现的话,我们的绑定 this 就需要“被忽略”。

为了实现这样的规则,就应该需要考虑如何区分这两种调用方式。 具体来讲bound 函数中就要进行 this instanceof 的判断。

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);

    // 返回一个新的函数
    var bound = function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(this instanceof bound ? this : context, finalArgs);
    };

    // 设置原始函数的原型为新函数的原型
    bound.prototype = Object.create(me.prototype);

    return bound;
};

另外一个细节是,函数具有 length 属性,表示形参的个数。上述实现方式形参的个数显然会失真。我们的实现就需要对 length 属性进行还原。

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);

    // 返回一个新的函数
    var bound = function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(this instanceof bound ? this : context, finalArgs);
    };

    // 设置原始函数的原型为新函数的原型
    bound.prototype = Object.create(me.prototype);

    // 还原 length 属性
    bound.length = Math.max(me.length - args.length, 0);

    return bound;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1389419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解读加密生态的未来:通过用户钱包画像分析预测市场趋势

作者:stellafootprint.network 加密生态领域变化莫测,但深入分析区块链数据,我们可以发现未来走势的线索。如果通过用户钱包画像分析,能够提前捕捉到这些信号,投资者是否能够抢占先机,领先市场&#xff1f…

Linux常用命令之vim、cat、grep

vim:编辑文件内容 cat&#xff1a;查看文件内容 grep:用于查找文件里符合条件的字符串 grep [-abcEFGhHilLnqrsvVwxy][-A<显示行数>][-B<显示列数>][-C<显示列数>][-d<进行动作>][-e<范本样式>][-f<范本文件>][--help][范本样式][文…

Virtual Box安装Kali Linux 虚拟机

一、Kali Linux —— 安装和配置 Kali Linux 是道德黑客最好的安全软件包之一&#xff0c;包含一组按类别划分的工具。它是一个开源的系统&#xff0c;其官方网页是https://www.kali.org。 一般来说&#xff0c;Kali Linux 可以作为操作系统安装在机器上。Kali Linux提供了更…

接口自动化神器 apin【 快速入门篇】

关于自动化测试&#xff0c;这些年经历了太多的坑&#xff0c;有被动的坑&#xff0c;也有自己主动挖的坑&#xff0c;在这里做了一些总结。 一、apin 介绍及安装 1、什么是 apin apin是一个无需写代码&#xff0c;就可以进行接口自动化测试的框架&#xff0c;只需要通过jso…

02章【JAVA编程基础】

变量与标识符 变量 数学名词&#xff1a;变数或变量&#xff0c;是指没有固定的值&#xff0c;可以改变的数。变量以非数字的符号来表达&#xff0c;一般用拉丁字母。变量是常数的相反。变量的用处在于能一般化描述指令的方式。计算机解释&#xff1a;变量就是系统为程序分配…

【Spring 篇】深入探索:Spring集成Web环境的奇妙世界

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇有关Spring集成Web环境的博客。如果你曾对如何在Spring中构建强大的Web应用程序感到好奇&#xff0c;那么这里将为你揭示Web开发的神秘面纱。我们将用情感丰富、语句通顺的文字&#xff0c;以小白友好的方式&#xff0c;一探…

DBeaver SQL脚本执行配置

对生产环境数据迁移到测试环境时&#xff0c;单条执行可能时间过长、或者内存装不下。最快的方法是执行SQL脚本。 本地数据库客户端&#xff1a;DBeaver 该客户端是不自带脚本执行客户端的&#xff0c;如下图&#xff1a; mysql官网下载workbench&#xff0c;可以连通客户端执…

CDMP认证与CDGA/CDGP的区别有哪些?

&#x1f451;CDMP是DAMA国际组织的全英文考试 &#x1f308;CDGA和CDGP是DAMA中国主导&#xff0c;考试为中文考试。需要在规定的时间内报名后&#xff0c;参加线下笔试考试。 &#x1f44d;CDGA、CDGP证书和英文版的CDMP证书都是国际通用的&#xff0c;是数据管理领域专业的职…

嵌入式工作岗位未来会不会越来越少?

今日话题&#xff0c;嵌入式工作岗位未来会不会越来越少&#xff1f;未来的嵌入式岗位机会将会逐渐增多&#xff0c;因为嵌入式技术是万物互联的基础&#xff0c;从智能手表到智能汽车&#xff0c;嵌入式都扮演着关键角色。虽然相比计算机科学CS&#xff0c;嵌入式领域的天花板…

Klocwork—符合功能安全要求的自动化静态测试工具

产品概述 Klocwork是Perforce公司产品&#xff0c;主要用于C、C、C#、Java、 python和Kotlin代码的自动化静态分析工作&#xff0c;可以提供编码规则检查、代码质量度量、测试结果管理等功能。Klocwork可以扩展到大多数规模的项目&#xff0c;与大型复杂环境、各种开发工具集成…

ACM:每日学习 状压dp

状压dp&#xff1a; 状压dp是对一般dp的改进&#xff1a; //对于判断多种物品的取法&#xff0c;开多维数组比较麻烦&#xff0c;也不好开&#xff0c;使用二进制来表示物品的取与否。 //使用二进制的话&#xff0c;位运算就更能省时间了&#xff0c;而且更会节省空空间&…

3D Guassians Splatting相关解读

从已有的点云模型出发&#xff0c;以每个点为中心&#xff0c;建立可学习的高斯表达&#xff0c;用Splatting即抛雪球的方法进行渲染&#xff0c;实现高分辨率的实时渲染。 1、主要思想 1.引入了一种各向异性&#xff08;anisotropic&#xff09;的3D高斯分布作为高质量、非结…

实战 php 使用 wkhtmltopdf 生成pdf的全过程

公司里边有生成pdf报告的业务需求,之前有过尝试用tcpdf,直接生成的pdf的过程,但是pdf报告的内容数据,根据不同内容的变化,都是各种各样的bug,一直处理修修补补的状态,让后台开发人员很是头疼. 经过思索和甄选,总结出我们的业务中是由于样式不可控导致的,当时从逻辑上就思考到用…

CHS_06.2.1.6_2+线程的实现方式和多线程模型

CHS_06.2.1.6_2线程的实现方式和多线程模型 知识总览线程的实现方式用户级线程&#xff08;User-Level Thread, ULT&#xff09;内核级线程 多线程模型一对一模型多对一多对多模型 知识回顾 在上个小节中 我们学习了线程相关的一些基本概念 基础的知识 那这个小节中 我们回来看…

人脸识别打卡系统的设计与应用!人脸识别打卡系统有什么用?

教育行业的快速日新月异&#xff0c;对信息化建设的要求也越来越高。学校物业管理成为保障学校正常运行的重要环节。然而&#xff0c;在实践中&#xff0c;由于服务人员数量不足、管理不规范等问题&#xff0c;服务质量往往难以保证物业对学校师生的服务质量。为了解决这一问题…

如果你正在学自动化测试,那么请你仔细看完这篇文章

接触了不少同行&#xff0c;由于他们之前一直做手工测试&#xff0c;现在很迫切希望做自动化测试&#xff0c;其中不乏工作5年以上的人。 本人从事软件自动化测试已经近5年&#xff0c;从server端到web端&#xff0c;从API到mobile&#xff0c;切身体会到自动化带来的好处与痛楚…

高级 Python 面试问题与解答

文章目录 专栏导读1.什么是PIP&#xff1f;2.什么是 zip 函数&#xff1f;3.Python 中的 __init __ () 是什么&#xff1f;4.Python 中的访问说明符是什么&#xff1f;5.Python 中的单元测试是什么&#xff1f;6.Python全局解释器锁&#xff08;GIL&#xff09;&#xff1f;7.P…

linux yum仓库

yum是基于rpm包构建的软件更新机制&#xff0c;能够自动解决软件包之间的依赖关系。 常用命令 查询 yum list [软件名] 显示可用的安装包&#xff0c;如果不加软件名则显示所有的可用包 yum info [软件名] 显示安装包的详细信息 如果不加软件名是显示所有包…

CF1178F2 Long Colorful Strip 题解 搜索

Long Colorful Strip 传送门 题面翻译 题目描述 这是 F 题的第二个子任务。F1 和 F2 的区别仅在对于 m m m 和时间的限制上 有 n 1 n1 n1 种颜色标号从 0 0 0 到 n n n&#xff0c;我们有一条全部染成颜色 0 0 0 的长为 m m m 的纸带。 Alice 拿着刷子通过以下的过…

MybatisPlus框架入门级理解

MybatisPlus 快速入门入门案例常见注解常用配置 核心功能条件构造器自定义SQLService接口 快速入门 入门案例 使用MybatisPlus的基本步骤&#xff1a; 1.引入MybatisPlus的起步依赖 MybatisPlus官方提供了starter&#xff0c;其中集成了Mybatis和MybatisPlus的所有功能&#…