JavaScript对象进阶:创建、属性管理与序列化详解
本文深入解析JavaScript对象的高级特性,涵盖对象直接量、构造函数及Object.create()三种创建方式。详细讲解属性查询、删除、检测与枚举机制,阐述getter/setter存取器属性及属性描述符的应用。最后介绍对象原型、类属性、可扩展性控制及JSON序列化方法,帮助开发者掌握对象核心操作。
JavaScript对象概述
深入理解JavaScript对象,需从对象的创建机制、属性操作技巧以及对象方法应用三个维度展开。主要内容涵盖以下模块:
对象的创建方式
1.创建对象
1.1 对象直接量
对象直接量是构建对象最为简便的方式,其结构由一系列键值对映射组成:
var point = {x: 0, y: 0 };
属性名并无严格限制,可以是JavaScript关键字或任意字符串。若属性名为关键字或特殊字符串,必须使用双引号包裹:
var empty = {};
va point = {x: 0, y: 0 };
var book = {
"main title": "Javascript",
"sub-title": "The definitive Guide",
"for": "all audience",
author: {
firstName: "Davide",
lastName: "Flanagan"
}
};
虽然对象直接量创建方式简单,但代码复用性较低。若需在其他地方使用该对象且属性值不同,是否需重新编写代码?
1.2 通过new创建对象
使用new关键字创建对象前,需先定义一个函数,new将该函数作为构造函数(constructor)。例如,通过new创建Person对象:
function Person(){
//构造函数
}
var person = new Person();
JavaScript语言核心中的原始类型均包含内置构造函数:
var a = new Array();
var d = new Date();
var r = new RegExp(“js”);
1.3 Object.create()
在理解Object.create方法前,需先了解原型概念。除null外,每个JavaScript对象都与另一个对象相关联,该对象即为原型。每个对象均从原型继承属性。
所有通过对象直接量创建的对象共享同一原型对象Object.prototype。使用关键字new和构造函数创建的对象,其原型为构造函数的prototype属性值。例如,通过new Array()创建的对象原型为Array.prototype,通过new Date()创建的对象原型为Date.prototype。关于原型的介绍暂止于此。
Object.create方法接受两个参数:第一个参数指定对象的原型,第二个参数可选,用于描述对象属性。使用十分简便,只需传入所需的原型对象即可:
var o1 = Object.create({x: 1, y: 2 }); //原型为Object.prototype
若需创建无原型的对象,可传入null作为参数。此类对象不继承任何属性,也不具备toString等方法:
var o2 = Object.create(null); //没有原型
若需创建普通空对象,直接传入Object.prototype:
var o3 = Object.create(Object.prototype);
对于自定义对象,创建方式与普通空对象相同,直接传入对象名.prototype:
function Person(){
}
var o4 = Object.create(Person.prototype);
属性管理详解
2.属性管理
2.1 属性查询和设置
对象属性可通过点(.)或方括号([])运算符获取。使用点运算符时,属性名必须是简单标识符,不能是保留字,例如o.for或o.class是非法的。
ar author = book.author; //正确
var name = author.surname; //正确
var title = book[“main title”]; //正确
var className = book.class; //错误
object[“property”]语法类似数组,但其元素通过字符串索引而非数字索引访问。这种数组被称为关联数组,亦称散列、映射或字典。JavaScript对象本质上都是关联数组。
既然对象是关联数组,JavaScript提供了for/in语句进行属性遍历。以下示例利用for/in计算portfolio的总值:
function getvalue(portfolio){
var total = 0.0;
for(stock in portolio){
var shares = portolio[stock];
var price = getquote(stock);
total += shares * price;
}
return total;
}
继承机制:JavaScript对象拥有自有属性(own property),同时也包含从原型对象继承的属性。首先查看一个实现继承功能的函数inherit:
function inherit(p){
if (p == null) throw TypeError(); //p是一个对象,大不能是null
if(Object.create){
return Object.create(p); //直接使用Object.create方法
}
var t = typeof p;
if(t !== "object" && t !== "function") throw TypeError();
function f() {};
f.prototype = p; //将其原型属性设置为p
return new f();
}
假设查询对象o的属性x,若o中不存在x,则继续在o的原型对象中查询x。若原型对象中仍无x,且该原型也有原型,则继续在该原型对象的原型上执行查询,直至找到x或查询到原型为null的对象为止。
var o = {}; //o从Object.prototype继承对象属性
o.x = 1; //给o定义x属性
var p = inherit(o); //p继承o和Object.prototype
p.y = 2; //p定义属性y
var q = inherit(p); //q继承p、o和Object.prototype
q.z = 3; //给q定义属性z
var s = q.toString(); //toString继承自Object.prototype
q.x + q.y // => 3:x和y分别继承自o和p
2.2 删除属性
delete运算符可用于删除对象属性:
delete book.author;
delete book[“main title”];
delete仅能删除自有属性,无法删除继承属性。要删除继承属性,必须从定义该属性的原型对象上删除它,且这会影响所有继承自该原型的对象。删除成功返回true。
ar o = {x: 1};
delete o.x; //删除x,返回true
delete o.x; //x已经不存在了,什么都没做,返回true。
delete o.toString; //什么都没做,返回true。
delete不能删除可配置型为false的属性。某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性:
delete Object.prototype //不能删除,属性是不可配置的
var x = 1;
delete this.x; //不能删除这个属性
function f() {}
delete this.f; //不能删除全局函数
2.3 检测属性
判断某属性是否存在于对象中,可通过in运算符、hasOwnProperty()和propertyIsEnumerable()方法进行检测。
in运算符:运算符左侧为属性名,右侧为对象。若对象的自有属性或继承属性包含该属性,则返回true:
var o = {x: 1};
"x" in o; //true:x是o的属性
"y" in o; //false:y不是o的属性
"toString" in o; //true:o继承toString属性
hasOwnProperty()方法:检测给定名称是否为对象的自有属性。对于继承属性,它将返回false:
var o = {x: 1};
o.hasOwnProperty("x"); //true:o有一个自由属性x
o.hasOwnProperty("y"); //false:o中不存在属性y
o.hasOenProperty("toString"); //false:toString是继承属性
propertyIsEnumerable()方法:是hasOwnProperty的增强版,仅当检测到自有属性且该属性为可枚举时返回true:
var o = inherit({y: 2});
o.x = 1;
o.propertyIsEnumerable("x"); //true: o有一个可枚举属的自有属性x
o.propertyIsEnumerable("y"); //false:y是继承来的
Object.prototype.propertyIsEnumerable("toString"); //false:不可枚举
2.4 枚举属性
通常使用for/in循环遍历对象属性,遍历范围包括自有属性和继承属性。对象继承的内置方法不可枚举,但代码中为对象添加的属性均为可枚举。例如:
var o = {x: 1, y: 2, z: 3}; //三个可枚举的自有属性
o.propertyIsEnumeable("toString"); //false,不可枚举
for (p in o) //遍历属性
console.log(p); //输出x、y和z,不会输出toString
有时仅需遍历自有属性,且排除函数属性:
for(p in o){
if(!o.hasOwnProperty(p)) continue;
if(typeof o[p] === "function") continue;
}
可通过枚举遍历功能实现可枚举属性的复制:
/*
* 把p中的可枚举属性复制到o中,并返回o
* 如果o和p含同名属性,则覆盖o中的属性
* 这个函数并不处理getter和setter以及复制属性
*/
function extend(o, p){
for(prop in p){ //遍历p中的所有属性
o[prop] = p[prop]; //将属性添加到o中
}
return o;
}
ES5定义了两个用于枚举属性名称的函数。第一个是Object.keys(),返回由对象中可枚举自有属性名称组成的数组。第二个枚举函数是Object.getOwnPropertyNames(),与Object.keys()类似,它返回对象的所有自有属性,而不仅仅是可枚举属性。
属性封装与特性
3.属性封装
3.1 属性getter和setter
对象属性由名字、值和一组特性(attribute)构成。在ES5中,属性值可用一个或两个方法替代,这两个方法即getter和setter。由getter和setter定义的属性称为“存取器属性”,不同于“数据属性”,数据属性仅包含简单值。
与数据属性不同,存取器属性不具备可写性(writeable attribute)。若属性同时具有getter和setter方法,则为读/写属性。若仅有getter方法,则为只读属性;若仅有setter方法,则为只写属性。读取只写属性总是返回undefined。
存取器属性定义语法较为简单,函数定义未使用function关键字,而是使用get或set:
var o = {
//普通的数据属性
data_prop: 1,
//存取器属性都是成对定义的函数
get accessor_prop(){/* 这里是函数体 */},
set accessor_prop(value){}
};
思考以下表示2D笛卡尔点坐标的对象。它有两个普通属性x和y,分别表示x坐标和y坐标,还有两个等价的存取器属性用于表示点的极坐标:
var p = {
//x和y是普通的可读写数据属性
x: 1.0,
y: 1.0,
//r是可读写的存取器属性,它有getter和setter
get r(){return Math.sqrt(this.x * this.x + this.y * this.y); },
set r(newValue){
var oldValue = Math.sqrt(this.x * this.x + this.y * this);
var ratio = newValue / oldValue;
this.x *= ratio;
this.y *= ratio;
},
//theta是只读存取器属性,只有getter方法
get theta() { return Math.atan2(this.y, this.x); }
};
与数据属性一样,存取器属性可被继承。因此,可将上述代码中的p对象作为另一个“点”的原型。可为子对象定义x和y属性,而r和theta属性则继承而来:
var q = inherit(p);
q.x = 1, q.y = 1;
console.log(q.r);
cosole.log(q.theta);
3.2 属性特性
可将存取器属性的getter和setter方法视为属性特性。依此逻辑,也可将属性值视为属性特性。因此,可认为一个属性包含一个名字和4个特性。
数据属性的4个特性分别是值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。
存取器属性不具有值(value)特性和可写性,因此包含:读取(get)、写入(set)、可枚举性、可配置性。
ES5定义了一个名为“属性描述符”的对象,代表这4个特性。数据属性的描述符对象属性包括value、writable、enumerable和configurable。存取器属性的描述符对象则用get属性和set属性替代value和writable。其中writable、enumerable、configurable均为布尔值,get属性和set属性为函数值。
通过调用Object.getOwnPropertyDescriptor()可获取某个对象特定属性的属性描述符:
//返回{value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnProeprtyDescriptor({x: 1},"x");
//查询上文中定义的random对象的octet属性
//返回{get: /*func */, set: undefined, enumerable: true, configurable: true}
Object.getOwnPropertyDesciptor(random, "octet");
//对于继承属性和不存在属性,返回undefined
Object.getOwnPropertyDesciptor({}, "x");
Object.getOwnPropertyDesciptor({}, "toString");
从函数名可知,Object.getOwnPropertyDesciptor()仅能获取自有属性的描述符。若要获取继承属性的特性,需遍历原型链(Object.getPrototypeOf())。
若要设置属性特性,或使新建属性具有某些特性,需调用Object.defineProperty(),包含三个参数:对象、属性名、属性描述符对象:
// 属性是存在的,但不可枚举
o.x; //=> 1
Object.keys(o) //=> []
//现在对属性x做修改,让它变成只读
Object.defineProperty(o, "x", {writable: true });
//视图更改这个属性的值
o.x = 2; //操作失败但不报错,而在严格模式中抛出类型错误异常
//属性依然是可配置的,因此可通过这种方式对它进行修改:
Object.defineProperty(o, "x", {value: 2 });
o.x //=> 2
//现在将x从数据属性修改为存取器属性
Object.defineProperty(o, "x", { get: function() {return 0;} });
o.x // => 0
若需同时修改或创建多个属性,则需使用Object.defineProperties()。第一个参数为要修改的对象,第二个参数为映射表。例如:
var p = Object.defineProperties({}, {
x: { value: 1, writable: true, enumerable: true, configurable: true},
y: { value: 2, writable: true, enumerable: true, configurable: true},
r: {
get: function(){ return Math.sqrt(this.x * this.x + this.y * this.y); },
enumerable: true,
configurable: true
}
});
getter和setter的老式API:在ES5采纳之前,大多数JavaScript实现已支持对象直接量语法中的get和set写法。这些实现提供了非标准的老式API用于查询和设置getter和setter。这些API由四个方法组成,所有对象均拥有这些方法。
__lookupGetter__()和__lookupSetter__()用于返回命名属性的getter和setter方法。
__defineGetter__()和__defineSetter__()用于定义getter和setter,第一个参数为属性名字,第二个参数为getter和setter方法。
var o = {};
o.__defineGetter__("x", function(){return 0;});
o.__defineSetter__("y", function(value){console.log("set value:" + value);});
对象核心属性解析
4.对象的三个属性
每个对象均关联有原型(prototype)、类(class)和可扩展性(extensible attribute)。接下来阐述这些属性的作用。
4.1 原型属性
对象的原型属性用于继承属性,我们常将“o的原型属性”直接称为“o的原型”。在之前的“创建对象”部分介绍了三种创建对象的方式。通过对象直接量创建的对象使用Object.prototype作为其原型。通过new创建的对象使用构造函数的prototype属性作为其原型。通过Object.create()创建的对象使用第一个参数作为其原型。
在ES5中,可通过Object.getPrototypeOf()查询对象原型。在ES3中,无等价函数,而是使用表达式o.constructor.prototype检查对象的原型。
要检测一个对象是否是另一个对象的原型(或处于原型链中),使用isPrototypeOf()方法。例如,可通过p.isPrototypeOf(o)来检测p是否是o的原型:
var p = {x: 1}; //定义一个原型对象
var o = Object.create(p); //使用这个原型创建一个对象
p.isPrototypeOf(o); //=> true,o继承自p
Object.prototype.isPrototypeOf(o) //=> true, p继承自Object.prototype
Mozilla实现的JavaScript对外暴露了一个专门命名为__proto__属性,用于直接查询/设置对象原型。但IE和Opera不支持__proto__属性,因此不建议直接使用__proto__属性。
4.2 类属性
对象的类属性是一个字符串,用于表示对象的类型信息。ES3和ES5均未提供设置此属性的方法,仅有一种间接方式查询它。默认的toString()方法返回格式为[object class]的字符串。
可通过调用toString()方法,然后提取已返回字符串的第八个到倒数第二个位置之间的字符。但有个麻烦是,许多对象继承的toString()方法已被重写。为调用正确的toString()版本,必须间接调用Function.call()方法。以下示例的classof函数可返回任意对象的类:
function classof(o){
if(o === null) return "Null";
if(o === undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8, -1);
}
4.3 可扩展性
对象的可扩展性用于表示是否可向对象添加新属性。所有内置对象和自定义对象均显式可扩展。在ES5中,可将对象转换为不可扩展状态。
Object.seal()方法除将对象设置为不可扩展外,还可将对象的所有自有属性都设置为不可配置。即不能给对象添加新属性,且已有属性也不能删除和配置。
Object.isSealed()方法用于检测对象是否封闭。
Object.freeze()方法对对象进行更严格的锁定,除具备Object.seal()方法的功能外,还可将自有的所有数据属性设置为只读(若对象的存取器属性有setter方法,存取器属性不受影响,仍可通过给属性赋值调用它们)。
Object.isFrozen()用于检测对象是否冻结。
对象序列化技术
5.序列化对象
对象序列化是指将对象状态转换为字符串,也可将字符串还原为对象。ES5提供了内置函数JSON.stringify()和JSON.parse()用于序列化和还原JavaScript对象。这些方法均使用JSON作为数据交换格式。例如:
o = {x: 1, y: {z: [false, null, ""]}}; //定义一个测试对象
s = JSON.stringify(o); //{"x":1,"y":{"z":[false,null,""]}}
p = JSON.parse(s); //p是o的深拷贝
JSON语法是JavaScript语法的子集,不能表示JavaScript中的所有值。支持对象、数组、字符串、无穷大数字、true、false和null,且它们可序列化和还原。NaN、Infinity和-Infinity序列化结果均为null。函数、RegExp、Error对象和undefined值不能序列化和还原。
此处附加说明对象的方法:
toString()方法:返回表示调用此方法的对象值的字符串。许多对象重写了toString()方法,如Array.toString()、Date.toString()以及Function.toString()。
toJSON()方法:Object.prototype实际上未定义toJSON()方法,但由于需执行序列化的对象而言,JSON.stringify()方法会调用toJSON()方法。若在带序列化的对象中存在此方法,则调用它。
valueOf()方法:valueOf()方法与toString()方法非常相似,但通常JavaScript需将对象转换为某种原始值而非字符串时才调用它,尤其是转换为数字时。某些内置类自定义了valueOf()方法,如Date.valueOf()。
以上关于JavaScript对象进阶的全面解析即为全部内容,希望能对大家有所帮助,也希望大家多多支持本站。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
