相关内容
- 作用域、作用域链
- 执行栈、执行上下文
- 闭包
作用域和作用域链
规定变量和函数的可使用范围叫做作用域。
function fn1() {
let a = 1;
}
function fn2() {
let b = 2;
}
声明两个函数,分别创建量两个私有的作用域(可以理解为两个封闭容器),fn2
是不能直接访问私有作用域 fn1
的变量 a
的。同样的,在 fn1
中不能访问到 fn2
中的 b
变量的。一个函数就是一个作用域。
每个函数都会有一个作用域,查找变量和函数时,由局部作用域到全局作用域一次查找,这些作用域形的集合就成为作用域链。
let a = 1
function fn() {
function fn1() {
function fn2() {
let c = 3;
console.log(a);
}
// 执行 fn2
fn2();
}
// 执行 fn1
fn1();
}
// 执行函数
fn();
虽然上边看起来嵌套有点复杂,我们前边说过,一个函数就是一个私有作用域,根据定义,在 fn2
作用域中打印 a
,首先在自己所在作用域搜索,如果没有就向上级作用域搜索,直到搜索到全局作用域,a = 1
,找到了打印出值。整个搜索的过程,就是基于作用域链搜索的。
执行栈和执行上下文
执行上下文
执行上下文分为三个,分别是:全局执行上下文、函数执行上下文、Eval函数执行上下文
全局执行上下文
这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局 Window 对象(浏览器的情况下),并设置 this 的值等于这个全局对象。一个程序中只能有一个全局执行上下文。
函数执行上下文
每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数执行上下文可以有任意多个。
Eval函数执行上下文
执行在 eval 函数内部的代码也会有它属于自己的执行上下文。
执行上下文的特点
- 单线程,在主线程上运行
- 同步执行,从上往下按顺序执行
- 全局上下文只有一个,在关闭浏览器时会被弹出栈
- 函数执行上下文没有数目限制
- 函数没被调用一次,都会产生一个新的执行上下文环境
执行栈
执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO
(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
当 JavaScript 引擎第一次遇到脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数被调用,它会该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数,当函数执行结束时,执行上下文会栈中弹出,控制流程到达当前栈中的下一个上下文。
闭包
什么是闭包
函数执行,形成一个私有作用域,保护里面的私有变量不受外界的干扰,除了保护私有变量外,还可以保存一些内容,这样的模式叫做闭包。
function fn() {
let num = 1;
let name = "jack";
return function () {
console.log(num++);
return name;
};
}
let f1 = fn();
f1(); // 1
f1(); // 2
f1(); // 3
f1(); // 4
console.log(f1()); // 5 jack
闭包的作用
闭包的作用有两个:保存和保护
保护
- 团队开发时,每个开发者把自己的代码放在一个私有的作用域中,防止相互之间的变量命名冲突;把需要提供给别人的方法,通过
return
或window.xxx
的方式暴露在全局下; jQuery
的源码中也是利用了这种保护机制;- 封装私有变量;
保存
- 选项卡闭包的解决方案。我们经常在网页中使用选项卡,但是它存在一个问题,那就是索引引发的问题,其实和下边的经典面试题问题相同。
var btnBox = document.getElementById("btnBox"),
inputs = btnBox.getElementsByTagName("input");
var len = inputs.length;
for (var i = 0; i < len; i++) {
inputs[i].onclick = function () {
alert(i);
};
}
此时运行程序,你会得出的结果都是 len
的数值。
为什么会出现这种问题,我们如何解决呢?
原因很简单,所有的事件绑定都是异步的,当触发点击事件,执行方法的时候,循环早就结束了。
- 同步:JS 中当前这个任务没有完成,下面的任务都不会执行,只有等当前彻底执行完成,才会执行下面的任务;
- 异步:JS 中当前任务没有完成,需要等一会在完成,此时我们可以执行下面的任务;
解决方案
当点击事件执行的时候,就会在私有作用域查找 i
的值,此时私有作用域没有 i
,就回去全局作用域查找,此时全局作用域的 i
已经被改变。所以说,要创建一个私有作用域的 i
。
- 方式一:闭包的方式,用来保存私有的变量,但是闭包解决有又优点,也有缺点
- 优点:通过创建私有作用域(闭包)方式解决,循环几次,就会创建几个私有作用域,然后每个私有作用域都会有一个私有变量
i
,存的值分别是循环的值。 - 缺点:生成多个不销毁的私有作用域(堆内存),对性能有一定的影响。
- 优点:通过创建私有作用域(闭包)方式解决,循环几次,就会创建几个私有作用域,然后每个私有作用域都会有一个私有变量
var btnBox = document.getElementById("btnBox"),
inputs = btnBox.getElementsByTagName("input");
var len = inputs.length;
for (var i = 0; i < len; i++) {
(function(i) {
inputs[i].onclick = function () {
alert(i);
};
})(i)
}
- 方式二:使用自定义属性。我们给每个对象添加一个索引属性就可以了
var btnBox = document.getElementById("btnBox"),
inputs = btnBox.getElementsByTagName("input");
var len = inputs.length;
for (var i = 0; i < len; i++) {
inputs[i].myIndex = i;
inputs[i].onclick = function () {
alert(this.myIndex);
};
}
- 方式三:使用ES6
- 终极解决方案,这是 ES6 中的知识,因为之前在 JS 中是没有块级作用域的概念的,到了 ES6 中就有了,
let
声明的变量就可以更好的解决上述问题;
- 终极解决方案,这是 ES6 中的知识,因为之前在 JS 中是没有块级作用域的概念的,到了 ES6 中就有了,
var btnBox = document.getElementById("btnBox"),
inputs = btnBox.getElementsByTagName("input");
var len = inputs.length;
for (let i = 0; i < len; i++) {
inputs[i].onclick = function () {
alert(i);
};
}
闭包的内存回收机制
内存回收机制就是不在用到的内存,我们系统就自动进行回收从而清理出空间供其他程序使用。那回收的规则是什么?
内部函数引用着外部的函数的变量,外部的函数尽管执行完毕,作用域也不会销毁。从而形成了一种不销毁的私有作用域。
某一变量或者对象被引用着,因此在回收的时候不会释放它,因为被引用代表着被使用,回收器不会对正在被引用的变量或者对象回收的。
大白话说什么是闭包,那就是在一个函数里边再定义一个函数。这个内部函数一直保持有对外部函数中作用域的访问权限。
函数执行,形成一个私有的作用域,保护里面的私有变量不受外界的干扰 ,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包。
经典面试题
var num = 10;
var obj = {
num: 20
};
obj.fn = (function (num) {
this.num = num * 3;
num++;
return function (n) {
this.num += n;
num++;
console.log(num);
};
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
输出结果为:
22
23
65 30
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!