阮一峰老师的JavaScript标准参考教程:Object对象和Object方法

Object对象

1. 概述

1.1 生成方法

对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型。

什么是对象?简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

var obj = {
  foo: 'Hello', bar: 'World' }; 

上面代码中,大括号就定义了一个对象,它被赋值给变量obj,所以变量obj就指向一个对象。该对象内部包含两个键值对(又称为两个“成员”),第一个键值对是foo: 'Hello',其中foo是“键名”(成员的名称),字符串Hello是“键值”(成员的值)。键名与键值之间用冒号分隔。第二个键值对是bar: 'World'bar是键名,World是键值。两个键值对之间用逗号分隔。

1.2 键名

对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以。上面的代码也可以写成下面这样。

var obj = {
  'foo': 'Hello', 'bar': 'World' }; 

如果键名是数值,会被自动转为字符串。

var obj = {
  1: 'a', 3.2: 'b', 1e2: true, 1e-2: true, .234: true, 0xFF: true }; obj // Object { // 1: "a", // 3.2: "b", // 100: true, // 0.01: true, // 0.234: true, // 255: true // } obj['100'] // true 

上面代码中,对象obj的所有键名虽然看上去像数值,实际上都被自动转成了字符串。

如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。

// 报错
var obj = { 1p: 'Hello World' }; // 不报错 var obj = { '1p': 'Hello World', 'h w': 'Hello World', 'p+q': 'Hello World' }; 

上面对象的三个键名,都不符合标识名的条件,所以必须加上引号。

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

var obj = {
  p: function (x) { return 2 * x; } }; obj.p(1) // 2 

上面代码中,对象obj的属性p,就指向一个函数。

如果属性的值还是一个对象,就形成了链式引用。

var o1 = {};
var o2 = { bar: 'hello' }; o1.foo = o2; o1.foo.bar // "hello" 

上面代码中,对象o1的属性foo指向对象o2,就可以链式引用o2的属性。

对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。

var obj = {
  p: 123, m: function () { ... }, } 

上面的代码中,m属性后面的那个逗号,有没有都可以。

属性可以动态创建,不必在对象声明时就指定。

var obj = {};
obj.foo = 123; obj.foo // 123 

上面代码中,直接对obj对象的foo属性赋值,结果就在运行时创建了foo属性。

1.3 对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

var o1 = {};
var o2 = o1; o1.a = 1; o2.a // 1 o2.b = 2; o1.b // 2 

上面代码中,o1o2指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。

此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

var o1 = {};
var o2 = o1; o1 = 1; o2 // {} 

上面代码中,o1o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象。

但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。

var x = 1; var y = x; x = 2; y // 1 

上面的代码中,当x的值发生变化后,y的值并不变,这就表示yx并不是指向同一个内存地址。

1.4 表达式还是语句?

对象采用大括号表示,这导致了一个问题:如果行首是一个大括号,它到底是表达式还是语句?

{ foo: 123 } 

JavaScript 引擎读到上面这行代码,会发现可能有两种含义。第一种可能是,这是一个表达式,表示一个包含foo属性的对象;第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123

为了避免这种歧义,V8 引擎规定,如果行首是大括号,一律解释为对象。不过,为了避免歧义,最好还是在大括号前加上圆括号。

({ foo: 123}) 

这种差异在eval语句(作用是对字符串求值)中反映得最明显。

eval('{foo: 123}') // 123 eval('({foo: 123})') // {foo: 123} 

上面代码中,如果没有圆括号,eval将其理解为一个代码块;加上圆括号以后,就理解成一个对象。

2. 属性的操作

2.1 读取属性

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

var obj = {
  p: 'Hello World' }; obj.p // "Hello World" obj['p'] // "Hello World" 

上面代码分别采用点运算符和方括号运算符,读取属性p

请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。

var foo = 'bar'; var obj = { foo: 1, bar: 2 }; obj.foo // 1 obj[foo] // 2 

上面代码中,引用对象objfoo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar

方括号运算符内部还可以使用表达式。

obj['hello' + ' world'] obj[3 + 3] 

数字键可以不加引号,因为会自动转成字符串。

var obj = {
  0.7: 'Hello World' }; obj['0.7'] // "Hello World" obj[0.7] // "Hello World" 

