ES6笔记

1.全局变量

//var function声明的变量属于全局变量属性;let,const,class声明的变量不属于全局变量属性
let a=1;
var b=2;
console.log(window.a,window.b)undefind,2

2.数组的解构赋值

 //var a=1,b=2,c=3;
 var [a, b, c] = [1, 2, 3];

 let [foo, [[bar], baz]] = [1, [[2], 3]];//foo 1,bar 2,baz 3

 let [head, ...tail] = [1, 2, 3, 4]; //head  1 tail // [2, 3, 4]

 let [, , third] = ["foo", "bar", "baz"]; //third  "baz"


 let [x, y, ...z] = ['a']; //x  "a",y  undefined,z  []


 console.log(y);

 function Dosth() {
     let [x, y] = [1, 2, 3];// x  1,y  2
     let [a, [b], d] = [1, [2, 3], 4];//a 1,b 2,d  4
            
 }

3.对象的解构赋值

        //对象的解构和数组的解构是不同的,数组的元素是按次序排列的,变量的值是由他的位置决定的,
        //对象的属性没有次序,变量名必须与属性名相同
        let { foo, bar } = { foo: "aaa", bar: "bbb" };
      
        //对象的解构值相当于下面的简写
        var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
        //解构值的内部机制,先找到同名属性,然后再赋值给对应的变量,真正赋值的是后者
        var { foo: baz } = { foo: "aaa", bar: "bbb" }; //baz "aaa"  ,foo error: foo is not defined

        //如果一个已声明的变量用于解构值,一定要在其外面加上括号,否则会报错
        var x;
        ({ x } = { x: 1 });


        let obj = { first: 'hello', last: 'world' };
        //f:'hello',l:'world'
        let { first: f, last: l } = obj;

4.字符串的解构赋值

const [a, b, c, d, e]="hello";
console.log(a);//h

5.数值和布尔类型的解构赋值

//如果右边是布尔值或者数值,会转换为对象
let { toString: s } = 123;//转换为key代表的方法 funciton toStrign(){}
console.log(Number.prototype.toString === s);
let { toString: a } = true;
console.log(Boolean.prototype.toString === a);
// console.log(Number,Boolean);
//解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。
// 由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错

6.函数的解构赋值

1.//函数参数的解构也可以使用默认值
//注意:结合对象的解构赋值
// {x,y}默认值为{0,0}
function move({ x = 0, y = 0 } = {}) {
    return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]


2.//{x,y}默认值为undefined
function move({ x, y } = { x:0, y:0 }) {
    return [x, y];
}
move({ x: 3, y: 8 }); // [3, 8]
move({ x: 3 }); // [3, undefined]
move({}); // [undefined,undefined]
move(); // [0, 0]

3.
function foo({ x, y = 5 }){
     return x + y;
 }
 foo({}); // undefined, 5
 foo(); // TypeError: Cannot read property 'x' of undefined

 4.//下面写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数
 function fetch1(url, { method = 'GET' }) {
     console.log(method);
 }
 fetch1('http://example.com', {});// "GET"
 // fetch1('http://example.com');// 报错 Cannot read property 'method' of undefined
 // 函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET。
 function fetch(url, { method = 'GET' } = {}) {
     console.log(method);
 }
 fetch('http://example.com')// "GET"

7.使用圆括号的情况

    //以下情况不能使用圆括号,全部报错
    //1.变量声明语句不能使用圆括号   
    // var [(a)] = [1];

    // var {x: (c)} = {};
    // var ({x: c}) = {};
    // var {(x: c)} = {};
    // var {(x): c} = {};

    // var { o: ({ p: p }) } = { o: { p: 2 } };

    //2.函数模式参数中不能带有圆括号,因为函数参数也属于声明变量
    // function f([(z)]){

    // }

    //3.赋值语句中,不能将整个模式,或者嵌套模式的其中一层,放在圆括号中

    //3.1下面将整个模式放在圆括号中
    // ({p: a }) = { p: 42 };
    // ([a]) = [5];
    //3.2不能嵌套在其中一层
    // [({ p: a }), { x: c }] = [{}, {}];
   
   //可以使用圆括号的情况
   //赋值语句的非模式部分可以使用圆括号        
   //它们的圆括号都不属于模式的一部分。

   //模式是取数组的第一个成员,跟括号无关
   [(b)] = [2];
   //模式是P,而不是d
   ({ p: (d) } = {});
   //模式是取数组的第一个成员,跟括号无关
   [(parseInt.prop)] = [3];

8.应用

        //1.交换值
        var a = 1, b = 2;
        [a, b] = [b, a];//a=2,b=1
        //2.返回多个值
        function example() {
            return [1, 2, 3];
        }
        var [a, b, c] = example();
        console.log(a,b,c);
        
        //4.提取json对象中的数据
        var jsonData={
            id:42,
            staus:"OK",
            data:[1,2]
        }
        let {id,data,staus}=jsonData;
        console.log(id,data,staus)

        //6.遍历map
        var map=new Map();
        map.set('first','hello');
        map.set('second','world');

        for(let [key,values] of map){
            console.log(key+" is "+values);
        }

9.字符串的拓展

        // includes() :返回布尔值,表示是否找到了参数字符串。
        // startsWith() :返回布尔值,表示参数字符串是否在源字符串的头部。
        // endsWith() :返回布尔值,表示参数字符串是否在源字符串的尾部。
        //这三个方法都支持第二个参数,表示搜索位置,endswith截取的是前面字符串
        var s = "hello,world";
        s.includes("ll");//true
        s.startsWith("e");//true
        s.endsWith("d");//true
        // console.log()

        //repeat()返回一个字符串,表示重复字符串n次,如果是小数,则会向下取整,如果是负数或者infinity,就会报错
        'a'.repeat(2);//'aa'

        //padStart(),padEnd()
        //ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。
        'a'.padStart(5, 'ab');
        'b'.padEnd(5, 'ab');

10.数值的拓展

        //ES6提供了二进制和八进制的表示方法,分别是0b(0B),0o(0O)
        let num1 = 0b111;
        let num2 = 0O77;
        console.log(num1, num2);
        //Number.isInteger(),判断是不是整数  在JavaScript中,浮点和整数是同样的储存方法,所以2和2.0被视为同一个值
        Number.isInteger(16);//true
        Number.isInteger(16.0);//true
        Number.isInteger(16.1);//false
        Number.isInteger("17");//false
        Number.isInteger("true");//false

        //Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内
        Number.isSafeInteger(Number.MAX_SAFE_INTEGER);//true  
        Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1);//flase
        Number.isSafeInteger(Number.MIN_SAFE_INTEGER);//true

11.指数运算符

        //指数运算符**
        //数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
        console.log(2 ** 4);

12.数组的拓展

        let obj = {
            0: "a",
            1: "b",
            2: "c",
            length: 3
        };
        //伪数组转数组 常用于DOM操作返回的NodeList集合,以及函数内部的arguments对象。
        //ES5写法  
        var arr1 = [].slice.call(obj);
        //ES6写法
        var arr2 = Array.from(obj);


        //Array.of方法用于将一组值,转换为数组
        Array.of(3, 8, 250);

        //find()找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined
        //findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
        [1, 4, -5, 10].find((n) => n < 0);//-5
        [1, 4, -5, 10].findIndex((n) => n < 0);//2

        //fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
        ['a', 'b', 'c'].fill(7);// [7, 7, 7]
        new Array(3).fill(7)// [7, 7, 7]

