掌握 JavaScript 对象:成为前端高手

7 阅读5分钟

对象属性操作

属性访问方式与对象的创建

在已有对象中添加属性的方式:

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')

如果用上面这种方式,虽然aCarcCar 两个对象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]))
}

总结:

  1. 每个 new 调用都会创建一个全新的对象
  2. 实例之间相互独立,修改一个不影响其他
  3. 构造函数可以批量创建结构相同的对象
  4. 通过参数可以让每个实例有不同的属性值

包装类深入理解

什么是包装类

  • vaScript 中有原始类型(string、number、boolean 等)和引用类型(object)
  • 包装类 ,是 JS 为了让基本数据类型能调用方法 / 属性,自动创建的临时对象

原始类型本身不能添加属性和方法:

var str = 'hello'
str.name = '小明'      // 并不报错
console.log(str.name)  // undefined

V8 内部发生了什么:

  1. V8 自动执行 const str = new String('hello') ,即将str包装成对象
  2. 在该对象中添加属性 name , 值为 小明
  3. 由于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__ 访问),它指向另一个对象。当访问一个对象的属性时:

  1. 先在对象自身查找
  2. 找不到则到 __proto__ 指向的对象([[Prototype]]
  3. 还找不到就继续往上找
  4. 直到找到或到达原型链末端(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 继承