JavaScript 作用域链
函数作用域
在函数内定义的变量不能在函数之外的任何地方访问,因为变量仅仅在该函数的域的内部有定义。
function peopleInfo() {
var name = 'yuuko';
}
console.log(name); // (空)
相对应的,一个函数可以访问定义在其范围内的任何变量和函数。
function peopleInfo() {
var name = 'yuuko';
console.log(name);
}
peopleInfo(); // "yuuko"
换言之,定义在全局域中的函数可以访问所有定义在全局域中的变量。
var name = 'yuuko';
function peopleInfo() {
console.log(name);
}
peopleInfo(); // "yuuko"
在另一个函数中定义的函数也可以访问在其父函数中定义的所有变量和父函数有权访问的任何其他变量。
var name = 'yuuko';
function peopleInfo() {
var age = 22;
function baseInfo() {
console.log('I am ' + name + ', I am ' + age + ' years old.');
}
return baseInfo;
}
var result = peopleInfo();
result(); // "I am yuuko, I am 22 years old."
作用域链相关面试题
题目1
var a = 1;
function fn1() {
function fn2() {
console.log(a);
}
function fn3() {
var a = 4;
fn2();
}
var a = 2;
return fn3;
}
var fn = fn1();
fn(); // 2
题目2
var a = 1;
function fn1() {
function fn3() {
var a = 4;
fn2();
}
var a = 2;
return fn3;
}
function fn2() {
console.log(a);
}
var fn = fn1();
fn(); // 1
题目3
var a = 1;
function fn1() {
function fn3() {
function fn2() {
console.log(a);
}
fn2();
var a = 4;
}
var a = 2;
return fn3;
}
var fn = fn1();
fn(); // undefined
总结
- 每当执行一个函数,就会进入一个 新的作用域 下;
- 函数在执行的过程中,先从自己 内部 找变量;
- 如果找不到,再从 创建当前函数所在的作用域 去找, 依次往上;
- 注意找的是变量的 当前 的状态。
立即执行函数表达式
IIFE
IIFE( 立即执行函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。
这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。
第一部分是包围在圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有 独立的词法作用域。
第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将 直接执行函数。
示例
- 当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
(function () {
var name = 'yuuko';
})();
console.log(name); // undefined
- 将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。
var result = (function () {
var name = 'yuuko';
return name;
})(); // IIFE 执行后返回的结果
console.log(result); // "yuuko"
作用
不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
调用栈
call stack
调用栈是解释器(就像浏览器中的javascript解释器)追踪函数执行流 的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
- 每调用一个函数,解释器就会把该函数 添加 进调用栈并开始 执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其 清出 调用栈,继续执行当前执行环境下的剩余的代码。当分配的调用栈空间被占满时,会引发“堆栈溢出”。
示例
var a = 1;
function fn1() {
function fn3() {
function fn2() {
console.log(a);
}
fn2();
var a = 4;
}
var a = 2;
return fn3;
}
var fn = fn1();
fn(); // undefined
上面的代码将这样执行:
- 忽略前面所有函数,直到 fn() 函数被调用。把 fn() 添加进调用栈列表,执行 fn() 函数体中的代码。
调用栈列表:
- fn
- 执行 fn() 函数体中的代码时,fn1() 函数被调用。把 fn1() 添加进调用栈列表,执行 fn1() 函数体中的代码。
调用栈列表:
- fn
- fn1
- 代码执行到 return fn3 时,fn3() 函数被调用。把 fn3() 添加进调用栈列表,执行 fn3() 函数体中的代码。
调用栈列表:
- fn
- fn1
- fn3
- 代码执行到 fn2() 时,fn2() 函数被调用。把 fn2() 添加进调用栈列表,执行 fn2() 函数体中的代码。
调用栈列表:
- fn
- fn1
- fn3
- fn2
- 代码执行到 console.log(a) 时,先从自己内部找变量 a;没有找到,再从创建 fn2() 函数所在的作用域 fn3() 函数内去找:
function fn3() {
function fn2() {
console.log(a);
}
fn2();
var a = 4;
}
fn3() 函数内部的变量和函数 声明前置:
function fn3() {
var a;
var fn2;
fn2 = function () {
console.log(a);
};
fn2(); // undefined
a = 4;
}
- 返回来继续执行 fn3() 函数体中 fn2() 后面的代码。删除调用栈列表中的 fn2() 函数。
调用栈列表:
- fn
- fn1
- fn3
- 当 fn3() 函数体中的代码全部执行完毕,返回来继续执行 fn1() 函数体中 fn3() 后面的代码。删除调用栈列表中的 fn3() 函数。
调用栈列表:
- fn
- fn1
- 当 fn1() 函数体中的代码全部执行完毕,返回到调用 fn1() 的代码行,继续执行剩余JS代码。删除调用栈列表中的 fn1() 函数。
调用栈列表:
- fn
- 依次往上,直到调用栈为空。
总结
- 一开始,我们得到一个 空空如也 的调用栈。
- 随后,每当有函数被调用都会自动地 添加 进调用栈。
- 执行 完函数体中的代码后,调用栈又会自动地 移除 这个函数。
- 最后,我们又得到了一个 空空如也 的调用栈。
网友评论