其他分享
首页 > 其他分享> > i5ting_ztree_toc:scopeChain

i5ting_ztree_toc:scopeChain

作者:互联网

预解析的概念

什么是预解析

js代码在执行之前,对代码进行的翻译解释,可以减少代码在执行时出现异常

为什么需要预解析

编译性语言

编译型语言: C, C++, C#, Java ...需要一个 "翻译" 程序, 将源代码翻译成计算机可以读懂的二进制数据( 指令 ).然后存储成可执行文件. 也就是会提前翻译好, 运行时直接执行得到结果

解释性语言

解释型( 脚本型 ): JavaScript, SQL, ... 代码在执行的时候, 有一个翻译程序, 读一句代码执行一句代码. 再读一句代码, 再执行一句代码.

为什么需要预解析

代码在执行之前, 快速的 "预览" 一遍,对错误部分进行暴露, 那么可以尽可能提高执行时的效率.

预解析的特点

预解析的过程中完成代码申明部分的标记与变量作用域的设定

什么是申明

寻找标识符进行标记,使js执行引擎知道当前环境有哪些东西可用

变量申明

语法:     var 变量名;
目的: 告诉解释器, 有一个名字是一个变量, 在当前环境中可以被使用.

var a; // 申明变量a 值是undefined
var b = 123; // 申明变量b 同时在执行时赋值123;

申明提升

if('a' in window){
    var a = 123;
}

console.log(a);

// 分析:
//    1.读取所有的代码( 字符串 ). 包含每一个字节, 每一个数据. 但是 "只留意" var
//     2.判断 var 后面紧跟的名字是否被标记. 如果没有, 则标记上. 
//        如果已标记, 则忽略.    表示在当前环境中已经有该变量了.
//     3.读取完毕后, 代码再从头开始, 从上往下, 从左至右一句一句的执行代码.
//        执行 'a' in window. 很显然当前环境中已有变量 a, 所以结果为真.

函数申明

函数的定义形式

两种形式定义函数的特点

函数的声明是独立于语句. 不需要加分号结束. 也不能嵌入到代码表达式中. 函数声明是独立于代码执行的. 代码在执行的时候, 声明部分已在预解析阶段处理完毕.所以可以先调用后申明.

表达式式, 本质上是使用函数表达式( 字面量 )给变量赋值. 因此它是语句.

表达式定义函数的注意点

var f1 = function f2 () {
    console.log( '带有名字的  函数表达式' );
    console.log( f2 );
};

// 当函数声明语法嵌入表达式环境中, 会自动进行转换, 将转换成函数表达式.
// 1> 引用函数的规则还是使用变量赋值, 所以外部可以使用该名字调用函数.即可以使用f1调用函数
// 2> 函数表达式带有名字, 该名字只允许在函数内部使用. 属于局部作用域. ( IE8 除外 )即f2只能在f2函数内部使用
// 3> 带有名字的函数表达式, 函数的 name 属性即为该名字,即函数的name为f2

什么叫表达式

1.将运算符与操作数连接起来的式子.

2.存在结果的代码单元(不包括语句).

var a;        // 声明, 不是语句, 也没有结果
123            // 字面量, 有值, 是表达式. 是常量表达式
a = 123        // 赋值, 有值, 就是被赋值的那个值. 是赋值表达式.

函数申明和变量申明同时出现的注意点

声明变量, 是告诉解释器当前环境可以使用该名字了.

声明函数, 是告诉解释器, 除了可以使用该名字, 该名字还表示一个函数体.

先var 后 function

var num;
function num(){

}
console.log(num); // 打印函数体

// 先 var num; 后 function num ...
// 首先告知解释器有 名字 num 了
// 后面是函数声明. 由于已经有 num 名字可以使用了, 所以就不再告诉解释器可以使用 num
// 而是直接将 num 与函数结合在起义.

先function 后 var

function num(){};
var num;
console.log(num); // 打印函数体

// 先 function num ... 后 var num;
// 一开始已经有 num 了, 而且是函数. 所以后面的 var num; 属于重复声明.

特例

if ( true ) {
    function foo() {
        console.log( true );
    }
} else {
    function foo() {
        console.log( false );
    }
}
foo();

// 在早期的浏览器中( 2015 年前) 所有的浏览器( 除了火狐 )都是将其解释为声明 : false
// 但是现在的运行结果, 得到: true. 表示 if 起到了作用

/******************************/

if ( true ) {
    function foo1() {
        console.log( true );
    }
} else {
    function foo2() {
        console.log( false );
    }
}
foo1();
// foo2();  大专栏  i5ting_ztree_toc:scopeChain // error: foo2 is not function. 已定义, 但是函数为被指向

// 好比: var foo1 = function foo1 () { ... }

// 虽然这两个函数不是声明, 但是也不能解释成函数表达式. 如果是函数表达式 foo1 与 foo2 只能在函数内部使用.

词法作用域

什么叫作用域

变量可以使用 到 不能使用的范围

js的词法作用域

js 的词法作用域, 就是根据预解析规则定义变量的使用范围, 而js只有函数可以限定范围. 其他均不能限定访问范围. 所以实际上词法作用域也可以称作函数作用域.

