1. 程式人生 > >js詞法作用域—欺騙詞法evel、with

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》(上卷)