# ES6+

# let const

let 声明的变量会绑定到当前作用域,用 let 声明变量可以保证代码命名不重复。

const声明的变量不能更改(对象等不能改变类型,可以改变对象属性)

import可以变量提升

一般我们尽可能用const,如果这个值需要改变,我们再使用let,letconst声明的值不会被放到全局上

# 箭头函数

特点: 箭头函数中没有 this, arguments, prototype。没有就向上找~

# 解构赋值

展开运算符 ... 常用于合并数组或对象

剩余运算符 ... (要放在最后面)

解构的方式都是根据 key 来实现的 (这句是重点)

对象的话修改 key 对应的变量名称用 : (这个东西叫冒号) 如果没有对应的属性可以直接设置默认值

代码示例:

// 解构赋值 ...
// 解构的方式都是根据key来实现的
let arr = ["姓名", "年龄"];
let [, age] = arr;
console.log(age);

let { objName, objAge } = { objName: "Mopecat", objAge: "forever18" }; // 这里的两边的key要相同
console.log(objName, objAge);
// 如果想要修改变量名称 可以通过:来修改,如果没有对应属性的话 可以设置默认值
let { changeName: changed, noName = "我王*泽难道不配拥有姓名吗?" } = {
  changeName: "Feely"
};
console.log("修改了变量名称啊扑街", changed);
console.log("设置了默认值得变量", noName);

// 剩余运算符
// 示例只取出第一项剩下的还是一个数组 剩余运算符 ... 必须是最后一个元素 对象的用法相同
let [name1, ...args] = ["Mopecat", 18, "北京"];
console.log("取出的变量name1", name1);
console.log("剩下的还是一个数组args", args);

// 展开运算符 常用于合并数组或对象
let number = [1, 2, 3, 4];
console.log("展开后的number", ...number);

# set 和 map

setmapes6中的新的数据类型

上面都有一下增删改查清空的方法,区别的话 setadd, mapsetget 还有对应Object的差不多功能方法 set.keys()set.values()set.entries()

特点: 不能放重复项 (数组去重什么的简直不要太方便)

对应的还有弱保持类型 WeakSetWeakMap

WeakSet: 对象是一些对象值的集合, 并且其中的每个对象值都只能出现一次. 与 set 的区别主要有一下两点

  • WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象都可以.
  • WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素.

WeakMap: 原生的 WeakMap 持有的是每个键或值对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行, WeakMapkey 也是不可枚举的

代码示例:

// set 和 map是es6中的新的数据类型 特点 不能放重复项

let set = new Set([1, 2, 3, 4, 4, 1, 2, 3, 4]);
console.log(set); // 没有key属性
set.add(6); // 添加方法
// set.clear(); // 清空方法
// set.delete(2); // 删除某一项
// 下面这几个方法跟Object.keys/ Object.values/ Object.entries 功能上差不多
console.log(set.keys());
console.log(set.values());
console.log(set.entries()); // [value, value] 形式的数组 分别对应着keys和values
// set 可以被迭代 即 set有Symbol.iterator
set.forEach(item => console.log(item));

set.has(1); // 判断set中是否有1

let a1 = [1, 2, 3];
let a2 = [1, 2, 3, 4, 5, 6];
// 数组并集去重
let arrbing = [...new Set([...a1, ...a2])];
console.log(arrbing);

// 数组取交集
let s1 = new Set(a1);
let s2 = new Set(a2);
let arrjiao = [...s2].filter(item => s1.has(item));
console.log(arrjiao);

// 数组取差集
let s3 = new Set(a1);
let s4 = new Set(a2);
let arrcha = [...s4].filter(item => !s3.has(item));
console.log(arrcha);

// map和set的用法基本一致,会有内存泄漏的问题,如下面的例子
class Example {
  constructor(a) {
    this.a = a;
  }
}
let a = new Example();
let newMap = new Map([a, 1]); // 一个对应key 一个对应value
a = null; // 即使a被释放 内存中仍然存在Example这个类 可以将这部分代码放在一个html中打开用memory拍一个快照搜索一下
// 上述这种情况可以通过WeakMap来解决  即 当map的引用源对象被释放 那么map的引用也被释放

