文章目录
  1. 1. 概述
    1. 1.1. 定义方法
    2. 1.2. 键名
    3. 1.3. 属性
    4. 1.4. 生成方法
    5. 1.5. 读写属性
    6. 1.6. 属性的删除
      1. 1.6.1. 以下这些都不能delete
    7. 1.7. 对象的引用
    8. 1.8. in运算符
    9. 1.9. for…in循环
  2. 2. with语句
  3. 3. Object
    1. 3.1. 概述
    2. 3.2. Object()
    3. 3.3. Object 对象的静态方法
      1. 3.3.1. Object.keys(),Object.getOwnPropertyNames()
      2. 3.3.2. 其他方法
    4. 3.4. Object对象的实例方法
      1. 3.4.1. Object.prototype.valueOf()
      2. 3.4.2. Object.prototype.toString()
      3. 3.4.3. toString()的应用:判断数据类型

概述

定义方法

对象(object)是JavaScript的核心概念,也是最重要的数据类型。JavaScript的所有数据都可以被视为对象。
简单说,所谓对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。

1
2
3
var o = {
'p': 'Hello World'
};

上面代码中,大括号就定义了一个对象,它被赋值给变量o。这个对象内部包含一个键值对(又称为“成员”),p是“键名”(成员的名称),字符串“Hello World”是“键值”(成员的值)。键名与键值之间用冒号分隔。如果对象内部包含多个键值对,每个键值对之间用逗号分隔。

键名

对象的所有键名都是字符串,所以加不加引号都可以。上面的代码也可以写成下面这样。

1
2
3
var o = {
p: 'Hello World'
};

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

1
2
3
4
5
var o = {
'1p': "Hello World",
'h w': "Hello World",
'p+q': "Hello World"
};

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

注意,JavaScript的保留字可以不加引号当作键名。

1
2
3
4
var obj = {
for: 1,
class: 2
};

如果键名是数字,则会默认转为对应的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
1e2: true,
1e-2: true,
.234: true,
0xFF: true,
};

//Obj =
// {
// 100: true,
// 255: true,
// 0.01: true,
// 0.234: true
// }

上面代码表示,如果键名为数值,则会先转为标准形式的数值,然后再转为字符串。

属性

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

1
2
3
4
5
6
7
8
var o = {
p: function(x) {
return 2 * x;
}
};

o.p(1)
// 2

上面的对象就有一个方法p,它就是一个函数。

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

1
2
3
4
var o = {
p: 123,
m: function () { ... },//这个逗号不能加
}

上面的代码中m属性后面的那个逗号,有或没有都不算错。但是,ECMAScript 3不允许添加逗号,所以如果要兼容老式浏览器(比如IE 8),那就不能加这个逗号。

生成方法

对象的生成方法,通常有三种方法。除了像上面那样直接使用大括号生成({}),还可以用new命令生成一个Object对象的实例,或者使用Object.create方法生成。

1
2
3
var o1 = {};
var o2 = new Object();// 可以简写为 var o2 = new Object; 但是不推荐
var o3 = Object.create(null);

上面三行语句是等价的。一般来说,第一种采用大括号的写法比较简洁,第二种采用构造函数的写法清晰地表示了意图,第三种写法一般用在需要对象继承的场合。

读写属性

(1)读取属性

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

1
2
3
4
5
6
var o = {
p: 'Hello World'
};

o.p // "Hello World"
o['p'] // "Hello World"

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

请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。但是,数字键可以不加引号,因为会被当作字符串处理。

1
2
3
4
5
6
var o = {
0.7: "Hello World"
};

o.['0.7'] // "Hello World"
o[0.7] // "Hello World"

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

1
2
o['hello' + ' world']
o[3 + 3]

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

1
2
3
4
obj.0xFF
// SyntaxError: Unexpected token
obj[0xFF]
// true

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

(2)检查变量是否声明

如果读取一个不存在的键,会返回undefined,而不是报错。可以利用这一点,来检查一个变量是否被声明。

1
2
3
4
5
6
// 检查a变量是否被声明

