JS进阶内容详解

76 阅读14分钟

JS进阶

作用域

局部作用域和全局作用域

局部作用域

局部作用域:函数作用域(函数内部) 和 代码块作用域 { }

局部作用域声明的变量外部不能用

全局作用域

script中间

.js 文件之间

作用域链

作用域链本质上是最底层的变量查找机制

总结:

  1. 嵌套关系的作用域链串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域链能够访问父作用域,父级作用域无法访问子作用域

像链子一样从小往大依次查找

垃圾回收机制

全局变量一般不会回收,页面关闭时回收

一般情况下局部变量的值,不用了会被自动回收掉

引用计数法

问题就是,相互引用时引用次数永远不是0.永远回收不了,造成回收泄露

标记清除法

从根部定期扫描对象进行寻找,如在继续使用则不回收

不再继续使用即刻进行回收

闭包

闭包 = 内层函数 + 外层函数的变量

作用:封闭数据,提供数据,外部可以访问函数内部的变量

应用:实现数据的私有

引起的问题:内存泄漏

<script>
        function count(){
            let i = 0
            function fu(){
                i++
                console.log(`调用了${i}次函数`);
            }
            return fu
        }
        const fun = count()
    </script>

函数进阶

函数参数

arguments法

当不知道有几个形参时,采用arguments动态参数

注意:arguments只存在于函数之间,并且arguments时伪数组

<script>
        function getSum(){
            let s = 0
            for(let i = 0; i < arguments.length;i++){
                s += arguments[i]
            }
​
            console.log(s);
        }
        getSum(2,3,4,5,6)
    </script>
剩余参数法

arr代表的是真数组,多用剩余函数,写在函数内部

a,b,...arr 代表传过来的实参已经给a,b值了,arr代表的是剩余参数的数组

 <script>
       function getsum(a,b,...arr  /  ...arr){
            let s = 0
            for(let i = 0; i < arr.length;i++){
                s += arr[i]
            }
​
            console.log(s);
        }
        getsum(2,3,4,5,6)  //15
        
    </script>
展开运算符

把数组展开,多用于求数组最大值和最小值 和合并数组

格式和剩余函数一样。但是区别是,剩余函数用于函数内部,展开运算符主要在数组中展开

<script>
        const arr1 = [1,2,3]
        const arr2 = [4,5,6]
        console.log(Math.max(...arr1));
        console.log(Math.min(...arr1));
        const arr3 = [...arr1,...arr2]
        console.log(arr3);
​
    </script>

箭头函数

语法

<script>
        1.箭头函数基本语法
         const fn = () =>{
             console.log(111);
         }
         fn()
        2.当只有一个形参时可以省略小括号
         const fn = x =>{
            console.log(x);
         }
         fn(1)
        3.函数体只有一句话时可以省略大括号
         const fn = x =>   console.log(x)
         fn(1)
        4.函数有return返回值时可省略return
          const fn = x => x+x
         console.log(fn(1));
        5.可以直接利用字面量返回一个对象
        const fn = (uname) => ({uname:uname})
        console.log(fn('liu'));
    </script>

注意:

  • 箭头函数没有arguments动态参数。但是有剩余函数...argus
  • 箭头函数没有自己的this值,箭头函数中所使用的this都是来自函数作用域链,它的取值遵循普通普通变量一样的规则,在函数作用域链中一层一层往上找。

this指向问题

?????

解构赋值

数组解构

把数组的批量单元值赋给一系列的变量

 const [a,b,c] = [1,2,3]
        console.log(a);

情况:

支持多维数组结构

变量数值大于单元值数量,多于变量值赋值为undifined

变量数值小于单元值数量,不进行赋值,直接忽略

或者用剩余函数来解决

js中需要加封号的两种情况

立即执行函数

        (function t(){})();   //必须加封号
        (function t(){})()

数组结构,数组开头时必须加封号

        let a = 1
        let b = 2;          //必须加封号
        [b,a] = [a,b]
        console.log(a);
        console.log(b);

对象解构

把对象的一系列属性和方法赋值给变量

但是注意:变量名称必须和对象属性或者方法名称一样

        const {uname,age} = {uname:'pink',age:18}
        console.log(uname);
        console.log(age);