上面代码中,对象obj的数字键0.7,加不加引号都可以,因为会被自动转为字符串。

注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。

var obj = {
  123: 'hello world' }; obj.123 // 报错 obj[123] // "hello world" 

上面代码的第一个表达式,对数值键名123使用点运算符,结果报错。第二个表达式使用方括号运算符,结果就是正确的。

2.2 属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

var obj = {};

obj.foo = 'Hello'; obj['bar'] = 'World'; 

上面代码中,分别使用点运算符和方括号运算符,对属性赋值。

JavaScript 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

var obj = { p: 1 }; // 等价于 var obj = {}; obj.p = 1; 

2.3 查看所有属性

查看一个对象本身的所有属性,可以使用Object.keys方法。

var obj = {
  key1: 1, key2: 2 }; Object.keys(obj); // ['key1', 'key2'] 

2.4 delete 命令

delete命令用于删除对象的属性,删除成功后返回true

var obj = { p: 1 }; Object.keys(obj) // ["p"] delete obj.p // true obj.p // undefined Object.keys(obj) // [] 

上面代码中,delete命令删除对象objp属性。删除后,再读取p属性就会返回undefined,而且Object.keys方法的返回值也不再包括该属性。

注意,删除一个不存在的属性,delete不报错,而且返回true

var obj = {};
delete obj.p // true 

上面代码中,对象obj并没有p属性,但是delete命令照样返回true。因此,不能根据delete命令的结果,认定某个属性是存在的。

只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。

var obj = Object.defineProperty({}, 'p', { value: 123, configurable: false }); obj.p // 123 delete obj.p // false 

上面代码之中,对象objp属性是不能删除的,所以delete命令返回false(关于Object.defineProperty方法的介绍,请看《标准库》的 Object 对象一章)。

另外,需要注意的是,delete命令只能删除对象本身的属性,无法删除继承的属性(关于继承参见《面向对象编程》章节)。

var obj = {};
delete obj.toString // true obj.toString // function toString() { [native code] } 

上面代码中,toString是对象obj继承的属性,虽然delete命令返回true,但该属性并没有被删除,依然存在。这个例子还说明,即使delete返回true,该属性依然可能读取到值。

2.5 in 运算符

in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false

var obj = { p: 1 }; 'p' in obj // true 

in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。

var obj = {};
'toString' in obj // true 

上面代码中,toString方法不是对象obj自身的属性,而是继承的属性。但是,in运算符不能识别,对继承的属性也返回true

2.6 for…in 循环

for...in循环用来遍历一个对象的全部属性。

var obj = {a: 1, b: 2, c: 3}; for (var i in obj) { console.log(obj[i]); } // 1 // 2 // 3 

下面是一个使用for...in循环,提取对象属性名的例子。

var obj = {
  x: 1, y: 2 }; var props = []; var i = 0; for (var p in obj) { props[i++] = p } props // ['x', 'y'] 

for...in循环有两个使用注意点。

  • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
  • 它不仅遍历对象自身的属性,还遍历继承的属性。

举例来说,对象都继承了toString属性,但是for...in循环不会遍历到这个属性。

var obj = {};
// toString 属性是存在的 obj.toString // toString() { [native code] } for (var p in obj) { console.log(p); } // 没有任何输出 

上面代码中,对象obj继承了toString属性,该属性不会被for...in循环遍历到,因为它默认是“不可遍历”的。关于对象属性的可遍历性,参见《标准库》章节中 Object 一章的介绍。

如果继承的属性是可遍历的,那么就会被for...in循环遍历到。但是,一般情况下,都是只想遍历对象自身的属性,所以使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。

var person = { name: '老张' }; for (var key in person) { if (person.hasOwnProperty(key)) { console.log(key); } } // name 

3. with 语句

with语句的格式如下:

with (对象) { 语句; } 

它的作用是操作同一个对象的多个属性时,提供一些书写的方便。

// 例一
var obj = { p1: 1, p2: 2, }; with (obj) { p1 = 4; p2 = 5; } // 等同于 obj.p1 = 4; obj.p2 = 5; // 例二 with (document.links[0]){ console.log(href); console.log(title); console.log(style); } // 等同于 console.log(document.links[0].href); console.log(document.links[0].title); console.log(document.links[0].style); 

注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。

var obj = {};
with (obj) { p1 = 4; p2 = 5; } obj.p1 // undefined p1 // 4 

