一.闭包
1.闭包(closure)是什么 :
- 闭包 是一个 访问其他函数内部变量 的 函数
- 闭包 = 函数 + 上下文引用
2.闭包作用 : 解决变量污染
一般用于回调函数
3.可以在浏览器中调试闭包
function fn() {
let num = 10
// fn1 + 访问num 组合才叫闭包
function fn1() {
console.log(num)
}
fn1()
}
fn()
4.代码示例
function a(){
let i = 0;
function b(){
alert(++i);
}
return b;
}
let c = a();
c();
这段代码有两个特点:
1、函数b嵌套在函数a内部;
2、函数a返回函数b。
这样在执行完let c = a( )后,变量c实际上是指向了函数b,再执行c( )后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,这是因为函数a外面的变量c引用了函数a内的函数b。也就是说,当函数a的内部函数b被函数a外面的一个变量引用的时候,就创建了一个闭包。
5.闭包案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>闭包案例</title>
</head>
<body>
<input type="text" placeholder="请输入搜索内容">
<button class="btn">点我搜索</button>
<script>
/*
1.闭包(closure)是什么 :
a. 闭包 是一个 访问其他函数内部变量 的 函数
b. 闭包 = 函数 + 上下文引用
2.闭包作用 : 解决变量污染
* 一般用于回调函数
3.在浏览器中调试闭包 :
*/
/* document.querySelector('.btn').onclick = function(){
//1.获取用户搜索的内容
let txt = document.querySelector('input').value
//2.网络请求 : 不是立即就能出结果的,网络请求需要时间的。
//使用定时器来模拟请求
setTimeout(function(){
alert(`${txt}的搜索结果如下:123条`)
},1000)
} */
/* 自己写
1.闭包是什么:闭包是一个访问其他函数内部变量的函数
闭包组合 = 函数 + 上下文引用
2.闭包作用:解决变量污染
闭包一般用于回调函数
3.可以在浏览器控制台调试闭包 */
// 点击搜索按钮, 获取输入框内容, 发送服务器
document.querySelector('button').onclick = function () {
// (1)获取输入框内容
let input = document.querySelector('input')
// (2)发送服务器: 使用定时器模拟
setTimeout(function () {
alert(`${input.value}的搜索结果为:189条`)
alert(22222222222)
}, 1000)
}
</script>
</body>
</html>
6. 应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i一直存在于内存中,因此每次执行c(),都会给i自加1。
7. 使用注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
二.递归
1.递归 : 在函数内部调用自己
递归作用和循环类似的,也需要有结束条件
2.递归作用 :
- 浅拷贝与深拷贝
- 遍历dom树
3.代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
1.递归函数: 一个函数 在内部 调用自己
* 递归作用和循环类似的,也需要有结束条件
2.递归应用:
*/
/* function fn(){
console.log('今天学得很开心')
fn()
}
fn() */
//双函数递归 : 两个函数互相调用
function fn1(){
console.log('哈哈')
fn2()
}
function fn2(){
console.log('呵呵')
fn1()
}
fn2()
/* 自己写
递归函数: 在函数内部调用自己
递归函数功能和循环类似的.(优先用循环, 递归少用)
单函数递归 */
/* function fn() {
console.log('哈哈')
fn()
} */
</script>
</body>
</html>
4.构成递归需具备的条件:
-
子问题须与原始问题为同样的事,且更为简单;
-
不能无限制地调用本身,须有个出口,化简为非递归状况处理。
5.浅拷贝与深拷贝:递归实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
1.递归函数: 一个函数 在内部 调用自己
* 递归作用和循环类似的,也需要有结束条件
2.递归应用:
浅拷贝与深拷贝 :
方式一(推荐) : JSON方式实现
* let newObj = JSON.parse( JSON.stringify( obj ) )
方式二(递归) : 了解
遍历dom树
*/
/* let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习'],
student:{
name:"班长",
score:90
}
}
//使用递归函数
function kaobei(obj,newObj){
for(let key in obj){
if( obj[key] instanceof Array ){
//声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = []
//递归调用继续拷贝 数组
kaobei(obj[key],newObj[key])
}else if( obj[key] instanceof Object ){
//声明一个空对象
newObj[key] = {}
//递归调用继续拷贝 对象
kaobei(obj[key],newObj[key])
}else{
newObj[key] = obj[key]
}
}
}
//创建一个空对象,然后深拷贝
let newObj = {}
kaobei(obj,newObj)
newObj.name = '李四'
newObj.hobby[0] = '摸鱼'
newObj.student.name = '欧阳'
console.log( obj,newObj) */
// 自己写
let obj = {
name: '张三',
age: 33,
hobby: ['学习', '上课', '干饭'],
student: {
name: '尼古拉斯凯奇',
age: 66
}
}
// 深拷贝函数
function copy(obj, newObj) {
for (let key in obj) {
if (obj[key] instanceof Array) {
newObj[key] = []
// 递归调用,继续深拷贝数组
copy(obj[key],newObj[key])
}else if(obj[key] instanceof Object){
newObj[key] = {}
// 递归调用,继续深拷贝对象
copy(obj[key],newObj[key])
}else{
newObj[key] = obj[key]
}
}
}
// 开始拷贝
let newObj = {}
copy(obj,newObj)
newObj.name = '嗯嗯嗯'
newObj.hobby[0] = '电影'
newObj.student.name = 'uuuuuuu'
console.log(obj,newObj)
</script>
</body>
</html>
6.递归遍历dom树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.menu p {
width: 100px;
border: 3px solid;
margin: 5px;
}
.menu>div p {
margin-left: 10px;
border-color: red;
}
.menu>div>div p {
margin-left: 20px;
border-color: green;
}
.menu>div>div>div p {
margin-left: 30px;
border-color: yellow;
}
</style>
</head>
<body>
<div class="menu">
<!-- <div>
<p>第一级菜单</p>
<div>
<p>第二级菜单</p>
<div>
<p>第三级菜单</p>
</div>
</div>
</div> -->
</div>
<script>
//服务器返回一个不确定的数据结构,涉及到多重数组嵌套
let arr = [
{
type: "电子产品",
data: [
{
type: "手机",
data: ["iPhone手机", "小米手机", "华为手机"]
},
{
type: "平板",
data: ["iPad", "平板小米", "平板华为"]
},
{
type: "智能手表",
data: []
}
]
},
{
type: "生活家居",
data: [
{
type: "沙发",
data: ["真皮沙发", "布沙发"]
},
{
type: "椅子",
data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
},
{
type: "桌子",
data: ["办公桌"]
}
]
},
{
type: "零食",
data: [
{
type: "水果",
data: []
},
{
type: "咖啡",
data: ["雀巢咖啡"]
}
]
}
]
//封装一个遍历dom树函数
/* function addElement(arr, father) {
//遍历数组
for (let i = 0; i < arr.length; i++) {
//(1)创建空标签
let div = document.createElement("div")
//(2)设置内容
div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
//(3)添加到父盒子
father.appendChild(div)
//如果元素还有data属性,则需要使用递归继续添加下级菜单
if (arr[i].data) {
addElement(arr[i].data, div)
}
}
}
addElement(arr, document.querySelector(".menu")) */
// 自己写
function addElement(arr, father) {
for (let i = 0; i < arr.length; i++) {
// (1)创建标签
let div = document.createElement('div')
// (2)设置内容
div.innerHTML = `<p>${arr[i].type || arr[i]}</p>`
// (3)添加到父元素上
father.appendChild(div)
// 如果有下级菜单, 继续递归添加
if (arr[i].data) {
addElement(arr[i].data, div)
}
}
}
// 调用函数
addElement(arr,document.querySelector('.menu'))
</script>
</body>
</html>
三.总结
闭包和递归是函数中的两个比较特殊的情况,所以人们要慎用。闭包相当于函数的返回值,而递归则相当于函数的参数。