180325 对象

本文深入探讨JavaScript中的对象,包括对象作为属性集合的性质、原型式继承、对象的可变性以及属性的特性。创建对象可以通过对象直接量或new运算符,而属性访问涉及.和[]运算符。对象的继承机制使得属性查询会查找原型链。文章还讨论了如何删除和检测属性,以及对象的序列化和方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

  • 对象可以看做是属性的无序集合,每个属性都是一个名值对。 属性名是字符串,可以把对象看成是从字符 串到值的映射。
    除了可以保持自己的属性,JS对象还可以从一个称为原型的对象继承属性。这种‘’原型式继承‘’是JS的核心 特征。

  • 对象是可变的,我们通过引用而非值来操作对象。如果变量x指向一个对象的引用,那么执行代码var y = x
    ;变量y也是指向同一个对象的引用,而非这个对象的副本。通过变量y来修改这个对象亦会对变量x造成影 响。

属性特性:

  • 可写:是否可以设置属性值

  • 可枚举:是否可以通过for/in循环来返回该属性

  • 可配置:是否可以删除或修改该属性

对象特性:

  • 原型:指向另一个对象,本对象的属性继承自它的原型对象。

  • 类:是一个标识对象类型的字符串。

  • 扩展标记:指明是否可以向该对象添加新属性。

内置对象:是由ES规范定义的对象或类。例如数组、函数、日期和正则表达式。

宿主对象:是有JS解释器所嵌入的宿主环境(比如浏览器)定义的。客户端JS中表示网页结构的HTMLElement 对象均是宿主对象。

自定义对象:由运行中的JS代码创建的对象。

自有属性:直接在对象中定义的属性。

继承属性:在对象的原型对象中定义的属性。


创建对象

  • 对象直接量

属性名可以是JS标识符也可以是字符串直接量。
属性的值可以是任意JS表达式或值。

var empty = {};
var point = {x: 0,y: 0};
var point1 = {x: point.x,y: point.y + 1};
var book = {
    "main title": "JavaScript",//属性名中有空格必须用字符串表示     
    'sub-title': "The Definitive Guide",//属性名中有连字符必须用引号     
     "for": "all audience",     
     author:{         
         firstname:"David",
         surname:"Flangan"         
     }  
};
  • 通过new创建对象

new运算符创建并初始化一个新对象。关键字new后跟随一个函数调用。这里的函数称作构造函数。构造函数用 以初始化一个新创建的对象。JS语言核心中的原始类型都包含内置构造函数。

var o = new Object();
var a = new Array();
var d = new Date();
var r = new RegExp('js');
  • 原型

每一个对象(null除外)都和另一个对象相关联。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JS代码Object.prototype获得对原型对象的 引用。
通过关键字new和构造函数调用创建的对象的原型,就是对应构造函数的prototype属性的值(某一个对象)。
例如,通过new Array() 创建的对象的原型就是Array.prototype。
Object.prototype没有原型。

  • Object.create(对象原型,)

这是一个静态函数,不提供给某个对象调用。

var o = Object.create({x:1,y:2}); // o继承了属性x和y

可以通过传入参数null创建一个没有原型的新对象,甚至没有基础方法。

var o = Object.create(null);//不继承任何属性和方法

想创建一个普通对象(通过{}或new Object()),需要传入Object.prototype:

var o = Object.create(Object.prototype);

属性查询与设置

var name = author.surname 
var title = book["main title"]

(.)右侧必须是一个标识符。
([])右侧必须是一个计算结果为字符串(字符串本身或可以转换成字符串的值)的表达式。

book.edition = 6; 
book["main title"] = "ECMAScript";
var person = {
    name : 'nike',
    age : 18
}
var property = 'age';
person['property'];
person[property];
// 没有任何意义,单纯的字符串拼接,像一个拼图,property就是 'age'
// 而两者的区别是,第一个访问失败,因为对象里没有'property'属性
// 而第二句,就是person['age']

作为关联数组的对象

JS对象都是关联数组:数组元素通过字符串索引而不是数字索引。

当通过(.)访问对象的属性时,属性名用一个标识符来表示。标识符必须直接出现在JS程序中,它们不是数据类型,因此程序无法修改它们

当通过([])来访问对象的属性时,属性名通过字符串来表示。字符串是JS的数据类型,在程序运行时可以修改和创建它们。

