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();

  1. <a name="pxd95"></a>
  2. ### 例子2(构造函数使用闭包)
  3. ```javascript
  4. function Hd(){
  5. let n=1;
  6. this.sum=function(){
  7. console.log(++n);
  8. }
  9. }
  10. /*1.因为已经声明创建的a是构造函数Hd的实例,所以不会重新声明和创建Hd函数*/
  11. /*2.并且需要注意,在实例化对象的时候,就已经在调用了一次Hd()函数*/
  12. let a=new Hd();
  13. /*3.所以疯狂调用a.sum()就会产生闭包效果*/
  14. a.sum();a.sum();a.sum();

例子3(块级作用域)

  1. //1.作用范围都在{}里面
  2. {
  3. let a=1;
  4. const b=1;
  5. }
  6. //2.作用范围提升到{}的外面
  7. {
  8. var a=1;
  9. }
  10. 相当于是在块的外面window中声明的a,在块内赋值,如下:
  11. window.a=undefined;
  12. {
  13. a=1; //window可以不写
  14. }

例子4(for循环)

  1. // 1.let的例子:
  2. for(let i=1;i<=3;i++){
  3. console.log(i); //依次输出1 2 3
  4. }
  5. console.log(i); //未定义undefined
  6. // 2.var的例子
  7. for(var i=1;i<=3;i++){
  8. console.log(i); //依次输出1 2 3
  9. }
  10. console.log(i); //输出4,由于i在循环完成后,还自增了一次(i++是在console.log之后执行的)

例子5(函数作用域)

  1. //1.虽然var在块里面会进行变量提升,但是var在函数内不会变量
  2. //2.setTimeout方法是挂在window对象下的,作用域属于window
  3. /* 对照1.
  4. var声明的i在for中进行变量提升,提升到了window ;
  5. 而i传入了setTimeout定时器中,执行定时器时,执行函数function的作用域在window里面。
  6. 因此,定时器中的i的作用域也同样处在window里面,待for循环体累加完执行完后,
  7. 立即执行定时器的队列任务。
  8. */
  9. for(var i=1;i<=3;i++){
  10. setTimeout(function(){
  11. console.log(i); //打印显示三个4
  12. },0);
  13. }
  14. ---------------------------------------这里是分割线-----------------------------------------
  15. /* 对照2.
  16. 为了防止for循环的作用域被污染,在作用域内包裹一个函数体,var在函数作用域内不会进行变量提升;
  17. 注意下面的调用方式:(function(a){})(i); 函数传入参数i,被立即调用。
  18. */
  19. for(var i=1;i<=3;i++){
  20. (function(a){ //*注意1:这里是形参,根据下面调用的(i)那传过来的=》看注意2
  21. setTimeout(function(){
  22. console.log(a); //打印显示1 2 3
  23. },0);
  24. })(i); //*注意2:这里是实参,传给上面的(a)的=》看注意1
  25. }

例子6(多级作用域嵌套)

  1. /* 对照1. 正常let对照*/
  2. let arr=[];
  3. for(let i=1;i<=3;i++){
  4. arr.push(function(){
  5. return i;
  6. })
  7. }
  8. console,log(arr[0]()); console,log(arr[1]()); console,log(arr[2]()); //依次打印 1 2 3
  9. ---------------------------------------这里是分割线-----------------------------------------
  10. /* 对照2. 正常var对照*/
  11. let arr=[];
  12. for(var i=1;i<=3;i++){
  13. arr.push(function(){
  14. return i;
  15. })
  16. }
  17. //因为i在块内的作用域会进行变量提升,所以i的作用域在window上,所以执行完i++后,window.i=4;
  18. console,log(arr[0]()); console,log(arr[1]()); console,log(arr[2]()); //打印出来都是4
  19. ---------------------------------------这里是分割线----------------------------------------
  20. /* 对照3. 增加函数作用域 */
  21. let arr=[];
  22. for(var i=1;i<=3;i++){
  23. (function(i){
  24. arr.push(function(){
  25. return i;
  26. });
  27. })(i);
  28. }
  29. //因为i在函数作用域中不能进行变量提升,因此能正常执行循环
  30. 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)));

  1. <a name="IOT7W"></a>
  2. ### 例子2(标志变量+闭包)
  3. ```javascript
  4. /*对照1 点击按钮 按钮进行来回抖动 频率越来越高*/
  5. let btns = document.querySelectorAll('button');
  6. //1.选取所有按钮
  7. btns.forEach(function (item) {
  8. //2.点击事件
  9. item.addEventListener('click', function () {
  10. //3.点击10次,执行10次,并且进行10次初始化left为1
  11. let left = 1;
  12. setInterval(function () {
  13. //4.所以疯狂点击同一个按钮,会出现来回抖动的情况
  14. item.style.left = left++;
  15. }, 400);
  16. });
  17. });
  18. ---------------------------------------这里是分割线----------------------------------------
  19. /*对照2 点击按钮 按钮进行加速*/
  20. let btns = document.querySelectorAll('button');
  21. //1.选取所有按钮
  22. btns.forEach(function (item) {
  23. //2.将初始速度left放在点击事件作用范围外,这样在点击的时候不会进行初始化,就不会来回抖动
  24. let left = 1;
  25. //3.点击事件闭包,引用了点击事件环境外的left
  26. item.addEventListener('click', function () {
  27. setInterval(function () {
  28. //4.可是疯狂点击同一个按钮,会出现疯狂加速的情况
  29. //5.因为如果点击10次,就会执行10次点击事件,变量left始终因为闭包一直在同一个作用域内(forEach的匿名函数内)。因此,left就会加10次。
  30. item.style.left = left++;
  31. }, 400);
  32. });
  33. });
  34. ---------------------------------------这里是分割线----------------------------------------
  35. /*对照3 点击按钮 始终进行匀速*/
  36. let btns = document.querySelectorAll('button');
  37. //1.选取所有按钮
  38. btns.forEach(function (item) {
  39. //2.设定标志变量
  40. let bind = false;
  41. //3.点击事件进行闭包,使用了bind变量
  42. item.addEventListener('click', function () {
  43. //4.如果!bind的结果为FALSE,那么if里面的语句就不会执行了,也就是不能进行加速了
  44. if (!bind) {
  45. bind = true;
  46. let left = 1;
  47. setInterval(function () {
  48. item.style.left = left++;
  49. }, 400);
  50. }
  51. });
  52. });

