JavaScript进阶2之执行上下文、作用域链、函数创建、函数激活、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在没有外力干预的情况下,始终指向调用他的地方

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

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

相关文章

【趣味项目】一键生成LICENSE

【趣味项目】一键生成LICENSE 项目地址:GitHub(最新版本) | GitCode(旧版本) 项目介绍 一款用于自动生成开源项目协议的工具,可以通过 npm 进行安装后在命令行使用,非常方便 使用方式 npm install xxhls/get-license -gget-license --l…

python的函数与类的定义

目录 1.函数 1.函数的定义 2.输入参数与输出参数的类型 3.输入和输出多个参数 1.普通参数 2.含有任意数量的参数 3.关键字参数 4.普通参数与多个参数的结合 2.类 1.类的定义 2.类的实例化 3.继承 1.函数 1.函数的定义 def 函数名(输入参数): 文档字符串 函数体 …

第七届国际通信与网络工程会议(ICCNE 2024)即将召开!

2024年第七届国际通信与网络工程会议(ICCNE 2024)将于7月26-28日在越南岘港召开。本次会议由维新大学主办,岘港大学、胡志明市科技大学协办。ICCNE 2024旨在为来自行业和学术界的研究人员、从业者和专业人士提供论坛,分享其最新研…

基于boost库的搜索引擎项目

文章目录 一、项目背景二、什么样的搜索引擎三、搜索引擎的宏观图原理四、Parse模块4.1下载boost库源代码4.2提取boost库中以.html为结尾的文件4.2.1 boost库的简单使用 4.3数据清洗(去标签化)4.3.1数据清洗的具体实现 4.4将清洗后的数据写入到raw.txt文件中 五、正排索引 vs 倒…

C++面试问题收集

0 持续更新中 目录 0 持续更新中 1 C语言相关 1.1 malloc/free和new/delete区别 1.2 内存泄漏 1.3 堆区和栈区的区别 1.4 宏定义和const的区别 1.5 多态 1.6 类中的静态成员变量 2 操作系统相关 2.1 进程和(用户)线程的区别 2.2 系统调用 2.3…

学习SSM的记录(七)-- SSM整合

目录 SSM整合理解 什么是SSM整合 SSM整合核心问题 1.SSM整合需要几个ioc容器 2.每一个ioc容器对应哪些类型组件 ​编辑 3.ioc容器之间关系和调用方向 4.具体多少配置类以及对应容器关系 5.ioc初始化方式和配置位置 ​编辑 SSM整合配置实战 1.数据库准备 2.创建项目…

十分钟教你有效运用百度检索式

十分钟教你有效运用百度检索式 ​ 百度作为国内最大的搜索引擎网站,虽然很多人都喷它的搜索页面广告太多,但是对于不会科学上网的同学来说,似乎除了继续用百度以外也没有更好的选择。 ​ 其实搜索引擎一般都会内涵一些高级的搜索技巧&#…

中间件漏洞(redis)

目录 1.Redis服务器被挖矿案例 2.redis常见用途 3.redis环境配置 4.redis的持久化机制 5.redis动态修改配置 6.webshell提权案例 7.定时任务bash反弹连接提权案例 8.SSH Key提权案例 9.redis安全加固分析 1.Redis服务器被挖矿案例 我没有体验过,那就看看别…

【Redis】Redis常用命令一

1.keys:返回所有满足条件的key,比如: KEYS pattern时间复杂度:O(N),返回值:匹配pattern的所有key。 • h?llo 匹配 hello , hallo 和 hxllo • h*llo 匹配 hllo 和 heeeello • h[ae]llo 匹配 hello 和 …

【CSP试题回顾】201709-1-打酱油

CSP-201709-1-打酱油 解题代码 #include<iostream> using namespace std;int n, num;int main() { cin >> n;if (n / 50 ! 0){num 7 * (n / 50);n % 50;}if (n / 30 ! 0){num 4 * (n / 30);n % 30;}if (n / 10 ! 0) num (n / 10);cout << num;return…

