JavaScript基础6之执行上下文、作用域链、函数创建、函数激活、checkScope的执行过程、闭包、this

JavaScript基础

  • 执行上下文
    • 执行上下文中的属性
      • 变量对象
        • 全局上下文的变量对象
        • 函数上下文
          • 执行过程
            • 进入执行上下文
            • 代码执行
            • 思考题
      • 作用域链
      • 函数创建
      • 函数激活
      • checkScope的执行过程
      • 总结
  • 闭包
    • 分析闭包
  • this

执行上下文

执行上下文中的属性

每一个执行上下文都有三个核心属性

  1. 变量对象(Variable object,VO)
  2. 作用域链(Scope chain)
  3. this

变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量函数声明

全局上下文的变量对象
  1. 全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。
  2. 在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。
  3. 例如,当JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。

简单来讲:

  1. 可以通过 this 引用,在客户端 JavaScript 中,全局对象就是 Window 对象。
console.log(this);
  1. 全局对象是由 Object 构造函数实例化的一个对象。
console.log(this instanceof Object);
  1. 预定义的属性是否可用
console.log(Math.random());
console.log(this.Math.random());
  1. 作为全局变量的宿主
var a = 1;
console.log(this.a);
  1. 客户端 JavaScript 中,全局对象有 window 属性指向自身
var a = 1;
console.log(window.a);

this.window.b = 2;
console.log(this.b);

综上,对JS而言,全局上下文中的变量对象就是全局对象。

函数上下文

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。

活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

执行过程

执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:

  1. 进入执行上下文;
  2. 代码执行;
进入执行上下文

当进入执行上下文时,这时候还没有执行代码,
变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)
    (1)由名称和对应值组成的一个变量对象的属性被创建;
    (2)没有实参,属性值设为 undefined;
  2. 函数声明
    (1)由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建;
    (2)如果变量对象已经存在相同名称的属性,则完全替换这个属性;
  3. 变量声明
    (1)由名称和对应值(undefined)组成一个变量对象的属性被创建;
    (2)如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性;
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

在进入执行上下文后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
还是上面的例子,当代码执行完后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to 函数表达式(FunctionExpression "d"}

到这里变量对象的创建过程就介绍完了,让我们简洁的总结我们上述所说:

  1. 全局上下文的变量对象初始化是全局对象;
  2. 函数上下文的变量对象初始化只包括 Arguments 对象;
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
  4. 在代码执行阶段,会再次修改变量对象的属性值;
思考题
  • 示例一
function foo() {
    console.log(a);
    a = 1;
}

foo(); // ???

请添加图片描述

function bar() {
    a = 1;
    console.log(a);
}
bar(); // ???

请添加图片描述
这是因为函数中的 “a” 并没有通过 var 关键字声明,所以不会被存放在 AO 中。
第一段执行 console 的时候, AO 的值是:

AO = {
    arguments: {
        length: 0
    }
}

没有 a 的值,然后就会到全局去找,全局也没有,所以会报错。
当第二段执行 console 的时候,全局对象已经被赋予了 a 属性,这时候就可以从全局找到 a 的值,所以会打印 1。

  • 示例二
console.log(foo);

function foo(){
    console.log("foo");
}

var foo = 1;

请添加图片描述
会打印函数,而不是 undefined 。
这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

函数创建

上文的词法作用域与动态作用域中讲到,函数的作用域在函数定义的时候就决定了。

这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

  • 举个例子:
 
function foo() {
    function bar() {
        ...
    }
}

函数创建时,各自的[[scope]]为:


foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

函数执行时的[[scope]]为:

foo.[[scope]] = [
  fooContext.AO,
  globalContext.VO
];

bar.[[scope]] = [
	barContext.AO,
    fooContext.AO,
    globalContext.VO
];
  • 示例二:
var scope = "global scope";
function checkscope(){
	var scope2 ='local scope';
	return scope2;
}
checkscope();

函数创建时候:

checkScope.[[scope]]=[
	globalContext.VO
]

函数执行时候:

checkScope.[[scope]]=[
	checkScope.AO,
	globalContext.VO
]

函数激活

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
这时候执行上下文的作用域链,我们命名为 Scope:

Scope = [AO].concat([[Scope]]);

至此,作用域链创建完毕。

checkScope的执行过程

[[scope]]父级作用域链的创建(对应着上述函数创建)->自己作用域链的执行(对应着上述函数执行)->变量对象(AO)的准备->变量对象(AO)执行完后

var scope = "global scope";
function checkscope(){
	var scope2 ='local scope';
	return scope2;
}
checkscope();

上述这段代码的情况:

第一:

checkScopeContext = {
    Scope: [
        globalContext.VO
    ]
}

第二:

checkScopeContext = {
    Scope: [
    	checkScope.AO,
        globalContext.VO
    ]
}

