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,注意包含了小数点占了一位
posted @ 2020-04-16 22:16  小猪冒泡  阅读(267)  评论(0编辑  收藏  举报