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;
        }

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]