1.1作用域
1.1.1作用域介绍
- js中全局变量能渗透到局部作用域中。
- 每执行一次函数后,就会重新给一个新的内存地址,有一个新的环境。
- 只要作用域中有一个变量在清除内存地址的时候还在使用,这个函数就不会清除内存,就形成了闭包
1.1.2 作用域例子
例子1(必须在清理内存的时候,使用作用域内的数据)
```javascript function hd(){ let n=1; return function sum(){ console.log(++n); }; }
/ 1.必须要赋值给全局的变量,全局变量才会接收这个函数,并将这个函数存起来。 这个全局变量没有重新声明定义,那么每调用一次,就会产生闭包。 如果是单使用hd()()的话,那么每使用一次hd(),它都会生成新的作用域,产生新的环境。/
/ 2.为什么会闭包:并且在return出去的时候,使用的是函数,还能在使用时被调用。 在return那被调用时,本来该清内存的,但用到了里面的n,那么就形成了闭包,没有清理掉内存/
let a=hd(); /3.所以疯狂调用a()就会产生闭包效果/ a();a();a();
<a name="pxd95"></a>
### 例子2(构造函数使用闭包)
```javascript
function Hd(){
let n=1;
this.sum=function(){
console.log(++n);
}
}
/*1.因为已经声明创建的a是构造函数Hd的实例,所以不会重新声明和创建Hd函数*/
/*2.并且需要注意,在实例化对象的时候,就已经在调用了一次Hd()函数*/
let a=new Hd();
/*3.所以疯狂调用a.sum()就会产生闭包效果*/
a.sum();a.sum();a.sum();
例子3(块级作用域)
//1.作用范围都在{}里面
{
let a=1;
const b=1;
}
//2.作用范围提升到{}的外面
{
var a=1;
}
相当于是在块的外面window中声明的a,在块内赋值,如下:
window.a=undefined;
{
a=1; //window可以不写
}
例子4(for循环)
// 1.let的例子:
for(let i=1;i<=3;i++){
console.log(i); //依次输出1 2 3
}
console.log(i); //未定义undefined
// 2.var的例子
for(var i=1;i<=3;i++){
console.log(i); //依次输出1 2 3
}
console.log(i); //输出4,由于i在循环完成后,还自增了一次(i++是在console.log之后执行的)
例子5(函数作用域)
//1.虽然var在块里面会进行变量提升,但是var在函数内不会变量
//2.setTimeout方法是挂在window对象下的,作用域属于window
/* 对照1.
var声明的i在for中进行变量提升,提升到了window ;
而i传入了setTimeout定时器中,执行定时器时,执行函数function的作用域在window里面。
因此,定时器中的i的作用域也同样处在window里面,待for循环体累加完执行完后,
立即执行定时器的队列任务。
*/
for(var i=1;i<=3;i++){
setTimeout(function(){
console.log(i); //打印显示三个4
},0);
}
---------------------------------------这里是分割线-----------------------------------------
/* 对照2.
为了防止for循环的作用域被污染,在作用域内包裹一个函数体,var在函数作用域内不会进行变量提升;
注意下面的调用方式:(function(a){})(i); 函数传入参数i,被立即调用。
*/
for(var i=1;i<=3;i++){
(function(a){ //*注意1:这里是形参,根据下面调用的(i)那传过来的=》看注意2
setTimeout(function(){
console.log(a); //打印显示1 2 3
},0);
})(i); //*注意2:这里是实参,传给上面的(a)的=》看注意1
}
例子6(多级作用域嵌套)
/* 对照1. 正常let对照*/
let arr=[];
for(let i=1;i<=3;i++){
arr.push(function(){
return i;
})
}
console,log(arr[0]()); console,log(arr[1]()); console,log(arr[2]()); //依次打印 1 2 3
---------------------------------------这里是分割线-----------------------------------------
/* 对照2. 正常var对照*/
let arr=[];
for(var i=1;i<=3;i++){
arr.push(function(){
return i;
})
}
//因为i在块内的作用域会进行变量提升,所以i的作用域在window上,所以执行完i++后,window.i=4;
console,log(arr[0]()); console,log(arr[1]()); console,log(arr[2]()); //打印出来都是4
---------------------------------------这里是分割线----------------------------------------
/* 对照3. 增加函数作用域 */
let arr=[];
for(var i=1;i<=3;i++){
(function(i){
arr.push(function(){
return i;
});
})(i);
}
//因为i在函数作用域中不能进行变量提升,因此能正常执行循环
console.log(arr[0]()); console.log(arr[1]()); console.log(arr[2]()); //依次打印 1 2 3
1.2闭包
1.2.1闭包介绍
- 函数能访问到其他函数作用域中的数据,就称为是闭包。(我的理解)
- 内部的函数可以访问外部函数的变量,形成一个词法环境的组合(别人的解释)链接
1.2.2 闭包例子
例子1(获取区间值)
```javascript /*1.介绍数组filter
arr.filter(function(item, index,arr){ 判断筛选 return 筛选结果(数组形式); });
*/
let arr=[1,2,3,4,5,6,7,8,9,10];
function between(a,b){ return function(item,index,arry){ console.log(item,index,arry); //1.先调用between函数,返回匿名函数后,filter才进行传参(才有地方能进行传参) return item>=a&&item<=b; //2.由于使用了父级函数作用域的参数,形成了闭包 } } console.log(arr.filter(between(5,7)));
<a name="IOT7W"></a>
### 例子2(标志变量+闭包)
```javascript
/*对照1 点击按钮 按钮进行来回抖动 频率越来越高*/
let btns = document.querySelectorAll('button');
//1.选取所有按钮
btns.forEach(function (item) {
//2.点击事件
item.addEventListener('click', function () {
//3.点击10次,执行10次,并且进行10次初始化left为1
let left = 1;
setInterval(function () {
//4.所以疯狂点击同一个按钮,会出现来回抖动的情况
item.style.left = left++;
}, 400);
});
});
---------------------------------------这里是分割线----------------------------------------
/*对照2 点击按钮 按钮进行加速*/
let btns = document.querySelectorAll('button');
//1.选取所有按钮
btns.forEach(function (item) {
//2.将初始速度left放在点击事件作用范围外,这样在点击的时候不会进行初始化,就不会来回抖动
let left = 1;
//3.点击事件闭包,引用了点击事件环境外的left
item.addEventListener('click', function () {
setInterval(function () {
//4.可是疯狂点击同一个按钮,会出现疯狂加速的情况
//5.因为如果点击10次,就会执行10次点击事件,变量left始终因为闭包一直在同一个作用域内(forEach的匿名函数内)。因此,left就会加10次。
item.style.left = left++;
}, 400);
});
});
---------------------------------------这里是分割线----------------------------------------
/*对照3 点击按钮 始终进行匀速*/
let btns = document.querySelectorAll('button');
//1.选取所有按钮
btns.forEach(function (item) {
//2.设定标志变量
let bind = false;
//3.点击事件进行闭包,使用了bind变量
item.addEventListener('click', function () {
//4.如果!bind的结果为FALSE,那么if里面的语句就不会执行了,也就是不能进行加速了
if (!bind) {
bind = true;
let left = 1;
setInterval(function () {
item.style.left = left++;
}, 400);
}
});
});
例子3(闭包排序)
let orders = [{ lesson: 1, time: 24 }, { lesson: 2, time: 36 }];
//默认参数asc升序
function order(field, type = "asc") {
return function (a, b) {
if (type === "asc") {
//读取对象,进行比较,返回1进行升序排序
return a[field] > b[field] ? 1 : -1;
}
else {
//读取对象,进行比较,返回-1进行升序排序
return a[field] > b[field] ? -1 : 1;
}
}
}
let arr = orders.sort(order('lesson'));
let brr = orders.sort(order('time','des'));
例子4(闭包内存泄露解决)
/*dom节点操作先进行同步调用,点击事件进行异步调用后执行*/
let divs=document.querySelectorAll('div');
divs.forEach(function(item){
let desc=item.getAttribute('desc');
item.addEventListener('click',function(){
console.log(desc); //正常显示打印的值
console.log(item); //显示null(获取不了dom节点了)
});
item=null;
});
例子5(闭包return的函数找不到父级的this)
/*1.通过变量接收this,在return中找到父级的this*/
let data = {
user: "666",
get: function () {
let that=this;
return () => {
return that.user;
};
}
}
let a = data.get();
console.log(a());
---------------------------------------这里是分割线-------------------------
/*2.通过箭头函数,在return中找到父级的this*/
let data = {
user: "666",
get: function () {
console.log(this);
return () => {
return this.user;
};
}
}
let a = data.get();
console.log(a());
1.3递归
1.3.1递归介绍
1.3.2递归例子
例子1(求n的阶乘)
//题目:1*2*3*...*(n-1)*n
//关键是要进行return,不然会产生死循环
//倒着写:n*(n-1)*(n-2)*...*2*1
function fn(n) {
//相当于判断条件,n等于1的时候结束
if (n == 1) {
return 1;
}
//进行阶乘
return n * fn(n - 1);
}
例子2(求斐波那契数列)
// 求斐波那契数列序列值,输入第几位,显示对应的数字
// 1、1、2、3、5、8、13、21... 前面两数之和等于第三个数
原理举例:
//输入n的前面两项,就可以计算出n对应的值 (n-1 n-2)
// fb(5) = fn(4)+fn(3)
// fb(4) = fn(3)+fn(2)
// fb(3) = fn(2)+fn(1)
// fb(2) = fn(1)
// fb(1) = fn(1)
// 斐波那契函数
function fib(n) {
// 如上面的例子,第一位和第二位都为1,fib(1)=1,fib(2)=1
if (n == 1 || n == 2) {
return 1;
}
// 相当于一直循环,f(n)+f(n-1)+f(n-2)+f(n-3)+f(n-4).......f(3)+f(2)+f(1) over
return fib(n-1)+fib(n-2);
}