Skip to content

属性描述符

js
/* 
对象属性修饰符:
{
  value:10,             该属性的属性值
  writable: false,      该属性是否可写
  enumerable: true,     该属性是否可遍历和访问
  configurable: true,   是否可以再次修改该属性的修饰符信息
}
*/

let obj = {
  a: 10,
  b: "hello",
};

//可修改
obj.a = "aaa";
console.log(obj);

//可遍历
for (let k in obj) {
  console.log(k);
}

console.log(Object.keys(obj)); //返回属性名组成的数组

/* 获取对象某个属性的修饰信息 */
console.log(Object.getOwnPropertyDescriptor(obj, "a"));

/* 设置对象的属性修饰符 */
Object.defineProperty(obj, "c", {
  value: "我是c",
  writable: false,
  enumerable: true,
  configurable: false,
  //   getter和setter
});

//如果上面设置configurable: false,那么此处再修改属性修饰符的信息就会报错
Object.defineProperty(obj, "c", {
  writable: true,
});

obj.c = 20; //writable:false就无法修改
console.log(obj); //enumerable:false就无法访问和遍历

getter 和 setter

js
let obj = {};

//getter和setter
Object.defineProperty(obj, "a", {
  get() {
    console.log("访问了a属性");
    return 111;
  },

  set(val) {
    console.log("设置了a属性,值是:", val);

    //此处可以直接将a属性设置为只读的,如果使用者设置了,就直接报错
    throw new Error("兄弟,你正在设置a属性,这是不允许的,因为a属性是只读的");
  },
});

console.log(obj.a); //等价于console.log(get())
obj.a = 20; //等价于set(20)  20会作为参数传入到set(val)中

对购物车案例的优化

js
// 上节课的购物车数据
let Goods = {
  pic: ".",
  title: "..",
  desc: `...`,
  sellNumber: 1,
  favorRate: 2,
  price: 3,
};

/* 上节课的数据处理 */
class UIData {
  constructor() {
    this.data = Goods;
    this.chooseCount = 0;
  }
}
//这样写会出现隐患,就是如果用户直接修改了data和chooseCount,那么程序就变得不稳定了。
const uidata = new UIData();
uidata.data = "123"; //data就被修改为了'123'
uidata.chooseCount = "我就是改着玩";
console.log(uidata);

/* 为了避免上述的问题,我们需要加强对程序的健壮性 */
class UIData2 {
  constructor(g) {
    g = { ...g }; //防止用户修改元数据
    Object.freeze(g); //放置用户再通过data修改g中的属性

    //data属性
    //法一:缺点是,用户在修改的时候,虽然修改不成功,但是不会报错,会让人产生疑问的
    // Object.defineProperty(this, "data", {
    //   value: Goods,
    //   writable: false,
    //   enumerable: true,
    //   configurable: false,
    // });

    //法二:使用setter和getter
    Object.defineProperty(this, "data", {
      configurable: false,
      get() {
        return g;
      },
      set(val) {
        throw new Error(
          "你正在修改data属性为val,这是不允许的,因为data是只读的"
        );
      },
    });

    //定义chooseCount
    let interval = undefined;
    Object.defineProperty(this, "chooseCount", {
      configurable: false,
      get() {
        return interval;
      },
      set(val) {
        // 做设置的限制
        if (typeof val !== "number") {
          throw new Error("chooseCount属性只能设置为number类型");
        }
        if (val < 0) {
          throw new Error("chooseCount值不能<0");
        }

        let temp = parseInt(val);
        if (temp !== val) {
          throw new Error("chooseCount值必须是整数");
        }
        interval = val;
      },
    });

    //冻结this.防止用户通过实例添加属性
    // Object.freeze(this);

    //冻结不可取,y允许用户修改,freeze()会将整个this冻结,将this密封起来就好了
    this.y = 20;
    Object.seal(this);
  }
}
Object.freeze(UIData2.prototype);

const uidata2 = new UIData2(Goods);
// uidata2.data = "我这次该不了了";
// uidata2.chooseCount = 13;
// console.log(uidata2);

//上述还会产生漏洞,因为你完全想不到用户会怎么使用
//1.用户直接修改元数据Goods -- 解决办法,深拷贝复制一份元数据
Goods.price = "123";
console.log(uidata2.data); //price被修改为123

//2.但是用户直接访问data的属性修改,解决办法,使用frezee()冷冻对象
uidata2.data.price = "我又修改了,想不到把";
console.log(uidata2.data.price);

//3.但是用户依旧可以通过原型修改或者添加,解决办法:冷冻原型
UIData2.prototype.haha = "我又来了";
console.log(uidata2.haha);

//4. 用户依旧可以通过uidata来新增新的属性,解决办法就是将这个对象this给冻结
uidata2.x = "新增的";
console.log(uidata2);

//5.但是,万一类中有普通属性,this.y=10,这个可以修改呢?freeze(this)之后所有的属性都不可以修改了,这又是问题了
//解决办法: 将freeze()改为seal()密封

/* 
结论:
应用方面的是最简单的,而开发一个库或者框架层面却是难的,需要考虑到千变万化的条件
而开发一个东西程序,并不能用代码量来判断好坏,而是程序的稳定性,有的程序代码多,但是健壮。
有的程序代码多,是繁琐。
*/

MIT License