第二章 函数高级
1 执行上下文和执行栈
1.1 执行上下文
全局执行上下文
① 在执行全局代码前将window确定为全局执行上下文
② 对全局数据进行预处理
var定义的全局变量==>undefined, 添加为window的属性
function声明的全局函数==>赋值(fun), 添加为window的方法
this==>赋值(window)
③ 开始执行全局代码
函数执行上下文:
① 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
② 对局部数据进行预处理
形参变量==>赋值(实参)==>添加为执行上下文的属性
arguments==>赋值(实参列表), 添加为执行上下文的属性
var定义的局部变量==>undefined, 添加为执行上下文的属性
function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
this==>赋值(调用函数的对象)
③ 开始执行函数体代码
1.2 执行栈
执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下全局执行上下文
<!--
1. 依次输出什么?
2. 整个过程中产生了几个执行上下文?
-->
<script type="text/javascript">
console.log('global begin: '+ i); //globa begin undeifined
var i = 1;
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log('foo() begin:' + i);
foo(i + 1);
console.log('foo() end:' + i);
}
console.log('global end: ' + i);
</script>
2 作用域和作用域链
① 作用域
作用域就是变量的作用范围
分类:
全局作用域
函数作用域(局部作用域)
特点:
在函数声明的时候就决定了,跟函数在哪里调用没有关系
② 作用域和执行上下文的关系
1. 区别1
* 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
* 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
* 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
2. 区别2
* 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
* 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
3. 联系
* 上下文环境(对象)是从属于所在的作用域
* 全局上下文环境==>全局作用域
* 函数上下文环境==>对应的函数使用域
③ 作用域链
函数内嵌套函数,产生作用域链
在函数内使用某个变量,先从本函数作用域中查找;如果查找不到取上层函数作用域查找;直到全局作用域
3 闭包
① 什么是闭包
1)简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
2)MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
② 如何产生闭包
1)在一个函数 A 内部创建另一个函数 B;
2)函数 B 中引用上层函数也就是函数 A 中的数据(函数 A 声明的变量);
3)函数A 把函数 B 作为返回值返回,或者函数 B 被其他方式引用(如作为事件监听函数)。
function A(){
var a = 1,b = 2;
function B(){
return a+b;
}
return B;
}
var fn = A(); // 返回的是函数B
闭包常见的形式:
- 把被嵌套的函数返回
- 把被嵌套的函数作为回调函数(事件的回调函数、定时器)
③ 闭包的作用(影响)
1. 函数内部的变量长时间存储在内存中(被内部的函数引用了),延长了局部变量的声明周期
2. 在函数的外部操作(读写)函数内部的数据(变量\函数)
④ 闭包与作用域
1) 函数外不能引用函数内的变量,在函数外无法使用 A 中的变量 a 和 b;
2) 函数 B 可以引用上层作用域中的变量 a 和 b;
3) 函数 A 调用结束后的返回值赋值给了 fn,通过调用 fn 我们就间接在函数外部调用了 A 中的变量 a 和 b。
⑤ 闭包与垃圾回收机制
1)在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
2)通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
3)但是,在创建了一个闭包以后,及时函数执行结束,函数作用域内的变量也不会被销毁;
4)以上面代码的函数 A 为例,由于A 最后返回了函数 B,而B中引用了 A中的变量a和变量b,在 A 调用结束后,由于返回值中引用了 a 和 b(引用关系存在不会触发垃圾回收机制),所以a和b都不会被销毁。
⑥ 闭包:定义js模块
JS模块:具有特定功能的文件
把模块内所有的数据和功能封装在一个函数内(私有的)
模块向外暴露一个对象,对象中想要对外的方法和属性
暴露方式: ①直接return ②作为window的属性
⑦ 闭包的缺点
函数闭包之后,里面的变量会长时间存在于内存中,可能会造成内存泄漏
解决方案:
闭包能不用不用
减少对闭包引用次数
及时释放,把引用闭包的对象变为null(垃圾)
⑧ 相关面试题
题目 1
/*
说说它们的输出情况
*/
//代码片段一
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()());
//代码片段二
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name2;
};
}
};
console.log(object2.getNameFunc()()); //? MyObject
题目 2
/*
说说它们的输出情况
*/
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n)
}
}
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);