通过JS运行机制的角度说说作用域
作者:不思 发布时间:2024-07-05 10:56:43
前言
任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围。JS中的作用域、闭包、this机制和原型往往是最难理解的概念之一。笔者将通过几篇文章和大家谈谈自己的理解,希望对大家的学习有一些帮助。如果有什么理解偏差的地方,希望大家可以评论指出,相互学习。
有过一定编程经验的同学,一定不会对作用域感到陌生,在C/C++/Java中等语言中,作用域从来没有JavaScript中的作用域那样令人困惑以致于成为一个大多数JS开发者都难以跨过的门槛。
作用域形成机制
JS中存在的三种作用域类型:全局作用域,函数作用域和ES6中新加入的块级作用域。
var a = 1;
function foo() {
var b = 2;
console.log(a);// 1
console.log(b);// 2
console.log(c);// ReferenceError
}
function foo1() {
var c = 3;
console.log(a);// 1
console.log(b);// ReferenceError
console.log(c);// 3
}
console.log(a);// 1
console.log(b); // ReferenceError
console.log(c);// ReferenceError
foo();
foo1();
从上面的例子可以看到,每个函数内部形成了属于自己的作用域,函数内部声明的变量仅仅在定义的函数内部才可以访问。全局作用域中可以访问到的有a,foo,foo作用域中可以访问到的有b,foo,a,foo1的作用域中可以访问到的有c,foo,a。因为foo的作用域嵌套在全局作用域之中,当console.log(a);执行的时候,JS在foo的作用域查找不到a,就会到它的上层(这里是foo的上层直接就是全局作用域)查找,发现这里声明了一个a,将它的值打印了出来。这种从里到外的查找就是根据作用域链查找。foo1和foo的作用域没有嵌套关系,所以相互隔离。
如果函数中使用了未声明的变量怎么办?
function foo() {
a = 2;
}
foo();
console.log(a);// 2
JS引擎在foo中查找不到a的声明,便会到它的上层(这里是全局作用域中)查找,这个时候还是没有查找到a的声明,在非严格模式下,JS引擎会在全局中自动声明一个a,这个时候,未经声明的变量a实际上泄漏到了全局作用域中。
只有使用未声明的变量才会出现变量泄漏的问题么,其实,不仅仅这种写法会出现,更常见的也会出现在for循环和if代码块中也会出现。
for(var i=1;i<10;i++) {
console.log(i);
}
console.log(i);// 10,这里的i泄漏到了全局作用域中
if(true) {
var a = 2;
}
console.log(a);// 2, 这里的a也泄漏到了全局变量之中
如果你学习过C语言系列的语法,往往很容易感到困惑,if和for居然没有作用域,这真是太奇怪了。这一切的问题的根源,都是由于ES6之前没有块级作用域导致的。所以可想而知,if包裹的代码块,同样里面的声明也是暴露出来的~
一切问题的解决直到ES6中引入了let和const得以完美的解决。使用let和const,将可以使用块级作用域,使得声明变量泄漏的问题得以解决。
for(let i=1;i<10;i++) {
console.log(i);
}
console.log(i);// ReferenceError
if(true) {
let a = 2;
}
console.log(a);// ReferenceError
声明提升机制
对于在JS中声明的不论是变量还是函数,基本上都会存在着变量声明提升的行为,将变量的声明提升到所在作用域的顶端。ES6中的let和const不会,在未声明之前都不可以使用。
看看下面的代码
console.log(a);// undefined
console.log(b);// undefined
console.log(foo);// Function
console.log(foo2);// ReferenceError
function foo () {
console.log('声明提升了哈');
}
var a = 1;
var b = function foo2() {
console.log('不同的函数声明方式提升的结果也不一样哦');
};
JS 引擎解释这段代码之前首先对代码中所有的变量进行了声明的提升,函数声明的提升的优先级是高于普通变量的,函数声明会整个提升到所在作用域的顶端(但是以函数表达式方式声明的函数不会),代码实际上是下面这个样子:
function foo () {
console.log('声明提升了哈');
}
var a;
var b;
var foo2;
console.log(a);
console.log(b);
console.log(foo);
console.log(foo2);
b = function foo2 () {
console.log('不同的函数声明方式提升的结果也不一样哦');
}
静态作用域机制(词法作用域)
关于JS中的作用域,需要明确的一点就是,JS中只存在静态作用域(词法作用域)。静态作用域是什么意思呢?意思就是它的作用域在你写下代码的时候就已经确定了,和函数的调用顺序无关,了解这一点。就可以对一些常见的现象进行解释。
var a = 2;
function foo() {
console.log(a);
}
var obj = {
a: 3,
foo: foo
}
obj.foo();// 2
foo中的a在代码写完时就确认了,指向了全局作用域中的a,一旦确定就无法更改了。同理,下面的代码
function foo() {
console.log(b);// ReferenceError
}
function foo1 () {
var b = 1;
foo();
}
foo1();
这里,JS引擎在全局作用域中查找不到b,所以会抛出一个异常。所以可以明确的道理是,foo的作用域和foo1的作用域仍然是相互独立的,不会因为调用时候的顺序而更改作用域的嵌套顺序,静态作用域在代码书写时就已经确定无法更改了,明白这一点在分析JS代码的时候尤为重要。
坑外话
变量的遮蔽效应
在函数中定义的变量会遮蔽上层作用域中同名的变量,两个变量互不影响。
var a = 1;
function foo() {
var a = 2;
console.log(a);// 2
}
console.log(a);// 1
Try-Catch 中的块级作用域
try-catch的catch中会创建一个块级作用域,该作用域内变量的表现同样遵守变量的声明提升规则。
try {
throw undefined;
}catch(e) {
a = 1;
console.log(e);// undefined
}
console.log(a);// 1,变量提升规则
console.log(e);// ReferenceError,catch的块作用域中定义的变量
隐式声明
以参数形式传入的变量在函数内部实际上存在的隐式的声明,使用时不算作未声明的变量。
function foo(a) {
a = 1;
console.log(a);
}
foo();// 1
console.log(a);// ReferenceError
本来想一篇文章写完作用域和闭包的,想例子实在是累,就拆作两篇吧,逃~
来源:https://juejin.im/post/5c8500d9e51d453b7666b376