// 深拷贝 应用了weakMap
// 如果对于对象来讲 ...展开运算符只是展开一层,如果是多层对象就不是很实用了  =>  是浅拷贝  功能跟Object.assign一样
let info1 = {
  name: "Mopecat",
  age: "forever18",
  detail: { tall: 190, body: "strong", face: "老好看了" }
};
let info2 = { hobby: "篮个球", detail: { face: "男神啊我的天" } };
let newObj = { ...info1, ...info2 };
console.log(newObj); // detail 被覆盖了

// 那么什么是深拷贝呢?  就是拷贝出来的结果和以前没有关系
// 如何实现深拷贝呢?
newObj = JSON.parse(JSON.stringify(newObj)); // 一般这样实现,但是这样有一定缺点,什么缺点呢: 只能实现json格式的深拷贝 什么意思呢 ,不能有fucntion,不能有undefined,不能有正则

// 靠谱的深拷贝,递归拷贝
// 想要实现递归拷贝首先得判断数据类型 那么如何判断类型
// 1) typeof  无法区分 Array 和 Object
// 2)  Object.prototype.toString().call()  无法判断是谁的实例
// 3)  instanceof 可以判断类型,可以判断是谁的实例
// 4) constructor 构造函数

// 下面有注解为什么要用map / WeakMap
const deepClone = (value, hash = new WeakMap()) => {
  // 排除 null 和 undefined
  if (value == null) return value;
  if (typeof value !== "object") return value; // 包含了函数类型
  if (value instanceof RegExp) return new RegExp(value); // 如果是正则 返回一个新的正则
  if (value instanceof Date) return new Date(value); // 如果是日期 返回一个新的日期
  // .....特殊的要求继续判断
  // 拷贝的可能是一个对象 或者是一个数组 既能循环数组 又能 循环对象 => for in 循环
  let instance = new value.constructor(); // 根据当前属性构建一个新的实例
  if (hash.has(value)) {
    return hash.get(value);
  }
  hash.set(value, instance);
  // console.log(instance);
  for (let key in value) {
    // 过滤掉原型链上的属性,如果是实例上的属性 再拷贝
    if (value.hasOwnProperty(key)) {
      // 将hash 继续向下传递 保证这次拷贝能拿到以前的拷贝结果
      instance[key] = deepClone(value[key], hash);
    }
  }
  return instance;
};
let cloneInfo = deepClone(info2);
cloneInfo.detail.face = "无敌炸天帅";
console.log(cloneInfo);
console.log(info2);

// 注解示例 为什么要用 map / WeakMap
// 用WeakMap代替Map是为了防止内存泄漏
// 如果不使用map/WeakMap 则下面的这个示例会陷入死循环不能自拔 用map做相对简单点不然要每次存一下对象 然后传到下一次里面 然后在判断是否有重复 跟现在的逻辑是一样的 但是实现起来相对麻烦很多
let objExample = { a: 1 };
objExample.b = objExample;
console.log(deepClone(objExample));

# class

es5 中没有类,用构造函数模拟类。es5 中的类可以当做函数来调用,es6 中不行

原型链原理

ES5中的原型链:

// 面试题
// 怎么用es5来模拟es6中的class
// new的原理

// es5中没有类 用构造函数来模拟类
// 类中有两种属性 1)实例上的属性 2)公共属性
function Animal() {
  // 判断当前是不是通过new来调用的
  if (!(this instanceof Animal)) {
    throw new Error("没有new呢哦");
  }
  this.name = { name: "九儿" }; // 实例上的属性
  this.age = 1;
}
// es5中的类可以当做函数来调用,es6中不可以
// Animal(); // 这样调用的时候构造函数里的 this是undefined
let a1 = new Animal(); // new的时候构造函数里的this指向新的实例
let a2 = new Animal();
// 两个实实例上都有相同的属性(实例属性),但是并不相等
console.log(a1, a2);
console.log(a1.name === a2.name); // false

