• ES6知识总结


    1 全局变量和局部变量

    1.1 let、const

      1. 在es5中,存在全局作用域和函数作用域,没有块级作用域的概念;
      1. 在es6中,新增了块级作用域,由大括号构成;
      1. letconst 因为暂时性死区的原因,不能在声明前使用。
      1. var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会。
    console.log(aa) // undefined[后面aa已经进行过声明提升]
    console.log(bb) // Cannot access 'bb' before initialization
    console.log(cc) // Cannot access 'cc' before initialization
    var aa = 1
    let bb = 1
    const cc = 1
    console.log(window.aa) // 1
    console.log(window.bb) // undefined
    console.log(window.cc) // undefined
    
    function test(){     //在一个{}构成的区域块中,let aa导致了在该区域内暂时性死区
      console.log(aa)   
      let aa;
    }
    test()  //Cannot access 'aa' before initialization
    

    1.2 const

    const PI = {};
    //PI.name='xihua'; //[可以]
    PI = {
        name:'xiaohua' //直接赋值的报错
    }
    console.log(JSON.stringify(PI));
    

    1.3 var变量


    var没有块级作用域【区别于let有块级作用域】,只有函数作用域。

    • 示例:while中不属于函数作用域,aa会被覆盖;而函数作用域中属于私有变量,不会影响外面的变量
    var myname = 'aa';
    while(true){
      var myname = 'bb';
      console.log(myname); //bb
      break;
    }
    console.log(myname);//bb
    
    • 示例:函数作用域中,影响aa的值
    var aa = 10;
    function fn(){
      console.log(aa);//undefined
      var aa = 20;
      console.log(aa);//20
    }
    fn();
    console.log(aa);//10
    
    • 示例:变量提升即把变量声明提升到它所在作用域的最开始的部分。注意只是提升声明,并没有赋值。
      函数提升, 创建函数有两种形式,一种是函数声明,另外一种是函数字面量,只有函数声明才有变量提升。
    var myname = 'aa';
    while(true){
        var myname = 'bb';
        console.log('1'+this.myname); //1bb
        break;
    }
    function cc(){
        var myname = 'cc';
        console.log('3' + myname); //3cc
        console.log('4' + this.myname); //4bb
    }
    console.log('2' + myname); //2bb
    cc();
    

    结果是: 1bb,2bb,3cc,4bb
    注意this默认指向windows;

    解析:

    function cc(){
        var myname = 'cc';
        console.log(myname); //cc
        console.log(this.myname); //undefined
    }
    cc()
    console.log(this.myname); //undefined
    

    注意,这里的this.myname相当于window.myname,window是一个对象,如果没有myname属性,则返回undefined

    function Cc(){
        var myname = 'cc';
        this.myname = myname;
        console.log(myname); //cc
        console.log(this.myname); //cc
    }
    var cc = new Cc();
    console.log(cc.myname);//cc
    console.log(this.myname);//undefined
    
    • 示例:变量提升,且函数提升优于变量提升;
    console.log(a) // ƒ a() {}
    var a = 1
    function a() {}
    
    function test(){
      for(let i=1;i<3;i++){
        console.log(i);
      }
      console.log(i);
    }
    test();
    //1
    //2
    //报错 i is not defined
    //let是for循环中的块级作用域中,所以在for循环中可以获取到i 但是在for循环外面 无法获取到i;
    

    • 示例:var定义的i是全局的,每一次循环,新的值都会覆盖旧值。
    var a = [];
    for(var i=0;i<5;i++){
        a[i] = function(){
            console.log(i);
        }
    }
    a[2]();
    

    let的出现很好的解决了

    2.闭包问题

    (最简单的闭包:当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。)

    function A(){
        return function B(){
            console.log('from B');
        }
    }
    var C = A();//from B
    C();
    

    闭包可以用在许多地方。它的最大用处有两个,

      1. 一个是可以读取函数内部的变量[示例1],
      1. 另一个就是让这些变量的值始终保持在内存中[示例2]。
    • 示例:可以读取函数内部的变量
    function aa(){
        var name = 'xiaohua';
        return function bb(){
            return name;
        }
    }
    var cc = aa();
    console.log(cc()) // xiaohua
    
    • 示例:这些变量的值始终保持在内存中
    function aa(){
        var num = 0;
        return function bb(){
            num++;
            return num;
        }
    }
    var cc = aa();
    console.log(cc())//1
    console.log(cc())//2
    console.log(cc())//3
    console.log(cc())//4
    

    综上,闭包就是在 函数A中 return 一个函数;外面调用 A()();
    常见考题:现有如下html结构

    <ul>
     <li>click me</li>
     <li>click me</li>
     <li>click me</li>
     <li>click me</li>
    </ul>
    

    运行如下代码:

    var elements=document.getElementsByTagName('li');
        var length=elements.length;
        for(var i=0; i<length; i++){
            elements[i].onclick=function(){
            alert(i);
        }
    
     }
    

    依次点击4个li标签,哪一个选项是正确的运行结果()?
    依次弹出1,2,3,4
    依次弹出0,1,2,3
    依次弹出3,3,3,3
    依次弹出4,4,4,4
    //解释一下,之所以先执行 for循环,是因为点击函数相当于异步函数,之后点击标签之后,才会执行onclick事件,所以for循环会先执行。

    var elements=document.getElementsByTagName('li');
    var length=elements.length;
    for(var i=0;i<length;i++){
        elements[i].onclick=function(j){
            return function() {
                alert(j);
            };
        }(i);
    }
    //在上述代码中,我们首先使用了立即执行函数将 `i` 传入函数内部,这个时候值就被固定在了参数 `j` 上面不会改变,当下次执行 `timer` 这个闭包的时候,就可以使用外部函数的变量 `j`,从而达到目的。
    

    3.新增类 class

    class 后面大写表示类, consructior 表示构造函数,子类中本来没有this,只有子类构造函数中用super函数来调用父类中的 consructior 函数,之后才可以使用this,子类中的this指向子类;这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    class People{
        constructor(name,age){
            this.name=name;
            this.age = age;
        }
        say(){
            console.log(this.name+' is '+this.age);
        }
    }
    
    class Man extends People{
        constructor(name,age){
            super(name,age);
            this.name = 'man'
        }
    }
    
    let man1 = new Man('xiaoming',12);
    man1.say(); 
    

    super,因为这段代码可以看成 People.call(this,name,age)

    4 this 指针

    箭头函数是定义的时候指定的this;普通函数是在调用的时候指定的this;
    this跟函数在哪里定义没有关系,函数在哪里调用才决定了this到底引用的是啥。也就是说this跟函数的定义没关系,跟函数的执行有大大的关系。所以,记住,“函数在哪里调用才决定了this到底引用的是啥”。

    1. 单独的this,指向的是window这个对象

    alert(this); // this -> window
    

    2. 全局函数中的this

    function demo() {
     alert(this); // this -> window
    }
    demo();
    

    在严格模式下,this是undefined.

    function demo() {
     'use strict';
     alert(this); // undefined
    }
    demo();
    

    3. 函数调用的时候,前面加上new关键字【这个同calss类】
    所谓构造函数,就是通过这个函数生成一个新对象,这时,this就指向这个对象。

    function demo() {
     //alert(this); // this -> object
     this.testStr = 'this is a test';
    }
    let a = new demo();
    alert(a.testStr); // 'this is a test'
    

    再如:

    function fn(){
        console.log(this.a);
    }
    var obj = {
        a:2,
        fn:fn
    }
    obj.fn(); //2 
    

    最后一个调用该函数的对象是传到函数的上下文对象,如:

    function fn() {
        console.log( this.a );
    }
    var obj2 = {
        a: 42,
        fn: fn
    };
    var obj1 = {
        a: 2,
        obj2: obj2
    };
    obj1.obj2.fn();//42
    

    注意的是,如果失去绑定,[即赋值给变量类] 则到全局:

    function fn() {
        console.log( this.a );
    }
    var obj = {
        a: 2,
        fn: fn
    };
    var bar = obj.fn; // 函数引用传递
    var a = "全局"; // 定义全局变量
    bar(); // "全局"
    

    4. 用call与apply的方式调用函数;比如A.call(B); 使用的是A中的方法,用的是B中的变量

    function demo() {
     alert(this);
    }
    demo.call('abc'); // abc
    demo.call(null); // this -> window
    demo.call(undefined); // this -> window
    

    5. 定时器中的this,指向的是window

    setTimeout(function() {
     alert(this); // this -> window ,严格模式 也是指向window
    },500)
    

    例如:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout(function(){
                console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
    var animal = new Animal()
    animal.says('hi')
    

    因为setTimeout是延时函数,执行的时候已经脱离对象环境,即在window下进行,所以 this 指向 window,所以结果为: undefined says hi

    6. 元素绑定事件,事件触发后,执行的函数中的this,指向的是当前元素

    window.onload = function() {
     let $btn = document.getElementById('btn');
     $btn.onclick = function(){
      alert(this); // this -> 当前触发
     }
    }
    

    7. 函数调用时如果绑定了bind,那么函数中的this指向了bind中绑定的元素

    window.onload = function() {
     let $btn = document.getElementById('btn');
     $btn.addEventListener('click',function() {
     alert(this); // window
     }.bind(window))
    }
    
    let a = {}
    let fn = function () { console.log(this) }
    fn.bind().bind(a)() // => ?
    

    可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window

    8. 对象中的方法,该方法被哪个对象调用了,那么方法中的this就指向该对象

    let name = 'finget'
    let obj = {
     name: 'FinGet',
     getName: function() {
        alert(this.name);
     }
    }
    obj.getName(); // FinGet
    let fn = obj.getName;
    fn(); //finget this -> window
    

    笔试题

    var x = 20;
    var a = {
      x: 15,
      fn: function() {
        var x = 30;
        //console.log(this.x),这个是15
        return function() {
          return this.x
        }
     }
    }
    console.log(a.fn());
    console.log((a.fn())());
    console.log(a.fn()());
    console.log(a.fn()() == (a.fn())());
    console.log(a.fn().call(this));
    console.log(a.fn().call(a));
    

    答案

    1.console.log(a.fn());
    对象调用方法,返回了一个方法。
    function() {return this.x}

    2.console.log((a.fn())());
    a.fn()返回的是一个函数,()()这是自执行表达式。this -> window
    20

    3.console.log(a.fn()());
    a.fn()相当于在全局定义了一个函数,然后再自己调用执行。this -> window
    20

    4.console.log(a.fn()() == (a.fn())());
    true

    5.console.log(a.fn().call(this));
    这段代码在全局环境中执行,this -> window
    20

    6.console.log(a.fn().call(a));
    this -> a
    15

    类似的

    var x = 20;
    var a = {
     x: 15,
     fn: function() {
        var x = 30;
        console.log(this.x)
     }
    }
    console.log(a.fn());//15
    

    5 箭头函数

    箭头函数是定义的时候指定的this;普通函数是在调用的时候指定的this;
    箭头函数中this倒底指向谁?一句话,箭头函数内的this就是箭头函数外的那个this! 为什么?因为箭头函数没有自己的this。
    箭头函数的this看定义他的时候,他的外层有没有函数
    有:外层函数的this就是箭头函数的this
    无:箭头函数的this就是window

    obj = {
      age:18, 
      getAge: ()=>console.log(this.age)
    }
    obj.getAge()
    //undefined    定义的时候外层没有函数,指向window
     
    obj = {
      age:18, 
      getAge: function(){
        print = ()=>console.log(this.age); 
        print()
      }
    }
    obj.getAge()
    //18    定义的时候外层函数的this就是箭头函数的this
    

    例如:

    class Animal {
        constructor(){
            this.type = 'animal'
        }
        says(say){
            setTimeout( () => {
              console.log(this.type + ' says ' + say)
            }, 1000)
        }
    }
    var animal = new Animal()
    animal.says('hi')  //animal says hi
    

    再如,第一个用的是function,this指向的是调用者 obj,而第二个示例用的是箭头函数,this指向定义时的对象window
    示例1:

    function fn() {
        console.log(this.a); //调用的时候,obj来调用的,this指向obj
    }
    var obj = {
        a:2,
        fn:fn
    }
    a =3 ;
    obj.fn(); //2 
    

    示例2:

    let fn = ()=>{
        console.log(this.a); //定义的时候,外侧没有函数,this指向window
    }
    var obj = {
        a:2,
        fn:fn
    }
    a =3 ;
    obj.fn(); //3
    
    //
    class Animal {
        constructor(){
            this.name = 'animal'
        }
        says(){
           console.log(this); //该 animal 对象
           setTimeout(()=>{
               console.log(this);
           },3000)
        }
    }
    let obj1 = new Animal();
    console.log(obj1.says());//该 animal 对象
    //
    function People (){ 
        this.name = "people"; 
        this.says = function(){ 
            console.log(this); //该 People 对象
        } 
    } 
    let obj2 = new People();
    console.log(obj2.says());
    let obj = {
        age:18, 
        getAge: function(){
            console.log(this); //该 obj 对象
            setTimeout(()=>{
                console.log(this);
            },2000)
        }
    }
    obj.getAge()//该 obj 对象
    //
    obj = {
        age:18, 
        getAge: ()=>{
            console.log(this.age) //指向window
        }
    }
    obj.getAge()
    

    6. 解构赋值/ 模板字符串

    `${name}的岁数是${age}`
    

    6.1 解构数组:

    let arr = [1,2,3];
    let [a,b,c] = arr;
    console.log(a,b,c); //1 2 3
    
    let [,,a,,b] = [1,2,3,4,5];
    console.log(a,b); // 3 5
    
    let [a,...reset] = [1,2,3,4,5];
    console.log(a,reset); //1,[2,3,4,5]
    

    6.2 可以设置默认值

    let obj = {
        name:"zhuangzhuang"
    };
    let {name,age = 18} = obj;
    console.log(name,age);//避免age为undefined
        
    

    6.2 使用别名
    如果使用别名,则不允许再使用原有的解构出来的属性名,看以下举例则会明白:

    let p1 = {
        "name":"zhuangzhuang",
        "age":25
    }
    let {name:aliasName,age:aliasAge} = p1;//注意变量必须为属性名
    console.log(aliasName,aliasAge);//"zhuangzhuang",25
    console.log(name,age);//Uncaught ReferenceError: age is not defined
    

    注意:
    只报错age,不报错name,这说明其实name是存在的,那么根据js的解析顺序,当在当前作用域name无法找到时,会向上找,直到找到window下的name,而我们打印window可以发现,其下面确实有一个name,值为“”,而其下面并没有属性叫做age,所以在这里name不报错,只报age的错。类似name的属性还有很多,比如length等。

    对于复杂的数据结构,获取内部数据,按照原来的数据结构取值即可:

    let metaData={
        title:'abc',
        test:[{
          name:'test',
          desc:'description'
        }]
      }
    let {title,test:[{name}]}=metaData;
    console.log(title,name);
    

    又比如,获取test是个对象中的name:

    let metaData2={
        title:'abc',
        test:{
          name:'test',
          desc:'description'
        }
      }
    let {title,test:{name}}=metaData2;//test在原数据中就是
    console.log(title,name);
    

    默认参数

    function animal(type = 'cat'){
        console.log(type)
    }
    animal();// cat
    

    7. export等规范

    AMD,CMD,CommonJS,ES6的比较:

    框架 实现 同步/异步
    AMD RequireJS 异步
    CMD SeaJS 同步
    nodeJs CommonJS
    ES6 import 异步

    【7.1】 AMD---异步模块定义
    是一个概念,RequireJS 是对这个概念的实现。好比JavaScript语言是对ECMAScript规范的实现。
    RequireJS:是一个AMD框架,可以异步加载JS文件,按照模块加载方法,通过define()函数定义,第一个参数是一个数组,里面定义一些需要依赖的包,第二个参数是一个回调函数,通过变量来引用模块里面的方法,最后通过return来输出。是一个依赖前置、异步定义的AMD框架(在参数里面引入js文件),在定义的同时如果需要用到别的模块,在最前面定义好即在参数数组里面进行引入,在回调里面加载。

    //被引用的文件是 used.js
    define('used.js',function(){
        return 'A is a cat'
    })
    
    require(['moduleA', 'moduleB', './used.js'], function (moduleA, moduleB, lib){
       function foo(){
            lib.log('hello');
        }
        return {
            foo:foo
        }
    });
    

    【7.2】CMD----是SeaJS的一个标准
    是SeaJS在推广过程中对模块定义的规范化产出,是一个同步模块定义,是SeaJS的一个标准,SeaJS是CMD概念的一个实现,SeaJS是淘宝团队提供的一个模块开发的js框架.

    html中使用方式

    seajs.use('./../js/index', function (init) {
        init();
    });
    
    define(function(require, exports, module) {
        require('./calendar.js');//使用require的方式引入依赖文件
        var init = function() {
            //xxxx
        }
        return init;
        //exports.init = function() {};
    }
    

    通过define()定义,没有依赖前置,通过require加载jQuery插件,CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,即用即返,这是一个同步的概念

    【7.3】CommonJS----node.js后端使用的规范
    CommonJS规范---是通过module.exports定义的,在前端浏览器里面并不支持module.exports, 通过node.js后端使用的。Nodejs端是使用CommonJS规范的,前端浏览器一般使用AMD、CMD、ES6等定义模块化开发的.前端的webpack也是对CommonJS原生支持的

    由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不适用于浏览器端的。

    //被调用的文件 used.js
    module.exports={
        //变量
        //函数
    }
    

    使用require引用

    //调用的文件 index.js 
    var animal = require('./used.js');
    

    【7.4】ES6----web前端使用的
    方法一:使用 export defalut 导出默认变量或函数

    //被调用的文件 used.js
    export defalut  xxx;
    
    //调用的文件 index.js--被调用的文件使用的 default 默认导出
    import animal from './used.js'
    

    方法二:使用 export 导出对象:

    //被调用的文件 used.js
    export {
        sum,
        add
    }
    
    //调用的文件 index.js 
    import {add} from './used.js'
    

    方法三: 修改变量名字

    import animal, { say, type as animalType } from './content'  
    

    【7.4】ES6 模块与 CommonJS 模块的差异

    它们有两个重大差异。

    CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
    而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

    下面重点解释第一个差异。

    CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。

    // lib.js
    var counter = 3;
    function incCounter() {
      counter++;
    }
    module.exports = {
      counter: counter,
      incCounter: incCounter,
    };
    

    上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。

    // main.js
    var mod = require('./lib');
    console.log(mod.counter);  // 3
    mod.incCounter();
    console.log(mod.counter); // 3
    

    上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。

    // lib.js
    var counter = 3;
    function incCounter() {
      counter++;
    }
    module.exports = {
      get counter() {
        return counter
      },
      incCounter: incCounter,
    };
    

    上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了

    ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

    还是举上面的例子。

    // lib.js
    export let counter = 3;
    export function incCounter() {
      counter++;
    }
    // main.js
    import { counter, incCounter } from './lib';
    console.log(counter); // 3
    incCounter();
    console.log(counter); // 4
    

    8. Symbol类型

    • 8.1 定义
    let s2 = Symbol('another symbol')
    console.log(s2)
    //Symbol(another symbol)
    
    • 8.2 特性

    1】每个Symbol实例都是唯一的。因此,当你比较两个Symbol实例的时候,将总会返回false:

      let s1 = Symbol()
      let s2 = Symbol('another symbol')
      let s3 = Symbol('another symbol')
      s1 === s2 // false
      s2 === s3 // false
    

    应用场景1】:使用Object.keys()或者for...in来枚举对象的属性名,无法获取到 Symbol定义的属性。我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

    let obj = {
       [Symbol('name')]: '一斤代码',
       age: 18,
       title: 'Engineer'
    }
    Object.keys(obj)   // ['age', 'title']
    for (let p in obj) { // p即是key; 
       console.log(p)   // 分别会输出:'age' 和 'title'
    }
    Object.getOwnPropertyNames(obj)   // ['age', 'title']
    //下面两个方法可以获取到Symbol
    // 使用Object的API
    Object.getOwnPropertySymbols(obj) // [Symbol(name)]
    // 使用新增的反射API
    Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']
    

    补充:for of 和 for in 的区别

    let aa = [12,'address',{
        name:'xiaohua',
        age:21
    }]
    
    for(let key of aa){ 
        console.log(key);
    }
    //of循环数组;in循环对象或者数组;
    //记忆点: of 和 for 都有 o;for用来循环数组,所以of也用来循环数组
    
    

    【应用场景2】:定义一些常量,比如redux中的 actionType,这样不用像以前一样,为每个常量定义名字:
    原来:

    const TYPE_AUDIO = 'type_audio'
    const TYPE_VIDEO = 'type_video'
    const TYPE_IMAGE = 'type_image'
    

    现在:

    const TYPE_AUDIO = Symbol()
    const TYPE_VIDEO = Symbol()
    const TYPE_IMAGE = Symbol()
    

    【应用场景3】:使用Symbol定义类的私有属性/方法

    参考文章:[1]理解和使用ES6中的Symbol:https://www.jianshu.com/p/f40a77bbd74e


    9. Iterator 和 for...of循环

    JavaScript原有的四种表示'集合'的数据结构,Object、Array、Set、Map。
    遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了Iterator接口,就可以完成遍历操作。
    Iterator 的作用有三个:
    一是为各种数据结构,提供一个统一的、简便的访问接口;
    二是使得数据结构的成员能够按某种次序排列;
    三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

    let arr = ['hello','world'];
    let map = arr[Symbol.iterator]();
    console.log(map.next());
    console.log(map.next());
    console.log(map.next());
    // 返回结果
    // {value: "hello", done: false}
    // {value: "world", done: false}
    // {value: undefined, done: true}
    

    for...of 就是调用的数组或者对象的 Symbol.iterator 方法,数组内置了Symbol.iterator 方法
    但是由于对象内置结构比较复杂,所以没有定义,这个方法需要自定义。

    let obj = {
        start:[1,3,2],
        end:[7,9,8],
        [Symbol.iterator](){
            let self = this;
            let index = 0;
            let arr = self.start.concat(self.end);
            let len = arr.length;
            return {
                next(){
                    if(index<len){
                        return {
                            value:arr[index++],
                            done:false //false表示没有结束
                        }
                    }else{
                        return {
                            value:arr[index++],
                            done:true
                        }
                    }
                }
            }
        }
    }
    console.log(obj[Symbol.iterator]().next()); //{value: 1, done: false}
    for(let value of obj){
        console.log(value);
    }
    // 1 3 2 7 9 8
    

    10. Generator

    基本定义

    // genertaor基本定义
      let tell=function *(){
        yield 'a';
        yield 'b';
        return 'c'
      };
    
      let k=tell();
    
      console.log(k.next());
      console.log(k.next());
      console.log(k.next());
      console.log(k.next());
    

    示例题:

    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   
    console.log(it.next(12))
    console.log(it.next(13)) 
    // => {value: 6, done: false}
    // => {value: 8, done: false}
    // => {value: 42, done: true}
    

    解析:
    yield相当于return返回;
    let it = foo(5) 并不会执行 yield,会返回一个Iterator实例, 然后再执行Iterator实例的next()方法
    如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值;
    每次执行 it.next()时,都相当于执行对应的 yield();

    所以 it.next() 时,会返回在 let y = 2 * (yield (x + 1))这句话的,yield (x + 1), foo传进来的参数 5 ,则
    返回 5+1 = 6;【注意此时不用管函数表达式 let y = 2 * (yield (x + 1)) ,只用考虑 yield (x + 1) 】

    第二次执行 it.next(12);会从上一次暂停的位置开始 let y = 2 * (yield (x + 1))
    且 传参12 等于yield (x + 1),所以 y = 2* 12 = 24;且返回第二个 yield 处
    yield (y / 3) == 24/3 == 8;
    【注意此时不用管函数表达式 let z = yield (y / 3),只用考虑yield (y / 3) 】

    第三次 it.next(13) 会从上一次yield处开始let z = yield (y / 3);传入的参数等于 let z = yield (y / 3) =13;
    综上所述 x=5;y=24;z=13;

    同理,很容易实现8中迭代器的对象循环的方法:

    let obj = {};
    obj[Symbol.iterator] = function *(){
        yield 1;
        yield 3;
        yield 5;
        yield 7;
    }
    for(let key of obj){
        console.log(key);
    }
    // 1 3 5 7
    

    Generator 实现状态机,也就是固定的状态

    let state=function* (){
    while(1){
        yield 'A';
        yield 'B';
        yield 'C';
    }
    }
    let status=state();
    console.log(status.next());//{value: "A", done: false}
    console.log(status.next());//{value: "B", done: false}
    console.log(status.next());//{value: "C", done: false}
    console.log(status.next());//{value: "A", done: false}
    console.log(status.next());//{value: "B", done: false}
    

    注意 async...await 就是用的 Generator 的语法糖

    应用一:抽奖功能,不需要全局定义剩余抽奖次数。

      let draw=function(count){
        //具体抽奖逻辑
        console.info(`剩余${count}次`)
      }
    
      let residue=function* (count){
        while (count>0) {
          count--;
          yield draw(count);
        }
      }
    
      let star=residue(5);
      let btn=document.createElement('button');
      btn.id='start';
      btn.textContent='抽奖';
      document.body.appendChild(btn);
      document.getElementById('start').addEventListener('click',function(){
        star.next();
      },false)
    

    常规写法:

     let draw=function(count){
        //具体抽奖逻辑
        console.info(`剩余${count}次`)
      }
    
      let residue=function (){
        if (currNum>0) {
          draw(currNum);
        }
      }
      let currNum = 5;
      let btn=document.createElement('button');
      btn.id='start';
      btn.textContent='抽奖';
      document.body.appendChild(btn);
      document.getElementById('start').addEventListener('click',function(){
        residue();
        currNum--;
      },false)
    

    常轮询:

      let ajax=function* (){
        yield new Promise(function(resolve,reject){
          setTimeout(function () {
            resolve({code:0})
          }, 200);
        })
      }
      let pull=function(){
        let genertaor=ajax();
        let step=genertaor.next();
        step.value.then(function(d){
          if(d.code!=0){
            setTimeout(function () {
              console.info('wait');
              pull()
            }, 1000);
          }else{
            console.info(d);
          }
        })
      }
      pull();
    
    

    常规写法:

     let ajax=function (){
        return new Promise(function(resolve,reject){
          setTimeout(function () {
            resolve({code:10})
          }, 200);
        })
      }
    
      let pull=function(){
        let genertaor=ajax();
        genertaor.then(function(d){
          if(d.code!=0){
            setTimeout(function () {
              console.info('wait');
              pull()
            }, 1000);
          }else{
            console.info(d);
          }
        })
      }
      pull();
    

    11. reduce函数

    reduce() 是数组的归并方法,与forEach()、map()、filter()等迭代方法一样都会对数组每一项进行遍历,但是reduce() 可同时将前面数组项遍历产生的结果与当前遍历项进行运算,

    1. 求数组项之和
    let arr = ['1','2','3']
    var sum = arr.reduce(function (prev, cur) {
        return Number(prev) + Number(cur);
    },0);
    console.log(sum); //6
    

    由于传入了初始值0,所以开始时prev的值为0,cur的值为数组第一项3,相加之后返回值为3作为下一轮回调的prev值,然后再继续与下一个数组项相加,以此类推,直至完成所有数组项的和并返回。
    2. 求数组项最大值

    let arr = ['12','2','5','8','4']
    var max = arr.reduce(function (prev, cur) { //使用reduce将传入的数组分开
        return Math.max(prev,cur);
    });
    console.log(max);
    

    由于未传入初始值,所以开始时prev的值为数组第一项3,cur的值为数组第二项9,取两值最大值后继续进入下一轮回调。

    方法2:
    由于Math.max不能直接作用于数组,所以使用apply,第一个参数为null,表示window作用域。第二个参数是aa数组,会以单个

    let aa = ['1','8','13','4','7']
    console.log(Math.max.apply(null,aa))
    

    类似的使用call
    console.log(Math.max.call(null,'1','8','13','4','7'))

    let bb = [1,8,13,4,7];
    bb.sort((num1,num2)=>{
        return num1-num2 
    })
    bb.reverse()[0];//reverse 函数翻转数组
    
    let bb = [1,8,13,4,7];
    bb.sort((num1,num2)=>{
        return num1-num2 
    })
    console.log(bb);//[1, 4, 7, 8, 13]
    
    1. 数组去重
    var newArr = arr.reduce(function (prev, cur) {
        prev.indexOf(cur) === -1 && prev.push(cur);
        return prev;
    },[]);
    //array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
    //initialValue 可选。传递给函数的初始值
    

    【参考文章】浅谈JS中 reduce() 的用法:https://www.jianshu.com/p/541b84c9df90

    12. Array.from方法

    Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
    1、将类数组对象转换为真正数组.

    let arrayLike = {
        0: 'tom', 
        1: '65',
        2: '男',
        3: ['jane','john','Mary'],
        'length': 4
    }
    let arr = Array.from(arrayLike)
    console.log(arr) // ['tom','65','男',['jane','john','Mary']]
    
    

    那么,如果将上面代码中length属性去掉呢?实践证明,答案会是一个长度为0的空数组。
    这里将代码再改一下,就是具有length属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:

    let arrayLike = {
        'name': 'tom', 
        'age': '65',
        'sex': '男',
        'friends': ['jane','john','Mary'],
        length: 4
    }
    let arr = Array.from(arrayLike)
    console.log(arr)  // [ undefined, undefined, undefined, undefined ]
    

    会发现结果是长度为4,元素均为undefined的数组
    由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:

    • 1、该类数组对象必须具有length属性,用于指定数组的长度。如果没有length属性,那么转换后的数组是一个空数组。
    • 2、该类数组对象的属性名必须为数值型或字符串型的数字
      ps: 该类数组对象的属性名可以加引号,也可以不加引号
      Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。如下:
    let arr = [12,45,97,9797,564,134,45642]
    let set = new Set(arr)
    console.log(Array.from(set, item => item + 1)) // [ 13, 46, 98, 9798, 565, 135, 45643 ]
    

    3、将字符串转换为数组:

    let  str = 'hello world!';
    console.log(Array.from(str)) // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "!"]
    

    13. 字符串补全长度的功能。

    如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

    let str = 'xx';
    let bb = str.padStart(5,'0'); //第一个参数表示位数
    console.log(bb); // 000xx
    

    padEnd()常用来给小数点补充0:

    
    let num1 = '34.1';
    let num2 = num1.padEnd(5,'0');
    console.log(num2); //34.10,注意包含了小数点占了一位
    
  • 相关阅读:
    Grub 和 UEFI启动
    神舟战神插上耳机没有声音,重启又有声音..
    批处理 ------ @、ECHO OFF、ECHO ON 的使用
    linux command ------ find
    Adobe Premiere Pro CC ------ 快捷键
    分布式session一致性问题
    DNS域名解析
    CDN内容分发
    令牌桶限流算法和漏桶限流算法区别
    AOP与IOC区别
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/12716262.html
Copyright © 2020-2023  润新知