对象解析的变量名可以改名

 const {uname:name,age} = {uname:'pink',age:18}
        console.log(name);
        console.log(age);

数组对象解构

const pig = [{
            uname:'佩奇',
            age:5
        }]
        const [{uname,age}] = pig

多级对象解构

const pig = {
            uname:'佩奇',
            family:{
                father:'bb',
                mother:'mm'
            }
        }
        const {uname,family:{father,mother}} = pig
        console.log(uname);
        console.log(father);
        console.log(mother);

数组常见方法

forEach遍历数组

forEach((ele,index){ 函数体 })

用于遍历数组,但是与map唯一的区别就是没有返回值

数组元素是必须写的,但是索引号可写可不写

适用于遍历数组对象

map遍历数组修改数据

map用于修改数组的数据,并且返回一个新的数组,与foreach一样使用,但是foreach不返回值

const newArr = map(function(ele,index)){

}

<script>
        const arr = ['pink','red','green']
        //arr.map(function(ele数组元素 , index数组索引值){}
        const newarr = arr.map(function(ele , index){
            return ele + '颜色'
        })
        console.log(newarr);
    </script>

filter数组筛选

<script>
        // const arr = [1,2,3]
        // const newarr = arr.filter(function(item,index){
        //     return item >= 2
        // })
        // console.log(newarr);
        const arr = [1,2,3]
        const newarr = arr.filter(item => item >= 2)
        console.log(newarr);
    </script>

数组reduce方法求和

注意:有返回值,必须return

通过循环重新赋值进行求和

    <script>
        const arr = [1,5,9]
//无初始值的情况                               起始值  下一个值
        // const total = arr.reduce(function(prev,current){
        //     return prev + current
        // })
        // console.log(total);
//有初始值的情况
        //  const total = arr.reduce(function(prev,current){
        //     return prev + current
        // },10)   要加初始值,如果没有加0即可
        // console.log(total);
//箭头函数表示
        const total = arr.reduce((prev,current) =>  prev + current,10)
           
        
        console.log(total);
    </script>

数组find以及some

every 判断数组中满不满足条件,若有全部满足,返回true;有一个不满足,返回false

some 判断数组中满不满足条件,若有一个满足,返回true,若全部不满足,返回false

<script>
        const arr = [1,2,3]
     //every  判断数组中满不满足条件,若有全部满足,返回true;有一个不满足,返回false
        // const flag = arr.every(function(item){
        //     return item >= 2
        // })
        // console.log(flag);    //false
        //箭头函数
        // const flag = arr.every(item =>  item >= 2)
        // console.log(flag);     //false
    //some   判断数组中满不满足条件,若有一个满足,返回true
         const flag = arr.some(function(item){
            return item >= 2
        })
        console.log(flag);        //true
    </script>

from把伪数组转换为真数组

格式:Array.from(伪数组)

       const lis = document.querySelectorAll('ul li')
        const liss = Array.from(lis)
        console.log(liss);

join把数组转换为一个字符串、

console.log(newarr.join());  //小括号里面没有东西,则使用逗号分割    pink颜色,red颜色,green颜色
console.log(newarr.join(''));  //里面是空字符串,则连在一起,        pink颜色red颜色green颜色 
console.log(newarr.join('|'));  //里面是什么字符就用什么字符分割     pink颜色|red颜色|green颜色

深入函数

创建对象(三种)

构造函数

  1. 用来初始化对象,

  2. 使用场景:用于快速创建多个对象

  3. 约定:函数的命名以大写字母开头

    创建函数时必须要new

  • new关键字调用函数的行为称为实例化
  • 构造函数内部不需要写return有默认的返回值
  • 好用。但是有内存浪费问题
       function Pig(uname,age){
            this.name = uname
            this.age = age
        }
        const Peiqi = new Pig('佩奇','5')
        const Qiao = new Pig('乔治','5')
        const Ma = new Pig('猪妈妈','5')

实例成员和静态成员

实例成员

通过构造函数创建的对象是实例对象

实例对象的属性和方法成为实例成员(实例属性和实例方法)

构造函数创建的实例对象彼此独立互不影响

       function Pig(uname){
            this.name = uname
        }
        const peiqi = new Pig('佩奇')
        //把属性加到对象身上就是实例成员和方法
        peiqi.age = '4'
        // peiqi.say = () => {
        //     console.log('hi');
        // }
        peiqi.say = function(){
            console.log('hi');
        }
        console.log(peiqi);