// 公共属性一般加在类的原型上
Animal.prototype.say = function() {
  console.log("喵~");
};

// 一般情况下不要直接操作__proto__
// 实例上有个属性 __proto__ 指向类的原型,类的原型上有constrcutor指向类本身(构造函数),所以实例__proto__的constructor指向类本身(构造函数)
console.log(a1.__proto__ === Animal.prototype);
console.log(a1.__proto__.constructor === Animal);
// 每个类都有个__proto__ 包括类的原型,原型的__proto__指向父类的prototype 形成链条,知道指向Object.prototype 然后Object.__proto__ === null
console.log(a1.__proto__.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null

// _--------------------------------------------
// 类的继承 继承实例上的属性,继承公共属性
function Animal1() {
  this.type = "哺乳类";
}
Animal1.prototype.say = function() {
  console.log("我是动物");
};
function Tiger(name) {
  this.name = name;
  Animal1.call(this); // 调用父类的构造函数,并且让this指向子类 =>继承实例上的属性
}

// 如果要拿到父类原型上的方法还要继承原型 =>继承公共属性
// Tiger.prototype = Animal1.prototype; // 这是错误的,相当于直接与父类的原型一样了,这样会造成修改Tiger上的属性时同时影响了Animal1
// Tiger.prototype.__proto__ = Animal1.prototype; // 根据上面原型链的学习可以知道=>子类原型的__proto__指向父类的原型
// 但是要遵循尽量不要操作__proto__的原则,下一行代码是es6的
// Object.setPrototypeOf(Tiger.prototype, Animal1.prototype); // 原理是一样的
// 下面的是用的最多的
Tiger.prototype = Object.create(Animal1.prototype, {
  constructor: { value: Tiger } // 将fn的constructor赋值为Tiger
});
// create 原理
// function create(parentProto, child) {
//   function Fn() {}
//   Fn.prototype = parentProto; // Animal1
//   let fn = new Fn();
//   fn.constructor = child; // Tiger
//   return fn;
// }

let tiger = new Tiger("大九儿");
console.log(tiger.constructor);
console.log(tiger.type);
tiger.say();

// 继承一般就两种 Object.create 和 __proto__改变指向
// 继承实例的属性就是call

class应用代码示例:

// 类
// __proto__ 指向所属类的原型
// prototype 所有类都有一个prototype属性
// constructor prototype.constructor 每个类的原型上都有这个属性
class Animal {
  // type = "哺乳类"; // 可以这样声明在实例上 (但是现在还不支持,还是实验性语法)
  constructor(name) {
    this.name = name;
    this.type = "哺乳类"; // 现在还是要这样写的,将来上面那样写就可以了
  }
  // 在原型上声明属性 Animal.prototype.a = 1
  get a() {
    // 这里的实现原理是 Object.defineProperty(Animal.prototype,a)
    return 1;
  }
  // 放到原型上的方法 相当于 Animal.pototype.say
  say() {
    console.log(this, "===");
  }
  // 静态属性就是定义在类上的属性
  static flag = "动物"; // 这样用=赋值是es7的语法 es6中只有静态方法 flag(){ return '动物'}
  // es6中的静态属性的写法是
  static get flagES6() {
    return "ES6 => flag";
  }
}
let animal = new Animal();
let say = animal.say; // 如果将类中的方法拿出来用必须绑定this 否则默认指向undefined
say(); // undefined
// 应该用bind绑定一下
let say1 = animal.say.bind(animal);
say1();
console.log(Animal.flag, Animal.flagES6);

class Tiger extends Animal {
  // 如果子类里写了constructor 就必须调用super 不然会报错 Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  constructor(name) {
    super(name); // 相当于 Animal.call(tiger,name) 在constructor中只能被调用一次
  }
  static getAnimal() {
    console.log(super.flag, "静态方法中的super就是父类");
  }
  say() {
    super.say(); //
    console.log("原型上的方法中的super是父类的原型");
  }
}
let tiger = new Tiger("老虎");
console.log(Tiger.flag); // 静态方法和静态属性在es6中也会被子类继承
console.log(tiger);
tiger.say();