上面代码中,对象obj并没有p1属性,对p1赋值等于创造了一个全局变量p1。正确的写法应该是,先定义对象obj的属性p1,然后在with区块内操作它。

这是因为with区块没有改变作用域,它的内部依然是当前作用域。这造成了with语句的一个很大的弊病,就是绑定对象不明确。

with (obj) { console.log(x); } 

单纯从上面的代码块,根本无法判断x到底是全局变量,还是对象obj的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with语句,可以考虑用一个临时变量代替with

with(obj1.obj2.obj3) { console.log(p1 + p2); } // 可以写成 var temp = obj1.obj2.obj3; console.log(temp.p1 + temp.p2);

4. Object对象自身的方法(又称为”静态方法“)和Object对象实例方法

1. 概述

JavaScript 原生提供Object对象(注意起首的O是大写),本章介绍该对象原生的各种方法。

JavaScript 的所有其他对象都继承自Object对象,即那些对象都是Object的实例。

Object对象的原生方法分成两类:Object本身的方法与Object的实例方法。

(1)Object对象本身的方法

所谓”本身的方法“就是直接定义在Object对象的方法。

Object.print = function (o) { console.log(o) }; 

上面代码中,print方法就是直接定义在Object对象上。

(2)Object的实例方法

所谓实例方法就是定义在Object原型对象Object.prototype上的方法。它可以被Object实例直接使用。

Object.prototype.print = function () { console.log(this); }; var obj = new Object(); obj.print() // Object 

上面代码中,Object.prototype定义了一个print方法,然后生成一个Object的实例objobj直接继承了Object.prototype的属性和方法,可以直接使用obj.print调用print方法。也就是说,obj对象的print方法实质上就是调用Object.prototype.print方法。

关于原型对象object.prototype的详细解释,参见《面向对象编程》章节。这里只要知道,凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享就可以了。

以下先介绍Object作为函数的用法,然后再介绍Object对象的原生方法,分成对象自身的方法(又称为”静态方法“)和实例方法两部分。

2. Object()

Object本身是一个函数,可以当作工具方法使用,将任意值转为对象。这个方法常用于保证某个值一定是对象。

如果参数为空(或者为undefinednull),Object()返回一个空对象。

var obj = Object(); // 等同于 var obj = Object(undefined); var obj = Object(null); obj instanceof Object // true 

上面代码的含义,是将undefinednull转为对象,结果得到了一个空对象obj

instanceof运算符用来验证,一个对象是否为指定的构造函数的实例。obj instanceof Object返回true,就表示obj对象是Object的实例。

如果参数是原始类型的值,Object方法将其转为对应的包装对象的实例(参见《原始类型的包装对象》一章)。

var obj = Object(1); obj instanceof Object // true obj instanceof Number // true var obj = Object('foo'); obj instanceof Object // true obj instanceof String // true var obj = Object(true); obj instanceof Object // true obj instanceof Boolean // true 

上面代码中,Object函数的参数是各种原始类型的值,转换成对象就是原始类型值对应的包装对象。

如果Object方法的参数是一个对象,它总是返回该对象,即不用转换。

var arr = [];
var obj = Object(arr); // 返回原数组 obj === arr // true var value = {}; var obj = Object(value) // 返回原对象 obj === value // true var fn = function () {}; var obj = Object(fn); // 返回原函数 obj === fn // true 

利用这一点,可以写一个判断变量是否为对象的函数。

function isObject(value) { return value === Object(value); } isObject([]) // true isObject(true) // false 

3. Object 构造函数

Object不仅可以当作工具函数使用,还可以当作构造函数使用,即前面可以使用new命令。

Object构造函数的首要用途,是直接通过它来生成新对象。

var obj = new Object(); 

注意,通过var obj = new Object()的写法生成新对象,与字面量的写法var obj = {}是等价的。或者说,后者只是前者的一种简便写法。

Object构造函数的用法与工具方法很相似,几乎一模一样。使用时,可以接受一个参数,如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象(详见《包装对象》一章)。

var o1 = {a: 1}; var o2 = new Object(o1); o1 === o2 // true var obj = new Object(123); obj instanceof Number // true 

虽然用法相似,但是Object(value)new Object(value)两者的语义是不同的,Object(value)表示将value转成一个对象,new Object(value)则表示新生成一个对象,它的值是value