if(a) {...} // 报错

if(window.a) {...} // 不报错
if(window['a']) {...} // 不报错

上面的后两种写法之所以不报错,是因为在浏览器环境,所有全局变量都是window对象的属性。window.的含义就是读取window对象的a属性,如果该属性不存在,就返回undefined,并不会报错。需要注意的是,后两种写法有漏洞,如果a属性值是一个空字符串(或其对应的布尔值为false的情况),则无法起到检查变量是否声明的作用。
正确的写法是使用in运算符,或者使用Object原型的方法hasOwnProperty()也就是Object原型。

1
2
3
4
5
6
7
if('a' in window) {
...
}

if(window.hasOwnProperty("a")) {
...
}

(3)写入属性

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

1
2
o.p = 'abc';
o['p'] = 'abc';

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

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

1
2
3
4
5
6
var o = { p: 1 };

// 等价于

var o = {};
o.p = 1;

(4)查看所有属性

Object.keys() 方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。
支持:IE9+(包括IE9)。

1
2
3
4
5
6
7
var o = {
key1: 1,
key2: 2
};

Object.keys(o);
// ['key1', 'key2']

兼容处理方案:

1
if (!Object.keys) {
    Object.keys = function(o) {
        if (o !== Object(o)) {
            throw new TypeError('Object.keys called on a non-object');
        }
        var k = [],
            p;
        for (p in o) {
            if (Object.prototype.hasOwnProperty.call(o, p)) {
                k.push(p);
            }
        }
        return k;
    }
}

属性的删除

删除一个属性,需要使用delete命令。

1
2
3
4
5
6
var o = {p: 1};
Object.keys(o) // ["p"]

delete o.p // true
o.p // undefined
Object.keys(o) // []

上面代码表示,一旦使用delete命令删除某个属性,再读取该属性就会返回undefined,而且Object.keys方法返回的该对象的所有属性中,也将不再包括该属性。

麻烦的是,如果删除一个不存在的属性,delete不报错,而且返回true。

1
2
var o = {};
delete o.p // true

上面代码表示,delete命令只能用来保证某个属性的值为undefined,而无法保证该属性是否真的存在。

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

1
2
3
4
5
6
7
8

var o = Object.defineProperty({}, "p", {
value: 123,
configurable: false
});

o.p // 123
delete o.p // false

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

另外,需要注意的是,delete命令只能删除对象本身的属性,不能删除继承的属性(关于继承参见《面向对象编程》一节)。delete命令也不能删除var命令声明的变量,只能用来删除属性。

以下这些都不能delete

  • 1
    2
    3
    4
    5
    function func() {
    console.log(delete arguments);//false
    console.log(Object.getOwnPropertyDescriptor(func,"arguments"));//{writable: false, enumerable: false, configurable: false}
    }
    func(1);
  • var 声明的变量。

  • 1
    2
    3
    4
    5
    function func() {

    }
    delete func//false
    Object.getOwnPropertyDescriptor(window,"func"); // {writable: true, enumerable: true, configurable: false}
  • delete不能删除对象继承来自原型上的属性
    总结下:

    1. 内置对象的属性及方法多数不能delete,保护该语言最核心API,这些API被delete了,基本上就废了。如delete Object.prototype。
    2. 对象继承于原型的属性和方法不能delete是出于保护原型,否则 “类A的对象delete了原型上的属性,那么继承于A的都将丢失该属性”

对象的引用

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

1
2
3
4
5
6
7
8
var o1 = {};
var o2 = o1;

o1.a = 1;
o2.a // 1

o2.b = 2;
o1.b // 2

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

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

1
2
3
4
5
var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}

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

但是,这种引用只局限于对象,对于原始类型的数据则是传值引用,也就是说,都是值的拷贝。

1
2
3
4
5
var x = 1;
var y = x;

x = 2;
y // 1

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

in运算符

  1. in操作符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false
1
2
var o = { p: 1 };
'p' in o // true
  1. 在JavaScript语言中,所有全局变量都是顶层对象(浏览器的顶层对象就是window对象)的属性,因此可以用in运算符判断,一个全局变量是否存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 假设变量x未定义