例子3(闭包排序)

  1. let orders = [{ lesson: 1, time: 24 }, { lesson: 2, time: 36 }];
  2. //默认参数asc升序
  3. function order(field, type = "asc") {
  4. return function (a, b) {
  5. if (type === "asc") {
  6. //读取对象,进行比较,返回1进行升序排序
  7. return a[field] > b[field] ? 1 : -1;
  8. }
  9. else {
  10. //读取对象,进行比较,返回-1进行升序排序
  11. return a[field] > b[field] ? -1 : 1;
  12. }
  13. }
  14. }
  15. let arr = orders.sort(order('lesson'));
  16. let brr = orders.sort(order('time','des'));

例子4(闭包内存泄露解决)

  1. /*dom节点操作先进行同步调用,点击事件进行异步调用后执行*/
  2. let divs=document.querySelectorAll('div');
  3. divs.forEach(function(item){
  4. let desc=item.getAttribute('desc');
  5. item.addEventListener('click',function(){
  6. console.log(desc); //正常显示打印的值
  7. console.log(item); //显示null(获取不了dom节点了)
  8. });
  9. item=null;
  10. });

例子5(闭包return的函数找不到父级的this)

  1. /*1.通过变量接收this,在return中找到父级的this*/
  2. let data = {
  3. user: "666",
  4. get: function () {
  5. let that=this;
  6. return () => {
  7. return that.user;
  8. };
  9. }
  10. }
  11. let a = data.get();
  12. console.log(a());
  13. ---------------------------------------这里是分割线-------------------------
  14. /*2.通过箭头函数,在return中找到父级的this*/
  15. let data = {
  16. user: "666",
  17. get: function () {
  18. console.log(this);
  19. return () => {
  20. return this.user;
  21. };
  22. }
  23. }
  24. let a = data.get();
  25. console.log(a());

1.3递归

1.3.1递归介绍

1.3.2递归例子

例子1(求n的阶乘)

  1. //题目:1*2*3*...*(n-1)*n
  2. //关键是要进行return,不然会产生死循环
  3. //倒着写:n*(n-1)*(n-2)*...*2*1
  4. function fn(n) {
  5. //相当于判断条件,n等于1的时候结束
  6. if (n == 1) {
  7. return 1;
  8. }
  9. //进行阶乘
  10. return n * fn(n - 1);
  11. }

例子2(求斐波那契数列)

  1. // 求斐波那契数列序列值,输入第几位,显示对应的数字
  2. // 1、1、2、3、5、8、13、21... 前面两数之和等于第三个数
  3. 原理举例:
  4. //输入n的前面两项,就可以计算出n对应的值 (n-1 n-2)
  5. // fb(5) = fn(4)+fn(3)
  6. // fb(4) = fn(3)+fn(2)
  7. // fb(3) = fn(2)+fn(1)
  8. // fb(2) = fn(1)
  9. // fb(1) = fn(1)
  10. // 斐波那契函数
  11. function fib(n) {
  12. // 如上面的例子,第一位和第二位都为1,fib(1)=1,fib(2)=1
  13. if (n == 1 || n == 2) {
  14. return 1;
  15. }
  16. // 相当于一直循环,f(n)+f(n-1)+f(n-2)+f(n-3)+f(n-4).......f(3)+f(2)+f(1) over
  17. return fib(n-1)+fib(n-2);
  18. }