手记

你不知道的javascript---词法作用域

‘你不知道的javascript’------笔记 之 词法作用域

不久前初识作用域,简单的认识了一下作用域的概念,然后书的后面分别介绍了词法作用域,函数作用域,块作用域,然后,今天来一起了解一下词法作用域吧,,哈哈哈,,)

词法作用域的概念

我们写好代码后,编译器就该工作了,,然后编译器的第一个工作阶段叫做词法化,然后词法作用域就是在这个阶段定义的,,
我们通俗的理解一下就是,,我们再写代码的时候,我们把代码,变量,块,,等写在了哪里,就决定了词法作用域,, 

总感觉概念那么饶,但是实际的原理还是很简单的(官方的语言,,总是把很简单的东西解释的高深莫测,让我们听不懂),我们来看一下实际的例子,,也许就会柳暗花明,,

一个例子

function foo(a){            //1.整个全局作用域 其中只有一个 foo(包含整个全局作用域的 **作用域气泡**)
    var b = a * 2;          //2.在 foo 创建的作用域中 包含 a , b , bar(包含foo所创建的作用域的**作用域气泡**)
    function bar(c){        //3.在 bar 创建的作用域,包含 c(a,b是foo中的)(包含bar所创建的作用域的**作用域气泡**)
        console.log(a,b,c)
    }
    bar(b*2)
}

foo(2)  //2,4,8

例子说明了

1.词法作用域是由代码写在哪里决定的(感觉是废话),,逐级包含
2.然后这里书中提到了作用域气泡,作用域气泡由其对应的作用域代码块写在哪里决定
3.bar 是被完全包含在 foo 中的(注意是完全,不可跨域,,(不可像文氏图那样)
4.查找,这样的作用域结构为引擎执行时提供了足够的位置信息,引擎可按照规则来找到相应的标识符,,(逐级向上查找,直到找到或者查找到最上一级,通过这种方法我们可以,定义与全局变量同名变量来遮蔽全局变量)

欺骗词法

 词法作用域是由写代码时就决定了,,那么我们在代码的运行阶段通过某种方法改变,破坏了原来的词法作用域,,然后就称是欺骗词法;

1.eval 欺骗词法

eval 函数可以接受一个字符串为参数,代码运行时会将这个字符串视为好像在书写时就存在与程序中这个位置的代码(通俗点来说就是如果你给这个函数传入了一个字符串类型的函数,,那么这个函数在代码运行时你可以调用它,,如果你给他传入了一个字符串是 'var a=3' ,那么在对应的作用域中就存在a=3这个变量)(注意:这个函数非常的危险,请尽量躲着点)
//eval 函数是如何欺骗词法的,,
function foo(str,a){
    eval(str);    //注意,要行动了
    console.log(a,b)
}
var b=2;

foo('var b=3',1)            //1,3

我们来看eval函数是如何欺骗词法作用域的,,
首先我们知道 foo 函数创建的作用域是在词法分析阶段创建的,但是在第二阶段引擎在执行代码时,遇见了eval函数,,这时引擎会把eval函数中的 var b=3;当作一句正常的javascript代码执行,,这时就出现了问题,,本来在 foo 创建的词法作用域中是没有声明b=2的,,b=2是在引擎执行代码的过程中新声明的,,这时,它就对我们的刚开始的词法作用域产生了破坏,,欺骗了我们的词法作用域,,这时就遮蔽了我们外部创建的变量b,,

注意:无论何种情况eval函数都可以在运行期修改书写其的词法作用域(以上均在非‘严格模式’下,因为在严格模式下,eval函数在运行时有自己的词法作用域,,意味着其中的声明无法修改所在的作用域看下面的例子:);
function foo(str){
    'use strict';
    eval(str);
    console.log(a)   //ReferenceError:a is not defined
}
foo('var a=2');

我们应该知道的是:setTimeout 和 setInterval 和eval 函数类似
在程序中我们应该尽量少的使用动态生成代码

2.with 欺骗词法

with通常被当作重复引用对象中的多个属性的快捷方式,可以不需要重复引用对象本身
我们先看例子:
var obj={
    a:1,
    b:2,
    c:3
};

//用obj调用
obj.a=2;
obj.b=3;
obj.c=4;
//用with调用
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

foo(o2);
console.log(o2.a);  //undefined
console.log(a);   //2   什么,,怎么会有a=2!!!

然后我们来看一下上面的代码
1.我们创建了一个函数,传入一个对象,然后用with 对属性a的值进行修改,,
2.我们首先创建对像 o1,具有属性 a ,,然后调用foo(o1),打印出o1.a=2,,说明函数起作用了
3.我们创建了 o2 ,具有属性 b ,然后调用foo(o2),打印 o2.a,,undefined,,正常,因为我们o2中并没有定义 a 属性,,
4.但是我们却打印出了a=2,,关键:其实with可以将一个或多个属性的对象处理为一个完全隔离的词法作用域,,这个对象的属性也会被处理为定义在这个作用域中的词法标识符,,(例如我们给了with一个o1那么with会创建一个o1作用域m,,o2同理),,
5.这时我么在看foo(o2),,首先with创建一个独立的词法作用域o2,,这时o2没有 a 这个属性,然后这时对 a 是一个LHS引用,,分别在o2作用域,foo作用域,全局作用域,,全都没有找到,,然后就会在全局作用域自动为我们创建 a 变量(非‘严格模式’),,
6.eval 和 with 都不推荐使用,,在严格模式下,,with被完全禁用,,而eval也要求相对安全的使用,,另外在性能上使用它们会大大降低性能,,使用他们往往缺点大大的大于优点,,

总结:词法作用域 eval with
10人推荐
随时随地看视频
慕课网APP

热门评论

讲的不错,期待后续干货

讲的很细,但是其实就是作用域,你看的那本书?还分词法作用域?我第一次听说。其实这玩意叫VO

查看全部评论