第三:

checkScopeContext = {
 	AO: {
        arguments: {
            length: 0
        },
        scope2: undefined,
    },
    Scope: [
    	checkScope.AO,
        globalContext.VO
    ]
}

第四:

checkScopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: local scope,
    },
    Scope: [
        checkScope.AO,
        globalContext.VO
    ]
}

总结

结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程

var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

执行过程如下:

  1. checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
    globalContext.VO
];
  1. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
    checkscopeContext,
    globalContext
];
  1. checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
    Scope: checkscope.[[scope]],
}
  1. 第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    }Scope: checkscope.[[scope]],
}
  1. 第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}
  1. 准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}
  1. 查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
  globalContext
];

闭包

  1. 闭包是指那些能够访问自由变量的函数。
  2. 自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
  3. 闭包由两部分组成,闭包 = 函数 + 函数能够访问的自由变量
var a = 1;

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

foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。
从技术的角度讲,所有的JavaScript函数都是闭包。
但是,这是理论上的闭包,其实还有一个实践角度上的闭包。

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域;
  2. 从实践角度:以下函数才算是闭包:
  3. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
  4. 在代码中引用了自由变量;

分析闭包

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();
  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈;
  2. 全局执行上下文初始化;
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈;
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this等;
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出;
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈;
  7. f 执行上下文初始化,创建变量对象、作用域链、this等;
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出;
globalContext = {
    VO: {
        scope: global scope,
        checkscope: reference to function checkscope,
        foo: 函数执行,
    },
    scope:[globalContext.VO]
}

checkScopeContext = {
    A0: {
        scope: local scope,
        f: reference to f,
    },
    scope: I[
        checkScopeContext.A0,
            globalContext.VO
        ]
}

fContext = {
    AO: {
        arguments:{
            length:0
        }
    },
    scope:[
        fContext.AO,
        checkScopeContext.A0,
        globalContext.VO
        ]
}

于是:
当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?
当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

所以,让我们再看一遍实践角度上闭包的定义:

  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
  2. 在代码中引用了自由变量;

this

this在没有外力干预的情况下,始终指向调用他的地方

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

haproxy-高性能负载均衡反向代理服务

目录 一、HAProxy(High Availability Proxy)概述 1、HAProxy的概念 2、HAProxy的主要特性 3、HAProxy的优缺点 4、Haproxy负载均衡策略 5、LVS、nginx、HAProxy的区别 二、安装HAProxy 1、yum安装 2、第三方rpm包安装 3、编译安装 3.1 解决 l…

【AI视频教程】只需5步,AI作出鸡你太美视频

1.视频效果 2.准备工作 制作视频效果,需要准备下面3个条件: 准备stable diffusion的环境剪辑一段【鸡你太美】原版视频stable diffusion安装sd-webui-IS-NET-pro插件 2.1部署stable diffusion环境 这里还是建议大家用云平台部署stable diffusion&am…

一个测开人员的大厂面试总结

前言 其实我已经入职有好一段时间了, 这里决定总结一下一些面试经验。 我呢,最终还是决定要离开服务了 5 年多的公司。 而这次跳槽历经 3 个月,前后聊了 10 家公司,进行了将近 40 场面试, 基本都是 41 的流程 (技术面 HR 面)&…

计算机网络-第5章 运输层(2)

5.6 TCP可靠传输实现 以字节为单位的滑动窗口。 发送窗口已满,停止发送。 发送和接收的数据都来自缓存。 超时重传时间RTO选择:自适应算法, 选择确认SACK:只传送缺少的数据。大多数实现还是重传所有未被确认的数据块。 5.7 TCP的…

小程序学习 1

pages/goods/search/home.wxml首页功能设定 1. loading入场 2. 下拉刷新 3. 搜索栏 4. 分类切换 5. 商品列表 6. 规格弹层 7. 加载更多 <view style"text-align: center; color: #b9b9b9" wx:if"{{pageLoading}}"><t-loading theme"circula…

网络安全防御保护 Day7

1.因为FW1和FW2已处于双机热备状态&#xff0c;所以只需要对主设备进行配置即可。进入FW1的配置界面&#xff0c;选择“网络”界面&#xff0c;点击“IPsec”&#xff0c;进行IPsec通道的基本配置&#xff0c;这里选择的是“电信”链路。 2.完成上述配置后&#xff0c;进行待加…

面试官:线程调用2次start会怎样?我支支吾吾没答上来

写在开头 在写完上一篇文章《Java面试必考题之线程的生命周期&#xff0c;结合源码&#xff0c;透彻讲解!》后&#xff0c;本以为这个小知识点就总结完了。 但刚刚吃晚饭时&#xff0c;突然想到了多年前自己面试时的亲身经历&#xff0c;决定再回来补充一个小知识点&#xff…

