通过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 相关文章推荐
关于文本限制字数的js代码
Apr 02 Javascript
javascript 日历提醒系统( 兼容所有浏览器 )
Apr 07 Javascript
jquery实现像栅栏一样左右滑出式二级菜单效果代码
Aug 24 Javascript
Node.js编写组件的三种实现方式
Feb 25 Javascript
JavaScipt选取文档元素的方法(推荐)
Aug 05 Javascript
jQuery实现Select左右复制移动内容
Aug 05 Javascript
React Native仿美团下拉菜单的实例代码
Aug 08 Javascript
Three.js利用顶点绘制立方体的方法详解
Sep 27 Javascript
js中DOM事件绑定分析
Mar 18 Javascript
Vue.js 中的 v-show 指令及用法详解
Nov 19 Javascript
Vue编写可显示周和月模式的日历 Vue自定义日历内容的显示
Jun 26 Javascript
微信小程序实现锚点跳转
Nov 23 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
蝙蝠侠:侠影之谜
2020/03/04 欧美动漫
如何使用PHP获取网络上文件
2006/10/09 PHP
真正的ZIP文件操作类(php)
2007/07/21 PHP
php的一些小问题
2010/07/03 PHP
解析php安全性问题中的:Null 字符问题
2013/06/21 PHP
PHP OPP机制和模式简介(抽象类、接口和契约式编程)
2014/06/09 PHP
PHP中include/require/include_once/require_once使用心得
2016/08/28 PHP
解析JavaScript中delete操作符不能删除的对象
2013/12/03 Javascript
继续学习javascript闭包
2015/12/03 Javascript
jquery限定文本框只能输入数字(整数和小数)
2016/01/08 Javascript
jQuery控制frames及frame页面JS的方法
2016/03/08 Javascript
jquery $.trim()去除字符串空格的实现方法【附图例】
2016/03/30 Javascript
全面解析JS字符串和正则表达式中的match、replace、exec等函数
2016/07/01 Javascript
为jQuery-easyui的tab组件添加右键菜单功能的简单实例
2016/10/10 Javascript
原生JS+CSS实现炫酷重力模拟弹跳系统的登录页面
2017/11/01 Javascript
vue-cli启动本地服务局域网不能访问的原因分析
2018/01/22 Javascript
Windows下支持自动更新的Electron应用脚手架的方法
2018/12/24 Javascript
详解js动态获取浏览器或页面等容器的宽高
2019/03/13 Javascript
微信小程序转化为uni-app项目的方法示例
2020/05/22 Javascript
JQuery获得内容和属性方法解析
2020/05/30 jQuery
vue props 一次传多个值实例
2020/07/22 Javascript
vue+axios全局添加请求头和参数操作
2020/07/24 Javascript
Vue+Spring Boot简单用户登录(附Demo)
2020/11/12 Javascript
Python运用于数据分析的简单教程
2015/03/27 Python
Python的爬虫程序编写框架Scrapy入门学习教程
2016/07/02 Python
python基础教程之五种数据类型详解
2017/01/12 Python
Ubuntu18.04中Python2.7与Python3.6环境切换
2019/06/14 Python
Python列表操作方法详解
2020/02/09 Python
Python利用FFT进行简单滤波的实现
2020/02/26 Python
python压包的概念及实例详解
2021/02/17 Python
Python中使用Selenium环境安装的方法步骤
2021/02/22 Python
webapp字号大小跟随系统字号大小缩放的示例代码
2018/12/26 HTML / CSS
Html5之title吸顶功能
2018/06/04 HTML / CSS
计算机专业毕业生求职信分享
2013/12/24 职场文书
企业宣传策划方案
2014/05/29 职场文书
为什么mysql字段要使用NOT NULL
2021/05/13 MySQL