深入了解JavaScript词法作用域


Posted in Javascript onJuly 29, 2020

JavaScript并不是传统的块级作用域,而是函数作用域!

一、作用域

  • JavaScript引擎在代码执行前会对其进行编译,在这个过程中,像var a = 2 这样的声明会被分解成两个独立的步骤:

第一步(编译阶段):var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
第二步(运行阶段):a = 2 会查询变量a(LHS查询)并对其进行赋值。

  • LHS & RHS(当前作用域->上级作用域->...->全局作用域)

LHS(左侧):试图找到变量的容器本身
RHS(右侧):查找某个变量的值

示例:

function foo(a){
	var b = a;
	return a + b;
}
var c = foo(2);
// LHS(3处):c;a(隐式变量分配);b;
// RHS(4处):foo(2);=a;a;b;
  • 异常
function foo(a){
	console.log(a + b);
	b = a;
}
foo(2);

(1)在ES5“严格模式”下,LHS抛出ReferenceError;“非严格模式”下,LHS会自动隐式的创建一个全局变量。

(2)RHS查询在所有嵌套的作用域中遍寻不到所需遍历会抛出ReferenceError。

(3)RHS查询到一个变量,但你尝试对其不合理的操作(引用null或undefined类型中的属性),会抛出TypeError。

一句话概括之:ReferenceError同作用域判别失败相关;而TypeError则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

PS:从原理上阐述了博客中《JavaScript函数及其prototype》函数执行覆盖等问题!!!

二、词法作用域

词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。JavaScript中有两个机制可以“欺骗”词法作用域:eval(...)和with。

  • eval

eval函数可以接受一个字符串参数,并将其中的内容视为好像在书写时存在于程序中这个位置的代码(在当前位置,可生成代码,并运行)。
eval可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此修改已经存在的词法作用域(运行阶段)。

function foo(str,a){
	eval(str);
	console.log(a, b);			//1 , 3
	console.log(a, window.b);	//1 , 2
}
var b = 2;
foo("var b = 3;", 1);

解释:上述全局变量b被覆盖了,由于b是全局的,可以window.b获取到;但非全局的变量如果被覆盖,就无法访问到了!
严格模式下:

function foo(str,a){
	"use strict";
	eval(str);
	console.log(a, b);			//1 , 2
	console.log(a, window.b);	//1 , 2
}
var b = 2;
foo("var b = 3;", 1);
  • with

with通常被当作重复引用一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
with将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(运行阶段)。

function foo(obj){
	with(obj){
		a = 2;
	}
}
var o1 = { a : 3 };
var o2 = { b : 3 };

foo(o1);
console.log( o1.a );	// 2

foo(o2);
console.log( o2.a );	// undefined
console.log( a );		// 2,表明a泄漏到全局作用域上了!

这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,导致代码运行速度变慢,建议不要使用它们!

PS:从原理上阐述了博客《 JavaScript语言精粹【糟粕、毒瘤】》中with不能使用的原因!!!

三、函数作用域和块作用域

匿名和具名

/* 匿名(引用自身只能用过期的arguments.callee引用) */
setTimeout(function(){
	console.log("i wait 1 second!")
},1000);
/* 具名(可读性好) */
setTimeout(function timeoutHandler(){
	console.log("i wait 1 seco nd!")
},1000);

立即执行函数表达式

/* IIFE模式 */
var a = 2;
(function IIFE(global){
	var a = 3;
	console.log(a);			//3
	console.log(global.a);	//2
})(window);
/* UMD模式 */
var b = 2;
(function UMD(def){
	def(window);
})(function tmpF(global){
	var b = 3;
	console.log(b);			//3
	console.log(global.b);	//2
});

块作用域

try/catch会创建一个块作用域

try{
	undefined();
}catch(err){
	console.log(err);	//可以正常使用
}
console.log(err);	//ReferenceError: err is not defined

/* 坑1 */
for(var i=0;i<10;i++){}
console.log(i);		//10
/* 坑2 */
{
	console.log(bar);	//undefined 不会报错!!
	var bar = 2;
}

ES6中引入新的let关键字!!

/* 填坑1 */
for(let i=0;i<10;i++){}
console.log(i);		//SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
/* 填坑2 */
{
	console.log(bar);	//SyntaxError 报错!!
	let bar = 2;
}

