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, 所以结果为真.
函数申明
函数的定义形式
声明式:
function func () { console.log( '使用声明式定义' ); }
表达式式( 匿名函数, 字面量函数, lambda 函数 ):
var func = function () { console.log( '使用表达式式定义' ); }; /*也可以带有名字*/ var func = function fn(){ console.log('表达式式函数'); };
两种形式定义函数的特点
函数的声明是独立于语句. 不需要加分号结束. 也不能嵌入到代码表达式中. 函数声明是独立于代码执行的. 代码在执行的时候, 声明部分已在预解析阶段处理完毕.所以可以先调用后申明.
表达式式, 本质上是使用函数表达式( 字面量 )给变量赋值. 因此它是语句.
表达式定义函数的注意点
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只有函数可以限定范围. 其他均不能限定访问范围. 所以实际上词法作用域也可以称作函数作用域.
词法作用域特点
在代码中只有函数可以限定作用范围. 允许函数访问外部的变量. 反之不允许.
在函数内优先访问内部声明的变量, 如果没有才会访问外部的.
所有变量的访问规则, 按照预解析规则来访问
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