猜你喜欢
- 本文实例讲述了Django restframework 框架认证、权限、限流用法。分享给大家供大家参考,具体如下:概述Django Rest
- ElementUI在el-table中使用el-popoverVue ElementUI在el-table中使用el-popover,点击嵌
- 春节休息了几天,今天上班第一天,最近混twitter混得比较多,经常要压缩URL,以前做了个书签用http://is.gd/压缩,后来发现了
- 项目/框架初始化时可能需要保留一些空文件,这时候就需要批量新增gitkeepconst fs = require('fs')
- facebook的信息架构设计,是目前为止互联网上我见过的最合理的信息架构。每次培训,我基本都需要拿20分钟左右的时间来解析它,包括老的、新
- pytorch默认使用单精度float32训练模型,原因在于:使用float16训练模型,模型效果会有损失,而使用double(float6
- 昨日,女票拿了一个Excel文档,里面有上万条数据要进行分析,刚开始一个字段分析,Excel用的不错,还能搞定,到后来两个字段的分析,还有区
- 方法1: 代码如下:truncate table TableName 删除表中的所有的数据的同时,将自动增长清零。 如果有外键参考这个表,这
- 列表推导式你有一个list: bag = [1, 2, 3, 4, 5]现在你想让所有元素翻倍,让它看起来是这个样子: [2, 4, 6,
- 在asp.net 2.0中,很多情况下,使用gridview控件的话,甚至只需要拖拉控件,设置属性就可
- asp中我们可以利用ERR对象来判断sql语句执行是否成功:SQL="Insert INTO TABLE(F1,F2) value
- 代码'''数据集:Mnist训练集数量:60000(实际使用:10000)测试集数量:10000(实际使用:1000
- 1、Mycat 应用场景Mycat 发展到现在,适用的场景已经很丰富,而且不断有新用户给出新的创新性的方案,以下是几个典型的应用场景:1.
- 本文实例讲述了Python 实现的微信爬虫。分享给大家供大家参考,具体如下:单线程版:import urllib.requestimport
- MySQL的Explain命令用于查看执行效果。虽然这个命令只能搭配select类型语句使用,如果你想查看update,delete类型语句
- VSCode卸载后进行重新安装,发现新安装的还有原来的一些配置,卸载的不彻底,有时候也容易出问题,可按照如下方法卸载干净:1.进入控制面板卸
- 项目演示:一、输入金额二、跳转到支付宝付款三、支付成功四、跳转回自己网站在使用支付宝接口的前期准备:1、支付宝公钥2、应用公钥3、应用私钥4
- gettext 是GNU 提供的一套 国际化与本地化 处理的相关函数库。大多数语言都有对应的gettext实现。本文主要使用jed 来实现g
- 仿射密码Affine Cipher是Multiplicative Cipher和Caesar Cipher算法的组合.仿射密码的基本实现如下
- 一、截取子串-切片方法:字符串名[初始位置:结束位置:步长]str1 = 'abcdefg'print(str1[:]) #