推荐两个将ES6代码转化成兼容ES6之前的环境(大部分是ES5,但不全是)工具:Traceur和let-er

以上就是深入了解JavaScript词法作用域的详细内容,更多关于JavaScript词法作用域的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
javascript 操作文件 实现方法小结
Jul 02 Javascript
javascript面向对象之定义成员方法实例分析
Jan 13 Javascript
jQuery调用ajax请求的常见方法汇总
Mar 24 Javascript
Angularjs中UI Router的使用方法
May 14 Javascript
BootStrap下拉框在firefox浏览器界面不友好的解决方案
Aug 18 Javascript
微信小程序本作用域下调用全局JS详解及实例
Feb 22 Javascript
浅谈js for循环输出i为同一值的问题
Mar 01 Javascript
vue一步步实现alert功能
Jul 05 Javascript
BootStrap下的弹出框加载select2框架失败的解决方法
Aug 31 Javascript
JS装饰器函数用法总结
Apr 21 Javascript
当vue路由变化时,改变导航栏的样式方法
Aug 22 Javascript
layui-table表复选框勾选的所有行数据获取的例子
Sep 13 Javascript
vue监听dom大小改变案例
Jul 29 #Javascript
VUE实时监听元素距离顶部高度的操作
Jul 29 #Javascript
详解JavaScript作用域 闭包
Jul 29 #Javascript
Angular+ionic实现折叠展开效果的示例代码
Jul 29 #Javascript
Vue 监听元素前后变化值实例
Jul 29 #Javascript
使用eslint和githooks统一前端风格的技巧
Jul 29 #Javascript
vue-cli或vue项目利用HBuilder打包成移动端app操作
Jul 29 #Javascript
You might like
PHP目录与文件操作技巧总结(创建,删除,遍历,读写,修改等)
2016/09/11 PHP
php is_writable判断文件是否可写实例代码
2016/10/13 PHP
PHP实现QQ、微信和支付宝三合一收款码实例代码
2018/02/19 PHP
javascript 复杂的嵌套环境中输出单引号和双引号
2009/05/26 Javascript
JavaScript学习笔记(二) js对象
2011/10/25 Javascript
js借助ActiveXObject实现创建文件
2013/09/29 Javascript
使用AngularJS 应用访问 Android 手机的图片库
2015/03/24 Javascript
jQuery事件绑定on()、bind()与delegate() 方法详解
2015/06/03 Javascript
Vue 父子组件、组件间通信
2017/03/08 Javascript
js实现彩色条纹滚动条效果
2017/03/15 Javascript
监控Nodejs的性能实例代码
2019/07/02 NodeJs
Vue的transition-group与Virtual Dom Diff算法的使用
2019/12/09 Javascript
JQuery使用数组遍历跳出each循环
2020/09/01 jQuery
前端vue+elementUI如何实现记住密码功能
2020/09/20 Javascript
Python脚本实现自动将数据库备份到 Dropbox
2017/02/06 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
2017/07/24 Python
Python微信企业号开发之回调模式接收微信端客户端发送消息及被动返回消息示例
2017/08/21 Python
Python设计模式之命令模式简单示例
2018/01/10 Python
Python数据结构之双向链表的定义与使用方法示例
2018/01/16 Python
Python中pillow知识点学习
2018/04/30 Python
Python实现的绘制三维双螺旋线图形功能示例
2018/06/23 Python
解读python如何实现决策树算法
2018/10/11 Python
Python迭代器iterator生成器generator使用解析
2019/10/24 Python
Python request post上传文件常见要点
2020/11/20 Python
HashMap和Hashtable的区别
2013/05/18 面试题
介绍一下#error预处理
2015/09/25 面试题
办理暂住证介绍信
2014/01/11 职场文书
运动会入场式解说词
2014/02/18 职场文书
热爱祖国的演讲稿
2014/05/04 职场文书
秋季运动会演讲稿
2014/09/16 职场文书
婚礼庆典答谢词
2015/01/20 职场文书
2016教师国培研修感言
2015/12/08 职场文书
2016年教师党员创先争优承诺书
2016/03/24 职场文书
MySQL8.0.24版本Release Note的一些改进点
2021/04/22 MySQL
如何正确理解python装饰器
2021/06/15 Python
nginx从安装到配置详细说明(安装,安全配置,防盗链,动静分离,配置 HTTPS,性能优化)
2022/02/12 Servers