什么是作用域链?
-- Js 引擎在执行过程中查找变量的顺序链条,看看下面的例子是否跟你分析的一样?
function bar() {
console.log(str);
}
function foo() {
var str = "我是 foo 定义的 str";
bar();
}
var str = "我是全局定义的 str";
foo();
/*
上述代码的执行流程分析
1、预编译阶段,有一个变量声明和两个函数声明,故进行提升后的变量环境为:
bar -> function(){
console.log(str)
}
foo -> function(){
var str = "我是 foo 定义的 str";
bar();
}
str -> undefined
2、解析执行阶段
(1)执行到 foo 函数调用时,全局 str 已被赋值为 "我是全局定义的 str",
(2)然后进入到 foo 函数内部, 预编译 foo 代码,并进行 str 变量提升为 undefined,
创建 foo 的执行上下文,并将执行上下文压入到 Js 调用栈中,
(3)执行 str 赋值过程,然后再调用 bar 函数,创建 bar 的执行上下文,压入调用栈顶
(4)执行 bar 的打印 str 语句,顺着调用栈顶往下寻找变量 str,在 foo 的执行上下文中
找到了 str = "我是 foo 定义的 str",故打印出 str 为 "我是 foo 定义的 str"
*/代码 run 起来的结果是???
词法作用域上场
-- 词法作用域是指作用域是由代码中函数声明的位置来决定的,词法作用域是静态的作用域。Js 作用域链是由词法作用域决定,所以上述的分析过程应该是这样子的:
- 在代码声明阶段就已经决定了整个代码的词法环境,foo 和 bar 函数的上级作用域都是全局作用域
- 在代码预编译时还是如上述分析的一致
- 执行变量查找的过程并不是按照调用栈的顺序,而是按照作用域链的顺序查找的,而作用域链是按照代码声明过程中的词法作用域关系定义的
块级作用域中的变量查找
什么是闭包?
-- 当通过调用一个外部函数返回一个内部函数后,即使该外部函数(如下面代码的 foo3 函数)已经执行结束了,但是内部函数(如下面 foo3 中 返回的 innerBar 中的 getFn 函数)引用外部函数的变量(test1)依然保存在内存中。这些在 foo3 函数执行结束之后保存在内存中的变量的集合就是 foo 3 函数的闭包。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作用域链和闭包</title>
</head>
<body>
<h1 id="title">
<span>作用域链和闭包</span>
</h1>
<script>
function bar() {
console.log(str);
}
function foo() {
var str = "我是 foo 定义的 str";
bar();
}
var str = "我是全局定义的 str";
foo();
/*
上述代码的执行流程分析 (注意下面的分析过程是有误的,看看你能不能发现)
1、预编译阶段,有一个变量声明和两个函数声明,故进行提升后的变量环境为:
bar -> function(){
console.log(str)
}
foo -> function(){
var str = "我是 foo 定义的 str";
bar();
}
str -> undefined
2、解析执行阶段
(1)执行到 foo 函数调用时,全局 str 已被赋值为 "我是全局定义的 str",
(2)然后进入到 foo 函数内部, 预编译 foo 代码,并进行 str 变量提升为 undefined,
创建 foo 的执行上下文,并将执行上下文压入到 Js 调用栈中,
(3)执行 str 赋值过程,然后再调用 bar 函数,创建 bar 的执行上下文,压入调用栈顶
(4)执行 bar 的打印 str 语句,顺着调用栈顶往下寻找变量 str,在 foo 的执行上下文中
找到了 str = "我是 foo 定义的 str",故打印出 str 为 "我是 foo 定义的 str"
*/
/* 作用域链分析 */
function bar2() {
var str2 = "我是 bar2 中的 str2";
if (1) {
let str2 = "我是 bar2 中块的 str2";
console.log(num);
}
}
function foo2() {
var str2 = "我是 foo2 中的 str2";
let num = 2;
{
let num = 3;
bar2();
}
}
var str2 = "我是全局 str2";
let num = 1;
foo2();
console.log('_____________example 分割线_______________')
/*
分析过程
1、预编译阶段,变量提升
(1)变量环境为:
str2 -> undefined
foo2 -> function(){...}
bar2 -> function(){...}
(2)词法环境为:
num -> undefined
(3)作用域链为:
全局作用域
foo2 作用域
块级作用域
bar2 作用域
块级作用域
2、执行过程
(1)调用 foo2 之前,全局 str2 被赋值为 "我是全局 str2",全局 num 被赋值为 1,此时
变量环境为
str2 -> 我是全局 str2
词法环境为:
num -> 1
(2)调用 foo2 对 foo2 进行编译,此时
变量环境为:
str2 -> undefined
词法环境为:
num - undefined
(3)执行 foo2 里面的代码,此时
变量环境为:
str2 -> 我是 foo2 中的 str2
词法环境为:
num - 3 //这是词法环境的栈顶
num - 2
(4)调用 bar2,同样进行 bar2 的预编译
变量环境为:
str2 -> undefined
词法环境为:
str2 - undefined
(5)执行 bar2 代码,此时
变量环境为:
str2 -> 我是 bar2 中的 str2
词法环境为:
str2 - 我是 bar2 中块的 str2
这里答应的 num 在当前块作用域中和当前函数的执行上下文中都不存在,
则继续向其作用域链的外层寻找,根据上面分析作用域链,bar2 的外层作用域为
全局作用域,则打印 num 应该为全局作用域中的值 1,而不是 foo2 中的 2
*/
/* 闭包分析 */
function foo3() {
var str = "我是 foo3 中 str";
let test1 = 1;
const test2 = 2;
var innerBar = {
getFn:function(){
console.log(test1);
return str;
},
setFn:function(str0){
str = str0;
}
}
return innerBar;
}
var bar = foo3();
/* 变量查找流程:当前执行上下文–>foo3 函数闭包–> 全局执行上下文 */
bar.setFn("我是全局环境的参数");
bar.getFn();
console.log(bar.getFn());
/*
分析过程
(1)解析上述代码,在预编译阶段创建全局执行上下文,此时全局执行上下文的
变量环境为:
bar -> undefined
foo3 -> function foo3(){...}
词法环境为:
null
(2)执行到 foo3 调用行,创建 foo3 执行上下文,进行 foo3 的预编译,此时 foo3 的
变量环境为:
str -> undefined
innerBar -> undefined
词法环境为:
test1 -> undefined
test2 -> undefined
(3)执行 foo3 函数,此时 foo3 执行上下文状态为:
变量环境为:
str -> 我是 foo3 中 str
innerBar -> {...} // innerBar 被赋值为对象,对象内有两个方法
词法环境为:
test1 -> 1
test2 -> 2
在 foo3 函数结束后,return 了 innerBar 对象给全局变量 bar,执行完函数 foo3 后,
本应销毁 foo3 的执行上下文,但是由于 foo3 返回了一个对象给全局上下文使用,也就是
在全局上下文中还保留了 foo3 的内部引用,故 foo3 执行上下文保留了下来
(4)调用 bar 中的 setFn 给 foo3 中的 str 设置值为 "我是全局环境的参数"
(5)再调用 bar 中的 getFn 给 foo3 中的 test1 为 1,
再在全局环境中打印时再次调用 getFn, 打印 foo3 getFn 中的 test1 为 1,
再在全局中答应 getFn 的返回值 str 我是全局环境的参数
*/
</script>
</body>
</html>闭包怎么回收?
引用闭包的函数是全局变量
-- 该闭包会一直存在直到页面关闭。但如果这个闭包以后不再使用,就会造成内存泄漏,不再使用是指什么意思?
引用闭包的函数是局部变量
-- 函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用,则 JavaScript 引擎的垃圾回收器回收这块内存
闭包使用原则
如果该闭包会一直使用,则可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大,那就尽量让它成为一个局部变量。
/* 闭包例子 */
var bar = {
str4:"我是 bar 中 str4",
logFn: function () {
console.log(str4);
}
}
function foo4() {
let str4 = "极客时间";
return bar.logFn;
}
let str4 = "我是全局变量 str4";
let _logFn = foo4();
_logFn();
bar.logFn();
/*
分析过程
预编译过程:
全局执行上下文
变量环境:
bar -> undefined
foo4 -> function(){...}
词法环境:
myName -> undefined
_logFn -> undefined
执行过程
1、执行到 _logFn 变量赋值时调用了 foo4 函数,在 foo4 中 return 了全局变量中
logFn 方法体,故全局变量 _logFn 被赋值为function(){...}。由于返回的 logFn 方法本就可以在全局环境中访问,这里不涉及到闭包
2、然后调用 _logFn(),打印全局的 str4 为 "我是全局变量 str4"
3、然后再次调用全局变量 bar 中的方法 printName(),也还是打印全局的 str4 为 "我是全局变量 str4"
*/