var addr = " "; 
for(i = 0, i < 4, i++){     
    addr += customer["address" + i] + '\n'; 
}

这段代码读取对象的address0~3的属性,并将它们连接起来。

当不知道属性名称时,可以使用([])运算符,因为它使用字符串值(字符串值是动态的,可以在运行时更改) 而不是标识符(标识符是静态的,必须写死在程序中)作为索引对属性进行访问。

function getvalue(portfolio){
     var total = 0.0;
     for(stock in portfolio){
         var shares = portfolio[stock];         
         var price = getquote(stock);         
         total += shares * price;     
     }     
     return total;       
}

继承

假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没 有x,但这个原型对象也有原型,直到找到属性或查找到一个原型是null的对象为止。

现在假设给对象o的属性x赋值,如果o中已经有属性x,那么这个赋值操作只改变这个已有属性x的值。如果o中 不存在属性x,那么赋值操作给o添加一个新的属性x。如果之前o继承自属性x,那么这个继承的属性就被新创建 的同名属性覆盖了。

// inherit()返回一个继承自原型对象p的属性的新对象
// 这里使用ES5的Object.create()函数
// 如果不存在的话,则退化使用其他方法
function inherit(p) {
    if (p == null) throw TypeError(); // p是一个对象,不能是null
    if (Object.create) { // 如果Object.create()存在的话
        return Object.create(p); // 直接使用它
        var t = typeof p;
        if (t !== "object" && t !== "function") throw TypeError();

        function f() {}; // 类似构造函数?
        f.prototype = p;
        return new f(); // 使用f()创建p的继承对象
    }
}

var o = {} // o从Object.prototype 继承对象的方法
o.x = 1;
var p = inherit(o); // p继承o和Object.prototype
p.y = 2;
var q = inherit(p); // q继承p、o和Object.prototype
q.z = 3;
var s = q.toString(); // toString继承自Object.prototype
console.log(q.x + q.y); // 3

属性赋值操作会首先检查原型链,以此判定是否允许赋值操作。例如,如果o继承自一个只读属性x,那么赋值 操作是不允许的。如果允许属性赋值操作,它也总是在原始对象上创建属性或对已有的属性赋值,而不会去修 改原型链。

在JS中,只有查询属性时才会体会到继承的存在,而设置属性则和继承无关,这是JS的一个重要特性,可以有 选择性的覆盖继承的属性。

var unitcircle = {r: 1}; // 一个用来继承的对象
var c = inherit(unitcircle); // c继承属性r
c.x = 1;
c.y = 1; // c定义两个属性
c.r = 2; // c覆盖继承来的属性
unitcircle.r; // 1,原型对象没有修改 

属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性。但有一个例外。如果o继承自属性x,而 这个属性是一个具有setter方法的accessor属性。那么这时将调用setter方法而不是给o创建一个属性x。需要注 意的是,setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的。因此setter方法定义任何属性, 这个操作只是针对o本身,并不会修改原型链。


属性访问错误

  • 查询一个不存在的属性不会报错。返回undefined。

  • 如果对象不存在,查询这个不存在的对象的属性就会报错。

  • null和undefined都没有属性,因此查询这些值的属性会报错。

未确定变量是对象的情况下,避免出错的方法:

var len = book && book.subtitle && book.subtitle.length

删除属性

  • delete运算符的操作数应该是一个属性访问表达式

  • delete只是断开属性和宿主之间的联系,而不会去操作属性中的属性

  • delete运算符只能删除自有属性,不能删除继承属性(要删除这个继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个对原型的对象)。

  • 当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true。

o = {x:1};
delete o.x;  //删除x,返回true  
delete o.x;  //什么都没做,x已经被删除,返回true  
delete o.toString;  // 什么也没做,无法删除继承属性,返回true 
delete 1; // 无意义,返回true
  • delete不能删除不可配置的属性。例如:通过变量声明和函数声明创建的全局对象的属性。
  • 可以删除不可扩展对象的可配置属性。
  • 在严格模式下,删除一个不可配置属性会报一个错误异常。在非严格模式下,会返回false
delete Object.prototype; // 不能删除,属性是不可配置的 
var x = 1;     // 声明一个全局变量 
delete this.x;   // 不能删除这个属性 
function f(){}   // 声明一个全局函数 
delete this.f;   // 也不能删除全局函数
  • 当在非严格模式下中删除全局对象的可配置属性时,可以省略对全局对象的引用,直接在delete操作符后跟 随要删除的属性名即可:
