参考链接:https://www.cnblogs.com/penghuwan/p/7404372.html
在《你不知道的JavaScript》中这样写道“对于那些有一点 JavaScript 使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生”。
理解闭包之前先巩固一下作用域和词法作用域的知识
作用域是一套规则,用于确定再何处以及和如何查找变量的规则
//函数作用域
function foo() {
var a = 'iceman';
console.log(a); // 输出"iceman"
}
foo();
// 全局作用域
// 函数内部如果没有找见就会像上级作用域查找,找到就停止
var b = 'programmer';
function foo() {
console.log(b); // 输出"programmer"
}
foo();
词法作用域是作用域的一种工作模型,作用域有两种工作模型,在JavaScript中的词法作用域是比较主流的一种,另一种动态作用域(比较少的语言在用)
var value = 1;
// 此时的foo函数定义在全局,
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar(); // 结果 1
// 执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,
// 就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
在此强调,词法作用域就是作用域是由书写代码时函数声明的位置来决定的。编译阶段就能够知道全部标识符在哪里以及是如何声明的,所以词法作用域是静态的作用域,也就是词法作用域能够预测在执行代码的过程中如何查找标识符。
事实上JavaScript并不具有动态作用域,它只有词法作用域,简单明了,但是this机制某种程度上很像动态作用域
闭包就是能访问另一个函数作用域中变量的函数(通常是指包含闭包函数的外部函数)
MDN中的定义: 闭包是指能访问那些自由变量的函数
自由变量:是指在函数中使用,既不是函数参数也不是局部局部变量的变量
function test(){
let a = 11
return function () {
console.log(a)
}
}
let fun = test()
fun() // 11
// 打印a的匿名函数被包裹在外部函数test中,并且访问了外部函数作用域的变量,因此从定义上说它就是一个闭包
function test () {
// 一段代码静静躺在这里,不会产生执行环境
// 简单的说一个运行中的环境即为执行环境
}
每个函数的变量对象保存了它所拥有的的数据,以供函数内部访问和调用(位于执行环境)
function test(data) {
let name = '变量'
function testFun () {
console.log('函数')
}
}
test('参数')
// 此时函数运行,产生了执行环境,所对应的变量对象如下:
ExecutionContext = {
variableObject: {
name:'变量'
testFun: [对函数声明testFun的引用]
arg: '参数'
},
this: thisValue,
Scope: [ // Scope chain
// 所有变量对象的列表
]
};
通过作用域链,函数能够访问来自上层作用域(执行环境)中的变量
function test () {
var a = 11
function fun () {
console.log(a)
}
fun()
}
test() // 11
在定义一个函数的时候,可能会使用到多层嵌套的闭包,这种用法,叫做“柯里化”。 而闭包柯里化有两大作用:参数累加和延迟调用
function foo (a) {
return function (b) {
return function (c) {
console.log(a + b + c);
}
}
}
// 调用方式一
foo('我')('是')('sun'); // 打印 我是sun
// 调用方式二
let foo1 = foo('我');
let foo2 = foo1('是');
foo2('sun'); // 打印 我是sun
最内层的闭包在外层函数foo和foo1调用的时候都没有调用,直到最后得到foo2并调用foo2()的时候,这个最内层的闭包才得到执行, 这也是闭包的一大特性——延迟执行
函数的变量对象一般在安徽省农户调用结束是被销毁,但是闭包的情况不同
function foo (a) {
return function () {
console.log(a)
}
}
let foo1 = foo(1);
let foo2 = foo(2);
let foo3 = foo(3);
foo1(); // 输出1
foo2(); // 输出2
foo3(); // 输出3
foo函数调用结束后, foo函数的变量对象并不会被立即销毁,而是只有当取得foo函数闭包的值的foo1, foo2, foo3调用结束, 这三个函数的变量对象和作用域链被销毁后, foo函数才算“完成任务”,这时,它才能被销毁。
因为使用闭包,可以使函数在执行完后不被销毁,保留在内存中,如果大量使用闭包就会造成内存泄露,内存消耗很大
闭包能够访问外部函数的变量,即使变量已经离开它所创建的环境,是因为外部变量会被闭包的作用域对象所持有。闭包这种特性实现了嵌套函数之间数据的隐式传递。
function addFn(a,b){
return(function(){
console.log(a+"+"+b);
})
}
let test =addFn(1,2);
setTimeout(test,3000);
一般setTimeout的第一个参数是个函数,但是不能传值。如果想传值进去,可以调用一个函数返回一个内部函数的调用,将内部函数的调用传给setTimeout。内部函数执行所需的参数,外部函数传给他,在setTimeout函数中也可以访问到外部函数。
文章链接:http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
1、内存泄露:是指申请的内存执行完后没有及时的清理或者销毁,占用空闲内存,内存泄露过多的话,就会导致后面的程序申请不到内存。因此内存泄露会导致内部内存溢出
2、堆栈溢出:是指内存空间已经被申请完,没有足够的内存提供了
3、常见的内存泄露的原因:
5、解决方法:
先看一下在循环内部出现异步操作引发的问题
fun = () => {
let work = ['周一移动审批', '周二对接oa', '周三uat']
work.forEach(item => {
// 这里的service.getList()是引入的一个外部接口文件里面的一个方法,返回一个Promise对象(Es6新特性)
// 是一个异步操作,
service.getList()
.then(()=>{
console.log(item)
// //这时候打印出来的很可能就是周三 uat,周三uat,周三uat (因为异步循环还没有等异步操作返回Promise对象过来item值已经改变)
})
.catch()
})
}
正确处理: 使用关键字async/await
fun = async () => {
let work = ['周一移动审批', '周二对接oa', '周三uat']
work.forEach(item => {
await service.getList()
.then(()=>{
console.log(item)
})
.catch()
})
}
async告诉fun方法里面存在异步的操作,await放在具体异步操作(方法)前面,意思是等待该异步返回Promise才会继续后面的操作
另一种递归处理
fun = (i) => {
let work = ['周一移动审批', '周二对接oa', '周三uat']
service.getList()
.then(()=>{
if(i < work.length){
console.log(work[i])
i++
fun(i)
}
})
.catch()
}
fun(0)
用递归来实现自我循环(具体循环在then里面,可以确保前面的compute.exec()的异步操作完成).then()是返回了Promise对象为resolve后才进行的(可以了解一下Promise对象)
1、CommonJS
阮一峰:Javascript模块化编程(一):模块的写法
2、AMD
阮一峰:Javascript模块化编程(二):AMD规范
阮一峰:Javascript模块化编程(三):require.js的用法
3、CMD
4、Es6模块化