静态成员

构造函数的属性和方法

      function Pig(uname){
            this.name = uname
        }
        Pig.eye = 3
        console.log(Pig.eye);

内置构造函数

Object

作用:Object.keys(对象)静态方法获取对象中所有属性(键)
Object.values(对象)静态方法获取对象中所有值

注意:返回的是一个数组

<script>
        const o = {
            uname:'pink',
            age:'13'
        }
        console.log(Object.keys(o));    // ['uname', 'age']
​
        console.log(Object.values(o));  // ['pink', '13']
</script>
Object.assign(被拷贝的对象,拷贝的对象)静态方法可以进行拷贝数值

使用:常用于添加属性

const o = {
            uname:'pink',
            age:'13'
        }
console.log(Object.keys(o));    // ['uname', 'age']
​
console.log(Object.values(o));  // ['pink', '13']
​
const oo = {}
Object.assign(oo,o)
console.log(oo);                //{uname: 'pink', age: '13'}
Object.assign(oo,{gender:'女'})
console.log(oo);                //{uname: 'pink', age: '13', gender: '女'}   //常用于添加属性

字符串常见方法

split(‘分隔符’) 用于把字符串转换为数组

<script>
        //split() 用于把字符串转换为数组。正好与join()方法相反
        //split括号里面是字符串之间分隔符
        const arr = 'pink,red'
        const str = arr.split(',')
        console.log(str);      //['pink', 'red']
        const arr1 = '2022-07-30'
        const str1 = arr1.split('-')  
        console.log(str1);       //['2022', '07', '30']
​
    </script> 

substing(起始位置,结束位置)用于分割字符串

 //substing(起始位置,结束位置)用于分割字符串
        //但是注意:结束位置输出内容不包括该索引号所对应的字符
        const a = 'pinkred'
        console.log(a.substring(0,3));    //pin
        console.log(a.substring(0,4));    //pink

startsWith('字符') 用于判断是否是以该字符开头

  //startsWith('字符')   用于判断是否是以该字符开头
        //startsWith('字符',数字)   用于判断是否是以该数字位置的字符开头
        //endsWith('字符')   用于判断是否是以该字符结尾
        const b = 'asdfsghjk'
        console.log(b.startsWith('a'));   //true
        console.log(b.startsWith('b'));   //false
        console.log(b.startsWith('f',3));   //true

includes('字符') 用于判断是否包含该字符

  // includes('字符') 用于判断是否包含该字符。但是切记区分大小写
        const c = 'asdfFhj'
        console.log(c.includes('s'));    //true

NUmber保留小数方法

toFixed() 括号里面保留几位小数。四舍五入

 const num = 10.234
        console.log(num.toFixed());

原型对象

构造函数里面的属性可以直接用,但是里面的方法不能共享,每创建一个对象就必须重新开辟构造函数中的方法的空间,会造成资源浪费,为了防止这种资源的浪费,我们把属性定义在构造函数内部,方法用原型对象 构造函数的名字.prototype.方法名 = function(){

console.log(11);

}来定义,所有new出来的 对象都可以使用,共享空间,不会造成浪费

构造函数都会自动生成原型对象

<script>
        function Star(name,age){
            this.name =  name,
            this.age =  age
            // this.sing = function(){
            //     console.log(11);
            // }
​
        }
​
        Star.prototype.sing = function(){
            console.log(11);
        }
        const ldf = new Star('ldf','12')
        const pink = new Star('pink','14')
        // console.log(ldf.sing === pink.sing);   //false
        console.log(ldf.sing === pink.sing);   //true
</script>
注意

原型的作用:共享方法。

把不变的方法定义在prototype中

构造函数和原型里面的this都指向实例化的对象

constructor属性

属于prototype的属性,可以指回原函数

使用场景:

当构造函数里面的方法过多时,直接给Star.prototype进行赋值,但是赋值之后就丢失了原本的constractor属性,找不到是哪个构造函数(相当于找不到 父亲)所以在给Star.prototype进行赋值后先要使constructor指向原来的构造函数 例如:constructor : Star,

