创建 Object 实例
Ojbect 的实例称之为对象,对象内的有属性和方法。
创建对象实例的方法有 5 种:
// 语法糖创建
let obj1 = { a: 1 }
// Object 构造函数创建 空对象
let obj2 = new Object()
obj2.a = 1;
// 自定义构造函数 创建对象
function Creater() {
this.a = 1
}
let obj3 = new Creater()
// 通过复制一个或多个对象来创建一个新的对象
let obj4 = Object.assign({}, obj1);
// 使用指定的原型对象和属性创建一个新对象
// 注意,obj5为空对象,但可以通过原型链访问到obj1的属性
let obj5 = Object.create(obj1);
其中,obj3/obj5 都与原型链有关,更多关于原型链可查看另一篇文章。下面简单介绍 "Object.assign()" 和 "Object.create()" :
- Object.assign(target, ...source):通过复制一个或多个对象来创建一个新的对象。
- 目标对象会被改变;
- 此方法属于浅拷贝;
- Object.create(proto, [propertiesObject]):使用指定的原型对象和属性创建一个新对象。
"proto":是新创建对象的原型对象;"propertiesObject":可选,新对象的属性的特性;返回值:一个继承原型对象的新对象。
对象的属性
添加 与 删除 属性
添加或删除属性,均有两种方法:"点" 和 "大括号" 。如下为示例代码:
let person = { name: 'zhangsan' }
// 通过.操作符为对象添加属性
person.age = 20; // { name: 'zhangsan', age: 20 }
// 通过[]操作符为对象添加属性
person["weight"] = 100; // { name: 'zhangsan', age: 20, weight: 100 }
// 删除属性,同样可以使用.操作和[]操作
delete person.weight;
delete person["age"];
检测属性是否属于某对象
对象的属性可以分为两种:自身的、继承于原型的。
假设有以下两个对象:
// son 继承自 obj ,且 son 上有属性 b ,而 parent 上有属性 a
let parent = { a: 1 }
let son = Object.create(parent);
son.b = 2;
- "in" 操作符,检测指定对象及其原型链上是否有该属性。
console.log('a' in son); // true
console.log('b' in son); // true
console.log('c' in son); // false
- 实例对象上的 "hasOwnProperty()" 方法,检测是否含有某个私有属性。
console.log(son.hasOwnProperty('a')); // false
console.log(son.hasOwnProperty('b')); // true
遍历
当我们需要对对象内所有属性作相同操作,或获取对象内所有属性,则需要遍历实现。遍历同样面临两个问题:只是遍历对象自身属性,还是及其原型链上的属性。
同样是先给定示例对象:
let parent = { a: 1, b: 2 }
let son = Object.create(parent);
son.x = 10;
son.y = 20;
- Object.keys(obj):返回一个包含所有给定对象自身可枚举属性名称的数组。
// 只返回对象自身的属性的键名的集合
console.log(Object.keys(son)); // [ 'x', 'y' ]
还有与Object.keys(obj)
对应的方法:Object.values()
,不同的是Object.values()
返回属性值集合。
- "for-in" 运算符:遍历给定对象自身及其原型链上可枚举属性名称的数组
// 获取对象及其原型链上的属性的键名
for(let prop in son){
console.log(prop)
}
// x
// y
// a
// b
- Object.entries():返回给定对象自身可枚举属性的 [key, value] 数组。
console.log(Object.entries(son)); // [ [ 'x', 10 ], [ 'y', 20 ] ]
这种数据结构很方便的构造一个 "Map" 对象,关于 "Map" 对象请点击查看更多。
深拷贝
由于 JavaScript 中的对象是引用类型,意味着直接赋值只能得到对象的指针,而并非创新一个新的对象副本。这情况会导致任意引用修改对象的值,其他引用也会发生变化。
上面提到的Object.assign()
是浅拷贝,下面示例代码:
let obj = { a: 1, b: { x: 1, y: 2 } }
// 浅克隆
let objClone = Object.assign(obj);
// objClone.b 对象的修改会导致 obj.b 对象也发生变化,因为两者引用同一个对象
objClone.b.x = 100;
console.log(obj.b.x); // 100
自定义深拷贝函数
下面函数原理是遍历对象内所有属性,遇到属性的类型为对象这重复调用该函数。
function deepCopy( source ) {
let target = Array.isArray( source ) ? [] : {}
for ( var k in source ) {
if ( typeof source[ k ] === 'object' ) {
target[ k ] = deepCopy( source[ k ] )
} else {
target[ k ] = source[ k ]
}
}
return target
}
利用 "JSON" 对象的内置方法深拷贝
要注意的是,本方法有局限性,仅使用属性为数据属性。如下:
let obj = {
_a: 1,
b: {
x() { console.log(1) },
y: function () { console.log('y') },
_z: 100,
get z() { return this._z },
set z(val) { this._z = val }
}
}
// 添加访问器属性
Object.defineProperty(obj, 'a', {
get: function () { return this._a },
set(val) { this._a = val }
})
// 使用 JSON 内置方法的骚操作
let objClone = JSON.parse(JSON.stringify(obj));
console.log(objClone); // { _a: 2, b: { _z: 100, z: 100 } }
以上代码可以看出:方法、访问器属性、setter 都会被忽略,而 getter 有点特别,先获取值后变成数据属性。
属性的特性
特性表示该属性的权限,如被读、被改写、被删除、被枚举。
数据属性有 4 个特性:
- [[configurable]]:能否通过 delete 运算符删除属性,缺省为 false;
- [[enumerable]]:能否通过 for-in 循环返回属性,缺省为 false;
- [[writable]]:能否修改属性的值,缺省为 false;
- [[value]]:属性的值,缺省为 undefined。
要修改数据属性的特性,必须使用 ES5 的 Object.defineProperty() 方法。
"obj":被操作的对象;"prop":被操作的属性名,存在则修改,不存在则添加;"descriptor":包含特性的描述符对象。如下是一个简单例子:
let obj = { a: 1, b: 2 }
// 添加属性"c",给定值为4,标记为不能修改
Object.defineProperty(obj, 'c', {
value: 4,
writable: false
})
访问器属性同样有 4 个特性:
- [[configurable]]:能否通过 delete 运算符删除属性,缺省为 false;
- [[enumerable]]:能否通过 for-in 循环返回属性,缺省为 false;
- [[get]]:属性被调用时触发的函数,函数返回值为属性值,缺省为 undefined;
- [[set]]:属性被修改时触发的函数,函数的唯一参数为被修改的值,缺省为 undefined。
修改访问器属性的特性,同样使用 Object.defineProperty() 方法。使用方法不赘述。
创建访问器的语法糖:
let obj = {
_a: 1,
get a() {
return this._a
},
set a(val) {
this._a = val
}
}
批量添加数据属性或访问器属性:
- Object.defineProperties(obj, props):是 "defineProperty" 方法的批量操作。下面给出简单例子:
let obj = {};
Object.defineProperties(obj, {
'prop': {
value: 1,
writable: true
},
'getProp': {
get: function () {
return this.prop
},
set: function (val) {
this.prop = val
}
}
});
获取属性的特性:
- Object.getOwnPropertyDescriptor(obj, prop):获取指定属性的描述符。
"obj":目标对象;"prop":目标属性;属性存在,则返回描述符对象,否则为 undefined 。
简单示例:
let obj = { bar: 42 };
let d = Object.getOwnPropertyDescriptor(obj, "bar");
console.log(d); // { value: 42, writable: true, enumerable: true, configurable: true }
对象的防篡改
由于对象是引用类型数据,所以对象可能在某个被引用的地方修改,造成不可预计的问题发生。有以下几种防篡改的模式,但要注意,一旦定义为防篡改,则无法撤销。
不可扩展对象
定义:不能向对象添加新的属性和方法。
方法: Object.preventExtensions(obj),传入被操作对象,返回该对象。
简单示例:
let obj = { a: 1 }
Object.preventExtensions(obj);
obj.b = 2; // 严格模式下报错
console.log(obj.b); // undefined
判断对象是否可扩展:Object.isExtensible(obj),传入被操作对象,返回布尔值。
密封对象
定义: 不可扩展,且已有属性的[[configurable]]特性被改为 false ,即所有成员均不能被删除。
方法: Object.seal(obj),传入被操作对象,返回该对象。
简单示例:
let obj = { a: 1 }
Object.seal(obj);
delete obj.a; // 严格模式下会报错
obj.a = 2; // 属性可修改
console.log(obj.a); // 2
判断对象是否密封: Object.isSealed(obj),传入被操作对象,返回布尔值。
冻结对象
定义: 密封,且已有属性的[[writable]]特性被改为 false ,即所有成员均不能被修改。
方法: Object.freeze(obj),传入被操作对象,返回该对象。
简单示例:
let obj = { a: 1 }
Object.freeze(obj);
obj.a = 2; // 严格模式下会报错
console.log(obj.a); // 1
判断对象是否冻结: Object.isFrozen(obj),传入被操作对象,返回布尔值。