// 写法一:报错
if (x) { return 1; }

// 写法二:不正确
if (window.x) { return 1; }

// 写法三:正确
if ('x' in window) { return 1; }
if ( x in window) { return 1; } // 注意x要用引号包括起来,是字符串

// 写法四: 正确
if (window.hasOwnProperty('x')) { return 1; } // 注意x要用引号包括起来,是字符串

上面三种写法之中,如果x不存在,第一种写法会报错;如果x的值对应布尔值false(比如x等于空字符串),第二种写法无法得到正确结果;只有第三种、第四种写法,才能正确判断变量x是否存在。

in运算符的一个问题是,它不能区分对象继承的属性。

1
2
3
4
var o = new Object();
o.hasOwnProperty('toString') // false

'toString' in o // true

上面代码中,toString方法不是对象o自身的属性,而是继承的属性,hasOwnProperty方法可以说明这一点。但是,in运算符不能区分,对继承的属性也返回true

  1. in的右边必须是一个对象

    1
    2
    3
    4
    var color1 = new String("green");
    "length" in color1 // return true
    var color2 = "coral";
    "length" in color2 // Uncaught TypeError: Cannot use 'in' operator to search for 'length' in coral(…)
  2. 检验数组指定角标是否越界。

    1
    2
    3
    4
    5
    6
    var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
    0 in trees // returns true
    3 in trees // returns true
    6 in trees // returns false
    "bay" in trees // returns false (you must specify the index number, not the value at that index)
    "length" in trees // returns true (length is an Array property)
  3. 如果你使用delete操作符删除了一个属性,再次用in检查时,会返回false,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var mycar = {make: "Honda", model: "Accord", year: 1998};
    mycar.make = undefined;
    "make" in mycar; // return true
    delete mycar.Accord;
    "Accord" in mycar; // return false


    var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
    trees[3] = undefined;
    3 in trees; // return true
    delete trees[2];
    2 in trees; // return false
  4. 如果一个属性是从原型链上继承来的,in 运算符也会返回 true。

    1
    "toString" in {}; // 返回true

for…in循环

for…in 循环不遍历不可枚举属性。使用内建构造器例如 Array 和 Object 创建的对象拥有从 Object.prototype 和 String.prototype 继承的不可枚举属性,例如 String 的 indexOf() 方法或者 Object 的 toString 方法。循环将迭代对象的所有可枚举属性,包括从它的构造函数的 prototype 继承而来的(包括被覆盖的内建属性)。
删除,添加或者修改属性
for…in 循环以任意序迭代一个对象的属性。通常,在迭代过程中最好不要在对象上进行添加、修改或者删除属性的操作,除非是对当前正在被访问的属性。这里并不保证是否一个被添加的属性在迭代过程中会被访问到,不保证一个修改后的属性(除非是正在被访问的)会在修改前或者修改后被访问,不保证一个被删除的属性将会在它被删除之前被访问。

Array 迭代和 for…in
数组索引仅是可枚举的整数名,其他方面和别的普通对象属性没有什么区别。for…in 并不能够保证返回的是按一定顺序的索引,但是它会返回所有可枚举属性,包括非整数名称的和继承的。因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。 因此当迭代那些访问次序重要的 arrays 时用整数索引去进行 for 循环 (或者使用 Array.prototype.forEach() 或 for…of 循环) 。

仅迭代自身的属性
如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames()(自身的可枚举和不可枚举属性都能获得) 或执行 hasOwnProperty() 来确定某属性是否是对象本身的属性 (也能使用propertyIsEnumerable)。另外,如果你知道外部不存在任何的干扰代码,你可以扩展内置原型与检查方法。

1
2
3
4
5
6
7
8
var o = {a: 1, b: 2, c: 3};

for (i in o){
console.log(o[i]);
}
// 1
// 2
// 3

下面是一个使用for...in循环,进行数组赋值的例子。

1
2
3
4
5
var props = [], i = 0;

