当前位置: 首页 > news >正文

属性描述符

为什么要学?

先看一段代码

let aGoods = {pic: '',title: '',desc: '',sellNumber: 1,favorRate: 2,price: 3
}class UIGoods {constructor(g) {this.data = g}
}let g = new UIGoods(aGoods)
console.log(g.data);

这段存在问题吗?

在功能这个层面, 是没问题的

在设计这个层面, 存在设计缺陷

// 按照我们的业务, data必须是一个商品对象
// 目前没有任何限制, 就可能埋下隐患, 带来更多的维护成本
g.data = 'abc'
console.log(g.data); 

作为码农, 我们专心把功能实现了就可以了

作为架构师, 我们要打造 "玩不坏" 的程序, 要从根本上杜绝问题的发生

属性描述符

属性描述符就是用来描述属性的特征

let obj = {a: 1,b: 2
}/*** 描述a的特征*/
// 值为1
console.log(obj.a);
// 可重写
obj.a = 10;
// ->可遍历
for (let key in obj) {console.log(key);
}let keys = Object.keys(obj)
console.log(keys);/*** 获取属性描述符* { value: 10, writable: true, enumerable: true, configurable: true }* value描述值* writable是否可重写* enumerable是否可遍历* configurable属性描述对象是否可修改*/
let desc = Object.getOwnPropertyDescriptor(obj, 'a');
console.log(desc);/***设置属性描述符*/
Object.defineProperty(obj, 'a', {value: 10,writable: false, // 不可重写enumerable: false, // 不可遍历configurable: false, // 不可修改描述符本身
})
obj.a = 'abc'
console.log(obj.a); // 10/*** 认识访问器* 读取器和设置器加起来就是访问器*/
Object.defineProperty(obj, 'a', {// 读取器getterget: function () {console.log("hello");return 123;},// 设置器setterset: function (val) {console.log(val);}
})
obj.a = 3 + 2 //为属性赋值就是调用set()方法, 相当于set(3+2), 这里打印5
console.log(obj.a) //读取属性值就是调用get()方法, 相当于get(), 这里打印hello后再打印123
// 更复杂的示例
obj.a = obj.a + 2 // set(get() + 2) => set(打印hello返回123 + 2) => set(125) => 打印125
console.log(obj.a); // 打印hello再打印123/*** 使用访问器设置属性值*/
// 一个错误的示范: Maximum call stack size exceeded(栈内存益出)
// 报错的原因: 死循环
Object.defineProperty(obj, 'a', {get: function () {return obj.a // 等价于get(),一直调用自己},set: function (val) {obj.a = val // 等价于set(val),一直调用自己}
})
obj.a = obj.a + 2
console.log(obj.a);// 正确的示范: 利用临时变量
let temp = null
Object.defineProperty(obj, 'a', {get: function () {return temp},set: function (val) {temp = val}
})
obj.a = obj.a + 2 // set( get() + 2) => set( null + 2) => set(2)
console.log(obj.a); // get() => 2 => 打印2/*** 使用设置器模拟属性只读*/
Object.defineProperty(obj, 'a', {get: function () {return 123},set: function (val) {throw new Error(`你正在给obj.a这个属性赋值,但它是只读的`)}
})
obj.a = 100 //报错: 你正在给obj.a这个属性赋值,但它是只读的

改造代码

通过属性描述符改造我们的代码, 让代码是坚固可靠

let aGoods = {pic: '',title: '',desc: '',sellNumber: 1,favorRate: 2,price: 3
}/*** 原始程序*/
// class UIGoods {
//   constructor(g) {
//     this.data = g
//   }
// }// let g = new UIGoods(aGoods)
// console.log(g.data);/*** 使用属性描述符改造程序* 目标: * 让data属性是只读的, 不会被篡改* 让choose属性的赋值更安全* 让totalPrice使用起来像属性* 保护原始数据不会被修改* 保护实例对象不被添加属性, 且自身属性可以不被影响* 保护对象原型不被添加属性或方法*/
class UIGoods {// es6提供了语法糖, 效果与下面的写法一致get isChoose() {return this.choose > 0}constructor(g) {g = { ...g } //克隆对象: 直接冻结原始对象, 会影响别人Object.freeze(g) // 冻结克隆对象, 保护对象的属性值不被更改Object.defineProperty(this, 'data', {get: function () {return g},set: function () {throw new Error('data属性是只读的')},configurable: false})let internalChooseValue = 0Object.defineProperty(this, 'choose', {configurable: false,get: function () {return internalChooseValue},set: function (val) {if (typeof val !== 'number') {throw new Error('choose属性是数字')}let temp = parseInt(val)if (temp !== val) {throw new Error('choose属性是整数')}if (val < 0) {throw new Error('choose属性必须 >= 0')}internalChooseValue = val}})Object.defineProperty(this, 'totalPrice', {get: function () {return this.choose * this.data.price}})// 不希望被人给自己加属性// 1.把自己冻结, 防止被添加属性, 弊端:其他不能被修改了// Object.freeze(this)// 2.对象密封: 不能添加属性, 但是对象已有的属性支持修改this.a = 1Object.seal(this)}
}Object.freeze(UIGoods.prototype) // 冻结原型, 防止随意添加东西let g = new UIGoods(aGoods)
/// g.data = 'abc' // 报错: data属性是只读的
g.choose = 2
console.log(g.totalPrice); // 6, 调用类似属性, 本质是函数g.data.peice = 100 // g.data只读, 但是原始数据还是可以篡改
console.log(g.data);  // { ..., peice: 100 }g.abd = 123 // 别人还能往对象上加属性
console.log(g); // { abd: 123  }g.a = 100 // 不能添加属性, 但是原有属性支持修改
console.log(g); // { a: 100  }UIGoods.prototype.haha = 'abc' // 还能往原型上加东西
console.log(g.haha);
http://www.dtcms.com/a/395857.html

相关文章:

  • JavaWeb之JSP 快递管理与过滤器详解
  • 《MedChat智能医疗问答系统》项目介绍
  • 使用FastAPI和Docker部署机器学习模型:从开发到生产的最佳实践
  • Per-Tensor 量化和Per-Channel 量化
  • 执行bat任务栏有图标显示,执行pycharm64.exe就没有是什么原因
  • 【Docker项目实战】使用Docker部署wealth-tracker个人资产分析工具
  • LeapMotion_Demo演示
  • 智慧图书管理|基于SprinBoot+vue的智慧图书管理系统(源码+数据库+文档)
  • 面试技巧第四篇:嵌入式通信机制考点:消息队列、信号量与互斥锁
  • 面试八股:C语言的预处理和类型定义
  • 强化学习1.3 深度学习交叉熵方法
  • 用PowerBI的思想解决QuickBI文本无法动态配色问题
  • 逆向解析 1688 商品详情接口:自主构建 Sign 签名算法实战
  • SpringCloud项目阶段六:feign服务降级处理以及基于DFA算法的自管理敏感词审核和tess4j图片文字识别集成
  • 跨行业安全合规文档协同平台:重塑制造企业的质量管理与合规运营新范式
  • 线性代数 · SVD | 奇异值分解命名来历与直观理解
  • Qt 控件与布局
  • TDengine 聚合函数 SPREAD 用户手册
  • 4090 云服务器租赁:高性能与灵活性的算力融合方案​
  • 阿里云服务器ECS上安装anaconda(jupyter)和OpenCV教程
  • CVE-2025–3246 本地提权
  • Chat API和Chat SDK
  • 爱奇艺技术实践:基于 StarRocks 释放天玑买量数据价值
  • 突破传统文本切分桎梏!基于语义理解的智能文档处理革命——AntSK-FileChunk深度技术解析
  • Git常用的使用方法
  • IDEA集成Claude Code (win系统)
  • MySQL执行计划:索引为何失效?如何避免?
  • 【附源码】基于SpringBoot的校园防汛物资管理平台的设计与实现
  • PyTorch 核心工具与模型搭建
  • ARM--时钟管理单元与定时器