# 装饰器

  • 装饰模式:不改变原有状态,在访问原来函数之前做一系列操作
  • AOP 面向切片编程
  • 重写原型方法 例子,vue 里调用数组方法实现视图的更新 函数劫持,下面例子
let oldProto = Array.prototype; // 保留数组原有的原型
let proto = Object.create(oldProto); // 创建了一个新proto
["push", "unshift", "shift"].forEach(method => {
  proto[method] = function(...args) {
    update();
    oldProto[method].call(this, ...args);
  };
});

function observer(obj) {
  // 只将我们传入的数组中的方法重写
  if (Array.isArray(obj)) {
    obj.__proto__ = proto;
  }
}
observer(arr);
arr.push(1);
console.log(arr);
[].push(4, 5, 6);

其他代码示例:

// 装饰器 装饰模式, 在执行类之前,对类,类的属性还有类中的方法进行包装(类中的属性,还有类中的方法)参数分别为 类的原型,装饰的key,key对应的属性装饰器
// 装饰器必须是个函数 传参了就再包一层返回一个函数
@type1("传了个参数1")
@type2("传了个参数2")
class Animal {}

// 对类进行扩展 如果不传参 就近执行 先执行2后执行1, 如果传参了 就先执行外层函数的1,2,然后执行内层函数的2,1,像切洋葱一样的执行顺序

function type1(args) {
  console.log(`t1`);
  return function(Constructor) {
    console.log("inner1");
    Constructor.type1 = args;
  };
}

function type2(args) {
  console.log(`t2`);
  return function(Constructor) {
    console.log("inner2");
    Constructor.type2 = args;
  };
}

let animal = new Animal();
console.log(animal);

class Circle {
  @readonly PI = 3.14; // 实例上的属性
  @before
  area(radius) {
    console.log(this);
    console.log("圆的面积是:" + radius * radius * this.PI);
  }
}

function readonly(circlePrototype, key, descriptor) {
  console.log(circlePrototype, key, descriptor);
  descriptor.writable = false;
}
function before(circlePrototype, key, descriptor) {
  let func = descriptor.value; // 赋值原函数

  descriptor.value = function(args) {
    console.log(
      "求圆的面积之前呢 可以做点什么,顺便求个周长吧:" + this.PI * args * 2
    );
    func.bind(this)(args); // 调用原函数,绑定当前的this,circle
  };
}
let circle = new Circle();
circle.PI = 3.111; // 修改失败
console.log(circle.PI);
circle.area.bind(circle)(10); // 将area的this指向circle 用es6 class声明的原型上的方法中的this指向undefined

// mixin
let obj = {
  name: "Feely",
  age: "forever 18",
  info() {
    console.log("Feely贼几把帅");
  }
};

@mixin(obj)
class Feely {}
// const mixin = obj => Feely => Object.assign(Feely.prototype, obj);
// 试了一下上面的简便写法 报错,错误是const不支持变量提升这种错误,所以目前来看装饰器的外层函数必须要用function来写了
function mixin(obj) {
  return function(Feely) {
    Object.assign(Feely.prototype, obj);
  };
}

let feely = new Feely();
console.log(feely); // obj的属性和方法都已经挂到了 Feely的原型上
feely.info();

# generator