<script>
        function Star(name){
            this.name = name
        }
        //正常应该这样写。但是当构造函数里面的方法过多时这样写比较繁琐
        // Star.prototype.sing = function(){
        //     console.log(11);
        // }
        // Star.prototype.dance = function(){
        //     console.log(22);
        // }
        //所以直接给Star.prototype进行赋值,但是赋值之后就丢失了原本的constractor属性,找不到是哪个构造函数(相当于找不到  父亲)所以在给Star.prototype进行赋值后先要使constractor指向原来的构造函数
        Star.prototype = {
            constractor : Star,    //重点
            sing: function(){
                console.log(11);
            },
            dance: function(){
                console.log(22);
            }
        }
        console.log(Star.prototype);
</script>

对象原型

每new一次实例化对象,都会生成对象原型 proto

对象原型指向原型对象

原型对象和对象原型都用constructor指回构造函数

原型继承

分三步

  1. 提取共有的部分
  2. 利用原型对象来继承
  3. 继承之后会把原有的constructor属性覆盖,重新把constructor属性指回
<script>
        function Person(){
            this.eyes = 2
            this.head = 1
        }
​
        function Woman(){
​
        }
​
        function Man(){
​
        }
        Woman.prototype.baby = function(){
            console.log(11);
        }
​
        //子类的原型 = new 父类()
        Woman.prototype = new Person()    //先利用原型对象来继承
        Woman.prototype.constructor = Woman    //继承之后会把原有的constructor属性覆盖,重新把constructor属性指回
        const a = new Woman()
        console.log(a);
​
​
        Man.prototype = new Person() 
        Man.prototype.constructor = Man
        const b = new Man()
        console.log(b);
    </script>

注意:

只要有对象就有proto

只要有原型对象就有constructor

instanceof 用于检测该对象是否在原型链上 对象 instanceof 原型链

深浅拷贝

浅拷贝

拷贝只能用于引用数据类型

浅拷贝只能拷贝一层的对象,不能拷贝对象里面包含的对象

对象拷贝 Object.assign(给谁拷贝,拷贝的对象)

const o = {...obj}

数组拷贝 Array.prototype.concat()

[...arr]

<script>
        const obj = {
            age:11,
            name:'dddd'
        }
​
        // 对象拷贝1.
        const o = {}
        Object.assign(o,obj)
        console.log(o);
​
        // 2.对象拷贝2
        const o1 = {...obj}
        console.log(o1);
​
        const arr = [1,2,3,4]
        const arr2 = []
        //数组拷贝1
        const arr3 = Array.prototype.concat(arr,arr2)
        console.log(arr3);    //[1,2,3,4]
​
        //数组拷贝2
        const arr4 = [...arr]
        console.log(arr4);
</script>

深拷贝

利用递归函数实现深拷贝
<script>
        //用递归函数实现setTimeout模仿setInterval
        function getTime(){
            document.querySelector('div').innerHTML = new Date().toLocaleString()
            setTimeout(getTime,1000)
        }
        getTime()
    </script>

深拷贝步骤:

  1. 先写深拷贝函数
  2. 判断数组,递归函数实现
  3. 判断对象,递归函数实现
<script>
        const obj = {
            uname:'pink',
            age:20,
            hobby:['蓝','足'],
            family:{
                baby:'oldpink'
            }
        }
        const o = {}
        function deepCopy(newObj,oldObj){
            for(let k in oldObj){
                //处理数组   k是属性名
                //先判断Array
                if(oldObj[k] instanceof Array){          //必须先判断数组然后判断对象,不能颠倒
                   
                    newObj[k] = []
                    
                    deepCopy(newObj[k],oldObj[k])    //此时的k指的是索引值
                    
                }else if(oldObj[k] instanceof Object){    //再判断Object,因为数组也属于Object
                    
                    newObj[k] = {}
                    
                    deepCopy(newObj[k],oldObj[k])
                } else{
                    
                    newObj[k] = oldObj[k]
                }
               
            }
        }
        
        deepCopy(o,obj)
        console.log(o);
        o.hobby = ['www']
        o.family.baby = 'newpink'
        console.log(obj);
    </script>
利用lodash在线链接

用 _.cloneDeep(obj)方法