Flask 专题

[CISCN2019 总决赛 Day1 Web3]Flask Message Board 查看session解密 但不知道密钥&#xff0c;题目说FLASK,那肯定就是找密钥,发现输入什么都没有显示&#xff0c;只有author那里有回显在版上&#xff0c;所以尝试sstl&#xff0c;{{config}}找到密钥 扫目录发现有admin进入…

6个免费的3D模型网站,助你3D建模渲染不用愁

​随着3D建模和渲染技术的不断发展&#xff0c;其重要性也日益增加。创建新的3D模型往往需要耗费大量时间&#xff0c;因此&#xff0c;本文整理了六个顶尖的免费3D模型资源网站&#xff0c;以便您能够方便地获取3D模型&#xff0c;大幅减少建模时间。这些资源有助于您更快地开…

高铁地铁智慧公厕方案与案例介绍

随着轨道交通的快速发展&#xff0c;高铁、地铁、火车成为人们出行的主要交通工具。而在这种规划建设要求极具的场所&#xff0c;信息化、智慧化的管理系统部署必不可少&#xff0c;智慧公厕作为公共设施的重要组成部分&#xff0c;建设和管理也越来越受到重视。智慧公厕系统的…

elasticsearch(学习笔记)(分布式搜索引擎)(黑马)(kibana操作)

一、索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类似表的结构。 我们要向es中存储数据&#xff0c;必须先创建“库”和“表”。 1、mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数据类型…

自动驾驶最强学习资料

自动驾驶最强学习资料 资料说明和获取方式 本资料包是作者吐血整理的干货&#xff01;目前为止应该是非常全的自动驾驶资料包&#xff01;是作者五年自动驾驶算法工程师的 积累&#xff01; 以上干货资料并不全是作者自己原创&#xff0c; 是作者作为五年自动驾驶算法工程的…

春招_程序员怎么写简历_写简历网站

你们在制作简历时,是不是基本只关注两件事:简历模板,还有基本信息的填写。 当你再次坐下来更新你的简历时,可能会发现自己不自觉地选择了那个“看起来最好看的模板”,填写基本信息,却没有深入思考如何使简历更具吸引力。这其实是一个普遍现象:许多求职者仍停留在传统简历…

c++的类型转换和IO流

类型转换和IO流 一、c语言的类型转换 int i 1; // 隐式类型转换&#xff0c;这些类型都是表示数据的大小&#xff0c;整型家族包括char相互转化是因为他们只是范围不一样&#xff0c;浮点数与整型支持是因为浮点数本质也是表示数据的大小&#xff0c;只是加了精度而已&#…

vscode-server的搭建方法

一、配置服务器端口支持 1、开放端口&#xff1a; 2、关闭防火墙 systemctl stop firewalld.service systemctl disable firewalld.service二、配置code-server到服务器上** 1、下载code-server-4.22.0-linux-amd64.tar.gz到本地&#xff08;可下载最新的版本&#xff09;&a…

多人语聊房社交APP开发需要有哪些功能呢?

随着移动互联网的快速发展&#xff0c;社交APP已经成为人们日常生活中不可或缺的一部分。而随着语音社交的兴起&#xff0c;多人语聊房社交APP也逐渐受到了用户的青睐。在开发多人语聊房社交APP时&#xff0c;需要具备一系列功能&#xff0c;以满足用户的需求并提供良好的使用体…

Linux的一些常用指令

一、文件中 r w x - 的含义 r&#xff08;read&#xff09;是只读权限&#xff0c; w&#xff08;write&#xff09;是写的权限&#xff0c; x&#xff08;execute&#xff09;是可执行权限&#xff0c; -是没有任何权限。 二、一些指令 # 解压压缩包 tar [-zxvf] 压缩包名…