几天没有更新,这两天使周末,给大家整理了一几篇东西,有关于作用域的,闭包的,还有递归的,闭包和递归,对于大部分初次接触编程的人来说还是有些难度的,昨天,花了一点时间给大家整理了一下,今天,给大家上传上来,让大家看看,部分属于个人观点,如有错误,欢迎指出
这一篇先给大家讲一讲词法作用域吧,但是讲之前我们需要先讲一讲变量名提升。
1. 变量名提升
var num = 123;
function foo1 () {
console.log( num ); //undefined
var num = 456;
console.log( num ); //456
}
foo1();
- 预解析的过程
- 代码的执行过程
程序在执行过程,会先将代码读取到内存中检查,会将所有的声明在此时进行标记。所谓的标记就是 让 JS 解析器中有这个名字,后面在使用名字的时候,不会出现未定义的错误,这个标记过程就是提升。
声明:
- 名字的声明,标识符的声明(变量名声明)
- 名字的声明就是让我们的解析器知道有这个名字
- 名字没有任何数据与之对应
- 函数的声明
- 函数声明包含两部分
- 函数声明与函数表达式有区别,函数声明是单独写在一个结构中,不存在任何语句,逻辑判断等结构中
- 什么是表达式:
- 首先函数声明告诉解析器有这个名字存在,该阶段与名字声明一样
- 告诉解析器,这个名字对应的函数体是什么
function f() {
function func() {
} // 声明
if ( true ) {
function func2() {} // 函数表达式
}
var f = function func3 () {}; // 函数表达式
this.sayHello = function () {}; // 函数表达式
var i = 1;
function func4 () {} // 函数声明
var j = 2;
}
例:
var num = 1;
function num () {
alert( num );
}
num();
分析:
- 预解析分析,提升名字
- 首先提升名字,num
- 再提升函数名,但是名字已经存在,因此只做了第二步,让名字与函数体对应和是哪个
- 结论就是 代码中已经存在一个函数 num 了
- 开始执行代码,第一步从赋值语句开始执行
- 给 num 赋值为 1
- 覆盖函数
- 调用 num ,由于 num 中存储的是数字1,因此报错
例1:
var num = 123;
function foo1 () {
console.log( num );
var num = 456;
console.log( num );
}
foo1();
- 预解析,提升 num 名字和foo1函数
- 执行第一句话: num = 123;
- 执行函数调用
- 函数调用进入函数的一瞬间也需要预解析,解析的是变量名 num
- 在函数内部是一个独立空间,允许使用外部数据,但是现在num 声明同名,即覆盖外面
- 执行第一句,打印 num ,没有数据,undefined
- 执行第二句 赋值: num = 456
- 执行第三局 打印num,结果456
例2:
if ( ! 'a' in window ) {
var a = 123;
}
console.log( a );
- 预解析, 读取提升 a, 有一个名字 a 存在了
- in 运算符: 判断某一个字符串描述的属性名是否在 对象中
- var o = { name: 'jim' }; 'name' in o , 'age' in o
- 执行第一个判断: ! 'a' in window
- 'a' in window 真
- ! 得到 假
- if 内部的赋值不进行
- 打印结果 a 的值为 undefined
例3:
if ( true ) {
function f1 () {
console.log( 'true' );
}
} else {
function f1 () {
console.log( 'false' );
}
}
f1();
老版本浏览器解析
- 预解析: 提升 f1 函数, 只保留最后提升的内容, 所以打印是 false
-
执行代码, 第一句话就是有一个 空的 if 结构
if ( true ) { } else { }
- 执行函数调用, 得到 false
新版本浏览器解析
- 预解析,f1是函数表达式,直接执行if里面的,得到true
问题: function foo() {} var foo = function () {}; 之间的不同之处???
- 上面的语法是声明,可以提升,因此在函数定义的上方也是可以调用的
- 下面的语法是函数表达式,函数名是foo,它会提升,但提升的不是函数体
- 函数表达式也是支持名字语法的
- 函数有一个属性 nume ,表示的是函数名,只有带有名字的函数定义,才会有
- 但是,函数表达式的名字,只允许在函数内部使用,但IE在外部可以访问
- () 可以将数据转换成表达式
var foo = function func () {
};
func();
新的浏览器中, 写在 if, while, do-while 结构中的函数, 都会将函数的声明转换成 特殊的函数表达式 将代码
if (...) {
function foo () { ... }
}
转换成
if (...) {
var foo = function foo () { ... }
}
2. 词法作用域
2.1. 作用域
域表示的就是 范围, 即 作用范围. 就是一个名字在什么地方可以被使用, 什么时候不能使用.
2.1.1. 块级作用域
即块级的作用范围
// 在 C , Java 等编程语言中, 下面的语法报错
{
var num = 123; // int
{
console.log( num ); // => 123
}
}
console.log( num ); // 报错
2.1.2. 在JS中采用词法作用域
所谓的词法(代码)作用域,就是代码在编写过程中体现出来的作用范围,代码一旦写好,不用执行 作用的范围就已经确定好了,这个就是所谓的词法作用域
在JS中才发作用域规则
- 函数允许访问函数外的数据
- 整个代码结构中只有函数可以限定作用域
- 作用规则首先使用提升规则分析
- 如果当前作用规则中有名字了,就不考虑外面的名字了
例子1:
var num = 123;
function foo() {
console.log( num );
}
foo();
例子2:
if ( false ) {
var num = 123;
}
console.log( num ); // undefiend
例子3:
var num = 123;
function foo() {
var num = 456;
function func() {
console.log( num );
}
func();
}
foo(); //456
例子4:
var num1 = 123;
function foo1() {
var num1 = 456;
function foo2() {
num1 = 789;
function foo3 () {
console.log( num1 );
}
foo3();
}
foo2();
}
foo1(); // 789
console.log( num1 ); // 123 //789会覆盖foo1中的num1,但是全局中的num1没有变化,所以还是123
2.2. 作用域链
可以发现只有函数可以制造作用域结构. 那么只要是代码, 至少有一个作用域, 即全局作用域. 凡是代码中有函数, 那么这个函数就构成另一个作用域. 如果函数中还有函数, 那么再这个作用域中就 又可以诞生一个作用域. 那么将这样的所有的作用域列出来, 可以有一个结构: 函数内指向函数外的链式结构.
例如:
function f1() {
function f2() {
}
}
var num = 456;
function f3() {
function f4() {
}
}
变量的访问规则
- 首先看变量在第几条链上,在该链上看是否有变量的定义与赋值,如果有,直接使用
- 如果没有到上一级链上找(n-1级链),如果有直接用,停止查找
- 如果还么有再次往上找。。。直到找到全局链(0级),还没有就是is not defined
- 注意,切记 ,同级的链不可混合查找
例子:
var num = 123;
function f1() {
console.log( num );
}
function f2() {
var num = 456;
f1();
}
f2(); //123
2.3. 补充
- 声明变量使用 var, 如果不使用 var 声明的变量就是全局变量( 禁用 )
- 因为在任何代码结构中都可以使用该语法. 那么再代码维护的时候会有问题. 所以除非特殊原因不要这么用.
- 下面的代码的错误
function foo () {
var i1 = 1 // 局部
i2 = 2, // 全局
i3 = 3; // 全局
}
- 此时注意
var arr = [];
for ( var i = 0; i < 10; i++ ) {
arr.push( i );
}
for ( var i = 0; i < 10; i++ ) {
console.log( arr[ i ] );
}
// 一般都是将变量的声明全部放到开始的位置, 避免出现因为提升而造成的错误
var arr = [],
i = 0;
for ( ; i < 10; i++ ) {
arr.push( i );
}
for ( i = 0; i < 10; i++ ) {
console.log( arr[ i ] );
}