词法作用域特点

  1. 在代码中只有函数可以限定作用范围. 允许函数访问外部的变量. 反之不允许.

  2. 在函数内优先访问内部声明的变量, 如果没有才会访问外部的.

  3. 所有变量的访问规则, 按照预解析规则来访问

  4. js中变量的作用域链与定义时的环境有关,与执行时无关。

案例

案例01

var num = 123;
function f1 () {
    console.log( num );
}
function f2 () {
    console.log( num );
    var num = 456;
    f1();
    console.log( num );
}
f2();

1> 读取代码预解析. 得到 num, f1, f2
2> 逐步的执行代码
    1) 赋值 num = 123;   注意 f1 和 f2 由于是函数, 所以也有数据.
    2) 调用 f2.
        进入到函数体内. 相当于做一次预解析. 得到 num. 注意, 此时有内外两个 num
        执行每一句代码
        -> 打印 num. 因为函数内部有声明 num. 所以此时访问的是函数内部的 num. 未赋值, 得到 undefined
        -> 赋值 num = 456
        -> 调用 f1(). 调用函数的规则也是一样. 首先看当前环境中是否有函数的声明. 如果有直接使用. 如果没有, 则在函数外面找, 看是否有函数. 此时在函数 f2 中没有 f1 的声明. 故访问的就是外面的 f1 函数
        -> 跳入 f1 函数中. 又要解析一次. 没有得到任何声明.
        -> 执行打印 num. 当前环境没有声明 num. 故在外面找. 外面的是 123. 所以打印 123. 
            函数调用结束, 回到 f2 中.
        -> 继续执行 f2, 打印 num. 在 f2 的环境中找 num. 打印 456.

案例02

(function ( a ) {
    console.log( a );
    var a = 10;
    console.log( a );
})( 100 );

拆解
( 函数 ) ( 100 )
第一个圆括号就是将函数变成表达式
后面一个圆括号就是调用该函数

注意: 函数定义参数, 实际上就是在函数最开始的时候, 有一个变量的声明
function ( a ) { ... }
其含义就是, 在已进入函数体, 在所有操作开始之前( 预解析之前 )就有了该变量的声明.

由于已经有了 a 参数的声明. 所以在代码中 var a = 10 是重复声明. 其声明无效.
所以上面的代码, 等价于
var func = function ( a ) {
    console.log( a );            // => 100
    a = 10;
    console.log( a );            // => 10
}
func( 100 );

案例03

(function ( a ) {
    console.log( a );
    var a = 10;
    console.log( a );
    function a () {
        console.log( a );
    }
    a();
})( 100 );

1> 直接调用
2> 进入到函数中, 已有声明 a 并且其值为 100
3> 在函数内部预解析. 得到 一个结论. 函数声明是两个步骤. 
    1) 让当前环境中, 有变量名 a 可以使用. 但是不需要. 因为已经有 a 的声明了
    2) 让 a 指向函数. 相当于
        var a;
        function a () {}
        ...
4> 开始逐步执行每一句代码
    1) 打印 a. 所以打印函数体
    2) 赋值 a = 10
    3) 打印 a, 打印出 10
    4) 调用 a, a已经被赋值为10,不在是函数体, 所以报错 error: a is not function

作用域链

链其实指的一种访问规则,作用域链就是指作用域的访问规则

绘制作用域链的规则

1. 将所有的 script 标签作为一条链结构. 标记为 0 级别的链.
2. 将全局范围内, 所有的声明变量名和声明函数名按照代码的顺序标注在 0 级链中.
3. 由于每一个函数都可以构成一个新的作用域链. 所以每一个 0 级链上的函数都延展出 1 级链.
4. 分别在每一个函数中进行上述操作. 将函数中的每一个名字标注在 1 级链中.
5. 每一条 1 级链中如果有函数, 可以再次的延展出 2 级链. 以此类推...

分析代码执行

1. 根据代码的执行顺序( 从上往下, 从左至右 )在图中标记每一步的变量数据的变化
2. 如果需要访问某个变量. 直接在当前 n 级链上查找变量. 查找无序.
3. 如果找到变量, 直接使用. 如果没有找到变量 在上一级, n - 1 级中查找.
4. 一直找下去, 直至到 0 级链. 如果 0 级链还没有就报错. xxx is not defined.

经典面试题

var  arr = [ { name: '张三1' }, 
             { name: '张三2' }, 
             { name: '张三3' }, 
             { name: '张三4' } ];

// 利用循环, 添加方法, 在方法中打印 name
for ( var i = 0; i < arr.length; i++) {
    // arr[ i ] 绑定方法
    arr[ i ].sayHello = function () {
        // 打印名字
        console.log( 'name = ' + arr[ i ].name );
    };
}

for ( var i = 0; i < arr.length; i++ ) {
    arr[ i ].sayHello();
}

// 打印结果?

标签:function,i5ting,函数,num,console,ztree,var,toc,log
来源: https://www.cnblogs.com/liuzhongrong/p/12390149.html