属性描述符

一、属性描述

属性描述用来描述一个属性的行为,具体的属性描述符如下:

  • value,属性的值
  • writable, 属性是否可写
  • enumerable,属性是否可被遍历,如for in
  • configurable,属性是否可以被重新描述
  • get 属性访问器, getter, 读取属性时会调用这个函数获取它的返回值作为结果。
  • set 属性访问器, setter, 当给属性赋值时会调用这个函数, 并将赋值作为参数传入。

语法:

js 复制代码
    //属性描述
    Object.defineProperty(obj, 'name', {
      value: 'jack',
      writable: true,
    });
    
    // 获取obj.name的属性的描述
    Object.getOwnPropertyDescriptor(obj, 'name');
    
    // 获取obj下所以字段的属性描述
    Object.getOwnPropertyDescriptors(obj);

1.1 writable

user对象使用属性描述新增了一个gender属性,writable描述为false,该属性无法被修改

js 复制代码
    const user = {
      name: 'jack',
      age: 21
    }

    Object.defineProperty(user, 'gender', {
      writable: false,
      value: '男',
    });

    user.gender = '女';
    console.log(user.gender); //男

1.2 enumerable

enumerable设置为false之后该属性不能再被遍历到

js 复制代码
    Object.defineProperty(user, 'gender', {
      writable: false,
      value: '男',
      enumerable: false,
    });

    for(key in user){
      console.log(key);
    }
    // name, age

1.3 configurable

上面的例子中gender属性被描述为不可写,但是仍然可以通过重新定义属性描述破坏这个"规则"。

js 复制代码
    const user = {
      name: 'jack',
      age: 21
    }

    Object.defineProperty(user, 'gender', {
      writable: false,
      value: '男',
      enumerable: false,
      configurable: true,
    });

    Object.defineProperty(user, 'gender', {
      writable: true,
      value: '女',
      enumerable: true,
      configurable: true,
    });

上面的代码修改了gender并重新设置了可写, 为了使gender真正的不能被修改,需要把configurable也设置为false。

需要注意的是在使用defineProperty的时候如果没有手动声明configurable则该描述符就默认为false。理解起来就是属性不能被reDefineProperty,除非手动声明了configurable: true。

js 复制代码
  Object.defineProperty(obj, 'name', {
      value: 'jack',
    });

    // 等同于上面的代码
    Object.defineProperty(obj, 'name', {
      value: 'jack',
      writable: false,
    });

1.4 get和set

get和set统称为访问器。

js 复制代码
    const user = {
      name: 'jack',
    }

    templeAge = 0;
    Object.defineProperty(user, 'age', {
      set(_age) {
        if (typeof _age !== 'number') {
          throw new Error(`age必须为数字类型, 当前age的类型为: ${typeof _age}`);
        } else if (_age < 0) {
          throw new Error(`age不能小于0, 当前age的值为: ${_age}`);
        } else if (!Number.isInteger(_age)) {
          throw new Error(`age必须是整数, 当前age的值为: ${_age}`);
        }else {
          templeAge = _age;
        }
      },
      get() {
        return `age is: ${templeAge}!`;
      }
    });
    user.age = '10'; // Uncaught Error: age必须为数字类型, 当前age的类型为: string
    user.age = 1;
    console.log(user.age); //age is: 1!

当使用访问器读取和设置属性的时候都会经过set、和get函数。因此可以在set函数中对设置的值进行拦截操作在,做一些数值、类型或者其它逻辑的判断,当不符合条件的时候抛出对应的自定义错误。在get函数中也可以进行一些拦截操作,比如属性计算,对原来的属性进行一些逻辑计算之后再进行返回。

访问器的死循环

思考下上面为什么单独使用一个templeAge临时变量,而不是直接像下面的代码直接使用user.age?

js 复制代码
    Object.defineProperty(user, 'age', {
      set(_age) {
        user.age = _age;
      },
      get() {
        return user.age;
      }
    });

因为user.age本身是一个读取操作,会执行get获取到返回值,而get内部也有user.age的逻辑也会执行get函数,就陷入了死循环。set同理。

二、购物车案例

js 复制代码
    class ShoppingCard {
      tempChoose = 0
      constructor(data) {
        Object.defineProperty(this, 'data', {
          writable: false,
          value: data,
        });

        Object.defineProperty(this, 'choose', {
          set(_val) {
            if (Number.isInteger(_val) && _val >= 0) {
              this.tempChoose = _val;
            } else {
              throw new Error('choole必须为大于0的正整数')
            }
          },
          get() {
            return this.tempChoose;
          }
        })
      }
      
      // calss中属性修饰的语法糖
      get totalPrice() {
        return this.data.price * this.tempChoose;
      }
    }

    const shoppingCard = new ShoppingCard({
      price: 10,
      id: '15896832',
      classify: '日常百货',
      name: '粉色牙刷'
    });

三、总结

属性描述可以控制对象属性的行为,增加代码的稳健性,get、和set提供了劫持读写操作的能力,具有很大的操作空间。 vue2的依赖监听就是通过属性描述的访问器实现的。

四、拓展

属性描述符只能用来修饰对象的某个属性,如果这个被修饰的属性也是对象呢?

js 复制代码
    const user = {
      name: '小赵',
    }
    Object.defineProperty(user, 'friends', {
      writable: false,
      value: {
        a: '小赵',
      }
    });
    user.friends.a = '小张';
    user.friends.b = '小王';
    console.log(user.friends); // {a: '小张', b: '小王'}

可以发现虽然friends不能被修改,但是friends下面的属性仍然可以新增和修改,这个时候就需要Object.freeze(user.friends)把对象进行冻结了,被冻结的对象不能进进行读以外的任何操作。此外还可以使用Object.seal(friends)将对象进行封存,被封存的对象只能修改属性的值,不能新增和删除属性。 需要注意的是freeze和seal操作都是不可逆的,如果不想影响到原本的对象,需要拷贝一下原数据再使用。

相关推荐
独立开阀者_FwtCoder7 分钟前
stagewise:让AI与代码编辑器无缝连接
前端·javascript·github
清沫8 分钟前
Cursor Rules 开发实践指南
前端·ai编程·cursor
江城开朗的豌豆14 分钟前
JavaScript篇:对象派 vs 过程派:编程江湖的两种武功心法
前端·javascript·面试
不吃糖葫芦315 分钟前
App使用webview套壳引入h5(二)—— app内访问h5,顶部被手机顶部菜单遮挡问题,保留顶部安全距离
前端·webview
菥菥爱嘻嘻33 分钟前
JS手写代码篇---手写ajax
开发语言·javascript·ajax
江城开朗的豌豆36 分钟前
JavaScript篇:字母侦探:如何快速统计字符串里谁才是'主角'?
前端·javascript·面试
kite01217 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон7 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想9 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘9 小时前
快速部署和启动Vue3项目
java·javascript·vue