JavaScript 高级程序设计 - 第 8 章 理解对象 学习笔记

JavaScript 高级程序设计 - 第 8 章 理解对象 学习笔记

本章内容量挺大的,因此笔记的话我也会分成 3-4 个部分去写,想要理解的细一点,顺便之后回顾的时候不会看的套类。

本章主要就是了解一下和理解一下什么是对象,包括 Object 上的 properties 和 mixin。

在 JavaScript 中,所有的存在(除了 primitive value)之外,都是对象,通常意义上来说,创建对象的方式有两种:

const person = new Object();
person.name = 'John';
person.age = 28;
person.sayHi = function () {
    
    
  console.log(`Hello from ${
      
      this.name}`);
};

const person2 = {
    
    
  name: 'Alex',
  age: 18,
  sayHi: function () {
    
    
    console.log(`Hello from ${
      
      this.name}`);
  },
};

二者在实现上没有什么区别,只是后者使用 object literal(对象字面量)更加的方便简洁。

attributes of property

以上面的实现为例,name, agesayHiperson 的 数据属性(data properties),并且同样也可以使用 Object.defineProperty() 的方法去赋值,如:

const person2 = {
    
    
  name: 'Alex',
  age: 18,
  sayHi: function () {
    
    
    console.log(`Hello from ${
      
      this.name}`);
  },
};

Object.defineProperty(person2, 'greet', {
    
    
  value: function () {
    
    
    console.log('Hello from the other side');
  },
});

person2.greet();

在这里插入图片描述

defineProperty 中传的第三个值,就是该 property 的 attributes。

interface PropertyDescriptor {
    
    
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}

/**
 * Marker for contextual 'this' type
 */
interface ThisType<T> {
    
     }

defineProperty<T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>): T;

PropertyDescriptor 中包含的,就是所有可能存在的属性,现在的规范是使用中括号括起来,如 [[Configurable]],不过说的都是一个东西。

  • value

    value 为 property 实际的值

  • writable

    扫描二维码关注公众号,回复: 15265037 查看本文章
    person2.name = 'John';
    Object.defineProperty(person2, 'name', {
          
          
      writable: false,
    });
    
    console.log(person2.name);
    person2.name = 'Dick';
    console.log(person2.name);
    
    Object.defineProperty(person2, 'name', {
          
          
      writable: true,
      value: 'Rewritten',
    });
    
    console.log(person.name);
    

    结果如下:

    在这里插入图片描述

    可以看到,当 writable 设置为 false 之后,重新对 property 赋值也不无法对其进行修改。就算是之后重新修改 writable 也不行

    非严格模式下这样的执行会被忽略,严格模式下这么做会报错

  • configurable

    configurable 是一个 boolean,当一个属性的 configurable 被设置成 false,意味着当前的属性无法被使用 defineProperty 被重新定义、无法被删除、无法修改 configurable, enumerablewritable

    如果 writable 的值为 true,那么 value 还是可以被修改的,否则这个对象就被彻底 freeze,再也无法修改。

    Object.defineProperty(person2, 'age', {
          
          
      configurable: false,
      writable: false,
    });
    
    Object.defineProperty(person2, 'age', {
          
          
      enumerable: false,
    });
    

    在这里插入图片描述

    顺便,Object.freeze() 等同于将 configurablewritable 设为 false。

  • enumerable

    这个 attribute 代表了当前属性是否会出现在 for...in

    for (const key in person2) {
          
          
      console.log(key);
    }
    
    Object.defineProperty(person2, 'name', {
          
          
      enumerable: false,
    });
    
    for (const key in person2) {
          
          
      console.log(key);
    }
    

    在这里插入图片描述

  • get

    getter 是另一个相对而言比较新的内容,它主要的目的就是为了处理一些返回值,比如:

    const person2 = {
          
          
      _id: 1,
      name: 'Alex',
      age: 18,
      sayHi: function () {
          
          
        console.log(`Hello from ${
            
            this.name}`);
      },
      get id() {
          
          
        return `id is: ${
            
            this._id}`;
      },
    };
    
    console.log(person2.id);
    

    在这里插入图片描述

    这个案例中,person2 没有直接声明 id property,但是可以通过 getter 返回一个 id property,其原因在于,它的具体实现为:

    const person = new Object();
    person.name = 'John';
    person.age = 28;
    person._id = 2;
    person.sayHi = function () {
          
          
      console.log(`Hello from ${
            
            this.name}`);
    };
    
    Object.defineProperty(person, 'id', {
          
          
      get: function () {
          
          
        return this._id;
      },
    });
    
    console.log(person.id);
    

    在这里插入图片描述

    使用 getter 还有另一个原因,放到下面 setter 一起说。

  • set

    setter 的作用和 getter 相似,其用法如下:

    const person2 = {
          
          
      _id: 1,
      name: 'Alex',
      age: 18,
      sayHi: function () {
          
          
        console.log(`Hello from ${
            
            this.name}`);
      },
      get id() {
          
          
        return `id is: ${
            
            this._id}`;
      },
      /**
       * @param {string} arg
       */
      set realName(arg) {
          
          
        this.name = 'my name is: ' + arg;
      },
    };
    

    在这里插入图片描述

    同样也可以用 defineProperty 去实现。

    JS 中出现 getter 和 setter 的另一个原因就在于,ES6 之前 JS 中是没有私有属性的。于是社区约定俗成了一个规范,就是在 property name 中添加 _ 代表不应该被访问的私有属性,要去访问和修改这个私有属性,就需要通过 getter 和 setter 去实现。这样实际保存值的 property,即 this.__privateValue,就不会直接被用户所访问。

