ES6面向对象

合并

取值函数Object.assign()

  1. /**
  2. * 合并对象
  3. * Object.assign(target, ...sourses);
  4. * 将需要合并的对象合并到对象里
  5. * @target 目标对象
  6. * @sourses 可能是多个的源对象
  7. * @返回值 目标对象所对应的值
  8. */
  9. let obj = {
  10. a: 1
  11. };
  12. let tar = {};
  13. let copy = Object.assign(tar, obj);
  14. console.log(copy); //{a: 1}
  1. //把几个对象进行合并
  2. //后面的属性会覆盖前面的属性
  3. const tar = {
  4. a: 1,
  5. b: 1
  6. };
  7. const tar2 = {
  8. b: 2,
  9. c: 2
  10. };
  11. const tar3 = {
  12. c: 3
  13. };
  14. Object.assign(tar, tar2, tar3);
  15. console.log(tar);
  16. //{a: 1, b: 2, c: 3}

利用Object.assign({},obj)拷贝对象,复制的值是浅拷贝对象

  1. var obj = Object.create({
  2. foo: 1
  3. }, {
  4. bar: {
  5. value: 2
  6. },
  7. baz: {
  8. value: 3,
  9. enumerable: true
  10. }
  11. })
  12. console.log(obj);
  13. //{baz: 3, bar: 2}
  14. //不能拷贝继承属性和不可枚举属性
  15. var copy = Object.assign({}, obj);
  16. console.log(copy);
  17. //{baz: 3}

拷贝数组 替换数组元素

  1. var a = Object.assign([1, 2, 3], [4, 5]);
  2. console.log(a);
  3. //[4, 5, 3]
  1. //拷贝的不是函数体本身而是具体的值
  2. const sourse = {
  3. get foo() {
  4. return 1;
  5. }
  6. }
  7. const target = {};
  8. Object.assign(target, sourse);
  9. console.log(target);
  10. //{foo: 1}

克隆对象

  1. const obj = {
  2. a: 1,
  3. b: 2,
  4. c: 3
  5. };
  6. const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
  7. console.log(clone);
  8. //{a: 1, b: 2, c: 3}

原型

访问原型的方法代替person.__proto__

  • Object.setPrototypeOf():写
  • Object.getPrototypeOf():读取
  • Object.create(obj):生成
  1. //通过Object.setPrototypeOf()方式手动指定原型
  2. let proto = {
  3. y: 20,
  4. z: 40
  5. };
  6. let obj = {x: 10};
  7. Object.setPrototypeOf(obj, proto);
  8. console.log(obj);
  9. /**
  10. * {x: 10}
  11. x: 10
  12. __proto__:
  13. y: 20
  14. z: 40
  15. */

super

super指向的是对象的原型对象(父类)

  1. //super指向原型
  2. let proto = {
  3. y: 20,
  4. z: 40,
  5. };
  6. let obj = {
  7. x: 10,
  8. //对象的简写方式访问原型上的y
  9. //只有对象的简写的写法才能生效
  10. foo() {
  11. console.log(super.y);
  12. }
  13. };
  14. Object.setPrototypeOf(obj, proto);
  15. obj.foo(); //20
  16. /**
  17. * {x: 10}
  18. x: 10
  19. __proto__:
  20. y: 20
  21. z: 40
  22. */

关于super()

  1. class Father{
  2. constructor(){
  3. //如果没有实例化Father类时,没有this绑定
  4. }
  5. }
  6. class Son extends Father{
  7. constructor(){
  8. //子类的super()方法实际上做了生成this绑定,并将this绑定为父类的this
  9. //所以如果在super()之前访问this会报错
  10. this.hobby = 'traval';
  11. super();
  12. }
  13. }

属性描述符

ES5之前没有提供检测属性特征的方法,检测属性是否是只读/可遍历

ES6提供属性描述符

属性描述符的方法有:

  • Object.defineProperty()
  • Object.defineProperties()

为什么会有属性描述符的方法?

