js词法作用域—欺骗词法evel、with

js欺骗词法

之前一直对欺骗词法感到很迷,直到看了《你不知道的JavaScript》(上卷)里面的解释,才解决了之前的谜团,这篇文章就是在看了书这部分内容之后再加上自己的一些尝试和理解写下的读书笔记。看这篇文章的时候可以自己动手试试看看输出结果,会有不一样的收获,希望对大家有帮助。

在对欺骗词法进行了解之前首先要对作用域要有一定的了解。

在这里插入图片描述
词法作用域简单来说:写代码时将变量块作用域写在哪里位置决定的
欺骗词法简单来说:运行时来“修改”词法作用域
js有两种实现欺骗词法机制:evel、with
在这里插入图片描述

1、evel

看看下面这段代码:

function foo(str, a) {
        eval( str ); // 欺骗!
        console.log( a, b );
 }
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
console.log( b ); //2 外部b的值没有变

分析一下这段代码:
evel(…)调用的“var b=3”会当作本来就在那里一样处理。事实上,在foo(…)内部创建了一个变量b,并遮蔽了(欺骗)外部(这里是全局)作用域的同名变量(b),在foo(…)内部永远无法找到外部的b,并没有改变外部b的值,所以可以看到最下面那行代码输出的b等于2

2、with

这里从它如何同被它所影响的词法作用域进行交互的角度进行解释
看看下面这段代码,with通常当作重复引用中一个对象中的多个属性的快捷方式

var obj = {
        a: 1,
        b: 2,
        c: 3
        };
//如何修改obj对象中的属性?
//方法一:
        // 单调乏味的重复 "obj"
         obj.a = 2;
         obj.b = 3;
         obj.c = 4;
//方法二:
        // 简单的快捷方式
        with (obj) {
        a = 3;
        b = 4;
        c = 5;
        }

自己尝试一下输出结果,可以看到with也能改变对象中的属性,用方法二比方法一改变对象中的属性就方便多了
再耐心看看下面代码,

function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
//分开两种情况执行:
//情况一:
foo( o1 );
console.log( o1.a ); // 2
console.log( a );    //Uncaught ReferenceError: a is not defined
//情况二:
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!这是执行foo(o2)的时候,创建的

with可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,被添加到with所处的函数作用域中
解释一下情况一、情况二发生了什么:
情况一:将o1传递给with时,with所声明的作用域是o1,而这个作用域中有a属性,则a=2赋值操作对o1.a进行赋值
情况二:将o2传递给with时,with所声明的作用域是o2,而这个作用域中没有a属性,继续在foo(…)的作用域全局作用域中找也没有找到标识符a,因此当a=2赋值执行时,自动创建了一个全局变量(非严格模式)

同样的代码,那如果全局作用域中有变量a结果会是什么呢?

var a=1;
function foo(obj) {
        with (obj) {
        a = 2;
         }
        
}
var o1 = {
        a: 3
 };
var o2 = {
        b: 3
};
//情况一
console.log( a ); //1         全局中的a
foo( o1 );
console.log( o1.a ); // 2      o1中的a
console.log( a ); //1          全局中的a
//情况二
console.log( a ); //1           全局中的a
foo( o2 )
console.log( o2.a ); // undefined   o2中的a
console.log( a ); // 2          全局中的a被修改了

同样的代码,那如果函数foo中有变量a结果会是什么呢?

function foo(obj) {
        var a=1;
        with (obj) {
        a = 2;
         }
        console.log( a );
 }
var o1 = {
        a: 3
 };
 var o2 = {
        b: 3
 };
 //情况一
foo( o1 );
console.log( o1.a );
console.log( a ); 
=>
1  //这是函数中的a的值,with执行时在o1中找到了a,所以a=2对o1.a进行赋值,没有对foo()函数中的a进行赋值
2  //o1中的a
Uncaught ReferenceError: a is not defined //全局中没有a,外部不能访问函数foo()中的a
//情况二
foo( o2 );
console.log( o2.a ); 
console.log( a ); 
=>
2   // 这是函数中的a的值,with执行时在o2中没找到a,在foo()中找到了,所以a=2对foo()中的a进行赋值,改变了原本为1的值
undefined  //o2中无a
Uncaught ReferenceError: a is not defined   //这是访问全局中的a,但是全局中没有,外部不能访问函数foo()的a

在严格模式下会影响evel(…)和with
evel vs with
evel:修改其所处的词法作用域
with:根据你传递给它的对象凭空创建了一个全新的词法作用域

需要注意的是,这两种机制的副作用是引擎无法在编译时对作用域查找进行优化,所以都会导致性能下降,导致代码运行变慢。
so,不要使用它们。

参考:《你不知道的JavaScript》(上卷)

猜你喜欢

转载自blog.csdn.net/i_ViOLeT_i/article/details/85257410