JavaScript对象

对象是一种复合值:它将很多值(原始值或其他对象)聚合在一起,可通过名字访问这些值。
对象也可看做是属性的无序集合,每个属性都是一个名/值对。

除了包含属性之外,每个对象还拥有3个特性(object attribute):

  • 对象的原型(prototype):指向另外一个原型对象,本对象的属性继承自它的原型对象。
  • 对象的类(class):一个标识对象类型的字符串。
  • 对象的扩展标记(extensible flag):指明是否可以向该对象添加新属性。

JavaScript中包含了3类对象和2类属性:

  • 内置对象(native object):由ECMAScript规范定义的对象或类。如,Array, function, Date, RegExp等。
  • 宿主对象(host object): 由JavaScript解释器所嵌入的宿主环境(如Wbd浏览器)定义的。如,表示网页结构的HTMLElement对象。
  • 自定义对象(user-defined object):由运行中的JavaScript代码创建的对象。
  • 继承属性(inherited property):在对象的原型中定义的属性。
  • 自有属性(own property):直接在对象中定义的属性。

对象的创建

直接量创建对象

对象直接量是同若干名/值对组成的映射表,名/值对中间用冒号分隔,名/值对之间用逗号分隔,整个映射表用花括号括起来。

var empty = {};                             // 没有任何属性的对象
var point  = { x:0, y:0 };                  // 包含2个属性的对象 
var point2 = { x:point.x, y:point.y+1 }     // 使用对象的值来赋值属性的值

var book   = {
    "main title":"JavaScript",              // 属性名字里有空格,必须用引号
    "sub-title":"The Definitive Guide",     // 属性名字里有连字符,必须用引号
    "for":"all audiences",                  // "for"是保留字,必须使用引号
    
    author:{                                // 对象属性   
        firstname:"David",
        surname:"Flanagan"
    }
}

对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象

通过new创建对象

new运算符创建并初始化一个新对象,关键字new后面跟随一个构造函数(constructor)调用。

var o = new Object();       // 创建一个空对象,和{}一样(只包含Object.prototype)
var a = new Array();        // 创建一个空数组,和[]一样
var d = new Date();         // 创建一个表示当前时间的Date对象
var r = new RegExp("js");   // 创建一个可以模式匹配的RegExp对象

通过Object.creat()创建对象

Object.creat()是一个静态函数,其中第一个参数是待创建对象的原型,第二个参数可选,用以对对象的属性进行进一步描述。

var o1 = Object.create(Object.property);    // 创建一个空对象,同{}、new Object()一样

var o2 = Object.create({x:1, y:2});         // 创建一个新对象,并继承了属性x和y

var o3 = Object.creat(null);                // 创建一个对象,但不继承任何属性及方法

可以通过任意原型创建新对象,如:

// inherit()返回一个继承自原型对象p属性的新对象
function inherit(p) {

    // p是一个对象,但不能是null
    if(p == null) throw TypeError();    
    
    // 如果Object.create()存在,则直接使用它来创建对象
    if(Object.create)                   
        return Object.create(p)
    
    var t = typeof p;
    if(t !== "object" && t !== "function") throw TypeError();
    
    // 通过prototype属性创建对象
    function f() {}
    f.prototype = p;
    return new f();
}

inherit()并不能代替Object.create(),它不能传入null原型来创建对象,也不能接收第二个参数。

属性相关操作

属性继承

通过继承创建的新对象,对新对象的操作并不会影响到原型对象。

var a = { r:1 };
var b = inherit(a);
b.x = 1; b.y = 1;   // 新对象增加2个属性
b.r = 2;            // 修改新对象的继承属性
a.r;                // => 1,原型对象的属性不会受影响

假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询,直接找到x或者找到一个原型是null的对象为止。<br/

属性访问错误

  • 查询一个不存在的属性并不会报错。
book.subtitle;          // => undefined:属性不存在,但不会报错
  • 但是,如果对象不存在,那么试图查询这个不存在对象的属性就会报错。
book.subtitle.length;   // undefined没有length属性,抛出一个类型错误异常
  • 有一此属性是只读的,不能重新赋值,但是设置属性的失败操作不会报错。
Object.prototype = 0;   // => Object原型是只读的,赋值失败,但不会报错

下面提供两种避免出错的方法:

// 一种冗余但很易懂的方法
var len = undefined;
if(book) {
    if(book.subtitle)
        len = book.subtitle.length;
}

// 一种更简练的方法,len被赋值为undefined或length
var len = book && book.subtitle && book.subtitle.length;

删除属性

  • delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性
a = { p:{x:1} };
b = a.p;
delete a.p;

b.x;            // => 1,已经删除的属性引用(b)依然存在
  • delete 只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响所有继承的对象)
  • delete 不能删除那么可配置性为false的属性