13.rest参数

        function add1(...values) {
            let sum = 0;
            for (var val of values) {
                sum += val;
            }
            return sum;
        }
        add1(2, 5, 3) // 10
        //about  
        let arr=[1,2,3]
        ...arr//1 2 3

14.字符串转数组

// 2.字符串
 res = [...'hello'];//将字符串转为真正的数组。
 console.log(res)

15.函数name属性

       function foo() {
            foo.name;//foo
        }

        var func1 = function () { };
        // ES6
        func1.name // "func1"

        // 1.个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字
        const bar = function baz() { };
        // ES5
        bar.name // "baz"

        // Function构造函数返回的函数实例,name属性的值为“anonymous”。
        (new Function).name // "anonymous"

        // bind返回的函数,name属性值会加上“bound ”前缀。
        function foo1() { };
         foo1.bind().name // "bound foo1"  
         (function(){}).bind().name // "bound "      匿名函数

16.尾调用的概念

       // 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,
        // 就是指某个函数的最后一步是调用另一个函数
        function f(x) {
            return g(x);
        }
        // 以下三种情况,都不属于尾调用。
        // 情况一
        //调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样
        function f(x) {
            let y = g(x);
            return y;
        }
        // 情况二
        //属于调用后还有操作,即使写在一行内
        function f(x) {
            return g(x) + 1;
        }
        // 情况三
        function f(x) {
            g(x);
        }
        // 尾调用不一定出现在函数尾部,只要是最后一步操作即可。
        function f(x) {
            if (x > 0) {
                return m(x)
            }
            return n(x);
        }

17.尾递归

        //8.7.3尾递归
        // 函数调用自身,称为递归。如果尾调用自身,就称为尾递归
        //只保留一个调用记录,复杂度 O(1) 
        //时间复杂度O(1)是常数阶,其基du本操作重复执行的次数是一个固zhi定的常数,执行次数不存在变化;
        function factorial(n, total) {
            debugger
            if (n === 1) {
                return total;
            }
            return factorial(n - 1, n * total);
        }
        console.log(factorial(5, 1)) // 120

        //最多需要保存n个调用记录,复杂度 O(n) 
        //而时间复杂度O(n)是线性阶,其基本操作重复执行的次数是与模块n成线性相关的,其值会随着模块n的变化而变化,
        // 当模块n的规模确定为定值后,其时间复杂度转化为O(1)
        function factorial(n) {
            if (n === 1) {
                return 1;
            }
            return n * factorial(n - 1);
        }
        factorial(5); // 120

18.尾递归的改写与柯里化的概念

//柯里化的概念:将多参数的函数转换成单参数的形式
//1.
        function tailFactorial(n, total) {
            if (n === 1) {
                return total;
            }
            return tailFactorial(n - 1, n * total);
        }
        function factorial(n) {
            return tailFactorial(n, 1);
        }
        factorial(5) // 120
//2.
        function currying(fn, n) {
            return function (m) {
                return fn.call(this, m, n);  //fn.call()直接运行
            };
        }
        function tailFactorial(n, total) {
            if (n === 1) {
                return total;
            }
            console.log(1)
            return tailFactorial(n - 1, n * total);

        }
        const factorial = currying(tailFactorial, 1);
        let res = factorial(5) // 120
        console.log(res);

19.Object.is()

//ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。
//它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
//JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
Object.is('foo', 'foo')// true
Object.is({}, {})// false

20.Object.assign()

//注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性
//如果只有一个参数,Object.assign会直接返回该参数
//由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
//如果undefined和null不在首参数,就不会报错
//其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。
//但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果
//Object.assign方法实行的是浅拷贝,而不是深拷贝。
//1.
        var target = { a: 1 };
        var source1 = { b: 2 };
        var source2 = { c: 3 };
        Object.assign(target, source1, source2);
        target // {a:1, b:2, c:3}
//2.
        var v1 = 'abc';
        var v2 = true;
        var v3 = 10;
        var obj = Object.assign({}, v1, v2, v3);
        console.log(obj); // { "0": "a", "1": "b", "2": "c" }

21.Object.getOwnPropertyDescriptor()

        let obj = { age: 10086 };
        let res = Object.getOwnPropertyDescriptor(obj, 'age');
        //enumerable 可枚举性,如果该属性为false,就表示某些操作会忽略当前属性
        //  {   value: 10086,   writable: true,  enumerable: true,  configurable: true  }

