虽然说在JavaScript编程语言中,函数是第一公民,但是JavaScript不仅支持函数式编程,也支持面向对象编程。JavaScript对象设计成了一组属性的无序集合,由key和value组成,key为一个标识符名称,而value可以是任意类型的值,当函数作为对象的属性值时,这个函数就可以称之为对象的方法。下面就来看看JavaScript的面向对象吧。
一般地,常用于创建对象的方式有两种,早期经常使用Object类,通过new关键字来创建一个对象,有点类似于Java中创建对象,后来为了方便就直接使用对象字面量的方式来创建对象了,用法更为简洁。
Object
类创建对象;const obj = new Object() // 创建一个空对象
// 往对象中添加属性
obj.name = 'curry'
obj.age = 30
// 直接往{}添加键值对
const obj = {
name: 'curry',
age: 30
}
对象创建出来后,如何对该对象进行操作控制呢?这里涉及到一个很重要的方法:Object.defineProperty()。
该方法可以在对象上定义一个新的属性,也可修改对象现有属性,并将该对象返回。
Object.defineProperty(obj, prop, descriptor)
接收三个参数:
什么是属性描述符?顾名思义就是对对象中的属性进行描述,简单来说就是给对象某个属性指定一些规则。属性描述符主要分为数据属性描述符和存取属性描述符两种类型。
对于属性描述符中的属性是否两者都可以设置呢?其实数据和存取属性描述符两者是有区别,下面的表格统计了两者可用和不可用的属性:
属性 | configurable | enumerable | value | writable | get | set |
---|---|---|---|---|---|---|
数据属性描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取属性描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
那么为什么有些属性可以用,有些属性又不能用呢?因为数据属性描述符和存取属性描述符所担任的角色不一样,下面就来详细介绍一下,它们两者的区别。
从上面的表格可以知道,数据属性描述符可以使用configurable、enumerable、value、writable。而这就是数据属性描述符的四个特性。
new Object()
或者字面量的方式创建对象时,其中的属性的configurable
默认为true
,当通过属性描述符定义一个属性时,其属性的configurable
默认为false
。new Object()
或者字面量的方式创建对象时,其中的属性的enumerable
默认为true
,当通过属性描述符定义一个属性时,其属性的enumerable
默认为false
。new Object()
或者字面量的方式创建对象时,其中的属性的writable
性描述符定义一个属性时,其属性的writable
默认为false
。const obj = {
name: 'curry'
}
Object.defineProperty(obj, 'age', {
configurable: false, // age属性是否可以删除,默认false
enumerable: false, // age属性是否可以枚举,默认false
writable: false, // age属性是否可以写入(修改),默认false
value: 30 // age属性的值,默认undefined
})
// 当configurable为false,age属性是不可被删除的
delete obj.age
console.log(obj) // { name: 'curry', age: 30 }
// 当writable为false,age属性的值是不可被修改的
obj.age = 18
console.log(obj) // { name: 'curry', age: 30 }
// 如果将enumerable修改为false,age属性是不可以被遍历出来的
for (const key in obj) {
console.log(key) // name
}
存取属性描述符可以使用configurable、enumerable、get、set。在获取对象某个属性值时,可以通过get来拦截,在设置对象某个属性值时,可以通过set来拦截。configurable和enumerable的用法和特性跟数据属性描述符一样。
get和set的使用场景:
_age
表示不想直接被外界使用,外界就可以通过使用age
的set和get来访问设置_age
了。const obj = {
name: 'curry',
_age: 30
}
// 注意:这里的this是指向obj对象的
Object.defineProperty(obj, 'age', {
configurable: true,
enumerable: true,
get: function() {
console.log('age属性被访问了')
return this._age
},
set: function(newValue) {
console.log('age属性被设置了')
this._age = newValue
}
})
obj.age // age属性被访问了
obj.age = 18 // age属性被设置了
上面使用Object.defineProperty()方法都是给单个属性进行定义描述符,想要一次性定义多个属性,那么就可以使用Object.defineProperties()方法了。写法如下:
Object.defineProperties(obj, {
name: {
configurable: true,
enumerable: true,
writable: true,
value: 'curry'
},
age: {
configurable: false,
enumerable: false,
get: function() {
return this._age
},
set: function(newValue) {
this._age = newValue
}
}
})
上面介绍了Object中defineProperty和defineProperties两个方法。其实Object中还有很多方法,下面介绍一些常用的。
获取对象的属性描述符:
获取单个属性:Object.getOwnPropertyDescriptor
;
获取所有属性:Object.getOwnPropertyDescriptors
;
const obj = {
name: 'curry',
age: 30
}
console.log(Object.getOwnPropertyDescriptor(obj, 'age')) // { value: 30, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
name: {
value: 'curry',
writable: true,
enumerable: true,
configurable: true
},
age: { value: 30, writable: true, enumerable: true, configurable: true }
}
*/
Object.preventExtensions()
:禁止对象扩展新属性,给一个对象添加新的属性会失败(在严格模式下会报错)。Object.seal()
:将对象密封起来,不允许配置和删除属性。(实际还是调用preventExtensions
,并且将现有属性的configurable
设置为false
)Object.freeze()
:将对象冻结起来,不允许修改对象现有属性。(实际上是调用seal,并且将现有属性的writable
设置为false
)上面提到的创建对象的方式仅适用于创建单个对象适用,如果有多个对象比较类似,那么一个个创建必然是很麻烦的,如何批量创建对象呢?JavaScript也给我们提供了一些方案。
如果我们不想在创建对象时做重复的工作,那么就可以定义一个函数为我们去做这些重复性的工作,我们只需要将属性对应的值传入函数即可。
function createObj(name, age) {
// 创建一个空对象
const obj = {}
// 设置对应属性值
obj.name = name
obj.age = age
// 公共方法共用
obj.sayHello = function() {
console.log(`My name is ${this.namename}, I'm ${this.age} years old.`)
}
// 将对象返回
return obj
}
const obj1 = createObj('curry', 30)
const obj2 = createObj('kobe', 24)
console.log(obj1) // { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
console.log(obj2) // { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }
obj1.sayHello() // My name is undefined, I'm 30 years old.
obj2.sayHello() // My name is undefined, I'm 24 years old.
缺点:创建出来的对象全是通过字面量创建的,获取不到对象真实的类型。
(1)什么是构造函数?
(2)new操作符调用函数的作用
当一个函数被new操作符调用了,默认会进行如下几部操作:
(3)构造函数创建对象的过程
function Person(name, age) {
this.name = name
this.age = age
this.sayHello = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
}
}
const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
console.log(p1) // Person { name: 'curry', age: 30, sayHello: [Function (anonymous)] }
console.log(p2) // Person { name: 'kobe', age: 24, sayHello: [Function (anonymous)] }
缺点:在每次使用new创建新对象时,会重新给每个对象创建新的属性,包括对象中方法,实际上,对象中的方法是可以共用的,消耗了不必要的内存。
console.log(p1.sayHello === p2.sayHello) // false
在了解该方案之前,需要先简单的认识一下何为原型。
(1)对象的原型
JavaScript中每个对象都有一个特殊的内置属性prototype(我们称之为隐式原型),这个特殊的属性指向另外一个对象。那么这个属性有什么用呢?
那么对象的prototype属性怎么获取呢?主要有两种方法:
__proto__
属性访问;Object.getPrototypeOf()
方法获取;const obj = {
name: 'curry',
age: 30
}
console.log(obj.__proto__)
console.log(Object.getPrototypeOf(obj))
(2)函数的原型
所有的函数都有一个prototype属性,并且只有函数才有这个属性。前面提到了new操作符是如何在内存中创建一个对象,并给我们返回创建出来的对象,其中第二步这个对象内部的prototype属性会被赋值为该构造函数的prototype属性。将代码与图结合,来看一下具体的过程。
示例代码:
function Person(name, age) {
this.name = name
this.age = age
}
const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
// 验证:对象(p1\p2)内部的[[prototype]]属性(__proto__)会被赋值为该构造函数(Person)的prototype属性;
console.log(p1.__proto__ === Person.prototype) // true
console.log(p2.__proto__ === Person.prototype) // true
内存表现:
(3)结合对象和函数的原型,创建对象
先简单的总结一下:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHello = function() {
console.log(`My name is ${this.name}, I'm ${this.age} years old.`)
}
const p1 = new Person('curry', 30)
const p2 = new Person('kobe', 24)
console.log(p1.sayHello === p2.sayHello) // true
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.cnblogs.com/MomentYY/p/15991788.html
内容来源于网络,如有侵权,请联系作者删除!