你不知道的JavaScript 上卷

时间:2020-04-01
本文章向大家介绍你不知道的JavaScript 上卷 ,主要包括你不知道的JavaScript 上卷 使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
        function foo (str){
                "use strict"
                eval( str );
                console.log( a );
            }
            foo ( "var a = 2");

 

 

 

第一部分——作用域和闭包

第一章

作用域是什么

1.几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。事实上,正是这种储存和访问变量的值的能力将状态带给了程序。

2.JavaScript与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。

3.传统编译语言的流程,编译“三个步骤”

   *分词/词法分析

  将由字符组成的字符串分解成(对编程语言来说)有意义的代码块(被称为词法单元 token)。例如 var a = 2; 会被分解成var、a、=、2、;

   *解析/语法分析

  将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树(抽象语法树 Abstract Syntax Tree ,AST)。如var a = 2;的抽象语法树中可能有一个叫做VariableDeclaration的顶级节点,接下来是一个叫做Identifier(值为a)的子节点,以及一个叫做AssignmentExpression的子节点。AssignmentExpression节点有一个叫做NumericLiteral(值为2)的子节点

   *代码生成

  将AST转换为可执行代码的过程。将AST转换为一组机器指令,用来创建一个叫a的变量,并将一个值存储在a中。

比起只有三个步骤的语言的编译器,js引擎要复杂得多。

4.理解作用域,可以将这个过程模拟成几个任务之间的对话。

  *引擎  从头到尾负责整个JavaScript程序的编译及执行过程

  *编译器   引擎的好朋友之一,负责语法分析及代码生成等脏活累活

  *作用域  引擎的另一个好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

5.变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没用声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

第二步,引擎有两种查找方式。LHS跟RHS。顾名思义,L R代表左侧,右侧。

LHS(赋值操作的目标是谁), RHS(谁是赋值操作的源头)简单来说就是在赋值操作的左边 LHS 赋值操作的右边 RHS

例如 function foo(a) {

    console.log(a);//2

}

  foo(2);

该例子中a = 2,a是赋值操作的目标对a进行LHS,这里2是参数传递进来 隐式的分配,foo(...)是赋值操作的源头所有RHS,意味着“去找到foo的值,并把它给我”,还有一个console.log(..)本身也需要一个引用才能执行,也有对a的RHS引用。

例如function foo(a){

    var b = a;

    return a + b ;

}

  var c = foo(2);

摘抄一下看别人写的好的微博的解析

以上代码中有3个LHS与4个RHS,分析如下:

第一,var c中的c需要被赋值,在赋值操作的左侧,所以对c进行LHS引用

第二,变量c需要被赋值,他的值是foo(2),那么foo(2)的值是多少呢,需要查找foo(2)的值,在赋值操作的右侧,所以对foo(2)进行RHS引用

第三,隐含赋值操作,将2传递给function foo(a){……}函数的参数a,a在赋值操作的左侧,对a进行LHS引用

第四,var b=a;中,b需要被赋值,处在赋值操作的左侧,所以b进行的LHS,b的值将从a来,那么右侧的a的值从何而来呢?这就需要对赋值操作右侧的a进行RHS。

第五,return a+b;中,需要找到a与b的值的来源,a与b都在赋值操作的右侧,才能得到a+b的值,所以对a与b都是进行RHS引用。

 6.当一个块或者函数嵌套在另一个块或者函数中时,就发生了作用域嵌套。

  LHS和RHS引用都会在当前作用域进行查找,如果没有找到,就会往上级作用域查找,直到全局作用域。到全局作用域后,无论如何查找过程都将停止。

7.为什么区分LHS和RHS是一件重要的事情

 

function foo(a){
        console.log(a+b);
        b = a;
        c = {};
        console.log(c.list.name);
}
foo(2);

第一次对b进行RHS查询时是无法找到该变量的。也就是说“未声明”的变量,在查询所有嵌套的作用域都找不到时,引擎抛出错误ReferenceError。  

 

 

 

相比下,LHS查询,如果全局作用域也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返回引擎,前提是非“严格模式”。

如果RHS查询到了变量,但是对变量进行的不合理操作。比如对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值的中的属性,抛出TypeError,当然先删掉了上面的b的RHS错误再操作的,因为不删掉在执行到第一个查询的时候就已经出错不再执行到下一个了。

 

 

 

 

 

 

第二章

 

词法作用域

 

1.作用域共有两种主要的工作模型。第一种最为普遍,被大多数编程语言所采用,词法作用域。 另外一种叫做动态作用域。

 

2.欺骗词法。

 

js中的eval(..)函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就在那一样,引擎并不知道或者在意代码是否以动态形式插入,只会如往常一样进行查找。

 

 

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

 

eval(..)通常被用来执行动态创建的代码。

注意:严格模式下,eval运行时有自己的词法作用域,意味着无法修改。

 

with简化对象属性赋值

        function foo(obj){
            with(obj){
                a = 2;
            }
        }
        var o1 = {
            a: 3
        };
        var o2 = {
            b: 3 
        };
        foo(o1);
        console.log(o1.a);
        foo(o2);
        console.log(o2.a);
        console.log(o2.b);
        console.log(a);//a 泄漏为全局变量

这里a会泄漏为全局变量的原因主要是,在非严格模式中,调用foo(o2) a = 2 时进行了LHS的查找,找不到,然后定义了一个a的全局变量。尝试将 foo(o2)删除后出现以下结果。

 

 

 

 

这两个机制的副作用是引擎无法在编译时对作用域查找进行优化;因为引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。所以最好不要使用它们。

 

$flag 上一页 下一页