深入了解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 相关文章推荐
jquery获取焦点和失去焦点事件代码
Apr 21 Javascript
修改file按钮的默认样式实现代码
Apr 23 Javascript
js使用removeChild方法动态删除div元素
Aug 01 Javascript
在AngularJS框架中处理数据建模的方式解析
Mar 05 Javascript
Bootstrap模块dropdown实现下拉框响应
May 22 Javascript
H5用户注册表单页 注册模态框!
Sep 17 Javascript
js利用for in循环获取 一个对象的所有属性以及值的实例
Mar 30 Javascript
JavaScript中的return布尔值的用法和原理解析
Aug 14 Javascript
基于JavaScript实现新增内容滚动播放效果附完整代码
Aug 24 Javascript
vuex实现登录状态的存储,未登录状态不允许浏览的方法
Mar 09 Javascript
简单的vuex 的使用案例笔记
Apr 13 Javascript
微信小程序如何实现五星评价功能
Oct 15 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
1982年日本摄影师镜头下的中国孩子 那无忧无虑的童年
2020/03/12 杂记
PHP中的string类型使用说明
2010/07/27 PHP
php中长文章分页显示实现代码
2012/09/29 PHP
探讨:parse url解析URL,返回其组成部分
2013/06/14 PHP
PHP的APC模块实现上传进度条
2015/10/27 PHP
PHP实现图片上传并压缩
2015/12/22 PHP
Yii2实现上下联动下拉框功能的方法
2016/08/10 PHP
Javascript call和apply区别及使用方法
2013/11/14 Javascript
中止javascript执行的方法
2014/02/14 Javascript
input输入框鼠标焦点提示信息
2015/03/17 Javascript
浅谈javascript的Array.prototype.slice.call
2015/08/31 Javascript
JS右下角广告窗口代码(可收缩、展开及关闭)
2015/09/04 Javascript
AngularJS入门教程之过滤器详解
2016/08/19 Javascript
javascript跨域请求包装函数与用法示例
2016/11/03 Javascript
AngularJS实现给动态生成的元素绑定事件的方法
2016/12/14 Javascript
ionic2打包android时gradle无法下载的解决方法
2017/04/05 Javascript
Vue的MVVM实现方法
2017/08/16 Javascript
使用JavaScript实现点击循环切换图片效果
2017/09/03 Javascript
Vue 进入/离开动画效果
2017/12/26 Javascript
JavaScript函数式编程(Functional Programming)声明式与命令式实例分析
2019/05/21 Javascript
原生js实现针对Dom节点的CRUD操作示例
2019/08/26 Javascript
[01:15]PWL S2开团时刻第二期——他们杀 我就白给
2020/11/25 DOTA
利用python、tensorflow、opencv、pyqt5实现人脸实时签到系统
2019/09/25 Python
python 轮询执行某函数的2种方式
2020/05/03 Python
大学辅导员事迹材料
2014/02/05 职场文书
刚毕业大学生自荐信范文
2014/02/20 职场文书
本科生求职信
2014/06/17 职场文书
学生检讨书如何写
2014/10/30 职场文书
2014年工作总结与下年工作计划
2014/11/27 职场文书
客户经理岗位职责
2015/01/31 职场文书
和谐拯救危机观后感
2015/06/15 职场文书
Python破解极验滑动验证码详细步骤
2021/05/21 Python
SpringBoot 集成Redis 过程
2021/06/02 Redis
Rhit高效可视化Nginx日志查看工具
2021/11/01 Servers
《辉夜大小姐想让我告白》第三季正式预告
2022/03/20 日漫
html网页引入svg图片的4种方式
2022/08/05 HTML / CSS