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(构造函数使用闭包)```javascriptfunction 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)那传过来的=》看注意2setTimeout(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为1let 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.点击事件闭包,引用了点击事件环境外的leftitem.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*1function 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)=1if (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) overreturn fib(n-1)+fib(n-2);}