var x = 1;          // 使用var定义的全局变量
delete this.x;      // 不能删除,可配置性为false

this.y = 1;         // 不使用var定义的全局变量
delete y;           // 可删除,可配置性为true

检测属性

可通过in运算符、hasOwnProperty()和propertyIsEnumerable()来判断某个属性是否存在于某个对象中。
其中propertyIsEnumerable()是hasOwnProperty()的增强版,保有检测到是自有属性并且这个属性是可枚举时才返回true。

var o = { x: 1 };
"x" in o;                       // true,x是o的属性
"y" in o;                       // false,y不是o的属性
"toString" in o;                // true,【继承属性】(方法)

o.hasOwnProperty("x");          // true,x是【自有属性】
o.hasOwnProperty("y");          // false
o.hasOwnProperty("toString");   // false,toString是【继承属性】

除了使用in运算符之外,还有更简便的方法(!== undefined):

var o = { x: 1 };
o.x !== undefined;          // true
o.y !== undefined;          // false
o.toStirng !== undefined;   // true

然而有一种场景这种方法是不可行的:属性值被显式地定义为undefined。

var o = { x: undefined };
o.x !== undefined;          // false,属性实际上是存在的!!!

注:还有一种情况是,这个属性只有setter方法,而没有getter方法。

枚举属性

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

// 把p中的可枚举属性复制到o中,并返回o
// 如果o和p有同名属性,则会覆盖o中的属性
// 这个函数不处理getter和setter以及复制属性
function extend(o, p) {
    // 遍历p中所有可枚举属性
    for(prop in p) {
        o[prop] = p[prop];
    }
    
    return o;
}

存取器属性(getter & setter)

在ECMAScript 5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。

  • 和数据属性不同,存取器属性不具有可写性。如果属性同时具有getter和setter方法,那么它是一个读/写属性;如果只有getter方法,则是一个只读属性;如果只有setter方法,则是一个只写属性,读取时返回undefined。
  • 和数据属性一样,存取器属性是可以继承的

定义存取器属性(使用直接量语法)的语法如下:

var p = {
    // 普通的数据属性
    x: 1.0,
    y: 1.0,
    
    // 存取器属性
    get r() { return Match.sqrt(this.x*this.x + this.y*this.y); } ,        // 不需要function声明,并且在最后添加“,”
    set r(newvalue) {
        this.x = newvalue;
        this.y = newvalue;
    } ,     
    
    get theta() { return Match.atan2(this.y, this.x); }
}
代码中定义了可读写属性r和只读属性theta。

属性的特性

除了包含名字和值之外,属性还包含可写可枚举可配置的特性。

属性类型 属性特性
数据属性 值(value)、可写性(writable)、可枚举性(enumerable)、可配置性(configurable)。
存取器属性 存取(get)、写入(set)、可枚举性和可配置性。

可通过Object.getOwnPropertyDescriptor()获取属性描述符:

// 返回{value: 1, writable:true, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor({x:1}, "x");

var random = {
    get octet() { return 1; }
}
// 返回{get:/*func*/, set:undefined, enumerable:true, configurable:true}
Object.getOwnPropertyDescriptor(random, "octet");

// 对于继承属性和不存在的属性,返回undefined
Object.getOwnPropertyDescriptor({}, "x");           // undefined,没有这个属性
Object.getOwnPropertyDescriptor({}, "toStirng");    // undefined,继承属性

Object.defineProperty()用于设置属性的特性:

var o = {}; // 创建一个空对象
// 添加一个属性x,并赋值为1
Object.defineProperty(o, "x", { value: 1,
                                writable: false,
                                enumerable: false,
                                configurable: true } );
                                      
o.x = 2;    // 操作失败,属性不可写

// 可通过重新设置属性特性来改变属性的值
Object.defineProperty(o, "x", {value: 2});
o.x;        // => 2

