【济南校区】前端就业班笔记服务器与Ajax:day04
代码的预解析
预解析:提前的翻译解释, 在运行代码之前的一个解释.
作用:
编译型语言: C, C++, C#, Java需要一个 "翻译" 程序, 将源代码翻译成计算机可以读懂的二进制数据( 指令 ).然后存储成可执行文件. 提前翻译好, 运行时直接执行得结果。
解释型( 脚本型 ): JavaScript, SQL, ...代码在执行的时候, 有一个翻译程序, 读一句代码执行一句代码. 再读一句代码, 再执行一句代码.一句一句的翻译执行. 每次运行都需要翻译一次. 代码在执行之前, 需要快速的 "预览" 一遍. 那么可以尽可能提高执行效率。
在 js 中预解析的特点
代码是如何执行的: 读取 js 文本, 预解析, 一句一句地执行
js 在预解析的过程中完成了声明部分的标记与变量作用域的设定
js 中的声明
简单的说就是让 js 执行引擎知道有什么东西( 标识符 )
[JavaScript] 纯文本查看 复制代码 console.log( num ); // error: num is not defined
num(); // error: is not function
即代码在执行之前的预解析, 首先让 js 的执行引擎知道在当前运行环境中有什么东西( 名字, 标识符 )是可以被使用的. 它是变量, 还是函数等?
在 js 中有哪些声明:
1) 标识符的声明( 变量的声明 )
2) 函数的声明
变量的声明:
语法: var 变量名;
目的: 告诉解释器, 有一个名字是一个变量, 在当前环境中可以被使用.
语句: 就是可以执行的东西.
var a = 123; 是一个语句
在使用 var 声明变量, 同时完成赋值的时候. 实际上, 预解析将其做了一定处理:
凡是读取到 var 的时候, 就检查 var 紧跟的名字是否已经标记了
如果没有标记, 就表明这个名字是一个标识符, 需要被标记
如果已经被标记了, 那么 这个 var 被忽略
结论:
[JavaScript] 纯文本查看 复制代码 var a;
var a = 10;
等价
var a;
a = 10;
var a = 123;
var a = 456;
var a = 789;
等价于
var a = 123;
a = 456;
a = 789;
如果在代码中有多个 var 后面紧跟的名字是一样的. 那么只有第一个 var 起作用,后面的所有 var 都会被自动的忽略.
变量名提升
[JavaScript] 纯文本查看 复制代码 // 注意: '字符串' in 对象
// 该字符串描述的名字, 是否在对象中存在一个属性, 与之同名
// var o = { num: 123 }
// 'num' in o => true
// 'age' in o => false
if ( 'a' in window ) {
var a = 123;
}
console.log( a );
函数的声明
函数的各种定义形式
[JavaScript] 纯文本查看 复制代码 声明式:
function func () {
console.log( '使用声明式定义' );
}
表达式式( 匿名函数, 字面量函数, lambda 函数 ):
var func = function () {
console.log( '使用表达式式定义' );
};
特点:
1 函数的声明是独立于语句. 不需要加分号结束. 也不能嵌入到代码表达式中.
2 表达式式, 本质上是使用函数表达式( 字面量 )给变量赋值. 因此它是语句.
表达式:将运算符与操作数连接起来的式子. 就是一个有结果的代码单元( 不包括语句 )
var a; // 声明, 不是语句, 也没有结果
123 // 字面量, 有值, 是表达式. 是常量表达式
a = 123 // 赋值, 有值, 就是被赋值的那个值. 是赋值表达式.
function () {}
如果将变量的声明与函数的声明放在一起有些需要注意的情况
1)函数的声明实际上包含两部分
1 告诉解释器 xxx 名字已经可以使用( 函数名, 标识符 )
2 告诉解释, 这个名字代表着一个函数( 变量里存储着函数的引用 )
2) 当函数声明与变量声明冲突的时候. 只看谁先有数据.
一个在新版本的浏览器中的特性
[JavaScript] 纯文本查看 复制代码 if ( true ) {
function foo() {
console.log( true );
}
} else {
function foo() {
console.log( false );
}
}
foo();//在早期的浏览器中( 2015 年 ) 所有的浏览器( 除了火狐 )都是将其解释为声明 : false
词法作用域
作用域: 就是变量可以使用到不能使用的范围
块级作用域:
块: 代码块, 即 { }
变量的使用从定义开始, 到其所在的块级作用域结束
[JavaScript] 纯文本查看 复制代码 // js 伪代码
{
console.log( num ); // error: num 未定义
var num = 123;
{
console.log( num ); // => 123
}
console.log( num ); // => 123
}
console.log( num ); // error: num 未定义
代表语言: C, C++, C#, Java, ...
js 是词法作用域
词法: 就是定义, 书写代码的规则.
所以 所谓的 词法作用域, 就是 在书写代码的时候, 根据书写代码的结构就可以确定数据的访问范围的作用域.
js 不受 块的影响, 即使在块中定义声明变量, 在块的外面依旧可以使用
[JavaScript] 纯文本查看 复制代码 console.log( num ); // => undefined
{
var num = 123;
}
console.log( num ); // => 123
所谓的 js 的词法作用域, 就是根据预解析规则定义变量的使用范围, 全部代码中
只有函数可以限定范围. 其他均不能限定访问范围. 在内部是一个独立的作用范围结构.
结论:词法作用域就是描述变量的访问范围:
1 在代码中只有函数可以限定作用范围. 允许函数访问外部的变量. 反之不允许.
2 在函数内优先访问内部声明的变量, 如果没有才会访问外部的.
3 所有变量的访问规则, 按照预解析规则来访问
案例
[JavaScript] 纯文本查看 复制代码 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.
任务:
[JavaScript] 纯文本查看 复制代码 var num = 123;
function f1 () {
console.log( num );
}
function f2 () {
console.log( num ); // => 123 , 456, 456
num = 456;
f1();
console.log( num );
}
f2();
案例
[JavaScript] 纯文本查看 复制代码 (function ( a ) {
console.log( a );
var a = 10;
console.log( a );
})( 100 );
拆解
( 函数 ) ( 100 )
第一个圆括号就是将函数变成表达式
后面一个圆括号就是调用该函数
[JavaScript] 纯文本查看 复制代码 var func = function ( a ) {
console.log( a );
var a = 10;
console.log( a );
}
func( 100 );
注意: 函数定义参数, 实际上就是在函数最开始的时候, 有一个变量的声明function ( a ) { ... }其含义就是, 在已进入函数体, 在所有操作开始之前( 预解析之前 )就有了该变量的声明.由于已经有了 a 参数的声明. 所以在代码中 var a = 10 是重复声明. 其声明无效.所以上面的代码, 等价于
[JavaScript] 纯文本查看 复制代码 var func = function ( a ) {
console.log( a ); // => 100
a = 10;
console.log( a ); // => 10
}
func( 100 );
// 变式
(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 调用, 那么报错 error: a is not function
作用域链规则
什么是作用域链:链指的就是访问规则
[JavaScript] 纯文本查看 复制代码 function foo() {
console.log( num );
}
--------------------
function func () {
function foo() {
console.log( num );
}
foo();
}
--------------------
function F () {
function func () {
function foo() {
console.log( num );
}
foo();
}
func();
}
... ...
由于这种一环套一环的访问规则, 这样的作用域构成一个链式结构. 所以直接称其为作用域链.
作用域链是用来做变量查找的. 因此变量可以存储什么东西. 链中就应该有什么东西. 换句话说就是, 链里面存储的是各种对象. 可以将其想象成对象的序列( 数组 )
绘制作用域链的规则
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.
经典面试题
[JavaScript] 纯文本查看 复制代码 // console.log( [][ 1 ] );
// console.log( i );
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 );
};
}
// 批注
// i == 0 => arr[ 0 ].sayHello = function () ... => 在 function 中使用的是 arr[ i ]
// i == 1 arr[ 1 ].sayHello = function () ... => 在 function 中使用的是 arr[ i ]
// ...
// i == 3 arr[ 3 ]... ...
// i++ => i === 4 不再小于 4( arr.length ) 跳出循环
// 依次其中的数据
// for ( var j = 0; j < arr.length; j++ ) {
// arr[ j ].sayHello();
// }
// arr[ 0 ].sayHello();
// arr[ 1 ].sayHello();
// ...
// arr[ 3 ].sayHello()
for ( var i = 0; i < arr.length; i++ ) {
arr[ i ].sayHello();
}
// console.log( i );
闭包的概念
字面意义: 闭: 关闭, 封闭,包: 包裹, 打包闭包的含义就是一个被包裹的隔离的空间.
在 js 中, 什么是闭包
在 js 中函数是一个具有变量作用域隔离特性的一个内存结构, 即为一个闭包.
[JavaScript] 纯文本查看 复制代码 function foo () {
}
学习闭包, 在 js 中要解决什么问题
在 js 中闭包要解决的问题就是间接的访问到这个被隔离的数据.
[JavaScript] 纯文本查看 复制代码 function foo () {
var num = 123;
return num;
}
*/
// 在外界访问到 num 中的数据. 怎么做?
// 通过 return 返回 num 的数据. 就间接访问到 num 了
函数, 在 js 中与普通的对象具有一样的意义函数可以像变量一样使用赋值、传递。
闭包的间接访问
使用 return 数据不能直接访问原来的数据, 那么可以考虑利用函数的返回访问原始数据
[JavaScript] 纯文本查看 复制代码 function foo () {
var num = Math.random(); // 原始数据
function func () {
return num; // 它就是原始数据 num
}
return func;
}
var f = foo(); // foo 只调用一次, 就可以创建一个原始数据, 但是返回的函数可以重复调用
// 每调用一次就是在获得闭包中的数据的值
var v1 = f();
console.log( v1 );
var v2 = f();
console.log( v2 );
如果你想了解更多黑马课程请点击这里,如果你想加入黑马这个大家庭学习先进技术,广交天下好友! 黑马程序员济南中心联系电话:0531-55696830
|