3.1 语法
定义对象的方式
- 声明形式
- 构造形式
//对象的文字语法大概是这样:
var myObj = {
key: value
// ...
};
//构造形式大概是这样:
var myObj = new Object();
myObj.key = value;
3.2 类型
在 JavaScript 中一共有六种主要类型
- string
- number
- boolean
- null
- undefined
- object
**简单基本类型 **
string、boolean、number、null 和 undefined
复杂基本类型
多特殊的对象子类型,比如函数、数组、内置对象
内置对象
这些内置对象从表现形式来说很像其他语言中的类型(type)或者类(class),在 JavaScript 中,它们实际上只是一些内置函数。
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
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
// 检查 sub-type 对象
console.log(Object.prototype.toString.call(strObject)); // [object String]
- 原始值 "I am a string" 并不是一个对象,它只是一个字面量,并且是一个不可变的值。
- 语言会自动把字符串字面量转换成一个 String 对象,可以访问属性和方法,比如获取长度、访问其中某个字符等
数字字面量也会自动转成Number对象,比如调用3.1415.toFixed(2)
3.3 内容
对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。
var myObject = {
a: 2
};
myObject.a; // 2
myObject["a"]; // 2
- 要访问 myObject 中 a 位置上的值,我们需要使用
. 操作符或者[] 操作符 . 操作符称为属性访问[] 操作符称为键访问- 区别在于属性访问要求属性名必须满足标识符的命名规范,而键访问可以访问任意字符串名字的属性
var myObjet = { a: 2 }
console.log(myObjet.a);//2
console.log(myObjet['a']);//2
myObjet['!SUPER-PROPERTY!'] = "test"
console.log(myObjet['!SUPER-PROPERTY!']);//test
属性名永远是字符串,即使你使用其他类型的值,也会被转换成字符串。
var myObjet = {}
myObjet[true] = 'foo'
myObjet[3] = 'bar'
myObjet[myObjet] = 'baz'
console.log(myObjet[true], myObjet['true']);//foo
console.log(myObjet[3], myObjet['3']);//bar
console.log(myObjet[myObjet], myObjet['[object Object]']);//baz
console.log(myObjet);//{ 'true': 'foo', 3: 'bar', [object Object]: 'baz' }
3.3.1 可计算属性名
ES6 增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名
var prefix = "foo";
var myObjet = {
[prefix + 'bar']: "hello",
[prefix + 'baz']: "world"
}
console.log(myObjet['foobar'], myObjet['foobaz']);//hello world
console.log(myObjet);//{foobar: 'hello', foobaz: 'world'}
可计算属性名最常用的场景可能是 ES6 的符号(Symbol)
const mySymbol = Symbol("demokey");
var myObject = {
[mySymbol]: "hello world"
}
console.log(myObject[mySymbol]);//hello world
console.log(myObject);//{Symbol(demokey): 'hello world'}
let newKey = Symbol("demokey");
console.log(myObject[newKey]);//undefined
3.3.3 数组
数组支持数值下标访问,也是支持键访问
var myArray = ["foo", 42, "bar"];
console.log(myArray.length); // 3
console.log(myArray[0]); // "foo"
console.log(myArray[2]); // "bar"
var myArray = ["foo", 42, "bar"];
myArray.baz = "baz";
console.log(myArray.length); // 3
console.log(myArray.baz); // "baz"
- 数组添加了命名属性(无论是通过
. 语法还是[] 语法),数组的 length 值并未发生变化。
3.3.4 复制对象
深复制的方式
var newObj = JSON.parse( JSON.stringify( someObj ) );
3.3.5 属性描述符
从 ES5 开始,所有的属性都具备了属性描述符,可以直接检测属性特性
var myObject = {
a: 2
};
console.log(Object.getOwnPropertyDescriptor(myObject, "a"));
//{
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
//}
属性描述符,不仅仅只是保存一个数据值,还包含另外三个特性
- writable(可写),是否可以修改属性的值
- enumerable(可枚举),控制的是属性是否会出现在对象的属性枚举中,比如for-in循环,为false时,不会出现在循环中,但是可以访问
- configurable(可配置),为true是可配置的,就可以使用 defineProperty(..) 方法来修改属性描述符,为false时,不能修改,也不能删除
创建普通属性 使用 Object.defineProperty(..),来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置
var myObject = {};
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
enumerable: true,
configurable: true
});
console.log(myObject.a);
不可写、不可配置、不可枚举示例
// 非严格模式
var notWriteable = {};
Object.defineProperty(notWriteable, "a", {
value: 2,
writable: false,
enumerable: true,
configurable: true
});
notWriteable.a = 3;
console.log(notWriteable.a);//2
"use strict";
var notWriteable = {};
Object.defineProperty(notWriteable, "a", {
value: 2,
writable: false,
enumerable: true,
configurable: true
});
notWriteable.a = 3;//TypeError: Cannot assign to read only property 'a' of object '#<Object>'
var notConfigurable = {
a: 2
};
Object.defineProperty(notConfigurable, "a", {
value: 3,
configurable: false,
});
console.log(notConfigurable.a);//3
notConfigurable.a = 4;
console.log(notConfigurable.a);//4
Object.defineProperty(notConfigurable, "a", { // Cannot redefine property: a
configurable: true,
});
var notEnumerable = { a: 1 };
Object.defineProperty(notEnumerable, "b", {
value: 2,
enumerable: false,
});
console.log(notEnumerable.a, notEnumerable.b)//1,2
for (let key in notEnumerable) {
console.log(key);//a
}
3.3.6 不变性
希望属性或者对象是不可改变
对象常量
- 结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、重定义或者删除)
var myObject = {};
Object.defineProperty(myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
});
myObject.FAVORITE_NUMBER = 33; //改动不成功,严格模式下会报错
console.log(myObject.FAVORITE_NUMBER); //42
禁止扩展
Object.preventExtensions,禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性
var myObject = {
a: 2
};
Object.preventExtensions(myObject);
myObject.b = 3;
console.log(myObject.b); // undefined
console.log(myObject); // {a: 2}
密封
Object.seal(..),密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。
- 这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false
冻结 Object.freeze(..) 会创建一个冻结对象
- 实际上会在一个现有对象上调用Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。
3.3.9 Getter和Setter
对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取
- getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏函数,会在设置属性值时调用。
- 给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。
var myObject = {
get a() {
return 2;
}
}
console.log(myObject.a);//2
Object.defineProperty(myObject, "b", {
get: function () {
return this.a + 1;
},
enumerable: true
});
console.log(myObject.b);//3
myObject.a = 100; //赋值失败,因为没有set a方法
console.log(myObject.a);//2
var myObject = {
__c__: 100,
get c() {
return this.__c__;
},
set c(value) {
this.__c__ = value;
}
}
console.log(myObject.c);//100
myObject.c = 999;//Setting c to 999
console.log(myObject.c);//999
3.3.10 存在性
在不访问属性值的情况下判断对象中是否存在这个属性
- in 操作符会检查属性是否在对象及其
[[Prototype]]原型链中 - hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链
var myObject = {
a: 2,
}
console.log('a' in myObject)//true
console.log('b' in myObject)//false
console.log(myObject.hasOwnProperty("a"));//true
console.log(myObject.hasOwnProperty("b"));//false
正常创建的对象都可以使用hasOwnProperty方法,但是如果是通过 Object.create(null) 来创建的对象,原型并没有委托给Object.prototype,所以不能调用hasOwnProperty方法。
这时候,可以通过显示绑定的方法来调用
Object.prototype.hasOwnProperty.call(myObject,"a")
in 操作符的迷惑性问题 in 操作符实际上检查的是某个属性名是否存在。 所以在数组中,in检查的是索引下标字符串是否在,而不是里面的原始值
const arr = [2, 4, 6]
console.log(4 in arr);//false
// in 遍历的都是属性
for (let a in arr) {
console.log(a, arr[a]);
// 0 2
// 1 4
// 2 6
}
枚举
- propertyIsEnumerable(..) 会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true
- Object.keys(..) 会返回一个数组,包含所有可枚举属性,只会查找对象直接包含的属性
- Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举,只会查找对象直接包含的属性
- 获取所有的属性,包括原型链上的,需要自行实现
var myObject = { a: 2 }
Object.defineProperty(myObject, 'b', {
value: 2,
writable: true,
enumerable: false,
configurable: true
})
console.log(myObject.propertyIsEnumerable('a')); //true
console.log(myObject.propertyIsEnumerable('b')); //false
console.log(Object.keys(myObject))//['a']
console.log(Object.getOwnPropertyNames(myObject))//['a', 'b']
3.4 遍历
- for..in 循环可以用来遍历对象的可枚举属性列表(包括
[[Prototype]]链)。 - 数组的迭代器方法
- forEach(..)、every(..) 和 some(..)。
- ES6 增加遍历数组的 for..of 循环语法
- 内部使用迭代器对象来遍历数组
const arr = [2, 4, 6]
for (let a in arr) {
console.log(a, arr[a]);
//0 2
//1 4
//2 6
}
for (let a of arr) {
console.log(a);//2,4,6
}
const arr = [2, 4, 6]
var iterator = arr[Symbol.iterator]();
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: 4, done: false}
console.log(iterator.next());//{value: 5, done: false}
console.log(iterator.next());//{value: undefined, done: true}
自定义迭代器
给对象添加一个 Symbol.iterator属性
var myObject = {
a: 2,
b: 3
};
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function () {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next: function () {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
});
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }
// 用 for..of 遍历 myObject
for (var v of myObject) {
console.log(v);
}
// 2
// 3
无限随机数示例
var randoms = {
[Symbol.iterator]: function () {
return {
next: function () {
return { value: Math.random() };
}
};
}
};
var randoms_pool = [];
for (var n of randoms) {
randoms_pool.push(n);
// 防止无限运行!
if (randoms_pool.length === 100) break;
}
console.log(randoms_pool.length);//100