原生JS第二,三周学习

125 阅读18分钟

day1

1.数据类型转换:

   1.转字符串:
       1.xx.toString();//xx不能是undefined和null - undefined和null不能调用方法和属性
       2.string(xx);//万能的,任何人都可以转为字符串,但是不重要
           原因:1.从页面获取的都是字符串
                  2.String();完全相当于隐式转换,还不如+“”;
   
   2.转数字:
       1.parseInt/Float(str);//专为字符串准备的,转为数字
           原理:从左向右一次读取每个字符,碰到第一个不是数字的字符就停止,开始就遇到不认识的字符则为NaN
                   int 不认识小数点,float认识第一个小数点
       2.Number:万能的,但是没什么用,完全相当于隐式转换 - 还不如-0 *1 /1
       
 3.转布尔:
     Boolean(x);//万能的,任何人都可以转为布尔,不会手动使用的,还不如:!!x
	   ***只有6个会为false0,"",undefined,null,NaN,false,其余都为true
	   在分支、循环的条件之中,以后不管代老师写的是什么,他都会悄悄的隐式转换为布尔值,你只需要考虑为true还是为false
               

2.运算符和表达式:

    1. *算术运算符:+ - * / %
	 特殊:1、%:取余,判断奇偶性
	       2、隐式转换:默认转为数字,在运算
			true->1
			false->0
			undefined->NaN
			null->0
		        特殊:1、+运算,碰到字符串,拼接
			      2、-*/%:字符串可以转为数字,但是纯数字组成才行,但凡包含一个非数字字符,则为NaN
    2.  *比较运算符:> < >= <= == != === !==
	  结果:布尔值
      隐式转换:默认,转为数字,再比较大小
          特殊:1.如果参与比较的两边都是字符串,则按位PK每一位的Unicode0-9<A-Z<a-z<汉字:第一个汉字:一:Unicode4E00 - ascii:19968
                                       最后一个汉字:龥:Unicode:9FA5 - ASCII40869
    3.undefined==null;//true;
          区分:undefined===null;
              === !==:不带隐式转换的等值比较和不等比较,要求数值相同,并且数据类型也要相同
                      function String(x){
			if(x===undefined){
				return "undefined";
			}else if(x===null){
				return "null";
			}else{
				return x.toString();
			}
		}
                    

3.逻辑运算

	&&:全部满足,才true	
            一个不满足,则false

	||:全部不满足,才false
	    一个不满足,则true

	 !:颠倒布尔值

	特殊:短路逻辑:如果前一个条件,已经能够得出最后结论了,则不看后续
		&&短路:如果前一个条件满足,才执行后续操作,如果前一个条件不满足,则不管后续操作
		  目的:简化【简单的】分支:1、一个条件一件事满足就做,不满足就不做 if(){}	  2、操作只能有一句话
			条件&&(操作);
		  举例:原来:if(money>=500){money*=0.8};
			现在:money>=500&&(money*=0.8);
                            
                            

day2

