es6新特性及性能优化

154 阅读10分钟

ES6新特性

变量和常量

es6 新增了两种存储数据的容器,一个是 let 一个 const 常量

let :变量, 可以修改数据

const :常量 不可以修改数据

let 关键字声明的变量没有变量提升的概念,只要在 let 关键字声明之前使用的会直接报错

并且 let 关键字有var关键字没有的块级作用域,只要被包裹在 if 或者 for 内的变量 (包含 for 循环的计数器)都可以生成一个独立的作用域,这个作用域和局部作用域一样,定义在其中的变量都不会被作用域外面访问到,这个作用域就叫做块级作用域

cosnt 关键字无法修改数据,除了存储的引用数据类型修改值以外的所有参数都会报错,const 关键字只有一次修改数据的机会,只能在声明的时候才能够修改数据,初始化的时候如果不给值,那么默认的值就是 undefined,即这个常量失去了意义

解构赋值

数组解构赋值

1.给变量赋值

let [num1,num2,num3,num4] = [10,20,30,40];

值和变量名之间必须一一对应,如果不是一一对应的关系,则会赋值 undefined 如果变量名多于值的数量,则也会是 undefined

2.变量给数组赋值


let arr = [num1,num2,num3,num4......] 实际就是直接把变量的数据直接赋值给数组下标元素


 // 数组给变量赋值
        // let [num1, num2, num3, num4] = [10, 20, 30, 40];
        // console.log(num1, num2, num3, num4); //10 20 30 40
        // 如果超过值的个数
        let [num1, num2, num3, num4, num5] = [10, 20, 30, 40];
        console.log(num1, num2, num3, num4, num5); //10 20 30 40 undefined 则是undefined

        // 变量给数组赋值
        let arr = [num1, num2, num3, num4];
        console.log(arr); //(4) [10, 20, 30, 40]

对象解构

变量名必须和对象名是一一对应的关系,如果不一样,则会赋值undefined

可以自定义变量名,只需要给和对象相同的对象名上,赋值一个属性即可

   let {
             name,
             age: god,
             hobby
         } = {
            name: 'ikun',
             age: 18,
             hobby: ['吃饭', '睡觉', '打豆豆']
         };

用变量给对象赋值的时候,如果对象名和变量名相同,可以省略对象名不写,直接使用变量名
 // 1.对象给变量赋值

        // let {
        //     name,
        //     age,
        //     hobby
        // } = {
        //     name: 'ikun',
        //     age: 18,
        //     hobby: ['吃饭', '睡觉', '打豆豆']
        // };
        // console.log(name, age, hobby); //ikun 18 (3) ['吃饭', '睡觉', '打豆豆']
        // 如果变量名和对象属性名对不上

        // let {
        //     name,
        //     god,
        //     hobby
        // } = {
        //     name: 'ikun',
        //     age: 18,
        //     hobby: ['吃饭', '睡觉', '打豆豆']
        // };
        // console.log(name, god, hobby); //ikun undefined (3) ['吃饭', '睡觉', '打豆豆'] 则会赋值Undefined

        // 当对不上的情况下自定义变量名

        // let {
        //     name,
        //     age: god,
        //     hobby
        // } = {
        //     name: 'ikun',
        //     age: 18,
        //     hobby: ['吃饭', '睡觉', '打豆豆']
        // };
        // console.log(name, god, hobby); //ikun 18 (3) ['吃饭', '睡觉', '打豆豆'] 只需要给对象解构一个同对象名的变量名即可

        // 变脸给对象赋值

        let name = 'ikun';
        let age = 18;
        let hobby = ['吃饭', '睡觉', '打豆豆'];

        // let obj = {
        //     name: name,
        //     age: age,
        //     hobby: hobby,
        // }
        // console.log(obj); //{name: 'ikun', age: 18, hobby: Array(3)} 

        // 如果变量名和属性名相同,可以省略属性名不写
        // let obj = {
        //     name,
        //     age,
        //     hobby,
        // }
        // console.log(obj); //{name: 'ikun', age: 18, hobby: Array(3)}  同上
        // 这种办法也可以用在对象函数上
        let obj = {
            name,
            age,
            hobby,
            fn() {
                console.log(11);
            }
        }
        console.log(obj);
        obj.fn(); //11 其原因就是可以写成 fn : function fn() {} 即属性名和方法名重名,可以省略不写