因为JavaScript是弱类型语言,对于对象属性,变量的描述是不够彻底的,所以才有属性描述符方法对其进行完善处理

  1. /**
  2. * 查找属性描述符
  3. * Object.getOwnPropertyDescriptor()
  4. * @参数1 目标对象
  5. * @参数2 当前对象的属性描述符
  6. * @返回值 一个包含value,writable,enumerable,configurable属性的对象
  7. * @value 当前值
  8. * @writable 可写
  9. * @enumerable 可枚举
  10. * @configurable 可配置
  11. */
  12. let obj = {
  13. a: 2
  14. };
  15. console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
  16. //{value: 2, writable: true, enumerable: true, configurable: true}
  1. /**
  2. * Object.defineProperty()
  3. * 能够修改一个已有的属性,或添加一个新的属性
  4. * @参数1 需要定义的对象
  5. * @参数2 当前对象的属性
  6. * @参数3 对象,定义属性的描述
  7. * @返回值 修改后的对象
  8. * @value 当前值
  9. * @writable 可写 默认不可修改
  10. * @enumerable 可枚举 默认不可枚举
  11. * @configurable 可配置 默认不可删除
  12. */
  13. let obj = {};
  14. Object.defineProperty(obj, 'a', {
  15. value: 2,
  16. enumerable: true,
  17. writable: true,
  18. configurable: true
  19. });
  20. console.log(obj);
  21. //{a: 2}
  22. //试着打印当前描述状态
  23. console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
  24. //{value: 2, writable: true, enumerable: true, configurable: true}
  1. //当writable为不可写(静默失败)时,却可以被删除值
  2. //但configurable为不可配置时,不能删除值
  3. let obj = {};
  4. Object.defineProperty(obj, 'a', {
  5. value: 2,
  6. enumerable: true,
  7. writable: false,
  8. configurable: true
  9. });
  10. delete.obj.a;
  11. console.log(obj); //undefined
  1. function defineProperty(){
  2. var _obj = {};
  3. var a = 1;
  4. //每次定义属性的时候,都会有getter/setter的机制
  5. //该机制会对每次获取或重新赋值操作属性时都生效
  6. //getter/setter操作时会可以增加额外的操作
  7. Object.defineProperties(_obj, {
  8. a: {
  9. //注意:此处不能加writable或value描述符(不能与get,set共存),否则报错
  10. get(){
  11. //额外的操作
  12. },
  13. set(newValue){
  14. //额外的操作
  15. }
  16. },
  17. b: {
  18. ...
  19. }
  20. });
  21. return _obj;
  22. }
  23. //执行
  24. var obj = defineProperty();
  25. //使用:赋值
  26. obj.a = 1;

Class

访问原型的方式有:

  • Object.getPrototypeOf(person);
  • Person.prototype