生成器 生成迭代器的 (很快就被 async+await 取代掉了,但是相对来讲更加灵活

应用实例: redux-saga

用法:生成器函数上加 * 函数中 关键字 yield可以暂停执行,返回值是迭代器 iterator可以通过 iterator.next()返回对象及当前执行状态类似于这种{value: 11, done: false}

代码示例:

// 生成器 生成迭代器的 => es6 (很快就被async+await取代掉了,但是相对来讲更加灵活)

// 用法 * 生成器函数 ,返回值叫迭代器
function* read() {
  yield 1;
  yield 2;
  yield 3;
}
// iterator 迭代器
let it = read();
console.log(it.next()); // {value: 1, done: false} done表示没有结束 不停地next执行下去

// 将类数组转化为数组
// 类数组的定义: 1.索引 2、长度
function add() {
  console.log([
    ...{
      0: 1,
      1: 2,
      2: 3,
      length: 3,
      // 生成器函数实现 返回的就是迭代器
      [Symbol.iterator]: function*() {
        let index = 0;
        while (index !== this.length) {
          yield this[index++];
        }
      }
      // 迭代器实现
      // [Symbol.iterator]() {
      //   let len = this.length;
      //   let index = 0;
      //   // 迭代器是有next方法 而且方法执行后需要返回value,done
      //   return {
      //     next: () => {
      //       return { value: this[index++], done: index === len + 1 };
      //     }
      //   };
      // }
    }
  ]);
}
add(1, 2, 3, 4, 5);
// 延伸面试题 ...和Array.from有什么区别: ...所展开的对象,类数组,数组等必须有迭代器方法

function* read1() {
  // 可以用try catch
  try {
    let a = yield 1;
    console.log("a", a);
    let b = yield 2; // 这里的返回值是上一次调用next传入的参数
    console.log("b", b);
    let c = yield 3;
    console.log("c", c);
  } catch (e) {
    console.log("e", e);
  }
}
let it1 = read1();
console.log(it1.next()); // 第一次传入的参数毫无意义
console.log(it1.next()); // a,undefined
console.log(it1.next(100)); // b, 100
it1.throw("有错误了");

const fs = require("fs").promises;
function* read2() {
  let content = yield fs.readFile("./name1.txt", "utf8");
  let age = yield fs.readFile(content, "utf8");
  return age;
}
let it2 = read2();
// 用法很复杂
// it2.next().value.then(data => {
//   it2.next(data).value.then(data => {
//     let r = it2.next(data);
//     console.log(r.value);
//   });
// });

// 封装一个co方法 用以简便上面复杂过程
function co(it) {
  return new Promise((resolve, reject) => {
    // 异步迭代需要先提供一个next方法
    function next(data) {
      let { value, done } = it.next(data);
      if (!done) {
        Promise.resolve(value).then(
          data => {
            next(data);
          },
          err => {
            reject(err);
          }
        );
      } else {
        resolve(value);
      }
    }
    next();
  });
}

co(read2()).then(data => console.log(data));

// async + await 其实是 generator + co的语法糖
async function read3() {
  try {
    let content = await fs.readFile("./name1.txt", "utf8");
    let age = await fs.readFile(content, "utf8"); // await会阻塞代码,必须要等待上一个await执行完才能执行这一部分,如果要求并发可以用Promise.all来实现
    return age;
  } catch (err) {}
}
read3().then(data => console.log(data));

# Symbol

5中基本的数据类型: string,boolean,null,number,undefined

Symbol是新增的一种数据类型,独一无二,一旦声明完跟别的值都不相等,所以一般用作声明常量

# 基础用法

const s1 = Symbol("mopecat"); // 接收参数是string || number
const s2 = Symbol("mopecat"); // 接收参数是string || number
console.log(s1 === s2); // false

另一种创建方式 Symbol.for() 如果没有就创建新的 如果有就返回

let sf1 = Symbol.for("sf");
let sf2 = Symbol.for("sf");
console.log(sf1 === sf2); // true
console.log(Symbol.keyFor(sf1)); // 返回key值 上面的sf

在对象中定义 Symbol 属性 可以用来做属性私有化


let obj = {
  [s1]: "Symbol定义的值" // es6的写法 []的含义是将s1结果取出来作为key
};
console.log(obj[s1]); // 缺陷是不能用obj.s1来取Symbol的值了

# 元编程

可以改变js原有的功能

# 1) instanceof 可以判断谁是谁的实例

let o = {
  name: 1
};
let obj1 = {
  // 下面在使用instanceof时会默认调用下面的这个方法 但是定义了Symbol.hasInstance的对象要在instanceof的右侧
  [Symbol.hasInstance]() {
    return "name" in o;
  }
};
console.log(o instanceof obj1); // true

# 2) Symbol.toPrimitive 当转换原始类型的时候会调此方法

let obj2 = {
  // 当转换原始类型的时候会调此方法
  [Symbol.toPrimitive](value) {
    console.log(value);
    return "hello";
  },
  a: 1
};
// valueOf toString
console.log(obj2 + 1);

# 3)修改衍生对象的类的构造函数

class MyArray extends Array {
  constructor(...args) {
    super(...args); // 继承父类
  }
  // 静态 属性上的方法
  static get [Symbol.species]() {
    return Array; // 控制衍生出来的对象的类的构造函数是Array 不是 MyArray
  }
}
let myarr = new MyArray(1, 2, 3);
let newArr = myarr.map(item => item * 2); // newArr就是衍生出来的数组对象
console.log(newArr instanceof MyArray); // false

# 4) Symbol.split 可以重写数组的分割方法

# 5) Symbol.search 可以重写数组的搜索方法

# 6)Symbol.match 可以重写字符串的match方法

# 7)Symbol.unscopables() 可以声明一下属性 不在with中使用

console.log(Array.prototype[Symbol.unscopables]); // 数组上不能用在with中的方法
with (Array.prototype) {
  fill(112); // fill不能用在with中所以报错
}

# 8) concat不展开数组 isConcatSpreadable

let arr = [1, 2, 3];
arr[Symbol.isConcatSpreadable] = false;
console.log(arr.concat(4, 5, 6)); // [[1,2,3],4,5,6]

# 例子:

// 再来一个例子
class Me {
  showName() {
    console.log("Mopecat");
  }
  get [Symbol.unscopables]() {
    return { showName: true };
  }
}
with (Me.prototype) {
  showName(); // 报错 如果注掉class Me里的Symbol.unscopables就不会报错了 就会正常打印Mopecat了
}

# proxy

proxy 兼容性差,代理,可以创建一个代理帮我们干某些事

# 用法:

let obj = {
  a: 1,
  b: 2
};
let proxy = new Proxy(obj, {
  // 只能代理当前的对象obj 也就是一层
  get(target, key) {
    // return target[key];
    return Reflect.get(target, key); // 跟上一行等价
  },
  set(target, key, value) {
    console.log("更新视图", target, key, value);
    // target[key] = value;
    return Reflect.set(target, key, value); // 跟上一行等价
  }
});
proxy.c = 3;

console.log(obj);

# 如果是多层对象呢 该怎样实现代理呢 看下面的代码~

let obj1 = {
  a: { a: 122 },
  b: 2
};
let handler = {
  get(target, key) {
    if (typeof target[key] === "object" && target[key] !== null) {
      return new Proxy(target[key], handler);
    }
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log("更新");
    return Reflect.set(target, key, value);
  }
};
let proxy1 = new Proxy(obj1, handler);
proxy1.a.a = 1000;
console.log(obj1);

# 数组一样可以实现代理监控

let arr = [1, 2, 3, 4];
let proxyArr = new Proxy(arr, {
  get(target, key) {
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    if (key === "length") return true; // 如果操作会更改数组的length 比如push等 会触发两次set 一次更改 对应的key,一次修改length,所以屏蔽修改length触发的set
    console.log("更新");
    return Reflect.set(target, key, value);
  }
});
proxyArr.push(100);
console.log(arr);

# Reflect 反射

有部分是对象的方法,放到了 Reflect 上,功能是基本一致的,但是用起来更加的方便

# 1) get/set

let obj = {};
obj.name = "Mopecat";
// Reflect用法
Reflect.set(obj, "age", "forever18"); // 参数分别为 target(目标对象) key(属性名) value(值)

# 2) has

console.log("a" in { a: 1 });
console.log(Reflect.has({ a: 1 }, "a")); // 参数分别为 target(目标对象) key(属性名)

# 3) defineProperty 用法和功能与Object.defineProperty基本一致

const obj3 = { a: 1 };
Object.freeze(obj3); // 这个对象就被冻结了 对象里的属性就不能配置了 如果用Object.defineProperty 配置就会报错了 但是用Reflect.defineProperty就不会 但是如果配置失败会返回false
let flag = Reflect.defineProperty(obj3, "a", {
  value: 100
});
console.log(flag); // false

# 4) getOwnPropertyDescriptor 获取对象自有属性对应的属性描述 与 Object.getOwnPropertyDescriptor 一样

const obj4 = { a: 1 };
console.log(Reflect.getOwnPropertyDescriptor(obj4, "a"));

# 5) ownKeys = [...Object.getOwnPropertyNames , ...Object.getOwnPropertySymbols]

Object.getOwnPropertyNames 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组

Object.getOwnPropertySymbols 返回一个给定对象自身的所有 Symbol 属性的数组

const obj5 = { a: 1, [Symbol()]: 1 };
Object.getOwnPropertySymbols;
let arr5 = Reflect.ownKeys(obj5);
console.log(arr5);

# 6) getPrototypeOf / setPrototypeOf 获取或者设置一个对象的原型 参数是 target 目标对象 prototype 该对象的新原型(也就是一个对象或者null

const obj6 = { a: 1 };
Reflect.setPrototypeOf(obj6, null);
let obj6R = Reflect.getPrototypeOf(obj6);
console.log(obj6R); // null

# 7) 函数的apply方法

// 延伸:经典面试题 bind call apply 的区别 共同点都能改变this的指向
const fn = function(a, b) {
  console.log(this, a, b);
};
// apply 支持多个参数传参
fn.apply(1, [2, 3]); // this:1 , a:2 , b:3
// call 参数要一个一个传 并直接执行函数
fn.call(1, 2, 3);
// bind产生一个新的函数 剩下的参数也要一个一个传(bind函数可以用函数柯里化写出来)
let fn2 = fn.bind(1, 2, 3);

Reflect.apply(fn, 1, [2, 3]); // fn 绑定的函数,1 this的指向,[2,3]是参数 等价于 Function.prototype.apply.call(fn,1,[2,3]) 这样做的是为了 调用原型上的apply方法 (比如在实例上写了一个apply方法 但是你想调用的原型上的 就这样用行了)

# 8) construct 构造器 跟new的功能一样

class Example {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
let example = Reflect.construct(Example, ["Mopecat", "forever18"]); // 参数  target构造函数 ,[]参数数组
console.log(example);

# 10) isExtensible / preventExtensions 是否可扩展 / 组织扩展

let obj10 = {};
Reflect.preventExtensions(obj10);
obj10.a = 1;
console.log(obj10, Reflect.isExtensible(obj10));

# Object.defineProperty

# 用法:

let obj1 = {};
let val = ""; // 修改的时候一样也要用个第三方变量中转一下
Object.defineProperty(obj1, "a", {
  configurable: true, // 是否可配置(删除)
  // writable: true, // 是否可写 (写了get 和 set 要删除这个属性 默认就是可写的了)
  enumerable: true, // 是否可枚举
  get() {
    return val;
  },
  set(value) {
    console.log(value);
    val = value;
  }
});
console.log(obj1);

# vue中的数据劫持 就是给每一个对象都添加一个getter和setter 当值变化了 可以实现更新视图的功能 原理代码~~

let obj3 = {
  a: { a: 1 },
  b: 2
};
// 更新视图方法
let updateView = () => {
  console.log("更新了视图");
};
// 缺陷就是无法监控数组的变化
function observer(obj) {
  if (typeof obj !== "object" || obj == null) {
    return;
  }
  for (let key in obj) {
    // 因为defineProperty需要一个特公共的值去修改,所以利用函数作用域将value保存下来作为公共的值去修改
    defineReactive(obj, key, obj[key]);
  }
}

function defineReactive(obj, key, value) {
  observer(value); // 递归增加getter 和 setter
  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(val) {
      updateView();
      value = val;
    }
  });
}
observer(obj3);
obj3.b = 100;
console.log(obj3.b);

因为递归更加消耗性能 且 不能监控数组变化 且 无法增加被监控的属性(需要通过vm.$set来实现) 所以新版改用proxy来实现