(一)语法
对象可以通过两种形式定义:
1. 声明(文字)形式
// 常用
var myObj = {
key: value
// ...
};
2. 构造形式
// 用“构造形式”来创建对象非常少见
var myObj = new Object();
myObj.key = value;
(二)类型
1. JavaScript
语言类型
console.log(typeof null); // "object"
// null 有时会被当作一种对象类型,但是这其实只是语言本身的一个bug。
// 实际上,null 本身是基本类型。
2. JavaScript
内置对象
在JavaScript
中,内置函数实际上只是一些内置函数。这些内置函数可以当作构造函数(由new
产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。
// 字面量形式
var strPrimitive = "I am a string";
console.log(typeof strPrimitive); // "string"
console.log(strPrimitive instanceof String); // false
// 构造形式
var strObject = new String( "I am a string" );
console.log(typeof strObject); // "object"
console.log(strObject instanceof String); // true
// 字面量形式
var arrPrimitive = [];
console.log(typeof arrPrimitive); // "object"
console.log(arrPrimitive instanceof Array); // true
// 构造形式
var arrObject = new Array();
console.log(typeof arrObject); // "object"
console.log(arrObject instanceof Array); // true
有关对象更详细的介绍可查看《【JS基础】(十三)JavaScript三大对象两类属性》。
(三)内容
对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。
存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置。
1. 属性访问
-
.
操作符: “属性访问” -
[]
操作符: “键访问”
这两种语法的主要区别在于:.
操作符要求属性名满足标识符的命名规范,而[]
语法可以接受任意UTF-8/Unicode
字符串作为属性名。当属性名为变量时(或者是表达式),只能使用[]
操作符。
var myObject = {
a: 2
};
myObject.a; // 2
myObject["a"]; // 2
在对象中,属性名永远都是字符串。如果你使用string
(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。
var myObject = { };
myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";
console.log(myObject["true"]); // "foo"
console.log(myObject["3"]); // "bar"
console.log(myObject["[object Object]"]); // "baz"
2. 属性与方法
从技术角度来说,函数永远不会“属于”一个对象。
无论返回值是什么类型,每次访问对象的属性就是属性访问。如果属性访问返回的是一个函数,那它也并不是一个“方法”。属性访问返回的函数和其他函数没有任何区别(除了可能发生的隐式绑定this
)。
function foo() {
console.log( "foo" );
}
var someFoo = foo; // 对foo 的变量引用
var myObject = {
someFoo: foo // 引用,不是复本!
};
console.log(foo); // function foo(){..}
console.log(someFoo); // function foo(){..}
console.log(myObject.someFoo); // function foo(){..}
console.log(foo === someFoo ); // true
console.log(foo === myObject.someFoo ); // true
console.log(someFoo === myObject.someFoo ); // true
// someFoo 和myObject.someFoo只是对于同一个函数的不同引用,
// 并不能说明这个函数是特别的或者“属于”某个对象。
// 如果foo() 定义时在内部有一个this引用,
// 那这两个函数引用的唯一区别就是myObject.someFoo 中的this 会被隐式绑定到一个对象。
// 无论哪种引用形式都不能称之为“方法”。
即使你在对象的文字形式中声明一个函数表达式,这个函数也不会“属于”这个对象——
它们只是对于相同函数对象的多个引用。
var myObject = {
foo: function() { // 函数表达式
console.log( "foo" );
}
};
var someFoo = myObject.foo;
console.log(someFoo); // function foo(){..}
console.log(myObject.foo); // function foo(){..}
3. 复制对象
对对象使用=
操作符进行的是浅复制。
function anotherFunction() { /*..*/ }
var anotherObject = {
c: true
};
var anotherArray = [];
var myObject = {
a: 2,
b: anotherObject, // 引用,不是复本!
c: anotherArray, // 另一个引用!
d: anotherFunction
};
(1) 对于JSON
安全(也就是说可以被序列化为一个JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法(这种方法算是深复制):
var newObj = JSON.parse( JSON.stringify( someObj ) );
(2) ES6
定义了Object.assign()
方法来实现浅复制。该方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举的自有键并把它们复制(使用 =
操作符赋值)到目标对象,最后返回目标对象。
var newObj = Object.assign( {}, myObject );
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true
4. 属性描述符
var myObject = {
a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {value: 2, writable: true, enumerable: true, configurable: true}
观察以上代码,对象属性对应的属性描述符(也被称为“数据描述符”,因为它只保存一个数据值)可不仅仅只是一个2
。它还包含另外三个特性:
-
writable
(可写) -
enumerable
(可枚举) -
configurable
(可配置)
也可以使用Object.defineProperty()
来添加一个新属性或者修改一个已有属性(如果它是configurable
)并对特性进行设置。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
myObject.a; // 2
(1) Writable
writable
决定是否可以修改属性的值。
var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 不可写!
configurable: true,
enumerable: true
} );
myObject.a = 3;
myObject.a; // 2
(2) Configurable
Configurable
决定属性是否可配置。
- 只要属性是可配置的,就可以使用
defineProperty()
方法来修改属性描述符; - 若属性是不可配置的,那该属性也不被
delete
删除; - 要注意有一个小小的例外:即便属性是
configurable:false
,我们还是可以
把writable
的状态由true
改为false
,但是无法由false
改为true
。
var myObject = {
a:2
};
myObject.a = 3;
myObject.a; // 3
Object.defineProperty( myObject, "a", {
value: 4,
writable: true,
configurable: false, // 不可配置!
enumerable: true
} );
myObject.a; // 4
myObject.a = 5;
myObject.a; // 5,(表明writable: true的情况下,属性的值可以修改)
Object.defineProperty( myObject, "a", {
value: 6,
writable: true,
configurable: true,
enumerable: true
} ); // TypeError
(3) Enumerable
Enumerable
控制属性是否会出现在对象的属性枚举中,比如说for..in
循环。
如果把enumerable
设置成false
,这个属性就不会出现在枚举中,虽然仍然可以正常访问它。
5. 不变性
多种方法实现属性或者对象不可改变:
(1) 对象常量
结合writable:false
和configurable:false
就可以创建一个真正的常量属性(不可修改、重定义或者删除)
var myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );
(2) 禁止扩展
var myObject = {
a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
(3) 密封
Object.seal()
会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用
Object.preventExtensions()
并把所有现有属性标记为configurable:false
。
所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(但可以
修改属性的值)。
(4) 冻结
Object.freeze()
会创建一个冻结对象,这个方法实际上会在一个现有对象上调用
Object.seal()
并把所有“数据访问”属性标记为writable:false
,这样就无法修改它们的值。
6. Getter
和Setter
对象默认的[[Put]]
和[[Get]]
操作分别可以控制属性值的设置和获取。
(1) [[Get]]
: 在访问属性使用.
或[]
操作符时,实际上是实现了[[Get]]
操作。
对象默认的内置[[Get]]
操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。然而,如果没有找到名称相同的属性,则去其[[Prototype]]
链上找。如果无论如何都没有找到名称相同的属性,那[[Get]]
操作会返回值undefined
。
(2)[[Put]]
:如果已经存在这个属性,[[Put]]
算法大致会检查下面这些内容
- 属性是否是访问描述符?如果是并且存在
setter
就调用setter
。 - 属性的数据描述符中
writable
是否是false
?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError
异常。 - 如果都不是,将该值设置为属性的值。
访问描述符
当你给一个属性定义getter
、setter
或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript
会忽略它们的value
和writable
特性,取而代之的是关心set
和get
(还有configurable
和enumerable
)特性。
var myObject = {
// 给 a 定义一个getter
get a() {
return this._a_;
},
// 给 a 定义一个setter
set a(val) {
this._a_ = val * 2;
}
};
myObject.a = 2;
myObject.a; // 4
7. 存在性
在不访问属性值的情况下判断对象中是否存在这个属性:
var myObject = {
a:2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
-
in
操作符会检查属性是否在对象及其[[Prototype]]
原型链中; -
hasOwnProperty()
只会检查属性是否在myObject
对象中,不会检查[[Prototype]]
链。 -
propertyIsEnumerable()
会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足enumerable:true
。 -
Object.keys()
会返回一个数组,包含所有可枚举属性; -
Object.getOwnPropertyNames()
会返回一个数组,包含所有属性,无论它们是否可枚举。
(四)遍历
1. for...in
for...in
语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。每次迭代时,分配的是属性名。
let array2 = ['a','b','c']
let obj1 = {
name : 'lei',
age : '16'
}
for(variable in array2){ //variable 为 index
console.log(variable ) //0 1 2
}
for(variable in obj1){ //variable 为属性名
console.log(variable) //name age
}
2. for...of
for...of
会遍历具有iterator
接口的数据结构,遍历(当前对象上的)每一个属性值。
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
let iterable = [3, 5, 7];
iterable.foo = "hello";
for (let i in iterable) {
console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i of iterable) {
console.log(i); // 3, 5, 7
}
有关数组对象的更多遍历的方法,可查看《【ES6】操作数组的常用方法有这些就够了》。