深入学习js之浅谈作用域之(eval()和with)

在深入学习js之浅谈作用域(一)中

将作用域定义为一套规则,用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

作用域分为两种主要的工作模式:1.词法作用域(大多数编程语言包括js)

2.动态作用域(Bash脚本、Perl中的一些模式)


1.嵌套词法作用域的栗子和“遮蔽效应”

以下嵌套作用域的栗子中

1.
		function foo(a) {
			2.
			var b = a * 2;
			function bar(c) {
				3.
				console.log(a, b, c);
			}

			bar(b * 3);
		}
		foo(2);

1.包含着整个全局作用域,其中只有一个标识符:foo

2.包含着foo创建的作用域,其中有三个标识符:a、bar和b

3.包含着bar所创建的作用域,其中只有一个标识符:c


遮蔽效应

作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫做“遮蔽效应".

”遮蔽效应“的实质是作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者向上进行,直到遇见第一个匹配的标识符为止

全局变量会自动成为全局对象(比如浏览器中的window对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接的通过全局对象属性的引用

来对其进行访问

window.a

var a = 1;
		function foo() {
			var a = 2;
			console.log(a);//2
			function bar() {
				var a = 3;
				console.log(a);//3
				console.log(window.a);//1
			}
			bar();
		}
		console.log(a);//1

		foo();


通过这种技术可以访问那些被同名变量所遮蔽的全局变量。但非全局的变量如果被遮蔽了,无论如何都无法被访问到。


无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。


词法作用域查找只会查找一级标识符,比如window。如果代码中引用window.document词法作用域查找只会试图查找window标识符,找到这个变量后,对象的属性访问规则接管对document属性的访问。


2.欺骗语法以及eval和with的禁用缘由

如果词法作用域完全由写代码期间函数所声明的位置来定义,那么怎样才能在运行时来”修改(欺骗)“作用域呢?

eval和with。


2.1eval 


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

eval(..)调用的"var b = 3;"这段代码会被当作本来就在那里一样来处理。由于那段代码声明了一个新的变量b,因此它对已经存在的foo()的词法作用域进行了修改。

事实上,和前面提到的原理一样,这段代码实际上在foo()内部创建了一个变量b并且遮蔽了外部作用域中的同名变量。

当console.log(..)被执行时,会在foo(..)的内部同时找到a和b,但是永远也无法找到外部的b。

在严格模式下,eval()在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域。

function foo(str) {
			"use strict"
			eval(str);
			console.log(a);//undefined
		}
		foo("var a = 3;");
另外eval(..)中代码的执行也不安全。不建议使用


2.2with()
with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象

var obj = {
			a: 1,
			b: 2,
			c: 3
		};

		//单调乏味的重复"obj"
		obj.a = 2;
		obj.b = 3;
		obj.c = 4;

		//简单的快捷方式
		with (obj) {
			a = 3;
			b = 4;
			c - 5;
		}
执行完,但实际上这不仅是为了方便地访问对象属性


这个例子中串讲了o1和o2两个对象,其中一个具有a属性,另外一个没有。foo()函数接收一个obj参数,该参数是一个对象引用,并对这个对象引用执行了with(obj){...}.在with块内部,我们写的代码看起来只是对变量a进行简单的词法引用,实际上就是一个LHS引用,并将2赋值给它。

当o1传进去的时候,a=2赋值操作找到了o1.a并将2赋值给它

当o2传进去的时候,没有找到o2的a属性,因此不会创建这个属性,o2.a保持undefined


那么a = 2赋值操作创建了一个全局变量a是什么鬼?


with可以将一个没有或有多个属性的对象处理为一个完全隔绝的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符

当我们传递o1给with时,with所声明的作用域是o1,而这个作用域中含有一个同o1.a属性相符的标识符。但当我们将o2作为作用域时,其中并没有a标识符

因此进行了LHS标识符查询,o2的作用域、foo的作用域,全局作用域都没有找到标识符a,因此当a=2执行时,自动创建了一个全局变量(非严格模式)



尽管with块可以将一个对象处理为词法作用域,但是这个块内部正常的var声明并不会被限制在这个块的作用域中,而是被添加到with所处的函数作用域中。


eval函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而with声明实际上是根据你传递给他的对象凭空创建了一个全新的词法作用域


其次js引擎会在编译阶段进行性能优化,其中有些性能优化能够根据代码的词法进行静态分析,并预设确定所有变量和函数的定义位置,才能在执行过程中快速的找到标识符

,但如果引擎在代码中发现了eval()和with,它只能假设关于标识符位置的判断是无效的,会影响优化。


总结禁用eval和with的原因

1.会被严格模式影响(eval在保留核心功能、安全情况下可使用,with被完全禁止)

2会导致性能下降

最好不要使用它们


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值