关键字class表示类,本质是语法糖

  1. //ES5写法
  2. function Person(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. Person.prototype.say = function () {
  7. console.log('My name is ' + this.name + ', my age is' + this.age);
  8. }
  9. new Person('zhangsan', '18');
  10. /**
  11. * Person {name: "zhangsan", age: "18"}
  12. * age: "18"
  13. * name: "zhangsan"
  14. * __proto__:
  15. * say: ƒ ()
  16. * constructor: ƒ Person(name, age)
  17. * __proto__: Object
  18. */
  1. //ES6写法
  2. //区别:class类里面的方法是不可枚举的
  3. class Person {
  4. //类似ES6简写方法的写法
  5. constructor(name, age) {
  6. //私有属性
  7. //实例化的属性配置
  8. this.name = name;
  9. this.age = age;
  10. }
  11. //共有方法(对应原型上的方法)
  12. say() {
  13. console.log('i can say');
  14. }
  15. }
  16. new Person('lisi', '20');
  17. /**
  18. * Person {name: "lisi", age: "20"}
  19. * age: "20"
  20. * name: "lisi"
  21. * __proto__:
  22. * constructor: class Person
  23. * say: ƒ say()
  24. * __proto__: Object
  25. */

尝试利用Object.keys()方法遍历原型上的方法

  1. //ES5
  2. console.log(Object.keys(Person.prototype)); //["say"]

尝试利用Object.keys()方法遍历class上的方法

  1. //ES6
  2. console.log(Object.keys(Person.prototype)); //[]

说明区别:

类内部的方法是不可枚举的

类里的constructor能否更改this的指向呢?

  1. class Person {
  2. constructor() {
  3. //手动指定this
  4. //返回一个新的对象会更改this指向
  5. return Object.create(null);
  6. }
  7. }
  8. console.log(new Person() instanceof Person); //false

奇怪的写法:函数表达式的写法

  1. let Person = class {
  2. say() {
  3. console.log('111');
  4. }
  5. }
  6. new Person().say(); //111

更奇怪的写法(理解即可,不能用于实际开发)

  1. //立即执行
  2. //必须通过new的方式执行类
  3. let person = new class {
  4. say() {
  5. console.log('111');
  6. }
  7. }();
  8. person.say(); //111

注意点:

  1. //不存在函数声明提升
  2. //跟let一样形成暂时性死区(TDZ)
  3. console.log(new Person());
  4. class Person{}

类里面是否可以含有共有属性?

  1. class Person {
  2. a = 1;
  3. constructor(name) {
  4. this.a = name;
  5. }
  6. }
  7. console.log(new Person('wangwu'));
  8. /**
  9. * Person {a: 1, name: "wangwu"}
  10. * a: "wangwu"
  11. * __proto__: Object
  12. */
  13. //说明并没有共有属性,默认把属性在constructor底下定义了(私有属性)

如何定义共有方法私有化?外部不能访问

  1. //利用Symbol生成独一无二的字符串定义方法名
  2. const eat = Symbol();
  3. class Person {
  4. constructor(name) {
  5. this.name = name;
  6. }
  7. say() {
  8. console.log('saying');
  9. };
  10. [eat]() {
  11. console.log('eating');
  12. }
  13. }
  14. let person = new Person('wangwu');
  15. person.say(); //saying
  16. person.eat(); //报错

static关键字:静态方法,一般情况下不能用于定于属性

  1. //静态方法一般不会被new 实例执行的,而是直接通过类名调用执行
  2. class Person {
  3. static a() {
  4. console.log(1);
  5. };
  6. }
  7. Person.a(); //1

类里自动定义取值函数和存值函数getter/setter

  1. class Person {
  2. get a() {
  3. console.log(1);
  4. }
  5. set a(value) {
  6. console.log(2);
  7. }
  8. }
  9. let person = new Person();
  10. person.a; //1
  11. person.a = '3'; //2

注:类里默认是严格模式

继承

extends关键字:实现继承关系

  1. class Parent {
  2. constructor() {
  3. this.name = 'zhangsan';
  4. }
  5. }
  6. //继承父级
  7. class Child extends Parent {}
  8. console.log(new Child());
  9. /**
  10. * Child {name: "zhangsan"}
  11. * name: "zhangsan"
  12. * __proto__: Parent
  13. */

派生类

  1. //通过类进行传值
  2. class Parent {
  3. constructor() {
  4. this.name = 'zhangsan';
  5. }
  6. }
  7. class Child extends Parent {
  8. constructor() {
  9. this.age = '19';
  10. }
  11. }
  12. console.log(new Child().age);
  13. //必须通过super关键字指定this
  14. //Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  15. //增加super方法
  16. class Child extends Parent {
  17. constructor() {
  18. //super必须写在constructor里的最前面
  19. super();
  20. this.age = '19';
  21. }
  22. }
  23. console.log(new Child().age); //19

super当对象的时候:

  • 在对象当中指代对象的原型
  • 在静态方法中指向自己的父类

getter/setter

get/put操作是获取属性/赋值操作

ES5getter/setter的存在为了改写当前的[[Get]]/[[Put]]的默认操作,getter/setter操作时会可以增加额外的操作

关于数据劫持:
把一个对象里的属性进行可配置,可写,可枚举的设置,通过getter/setter方法对取值赋值进行逻辑上的扩展

  1. let obj = {
  2. a: 1
  3. };
  4. //这里是通过什么方式访问到a?
  5. //通过 [[Get]] 属性获取操作
  6. //对象默认的内置[[Get]]操作如何实现查找属性的?
  7. //[[Get]]默认操作:查找当前属性,如没有往原型找,如果原型都没有,返回undefined
  8. obj.a;
  9. //通过 [[Put]] 属性赋值操作
  10. //[[Put]]默认操作:
  11. //1.访问是否是属性描述符getter/setter
  12. //2.如果不是,再看writable的状态是否为false
  13. //3.赋值
  14. obj.a = 3;
  1. //模拟get方法
  2. var obj = {
  3. log: ['example', 'test'],
  4. //自定义get方法获取obj里的属性
  5. get latest() {
  6. if (this.log.length === 0) {
  7. return undefined;
  8. }
  9. //返回当前数组的最后一项
  10. return this.log[this.log.length - 1];
  11. }
  12. }
  13. console.log(obj.latest); //test
  1. //变种写法
  2. //通过defineProperty来定义一个getter
  3. var myObject = {
  4. get a() {
  5. return 2;
  6. }
  7. }
  8. Object.defineProperty(myObject, 'b', {
  9. get: function () {
  10. //拿到值乘以2
  11. return this.a * 2;
  12. },
  13. enumerable: true
  14. })
  15. console.log(myObject.a); //2
  16. console.log(myObject.b); //4
  1. //setter 调用set方法
  2. var language = {
  3. //参数形式赋值到name里
  4. set current(name) {
  5. this.log.push(name);
  6. },
  7. log: []
  8. }
  9. language.current = 'EN';
  10. console.log(language.log); //["EN"]
  1. //get和set一般请求都是成对出现的
  2. //getter/setter操作,覆盖了原本[[Get]][[Put]]
  3. var obj = {
  4. get a() {
  5. return this._a;
  6. },
  7. set a(val) {
  8. return this._a = val * 2;
  9. }
  10. }
  11. obj.a = 3;
  12. console.log(obj.a); //6
  1. const obj = {
  2. get foo() {},
  3. set foo(x) {}
  4. }
  5. //获取属性描述符
  6. var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
  7. //打印可描述性属性的取值函数 get foo
  8. console.log(descriptor.get.name);

禁止拓展/判断是否可以拓展

  1. //对象常量 设置
  2. //不可修改/不可删除 configurable:false
  3. //不可重写 writable: false
  4. //但可以往对象里新增属性和值
  5. var obj = {
  6. a: 2
  7. };
  8. //禁止对象拓展,阻止拓展属性 prventExtensions()
  9. Object.preventExtensions(obj);
  10. obj.b = 3;
  11. console.log(obj); //{a: 2}
  1. //isExtensionsible()判断是否可以拓展
  2. //false禁止拓展/true可以拓展
  3. console.log(Object.isExtensible(obj)); //false

描述符的默认状态

  1. //属性描述符 默认是false
  2. var obj = {
  3. a: 2
  4. };
  5. Object.defineProperty(obj, 'b', {
  6. value: 6
  7. })
  8. console.log(Object.getOwnPropertyDescriptor(obj, 'b'));
  9. //{value: 6, writable: false, enumerable: false, configurable: false}
  1. //属性描述符 全都是false
  2. var obj = {
  3. a: 2
  4. };
  5. obj.b = 3;
  6. console.log(Object.getOwnPropertyDescriptor(obj, 'b'));
  7. //{value: 3, writable: true, enumerable: true, configurable: true}

Proxy

Proxy代理与defineProperty()定义属性方法像完成同一个功能,但原理完全不一样,有了vue存在,proxydefineProperty有了交集

本质区别:

defineProperty给对象增加属性,修改数组的长度,用索引设置元素的值,数组的push, pop, 等一系列方法是无法出发defineProperty里的set方法,所以vue2.x版本里对数组的所有的操作都是vue再封装,并不是原生的push,pop等方法,导致vue代码非常的重,但Proxy可以正常使用原生的数组方法,比较轻便,不影响set方法使用,在数据拦截编写操作时更加合理,功能更加强大

写法:

  1. let obj = new Proxy(target, handler);
  2. //target 目标对象 要进行处理的对象
  3. //handler 容器 无数可以处理对象属性的方法

Proxy是一个ES6的构造函数

  1. var proxy = new Proxy(obj, {...});

ProxydefineProperty区别:

  • defineProperty:数据劫持 -> 给对象进行拓展 -> 属性进行设置
  • Proxy:并不是数据劫持,通过处理一个对象,返回一个代理对象,操作代理对象对数据进行操作

Proxy能够产生什么样的作用?

自定义对象属性的获取,赋值,枚举,函数调用的等功能

  1. //如何使用proxy读取属性?
  2. var target = {
  3. a: 1,
  4. b: 2
  5. }
  6. let proxy = new Proxy(target, {
  7. get(target, prop){
  8. //注意:get里面必须带有return否则undefined
  9. return `This is propery value ${target[prop]}`;
  10. }
  11. });
  12. //如果访问proxy.a,则返回代理后的对象里的属性
  13. console.log(proxy.a); //1
  14. //如果访问没有代理过的对象target,则访问原来对象里的属性
  15. console.log(target.a); //1
  1. //如何使用proxy更改属性?
  2. var target = {
  3. a: 1,
  4. b: 2
  5. }
  6. let proxy = new Proxy(target, {
  7. //target 处理的对象
  8. //prop 处理的对象里的属性
  9. //value 新修改的属性值
  10. set(target, prop, value){
  11. //需要处理新修改的值
  12. target[prop] = value;
  13. }
  14. });
  15. proxy.b = 3;
  16. console.log(target);
  17. //注意:代理后会更改原有对象的值
  18. //{a: 1, b: 3};

Proxy不仅可以操作对象,还可以操作数组和函数

  1. var arr = [];
  2. var fn = function(){...}
  3. let proxy1 = new Proxy(arr, {
  4. get(arr, prop){
  5. return arr[prop];
  6. }
  7. });
  8. let proxy2 = new Proxy(fn, {
  9. get(fn, prop){
  10. return fn[prop];
  11. }
  12. });
  1. let star = {
  2. name: 'wyf',
  3. age: 30,
  4. phone: 'star 15800000000'
  5. }
  6. /**
  7. * Proxy()
  8. * @param1 代理目标对象
  9. * @param2 定义代理值行为的一个函数的对象
  10. * @target 目标对象
  11. * @key 目标对象的键名
  12. * @value 目标对象的键值
  13. */
  14. //目标某些信息被拦截,且改写为中间商的信息
  15. let agent = new Proxy(star, {
  16. //查找明星的联系方式
  17. //读取操作
  18. get: function (target, key) {
  19. if (key === 'phone') {
  20. //返回代理人的电话
  21. return 'agent: 13788888888';
  22. }
  23. if (key === 'price') {
  24. //出场费
  25. return 120000;
  26. }
  27. return target[key];
  28. },
  29. //赋值操作
  30. set: function (target, key, value) {
  31. if (value < 1000000) {
  32. console.log('出场费太低,拒绝');
  33. } else {
  34. target[key] = value;
  35. return true;
  36. }
  37. },
  38. //has操作
  39. has: function (target, key) {
  40. console.log('请联系agent:14800000000');
  41. if (key === 'customPrice') {
  42. return target[key];
  43. } else {
  44. return false;
  45. }
  46. }
  47. });
  48. console.log(agent.phone); //agent: 13788888888
  49. console.log(agent.price); //120000
  50. console.log(agent.name); //wyf
  51. console.log(agent.age); //30
  52. agent.customPrice = 1500000;
  53. console.log(agent.customPrice); //1500000

拦截对象属性的读取,比如proxy.fooproxy['foo']

  1. get(target, propKey, receiver);

拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v ,返回一个布尔值

  1. set(target, propKey, receiver);

拦截propKey in proxy的操作,返回一个布尔值

  1. has(target, propKey);

拦截delete proxy[propKey]的操作,返回一个布尔值

  1. deleteProperty(target, propKey);
  1. //拦截
  2. Object.getOwnPropertyNames(proxy)
  3. Object.getOwnPropertySymbols(proxy)
  4. Object,keys(proxy)
  5. //返回一个数组,改方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
  1. Object.getOwnPropertyDescriptor(target, propKey);
  2. Object.getOwnPropertyDescriptor(proxy, propKey);
  3. //返回属性的描述对象
  1. //拦截
  2. Object.defineProperty(target, propKey, propDesc);
  3. Object.defineProperties(proxy, propDesc);
  4. //返回一个布尔值
  1. //拦截
  2. Object.preventExtensions(proxy);
  3. //返回一个布尔值
  1. //拦截
  2. Object.preventExtensions(proxy);
  3. //返回一个布尔值
  1. //拦截
  2. Object.getPrototypeOf(proxy);
  3. //返回一个对象
  1. //拦截
  2. Object.isExtensible(proxy);
  3. //返回一个布尔值
  1. //拦截
  2. Object.setPrototypeOf(proxy, proto);
  3. //返回一个布尔值,如果目标对象是函数,那么还有两种操作以外可以拦截
  1. //拦截Proxy实例作为函数调用的操作
  2. //比如proxy(...args), proxy.call(object, ...args);
  3. proxy.apply(target, object, args)
  1. //拦截Proxy实例作为函数调用的操作
  2. //比如new Proxy(...args)
  3. construct(target, args);

重写一个Proxy

  1. //基本写法
  2. var target = {
  3. a: 1,
  4. b: 2
  5. }
  6. let proxy = new Proxy(target, {
  7. get(target, prop){
  8. return 'This is property value' + target[prop];
  9. },
  10. set(target, prop, value){
  11. target[prop] = value;
  12. }
  13. });
  14. console.log(proxy.a);
  15. console.log(target.a);
  1. function MyProxy(target, handler) {
  2. //传入的target需要处理,并且是一个新的target克隆对象
  3. let _target = deepClone(target);
  4. function deepClone(org, t ar) {
  5. var tar = tar || {},
  6. toStr = Object.prototype.toString,
  7. arrType = '[object Array]';
  8. //遍历原来传入的对象
  9. for (var key in org) {
  10. //判断是否含有自身属性
  11. if (org.hasOwnProperty(key)) {
  12. //判断自身的属性值是否是对象并且不能为null
  13. if (typeof (org[key]) === 'object' && org[key] !== null) {
  14. //判断对象里属性值是否为数组
  15. if (toStr.call(org[key]) === arrType) {
  16. //是数组 将新的对象的属性值创建为一个新的空数组
  17. tar[key] = [];
  18. } else {
  19. //不是数组 将新的对象的属性值创建为一个新的空对象
  20. tar[key] = {};
  21. }
  22. //递归底层的属性值 深克隆
  23. deepClone(org[key], tar[key]);
  24. } else {
  25. //不是对象的情况就让其变为对象
  26. tar[key] = org[key];
  27. }
  28. }
  29. }
  30. return tar;
  31. }
  32. //拷贝完后,遍历键名和键值
  33. //Object.keys()遍历出自身的可枚举的键名(不含继承属性)
  34. Object.keys(_target).forEach(function (key) {
  35. //Proxy实现用defineProperty定义属性
  36. Object.defineProperty(_target, key, {
  37. //handler里面有get/set函数
  38. //get有target, prop参数
  39. //set有target, prop, value参数
  40. //get函数里必须有返回值return
  41. get() {
  42. //有get函数时执行它并传入对象和属性
  43. return handler.get && handler.get(target, key);
  44. },
  45. set(newValue) {
  46. handler.set && handler.set(target, key, newValue);
  47. }
  48. });
  49. });
  50. //最终返回处理好的对象
  51. return _target;
  52. }

总结:

Proxy的内部方法如has/set/get/deletePropert等转发给target对象,调用Proxy的方法相当于调用target相应的方法, handler方法可以复写任意代理内部的方法,可以通过handler重写Proxy上面的内部的方法,外界每次通过Proxy访问target对象属性的时候会经过handler里面每一个方法,因此可以通过重写handler对象中的一些方法来做一些拦截