for (props[i++] in {x: 1, y: 2});//循环执行的时候会给每一个变量(`props[i++]`)赋`{x: 1, y: 2}`的属性值。

props // ['x', 'y']

注意,for...in循环遍历的是对象所有可enumberable的属性(属性特性[[enumberable]]true),其中不仅包括定义在对象本身的属性,还包括对象继承的属性。

属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// name 是 Person 本身的属性
function Person(name) {
this.name = name;
}

// describe是Person.prototype的属性
Person.prototype.describe = function () {
return 'Name: '+this.name;
};

var person = new Person('Jane');

// for...in循环会遍历实例自身的属性(name),
// 以及继承的属性(describe)
for (var key in person) {
console.log(key);
}
// name
// describe

上面代码中,name是对象本身的属性,describe是对象继承的属性,for...in循环的遍历会包括这两者。

如果只想遍历对象本身的属性,可以使用hasOwnProperty方法,在循环内部做一个判断。

1
2
3
4
5
6
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name

为了避免这一点,可以新建一个继承null的对象。由于null没有任何属性,所以新对象也就不会有继承的属性了。

with语句

with语句的格式如下:

1
2
3

with (object)
statement

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// 例一
with (o) {
p1 = 1;
p2 = 2;
}

// 等同于

o.p1 = 1;
o.p2 = 2;

// 例二
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区块内部的变量,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。这是因为with区块没有改变作用域,它的内部依然是当前作用域。

1
2
3
4
5
6
7
8
9
10
11
12

var o = {};

with (o){
x = "abc";
}

o.x
// undefined

x
// "abc"

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

1
2
3
4
5
6
7
8
9
10
11

var o = {};

o.x = 1;

with (o){
x = 2;
}

o.x
// 2

这是with语句的一个很大的弊病,就是绑定对象不明确。

1
2
3
4

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

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

1
2
3
4
5
6
7
8
9

with(o1.o2.o3) {
console.log(p1 + p2);
}

// 可以写成

var temp = o1.o2.o3;
console.log(temp.p1 + temp.p2);

with语句少数有用场合之一,就是替换模板变量。

1
var str = 'Hello <%= name %>!';

上面代码是一个模板字符串,为了替换其中的变量name,可以先将其分解成三部分'Hello ', name, '!',然后进行模板变量替换。

1
2
3
4
5
6
7
8
9
10
11
12
13

var o = {
name: 'Alice'
};

var p = [];
var tmpl = '';

with(o){
p.push('Hello ', name, '!');
};

p.join('') // "Hello Alice!"

上面代码中,with区块内部,模板变量name可以被对象o的属性替换,而p依然是全局变量。事实上,这就是很多模板引擎的实现原理。

Object

概述

JavaScript原生提供一个Object对象(注意起首的O是大写),所有其他对象都继承自这个对象。Object本身也是一个构造函数,可以直接通过它来生成新对象。
Object 的每个实例也就是Object原型 prototype 具有下列属性和方法:

  • constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是Object()。
  • hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty(“name”))。
  • isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第5章将讨论原型)。
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in 语句(本章后面将会讨论)来枚举。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。
    propertyIsEnumerable()方法的返回值为Boolean类型,该值指示指定属性是否为对象的一部分以及该属性是否是可枚举的(只有这两个条件同时满足才返回true)。如果propertyName存在于object中且可以使用for…in循环遍历出来,则propertyIsEnumerable()方法将返回true。如果object不具有所指定名称的属性或者所指定的属性不是可枚举的,则propertyIsEnumerable()方法将返回false
    通常,预定义的属性不是可枚举的,而用户定义的属性总是可枚举的。不过有些属性虽然可以通过for...in循环遍历到,但因为它们不是自身属性,而是从原型链上继承的属性,所以该方法也会返回false
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。
  • __defineGetter__()
  • __defineSetter__()
  • __lookupGetter__()
  • __lookupSetter__()
  • get __proto__() //不可被显示调用(.出来)
  • set __proto__() //不可被显示调用(.出来)