this.x = 1;  // 创建一个可配置的全局熟属性(没用var) 
delete x; // 将它删除
  • 在严格模式中,delete后跟随一个非法的操作数,则会报一个语法错误,因此必须显示指定对象及其属性:
delete x;   //在严格模式下报语法错误 
delete this.x; //正常工作

检测属性

检测集合中成员的所属关系,判断是否存在于某一个对象中。

  • in运算符

属性名(字符串) in 对象

var o = {x:1} 
"x" in o;   // true  
"y" in o;   // false 
"toString" in o; // true

in运算符可以区分不存在的属性和存在但值为undefined的属性。

var o = {x:undefined} 
o.x !== undefined    // false:属性存在,但值为undefined
o.y !== undefined    // false: 属性不存在 
"x" in o     // true 
"y" in o    // false 
delete o.x;     
"x" in o     //false
  • hasOwnPreperty()

检测给定的名字是否是对象的自有属性。对于继承属性它将返回false。

var o = {x:1} o.hasOwnProperty("x");   // true 
o.hasOwnProperty("y");   // false 
o.hasOwnProperty("toString");  //false
  • propertyIsEnumberable()

检测到是自有属性且这个属性可枚举才返回true。某些内置属性是不可枚举的。

  • !==

判断一个属性是否是undefined。并且可以区分null和undefined。

var o = {x:1} 
o.x !== undefined;  // true 
o.y !== undefined;  // false 
o.toString !== undefined;  // true
  • instanceof
    A instanceof B
    A的原型链上有B的原型

枚举属性

for/in 循环可以在循环体中遍历所有可枚举的属性(包括自有属性和继承属性),把属性名称赋值给循环变量。 对象继承的内置方法是不可枚举的,但在代码中给对象添加的属性都是可以枚举的。

var o = {x: 1, y: 2, z: 3};   
o.propertyIsEnumerable('toString');  //false 不可枚举 
for (p in o)       
    console.log(p,o[p],o.hasOwnProperty(p));  // 不会输出toString

ES5之前,一些新添加的方法不能定义为不可枚举,因此它们可以在for/in循环中枚举出来。为了避免这种情 况。需要过滤结果:

for (p in o){     
    if(!o.hasOwnProperty(p)) continue;   // 跳过继承的属性 
} 
for (p in o){     
    if(typeof o[p] === "function")continue;// 跳过方法 
}

属性getter和setter


属性的特性


对象的三个属性

  • 原型属性

    对象的原型属性是用来继承属性的,经常把“o的原型属性”直接叫做“o的原型”。
    查询对象的原型方法:

    • Object.getPrototypeOf(obj)

    • obj.constructor.prototype

      检测一个对象是否是另一个对象的原型(或处于原型链中):

    • p.isPrototypeOf(o)

    • [_ proto_] 用以直接查询设置对象的原型。

  • 类属性

    对象的类属性是一个字符串,用以表示对象的类型信息。
    只有一种间接的方式可以查询它:
    默认的toString方法(继承自Object.prototype)返回如下格式的字符串:
    [ object class ]
    很多对象继承的toString方法重写了,为了能调用正确的toString()版本,必须间接的调用Function.call()方法。

//返回传递给它的任意对象的类: 
function classof(o){     
    if(o === null) return "Null";     
    if(o === undefined) return "Undefined";     
    return Object.prototype.toString.call(o).slice(8,-1); 
}
  • 可扩展性

    对象的可扩展性用以表示是否可以给对象添加新属性。


序列化对象

对象序列化是指将对象的状态转换为字符串,也可将字符串还原为对象。

ES5提供了 JSON.stringify() 和 JSON.parse() 用来序列化和还原JS对象。

JSON :”JavaScript Object Notation” JS对象表示法。

o = {x:1,y:{z:[false,null,""]}};   
s = JSON.stringify(o);   
// s = '{"x":1,"y":{"z":[false,null,""]}}' 
p = JSON.parse(s);  // p是o的深拷贝

JSON语法支持对象,数组,字符串,无穷大数字,true,false,null。

NaN,Infinity,序列化结果是 null。


对象方法

  • toString()
    该方法没有参数,它返回一个表示调用这个方法的对象值的字符串。 ’ [object Object] ‘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值