1.自定义function:

   什么是函数:需要先定义好,可以反复使用的代码段
   何时使用:1.不希望一打开页面立刻就执行,2.以后可以反复使用,3.希望可以绑定在页面元素上
   如何使用:
       1.创建并调用:
           1.创建:
               1.【声明方式】创建函数
                       function xx(){
                            函数体;
                            return ;
                               }
                2.【直接量方式】创建函数
                       var xx=function(){
                                  函数体;
                                  return ;
                                   }
           2.调用:
               var 接住返回的结果= 函数名(实参)
               //return 其实本意是退出函数,但是如果return 后面跟一个数据,顺便就会将数据返回到作用域的外部,但是return 只负责返回,不负责接住保存,因此,若是需要使用return 返回的值就需要声明一个变量接住return 。就算省略return 也会默认返回一个undefined;具体需不需要得到函数返回的结果,看自己需求
      2.作用域:
          1、全局作用域:全局变量 和 全局函数,在页面的任何一个位置都可以使用

	2、函数/局部作用域:局部变量 和 局部函数,在【当前函数调用时内部可用】

		*变量的使用规则:优先使用自己的,自己没有找全局,全局没有报错
		特殊:缺点:1、千万不要再函数中对着未声明的变量直接赋值 - 全局污染
			    2、局部可以用全局的,但是全局不能用局部的 - 解决:看上面return
      3.声明提前:
          在程序正式执行之前:
          将var声明的变量(轻)和function声明的函数(重)
          都会悄悄集中定义在当前作用域顶部
          但是赋值留在原地
          
          声明方式创建的函数会整个提前(第一种创建方式)
          直接量方式创建的函数不会整个提前,只会将变量部分提前
              何时使用:永远不会主动使用,干扰我们判断!  - 只会在笔试中遇到,为什么平时开发中跟本遇不到?
              原因:只要你遵守以下规则:
                  1.变量名和函数名尽量不要重复
                  2.先创建后使用
                  3.如果笔试中,先使用后声明多半是考变量提前
      4.按值传递:两个变量之间赋值,分两种情况
          1.如果传递的两个是原始类型的值:
              修改一个变量的值,另一个变量不会受到影响,其实是复制了一个副本给对方
          2.如果传递的是引用类型的值:
              修改一个变量的值,另一个也会受到影响,引用类型其实根本没有保存到变量中,仅仅是保存了一个变量值,
              两者使用的是同一个地址值,所以会相互影响

2.预定义全局函数:前辈们定义好的,我们只负责学习如何使用

*parseInt/Float/isNaN等等都是预定义全局函数


1、编码和解码
      问题:url中不允许出现多字节字符(汉字,utf-8编码格式下,一个汉字占3字节),如果出现会乱码
      解决:发送前,前端将多字节字符编码为单字节字符(数字、字母、符号)
	发送后,后端将单字节字符在解码为多字节原文
      如何:
	编码:var 不认识=encodeURIComponent("大梵");
	解码:var 原文=decodeURIComponent(不认识);
     这个东西没有用,在某一次浏览器更新后,当前就被淘汰了,浏览器自带此功能 - 唯一的用处,现在就是玩了:悄悄话
    2.2isFinite(num):判断num是不是有效范围 - 垃圾并不能用于判断是不是NaN,因为有三个人会是false
	哪些会为falseNaN,Infinity,分母为0
            
            

3、分支结构:根据条件的不同,选择部分代码执行

1if分支
2、三目&短路
3switch...case...语法
	switch(变量/表达式){
		case1:
		操作1;
		case2:
		操作2;
		case3:
		操作3;
		default:
		默认操作;
	}
	特殊:1、不具备隐式转换
	           2、问题:默认只要一个case满足后,就会将后面所有操作全部做一次
		解决:break;
		建议:每一个case的操作后都要跟上一个break
			有的地方可以不加break:
				1、最后一个操作default可以省略break
				2、如果中间连续的多个操作,是相同的,也可以省略掉中间部分
     
     ***面试题:if vs  switch
             1.switch:缺点:必须要直到最后结果才使用,不能做范围判断
                       优点:执行效率相对较高
             2.if:优点:可以做范围运算
                   缺点:执行效率相对较低
             
             建议:代码优化:尽量将if 替换为switch,三目或者短路
             
             
             
             

day3

1.循环结构

1.var 循环变量=几;
    while(循环条件){
            操作;
            变量变化;
    }
2.do{
    操作;
    变量条件;
        }while(循环条件)
   面试题:whiledo..while 的区别
       while是先判断再执行
       do ...while  是先执行再判断
3.forvar 循环变量;循环条件;变量变化){

}
4.退出循环语句:break5.死循环:whiletruefor(;;)
建议:死循环用while  知道循环次数用for循环
6.for each 和for infor of - 专门为数组遍历准备

2.数组