还有一些其他方法据各个浏览器厂商的实现来定,比如firefox还有toSource()watch()unwatch(),这些内容不做过多介绍,毕竟不是标准。后面将会对这些方法做详细介绍。
Object作为构造函数使用时,可以接受一个参数。如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象。

1
2
3
4
5
6
var o1 = {a: 1};
var o2 = new Object(o1);
o1 === o2 // true

new Object(123) instanceof Number
// true

注意,通过new Object()的写法生成新对象,与字面量的写法o = {}是等价的,但是通过对象字面量定义对象时,实际上不会调用Object 构造函数。

与其他构造函数一样,如果要在Object对象上面部署一个方法,有两种做法:

  1. 部署在Object对象本身
    比如,在Object对象上面定义一个print方法,显示其他对象的内容。

    1
    2
    3
    4
    5
    6
    Object.print = function(o){ console.log(o) };

    var o = new Object();

    Object.print(o)
    // Object
  2. 部署在Object.prototype对象
    所有构造函数都有一个prototype属性,指向一个原型对象。凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享。

    1
    2
    3
    4
    5
    Object.prototype.print = function(){ console.log(this)};

    var o = new Object();

    o.print() // Object

上面代码在Object.prototype定义了一个print方法,然后生成一个Object的实例o。o直接继承了Object.prototype的属性和方法,可以在自身调用它们,也就是说,o对象的print方法实质上是调用Object.prototype.print方法。
可以看到,尽管上面两种写法的print方法功能相同,但是用法是不一样的,因此必须区分“构造函数的方法”和“实例对象的方法”。

Object()

Object本身当作工具方法使用时,可以将任意值转为对象。这个方法常用于保证某个值一定是对象。如果参数是原始类型的值,Object方法返回对应的包装对象的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Object() // 返回一个空对象
Object() instanceof Object // true

Object(undefined) // 返回一个空对象
Object(undefined) instanceof Object // true

Object(null) // 返回一个空对象
Object(null) instanceof Object // true

Object(1) // 等同于 new Number(1)
Object(1) instanceof Object // true
Object(1) instanceof Number // true

Object('foo') // 等同于 new String('foo')
Object('foo') instanceof Object // true
Object('foo') instanceof String // true

Object(true) // 等同于 new Boolean(true)
Object(true) instanceof Object // true
Object(true) instanceof Boolean // true

如果Object方法的参数是一个对象,它总是返回原对象。

1
2
3
4
5
6
7
8
9
10
11
var arr = [];
Object(arr) // 返回原数组
Object(arr) === arr // true

var obj = {};
Object(obj) // 返回原对象
Object(obj) === obj // true

var fn = function () {};
Object(fn) // 返回原函数
Object(fn) === fn // true

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

1
2
3
4
5
6
function isObject(value) {
return value === Object(value);
}

isObject([]) // true
isObject(true) // false

Object 对象的静态方法

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

Object.keys(),Object.getOwnPropertyNames()

Object.keys方法和Object.getOwnPropertyNames方法很相似,一般用来遍历对象的属性。它们的参数都是一个对象,都返回一个数组,该数组的成员都是对象自身的(不包含prototype)所有属性名。它们的区别在于,Object.keys方法只返回可枚举的属性(关于可枚举性的详细解释见后文),Object.getOwnPropertyNames方法还返回不可枚举的属性名。
上面的代码表示,对于一般的对象来说,这两个方法返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。

1
2
3
4
5
6
7
var a = ["Hello", "World"];

Object.keys(a)
// ["0", "1"]

Object.getOwnPropertyNames(a)
// ["0", "1", "length"]

上面代码中,数组的length属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames方法的返回结果中。
由于JavaScript没有提供计算对象属性个数的方法,所以可以用这两个方法代替。

1
2
Object.keys(o).length
Object.getOwnPropertyNames(o).length

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