<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
    <script>
        const obj = {
            uname:'pink',
            age:20,
            hobby:['蓝','足'],
            family:{
                baby:'oldpink'
            }
        }
        const o = _.cloneDeep(obj)
        console.log(o);
</script>
利用JSON深拷贝
 const o = JSON.parse(JSON.stringify(obj))
 console.log(o);

异常处理

throw抛出异常

throw抛出异常会中止程序,经常和 new Error()搭配使用

<script>
        function fn(x,y){
            if(!x || !y){
                throw new Error('没有参数传递')
            }
            return x + y
        }
        console.log(fn());
    </script>

try catch finally 捕获异常

this指向

普通函数

  • 通常情况下(非严格模式,若没使用 'use strict'),没找到直接调用者,则this指的是 window ;
  • 严格模式,没有直接调用者的函数中的this是 undefined;
  • this代表它的直接调用者(js中的this是执行上下文), 例如 obj.fun ,fun中的this就是obj;
  • 使用call,apply,bind(ES5新增)绑定的,this指的是绑定的对象;

箭头函数

  • 箭头函数无自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域,而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以让我们在 setTimeout ,setInterval中方便的使用this;
  • 在箭头函数中this指向的固定化,并非箭头函数内部有绑定this的机制,而是箭头函数无自己的this,导致内部的this就是外层代码块的this。

改变this指向

call,apply,bind方法最牛之处在于,call,apply,bind指向那个对象后,相当于那个对象可以用当前的方法,比如[].slice.apply(A),其实是让A数组使用slice方法
call方法
 <script>
        function fn(x,y){
            console.log(this);
            console.log(x+y);
        }
        fn.call(Object,1,2)
    </script>
apply方法
blind方法

注意:blind不调用函数,只是改变this指向,返回值是一个函数

let xiaowang = {
    name:'小王',
    gender:'男',
    age:22,
    say:function(school,grade){
        console.log(this); //{name: '小红', gender: '女', age: 22}
        console.log('姓名:' + this.name + ' 性别:' + this.gender + ' 年龄: ' + this.age + ' 在: ' + school + ' 上:' + grade)
    }
}
let xiaohong = {
    name:'小红',
    gender:'女',
    age:22
}
​
xiaowang.say.apply(xiaohong,["实验小学","六年级"]) //姓名:小红 性别:女 年龄: 22 在: 实验小学 上:六年级
xiaowang.say.call(xiaohong,"实验小学","六年级") //apply和call的区别,apply传参是数组的形式,是内包含,也会一一对应var ff=xiaowang.say.bind(xiaohong,"实验小学","六年级")
ff()  // bind返回的是一个新函数,我们还需要在调用一次
总结

重点:箭头函数,不能改变this指向,只有普通function函数,能改变this指向

改变this指向的方法

1, call()方法 语法: 函数.call(参数1,其他参数....可以是多个或者没有 ) 作用: 调用并且执行函数,同时,将函数的this指向,定义为指定的内容(参数1) 参数1,是改变的this的指向 其他参数,是原始函数的实参,原始函数有几个形参,此时就要对应的输入几个实参,没有形参,就没有实参

2, apply()方法 语法: 函数.apply(参数1,参数2) 只有两个参数 参数1:改变的this的指向内容 参数2:原始函数的实参,必须是一个数组的形式,将实参定义成数组的单元 其他用法和作用于 .call是相同的

总结: call方法与apply方法,作用,效果,都是完全一致的 只是对于原始函数的参数赋值方法,不同 call方法是通过其他多个参数来实现 apply方法是通过一个数组参数,来实现 两个方法没有本质的区别,爱用哪个用那个

3, bind()方法 语法: const 变量 = 函数.bind(参数1); 不是立即执行函数(下一篇博客有介绍 立即执行函数) 生成一个新的函数,这个新的函数是改变this指向之后的新的函数 参数1,定义的要改变的的this指向 其他参数,一般不定义,是使用函数原有的形参

总结: call apply 都是立即执行函数 参数1,都是改变的this指向 其他参数,是原始函数的形参(可以有,也可以没有) bind 不是立即执行函数,是生成一个新的函数 参数1,是改变的this指向 就使用原始函数的形参