1.什么是数组:一个集合可以保存多个数据
    何时使用:多个相关数据,都要集中都要集中定义在一个集合中
    为什么:因为一个好的数据结构,能够极大地提高程序员的开发效率
2.创建:
    1.直接量方式:var arr=[值1..,值2...];
    2.构造数组方式:var arr=new Array(值1,,值2,...);
            坑:new array(num); - 它懂不起,会以为是创建了一个长度为num的数组
    3.访问:数组名[下标]
        添加/修改:数组名[下标]=新值;
        特殊:访问时,下标越界 - 返回undefined
            添加时,下标越界 - 变为稀疏数组,导致下标不连续,导致以后遍历一定会得到undefined
    4.数组的三大不限制:
        1.不限制类型
        2.不限制长度
        3.不限制下标越界 - 不推荐
   5.数组唯一的一个属性:数组名.length - 获取数组的长度
       三个固定套路:
           1.末尾添加:arr[arr.length]=新值;
           2.获取倒数第n个:arr[arr.length-n];
           3.缩容:arr.length-=n;
   6.遍历数组:对数组中每个元素执行相同或相似的操作
           for(i=0;i<arr.length;i++){
               arr[i];
           }
   7.如何释放一个引用类型:看清楚此引用类型有几个变量关联着每个变量释放后才能释放干净
       建议:我们代码尽量封装在函数中,函数中的所有变量会自动释放
       
       索引数组:下标都是由数字组成的数组
   8.关联数组(hash数组):下标可以自定义的数组
       为什么:索引数组的下标无具体意义,不方便查找
      如何使用:
          1.创建:1.创建一个空数组:var arr=[];
                  2.为空数组添加自定义下标并且赋值:arr[自定义下标]=新值
          2.访问:arr["自定义下标"]
          3.强调:hash数组;ength永久失效,永久为0;
              问题:hash数组不能使用for遍历,只能使用for in 遍历    语法:
                  for(var i in 数组名){
                      console.log(i)//自动获得当前数组的下标,不需要我们设置从哪儿开始,从哪儿结束
                      console.log(arr[i];//当前次元素
                      }
              牛逼处:不仅可以遍历hash数组,也可以遍历索引数组
              建议:hash用for in 索引用for 
          4.js中所有东西都是对象,万物皆对象,除了undefined和null

 3.数组中的API:数组中的函数,前辈们定义好的,只有*数组可以使用*
     1.arr转为 str
         1.语法:var str=arr.join(“自定义连接符”);
             作用:1.笔试时,给你一个给你一个数组,将他无缝拼接在一起
                   var arr=["h","e","e"];
                   var str=arr.join("");
                  2.拼接为页面元素:
                      //以后从数据获取元素
                          var cities=["-请选择-","北京","南京","西京","东京","重庆"]
                      //拼接为页面元素后,innerHTML是识别标签的
                          sel.innerHTML="<option>"+cities.join("</option><option>")+"</option>";
        2.拼接数组:添加元素的新方式:
            语法:var newArr=arr.concat(值1,...);
            特殊:1.此方法不会改变原数组,只会返回一个新数组
                2.支持传入数组参数,悄悄将我们的数组打散,不会变为二维数组。
        3.截取子数组:取出数组中想要的部分组成一个新数组
            语法:var subArr=arr.slice(starti,endi);
            t特殊:1.此方法不修改原数组,只会返回一个新数组
                   2.含头不含尾
                   3.第二参数也可以不写,截取到末尾
                   4.第一个参数也可以不写,两个参数都没写,从头截到尾 - 深拷贝(副本)
                   5.支持负数参数 - -1代表倒数第一个
       4.删除,插入,替换
           删除:var dels=arr.splice(starti,n);//n代表删除个数
               特殊:此方法也有返回值,返回的就是你删除的组成新的数组
           插入:arr.splice(starti,0,新值1,...);
               特殊:1.没有删除,也有返回值,返回的是一个空数组
                   2.原starti以及后续元素会向后移
                   3.不建议插入,数组会变得不伦不类
          替换:var dels=arr.splice(start,n,新值1,...);
              特殊:删除的个数和插入的个数不必相同
       5.反转数组:
           arr.reverse()//没什么大用
           
           

day4

Array API:

1.排序:
    1.笔试时,不使用数组API将数组进行升降排序 - 冒泡排序 -> 拿着数组中前一个和后一个作比较,前一个大于后一个则二者交换位置
        var arr=[32,14,43,453,6,58,56,531,5,57];
        for(i=0;i<arr.length;i++){
            for(j=0;j<arr.length-i;j++){
                var m;
                if(arr[j]>arr[j+1]){
                    m=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=m;
                            }
                        }
                        
                        
     2.数组API:arr.sort();
         默认:悄悄转为字符串,按位PK每个字符的unicode号,默认是按照字符串排序
         问题1:希望按照数组升序排列
         解决:arr.sort(function(a,b){
                 a-b;
                 });
           原理:1.匿名函数回调,一般是前辈们规定好的,我们只学习怎么使用,一般是自动调用,而且有多少对儿就调用几次
                   2.a:后一个数字,b:前一个数字
                   3.返回结果:如果是正数,说明a大
                               如果是负数,说明b大
                               如果是0,说明一样大
                   4.而我们的sort会根据你返回的值得正负来判断是否交换位置
         问题2:希望按照降序排列:
         解决:arr.sort(function(a,b){
                 b-a;
                 });
         建议:开发中使用API排序,鄙视中可能会遇到冒泡排序
         强调:切记前端中所有技术中:只有数组可以排序,如果遇到页面中需要排序,name他的底层一定是数组
         
 2.栈和队列:
     栈:其实就是一个数组,只不过只能要求从一端进出,另一端是封闭的
         何时:始终希望使用最新的数据时,现实中很少,代码中也很少
         如何:
             前进:arr.unshift(新元素) - 添加元素的新方式,建议不要添加数组
             前出:var first=arr.shift(); - 删除元素的新方式,只能删除第一个元素
             后进:arr.push(新元素); - 添加元素的新方式,只能向数组末尾添加一个
             后出:var last=arr.pop(); - 删除元素的新方式,只能删除数组最后一个元素
    队列:其实就是一个数组,要求一端进,另一端出
             何时:按照先来后到的顺序
             如何:
                 前进:arr.unshift(新元素) - 添加元素的新方式,只能向首位添加,建议不要添加数组
                 后出:var last=arr.pop(); - 删除元素的新方式,只能删除数组最后一个元素
                 
                 后进:arr.push(新元素); - 添加元素的新方式,只能向末尾添加一位元素
                 前出:var first=arr.shift(); - 删除元素的新方式,只能删除数组第一个元素
              
        

二维数组:数组的新元素,又引用着另一个数组

何时使用:在数组中,希望再次细分每个分类
如何使用:创建:
    var arr=[
                []
                []
                []
             ];
访问:arr[行下标][列下标]
特殊:列下标越界:返回undefined
    行下标越界:报错 虽然行下标越界确实会得到一个undefined,但是undefined没资格再加[]
遍历二维数组:必然使用两层循环,第一个控制行,第二个控制列
    
		for(var r=0;r<arr.length;r++){
			for(var c=0;c<arr[r].length;c++){
				console.log(arr[r][c]);
			}
		}
   总结:ES3的数组学习完毕
       2.数组API:10个(concat,join,slice,splice(删,替换,插入),sort,shift,unshift,push,pop);
       3.二维数组

字符串

1.什么是字符串:多个字符组成的【只读】字符【数组】
    1.只读:所有字符串的API都不会改变原数组,只会返回一个新数组
    2.数组:和数组有相同点:
        1.可以遍历
        2.可以使用length
        3.可以使用下标获取字符
        4.数组不修改原数组的API,字符串也可以使用(concat:还不如用+拼接,slice);
    当然,数组中修改原数组的API,字符串不能使用
引用/对象类型:11个:
    *String,Number,Boolean - >具有包装类型
    Array,function,Math,Date,*RegExp(z正则表达,验证用的)
    Error:错误
    Object:对象
    Global:全局对象 - 在前端/客户端/浏览器端 js被window代替了:功能:保存了全局变量和全局函数,只有window可以省略
  3.包装类型:专门用于将一个原始类型封装为引用类型的对象(带了属性和方法)
      为什么:原始类型原本是不具备属性和方法的,但是前辈们发现我们常操作字符串,因此为三个原始类型提供了包装类型
      何时使用:只要试图用原始类型的变量去用.调用方法和属性时,就会悄悄地转为引用类型
      何时释放:方法调用完毕,自动释放包装类型,并且返回数据(又会变为原始类型)
        为什么undefinednull,没有提供包装类型

day5

1.正则表达式

什么是:定义字符串中【字符出现规则】的一个表达式
何时使用:切割,替换,【验证】
如何使用:语法:/正则表达式/
    1.最简单正则:关键字原文     “” -> /no/gi 只要用上正则就可以加上后缀
        g:全部 i:忽略大小写
    2.备选字符集:/[备选字符集]/
        强调:1.一个中括号,只管一个字符
                2.正则表达式【默认只要满足条件,不管其他的了】,解决:前加^后加$:/^[]$/
                    代表要求:用户从头到尾必须完整匹配我们的要求 - 只要是做验证就必须前加^后加$
                   特殊:如果备选的ASCII码是连续的,中间部分可以用-代替
                       一位数字:[0-9]
                       一位字母:[a-z],[A-Z]
                       一位汉字:[\u4e00-\9fa5]
                
    3.预定字符集:前辈们定义好的,我们直接使用的
        目的:简化备选字符集
            一位数字:\d
            一位数字,字母,下划线 :\w
            一位空白字符:\s
        建议:优先使用备选字符集,备选字符集满足不了再用自定义字符集
        问题:不管备选字符集还是自定义字符集,都只管一位
   4.量词:规定一个字符集出现的次数
       有明确数量:
           {m,n}前边相邻字符集,最少出现m次,最多出现n次
           {m,}前边相邻字符集,最少出现m次,最多不限
           {n}前边相邻字符集,必须出现n次
           
       无明确数量:
           ?:前边相邻字符集,可有可无,做多出现一次
           *:前边相邻字符集,可有可无,多了不限
           +:前边相邻字符集,至少出现一次,多了不限
  5.选择和分组:
      选择:多个规则中选择一个
          规则1|规则2
      分组:将多个字符集临时组成一组自规则
          (规则1|规则26.指定匹配位置:
       ^:开头
       $:结尾
       特殊:如果两者同时出现,表示要从头到尾完全匹配 - 验证时必须加上
       
  7.密码验证:4位,数字和字母,必须出现一位
      /^[0-9A-Za-z]{4}$/
      预判公式:(?![0-9]+$) -> 不能完全由数字组成
                 (?![a-z]+$) ->不能完全由小写组成
                 (?![0-9a-z]+$) ->不能全由数字组成,也不能全由小写组成,必须是小写和数字的组合
                 (^(?![0-9a-z]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{4}$/; - 4位,数字和字母,必须出现一位大写和一位小写
                 (^(?![0-9a-z]+$)(?![A-Za-z]+$)(?![A-Z0-9]+$)[0-9a-zA-Z]{4}$/
      

2.字符串支持正则表达式的API

    1.切割:
        var arr=str.split("固定切割符"/regexp,"新内容");
    2.替换:
        1.基础替换法:var arr=str.splace("固定关键字"/regxp,“新内容”);
        2.高级替换法:
            var arr=str.splace("固定关键字"/regxp,function(a,b,c){
                    console.log(a);//第一个形参会保存到正则匹配到的关键字
                    console.log(b)//第二个形参保存的是正则匹配到的每一个关键字的第一个下标
                    console.log(c);//原文本身
                    return a==arr.length?"**":"***";
                        })
        3.格式化:
            在使用replace替换时,如果搭配上了正则,并且正则中加入分组,name我们的高级替换法就会多很多形参,有几个分组就会多几个形参
              var newStr=id.replace(reg,function(a,b,c,d,e,f,g,h){
////				console.log(a);//第一个形参保存的是正则匹配到的关键字
////				console.log(b);//第二个形参会保存第一个分组匹配到的关键字
////				console.log(c);//第三个形参会保存第2个分组匹配到的关键字
////				console.log(d);//第四个形参会保存第3个分组匹配到的关键字
////					       //....说不清楚有多少个,具体看有多少个分组
////				console.log(e);//关键字下标
////				console.log(f);//原文本身
//				return c+"年"+d+"月"+e+"日";
//			})

3.正则表达API:

    1、创建正则对象:
                *直接量方式:var reg=/正则表达式/后缀;
	构造函数方式:var reg=new RegExp("正则表达式","后缀")

	为什么有时候要加 前加^后加$,为什么有时候又要加后缀g
		1、验证:前加^后加$ - 用户要和我们的规则完全匹配
		2、替换:加后缀g
	
2、API:var bool=reg.test(用户输入的东西);
	返回true,说明用户验证成功,否则验证失败
            

day6

1.Error:错误

    1、***浏览器自带四种错误类型:可以快速找到自己的错误
语法错误:SyntaxError - 符号写错了
引用错误:ReferenceError - 没有创建就去使用了
类型错误:TypeError  - 不是自己的方法,你却去使用了,最典型的,就是你们经常会undefined.xxxnull.xxx
范围错误:RangeError - 只有一个API会碰到:num.toFixed(d);//d的取值范围只能是0~100之间

2、只要发生错误,就会报错,会导致后续代码不执行(如果APP报错,那会直接闪退),我们程序不希望报错
错误处理:就算发生错误,也不会报错,不希望抛出错误,而是希望给一个错误提示即可,后续代码依然可以继续允许
语法:
   try{
	只放入你可能出错的代码
   }catch(err){
	发生错误的时候才会执行
	console.log(err);
   }

try...catch...的性能非常差,几乎里面的代码效率会被降到最低,所以不推荐使用
*可以用一个技术代替:if...else... 提前预判
*开发经验:一切的用户都是坏人,都要防一手(!isNaN(x)、正则:把用户控死)

3、抛出自定义错误:
        throw new Error("自定义提示");

function:函数对象:提前创建好的,以后可以反复使用的代码段

    1.创建:3种
        声明方式:function xx(){} - 完整提前
        直接量方式:var xx=function(){} 
        构造函数方式:var 函数名 =new function(“形参,...”,函数体;return 返回值);
    2.调用:如果有return 记得接住返回值
        var 结果=函数名(实参)
    3.考点:
        1.创建函数的三种方式
        2.作用域:变量的使用规则:优先使用自己的,自己的找不到找全局
        3.声明提前:变量和声明的函数会提前到当前作用域的顶部,赋值留在原地
        4.按值传递
        5.重载 overlord:相同的函数名,根据传入的形参不同自动选择不同函数执行
            为什么:减轻程序员负担
            问题:JS不支持重载
            JS不允许多个同名函数同时存在,如果存在会覆盖之前的函数
            解决:在【函数中】有一个arguments对象,不需要我们创建,自带
                什么是arguments:是一个类数组对象,但是和数组有三个相同点
                    1.都可以使用下标
                    2.都可以使用length
                    3.都可以遍历
                 作用:可以接住传入函数的实参,哪怕不写形参也行
                 可以做的事:
                     1.实现重载:可以通过在函数内部判断arguments的不同,执行不同操作
                     2.可以不写形参
                     3.正式开发中,可能会将多个函数整合为一个函数 - 代码优化
         6.匿名函数:没有名字的函数
             1.匿名函数的字调:此函数只会执行一次,执行完毕会自动释放优点:自动释放 - 代替全局写法,提升网站性能
                 (function(){
                                var a=2;
                                console.log(a);

                                btn.onclick=function(){
                                        console.log("释放了码");
                                }
                                //引用类型:在还有人使用的情况下,是不会自动释放的
                        })();
             2.匿名函数的回调:将函数作为实参,传递给其它函数使用 - 没有名字的函数就是匿名函数,匿名函数只有自调和回调
                 1.学习回调函数的目的:知道哪些是回调函数
                     arr.sort(function(a,b){return a-b;})
			str.replace(reg,function(){})
			btn.onclick=function(){}
                            .
                            .
                            .
                            这些东西都是规定好的,只学习如何使用
                            
                 2.ES6 - 一切的回调函数都可以简化为箭头函数
                 了解回调函数的原理
                 
        7.*****闭包
            1.function
                作用域:
                    1.全局:成员,随处可以使用,缺点:容易被污染
                    2.局部:成员,只能在函数内部使用,不会被污染 缺点:一次性的,用完会被释放
                函数的执行原理:
                    1.程序加载时:
                        创建执行环境栈(ECS):保存函数调用顺序的数组
                        首先压入全局环境(全局EC)
                        全局EC饮用着全局对象window
                        window中保存着全局变量
                    2.定义函数时:
                        创建函数对象:封装代码段
                        在函数对象中有一个scope(作用域)属性:记录着函数作用域来自哪里
                        全局函数的scope都是window
                    3.调用前:
                        在执行环境栈(ECS)压入新的EC(函数EC)
                        创建活动对象(AO):保存着本次函数调用时用到的局部变量
                        在函数的EC中有一个scope chain(作用域链)引用AO
                        AO中有一个parent属性是函数scope引用对象
                    4.调用时:正是有了前三步,我们才有了变量的使用规则:先使用自己的,自己没有找全局,全局没有报错
                    5.调用完:函数的EC会出栈,没人引用AOAO会自动释放,所以局部变量也就自动释放了

两链一包

    作用域链:以函数的EC中的scope chain属性为起点,经过AO,逐级引用,形成的一条链式结构,就称之为作用域链
        变量的使用规则,查找变量
   闭包:希望保护一个【可以反复使用的局部变量】的一种词法结构,其实是一个函数,只是写法和以前有点区别
       何时使用:希望可以保护【可以反复使用的局部变量】
       如何使用:41.两个函数相互嵌套
           2.外层函数创建出受保护的变量
           3.外层函数要return返回内层函数
           4.内层函数要操作受保护的变量
      强调:
          1.判断是不是闭包:有没有两个函数嵌套,外层创建受保护的变量,返回内层,内层操作受保护的变量
          2.外层函数调用了几次,就创建了几个闭包,受保护的变量就多了几个副本
          3.同一次外层函数的调用,返回的内层函数,都是在操作同一个受保护的变量
     缺点:受保护的变量,永远不会被释放,食用过多,会造成内存泄漏
     使用场景:防抖节流 - 有三个事件需要做防抖节流:执行速度非常快,减少DOM树的渲染
                 1.elem.onmousemove - 鼠标移动事件
                 2.input.oninput - 输入框内容有变化就会触发
                 3.window.onresize - 当前窗口尺寸发生变化就会触发 - JS的媒体查询
              公式:
                  elem.on事件名=function(){
                      inner;
                          }
                      function fdjl(){
                              var timer=null;//定时器的序号
                              return function(){
                                     if(timer){clearTimeout(timer);timer=null;}
                                     timer=setTimeout(function(){
                                                 //操作
                                          },1000)
                                        }
                                      }
                                      var inner=fdjl();