【DPDK】基于dpdk实现用户态UDP网络协议栈

文章目录 一.背景及导言二.协议栈架构设计1. 数据包接收和发送引擎2. 协议解析3. 数据包处理逻辑 三.网络函数编写1.socket2.bind3.recvfrom4.sendto5.close 四.总结 一.背景及导言 在当今数字化的世界中&#xff0c;网络通信的高性能和低延迟对于许多应用至关重要。而用户态网…

并发通信(网络进程线程)

如果为每个客户端创建一个进程&#xff08;或线程&#xff09;&#xff0c;因为linux系统文件标识符最多1024位&#xff0c;是有限的。 所以使用IO复用技术&#xff0c;提高并发程度。 阻塞与非阻塞 阻塞式复用 非阻塞复用 信号驱动IO 在属主进程&#xff08;线程中声明&…

4、Generator、class类、继承、Set、Map、Promise

一、生成器函数Generator 1、声明generator函数 function* 函数名() { }调用生成器函数 需要next()返回一个generator对象&#xff0c;对象的原型上有一个next(),调用返回对象{value:yield后面的值,done} function* fn() {console.log("我是生成器函数") } let it…

JAVA开发常见小问题整合

文章目录 1&#xff1a;身份证工具类相关方法1.1 身份证脱敏处理 2&#xff1a;字符串补零处理(此处是JAVA类的方法&#xff0c;并无引用StrUtil)3&#xff1a;springboot前后端分离&#xff0c;后端返回json字符串带斜杠问题处理4&#xff1a;WebUploader 文件上传组件 -编辑回…

java基本认识?java跨平台原理?jdk、jre、jvm的联系?

1、java基本认识 1.1 java语言 语言&#xff1a;人与人交流沟通的方式。比如&#xff0c;你好、hello等。 计算机语言&#xff1a;人与计算机之间进行信息交流的一种特殊方式。比如&#xff0c;Java语言、C语言、C等。 1.2 java的来源 Java 是由 Sun Microsystems 公司于 …

如何正确选择国外服务器的带宽和线路呢?

国外大带宽服务器是一种提供高带宽、高速网络连接和良好稳定性的服务器&#xff0c;但在中国使用这类服务器可能涉及到违反法律法规的风险。因此我无法为你提供相关帮助。接下来和源库一起了解如何正确选择国外服务器的带宽和线路呢? 考虑目标用户的地理位置。如果目标用户主要…

计算机网络-第5章 运输层(1)

主要内容&#xff1a;进程之间的通信与端口、UDP协议、TCP协议、可靠传输原理&#xff08;停止等待协议、ARQ协议&#xff09;、TCP报文首部、TCP三大题&#xff1a;滑动窗口、流量控制、拥塞控制机制 5.1 运输层协议概述 运输层向它上面的应用层提供通信服务&#xff0c;真正…

window Zookeeper 启动;

文章目录 前言一、Zookeeper 介绍&#xff1a;二、window 使用&#xff1a;2.1 下载&#xff1a;2.2 启动2.3 连接&#xff1a; 总结 前言 本文对window Zookeeper zk 启动 进行介绍&#xff1b; 一、Zookeeper 介绍&#xff1a; ZooKeeper 是一个开源的分布式协调服务&#…

辽宁博学优晨教育:视频剪辑培训的安全正规之路

在当今数字化时代&#xff0c;视频剪辑已成为一项炙手可热的技能。为满足广大学习者的需求&#xff0c;辽宁博学优晨教育推出了一系列专业的视频剪辑培训课程。本文将重点介绍辽宁博学优晨教育的视频剪辑培训如何在保障学员安全和学习效果方面做出了卓越的努力。 一、正规资质&…

PID控制器组(完整SCL代码)

PID控制器组不是什么新概念,是在PID控制器的基础上,利用面向对象的思想对对象进行封装 批量实例化。 1、增量式PID https://rxxw-control.blog.csdn.net/article/details/124363197https://rxxw-control.blog.csdn.net/article/details/1243631972、完全增量式PID https:/…

5款好用的AI办公软件,一键轻松制作PPT、视频,提升工作效率!

众所周知&#xff0c;AI 人工智能技术已渗透到生活的方方面面&#xff0c;无论是很多人早已用上的智能音箱、语音助手&#xff0c;还是新近诞生的各种 AI 软件工具&#xff0c;背后都离不开 AI 人工智能技术的加持。 对于各类新生的 AI 软件工具&#xff0c;人们很容易「选边站…

free pascal 调用 C#程序读 Freeplane.mm文件,生成测试用例.csv文件

C# 请参阅&#xff1a;C# 用 System.Xml 读 Freeplane.mm文件&#xff0c;生成测试用例.csv文件 Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#x…

Java基于SpringBoot+Vue的人事管理系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…