// 将数据属性修改存取器属性
Object.defineProperty(o, "x", { get: function() { return 3; });
o.x;        // => 3
前提 特性
对象不可扩展 可以编辑已有属性,但不能添加新属性
属性不可配置 不能修改它的可配置性和可枚举性
存取器属性不可配置 不能修改其getter和setter方法,也不能将它转换为数据属性
数据属性不可配置 则不能将它转换为存取器属性;不能修改它的可写性false=>true,但可以修改true=>false
数据属性不可配置且不可写 不能修改属性的值
数据属性可配置不可写 可以修改属性的值(实际上先修改属性为可写,然后修改值)

一个更完善的属性扩展属性函数extend():

Object.defineProperty(Object.prototype,
    "extend",
    {
        writable: true,
        enumerable: false,
        configurable: true,
        // 扩展值是一个函数
        value: fucntion(o) {
            // 得到所有的自有属性,包括不可枚举属性
            var names = Object.getOwnPropertyNames(o);
            // 将属性逐个扩展给Object.prototype
            for(var i=0; i < names.length; i++) {
                // 如果属性已经存在,则跳过
                if(names[i] in this) continue;
                var desc = Object.getOwnPropertyDescriptor(o, names[i]);
                Object.defineProperty(this, names[i], desc);
            }
        }
    }
);

对象的三个属性

原型属性

每一个JavaScript对象(null除外)都和另外一个对象相关联,这个对象就是原型对象,每一个对象都从原型继承属性
没有原型的对象为数不多,Object.prototype就是其中之一。它不继承任何属性。

对象 原型
通过对象直接量创建的对象 Object.prototype。
通过new创建的对象 构造函数的prototype属性。
通过Object.creat()创建的对象 第一个参数(也可以是null)作为它们的原型。

使用isPrototypeOf()进行原型判断,判断在原型链(prototype chain)上进行,如:

var p = { x: 1 };
var o = Object.create(p);
p.isPrototypeOf(o);                 // true,o继承自p
Object.prototype.isPrototypeOf(o);  // true,p继承自Object.prototype

类属性

对象的类属性(class attribute)是一个字符串,用以表示对象的类型信息。
默认的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);  // 使用Function.call方法
}

通过对象直接量、new构造函数或者Object.create()这3种方式创建的对象,类属性都是"Object"。

classof(null);      // "Null"
classof(1);         // "Number"
classof("");        // "String"
classof(false);     // "Boolean"
classof({});        // "Object"
classof([]);        // "Array"
classof(/./);       // "RegExp"
classof(new Date());// "Date"
classof(window);    // "Window"
function f() {}
classof(f);         // "Function",注意与下面的区别
classof(new f());   // "Object"

可扩展性

对象的可扩展性表示是否可以给对象添加新属性。
所有的内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性由JavaScript引擎定义的。

方法 作用
Object.isExtensible() 判断对象是否可扩展。
Object.preventExtensions() 将对象设置为不可扩展。注意:一旦设置为不可扩展,将无法再转换回可扩展。
Object.seal() 将对象设置为不可扩展,同时所有的自有属性不可配置。注意:对于已经封闭(sealed)的对象不能解封。
Object.freeze() 除了完成seal()的功能外,同时还将所有的数据属性设置为只读(如果对象的存取器属性具有settter方法,存取器属性将不受影响,仍可以通过给属性赋值调用它们)。

序列化对象(serialization)

对象序列化(JSON.stringify())是指对象的状态转换为字符串,也可将字符串还原(JSON.parse())为对象。
JSON.stringify()只能序列化对象可枚举的自有属性

o = { x:1, y:{z:[false, null, ""]} };
s = JSON.stringify(o);                  // s是"{ x:1, y:{z:[false, null, ""]} }"  
p = JSON.parse(s);                      // p是o的深拷贝
  • Number、String、Boolean、null、对象、数组都可以序列化和还原。NaN、Infinity和-Infinity序列化结果是null。
  • Date对象序列化的结果是ISO格式的日期字符串(参照Date.toJSON()函数),但JSON.parse()依然保留它们的字符串形态。
  • 函数、RegExp、Error对象和undefined值不能序列化和还原(没有定义toJSON()方法)。
var obj = new Date();
var s = JSON.stringify(obj);
var d = JSON.parse(s);
alert(obj);                     // Sat May 14 2016 17:33:04 GMT+0800
alert(s);                       // "2016-05-14T09:33:04.874Z" (stringify返回时包含2层"")
alert(d);                       // 2016-05-14T09:33:04.874Z
alert(typeof d);                // string类型,无法还原为Date类型

对象方法

toStirng()

由于默认的toString()方法并不会输出很多有用的信息([object Object]),因此很多类都带有自定义的toString()。
例如,数组转换为字符串时,结果是一个数组元素列表;当函数转换为字符串时,得到函数的源代码。

toLocaleString()

这个方法返回本地化字符串。Object默认的toLocaleString()并不做任何本地化自身的操作,它仅调用toString()方法并返回对应值。
Date和Number类对toLocaleString()方法做了定制,可以用它对数字、日期和时间做本地化的转换。

toJSON()

Object.property实际上没有定义toJSON()方法,但对于需要序列化的对象来说,JSON.stringify()方法会调用toJSON()方法。
如果在待序列化的对象中存在这个方法,则调用它,返回值即是序列化的结果,而不是原始的对象。

valueOf()

当JavaScript需要将对象转换为某种原始值时调用,尤其是转换为数字的时候。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容