4. Object 的静态方法

所谓“静态方法”,是指部署在Object对象自身的方法。

4.1 Object.keys(),Object.getOwnPropertyNames()

Object.keys方法和Object.getOwnPropertyNames方法都用来遍历对象的属性。

Object.keys方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继承的)所有属性名。

var obj = {
  p1: 123, p2: 456 }; Object.keys(obj) // ["p1", "p2"] 

Object.getOwnPropertyNames方法与Object.keys类似,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。

var obj = {
  p1: 123, p2: 456 }; Object.getOwnPropertyNames(obj) // ["p1", "p2"] 

对于一般的对象来说,Object.keys()Object.getOwnPropertyNames()返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。Object.keys方法只返回可枚举的属性(详见《对象属性的描述对象》一章),Object.getOwnPropertyNames方法还返回不可枚举的属性名。

var a = ['Hello', 'World']; Object.keys(a) // ["0", "1"] Object.getOwnPropertyNames(a) // ["0", "1", "length"] 

上面代码中,数组的length属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames方法的返回结果中。

由于 JavaScript 没有提供计算对象属性个数的方法,所以可以用这两个方法代替。

var obj = {
  p1: 123, p2: 456 }; Object.keys(obj).length // 2 Object.getOwnPropertyNames(obj).length // 2 

一般情况下,几乎总是使用Object.keys方法,遍历数组的属性。

4.2 其他方法

除了上面提到的两个方法,Object还有不少其他静态方法,将在后文逐一详细介绍。

(1)对象属性模型的相关方法

  • Object.getOwnPropertyDescriptor():获取某个属性的描述对象。
  • Object.defineProperty():通过描述对象,定义某个属性。
  • Object.defineProperties():通过描述对象,定义多个属性。

(2)控制对象状态的方法

  • Object.preventExtensions():防止对象扩展。
  • Object.isExtensible():判断对象是否可扩展。
  • Object.seal():禁止对象配置。
  • Object.isSealed():判断一个对象是否可配置。
  • Object.freeze():冻结一个对象。
  • Object.isFrozen():判断一个对象是否被冻结。

(3)原型链相关方法

  • Object.create():该方法可以指定原型对象和属性,返回一个新的对象。
  • Object.getPrototypeOf():获取对象的Prototype对象。

5. Object 的实例方法

除了静态方法,还有不少方法定义在Object.prototype对象。它们称为实例方法,所有Object的实例对象都继承了这些方法。

Object实例对象的方法,主要有以下六个。

  • Object.prototype.valueOf():返回当前对象对应的值。
  • Object.prototype.toString():返回当前对象对应的字符串形式。
  • Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。
  • Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
  • Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。
  • Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。

本节介绍前四个方法,另外两个方法将在后文相关章节介绍。

5.1 Object.prototype.valueOf()

valueOf方法的作用是返回一个对象的“值”,默认情况下返回对象本身。

var obj = new Object(); obj.valueOf() === obj // true 

上面代码比较obj.valueOf()obj本身,两者是一样的。

valueOf方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法(详见《数据类型转换》一章)。

var obj = new Object(); 1 + obj // "1[object Object]" 

上面代码将对象obj与数字1相加,这时 JavaScript 就会默认调用valueOf()方法,求出obj的值再与1相加。所以,如果自定义valueOf方法,就可以得到想要的结果。

var obj = new Object(); obj.valueOf = function () { return 2; }; 1 + obj // 3 

上面代码自定义了obj对象的valueOf方法,于是1 + obj就得到了3。这种方法就相当于用自定义的obj.valueOf,覆盖Object.prototype.valueOf

5.2 Object.prototype.toString()

toString方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。

var o1 = new Object(); o1.toString() // "[object Object]" var o2 = {a:1}; o2.toString() // "[object Object]" 

上面代码表示,对于一个对象调用toString方法,会返回字符串[object Object],该字符串说明对象的类型。

字符串[object Object]本身没有太大的用处,但是通过自定义toString方法,可以让对象在自动类型转换时,得到想要的字符串形式。

var obj = new Object(); obj.toString = function () { return 'hello'; }; obj + ' ' + 'world' // "hello world" 

上面代码表示,当对象用于字符串加法时,会自动调用toString方法。由于自定义了toString方法,所以返回字符串hello world