其他方法

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

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

    1
    2
    3
    4
    Object.getOwnPropertyDescriptor():获取某个属性的attributes对象。
    Object.defineProperty():通过attributes对象,定义某个属性。
    Object.defineProperties():通过attributes对象,定义多个属性。
    Object.getOwnPropertyNames():返回直接定义在某个对象上面的全部属性的名称。
  2. 控制对象状态的方法

    1
    2
    3
    4
    5
    6
    Object.preventExtensions():防止对象扩展。
    Object.isExtensible():判断对象是否可扩展。
    Object.seal():禁止对象配置。
    Object.isSealed():判断一个对象是否可配置。
    Object.freeze():冻结一个对象。
    Object.isFrozen():判断一个对象是否被冻结。
  3. 原型链相关方法

    1
    2
    Object.create():创建一个拥有指定原型和若干个指定属性的对象。
    Object.getPrototypeOf():获取对象的Prototype对象。

Object对象的实例方法

除了Object对象本身的方法,还有不少方法是部署在Object.prototype对象上的,所有Object的实例对象都继承了这些方法。
Object实例对象的方法,主要有以下六个。

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

    Object.prototype.valueOf()

    valueOf方法的作用是返回一个对象的“值”,默认情况下返回对象本身。
    1
    2
    var o = new Object();
    o.valueOf() === o // true

上面代码比较o.valueOf()o本身,两者是一样的。valueOf方法的主要用途是,JavaScript自动类型转换时会默认调用这个方法。

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

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

1
2
3
4
5
6
var o = new Object();
o.valueOf = function (){
return 2;
};

1 + o // 3

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

Object.prototype.toString()

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

1
2
3
4
5
var o1 = new Object();
o1.toString() // "[object Object]"

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

上面代码表示,对于一个对象调用toString方法,会返回字符串[object Object],该字符串说明对象的类型。字符串[object Object]本身没有太大的用处,但是通过自定义toString方法,可以让对象在自动类型转换时,得到想要的字符串形式。

1
2
3
4
5
6
7
var o = new Object();

o.toString = function () {
return 'hello';
};

o + ' ' + 'world' // "hello world"

上面代码表示,当对象用于字符串加法时,会自动调用toString方法。由于自定义了toString方法,所以返回字符串hello world。
数组、字符串、函数、Date对象都分别部署了自己版本的toString方法,覆盖了Object.prototype.toString方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
[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)"

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

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

1
2
var o = {};
o.toString() // "[object Object]"

上面代码调用空对象的toString方法,结果返回一个字符串"object Object",其中第二个Object表示该值的构造函数。这是一个十分有用的判断数据类型的方法。实例对象可能会自定义toString方法,覆盖掉Object.prototype.toString方法。通过函数的call方法,可以在任意值上调用Object.prototype.toString方法,帮助我们判断这个值的类型。

1
Object.prototype.toString.call(value)

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

1
2
3
4
5
6
7
8
9
10
11
12
数值:返回[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.prototype.toString可以得到一个实例对象的构造函数。

1
2
3
4
5
6
7
8
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运算符更准确的类型判断函数。

1
2
3
4
5
6
7
8
9
10
11
12
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函数的基础上,还可以加上专门判断某种类型数据的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
['Null',
'Undefined',
'Object',
'Array',
'String',
'Number',
'Boolean',
'Function',
'RegExp',
'NaN',
'Infinite'
].forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase();
};
});

type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true

阮一峰

文章目录
  1. 1. 概述
    1. 1.1. 定义方法
    2. 1.2. 键名
    3. 1.3. 属性
    4. 1.4. 生成方法
    5. 1.5. 读写属性
    6. 1.6. 属性的删除
      1. 1.6.1. 以下这些都不能delete
    7. 1.7. 对象的引用
    8. 1.8. in运算符
    9. 1.9. for…in循环
  2. 2. with语句
  3. 3. Object
    1. 3.1. 概述
    2. 3.2. Object()
    3. 3.3. Object 对象的静态方法
      1. 3.3.1. Object.keys(),Object.getOwnPropertyNames()
      2. 3.3.2. 其他方法
    4. 3.4. Object对象的实例方法
      1. 3.4.1. Object.prototype.valueOf()
      2. 3.4.2. Object.prototype.toString()
      3. 3.4.3. toString()的应用:判断数据类型