函数解构

函数解构的实质,就是实参给形参赋值

        // 函数解构的实质就是实参给形参赋值

        // let num1, num2, num3;

        // function getData(n1, n2, n3) {
        //     num1 = n1;
        //     num2 = n2;
        //     num3 = n3;
        // }
        // getData(10, 20, 30);
        // console.log(num1, num2, num3); //10 20 30
        // 可以利用这个特性给变量赋值

        // function getData(obj) {
        //     let {
        //         num1,
        //         num2,
        //         num3
        //     } = obj;
        // }
        // getData({
        //     name: 'ikun',
        //     age: 18,
        //     hobby: ['吃饭', '睡觉', '打豆豆'],
        // });
        // console.log(num1, num2, num3); //函数解构.html:35 Uncaught ReferenceError: num1 is not defined
        // at 函数解构.html:35 因为变量只能在函数被拿到
        // 所以:
        function getData(obj) {
            let {
                name,
                age,
                hobby
            } = obj;
            console.log(name, age, hobby);
          
        getData({
            name: 'ikun',
            age: 18,
            hobby: ['吃饭', '睡觉', '打豆豆'],
        }); // ikun 18 (3) ['吃饭', '睡觉', '打豆豆']说白了还是对象解构赋值

函数的默认参数

当数据传递至函数的参数的时候,有一些不确定有没有参数,所以需要设置默认参数以防止报错

// es5默认参数

        // function getData(a, b) {
        //     a = a || 1;
        //     b = b || 1;
        //     console.log(a + b);
        // };
        // getData(10, 20); // 30 如果有参数则传递参数
        // getData(); // 2 如果没有则以后面赋值的参数为准


        // es6默认参数
        function getData(a = 1, b = 1) {
            console.log(a + b);
        };
        getData(10, 20); // 30 如果有参数则传递参数
        getData(); // 2 如果没有则以后面赋值的参数为准

 function getSum(num1, num2) {
            console.log(num1 + num2);
        }
        getSum(); //NaN 因为形参没有被定义,传入的值是undefined,会触发数字类型的隐式转换,undefined会被转换成NaN


function getSum(num1 = 10, num2 = 10) {
            console.log(num1 + num2);
        }
        getSum(); //20 给函数添加默认参数后,如果有实参则调用实参,如果没有实参,则调用等号后面的默认参数

箭头函数

es6新函数 箭头函数

语法:

(形参)=>{函数体}

如果形参只有一个,那么可以省略括号,即

arg => {函数体}

如果函数体只有返回值,可以省略大括号和return,且省略了大括号就必须得省略return

arg => 函数体

this 指向问题

箭头函数没有 this ,所以不存在有任何的 this 指向问题,上下文的 call apply bind 方法都会失效, 注:因为没有this,所以如果打印 this 会指向上一个作用域的 this

作用域等级:

全局作用域 0级

    函数作用域 1级

        对象方法作用域 1级

            函数中的函数的作用域 2级

箭头函数的作用域会跳过自己应该有的函数作用域,直接打印上一个作用域的调用者

 let obj = {
            name: '张三',
            age: 18,
            sayHi() {
                // 一级链
                let fn1 = function () {
                    // 二级链
                    console.log(this)
                }
                fn1() //普通函数的this指向指向了函数的调用者,因为fn1的调用者是window,所以这里的this打印window
                //或者直接看调用者,普通函数看的是调用者嘛,这里的调用者被省略掉了,所以应该是window

                let fn2 = () => {
                    // 没有this,所以直接跳过了本应该是二级链,直接上了一级链
                    console.log(this)
                }
                fn2() //这里的this指向了一级链,也就是obj
            },
            eat: () => {
                // eat没有this,所以本应该是1级链,却跳过自身的this指向,直接指向了0级链
                let fn1 = function () {
                    // eat没有this,所以本应该是1级链,却跳过自身的this指向,直接指向了0级链 ,也就是window
                    console.log(this)
                }
                fn1()

                let fn2 = () => {
                    // eat没有this,所以本应该是1级链,却跳过自身的this指向,直接指向了0级链 ,也就是window
                    console.log(this)
                }

                fn2()
            }
        }
        obj.sayHi()
        obj.eat()



        // 箭头函数的this指向问题.html: 17
        // Window {
        //     window: Window,
        //     self: Window,
        //     document: document,
        //     name: '',
        //     location: Location,
        //     …
        // }
        // 箭头函数的this指向问题.html: 23 {
        //     name: '张三',
        //     age: 18,
        //     sayHi: ƒ,
        //     eat: ƒ
        // }
        // 箭头函数的this指向问题.html: 31
        // Window {
        //     window: Window,
        //     self: Window,
        //     document: document,
        //     name: '',
        //     location: Location,
        //     …
        // }
        // 箭头函数的this指向问题.html: 37
        // Window {
        //     window: Window,
        //     self: Window,
        //     document: document,
        //     name: '',
        //     location: Location,
        //     …
        // }

拓展运算符和set数据结构

扩展运算符

拓展运算符

1.语法 ...

2.相当于遍历对象的简写

3.场景:常用于添加数组

 function arg(num1, num2, num3, num4, num5) {
            return arguments;
        };

        // let arr = [...arg(10, 30, 20, 60, 100)]; //扩展运算符可以把伪数组转换成真正的数组
        // // console.log(arr);
        // // 数组合并
        // console.log([...arr, ...arg(10, 30, 20, 60, 100)]);
        // //高级版数组合并
        // arr.push(...arg(10, 30, 20, 60, 100));
        // console.log(arr); //

        // set数据结构
        // 1.声明:
        // let set = new Set([...arg(10, 30, 10, 20, 60, 20, 100)]); //set数据结构要求传入一个数组,伪数组也可以
        // console.log(set);
        // // 2.添加方法
        // set.add(50);
        // console.log(set);
        // // 3.删除方法
        // set.delete(50);
        // console.log(set);
        // // 4.清空方法
        // set.clear();
        // console.log(set);

        /* 
            Set(5) {10, 30, 20, 60, 100}
            扩展运算符和set数据结构.html:28 Set(6) {10, 30, 20, 60, 100, …}
            扩展运算符和set数据结构.html:31 Set(5) {10, 30, 20, 60, 100}
            扩展运算符和set数据结构.html:34 Set(0) {size: 0}
        */
        //    利用set数据结构实现数组去重

        // console.log([...new Set([...arg(10, 30, 10, 20, 60, 20, 100)])]); //(5) [10, 30, 20, 60, 100]利用set数据结构的唯一性
        // 另 ...拓展运算符也可以展开字符串
        // let str = '12345';
        // console.log(...str); //1 2 3 4 5
        // 也可以把字符串转换成数组
        let str = '12345';
        console.log([...str]); //(5) ['1', '2', '3', '4', '5']

set数据结构

set数据结构

特性:set数据结构里面的数据不会重复存储,所以这种数据结构常常用来做数组去重

1.声明:
         let set = new Set([...arg(10, 30, 10, 20, 60, 20, 100)]); //set数据结构要求传入一个数组,伪数组也可以
         console.log(set);

2.添加方法
         set.add(50);
         console.log(set);

3.删除方法
         set.delete(50);
         console.log(set);
         
4.清空方法
         set.clear();
         console.log(set);

         
            Set(5) {10, 30, 20, 60, 100}
            扩展运算符和set数据结构.html:28 Set(6) {10, 30, 20, 60, 100, …}
            扩展运算符和set数据结构.html:31 Set(5) {10, 30, 20, 60, 100}
            扩展运算符和set数据结构.html:34 Set(0) {size: 0}
        
            利用set数据结构实现数组去重

         console.log([...new Set([...arg(10, 30, 10, 20, 60, 20, 100)])]); //(5) [10, 30, 20, 60, 100]利用set数据结构的唯一性
         另 ...拓展运算符也可以展开字符串
         let str = '12345';
         console.log(...str); //1 2 3 4 5
         也可以把字符串转换成数组
        let str = '12345';
        console.log([...str]); //(5) ['1', '2', '3', '4', '5']

防抖和节流阀

防抖

1.什么是防抖

所谓的防抖,其实就是防止抖动的意思,因为普通情况下存在一些用户可能造成的误操作的问题,例如手风琴效果,这种如果不设置防抖,就会可能造成用户的误操作,给用户带来体验问题

2.为什么需要防抖

为了推高用户的体验

3.怎么设置防抖函数

利用定时器原理,当用户触发对应的事件后,利用设置定时给予一定的操作延时(通常是300-500ms),然后用户在防抖时间内继续操作,则移除这个定时器,如果用户不继续操作,则开启定时器,调用定时器内部的定时器函数

原理总结:定时器函数的延时性,控制用户的连续操作


 //2.注册事件
  //2.1 鼠标移入
  // 首先定时器是必须要的
  // 如果不设置防抖,只要用户鼠标移动到任何的选项都会触发事件效果,如果这种事件效果可能会产生数据的交互会造成用户的体验下降,服务器的压力增加的问题
  // 所以需要给每个事件添加响应时间
  // timeid需要设置为全局变量,因为第一个可以重复使用这个定时器,第二个需要移除定时器
  let timeId;
  for (let i = 0; i < liList.length; i++) {
    liList[i].onmouseenter = function () {
      clearTimeout(
        timeId
      ); //但是仅仅添加了时间的响应时间还是不够的,因为同样也会触发这个时间,所以为了彻底解决问题,需要在用户触发的时候移除定时器,这样在用户进行选择操作的时候只要鼠标移动到了其他的选项卡上就会移除掉上一个定时器,即不会触发上一个时间,只有用户的鼠标停留在某一个选项卡上超过300ms后才会触发时间
      timeId = setTimeout(() => {
        for (let j = 0; j < liList.length; j++) {
          if (i === j) {
            liList[j].style.width = '800px'
          } else {
            liList[j].style.width = '100px'
          }
        }
      }, 500) //这里的时间就是防抖的响应时间,也就是时间触发延时,防止用户在选择的时候进行误选
      //3.排他思想修改宽度: 我自己800px,其他人100px
      //防抖的实质其实就是利用定时器,给每个事件加上延时,作为用户防止误触的一个基准,然后再把上一个定时器移除掉,就是不再触发上一个定时器,定时器里面放的是触发的方法,这样就不会再触发上一个事件了,也就达成了用户防止误触的目的
    }
  }

节流阀

节流函数

1.什么是节流函数:能够控制高频触发事件的函数叫做节流函数

2.节流函数的作用:节流函数的主要目的是防止高频事件的连续触发问题

3.节流函数的使用

   思路:事件中存在有类似 mousemove scroll 这样的可能高频触发的事件,这种事件如果事件处理函数存在有大量的运算,可能会造成浏览器的卡死,或者服务器的不可逆转的物理损坏,所以为了限制这些高频事件,需要让这些时间有触发间隔,使其节流时间内不允许重复触发,以达到大幅度限流问题

核心思想:获取本次事件和上一次触发时间的时间戳,如果两两间的时间戳大于节流事件则允许触发,如果小于节流时间则不允许触发

       let i = 0;
        // 先定义一个上一次触发事件的时间戳
        let previousTime = 0;
        window.onmousemove = () => {
            // 然后再获取当前触发事件的时间戳
            let newTime = Date.now();
            if (newTime - previousTime >= 300) { //如果当前触发事件的时间戳和上一次触发事件的时间戳的差大于节流事件则允许触发,如果小于节流事件则被过滤
                console.log(`我已经移动了${i}次了`);
                i++; //如果不限制高频时间的触发频率,则会造成大量的数据产生
                previousTime = newTime; //
            }
        }

节流函数和防抖函数的区别

相同点:都是为了浏览器的响应优化

不同点:防抖函数是间隔时间内以最后一次为准

	  节流函数是间隔事件内不会触发,以当前触发时间戳为准