(一)语法
对象可以通过两种形式定义:
1. 声明(文字)形式
// 常用
var myObj = {
key: value
// ...
};
2. 构造形式
// 用“构造形式”来创建对象非常少见
var myObj = new Object();
myObj.key = value;
(二)类型
1. JavaScript
语言类型
JavaScript语言类型
console.log(typeof null); // "object"
// null 有时会被当作一种对象类型,但是这其实只是语言本身的一个bug。
// 实际上,null 本身是基本类型。
2. JavaScript
内置对象
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】操作数组的常用方法有这些就够了》。
网友评论