JavaScript 对象属性、保护机制与代理全解析
1. 对象属性特性
在处理对象属性时,我们通常知道它们有键(可以是字符串或符号)和值(可以是任何类型)。但实际上,对象属性还有一些特性,这些特性控制着属性在所属对象上下文中的行为。
1.1 属性特性概述
属性特性、属性描述符和属性配置这几个术语可以互换使用,它们都指的是同一件事。属性有三个主要特性:
-
可写性(Writable)
:控制属性的值是否可以更改。
-
可枚举性(Enumerable)
:控制在枚举对象属性时(使用
for...in
、
Object.keys
或扩展运算符),该属性是否会被包含在内。
-
可配置性(Configurable)
:控制属性是否可以从对象中删除,或者其特性是否可以修改。
我们可以通过一个简单的例子来查看属性的特性:
const obj = { foo: "bar" };
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(descriptor);
// 输出: { value: "bar", writable: true, enumerable: true, configurable: true }
1.2 控制属性特性
我们可以使用
Object.defineProperty
来控制属性的特性,它允许我们创建新属性或修改现有属性(只要该属性是可配置的)。
1.2.1 使属性只读
Object.defineProperty(obj, 'foo', { writable: false });
try {
obj.foo = 3;
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot assign to read only property 'foo' of [object Object]
}
需要注意的是,尝试设置只读属性只会在严格模式下导致错误。在非严格模式下,赋值不会成功,但不会有错误。
1.2.2 添加新属性
Object.defineProperty(obj, 'color', {
get: function() { return this.color; },
set: function(value) { this.color = value; },
});
1.2.3 创建数据属性
Object.defineProperty(obj, 'name', {
value: 'Cynthia',
});
Object.defineProperty(obj, 'greet', {
value: function() { return `Hello, my name is ${this.name}!`; }
});
1.2.4 使数组属性不可枚举
const arr = [3, 1.5, 9, 2, 5.2];
arr.sum = function() { return this.reduce((a, x) => a+x); }
arr.avg = function() { return this.sum()/this.length; }
Object.defineProperty(arr, 'sum', { enumerable: false });
Object.defineProperty(arr, 'avg', { enumerable: false });
我们也可以一步完成:
const arr = [3, 1.5, 9, 2, 5.2];
Object.defineProperty(arr, 'sum', {
value: function() { return this.reduce((a, x) => a+x); },
enumerable: false
});
Object.defineProperty(arr, 'avg', {
value: function() { return this.sum()/this.length; },
enumerable: false
});
还可以使用
Object.defineProperties
来批量定义属性:
const arr = [3, 1.5, 9, 2, 5.2];
Object.defineProperties(arr, {
sum: {
value: function() { return this.reduce((a, x) => a+x); },
enumerable: false
},
avg: {
value: function() { return this.sum()/this.length; },
enumerable: false
}
});
2. 保护对象:冻结、密封和防止扩展
JavaScript 的灵活性虽然强大,但也可能带来问题。为了防止意外修改(并使有意修改更困难),JavaScript 提供了三种机制:冻结、密封和防止扩展。
2.1 冻结对象
冻结对象可以防止对对象进行任何更改。一旦冻结对象,就不能:
- 设置对象属性的值。
- 调用修改对象属性值的方法。
- 调用对象上的 setter(修改对象属性值的)。
- 添加新属性。
- 添加新方法。
- 更改现有属性或方法的配置。
const appInfo = {
company: 'White Knight Software, Inc.',
version: '1.3.5',
buildId: '0a995448-ead4-4a8b-b050-9c9083279ea2',
copyright() {
return `© ${new Date().getFullYear()}, ${this.company}`;
},
};
Object.freeze(appInfo);
console.log(Object.isFrozen(appInfo));
try {
appInfo.newProp = 'test';
} catch (error) {
console.log(error);
// 输出: TypeError: Can't add property newProp, object is not extensible
}
try {
delete appInfo.company;
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot delete property 'company' of [object Object]
}
try {
appInfo.company = 'test';
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot assign to read-only property 'company' of [object Object]
}
try {
Object.defineProperty(appInfo, 'company', { enumerable: false });
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot redefine property: company
}
2.2 密封对象
密封对象可以防止添加新属性,或重新配置或删除现有属性。
class Logger {
constructor(name) {
this.name = name;
this.log = [];
}
add(entry) {
this.log.push({
log: entry,
timestamp: Date.now(),
});
}
}
const log = new Logger("Captain's Log");
Object.seal(log);
console.log(Object.isSealed(log));
log.name = "Captain's Boring Log";
log.add("Another boring day at sea....");
try {
log.newProp = 'test';
} catch (error) {
console.log(error);
// 输出: TypeError: Can't add property newProp, object is not extensible
}
try {
delete log.name;
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot delete property 'name' of [object Object]
}
try {
Object.defineProperty(log, 'log', { enumerable: false });
} catch (error) {
console.log(error);
// 输出: TypeError: Cannot redefine property: log
}
2.3 防止对象扩展
防止对象扩展只会阻止添加新属性,属性仍然可以被赋值、删除和重新配置。
const log2 = new Logger("First Mate's Log");
Object.preventExtensions(log2);
console.log(Object.isExtensible(log2));
log2.name = "First Mate's Boring Log";
log2.add("Another boring day at sea....");
try {
log2.newProp = 'test';
} catch (error) {
console.log(error);
// 输出: TypeError: Can't add property newProp, object is not extensible
}
log2.name = 'test';
delete log2.name;
Object.defineProperty(log2, 'log', { enumerable: false });
2.4 对象保护选项总结
| 操作 | 普通对象 | 冻结对象 | 密封对象 | 不可扩展对象 |
|---|---|---|---|---|
| 添加属性 | 允许 | 阻止 | 阻止 | 阻止 |
| 读取属性 | 允许 | 允许 | 允许 | 允许 |
| 设置属性值 | 允许 | 阻止 | 允许 | 允许 |
| 重新配置属性 | 允许 | 阻止 | 阻止 | 允许 |
| 删除属性 | 允许 | 阻止 | 阻止 | 允许 |
2.5 对象保护机制流程图
graph TD;
A[普通对象] -->|冻结| B(冻结对象);
A -->|密封| C(密封对象);
A -->|防止扩展| D(不可扩展对象);
B -->|不可进行多种操作| E(属性和方法不可更改);
C -->|不可添加、重新配置或删除属性| F(部分操作受限);
D -->|不可添加新属性| G(属性仍可赋值、删除和重新配置);
3. 代理(Proxies)
ES6 中引入了代理,它提供了额外的元编程功能(元编程是指程序能够修改自身的能力)。
3.1 代理基础
对象代理本质上能够拦截并(可选地)修改对对象的操作。我们通过一个简单的例子来演示如何修改属性访问。
const coefficients = {
a: 1,
b: 2,
c: 5,
};
function evaluate(x, c) {
return c.a + c.b * x + c.c * Math.pow(x, 2);
}
const betterCoefficients = new Proxy(coefficients, {
get(target, key) {
return target[key] || 0;
},
});
console.log(betterCoefficients.a);
console.log(betterCoefficients.b);
console.log(betterCoefficients.c);
console.log(betterCoefficients.d);
console.log(betterCoefficients.anything);
Proxy
构造函数的第一个参数是目标对象,即被代理的对象。第二个参数是处理程序,它指定要拦截的操作。在这个例子中,我们只拦截属性访问,由
get
函数表示。
3.2 进一步修改代理
我们可以进一步修改代理,使其只代理单个小写字母:
const betterCoefficients = new Proxy(coefficients, {
get(target, key) {
if(!/^[a-z]$/.test(key)) return target[key];
return target[key] || 0;
},
});
3.3 拦截属性设置
我们也可以使用
set
处理程序来拦截属性(或访问器)的设置。
const cook = {
name: "Walt",
redPhosphorus: 100,
water: 500,
};
const protectedCook = new Proxy(cook, {
set(target, key, value) {
if(key === 'redPhosphorus') {
if(target.allowDangerousOperations)
return target.redPhosphorus = value;
else
return console.log("Too dangerous!");
}
target[key] = value;
},
});
protectedCook.water = 550;
protectedCook.redPhosphorus = 150;
protectedCook.allowDangerousOperations = true;
protectedCook.redPhosphorus = 150;
4. 学习资源推荐
4.1 在线文档
- Mozilla Developer Network (MDN) :JavaScript、CSS 和 HTML 文档的首选资源。
- Mark Pilgrim 的 Dive Into HTML5 :适合新手学习 HTML5。
- WHATWG 的 HTML5 规范 :对于难以回答的 HTML 问题很有帮助。
- W3C 网站的 HTML 和 CSS 官方规范 :虽然文档枯燥难读,但对于解决难题是最后的手段。
- ES6 遵循的 ECMA - 262 ECMAScript 2015 语言规范 :可参考 @kangax 维护的指南来跟踪 ES6 特性在 Node 和各种浏览器中的可用性。
- jQuery 和 Bootstrap 的在线文档 :非常优秀。
- Node 文档 :高质量且全面,是 Node 模块的权威文档。
-
npm 文档
:特别是关于
package.json文件的页面很有用。
4.2 期刊
- JavaScript Weekly
- Node Weekly
- HTML5 Weekly
4.3 博客和教程
- Axel Rauschmayer 的博客 :关于 ES6 和相关技术的优秀文章。
- Nolan Lawson 的博客 :有很多关于实际 JavaScript 开发的详细文章。
- David Walsh 的博客 :关于 JavaScript 开发和相关技术的精彩文章。
- @kangax 的博客,Perfection Kills :充满了精彩的教程、练习和测验。
4.4 在线课程和教程
- Lynda.com JavaScript 教程
- Treehouse JavaScript 轨道
- Codecademy 的 JavaScript 课程
- Microsoft Virtual Academy 的 JavaScript 入门课程
4.5 Stack Overflow 使用建议
Stack Overflow 是程序员获取 JavaScript 问题答案的最佳资源。为了增加问题得到有用答案的机会,可以采取以下措施:
1.
成为知情的 SO 用户
:参加 SO 之旅,阅读 “How do I ask a good question?”,如果愿意,可以阅读所有帮助文档。
2.
不重复提问
:在提问前先进行充分的搜索,避免提出已有答案的问题。
3.
不要求别人代写代码
:在寻求帮助之前,先尝试自己解决问题,并在问题中描述自己的尝试和遇到的问题。
4.
一次只问一个问题
:避免提出包含多个问题的复杂问题。
5.
构建最小示例
:避免粘贴大量代码,构建最小示例有助于自己调试和提高批判性思维能力。
6.
学习 Markdown
:Stack Overflow 使用 Markdown 格式化问题和答案,学习 Markdown 可以提高问题的可读性。
7.
接受和点赞答案
:如果有人满意地回答了你的问题,应该点赞并接受答案,以提高回答者的声誉。
8.
自己回答问题
:如果自己解决了问题,为了社区的利益,可以自己回答问题。
如果享受帮助社区的过程,可以考虑自己回答问题。如果问题两天内没有得到有用的答案,可以使用自己的声誉发起悬赏。
5. 总结与应用场景分析
5.1 特性总结
-
对象属性特性
:对象属性除了键值对,还有可写性、可枚举性和可配置性三个特性,通过
Object.getOwnPropertyDescriptor查看,使用Object.defineProperty和Object.defineProperties进行控制。 - 对象保护机制 :JavaScript 提供了冻结、密封和防止扩展三种机制来保护对象,防止意外修改。
- 代理 :ES6 引入的代理提供了元编程功能,可拦截并修改对对象的操作。
5.2 应用场景分析
| 技术 | 应用场景 |
|---|---|
| 对象属性特性 | 当需要控制属性的读写权限、枚举性或配置时,如创建只读属性、不可枚举属性等。 |
| 对象保护机制 | 当需要确保对象的属性和方法不被意外或恶意修改时,如存储程序的不可变信息。 |
| 代理 | 当需要对对象的属性访问或设置进行额外处理时,如处理缺失属性、防止危险操作等。 |
5.3 综合应用示例
下面是一个综合应用对象属性特性、保护机制和代理的示例:
// 创建一个对象
const user = {
name: 'John',
age: 30,
email: 'john@example.com'
};
// 使用 Object.defineProperty 设置属性特性
Object.defineProperty(user, 'age', {
writable: false,
enumerable: false
});
// 冻结对象
Object.freeze(user);
// 创建代理
const userProxy = new Proxy(user, {
get(target, key) {
if (key === 'email') {
// 对邮箱进行脱敏处理
return target[key].replace(/(\w{1})\w+(\w{1}@\w+\.\w+)/, '$1***$2');
}
return target[key];
},
set(target, key, value) {
console.log('对象已冻结,不能设置属性');
return false;
}
});
// 测试
console.log(userProxy.name);
console.log(userProxy.age);
console.log(userProxy.email);
userProxy.age = 31;
5.4 操作流程总结
graph LR;
A[创建对象] --> B[设置属性特性];
B --> C[保护对象];
C --> D[创建代理];
D --> E[使用对象和代理];
6. 未来发展趋势与展望
6.1 技术发展趋势
- 代理的广泛应用 :随着 ES6 的普及,代理的应用场景将越来越广泛,如实现数据绑定、拦截器等。
- 对象保护机制的强化 :未来可能会有更多的对象保护机制或更精细的控制方式,以满足不同的安全需求。
- 与其他技术的融合 :对象属性特性、保护机制和代理可能会与其他 JavaScript 技术(如异步编程、模块化等)更紧密地结合,提供更强大的功能。
6.2 学习建议
- 持续学习 :JavaScript 是一个不断发展的语言,需要持续学习新的特性和技术。
- 实践项目 :通过实际项目来应用所学的知识,加深理解和掌握。
- 参与社区 :参与 JavaScript 社区,与其他开发者交流经验和学习心得。
6.3 总结
通过对 JavaScript 对象属性特性、保护机制和代理的学习,我们可以更好地控制对象的行为,提高代码的安全性和可维护性。在实际开发中,我们应该根据具体的需求选择合适的技术,灵活运用这些特性和机制。同时,我们也要关注技术的发展趋势,不断学习和进步,以适应不断变化的开发需求。
希望本文对你理解 JavaScript 的这些重要特性有所帮助,如果你有任何问题或建议,欢迎在评论区留言。
超级会员免费看

被折叠的 条评论
为什么被折叠?