22.属性的遍历

 //  9.7 属性的遍历属性的遍历
 //ES6一共有5种方法可以遍历对象的属性。
 /*
 1.for..in
 for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。

 2.Object.keys(obj)
 Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)

 3.Object.getOwnPropertyNames(obj)
 Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
        
 4.Object.getOwnPropertySymbols(obj)
 Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性

 5.Reflect.ownKeys(obj)
 Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。但不包括继承自原型的属性

23.Object.setPrototypeOf()

        //Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。
        //它是ES6正式推荐的设置原型对象的方法     
        //设置原型后,obj可以访问proto里面的属性,proto不可以访问obj里面的属性
        let proto = {}; 
        let obj = { x: 10 }; 
        Object.setPrototypeOf(obj, proto);
         proto.y = 20; 
         proto.z = 40; 
         obj.x // 10
         obj.y // 20
         obj.z // 40

24.Object.getPrototypeOf()

        //Object.getPrototypeOf():读取一个对象的prototype对象   
        function Person() {

        }
        let per = new Person();
        let res = Object.getPrototypeOf(per) === Person.prototype;
        console.log(res);//true


        //Object.getPrototypeOf():读取一个实例化对象的prototype对象
        let proto = {};
        let obj = { x: 10 };
        Object.setPrototypeOf(obj, proto);
        res = Object.getPrototypeOf(proto) === Object.prototype;
        console.log(res)//true
        //add   Object.getPrototypeOf(obj) === proto;//true

25.Object.keys()

//ES5引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名
        var obj = { namme: "hehe", age: 18 };
        let res = Object.keys(obj);
        console.log(res);//[name,age]

26.Object.values()

 // Object.values()Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
        var obj = { 100: 'a', 2: 'b', 7: 'c' };
        Object.values(obj)// ["b", "c", "a"]
        //属性名为数值的属性,是按照数值大小,从小到大遍历的

        let o = {};
        obj = Object.create(o, { p: { value: 42 } });
        var res = Object.values(obj) // []
        //上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),
        //如果不显式声明,默认是不可遍历的。Object.values不会返回这个属性
        console.log(obj.p);

        //Object.values会过滤属性名为Symbol值的属性
        Object.values({ [Symbol()]: 123, foo: 'abc' });// ['abc']

        //如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组
        Object.values('foo')// ['f', 'o', 'o']
        //上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。
        //因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组

        //如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,
        //都不会为实例添加非继承的属性。所以,Object.values会返回空数组
        Object.values(42) // []
        Object.values(true) // []

27.Object.Enties()

        //Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
        var obj = { foo: 'bar', baz: 42 };
        Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]
        //除了返回值不一样,该方法的行为与Object.values基本一致

28.对象的拓展运算符

        //2.拓展运算符
        //扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中
        let z = { a: 3, b: 4 };
        let n = { ...z };
        n // { a: 3, b: 4 }
        // 同于使用Object.assign方法
        let aClone = Object.assign({}, z);

        //扩展运算符可以用于合并两个对象。
        let a = { c: 1 }, b = { d: 2 };
        let ab = { ...a, ...b };
        // 等同于
        ab = Object.assign({}, a, b)

29.Object.getOwnPropertyDescriptors()实现方法

        //object.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,
        //对应的属性值就是该属性的描述对象。该方法的实现非常容易
        //注:该方法的提出目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题
        function getOwnPropertyDescriptors(obj) {
            const result = {};
            for (let key of Reflect.ownKeys(obj)) {   // Reflect.ownKeys()返回所有自有属性key,不管是否可枚举,但不包括继承自原型的属性
                result[key] = Object.getOwnPropertyDescriptor(obj, key);
            }
            return result;
        }

29.1Object.getOwnPropertyDescriptors()

        //ES7有一个提案,提出了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象
        obj = {
            foo: 123,
            get bar() {
                return 'abc'
            }
        };
        res = Object.getOwnPropertyDescriptor(obj, 'bar');
        console.log(res);
        res = Object.getOwnPropertyDescriptors(obj);
        console.log(res.bar);
        // { foo:
        //    { value: 123,
        //      writable: true,
        //      enumerable: true,
        //      configurable: true },
        //   bar:
        //    { get: [Function: bar],
        //      set: undefined,
        //      enumerable: true,
        //      configurable: true } }
        //bject.getOwnPropertyDescriptors方法返回一个对象,所有原对象的属性名都是该对象的属性名,
        //对应的属性值就是该属性的描述对象。该方法的实现非常容易

30.使用Object.getOwnPropertyDescriptors()拷贝

        const source = {
            set foo(value) {
                console.log(value);
            }
        };
        const target2 = {};
        res = Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
        res=Object.getOwnPropertyDescriptors(target2,'foo');

31.Symbol基本概念

        //Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的
        ///Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
        let a1 = Symbol('foo');
        // console.log(a1); //Symbol(foo)

        //1.注意,Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的
        let a=Symbol("t");
        let b=Symbol("t");
        console.log(a===b);
        
        //2.Symbol值不能与其他类型的值进行运算,会报错  可以使其值转换成字符串后加减
        console.log("your Symbol is"+a.toString());

        //3.Symbol值也可以转为布尔值,但是不能转为数值。
        let c=Symbol();
        let flag= Boolean(c);//true
        console.log(flag);
        //  Number(c);
        //注:遍历含Symbol属性的对象使用Object.getOwnPropertySymbols()

32.Symbol的基本使用

        let mySymbol = Symbol();
        var obj = {};
        obj[mySymbol] = 1;
        console.log(obj[mySymbol]);
        var obj = {
            [mySymbol]: 2
        }
        console.log(obj[mySymbol]);
        var obj = {};
        Object.defineProperty(obj, mySymbol, {
            value: 3
        });
        console.log(obj[mySymbol]);

        //注意,Symbol值作为对象属性名时,不能用点运算符
        var a = {};
        a.mySymbol = 'Hello!'; //注:这里是基本对象,不是 Symbol()的mySymbol
        a[mySymbol] // undefined   有Symbol属性名的对象输出
        a['mySymbol'] // "Hello!"  普通对象输出

33.Symbol.for(),Symbol.keyfor()

//Symbol.for()重新使用同一个Symbol值
 var s1 = Symbol.for('foo');
 var s2 = Symbol.for('foo');
 s1 === s2;//true

 //Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,
 // 后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,
 // 如果不存在才会新建一个值比如,如果你调用Symbol.for("cat")30次,每次都会返回同一个Symbol值,
 //但是调用Symbol("cat")30次,会返回30个不同的Symbol值。


 //Symbol.keyFor方法返回一个已登记的Symbol类型值的key
 var res = Symbol.keyFor(s1);
 // console.log(res);//foo
 s2 = Symbol('foo');
 res = Symbol.keyFor(s2);
 console.log(res);

34.Symbol.hasInstance

//对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,
//会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
        class MyClass {
            [Symbol.hasInstance](foo) {
                return foo instanceof Array;
            }
        }
        [1, 2, 3] instanceof new MyClass() // true
         console.log(new MyClass());

        class Even {
            static [Symbol.hasInstance](obj) {
                return Number(obj) % 2 === 0;
            }
        }
        console.log(1 instanceof Even);

35.Proxy概念

//Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,
//因此提供了一种机制,可以对外界的访问进行过滤和改写。

//let proxy=new Proxy(target,handler);
//Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。
//其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为
    //拦截读取属性行为的例子
    var proxy = new Proxy({}, {
      get: function (target, property) {  //{},time
        console.log("getting");
       return 35;
      }
    });
    proxy.time;//35
    proxy.age;//35
    proxy.title;//35

36.Proxy中target和handler的关系

        //如果handler没有设置任何拦截,那就等同于直接通向原对象。
        var target = {};
        var handler = {};
        var proxy = new Proxy(target, handler);
        proxy.a = 'b';
        target.a // "b"
        proxy.doth = function () {
            console.log("h");
        }
        console.log(target,proxy);
        //如果未设置拦截(handler为空),那么实例化对象的属性会访问target里面的属性
        //如果未设置拦截(handler为空),设置实例化对象的属性相当于设置target里面的属性

37.Proxy拦截多个操作

        var handler = {
            get: function (target, name) {
                if (name === 'prototype') {
                    return Object.prototype;
                }
                return 'Hello, ' + name;
            },
            apply: function (target, thisBinding, args) {
                return args[0];
            },
            construct: function (target, args) {
                return { value: args[1] };
            }
        };

        let fproxy = new Proxy(function (x, y) {
            return x + y;
        }, handler);

        let res = fproxy(2, 3);//2
        console.log(res);
        res = new fproxy(1, 2);// {value: 2}
        console.log(res);
        fproxy.prototype === Object.prototype // true
        fproxy.foo // "Hello, foo"

38.Proxy支持的拦截操作

        // 对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

        ///(1)get(target, propKey, receiver)
        // 拦截对象属性的读取,比如proxy.foo和proxy['foo']。
        // 最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。

        //(2)set(target, propKey, value, receiver)
        // 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

        //(3)has(target, propKey)
        //拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。

        //(4)deleteProperty(target, propKey)
        //拦截delete proxy[propKey]的操作,返回一个布尔值。

        //(5)ownKeys(target)
        //拦截Object.getOwnPropertyNames(proxy) 、Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) ,
        //返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。

        //(6)getOwnPropertyDescriptor(target, propKey)
        //拦截Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。

        //(7)defineProperty(target, propKey, propDesc)
        //拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs) ,
        // 返回一个布尔值。

        //(8)preventExtensions(target)
        //拦截Object.preventExtensions(proxy) ,返回一个布尔值。

        //(9)getPrototypeOf(target)
        //拦截Object.getPrototypeOf(proxy) ,返回一个对象。

        //(10)isExtensible(target)
        //拦截Object.isExtensible(proxy) ,返回一个布尔值。//Object.isExtensible()判断是否能拓展

        //(11)setPrototypeOf(target, proto)
        //拦截Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

        //(12)apply(target, object, args)
        //拦截Proxy实例作为函数调用的操作,比如proxy(...args) 、proxy.call(object, ...args) 、proxy.apply(...) 。

        //(13)construct(target, args)拦截Proxy实例作为构造函数调用的操作,
        //比如new proxy(...args) 。对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

38.1 get()

        var person = { name: "张三" };
        var proxy = new Proxy(person, {
            get: function (target, property) {
                if (property in target) {
                    return target[property];
                }
                else {
                    throw new ReferenceError("Property \"" + property + "\" does not exist.");
                }
            }
        });
        proxy.name;//张三
        // proxy.age;//报错
        //上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。
        //如果没有这个拦截函数,访问不存在的属性,只会返回undefined

38.2 set()

        let proxy = new Proxy({}, {
            set: function (obj, prop, value) {
                if (prop === "age") {
                    if (!Number.isInteger(value)) {
                        throw new TypeError("The age not an integer");
                    }
                    if (value > 200) {
                        throw new RangeError("The age seems invalid");
                    }
                }

                //对于age以外的属性直接保存
                obj[prop] = value;
            }


        });
        proxy.age=12;
        console.log(proxy.age);
        proxy.age=201; //error
        proxy.age="a?"; //error

38.3 apply()

        //apply方法拦截函数的调用、call和apply操作。
        //apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组
        var twice = {
            apply(target, ctx, args) {
        //rest传参会将参数整成数组,而apply是以数组的方式传参,代入方法后参数会转换成数字或字符串
                return Reflect.apply(...arguments)*2;
            }
        };
        function sum(left, right) {
            return left + right;
        };
        var proxy = new Proxy(sum, twice);
        let res = proxy(1, 2) // 6
        console.log(res);
        proxy.call(null, 5, 6) // 22
        proxy.apply(null, [7, 8]) // 30

        //另外,直接调用Reflect.apply方法,也会被拦截
        res = Reflect.apply(proxy, null, [1, 3]);//8
      
        console.log(res);

38.4 has()

//has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符
//下面的例子使用has方法隐藏某些属性,不被in运算符发现。
        var target = {
            _prop: 'foo',
            prop: 'foo'
        }
        let proxy = new Proxy(target, {
            has(target, propKey) {
                if (propKey[0] === "_") {
                    return false;
                }
                return propKey in target;
            }
        });
        let res = 'prop' in proxy;
        console.log(res);


        //注: 如果原对象不可配置或者禁止扩展,这时has拦截会报错
        var obj = { a: 10 };
        Object.preventExtensions(obj);
        var p = new Proxy(obj, {
            has: function (target, prop) {
                return false;
            }
        });
        'a' in p // TypeError is thrown

38.5 construct()

        //construct方法用于拦截new命令
        //construct方法可以接受两个参数。target: 目标对象,args:构建函数的参数对象
        let proxy = new Proxy(function () { }, {//注:第一个参数必须是构造函数,不能是对象!
            construct(target, args) {
                console.log('called: ' + args.join(', '));
                return { value: args[0] * 10 };
            }
        });
        console.log(new proxy(1).value);

        //注:construct方法返回的必须是一个对象,否则会报错。
        var p = new Proxy(function () {}, {
            construct: function (target, argumentsList) {
                return 1;
            }
        });
        // new p() // 报错

39.6 deleteProperty()

       //deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
        let proxy = new Proxy({ _prop: 'foo' }, {
            deleteProperty(target, key) {
                if (key[0] === "_") {
                    throw new Error(`Invalid attempt to ${'delete'} private "${key}" property`);
                }
                return true;
            }
        });
        delete proxy._prop;
        // Error: Invalid attempt to delete private "_prop" property

39.7 defineProperty()

        var proxy = new Proxy({ foo: 3 }, {
            defineProperty(target, key, descriptor) {
                console.log("hh");
                console.log(descriptor);
                return true;
                // return false; 
            }
        });
        // proxy.foo = 2;
        Object.defineProperty(proxy, "foo", {
            configurable: true,
            enumerable: true,
            value: 2,
            writable: true,
        })
 //上面代码中,defineProperty方法返回false,导致使用Object.defineProerty()添加新属性会抛出错误

39.8 getOwnPropertyDescriptor()

        //getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor,返回一个属性描述对象或者undefined。
        var proxy = new Proxy({ _foo: 'bar', baz: 'tar' }, {
            getOwnPropertyDescriptor(target, key) {
                if (key[0] === "_") {
                    return;
                }
                return Object.getOwnPropertyDescriptor(target, key);
            }
        });
        let res = Object.getOwnPropertyDescriptor(proxy, "wait");//undefined
        res = Object.getOwnPropertyDescriptor(proxy, "_wait");//undefined
        // { value: 'tar', writable: true, enumerable: true, configurable: true 
        res = Object.getOwnPropertyDescriptor(proxy, 'baz');
        console.log(res);

39.9 getPropertyOf()

        //getPrototypeOf方法主要用来拦截Object.getPrototypeOf()运算符,以及其他一些操作
        //Object.__proto__
        //Object.isPrototypeOf()
        //Object.getPrototypeOf()
        //Reflect.getPrototypeOf()
        //instanceof运算符
        var proto = {};
        var p = new Proxy({}, {
            getPrototypeOf(target) {
                return proto;
            }
        });
       let res= Object.getPrototypeOf(p) === proto // true
       console.log(res);

       //上面代码中,getPrototypeOf方法拦截Object.getPrototypeOf(),返回proto对象

39.10 isExtensible()

        //isExtensible方法拦截Object.isExtensible操作
        var p = new Proxy({}, {
            isExtensible: function (target) {
                console.log("called");
                return true;
            }
        });
        Object.isExtensible(p);
        // "called"

39.11 ownkeys()

        // ownKeys方法用来拦截Object.keys()操作
        let proxy = new Proxy({ "a": 1 }, {
            ownKeys(target) {
                console.log("!!!");
                return ['a'];
            }
        });
        console.log(Object.keys(proxy));

39.12 preventExtensible()

        //preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值。
        //这个方法有一个限制,只有当Object.isExtensible(proxy)为false(即不可扩展)时,
        // proxy.preventExtensions才能返回true,否则会报错      
        var proxy = new Proxy({}, {
            preventExtensions: function (target) {
                console.log('called');
                Object.preventExtensions(target);//将target变成false
                return true;
            }
        })
        console.log(Object.preventExtensions(proxy));

39.13 setPropertyOf()

        //setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法      
        let proto = {};
        var proxy = new Proxy(function () { }, {
            setPrototypeOf(target, proto) {
                throw new Error('Changing the prototype is forbidden');
            }
        });
        
        Object.setPrototypeOf(proxy, proto);
        //// Error: Changing the prototype is forbidden
        //上面代码中,只要修改target的原型对象,就会报错。

39.Proxy.revocable()

    let {proxy,revoke}= Proxy.revocable({},{});
    proxy.x=12;
    console.log(proxy.x);
    revoke();
    console.log(proxy.x);
    //Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,
    //revoke属性是一个函数,可以取消Proxy实例。
    //上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

40.set的概念

        //1.Set是伪数组 Set可以用来去除重复值
        let arr = new Set([1, 2, 3, 4, 4]);
        console.log(arr);//Set(4)[1,2,3,4]
        console.log(...arr);//1,2,3,4	
        //向Set加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。        
        //主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身

        //2.两个对象总是不相等的。
        let Numset = new Set();
        Numset.add({});
        let numres = Numset.size;
        console.log(numres);//1

        Numset.add({});
        numres = Numset.size;
        console.log(numres);//2

41.Set遍历

  //key方法、value方法、entries方法返回的都是遍历器对象(详见《Iterator对象》一章)。
  //由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以key方法和value方法的行为完全一致
        let set = new Set(['red', 'green', 'blue']);
        for (let item of set.keys()) {
            console.log(item);
        }
        // red// green// blue
        for (let item of set.values()) {
            console.log(item);
        }
        // red// green// blue
        for (let item of set.entries()) {
            console.log(item);
        }
   // ["red", "red"]// ["green", "green"]// ["blue", "blue"]
   //上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等

        //Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法
        Set.prototype[Symbol.iterator] === Set.prototype.values//true
        //这意味着,可以省略values方法,直接用for...of循环遍历Set。
        for (let x of set) {
            console.log(x);
        }
        // red// green// blue

        //Set结构的实例的forEach方法,用于对每个成员执行某种操作,没有返回值
        let numset = new Set([1, 2, 3]); 
        numset.forEach((value, key) => console.log(value * 2));

42.Set应用

        //数组的map和filter方法也可以用于Set了。        
        var set = new Set([1, 2, 3]);

        set = new Set([...set].map(x => x * 2));
        // 返回Set结构:{2, 4, 6}

        set = new Set([1, 2, 3, 4, 5]);
        set = new Set([...set].filter(x => (x % 2) == 0));
        // 返回Set结构:{2, 4}

        //使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)
        let a = new Set([1, 2, 3]);
        let b = new Set([4, 3, 2]);
        // 并集
        let union = new Set([...a, ...b]);
        // Set {1, 2, 3, 4}        

        // 交集
        let intersect = new Set([...a].filter(x => b.has(x)));
        // set {2, 3}

        // 差集
        let difference = new Set([...a].filter(x => !b.has(x)));
        // Set {1}

        //如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。
        //一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;
        //另一种是利用Array.from方法。

        // 方法一
        set = new Set([1, 2, 3]);
        set = new Set([...set].map(val => val * 2));
        // set的值是2, 4, 6

        // 方法二
        set = new Set(Array.from(set, val => val * 2));// set的值是2, 4, 6

43.WeakSet

        //WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别
        //1.WeakSet的成员只能是对象,而不能是其他类型的值。
        //2.WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,
        //也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,
        //不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

        //作为构造函数,WeakSet可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有iterable接口的对象,都可以作为WeakSet的参数。)
        //该数组的所有成员,都会自动成为WeakSet实例对象的成员。
        var a = [[1, 2], [3, 4]];
         var ws = new WeakSet(a);
         //上面代码中,a是一个数组,它有两个成员,也都是数组。
         //将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员。
         //注意,是a数组的成员成为WeakSet的成员,而不是a数组本身。这意味着,数组的成员只能是对象

        //  var b = [3, 4];var ws = new WeakSet(b);
         // Uncaught TypeError: Invalid value used in weak set(...)
         //上面代码中,数组b的成员不是对象,加入WeaKSet就会报错。

44.Map的概念

        //Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
        var m = new Map();
        let o = "";
        m.set(o, 'content');
        let res = m.get(o);
        console.log(res);
        console.log(m);

        //下面的例子中,字符串true和布尔值true是两个不同的键
        m = new Map([[true, 'foo'], ['true', 'bar']]);
        m.get(true) // 'foo'
        m.get('true') // 'bar'
        console.log(m);

        //如果对同一个键多次赋值,后面的值将覆盖前面的值
        m.clear();
        m.set(1, 'aaa').set(1, 'bbb');
        m.get(1) // "bbb"

        //如果读取一个未知的键,则返回undefined。
        new Map().get('asfddfsasadf');//undefined

        //注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心
        var map = new Map();
        map.set(['a'], 555);
        map.get(['a']) // undefined
        //上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,
        //因此get方法无法读取该键,返回undefined。

        //同理,同样的值的两个实例,在Map结构中被视为两个键
        map = new Map();
        var k1 = ['a'];
        var k2 = ['a'];
        map.set(k1, 111).set(k2, 222);
        map.get(k1) // 111
        map.get(k2) // 222
        //由上可知,Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
        //这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,
        //如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

        //如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,
        //Map将其视为一个键,包括0和-0。另外,虽然NaN不严格相等于自身,但Map将其视为同一个键
        map = new Map();
        map.set(NaN, 123);
        map.get(NaN) // 123
        map.set(-0, 123);
        map.get(+0) // 123

45.Map遍历

        //Map原生提供三个遍历器生成函数和一个遍历方法。
        //keys():返回键名的遍历器。
        //values():返回键值的遍历器。
        //entries():返回所有成员的遍历器。
        //forEach():遍历Map的所有成员。
        let map = new Map([['F', 'no'], ['T', 'yes'],]);
        for (let item of map.entries()) {
            console.log(item[0], item[1]);
        }
        // "F" "no"
        // "T" "yes"
        // 或者
        for (let [key, value] of map.entries()) {
            console.log(key, value);
        }
        // 等同于使用map.entries()
        for (let [key, value] of map) {
            console.log(key, value);
        }
       //上面代码最后的那个例子,表示Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
        map[Symbol.iterator] === map.entries;//true

46.Map拓展运算符

        //Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...)。
        let map = new Map([[1, 'one'], [2, 'two'], [3, 'three']]);
        [...map.keys()];// [1, 2, 3]

        [...map.values()];// ['one', 'two', 'three']

        [...map.entries()];// [[1,'one'], [2, 'two'], [3, 'three']]
        [...map];// [[1,'one'], [2, 'two'], [3, 'three']]

        //结合数组的map方法、filter方法,可以实现Map的遍历和过滤(Map本身没有map和filter方法)。
        let map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
        let map1 = new Map([...map0].filter(([k, v]) => k < 3));
        // 产生Map结构 {1 => 'a', 2 => 'b'}

        let map2 = new Map([...map0].map(([k, v]) => [k * 2, '_' + v]));
        // 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}

        map.forEach(function (value, key, map) {
            console.log("Key: %s, Value: %s", key, value);
        });

        //forEach方法还可以接受第二个参数,用来绑定this。
        var reporter = {
            report: function (key, value) {
                console.log("Key: %s, Value: %s", key, value);
            }
        };
        map.forEach(function (value, key, map) {
            this.report(key, value);
        }, reporter);
        //上面代码中,forEach方法的回调函数的this,就指向reporter。

47.weakMap

  //1.WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),
  //2.不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制


  //WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),
  //所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。
  //典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。
  //基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏

  //下面是WeakMap结构的一个例子,可以看到用法上与Map几乎一样。
        var wm = new WeakMap();
        var element = document.querySelector(".element");
        wm.set(element, "Original");
        wm.get(element); // "Original"
        element.parentNode.removeChild(element);
        element = null;
        wm.get(element) // undefined
 //上面代码中,变量wm是一个WeakMap实例,我们将一个DOM节点element作为键名,然后销毁这个节点,
 //element对应的键就自动消失了,再引用这个键名就返回undefined。

 //WeakMap与Map在API上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),
 //也没有size属性;二是无法清空,即不支持clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。
 //因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。

48.Iterator概念

        //遍历器(Iterator)一种统一的接口机制,来处理所有不同的数据结构.
        //Iterator的作用有三个:
        //一是为各种数据结构,提供一个统一的、简便的访问接口;
        //二是使得数据结构的成员能够按某种次序排列;
        //三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。

        //Iterator的遍历过程是这样的。
        //1.创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
        //2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
        //3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
        //4.不断调用指针对象的next方法,直到它指向数据结构的结束位置。

        //每一次调用next方法,都会返回数据结构的当前成员的信息。
        //具体来说,就是返回一个包含value和done两个属性的对象。
        //其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
        var it = makeIterator(['a', 'b']);
        it.next() // { value: "a", done: false }
        it.next() // { value: "b", done: false }
        it.next() // { value: undefined, done: true }
        function makeIterator(array) {
            var nextIndex = 0;
            return {
                next() {
                    return nextIndex < array.length ?
                        { value: array[nextIndex++], done: false } :
                        { value: undefined, done: true };
                }
            };
        }

49.for..of与Iterator接口

        //Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环
        //使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
        //在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。
        //一个对象如果要有可被for...of循环调用的Iterator接口,
        //就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
        class RangeIterator {
            constructor(start, stop) {
                this.value = start;
                this.stop = stop;
            }
            [Symbol.iterator]() {
                return this;
            }
            next() {
                var value = this.value;
                if (value < this.stop) {
                    this.value++;
                    return { done: false, value: value };
                }
                else {
                    //done属性是一个布尔值,表示遍历是否结束。如果结束,则值也不会输出
                    return { done: true, value: undefined };
                }
            }
        }

        for (var value of new RangeIterator(0, 3)) {
            console.log(value);
            //0 1 2
        }

50.为对象添加Iterator接口

//1.Nodelist.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator]
//2.Nodelist.prototype[Symbol.iterator]=[][Symbol.iterator]
//3.在对象内部直接设置 [Symbol.iterator]:Array.prototype[Symbol.iterator]

51.使用Generator实现Iterator接口

        //Symbol.iterator方法的最简单实现,还是使用下一章要介绍的Generator函数。
        var myIterable = {};
        myIterable[Symbol.iterator] = function* () {
            yield 1;
            yield 2;
            yield 3;
        };
        [...myIterable] // [1, 2, 3]
        
        // 或者采用下面的简洁写法
        let obj = {
            *[Symbol.iterator]() {
                yield 'hello';
                yield 'world';
            }
        };
        for (let x of obj) {
            console.log(x);
        }
        // hello
        // world

52.for..of循环

        //for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象
        //(比如arguments对象、DOM NodeList对象、后文的Generator对象,以及字符串)
        //1. 数组
        const arr = ['red', 'green', 'blue'];
        console.log(arr);
        let iterator = arr[Symbol.iterator]();
        for (let v of arr) {
            console.log(v);
            // red green blue
        }
        for (let v of iterator) {
            console.log(v);
            // red green blue
        }
        //上面代码的for...of循环的两种写法是等价的。

53.Generator函数

   //Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
   //从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
   //执行Generator函数会返回一个遍历器对象,返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态
     function* helloGenerator() {
            yield 'hello';
            yield 'Generator';
            return "huohuo";  //如果没有return,返回对象的value值为undefined
        }
        var res = helloGenerator();
        // console.log(res.next());
        // console.log(res.next());
        // console.log(res.next());
        for (const iterator of res) {
            console.log(iterator);
        }

        function* gen() {
            // some code
        }
        var g = gen();
        g[Symbol.iterator]() === g;// true
        //上面代码中,gen是一个Generator函数,调用它会生成一个遍历器对象g。
        //它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己

54.Generator next()

          function* foo(x) { 
            var y = 2 * (yield (x + 1)); 
            var z = yield (y / 3);
             return (x + y + z);
              }
               var a = foo(5);
                a.next() // Object{value:6, done:false}
                a.next() // Object{value:NaN, done:false}
                a.next() // Object{value:NaN, done:true}
                var b = foo(5);
                b.next() // { value:6, done:false }
                b.next(12) // { value:8, done:false }
                b.next(13) // { value:42, done:true }
      //如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;
      //第二次调用next方法,将上一次yield语句的值设为12,因此y等于24,返回y / 3的值8;
      //第三次调用next方法,将上一次yield语句的值设为13,因此z等于13,这时x等于5,y等于24,
      //所以return语句的值等于42。
                
      //注意,next方法的参数表示上一个yield语句的返回值,第一次使用next方法时,不能带有参数
      //从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

55.第一次调用

        function wrapper(generatorFunction) {
            return function () {
                let generatorObject = generatorFunction();
                generatorObject.next();
                return generatorObject;
            };
        }
        const wrapped = wrapper(function* () {
            console.log('First input: ${yield}');
        });
        wrapped().next('hello!');// First input: hello!
        //上面代码中,Generator函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的

        //通过next方法的参数,向Generator函数内部输入值的例子
        function* dataConsumer() {
            console.log('Started');
            console.log('1. ${yield}');
            console.log('2. ${yield}');
            return 'result';
        }
        let genObj = dataConsumer();
        genObj.next();// Started  {value:undefind,done:false}
        genObj.next('a')// 1. a   {value:undefind,done:false}
        genObj.next('b')// 2. b   {value:result,done:true}
        //每次通过next方法向Generator函数输入值,然后打印出来

56.使用Generator函数实现斐波那契数列

        function* fibonacci() {
            let [prev, curr] = [0, 1];
            for (; ;) {  //死循环
                [prev, curr] = [curr, prev + curr];
                yield curr;
            }
           

        }
      for(let i of fibonacci()){
          if (i>30) {
              break;
          }
          console.log(i);
      }

57.使用generator函数为对象添加遍历接口

        function* objctEntries(obj) {
            let keyarr = Reflect.ownKeys(obj);//Reflect.ownKey()获取对象的key,返回一个数组
            for (let propkey of keyarr) {
                yield [propkey, obj[propkey]];
            }
        }
        let obj={
            name:"yang",
            age:18,
            sex:"nv"
        }
        for(let [key,value] of objctEntries(obj)){
            console.log(`${key}: ${value}`);
        }
        //上面代码中,对象jane原生不具备Iterator接口,无法用for...of遍历。这时,
        // 我们通过Generator函数objectEntries为它加上遍历器接口,就可以用for...of遍历了

58.generater throw

//Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获
        var g = function* () {
            try {
                yield;
            }
            catch (e) {
                console.log('内部捕获', e);
            }
        };
        var i = g();
        i.next();
        try {
            i.throw('a');
            i.throw('b');
        }
        catch (e) {
            console.log('外部捕获', e);
        }
              // 内部捕获 a
              // 外部捕获 b

   //上面代码中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获。
   //i第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,
   //所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。
   //注意:如果Generator函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行

59.throw

        //throw方法被捕获以后,会附带执行下一条yield语句。也就是说,会附带执行一次next方法。
        var gen = function* gen() {
            try {
                yield console.log('a');
            }
            catch (e) {
                // ... 
            }
            yield console.log('b');
            yield console.log('c');
        }
        var g = gen();
        g.next(); // a
        g.throw() // b
        g.next() // c

     //上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,
     //只要Generator函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历

60.return

//Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数。后面的yield将不会再执行
// 如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
        function* numbers() {
            yield 1;
            try {
                yield 2;
                yield 3;
            }
            finally {
                yield 4;
                yield 5;
            }
            yield 6;
        }
        var g = numbers();
        g.next();// { done: false, value: 1 }
        g.next(); // { done: false, value: 2 }
        g.return(7); // { done: false, value: 4 }
        g.next(); // { done: false, value: 5 }
        g.next(); // { done: true, value: 7 }
//上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法。

61.yield语句

        // yield*语句 在一个Generator函数里面执行另一个Generator函数。
        function* foo() {
            yield 'a';
            yield 'b';
        }


        function* bar() {
            yield 'x';
            yield* foo();
            yield 'y';
        }
        // 等同于
        function* bar() {
            yield 'x';
            yield 'a';
            yield 'b';
            yield 'y';
        }
        // 等同于
        function* bar() {
            yield 'x';
            for (let v of foo()) {
                yield v;
            }
            yield 'y';
        }
        for (let v of bar()) {
            console.log(v);
        }
        // "x"// "a"// "b"// "y"

62.yield*语句

        function* inner() {
            yield 'hello!';
        }
        function* outer1() {
            yield 'open';
            yield inner();
            yield 'close';
        }
        var gen = outer1();
        gen.next().value; // "open"
        gen.next().value;// 返回一个遍历器对象      可以通过gen.next().value.next().value输出值
        gen.next().value; // "close"

        function* outer2() {
            yield 'open';
            yield* inner();
            yield 'close';
        }
        // 从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,
        //表明它返回的是一个遍历器对象。这被称为yield*语句。

63.yield遍历

        //如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员
        function* gen() {
            yield* ["a", "b", "c"];
        }
        gen().next(); // { value:"a", done:false }
        //上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象

        //实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历
        let read = (function* () {
            yield 'hello';
            yield* 'hello';
        })();
        read.next().value // "hello"
        read.next().value // "h"
        
       //上面代码中,yield语句返回整个字符串,yield*语句返回单个字符。因为字符串具有Iterator接口,所以被yield*遍历

64.Promise

        //基本用法
        //then方法 的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数
        let promise = new Promise(function (resolve, reject) {
            console.log('Promise');
            resolve();
            // reject("reject");

        });
        promise.then(function () {
            console.log('Resolved.');
        }, function (str) {
            console.log(str);
        }).then(function () {
            console.log("one");
        }, function () {
            console.log("two");
        });
        console.log('Hi!');
       //Promise  Hi   Resolved  one 

       //简写:
       Promise.resolve("hello");
       Promise.reject("hi")

65.catch()

        // Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获
        let promise = new Promise(function (resolve, reject) {
            resolve();
        });
        promise.then(
            function () {
                y+2;
                return;
            }
        ).then(function () {
            x++;
            return;
        }).catch(function (error) {
            console.log(error);
        });
        //一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法

66.catch()

        //1.Promise.prototype.catch()就是.then(null,reject)的别名
        //2.第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
        var p1 = new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('fail')), 3000)
        });
        var p2 = new Promise(function (resolve, reject) {
            setTimeout(() => resolve(p1), 1000)
        });

  // p2.then因为参数是p1返回的是p1
  p2.then(result => console.log("res")).catch(error => console.log(error));// Error: fail

67.Promise.all()

      //Pomise.all的使用
      //Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,
      //成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
        let p1 = new Promise((resolve, reject) => {
            resolve('成功了')
        })

        let p2 = new Promise((resolve, reject) => {
            resolve('success')
        })

        let p3 = Promise.reject('失败')

        Promise.all([p1, p2]).then((result) => {
            console.log(result)               //['成功了', 'success']
        }).catch((error) => {
            console.log(error)
        })

        Promise.all([p1, p3, p2]).then((result) => {
            console.log(result)
        }).catch((error) => {
            console.log(error)      // 失败
        })           

68.Promise异步操作

        //1.回调函数
        fs.readFile('/etc/passwd', function (err, data) {
            if (err) throw err;
            console.log(data);
        });
        //上面代码中,readFile函数的第二个参数,就是回调函数,也就是任务的第二段。
        //等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行

        //2.Promise
        //回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。
        fs.readFile(fileA, function (err, data) {
            fs.readFile(fileB, function (err, data) {
                // ...  
            });
        });

        //Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。
        //采用Promise,连续读取多个文件,写法如下。
        var readFile = require('fs-readfile-promise');
        readFile(fileA).then(function (data) {
            console.log(data.toString());
        }).then(function () {
            return readFile(fileB);
        }).then(function (data) {
            console.log(data.toString());
        }).catch(function (err) {
            console.log(err);
        });

69.async和Promise

        //async和Promise对比
        function takeLongTime(n) {
            return new Promise(resolve => {
                setTimeout(() => resolve(n + 200), n)
            });
        }
        function step1(n) {
            console.log(`step1 with ${n}`);
            return takeLongTime(n);
        }
        function step2(n) {
            console.log(`step2 with ${n}`);
            return takeLongTime(n);
        }
        function step3(n) {
            console.log(`step3 with ${n}`);
            return takeLongTime(n);
        }
        //Promise方式
        function doIt() {
            console.time("doit");
            const time1 = 300;
            step1(time1)
                .then(time2 => step2(time2))
                .then(time3 => step3(time3))
                .then(result => {
                    console.log(`result is ${result}`);
                    console.timeEnd("doit");
                })
        }
        doIt();

        //async方式
        async function doSth() {
            console.time("doSth");
            const time1 = 300;
            var time2 = await step1(time1);
            var time3 = await step2(time2);
            var result = await step3(time3);
            console.log(`result is ${result }`);
            console.timeEnd("doSth")

        }
        doSth();

70.语法

      //1.正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。
        async function f() {
            return await 123;
        }
        f().then(v => console.log(v));// 123
       //上面代码中,await命令的参数是数值123,它被转成Promise对象,并立即resolve。


        //2.await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
        async function f1() {
            await Promise.reject('出错了');
        }
        f1().then(v => console.log(v)).catch(e => console.log(e));
       //注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。
       //这里如果在await前面加上return,效果是一样的。


        //3.只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。
        async function f2() {
            await Promise.reject('出错了');
            await Promise.resolve('hello'); // 不会执行
        }
        //上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。

        //4.为了避免这个问题,可以将第一个await放在try...catch结构里面,这样第二个await就会执行
        async function f3() {
            try {
                await Promise.reject('出错了');
            }
            catch (e) {

            }
            return await Promise.resolve('hello world');
        }
        f3().then(v => console.log(v));

        //5.另一种方法是await后面的Promise对象再跟一个catch方面,处理前面可能出现的错误。
        async function f5() {
            await Promise.reject('error').catch(e => console.log(e));
            return await Promise.resolve('hello.5');
        }
        f5().then(v => console.log(v));


        //6.如果有多个await命令,可以统一放在try...catch结构中。

71.Class基本语法

        function Point(x, y) {
            this.x = x;
            this.y = y;
        }
        Point.prototype.foo = function () {
            return (this.x + this.y);
        }
        let pt1 = new Point(1, 2);
        console.log(pt1.foo());



        class Dosth {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
            foo() {
                return this.x + this.y;
            }
        }
        let dt1 = new Dosth(12, 2);
        console.log(dt1.foo());

        //由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。
        //Object.assign方法可以很方便地一次向类添加多个方法。
        Object.assign(Dosth.prototype, {
            tostring(){},
            tovalue(){}
        });
        //注:ES6中类的内部所有定义的方法,都是不可枚举的(non-enumerable)
        

72.Constructor

   //constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
   //一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。例如,           constructor() {}

   //constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
        class Foo {
            constructor() {
                return Object.create(null);
            }
        }
        new Foo() instanceof Foo;//false       输出对象为空

        //注: 类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
        // Foo();


        //注:Class不存在变量提升(hoist),这一点与ES5完全不同。
        // new Foo1(); // ReferenceError
        // class Foo1 {}

73.私有方法

 //私有方法:类的外部无法看到这个方法,包括不同命名空间的、同一命名空间不同的类和这个类的子类,都不能看到这个方法。私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现
        //1.一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
        class Widget {
            foo(baz) {
                bar.call(this, baz);
            }
            // ... 

        }
        function bra(baz) {
            return this.snaf = baz;
        }

        //2.利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
        const bar = Symbol('bar');
        const snaf = Symbol('snaf');
        class Dosth {
            // 公有方法  
            foo(baz) {
                return this[bar]('baz');
            }
            //私有方法
            [bar](baz) {
                this[snaf] = baz;
                return this[snaf]
            }
        }
        let _dosth = new Dosth();
        console.log(_dosth.foo());
        console.log(_dosth[bar]('a'));
   // 上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。

74.this指向

       //类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
        class Logger {
            printName(name = 'there') {
                this.print(`Hello ${name}`);
            }
            print(text) {  //print()window下面的方法,打印内容
                console.log(text);
            }
        }
        const logger = new Logger();
        const { printName } = logger;
        printName();
     //上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,
        //this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。

        //1.一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
        class Logger {
            constructor() {
                this.printName = this.printName.bind(this);
            }
            // ...
        }

        //2.使用箭头函数
        class Logger {
            constructor() {
                this.printName = (name = 'there') => {
                    this.print(`Hello ${name}`);
                };
            }
            // ...
        }


        //3.使用Proxy,获取方法的时候,自动绑定this
        function selfish(target) {
            const cache = new WeakMap();
            const handler = {
                get(target, key) {
                    const value = Reflect.get(target, key);
                    if (typeof value !== 'function') {
                        return value;
                    }
                    if (!cache.has(value)) {
                        cache.set(value, value.bind(target));
                    }
                    return cache.get(value);
                }
            };
            const proxy = new Proxy(target, handler);
            return proxy;
        }
        const logger = selfish(new Logger());

75.继承

        class Point {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
        }
        class ColorPoint extends Point {
            constructor(x, y, color) {
                // this.color = color; // ReferenceError  
                super(x, y);// 调用父类的constructor(x, y) 注意:是方法,会直接运行父类构造函数
                this.color = color; // 正确 
            }
        }

        let cp = new ColorPoint(25, 8, 'green');
        cp instanceof ColorPoint // true
        cp instanceof Point // true
        console.log(cp.__proto__,cp.__proto__.__proto__);//分别指向ColorPoint,Point

76.类的prototype和proto

        //1.子类的__proto__属性,表示构造函数的继承,总是指向父类
        //2.子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
        class A {

        }
        class B extends A {

        }
        B.__proto__ === A;//true         //实例化对象也是指向类A
        B.prototype.__proto__ === A.prototype;//true

        //上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性

        //这样的结果是因为,类的继承是按照下面的模式实现的。
        class A {

        }
        class B {

        }
        // B的实例继承A的实例
        Object.setPrototypeOf(B.prototype, A.prototype);
        // B继承A的静态属性
        Object.setPrototypeOf(B, A);

77.类的静态方法

        //类相当于实例的原型,所有在类中定义的方法,都会被实例继承。

        //静态方法
        //1.如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
        class Foo {
            static classMethod() {
                return 'hello';
            }

        }
        //静态方法可以直接调用,而不是在Foo类的实例上调用
        Foo.classMethod(); //'hello'

        let foo = new Foo();
        // foo.classMethod();   // TypeError: foo.classMethod is not a function


        //2.父类的静态方法,可以被子类继承,子类实例化对象可以直接调用。
        class Bar extends Foo { 
          static Dosth(){
              return super.classMethod()+" static";
          }
        } 
        Bar.classMethod(); // 'hello'
        Bar.Dosth();//"hello,static"

78.静态属性和实例属性

        //1.静态属性
        //静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
        class Student {
            static age = 18;//ES7提出
        }
        //注:如果同时定义了相同属性,会输出外边的属性
        Student.sex = "girl";
        console.log(Student.age);

        //2.实例属性
        //类的实例属性可以用等式,写入类的定义之中
        class Person {
            myProp = 42;
            constructor() {
                console.log(this.myProp); // 42 
            }
        }

79.new target属性

        //ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令作用于的那个构造函数

        //1.定义:如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的
        function Person(name) {
            if (new.target !== undefined) {
                this.name = name;
            }
            else {
                throw new Error('必须使用new生成实例');
            }
        }
        // 另一种写法
        function Person(name) {
            if (new.target === Person) {
                this.name = name;
            }
            else {
                throw new Error('必须使用new生成实例');
            }
        }
        var person = new Person('张三'); // 正确
        // var notAPerson = Person.call(person, '张三');  // 报错

        //2.特点:Class内部调用new.target,返回当前Class
        class Rectangle {
            constructor(length, width) {
                console.log(new.target === Rectangle);
                this.length = length;
                this.width = width;
            }
        }
        var obj = new Rectangle(3, 4); // 输出 true

        //需要注意:子类继承父类时,new.target会返回子类
        class Square extends Rectangle {
            constructor(length) {
                super(length, length);
            }
        }
        var obj = new Square(3); // 输出 false

        //3.应用:利用这个特点,可以写出不能独立使用、必须继承后才能使用的类
        class Shape {
            constructor() {
                if (new.target === Shape) {
                    throw new Error('本类不能实例化');
                }
            }
        }
        class Rect extends Shape {
            constructor(length, width) {
                super();
                // ...  
            }
        }
        // var x = new Shape();  // 报错    
        var y = new Rect(3, 4);  // 正确

        //上面代码中,Shape类不能被实例化,只能用于继承。
        //注意,在函数外部,使用new.target会报错。

80.export

//模块功能主要由两个命令构成:export和import。
//export命令用于规划模块的对外接口
//import命令用于输出其他模块提供的功能

//1.export有三种写法(as写法)
//第一种
export var firstName = "yang";
export var lastName = "wen";
//第二种
var firstAge = 18;
var lastAge = 22;
export { firstAge, lastAge }

//2.export命令除了输出变量,还可以输出函数或类(class)。
export function foo(x, y) {
  return x + y;
}

//3.通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() {
  // ...
}
function v2() {
  // ...
}
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

  //上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次

//4.export命令可以出现在模块的任何位置,只要处于模块顶层就可以。
//如果处于块级作用域内,就会报错,下一节的import命令也是如此

81.import

// 1.js
var a = 1;
var b = function () {
    console.log(a);
}

var c = 3;
var d = a + c;

var obj = { a, b, c }



export { a, b };

export { c, d };

export default obj;

        //注:变量定义的值必须与模块中的值一致
        import { a, b } from "./199.exports.js"; // 导入export
        import m1 from "./199.exports.js"; // 不加{}即导入export default 
        import { c } from "./199.exports.js"; // 导入export 按需导入
        import * as m from "./199.exports.js"

        console.log(a); // 1
        console.log(b); // ƒ (){ console.log(a); }
        console.log(m1); // {a: 1, c: 3, b: ƒ}
        console.log(c); // 3
//export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export deault命令只能使用一次。

82.语法

2.1 转码结果输出到标准输出(控制台输出)
$ babel example.js

2.2 转码结果写入一个文件
--out-file or -o 参数指定输出文件
$ babel example.js --out-file compiled.js
$ babel example.js -o compiled.js

2.3整个目录转码
--out-dir or -d 参数指定输出目录
$babel src  --out-dir lib
$babel src  -d lib
posted @ 2021-08-08 23:07  yongerbingxuan  阅读(55)  评论(0)    收藏  举报