JavaScript 对象系列:
第一篇
面向对象是对现实的抽离
对象是JavaScript中一个重要概念,对象可以将多个关联的数据绑定在一起,更好的描述一个事物。
- 描述一个人:Person,具有姓名(name)、年龄(age)、身高(height)、学习(study)等等。
- 描述一辆车:Car,具有颜色(color)、速度(speed),价格(price)等等。
用对象来描述事物,更加有利于将现实的事物,抽离成代码中的某个数据结构。
JavaScript中的面向对象
JavaScript支持多种的编程范式,包括函数式编程和面向对象编程。
- JavaScript中的对象被设计成一组属性的无序集合,像一个哈希表,由key和value组成。
- key是一个标识符名称,value是任意类型,也可以是其他对象或则函数类型。
- 如果value是一个函数,一般被称为对象中的方法。
创建对象的两种方式
- 构造函数
- 字面量
// 构造函数
const obj = new Object()
obj.name = 'copyer'
obj.age = 18
// 字面量
const obj = {
name: "copyer",
age: 18,
}
对象属性的操作
三种基本操作:获取、设置、删除
- 获取
const obj = {
name: "copyer",
age: 18,
}
obj.name // 获取name属性值
obj.age // 获取age属性值
- 设置
const obj = {
name: "copyer",
age: 18,
}
obj.name = 'james' // 设置obj对象中name属性值
- 删除
const obj = {
name: "copyer",
age: 18,
}
delete obj.name // 删除obj对象中的name属性
console.log(obj.name) // undefined
对象的属性描述符
在上面定义了一个对象,对象的属性,可以删除,可以修改,那么怎么才能做到:
- 不能删除对象中的某个属性;
- 不能修改对象中的某个属性的属性值;
- 不能遍历出对象中的某个属性;
想要对对象的某个属性做出比较精确的操作控制,那么就需要使用属性描述符。
属性描述符:就是对对象的属性进行精确的控制,比如说:不能让其被修改,不能让其被删除,不能让其被遍历等一些列操作。
1、获取属性描述符
定义一个对象,添加一个属性,都有着默认的属性描述符。怎么知道呢?
Object对象的原型上中提供了两个方法:
Object.getOwnPropertyDescriptor(obj, key): 获取对象的某一个属性的属性描述信息
const obj = {
name: 'copyer',
age; 18
}
Object.getOwnPropertyDescriptor(obj, 'name') // 获取name的属性描述
/*
{
value: 'copyer',
writable: true,
enumerable: true,
configurable: true
}
*/
Object.getOwnPropertyDescriptors(obj): 获取对象的全部属性的描述信息;
const obj = {
name: 'copyer',
age; 18
}
Object.getOwnPropertyDescriptors(obj) // 加上了一个 s
/*
{
name: {
value: 'copyer',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 18,
writable: true,
enumerable: true,
configurable: true
}
}
*/
2、认识属性描述信息
属性描述信息,是一个对象,对象里面包含四个属性,这四个属性用来精确的控制该属性。
| 属性 | 作用 |
|---|---|
| value | 属性值 |
| writable | 是否可以修改属性值;true:可以修改;false:则不可以被修改; |
| enumerable | 是否可以被遍历;true: 可以被遍历;false:不可以被遍历; |
| configurable | 是否可以被配置(是否可以被删除,是否可以重新定义属性描述信息) ;true: 可以; false: 不可以; |
3、设置属性描述符
上面认识了属性描述信息的内容,那么就可以对里面的内容进行操作了。
Object对象的原型上提供了Object.defineProperty()方法,来对属性进行精确的控制。
3.1、MDN解释
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
obj: 要定义属性的对象。
prop: 要定义或修改的属性的名称或 Symbol。
descriptor:要定义或修改的属性描述符。
3.2、属性描述符分类
- 数据属性描述符(成员有:
enumerable、configurable、writable、value) - 存取属性描述符(成员有:
enumerable、configurable、get、set)
从上面的所属成员就可以看出:writable/value 不能与 get/set共存,但是其实它们的功能都是相似的。
3.3、默认值
默认值分为两种情况:
默认情况一: 针对直接在对象中添加属性的默认值。信息为true。
const obj = {
name: 'copyer'
}
/*
{
value: 'copyer',
writable: true,
enumerable: true,
configurable: true
}
*/
默认情况二: 通过Object.defineProperty()添加的属性
const obj = {}
Object.defineProperty(obj, 'name', {}) // 这里传递了一个空对象,内部就会采用默认值
/*
{
value: undefined,
writable: false,
enumerable: false,
configurable: false,
}
*/
如果是存取数据描述符:get 和 set 是undefined。
3.4、代码演示
3.4.1、修改value值
const obj = {
name: "copyer",
};
Object.defineProperty(obj, 'name', {
value: 'james', // 重新设置值为 james
})
console.log(name) // james
注意这里修改的现有属性name,采用默认情况一的方式,那么 writable 为 true,所以name修改成功。
const obj = {}
Object.defineProperty(obj, 'name', {
value: 'james',
})
obj.name = 'copyer'
console.log(name) // james
这里name就没有修改成功,是因为,这里采用的默认值情况二的方式,那么 writable 为 false,所以name修改失败。
所以需要区分,采用的默认值的情况。
3.4.2、修改configurable
const obj = {
name: "copyer",
};
Object.defineProperty(obj, 'name', {
configurable: false
})
delete obj.name
console.log(name) // copyer
说明obj对象的name属性没有删除成功
const obj = {};
Object.defineProperty(obj, "name", {
value: "copyer",
configurable: false,
});
Object.defineProperty(obj, "name", {
value: "james",
});
console.log(obj.name);
// 直接报错
3.4.3、修改enumerable
const obj = {
name: 'copyer',
age: 18
}
console.log(Object.keys(obj)); // ["name", "age"]
// 修改之后
Object.defineProperty(obj, 'age', {
enumerable: false
})
console.log(Object.keys(obj)); // ["name"]
3.4.4、修改writable
const obj = {
name: 'copyer',
age: 18
}
obj.age = 100
console.log(obj.age); // 100
// 修改之后
Object.defineProperty(obj, 'age', {
writable: false
})
obj.age = 100
console.log(obj.age); // 18
3.5、 同时修改多个属性描述符
Object原型上提供了 defineProperties()方法。
const obj = {};
Object.defineProperties(obj, {
name: {
value: "copyer",
configurable: true,
writable: false,
enumerable: true,
},
age: {
value: 17,
configurable: true,
writable: false,
enumerable: true,
},
});
console.log(obj); // { name: 'copyer', age: 17 }
3.6、认识set/get
存取属性描述符中是没有value和writable属性的,代替它们的就是get 和 set。
const obj = {
_name: 'copyer'
};
Object.defineProperty(obj, "name", {
configurable: true,
enumerable: true,
set: function (value) {
console.log('进入了set方法')
this._name = value;
},
get: function () {
console.log('进入了get方法')
return this._name;
},
});
obj.name = 'james' // 调用set方法
console.log(obj.name); // 调用了get方法
// 打印结果:
// 进入了set方法
// 进入了get方法
// james
使用场景:
- 隐藏某一个私有属性被希望直接被外界使用和赋值
- 如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符(
Vue2的响应式源码的实现方式)
3.7 小知识点的补充
var obj = {
name: 'copyer',
age: 15
}
Object.defineProperty(obj, 'height', {
value: 1.88,
// 三个配置属性默认都是false
// enumerable: false,
// writable: false,
// configurable: false,
})
console.log(obj) // {name: 'copyer', age: 15}
上面这段代码没有任何问题吧,因为 height 属性是不可以被枚举的,所以不能被遍历出来。
但是呢?如果放到浏览器上,有一个奇怪的现象:
这是浏览器上打印的结果,可以打印出
height属性,但是呢,它的属性值的颜色要浅一点,这是浏览器提供的。打印不就是为了调试嘛,浏览器就想让你看清整个对象,只不过用颜色来标记一下,有些属性的属性描述符是不一样的。
对象方法
Object.preventExtensions(): 阻止对象继续添加新的属性
const obj = {
name: 'copyer',
age: 18
}
Object.preventExtensions(obj)
obj.height = 188
console.log(obj) // { name: 'copyer', age: 18 }
Object.seal(): 禁止对象删除里面属性和重新配置,就相当于设置属性描述信息:{configurable: false}
const obj = {
name: "copyer",
age: 18,
};
// 方式一: 自己实现,手动设置
for (let key in obj) {
Object.defineProperty(obj, key, {
configurable: false,
enumerable: true,
writable: true,
value: obj[key],
});
}
// 方式二:
Object.seal(obj)
Object.freeze():冻结一个对象,让对象属性不可修改。
实际上是调用了 Object.seal()方法,然后再加上 {writable: false}
const obj = {
name: "copyer",
age: 18,
};
// 方式一: 自己实现,手动设置
for (let key in obj) {
Object.defineProperty(obj, key, {
configurable: false,
enumerable: true,
writable: false,
value: obj[key],
});
}
// 方式二:
Object.freeze(obj)