对象属性操作
属性访问方式与对象的创建
在已有对象中添加属性的方式:
const obj = {//创建对象
name: 'aa'
}
let hello = 'world'
obj.hello = 123
obj[hello] = 123 // 使用变量作为属性名
//delete obj[hello]
console.log(obj) // { name: 'aa' world:123 }
上述代码obj.hello = 123无法在对象中添加一个world:123,而是会添加hello:123
要点:
- 使用
{}来创建对象的方式称为对象字面量 - 使用点语法
obj.name访问已知属性名 - 使用方括号
obj[variable]动态访问属性 delete操作符用于删除对象属性
创建对象的另一种方法(构造函数)
const obj = new Object() //构造函数
obj.name = 'dd'
console.log(obj);
- 在v8引擎眼中,任何创建对象的方式都被视为new Object()
构造函数详解
什么是构造函数
构造函数是专门用来创建对象的特殊函数。它的作用就像一个对象工厂,可以批量生产具有相同结构的对象。
构造函数的命名约定:首字母大写,这是约定俗成的规范,方便区分普通函数。
基本构造函数
function Person() {
// new 调用时自动创建 this 对象
// 我们可以在函数体内给 this 添加属性
this.name = '小明'
this.age = 18
}
Person() // 普通调用,无返回值
const obj = new Person() // new 调用,返回实例对象
console.log(person1) // { name: '小明', age: 18 }
- 构造函数本身就是一个普通函数
- 关键在于调用方式:是否使用
new关键字 - 使用
new调用时,函数内部会自动创建一个空对象this new调用的函数会返回之前自动创建的对象this
带参数的构造函数
为了让每个对象有不同的属性值,我们可以给构造函数添加参数:
function Car(color) {
this.name = 'su7'
this.height = 1400
this.length = 4800
this.weight = 1500
this.color = color
}
const zCar = new Car('pink')
const qCar = new Car('blue')
qCar.name = '劳斯莱斯' // 修改实例属性不影响其他实例
console.log(zCar)
console.log(qCar)
如上述代码所示,给函数添加参数color后,可以创建两个color值不同的对象
注意
const aCar = new Car('pink')
const cCar = new Car('pink')
如果用上面这种方式,虽然aCar 和 cCar 两个对象console.log的值都一样,但他们并不相等
为什么需要构造函数?
假设我们需要创建100个汽车对象,对比两种写法:
不用构造函数:
const car1 = { name: 'su7', color: 'red' }
const car2 = { name: 'su7', color: 'blue' }
const car3 = { name: 'su7', color: 'white' }
// ... 重复100次
使用构造函数:
const cars = []
const colors = ['red', 'blue', 'white', ...]
for (let i = 0; i < 100; i++) {
cars.push(new Car(colors[i % colors.length]))
}
总结:
- 每个
new调用都会创建一个全新的对象 - 实例之间相互独立,修改一个不影响其他
- 构造函数可以批量创建结构相同的对象
- 通过参数可以让每个实例有不同的属性值
包装类深入理解
什么是包装类
- vaScript 中有原始类型(string、number、boolean 等)和引用类型(object)
- 包装类 ,是 JS 为了让基本数据类型能调用方法 / 属性,自动创建的临时对象。
原始类型本身不能添加属性和方法:
var str = 'hello'
str.name = '小明' // 并不报错
console.log(str.name) // undefined
V8 内部发生了什么:
- V8 自动执行
const str = new String('hello'),即将str包装成对象 - 在该对象中添加属性
name, 值为 小明 - 由于str实际是一个字符串,原始类型是不能添加属性和方法的,v8会立即销毁这个临时对象(拆箱)
但是你可能会疑惑:为什么 str.length 可以正常访问?这就涉及到包装类的概念。
包装类示例详解
前面我们说到,包装类 ,是 JS 为了让基本数据类型能调用方法 / 属性,自动创建的临时对象。我们是否能修改这些方法 / 属性呢?来看一段代码:
var str = 'abc'
str.length = 4 // 尝试修改 length
console.log(str.length) // 3(length 是原型属性,无法修改)
- 如果按上述代码方式修改length,我们发现无法修改,因为length并不是写在str对象里面,此时v8认为在str中添加一个属性length 值为4,并且在打印前销毁了
既然无法直接改写,那我们调用的 length、各类内置方法,本质上又是从何处获取而来?接下来就一起认识原型链。
原型链查找机制
什么是原型链
每个 JavaScript 对象都有一个内部属性 [[Prototype]](通常通过 __proto__ 访问),它指向另一个对象。当访问一个对象的属性时:
- 先在对象自身查找
- 找不到则到
__proto__指向的对象([[Prototype]]) - 还找不到就继续往上找
- 直到找到或到达原型链末端(
null)
这个查找链条就叫原型链。
刚才代码的原型链查找过程:
str对象
↓ 访问 length
↓ 自身没有 toString
↓ 查找 obj.__proto__
Object.prototype
↓ 找到 length
- 因此,刚才的代码想要正确修改length的值也应该修改
pototype中的length属性的值
内置对象原型
我们可以给内置对象的原型添加新方法:
// 给 String 原型添加一个自定义属性
String.prototype.len = 5
const str = 'hello'
console.log(str.len) // 5
console.log(str.__proto__.len) // 5
console.log(String.prototype.len) // 5
// 所有字符串都能访问
const str2 = 'world'
console.log(str2.len) // 5
注意事项:
- 修改内置原型会影响所有实例(包括后续创建的)
- 不推荐在生产环境中修改内置原型
- 可能与第三方库冲突,导致难以排查的 bug
- 如果要扩展,建议使用 ES6 的 class 继承