定义的方法为 defineProperty,获取 properties 的方法有两个:Object.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptors()。前者需要知道 property 的名称,后者则会直接返回当前对象上的 properties。

console.log(Object.getOwnPropertyDescriptor(person2, 'name'));
console.log(Object.getOwnPropertyDescriptors(person2));

在这里插入图片描述

mixin

mixin 是一个被提到很多次的 concept,如果找一下的话,会发现 vue、scss 等都会发现直接有 mixin 这一属性或是函数。

ES6 也提供了 Object.assign() 的静态方法,它会遍历接收到的参数,然后将参数中所有可枚举 (Object.propertyIsEnumerable()true 的 properties) 和 自由(Object.hasOwnProperty()true 的 properties) 复制到目标属性上。

这可以很方便的实现 mixin,以下面代码为例:

const writeFile = {
    
    
  write: function () {
    
    
    console.log('Assume I can read file.');
  },
};

const scanFile = {
    
    
  scan: function () {
    
    
    console.log('Assume I can scan file');
  },
};

const readWriter = Object.assign({
    
    }, readFile, writeFile);
console.log(readWriter);

readFile.read();

const readWriterScanner = Object.assign({
    
    }, readWriter, scanFile);
readWriterScanner.scan();

在这里插入图片描述

可以看到,使用 mixin 提供更加灵活的语法,并且直接地满足了 SOLID 中的 single responsibility principle 和 interface segregation principle 两条原则。

liskov substitution principle 的判定我觉得稍微有点麻烦,逻辑上来说它要满足的事子类和父类,实现上来说 Object.assign() 完成了 shallow copy……

因为 JS 没有办法提供很好的静态检查,因此当使用 mixin 还会有其他的一些问题,如属性被重写的问题,依旧用上面的分代码为例,这里添加一点新的实现:

const fileOperator = {
    
    
  read: function () {
    
    
    console.log('I read file randomly.');
  },
  scan: function () {
    
    
    throw new Error('scan file fail.');
  },
};

const errorFileOperator = Object.assign({
    
    }, scanFile, fileOperator);
errorFileOperator.scan();

可以看到,在这个 mixin 中,scanfileOperator 重写了,所以这里的输出结果不是做一个 log,而是抛出异常:

在这里插入图片描述

搭配上 JS 中不存在的类型见擦,当项目规模比较大的情况下,尤其是出现 mixin 套用 mixin 的情况,property 的管理会成为一个比较麻烦的事情。

顺便,只是针对 Object.assign() 的话,它是没有回滚的概念的,也就是说,如果操作中出现一些异常,可能会出现一个部分实现复制的新对象。

总结一下 mixin 的优点和缺点。

优点:

  • 提高代码的复用性

  • 提高代码的灵活性

  • 模块化

  • composition over inheritance

    这是一个 OOP 常用的概念,组成偏向于继承,这样可以做到比较轻松的修改组成部分的代码,而不用担心影响到继承部分的功能

缺点:

  • 命名冲突

    假设说多个对象中都存在 id 这个属性,那么 JS 就无法 capture 这个问题

  • 排序很重要

    假设说多个对象中都存在 id 这个属性,那么最后一个 id 将会重写其他的 id

  • 直接依赖

    就不太像 spring 一样下面有底层的 DI 管理,很多时候要实现 mixin 还是得手动 cv,这样就会造成直接依赖(必须要知道对象,才能够实现 assign)

  • 提高代码复杂性

reference

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/130926402