ECMAScript《JavaScript 闯关记》之对象

靶是 JavaScript
的数据类型。它以多价值(原始值或者其它对象)聚合在一起,可经名字访问这些价值,因此我们可拿它们看成是打字符串到价值的照射。对象是动态的,可以天天新增与去自来性能。对象除了可保障自有的习性,还可由一个叫作原型的对象继承属性,这种「原型式继承(prototypal
inheritance)」是 JavaScript 的着力特征。

对象极其广的用法是创造(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性。

性能包括名字跟价值。属性名可以是包含空字符串在内的任意字符串,但目标中不可知在个别只同名的属性。值好是随意
JavaScript 值,或者当 ECMAScript 5遭到可是 gettersetter 函数。

除外名字与价值之外,每个属性还有局部及的休戚相关的值,称为「属性特性(property
attribute)」:

  • 但是写(writable attribute),表明是否足以装该属性之价值。
  • 可是枚举(enumerable attribute),表明是否可经 for-in
    循环返回该属性。
  • 可配置(configurable attribute),表明是否好去除或改该属性。

当 ECMAScript
5之前,通过代码给目标创建的有所属性都是可写的、可枚举的跟而安排的。在
ECMAScript 5备受虽然足以本着这些特色加以配置。

而外饱含属性特性外,每个对象还具有三单相关的「对象特性(object
attribute)」:

  • 目标的类(class),是一个标识对象类型的字符串。
  • 目标的原型(prototype),指向另外一个靶,本对象的特性持续自它的原型对象。
  • 靶的扩张标记(extensible flag),指明了当 ECMAScript
    5中是不是好望该目标上加新属性。

最终,用脚术语来对 JavaScript 的「三类对象」和「两看似特性」进行分:

  • 嵌入对象(native object),是由 JavaScript
    规范定义的靶子或近乎。例如,数组、函数、日期以及正则表达式都是停放对象。
  • 宿主对象(host object),是由 JavaScript
    解释器所安放的宿主环境(比如 Web 浏览器)定义的。客户端 JavaScript
    中意味网页结构的 HTMLElement 对象均是宿主对象。
  • 自从定义对象(user-defined object),是由运行着之 JavaScript
    代码创建的靶子。
  • 从出性能(own property),是一直当目标被定义的性能。
  • 承属性(inherited property),是当对象的原型对象被定义之习性。

创建对象

得以对象字面量、new 关键字和 ECMAScript 5面临的 Object.create()
函数来创建对象。

采取对象字面量创建对象(推荐)

创建对象最简易的主意尽管是于 JavaScript
代码中行使对象字面量。对象字面量是由于几名值对成的映射表,名值对中等用冒号分隔,名值对中因此逗号分隔,整个映射表用花括号括起来。属性名可以是
JavaScript
标识符也得以是字符串直接量(包括空字符串)。属性之价值好是不管三七二十一档次的
JavaScript
表达式,表达式的价(可以是原始值也可是针对象值)就是者特性的价。例如:

// 推荐写法
var person = {
    name : "stone",
    age : 28
};

// 也可以写成
var person = {};
person.name = "stone";
person.age = 28;

使用 new 关键字创建对象

new 关键字创建并初始化一个初目标。关键字 new
后紧跟着一个函数调用。这里的函数称做构造函数(constructor),构造函数用以初始化一个初创造的靶子。JavaScript
语言基本中之原始类型都含有内置构造函数。例如:

var person = new Object();
person.name = "stone";
person.age = 28;

其中 var person = new Object(); 等价于 var person = {};

使用 Object.create() 函数创建对象

ECMAScript 5概念了一个叫也 Object.create()
的法子,它创建一个新对象,其中第一单参数是其一目标的原型。Object.create()
提供次个可挑选参数,用以对目标的性质进行更描述。Object.create()
是一个静态函数,而休是提供于某对象调用的计。使用她的措施充分粗略,只须传入所需要的原型对象即可。例如:

var person = Object.create(Object.prototype);
person.name = "stone";
person.age = 28;

其中 var person = Object.create(Object.prototype); 也当于
var person = {};

原型(prototype)

装有通过对象字面量创建的目标还负有与一个原型对象,并可经 JavaScript
代码 Object.prototype 获得对原型对象的援。通过机要字 new
和构造函数调用创建的对象的原型就是构造函数的 prototype
属性的价。因此,同以 {} 创建对象一样,通过 new Object()
创建的对象也连续自 Object.prototype。同样,通过 new Array()
创建的靶子的原型就是是 Array.prototype,通过 new Date()
创建的对象的原型就是是 Date.prototype

从来不原型的靶子为数不多,Object.prototype
就是内有。它不累任何性质。其他原型对象都是通常对象,普通对象还具备原型。所有的嵌入构造函数(以及大部分于定义之构造函数)都持有一个累自
Object.prototype 的原型。例如,Date.prototype 的习性持续自
Object.prototype,因此由 new Date() 创建的 Date
对象的特性同时继续自 Date.prototypeObject.prototype

旋即等同密密麻麻链接的原型对象就是是所谓的「原型链(prototype chain)」。

特性的查询和设置

眼前来涉嫌过,可以由此点 . 或方括号 [] 运算符来获取属性的价。对于点
.
来说,左侧应当是一个对象,右侧必须是一个为性名称命名的大概标识符。对于方括号吧
[]
,方括号内必须是一个盘算结果也字符串的表达式,这个字符串就是性质的称号。例如:

// 推荐写法
console.log(person.name);   // "stone"
console.log(person.age);    // "28"

// 也可以写成
console.log(person["name"]);    // stone
console.log(person["age"]);     // 28

与取属性之价值写法平,通过接触和方括号为堪创建属性或叫属性赋值,但待拿其位于赋值表达式的左手。例如:

// 推荐写法
person.name = "sophie"; // 赋值
person.age = 30;        // 赋值
person.weight = 38;     // 创建

// 也可以写成
person["name"] = "sophie";  // 赋值
person["age"] = 30;         // 赋值
person["weight"] = 38;      // 创建

当以方括号时,方括号内之表达式必须返回字符串。更严苛地说道,表达式必须回到字符串或返回一个可以转换为字符串的价。

属性之拜访错误

查询一个未设有的性能并无见面报错,如果当靶 o
自身的性质或继续的性质被皆无找到属性 x,属性访问表达式 o.x 返回
undefined。例如:

var person = {};
person.wife;    // undefined

而,如果目标不在,那么试图询问这不有的靶子的性就见面报错。null
undefined 值都不曾性,因此查询这些价值的特性会报错。例如:

var person = {};
person.wife.name;   // Uncaught TypeError: Cannot read property 'name' of undefined.

除非确定 personperson.wife 都是目标,否则不可知如此形容表达式
person.wife.name,因为会报「未捕获的荒唐类型」,下面提供了简单栽避免失误的艺术:

// 冗余但易懂的写法
var name;
if (person) {
    if (person.wife) 
        name = person.wife.name;
}

// 简练又常用的写法(推荐写法)
var name = person && person.wife && person.wife.name;

去属性

delete 运算符用来删除对象属性,事实上 delete
只是绝对开属性和宿主对象的牵连,并没有真正的勾其。delete
运算符只能去自发生总体性,不可知去继承属性(要抹继承属性必须从概念之特性之原型对象及删除其,而且就会潜移默化到具有继续自是原型的目标)。

代码范例,请参见「变量和数据类型」-「数据类型」-「delete
运算符」。

检测属性

JavaScript
对象可以当做属性之汇聚,我们常常会面检测集合中成员的所属关系(判断有属性是否是被某某对象吃)。可以由此
in 运算符、hasOwnPreperty()propertyIsEnumerable()
来完成这个工作,甚至单独通过性查询也得就即一点。

in
运算符的左是属于性名(字符串),右侧是目标。如果目标的由来总体性或延续属性被隐含这个特性则归
true。例如:

var o = { x: 1 }
console.log("x" in o);          // true,x是o的属性
console.log("y" in o);          // false,y不是o的属性
console.log("toString" in o);   // true,toString是继承属性

对象的 hasOwnProperty()
方法用来检测给定的讳是否是目标的从来性能。对于连续属性它用回来
false。例如:

var o = { x: 1 }
console.log(o.hasOwnProperty("x"));          // true,x是o的自有属性
console.log(o.hasOwnProperty("y"));          // false,y不是o的属性
console.log(o.hasOwnProperty("toString"));   // false,toString是继承属性

propertyIsEnumerable()hasOwnProperty()
的滋长版,只有检测及是从来属于性且这个特性的不过枚举性(enumerable
attribute)为 true 时它才回来 true。某些内置属性是不可枚举的。通常由
JavaScript 代码创建的性能都是可枚举的,除非在 ECMAScript
5蒙受应用一个不同寻常的艺术来改变属性之不过枚举性。例如:

var o = inherit({ y: 2 });
o.x = 1;
o.propertyIsEnumerable("x");    // true:,x是o的自有属性,可枚举
o.propertyIsEnumerable("y");    // false,y是继承属性
Object.prototype.propertyIsEnumerable("toString");  // false,不可枚举

除外采用 in 运算符之外,另一样种植更简便的法子是使 !==
判断一个属性是否是 undefined。例如:

var o = { x: 1 }
console.log(o.x !== undefined);              // true,x是o的属性
console.log(o.y !== undefined);              // false,y不是o的属性
console.log(o.toString !== undefined);       // true,toString是继承属性

然而生一致种植状况只能利用 in 运算符而不可知用上述特性访问的章程。in
可以分别无有的性与在但值为 undefined 的属性。例如:

var o = { x: undefined }        // 属性被显式赋值为undefined
console.log(o.x !== undefined); // false,属性存在,但值为undefined
console.log(o.y !== undefined); // false,属性不存在
console.log("x" in o);          // true,属性存在
console.log("y" in o);          // false,属性不存在
console.log(delete o.x);        // true,删除了属性x
console.log("x" in o);          // false,属性不再存在

推而广之阅读「JavaScript 检测原始值、引用值、属性」
http://shijiajie.com/2016/06/20/javascript-maintainable-javascript-validate1/

壮大阅读「JavaScript 检测的 basevalidate.js」
http://shijiajie.com/2016/06/25/javascript-maintainable-javascript-basevalidatejs/

枚举属性

除开检测对象的性能是否留存,我们尚会时时遍历对象的性质。通常使用
for-in 循环遍历,ECMAScript 5资了个别独再好用的代表方案。

for-in
循环可以当循环体中遍历对象吃所有可枚举的性质(包括由出性与持续的特性),把性能名称赋值给循环变量。对象继承的坐方法不可枚举的,但在代码中给目标添加的性都是可枚举的。例如:

var o = {x:1, y:2, z:3};            // 三个可枚举的自有属性
o.propertyIsEnumerable("toString"); // false,不可枚举
for (p in o) {          // 遍历属性
    console.log(p);     // 输出x、y和z,不会输出toString
}

生不少实用工具库给 Object.prototype
添加了新的法子还是性质,这些办法与总体性可以叫所有目标继承并应用。然而在ECMAScript
5规范之前,这些新长的法门是勿可知定义也不可枚举的,因此她都好当
for-in 循环中枚举出来。为了避免这种状态,需要过滤 for-in
循环返回的习性,下面两种办法是最好广大的:

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

除了 for-in 循环之外,ECMAScript
5定义了点滴单用于枚举属性名称的函数。第一只凡是
Object.keys(),它回到一个频繁组,这个数组由对象中可是枚举的打生性能之称组成。第二独凡是
Object.getOwnPropertyNames(),它和 Ojbect.keys()
类似,只是她回到对象的具备由来总体性的名号,而不仅仅是可枚举的属性。在ECMAScript
3遭受凡无法兑现的好像之函数的,因为ECMAScript
3受到无提供其他方法来赢得对象不可枚举的习性。

属性的 gettersetter

咱清楚,对象属性是由名字、值与同样组特性(attribute)构成的。在ECMAScript
5挨,属性值可以据此一个要少于独方法替代,这半个法子就是 getter
setter。由 gettersetter 定义的性称做「存取器属性(accessor
property)」,它不同让「数据性(data
property)」,数据性只生一个简练的值。

当次查询存取器属性的价经常,JavaScript 调用 getter
方法。这个措施的归来值就是是性存取表达式的价。当次设置一个存取器属性的值时,JavaScript
调用 setter 方法,将赋值表达式右侧的价当参数传入
setter。从某种意义上道,这个主意负责「设置」属性值。可以忽略 setter
方法的归值。

及数量性不同,存取器属性不抱有可写性(writable
attribute)。如果属性同时负有 gettersetter
方法,那么她是一个念/写属性。如果它们才发生 getter
方法,那么它是一个仅仅念属性。如果其独自出 setter
方法,那么它们是一个独自写属性,读取只写属性总是回到
undefined。定义存取器属性最简单易行的法是利用对象直接量语法的如出一辙种植扩大写法。例如:

var o = {
    // 普通的数据属性
    data_prop: value,

    // 存取器属性都是成对定义的函数
    get accessor_prop() { /*这里是函数体 */ },
    set accessor_prop(value) { /* 这里是函数体*/ }
};

存取器属性定义为一个或少数个与性能同名的函数,这个函数定义尚无用
function 关键字,而是下 get
set。注意,这里没下冒号将属于性名和函数体分隔开,但以函数体的截止和下一个艺术要数额性之间时有发生逗号分隔。

序列化对象(JSON)

目标序列化(serialization)是借助将对象的状态转换为字符串,也只是拿字符串还原为对象。ECMAScript
5资了内置函数 JSON.stringify()JSON.parse() 用来序列化和恢复
JavaScript 对象。这些措施还使 JSON 作为数据交换格式,JSON
的齐全是「JavaScript 对象表示法(JavaScript Object
Notation)」,它的语法和 JavaScript
对象及数组直接量的语法非常相近。例如:

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

ECMAScript 5中的这些函数的当地实现和
https://github.com/douglascrockford/JSON-js
中的公共域ECMAScript
3本子的兑现好类似,或者说完全相同,因此好通过引入 json2.js
模块于ECMAScript 3的环境被动用ECMAScript 5惨遭之这些函数。

JSON 的语法是 JavaScript 语法的子集,它并无克代表 JavaScript
里之具备值。它支持对象、数组、字符串、无穷大数字、truefalse
null,可以序列化和恢复它。NaNInfinity-Infinity
序列化的结果是 null,日期对象序列化的结果是 ISO 格式的日期字符串(参照
Date.toJSON() 函数),但 JSON.parse()
依然保留它的字符串形态,而不会见将它们还原为本来日期对象。函数、RegExpError
对象和 undefined 值未克序列化和回复。JSON.stringify()
只能序列化对象只是枚举的起发生性。对于一个休可知序列化的性质来说,在序列化后的输出字符串中会将这特性省略掉。JSON.stringify()
JSON.parse()
都可以接纳第二独可选参数,通过传播需要序列化或还原的性列表来定制自定义的序列化或还原操作。

关卡

要实现下面用来朵举属性的对象工具函数:

/*
 * 把 p 中的可枚举属性复制到 o 中,并返回 o
 * 如果 o 和 p 中含有同名属性,则覆盖 o 中的属性
 */
function extend(o, p) {
    // 请实现函数体
}

/*
 * 将 p 中的可枚举属性复制至 o 中,并返回 o
 * 如果 o 和 p 中有同名的属性,o 中的属性将不受影响
 */
function merge(o, p) {
    // 请实现函数体
}

/*
 * 如果 o 中的属性在 p 中没有同名属性,则从 o 中删除这个属性
 * 返回 o
 */
function restrict(o, p) {
    // 请实现函数体
}

/*
 * 如果 o 中的属性在 p 中存在同名属性,则从 o 中删除这个属性
 * 返回 o
 */
function subtract(o, p) {
    // 请实现函数体
}

/*
 * 返回一个新对象,这个对象同时拥有 o 的属性和 p 的属性
 * 如果 o 和 p 中有重名属性,使用 p 中的属性值
 */
function union(o, p) { 
    // 请实现函数体
}

/*
 * 返回一个新对象,这个对象拥有同时在 o 和 p 中出现的属性
 * 很像求 o 和 p 的交集,但 p 中属性的值被忽略
 */
function intersection(o, p) { 
    // 请实现函数体
}

/*
 * 返回一个数组,这个数组包含的是 o 中可枚举的自有属性的名字
 */
function keys(o) {
    // 请实现函数体
}

更多

体贴入微微信公众号「劼哥舍」回复「答案」,获取关卡详解。
关注
https://github.com/stone0090/javascript-lessons,获取最新动态。