const obj1 = {
    name:'张三',
    age:18,
    sex:'男',
}
​
const obj2 = {
    name:'李四',
    fun2 : function(){
        console.log(this);
    }
}
// 对象中的函数,this指向的是这个对象,obj2
obj2.fun2();
// 改变this指向,指向的是obj1这个对象
// 代用,并且执行fun2这个函数,同时将fun2的this指向,从原始的obj2,改变为obj1
obj2.fun2.call(obj1);
​
// 带有参数的函数,this指向的改变// 定义的带有参数的普通函数
function fun3(name,age,sex){
    console.log(name,age,sex,this);
}
// 执行时,输出实参,此时this指向是window
fun3('张三',18,'男');
​
// 改变this指向 , call方法
fun3.call(obj1,'李四',20,'女');
​
// 改变this指向 , apply方法
fun3.apply(obj1 , [ '王五' , 20 , '不知道' ])
​
// bind方法,不是立即执行函数,而是定义生成一个新的函数
// 新生成的函数,this指向是参数1
// 新生成的函数,形参是原始函数fun3的形参
const fun4 = fun3.bind(obj1);                      //注意fun4('王二麻子' , 100 , '不详');

防抖

有的操作是高频触发的,但是其实触发一次就好了,比如我们短时间内多次缩放页面,那么我们不应该每次缩放都去执行操作,应该只做一次就好。再比如说监听输入框的输入,不应该每次都去触发监听,应该是用户完成一段输入后在进行触发。

lodash里面的debounce函数,直接

_.debounce(函数体,毫秒值)

 <div class="box"></div>
    <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
    <script>
        const box = document.querySelector('.box')
        let i = 1
        function mouseMove(){
            box.innerHTML = i++
            console.log(i);
        }
        // box.addEventListener('mousemove',mouseMove)
        box.addEventListener('mousemove',_.debounce(mouseMove,500))
   </script>
防抖函数实现

思路:利用setTimeout定时器来实现

  1. 声明定时器变量
  2. 每次鼠标移动的时候都要先判断是否存在定时器,如果存在,先关掉定时器
  3. 如果没有,则开启定时器,存储在定时器变量里面
  4. 定时器里面有函数调用
<div class="box"></div>
    <script>
        const box = document.querySelector('.box')
        let i = 1
        
   //这是防抖函数执行的语句
        function mouseMove(){
            box.innerHTML = i++
            // console.log(i);
        }
​
   //这是一个封装好的防抖函数
        function debounce(fn,t){
            let timer
            return function(){                   //注意此处的写法,要用return返回一个函数才能达到一直调用的效果
                 if(timer) clearTimeout(timer)   //如果存在定时器,先关掉定时器
                 timer = setTimeout(function(){
                fn()
            },t)
            }
           
        }
​
   //新的函数写法,
        box.addEventListener('mousemove',debounce(mouseMove,500))
    </script>

节流

在500ms内,不管触发多少次事件,都只执行一次

使用场景:在高频鼠标移动或者resize变化时

节流就是减少流量,将频繁触发的事件减少,并每隔一段时间执行。即,控制事件触发的频率

用lodash节流函数throttle来做
 box.addEventListener('mousemove',_.throttle(mouseMove,3000))
节流函数
<div class="box"></div>
    <script>
         const box = document.querySelector('.box')
        let i = 1
        function mouseMove(){
            box.innerHTML = i++
            console.log(i);
        }
​
        function throttle(fn,t){
            let timer = null
            return function(){
                if(!timer){
                    timer = setTimeout(function(){
                        fn()
                        timer = null   //注意:这不能使用clearTimeout,因为在定时器内部关闭不了定时器,要想关闭定时器只                                               能赋值为null
                    },t)
                }
            }
        }
        box.addEventListener('mousemove',throttle(mouseMove,3000))
    </script>

总结

防抖和节流相同点:
  • 防抖和节流都是为了阻止操作高频触发,从而浪费性能。
防抖和节流区别:
  • 防抖是触发高频事件后n秒内函数只会执行最后一次,如果n秒内高频事件再次被触发,则重新计算时间。适用于可以多次触发但触发只生效最后一次的场景。
  • 节流是高频事件触发,但在n秒内只会执行一次,如果n秒内触发多次函数,只有一次生效,节流会稀释函数的执行频率。