通过JS运行机制的角度说说作用域


Posted in Javascript onMarch 12, 2019

前言

任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围。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

本来想一篇文章写完作用域和闭包的,想例子实在是累,就拆作两篇吧,逃~

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
innerText和innerHTML 一些问题分析
May 18 Javascript
jQuery插件实现表格隔行换色且感应鼠标高亮行变色
Sep 22 Javascript
JS对象与json字符串格式转换实例
Oct 28 Javascript
javascript闭包的理解
Apr 01 Javascript
浅谈js键盘事件全面控制
Dec 01 Javascript
微信小程序本作用域下调用全局JS详解及实例
Feb 22 Javascript
微信小程序 出现错误:{&quot;baseresponse&quot;:{&quot;errcode&quot;:-80002,&quot;errmsg&quot;:&quot;&quot;}}解决办法
Feb 23 Javascript
VsCode新建VueJs项目的详细步骤
Sep 23 Javascript
JQ图片文件上传之前预览功能的简单实例(分享)
Nov 12 Javascript
js实现烟花特效
Mar 02 Javascript
JS数组降维的实现Array.prototype.concat.apply([], arr)
Apr 28 Javascript
JavaScript 中的执行上下文和执行栈实例讲解
Feb 25 Javascript
Jquery的autocomplete插件用法及参数讲解
Mar 12 #jQuery
如何使用pm2快速将项目部署到远程服务器
Mar 12 #Javascript
详解用Webpack与Babel配置ES6开发环境
Mar 12 #Javascript
详解微信小程序框架wepy踩坑记录(与vue对比)
Mar 12 #Javascript
javascript中数组的常用算法深入分析
Mar 12 #Javascript
详解javascript 变量提升(Hoisting)
Mar 12 #Javascript
NestJs 静态目录配置详解
Mar 12 #Javascript
You might like
用PHP和ACCESS写聊天室(四)
2006/10/09 PHP
php创建多级目录代码
2008/06/05 PHP
php使用pear_smtp发送邮件
2016/04/15 PHP
ext checkboxgroup 回填数据解决
2009/08/21 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
Jquery EasyUI中弹出确认对话框以及加载效果示例代码
2014/02/13 Javascript
开发中可能会用到的jQuery小技巧
2014/03/07 Javascript
javascript组合使用构造函数模式和原型模式实例
2015/06/04 Javascript
jQuery实现选项联动轮播效果【附实例】
2016/04/19 Javascript
使用JS批量选中功能实现更改数据库中的status状态值(批量展示)
2016/11/22 Javascript
Vue实现typeahead组件功能(非常靠谱)
2017/08/26 Javascript
JavaScript实现音乐自动切换和轮播
2017/11/05 Javascript
在vant 中使用cell组件 定义图标该图片和位置操作
2020/11/02 Javascript
[47:53]DOTA2上海特级锦标赛主赛事日 - 1 败者组第一轮#2COL VS Spirit
2016/03/02 DOTA
[01:19:35]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#2Fnatic VS OG第二局
2016/03/05 DOTA
[14:03]2017DOTA2亚洲邀请赛开幕式:12神兵演绎水墨中华
2017/04/01 DOTA
Eclipse + Python 的安装与配置流程
2013/03/05 Python
Python基于多线程实现抓取数据存入数据库的方法
2018/06/22 Python
Python 串口读写的实现方法
2019/06/12 Python
Python 用turtle实现用正方形画圆的例子
2019/11/21 Python
pytorch中交叉熵损失(nn.CrossEntropyLoss())的计算过程详解
2020/01/02 Python
在pycharm中实现删除bookmark
2020/02/14 Python
python实现简单的井字棋游戏(gui界面)
2021/01/22 Python
用CSS3将你的设计带入下个高度
2009/08/08 HTML / CSS
IE下实现类似CSS3 text-shadow文字阴影的几种方法
2011/05/11 HTML / CSS
HTML5单选框、复选框、下拉菜单、文本域的实现代码
2020/12/01 HTML / CSS
澳大利亚领先的优质葡萄酒拍卖会:Langton’s Fine Wines
2019/03/24 全球购物
物业保安主管岗位职责
2013/12/25 职场文书
乡镇总工会学雷锋活动总结
2014/03/01 职场文书
2014年最新大专生职业生涯规划书范文
2014/09/13 职场文书
单位更名证明
2015/06/18 职场文书
给朋友的赠语
2015/06/23 职场文书
JavaScript实现队列结构过程
2021/12/06 Javascript
js前端面试常见浏览器缓存强缓存及协商缓存实例
2022/06/21 Javascript
CSS中使用grid布局实现一套模板多种布局
2022/07/15 HTML / CSS
MySQL count(*)统计总数问题汇总
2022/09/23 MySQL