一、执行上下文和执行栈
1. 执行上下文
1.1 全局执行上下文
- 在执行执行代码之前,创建全局执行上下文对象,确定window就是全局执行上下文对象。
- 对全局数据进行预处理
- 查找代码中 var 关键字定义的变量,添加为 window 的属性, 不进行赋值。
- 查找代码中 function 关键字定义的函数,添加为 window 的方法(属性名是函数名,属性值就是函数体)。
- 创建 this,赋值为 window
- 正式执行全局代码
1.2 函数内的执行上下文
- 调用函数的时候,执行函数体中代码之前,创建执行上下文对象。
- 对函数中的数据进行预处理:
- 对形参进行赋值,将形参添加为执行上下文对象的属性。
- 创建 arguments 并进行赋值,将 arguments 添加为执行上下文对象的属性。
- 查找函数代码中,var 关键字定义的变量,添加为执行上下文对象的属性,不进行赋值。
- 查找函数代码中,function 关键字定义的函数,添加为执行上下文对象的方法(属性名是函数名,属性值就是函数体)。
- 创建this,赋值为调用该函数的对象
- 正式执行函数体代码。
函数每调用一次,就创建一个执行上下文对象。
2. 执行栈
执行栈: 也叫调用栈,是一种栈数据结构,用来存储代码执行过程中所创建的执行上下文对象。
栈结构: 是一种数据存结构,特点是先进后出,后进先出。将数据放入栈栈结构称为进栈,栈结构中的数据销毁称为出栈。
console.log('hello');
function func() {
console.log('func');
}
func();
func();
/**
* 创建全局的执行上下文对象,该对象放入执行栈
* 调用func,创建函数的执行上下文对象, 执行函数体, 执行完毕销毁
* 调用func,创建函数的执行上下文对象, 执行函数体, 执行完毕销毁
*/
二、作用域和执行上下文的关系
区别:
- 作用域是静态的,在函数声明的时候,函数中的变量就已经确定了作用域。
- 执行上下文对象是动态的,每调用一次函数,就创建一个执行上下文对象。
联系:
执行上下文对象同样具有作用域:
全局执行上下文对象 -> 全局
函数中的执行上下文对象 -> 函数
三、闭包
1. 什么是闭包?
- 简单讲,闭包就是指有权访问另一个函数作用域中的数据的函数。
- MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
2. 如何产生闭包
- 在函数A的里面定义函数B
- 函数B中使用函数A中(函数A是函数B的上层作用域)的数据
- 将函数B被外部引用,如下三种方式可以让函数B被外部引用:
- 将函数B作为函数A的返回值
- 将函数B设置为全局对象(window)的属性
- 将函数B作为异步操作(DOM事件、定时器)的回调函数。
/*
1. A函数中定义B函数
2. 在B函数中访问A函数作用域中的数据
3. 让B函数被外部引用
*/
function A() {
var a = 100;
var b = 200;
function B() {
console.log('我是B:我使用了A中的数据', a + b);
}
// 第一种方法
// return B;
// 第二种方法
// window.fb = B;
// 第三种方法
document.onclick = B;
}
// 第一种方法
// var f = A();
// f();
// 第二种方法
// A();
// fb();
// 第三种方法
A();
3. 闭包和作用域
- 下层作用域可以使用上层作用域的数据
- 作用域只与函数声明的位置有关,有函数在哪里调用无关!
4. 闭包和垃圾回收
- 闭包让某个数据,即使函数调用结束,也仍然被引用;函数被销毁该数据仍然不会变为垃圾对象
- 闭包延长了局部变量的生命周期
5. 闭包的缺点
- 闭包会导致数据长时间占用内存,提高内存泄漏的风险
- 闭包慎重使用
6. 闭包的应用
- 通过遍历,给多个元素监听事件,闭包可以让事件回调函数中获取到元素的索引
- 实现JS的模块化
// 遍历所有 p 为每个 p 监听 click 事件
// 遍历所有 p 为每个 p 监听 click 事件
// for (var i = 0; i < pitems.length; i++) {
// pitems[i].onclick = function () {
// this.classList.toggle('active');
// console.log('当前被选中的P的序号:', i); //i是全局变量,都是6
// };
// }
// forEach使用了闭包
pitems.forEach(function(pitem, index){
pitem.onclick = function() {
pitem.classList.toggle('active');
console.log('当前被选中的P的序号:', index);
};
});
// for循环
for (var i = 0; i < pitems.length; i ++) {
(function(n){
pitems[n].onclick = function() {
pitems[n].classList.toggle('active');
console.log('当前被选中的P的序号:', n);
}
})(i);
}
7. 练习题
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()());
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);