详解JavaScript中的属性和特性
本文深入解析JavaScript中对象、属性与特性的区别。详细讲解数据属性的四个特性(Configurable、Enumerable、Writable、Value)及访问器属性的Get/Set机制,并通过Object.defineProperty和Object.defineProperties方法演示如何修改和定义属性特性,帮助开发者深入理解对象模型。
JavaScript对象本质与关系解析
在JavaScript中,属性和特性是两个截然不同的概念。本文将基于个人学习经验,深入剖析这两者的区别与联系。
文章主要涵盖以下内容:
- 理解JavaScript中对象的本质,以及对象与类、对象与引用类型之间的关系。
- 探讨对象属性的分类方式。
- 深入理解属性中特性的具体含义。
第一部分:解析JavaScript对象的本质、对象与类的关系,以及对象与引用类型的关系。
对象的本质:根据ECMA-262标准,对象被定义为无序属性的集合,这些属性可以包含基本值、对象或函数。换句话说,对象是一组没有特定顺序的值,每个属性或方法都有一个名称,该名称映射到一个值。因此,对象的本质是一个散列表,其中包含一组名值对,值可以是数据或函数。
对象和类的关系:在JavaScript中,对象与类之间没有直接联系。这是因为ECMAScript标准中并未定义“类”的概念,其对象模型与其他基于类的编程语言中的对象存在显著差异。
对象和引用类型的关系:对象与引用类型并不等价,因为每个对象都是基于某个引用类型创建的。
对象属性分类:数据与访问器
第二部分:探讨对象属性的分类方法。
通过构造函数或对象字面量创建的对象通常包含属性和方法。只要涉及属性和方法,它们必然属于某个对象;反之,只要涉及对象(自定义对象除外),它必然具备属性和方法。其中,属性可进一步分为数据属性和访问器属性,两者的区别如下:
数据属性主要用于存储具体的数据数值,而访问器属性则不包含实际的数据值。
访问器属性通常用于执行get/set操作。
属性特性详解与修改方法
第三部分:深入理解属性中的特性(attribute)。
ECMAScript标准为了描述对象属性(property)的各种特征,引入了“特性”(attribute)这一概念。这意味着特性与属性不同,特性是用来描述属性的。接下来,我们将分别进行讲解:
- 数据属性及其特性
- 访问器属性及其特性
- 如何利用Object.defineProperties()方法定义多个特性
- 如何利用Object.getOwnPropertyDescripter()方法读取属性的描述符以获取属性特性
数据属性及其四个特性
1. 数据属性及其特性
前文提到,数据属性用于存储数据数值,因此它拥有一个存储数据值的位置,可以在此位置读取和写入值。数据属性具有4个描述其行为特性的特征。由于ECMAScript规定JavaScript中不能直接访问属性的特性(注意:并非完全无法访问,而是不能直接通过语法访问),因此这些特性被放置在两组方括号中,如下所示:
- [[Configurable]]:默认值为true。a、表示能否通过delete操作删除属性从而重新定义该属性;b、能否修改属性的特性;c、能否把属性由数据属性修改为访问器属性。
- [[Enumerable]]:默认值为true,表示能否通过for-in循环返回该属性(因此,如果该值为false,for-in循环将无法枚举该属性)。
- [[Writable]]:默认值为true,表示能否修改属性的值。这是与[[Configurable]]的不同之处。
- [[Value]]:默认值为undefined,该位置存储属性的实际值,可以在这里读取属性值,也可以写入新值。
- 注意:上述默认值仅适用于通过构造函数或对象字面量直接创建的对象所拥有的属性,而不包括下文介绍的Object.defineProperty()方法创建的属性。
如果这些特性的默认值不符合需求,该如何处理?答案是修改它们。我们可以通过Object.defineProperty()方法来修改属性的默认特性。英文defineProperty意为“定义属性”。该方法接收三个参数:属性所在的对象、属性的名字以及一个描述符对象。其中,第三个参数描述符对象是通过对象字面量创建的,其内部的属性和属性值保存了需要修改的特性和对应的特性值。
下面通过几个示例来深入理解。
示例a:
var person={};
Object.defineProperty(person,"name",{
writable:false,
value:"zhuzhenwei"
});
console.log(person.name);//zhuzhenwei
person.name="heting";
console.log(person.name);//zhuzhenwei
这里我使用对象字面量创建了一个对象,但未同时创建方法和属性,而是利用Object.defineProperty()方法来创建属性并修改默认值。这里将writable设置为false,因此后续尝试修改person.name时操作无效。
示例b:
var person={};
Object.defineProperty(person,"name",{
value:"zhuzhenwei"
});
console.log(person.name);//zhuzhenwei
person.name="heting";
console.log(person.name);//zhuzhenwei
请注意此例,其中去掉了writable:false,为何仍无法修改?这是因为前文介绍特性时,前三个特性默认为true,是在创建对象并直接定义属性的情况下得出的结论。对于通过调用Object.defineProperty()方法创建的属性,其前三个特性的默认值均为false,这一点需要特别注意。
示例c:
var person={};
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
configurable:false
});
console.log(person.name);//zhuzhenwei
delete person.name;
console.log(person.name);//zhuzhenwei
这里我们将新建属性name的特性设置为configurable:false;因此,后续删除属性的操作无效。根据示例b可知,configurable默认即为false,即使去掉该设置也无法修改。
示例d:
var person={};
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
configurable:true
});
console.log(person.name);//zhuzhenwei
delete person.name;
console.log(person.name);//undefined
在这里,我将默认的configurable值由false修改为true,使其变为可配置状态,因此最后成功删除了该属性。
示例e:
var person={};
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
configurable:false
});
console.log(person.name);//zhuzhenwei
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
configurable:true
});
console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)
如果之前已将其设置为false,那么后续再尝试改为true也是徒劳的,即:一旦将属性设置为不可配置,就不能再将其变回可配置状态。
示例f:
console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)
var person={};
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
});
console.log(person.name);//zhuzhenwei
Object.defineProperty(person,"name",{
value:"zhuzhenwei",
configurable:true
});
console.log(person.name);//Uncaught TypeError: Cannot redefine property: name(…)
此处说明,即使前一步忽略默认的configurable:false,后续结果仍为不可配置。因此可以得出结论:为了保持可配置,必须在第一次调用Object.defineProperty()函数时就将默认值修改为true。
访问器属性及其Get/Set机制
2. 访问器属性及其特性
前文提到,访问器属性不包含数据值,它们包含一对getter函数和setter函数(这两个函数并非必须)。在读取访问器属性时,会调用getter函数,该函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,该函数负责决定如何处理数据。同样,由于无法通过JavaScript直接访问访问器属性的特性,下列列出的特性将由[[]]括起以作区分。
- [[Configurable]]:默认值为true。a、表示能否通过delete删除属性从而重新定义属性;b、能否修改属性的特性;c、能否把属性由访问器属性修改为数据属性。
- [[Enumerable]]:默认值为true,表示能否通过for-in循环返回该属性(因此,如果为false,for-in循环无法枚举该属性)。
- [[Get]]:在读取属性时调用的函数。默认值为undefined。关键点:特性可以是一个函数。
- [[Set]]:在写入属性时调用的函数。默认值为undefined。关键点:特性可以是一个函数。由于get和set函数也属于属性的特性,它们有可能(说有可能,因为这两个函数并非必须)出现在Object.defineproperty的第三个参数描述符对象的属性中。
注意:1. 相对于数据属性,我们发现访问器属性中没有writable特性和value特性。这是因为访问器属性不包含数据值,因此当然不可直接修改属性的值(用不到writable特性),也不涉及value。
2. 访问器属性不能直接定义,必须使用Object.defineProperty()来定义。(通过这一规定,我们可以准确判断出访问器属性和数据属性。)
通过以下示例深入理解:
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){<br> return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
});
book.year=2005;
console.log(book.edition);//2
几个需要深入理解的要点:
- 访问器属性不能直接定义,必须使用Object.defineProperty()来定义,且该属性具有set和get特性,由此可判断,_year和edition是数据属性,而year是访问器属性。
- 我们看到_year这个数据属性前带有_(下划线),这是一种常用记号,表示该属性只能通过对象方法访问。从上述示例中可以看到,get相当于描述符对象的一个方法,而_year正是在该对象方法中访问的属性。而edition既可以通过对象方法访问,也可以由对象直接访问。
- book.year表示正在读取访问器属性,此时会调用get函数,并返回2004这个有效值。
- book.year=2005表示写入访问器属性,此时会调用set函数并传入新值,即将2005传给newValue,该函数决定如何处理数据。
- 这是使用访问器属性的常见方法,即设置一个属性的值会导致其他属性发生变化。
批量定义与读取属性特性
3. 如何利用Object.defineProperties()方法定义多个特性
显然,一个对象不可能只具有一个属性,因此定义多个属性的可能性很大。JavaScript提供了Object.defineProperties()方法来解决这一问题。该方法接收两个参数:第一个是要定义属性所在的对象,第二个是通过对象字面量创建的对象,对象的属性名即为要定义的特性的名称,对象的属性值又是一个对象,这个对象里的属性名和属性值分别是特性名和特性值(此处理解起来可能较为困难,参考示例即可)。
var book={};
Object.defineProperties(book,{
_year:{
writable:true,
value:2004
},
edition:{
writable:true,
value:1
},
year:{
get:function(){
return this._year;
},
set:function(){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
}
});
4. 如何利用Object.getOwnPropertyDescripter()方法读取属性的描述符以读取属性的特性
我们可以使用Object.getOwnPropertyDescripter()方法来取得给定属性的描述符。getOwnPropertyDescripter意为“取得自身属性描述符”。该方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回一个对象。
对于访问器属性而言,该返回对象的属性包括configurable、enumerable、get和set;
对于数据属性而言,该返回对象的属性包括configurable、enumerable、writable和value。
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set:function(){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
}
});
var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
console.log(descriptor.value);//2004
console.log(descriptor.configurable);//false 因为通过Object.defineProperties()方法创建的属性的特性configurable enumerable都是false
console.log(typeof descriptor.get);//undefined 注意:这是数据属性,是不具有get特性的
var descriptor=Object.getOwnPropertyDescriptor(book,"year");
console.log(descriptor.value);//undefined
console.log(descriptor.enumerable);//false
console.log(typeof descriptor.get);//function get虽然是属性的一个特性,但是它也是函数。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,同时也希望多多支持本站!
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
