闭包是指有权访问另一个函数作用域中变量的函数。
var i = 0;
function a() {
console.log(i);
}
a();
// 这就是一个简单的闭包,该函数使用了外部的数据
我们先说一下闭包的特点:
(1)通过闭包可以让外部环境访问到函数内部的局部变量。
(2)通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
理解闭包以及它的特点我们需要先了解两个概念:
(1)作用域链:比如你在一个函数中用到了一个变量,但是在当前作用域中没有找到它的值,就会向上级作用域去查,直到查到全局作用域,这个过程就形成了一条作用域链。
(2)垃圾回收:垃圾收集器会周期性地找出那些不再使用的变量,然后释放其内存。如果该变量还在使用,那么就不会被回收。
然后我们来看个栗子:
function a() {
var i = 0;
}
a();
console.log(i); // 报错i is not defined
输出会报错,为什么?因为执行完a函数后,i 作为局部变量不再使用会被销毁(垃圾回收),所以在输出就会报错not defined。
function a() {
var i = 0;
return function b() {
console.log(i); // 输出0
}
}
var c = a();
c();
显而易见,b函数中用到了a函数中的变量,于是形成了闭包。为什么执行完a函数后i 没被销毁呢?因为执行完a函数后,函数b被赋值给了c,c是全局变量不会被销毁,也就是函数b中的i 还会继续被使用,所以不会被回收。可以看出外部环境也可以访问到函数内部的局部变量。
理解完这个再回看它的概念以及特点就比较好理解了,同时也会发现由于变量不会被销毁,所以过度的使用闭包就可能会产生内存泄漏的问题。
完事儿来做两个题吧!!:
(1)请补全JavaScript代码,要求每次调用函数"closure"时会返回一个新计数器。每当调用某个计数器时会返回一个数字且该数字会累加1。
注意:1. 初次调用返回值为1;2. 每个计数器所统计的数字是独立的。
//来自牛客网上的一道js题
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const closure = () => {
// 补全代码
}
</script>
</body>
</html>
答案(没有标准答案哦,实现就行):
const closure = () => {
var i = 0;
return function () {
return ++i;
}
}
let a = closure();
a();
(2)执行以下代码,会输出什么结果?
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
答案: 4 4 4
解析:在匿名函数中使用了外部变量i ,形成了闭包,匿名函数在1s后执行,因为是闭包所以在循环结束后依然可以访问到i ,此时i 已经变成4(i =4后,不符合条件,才停止循环),所以就会输出三个4。
扩展:如何可以输出1 2 3呢?最简单的办法把var写成let即可。
(1)直接赋值: 把一个对象a赋值给一个对象b相当于把对象b的地址指向对象a的地址,所以他们实际上是同一个对象,新旧对象还是共享同一块内存。因此,修改其中一个对象的对象或非对象属性,另一个也会受到影响。
(2)浅拷贝: 浅拷贝只拷贝对象的非对象属性,赋值后的对象与原对象不会指向同一个地址。修改赋值后的对象b的非对象属性,不会影响原对象a的非对象属性;修改赋值后的对象b的对象属性,却会影响原对象a的对象属性。
扩展:
1、es6中的 Object.assign,如果对象的属性值为基础类型,通过Object.assign()拷贝的那个属性而言是深拷贝。如果对象的属性值为引用类型,通过Object.assign()拷贝的那个属性而言是浅拷贝。
2、es6中还有一个扩展运算符"…"也是浅拷贝。
(3)深拷贝: 拷贝父级对象和父级对象中的所有引用数据类型,会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
(1)slice():可操作字符串和数组,通过索引位置获取新的数组,该方法不会改变原数组,只是返回一个新的子数组(左闭右开)。
(2)splice(index,howmany,item1,…,itemX): 只可操作数组,删除、插入和替换,这种方法会改变原数组。
参数如下:
1、index:必需。规定从何处添加/删除元素;
2、howmany:可选。规定应该删除多少元素。必须是数字,但可以是 “0”,如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素;
3、item:可选。要添加到数组的新元素;
(3)split():把一个字符串分割成字符串数组。
(4)filter():对数组进行过滤,Array.filter(function(currentValue, index, arr), thisValue),不改变原数组。
(5)concat():用于连接两个或多个数组,该方法不会改变原数组。
(6)sort():用于对数组的元素进行排序,默认排序顺序为按字母升序,使用数字排序,你必须通过一个函数作为参数来调用,会改变原数组。
(1)for…in 循环:只能获得对象的键名,不能获得键值,不能return。
(2)for…of 循环:允许遍历获得键值,遍历数组,不可遍历对象,可以return。
(3)map:循环遍历数组中的每一项,只能遍历数组,返回新的数组,有返回值。
(4)foreach:循环遍历数组中的每一项,只能遍历数组,会修改原来的数组,没有返回值。
等我明天补充!