JavaScript作用域、作用域链 学习随笔
(本文是这些知识点的自我理解。写之余从头回顾,加深理解、取得更多收获之用。)
作用域(scope)
程序设计概念,通常来说,一段程序代码中所用到的名字(JS叫标识符(如变量名、函数名、属性名、参数..))并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
百度来的概念解释。我把JS的作用域理解为--标识符的可用性的范围。
作用域一般分全局作用域、函数作用域、let/const块级作用域。
先来看看全局作用域。代码演示:
<script>
var name = 'xm';
</script>
以上代码定义了一个变量name。name的作用域是全局作用域。
浏览器碰到上面的代码时,会创建一个全局执行环境(也叫全局执行上下文)。
执行环境(也叫执行上下文)(execution context)是js代码执行的环境。
全局执行上下文的特点是一个网页只有一个全局执行上下文,关闭网页关闭浏览器时全局执行上下文销毁。不然一直都存在。
在浏览器环境中,全局执行上下文就是window对象。全局执行上下文里定义的所有的变量和函数都是作为window对象的属性和方法创建的。(let关键字声明的变量除外,但不影响它是全局变量)
那么,以上代码定义的变量name是全等于window.name的,并在任何地方都可以访问到。
总结:在全局执行上下文定义的变量、声明的函数。或者通过window.XXX定义的属性,其作用域都是全局作用域。
函数作用域也叫局部作用域。代码演示:
var name = 'xm';
function foo() {
var age = 18;
console.log(name);
}
foo();
以上代码在全局执行上下文定义name、foo函数,调用foo()函数。在函数内部定义了age变量,访问name。
age的作用域就是函数内部。外部访问age会报错。
而函数内部访问name是没有问题的。在以前学过的知识里,我们知道这是作用域链的原因。
后台如何实现作用域链的呢。
首先,在定义foo()函数时,后台会创建一个预先包含全局变量对象的作用域链。
然后该作用域链保存在内部属性[[Scope]]中。
变量对象与执行上下文紧紧关联。每个执行上下文都有一个变量对象(variable object)。
简单理解为存储变量的容器。全局变量对象就是window对象。
执行上下文被销毁时,对应的变量对象也会随着被销毁。(闭包的情况不同。简而言之,函数执行上下文被销毁了,活动对象(下文有解释)还保留在内存中)
前言所叙,全局执行上下文始终存在,那么函数foo的执行上下文呢。(叫做函数执行上下文或局部执行上下文)
函数执行上下文只有在函数执行过程中存在。
当调用foo函数时。后台会为函数创建一个执行上下文。(叫做函数执行上下文,调用函数时创建。)而且此时,执行流从全局执行上下文,进入函数执行上下文。
有了执行上下文,就会有一个与之关联的变量对象。在函数执行上下文里,这个变量对象又叫活动对象(active object)(可能因为函数变量对象经常被销毁,故取名活动对象..)
活动对象最开始只有一个arguments对象(耳熟能详emm...)。
后续会把命名参数、函数内部定义的变量、函数添加到活动对象中。
接着复制函数的[[Scope]]属性中的对象构建起函数执行上下文的作用域链。
然后活动对象被推入函数执行上下文的作用域链的前端。(以上都在函数内部代码执行前完成)
如图:
arguments应该类似于这样:,不是undefined
再此,可以总结下:
作用域链是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
调用foo()函数,当执行到console.log(name)时,会在作用域链中搜索name,搜索顺序先从当前活动对象找,没找到接着去外层变量对象找,一层一层的找。(都找不到,一般会报错)
如图:
arguments应该类似于这样:,不是undefined
变量提升
执行上下文建立后,变量对象保存当前执行上下文声明的变量,值为undefined。
定义了多个同名变量。只保存一个。执行到对应代码时,完成赋值。
访问一个变量时,总是先去当前变量对象上查找。找不到往上一层变量对象找。(如果有的情况下) 全局变量对象为最外层变量对象。在这里也找不到就报错。
console.log(name); // undefined
var name = 'xm';
console.log(name); // xm
var name = 'xh';
console.log(name); // xh
以上代码,执行到第一行时,引擎去全局变量对象找name,刚好找到了,输出。
第二行给name重新赋值,第三行输出最新的name。
第四行有给name重新赋值,第五行输出最新的name。
函数声明提升
执行上下文创建时。把function关键字开头的函数声明载入内存中。当前变量对象保存函数名,属性值为指针,指向函数。
后续执行到对应函数声明时,不会再去执行。
foo(); // xm
function foo() {
console.log('xm')
}
以上代码,执行到第一行时,引擎会去访问全局变量对象,看有没有foo。有就执行,没有报错。
当变量声明和函数声明重名时
当前变量对象保存该名称变量,值为指针,指向函数。执行到对应代码时重新赋值。
console.log(foo); // function foo() {}
function foo() {}
var foo = 'haha';
console.log(foo); // haha
以上代码,执行到第一行时,引擎会去访问全局变量对象,看有没有foo。有就输出foo,没有报错。
接着执行到第三行,重新给foo赋值。执行到第四行。输出最新foo。
在函数内部,变量提升、函数声明提升同理
此时,变量对象改名叫活动对象,活动对象包含了arguments对象。(全局变量对象没有arguments对象)
当命名参数与变量名重名时,当前活动对象保存该名称变量,值为命名参数值。代码演示:
function foo(name) {
console.log(name); // xm
var name = 'xh';
console.log(name); // xh
}
foo('xm');
如图:
当命名参数、变量名、函数名重名时。当前活动对象保存该名称变量,值为指针,指向函数。
代码演示:
function foo(name) {
console.log(name); // function name() {}
var name = 'xh';
console.log(name); // xh
function name() {
//
}
name(); // 报错
}
foo('xm');
如图:
关于let、const
共同点:都不能声明重名的变量。都不能在声明前访问。定义的变量存在块级作用域。
不同点:let声明的变量。其值可以改变。const声明的变量,其值不太能改变。当值为引用类型时,可以改变引用类型值的属性。
关于闭包
闭包是指有权访问另外一个函数作用域中的变量的函数。
创建闭包的常用方式:在函数内部创建另一个函数。
代码演示:
var name = 'xm';
function foo() {
var age = 18
return function () {
console.log(name,age);
}
}
var foo1 = foo();
foo1(); // xm 18
以上代码。匿名函数作用域链包含了自己活动对象,foo函数的活动对象,全局变量对象。
第八行执行完成时,foo函数执行上下文被销毁,重新返回全局执行上下文。
此时foo的活动对象并未被销毁,匿名函数的作用域链保持了对foo活动对象的引用。foo活动对象扔保留在内存中。
执行第九行代码,进入匿名函数执行上下文,执行第五行代码,引擎会在匿名函数作用域链去查找name、age。找到输出,找不到报错。
如图:
在看一个经典的例子:
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = function () {
return i;
}
}
// console.log(result[0]());// 3
// console.log(result[1]());// 3
// console.log(result[2]());// 3
以上代码执行完成,result是一个函数数组,每个函数都会返回一个i。
似乎每个函数都应该返回自己的索引值,0位置函数返回0,1位置范围1...
实际上。每个函数返回的都是3。
通过作用域链可以很好的理解:
比如:调用result[0]()函数。函数执行上下文创建,推入执行栈,当前函数的活动对象只包含一个变量,即arguments对象。
外层活动对象是全局变量对象。这里保存着result、i两个变量。此时i值为3。
查找从当前活动对象开始,没有找到,接着往全局变量对象找,找到i。返回i值。
要让输出值符合预期。可以使用let声明变量i。(需在for表达式中声明)
其原理是let声明的变量存在块级作用域。作用域链的查找变为:当前活动对象---块级作用域活动对象---全局变量对象。
相当于:
或者:
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = (function(i) {
return function () {
return i;
};
})(i);
}
作用域链的查找变为:当前活动对象---外层自执行匿名函数的活动对象---全局变量对象。
其他一些坑:
var a = 1;
var a;
console.log(a); //
全局变量对象保存了a。第一行把1赋值给a。第二行啥也没做。(只声明不赋值。会被忽略。)第三行输出1。
var a = 1;
function a() {
console.log(1);
}
a();// 报错。a is not a function
function关键字打头的函数声明会在执行上下文创建时载入内存中。变量对象里保存了同名属性指针,指向函数。执行到对应行代码时不会重新声明。
执行第一行代码,把1赋值给了a。接着到第五行。此时a=1。不是函数。
JavaScript作用域、作用域链 学习随笔的更多相关文章
- javascript原型原型链 学习随笔
理解原型和原型链.需从构造函数.__proto__属性(IE11以下这个属性是undefined,请使用chrome调试).prototype属性入手. JS内置的好多函数,这些函数又被叫做构造函数. ...
- 从零开始讲解JavaScript中作用域链的概念及用途
从零开始讲解JavaScript中作用域链的概念及用途 引言 正文 一.执行环境 二.作用域链 三.块级作用域 四.其他情况 五.总结 结束语 引言 先点赞,再看博客,顺手可以点个关注. 微信公众号搜 ...
- 初探JavaScript(四)——作用域链和声明提前
前言:最近恰逢毕业季,千千万万的学生党开始步入社会,告别象牙塔似的学校生活.往往在人生的各个拐点的时候,情感丰富,感触颇深,各种对过去的美好的总结,对未来的展望.与此同时,也让诸多的老“园”工看完这些 ...
- JavaScript的作用域与作用域链
作用域 作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.可以说,变量和函数在什么时候可以用,什么时候被摧毁,这都与作用域有关. JavaScript中,变量的作用域有全局 ...
- Javascript的作用域、作用域链以及闭包
一.javascript中的作用域 ①全局变量-函数体外部进行声明 ②局部变量-函数体内部进行声明 1)函数级作用域 javascript语言中局部变量不同于C#.Java等高级语言,在这些高级语言内 ...
- javascript 之作用域链-07
复习作用域 上一节我们说到作用域:是指变量可以访问的范围,他规定了如何查找变量,以及确定当前执行代码对变量的访问权限:也说到静态作用域即词法作用域,是在编译阶段决定变量的引用(由程序定义的位置决定,和 ...
- 前端知识体系:JavaScript基础-作用域和闭包-JavaScript的作用域和作用域链
JavaScript的作用域和作用域链 作用域: 变量的作用域无非两种:全局作用域和局部作用域 全局作用域: 最外层函数定义的变量拥有全局作用域.即对任何内部函数来说都是可以访问的. <scri ...
- JavaScript之作用域-作用域链
作用域 ==> 作用域链 作用域:变量可以其作用的区域(声明定义好一个变量,变量可以在哪些范围内使用) 分类:全局作用域和局部作用域(函数作用域):在js中,目前全局有作用域以及函数可以形成 ...
- 浅谈JavaScript的作用域
前段时间学了下JavaScript作用域,这个东西在JavaScript非常重要,也是JavaScript很基础的东西,正如少林里面基础武功,有了基础,才能学绝世武功. 作用域的作用是啥?一套设计良好 ...
随机推荐
- pytorch如何先初始化变量,然后再赋值
下面是定义初始化 #初始化输入的张量 - torch.empty是返回一个包含未初始化数据的张量 self.input = torch.empty(size=(self.opt.batchsize, ...
- Python手册 3.7
Python手册 3.7 下载地址:https://pan.baidu.com/s/1dPzwwP3ehnyLUNWTsB2QJg 关注微信公众号获取提取码: 输入:py99 获取提取码
- 报错:Error, CM server guid updated, expected xxxxx, received xxxxx (未解决)
报错背景: CDH断电重启后,cloudera-scm-server启动报错, cloudera-scm-server 已死,但 pid 文件仍存 由于没有成熟的解决方案,于是我就重新安装了MySQL ...
- CentOS7 配置sendmial + PHP mail函数发送邮件
https://blog.csdn.net/jiabangok/article/details/51840556
- 原生JavaScript常用本地浏览器存储方法五(LocalStorage+userData的一个浏览器兼容类)
基于LocalStorage+globalStorage+userData实现的一个本地存储类 userData用来兼容ie6 ie7 由userData模仿Session的方法:浏览器关闭删除保存的 ...
- android基础---->传感器的使用
现在每部Android 手机里面都会内置有许多的传感器,它们能够监测到各种发生在手机上的物理事件,而我们只要灵活运用这些事件就可以编写出很多好玩的应用程序.今天我们开始简单的传感器使用的学习. 目录导 ...
- 任务调度Quartz.Net之Windows Service
这个应该是关于Quartz.Net使用的最后一篇文章了,之前的介绍都是基于Web的,这种实现任务调度的方式很少见,因为不管是MVC.WebApi还是WebService,它们都需要寄宿在IIS上运行, ...
- SQL语言的分类(DQL、DML、DDL、DCL的概念与区别)
SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL. 1. 数据查询语言DQL数据查询语言DQL基本结构是由SELECT子句,FROM子句,WHER ...
- java23种设计模式之十:责任链模式
最近在学习netty中发现其中用到了责任链模式,然后结合自己在写代码中遇到了大量写if...else的情况,决定学习一下责任链模式. 一.什么样的场景下会选择用责任链模式 我们在进行业务逻辑判断时,需 ...
- 人工智能对人类有哪些影响 选择Python入门怎样
人工智能对人类有哪些影响?选择Python入门怎样?人工智能是科技时代进步的产物,也是目前人们非常关注的一个产业.那么,随着人工智能的发展,对人类生活的有哪些影响呢? 1.人工智能对文化产业影响 据了 ...