《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

然则,假如对象不存在,那末试图查询这个不存在的对象的属性就会报错。nullundefined 值都没有属性,因而查询这些值的属性会报错。比方:

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/…

扩大浏览「JavaScript 检测之 basevalidate.js」
http://shijiajie.com/2016/06/…

罗列属性

除了检测对象的属性是不是存在,我们还会常常遍历对象的属性。一般运用 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中,属性值能够用一个或两个要领替换,这两个要领就是 gettersetter。由 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 关键字,而是运用 getset。注重,这里没有运用冒号将属性名和函数体分开开,但在函数体的完毕和下一个要领或数据属性之间有逗号分开。

序列化对象(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/douglascro… 中的大众域ECMAScript 3版本的完成异常相似,或许说完整一样,因而能够经由过程引入 json2.js 模块在ECMAScript 3的环境中运用ECMAScript 5中的这些函数。

JSON 的语法是 JavaScript 语法的子集,它并不能示意 JavaScript 里的一切值。它支撑对象、数组、字符串、无穷大数字、truefalsenull,能够序列化和复原它们。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,猎取最新动态。

    原文作者:劼哥stone
    原文地址: https://segmentfault.com/a/1190000006846163
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