数组、字符串、函数、Date 对象都分别部署了自定义的toString方法,覆盖了Object.prototype.toString方法。

[1, 2, 3].toString() // "1,2,3" '123'.toString() // "123" (function () { return 123; }).toString() // "function () { // return 123; // }" (new Date()).toString() // "Tue May 10 2016 09:11:31 GMT+0800 (CST)" 

上面代码中,数组、字符串、函数、Date 对象调用toString方法,并不会返回[object Object],因为它们都自定义了toString方法,覆盖原始方法。

5.3 toString() 的应用:判断数据类型

Object.prototype.toString方法返回对象的类型字符串,因此可以用来判断一个值的类型。

var obj = {};
obj.toString() // "[object Object]" 

上面代码调用空对象的toString方法,结果返回一个字符串object Object,其中第二个Object表示该值的构造函数。这是一个十分有用的判断数据类型的方法。

由于实例对象可能会自定义toString方法,覆盖掉Object.prototype.toString方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString方法。通过函数的call方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。

Object.prototype.toString.call(value) 

上面代码表示对value这个值调用Object.prototype.toString方法。假如value..prototype.toString已经被覆盖也没有影响,因为call只是借助这个方法Object.prototype.toString。

不同数据类型的Object.prototype.toString方法返回值如下。

  • 数值:返回[object Number]
  • 字符串:返回[object String]
  • 布尔值:返回[object Boolean]
  • undefined:返回[object Undefined]
  • null:返回[object Null]
  • 数组:返回[object Array]
  • arguments 对象:返回[object Arguments]
  • 函数:返回[object Function]
  • Error 对象:返回[object Error]
  • Date 对象:返回[object Date]
  • RegExp 对象:返回[object RegExp]
  • 其他对象:返回[object Object]

这就是说,Object.prototype.toString可以看出一个值到底是什么类型。

Object.prototype.toString.call(2) // "[object Number]" Object.prototype.toString.call('') // "[object String]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(Math) // "[object Math]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call([]) // "[object Array]" 

利用这个特性,可以写出一个比typeof运算符更准确的类型判断函数。

var type = function (o){ var s = Object.prototype.toString.call(o); return s.match(/\[object (.*?)\]/)[1].toLowerCase(); }; type({}); // "object" type([]); // "array" type(5); // "number" type(null); // "null" type(); // "undefined" type(/abcd/); // "regex" type(new Date()); // "date" 

在上面这个type函数的基础上,还可以加上专门判断某种类型数据的方法。

var type = function (o){ var s = Object.prototype.toString.call(o); return s.match(/\[object (.*?)\]/)[1].toLowerCase(); }; ['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp' ].forEach(function (t) { type['is' + t] = function (o) { return type(o) === t.toLowerCase(); }; }); type.isObject({}) // true type.isNumber(NaN) // true type.isRegExp(/abc/) // true 

5.4 Object.prototype.toLocaleString()

Object.prototype.toLocaleString方法与toString的返回结果相同,也是返回一个值的字符串形式。

var obj = {};
obj.toString(obj) // "[object Object]" obj.toLocaleString(obj) // "[object Object]" 

这个方法的主要作用是留出一个接口,让各种不同的对象实现自己版本的toLocaleString用来返回针对某些地域的特定的值。目前,主要有三个对象自定义了toLocaleString方法。

  • Array.prototype.toLocaleString()
  • Number.prototype.toLocaleString()
  • Date.prototype.toLocaleString()

举例来说,日期的实例对象的toStringtoLocaleString返回值就不一样,而且toLocaleString的返回值跟用户设定的所在地域相关。

var date = new Date(); date.toString() // "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)" date.toLocaleString() // "1/01/2018, 12:01:33 PM" 

5.5 Object.prototype.hasOwnProperty()

Object.prototype.hasOwnProperty方法接受一个字符串作为参数,返回一个布尔值,表示该实例对象自身是否具有该属性。

var obj = {
  p: 123 }; obj.hasOwnProperty('p') // true obj.hasOwnProperty('toString') // false 

上面代码中,对象obj自身具有p属性,所以返回truetoString属性是继承的,所以返回false

 

猜你喜欢

转载自www.cnblogs.com/moumoon/p/10988977.html
今日推荐