JavaScript 系列七:对象

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。

"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!

对象

前言

在开始学习之前,我们想要告诉您的是,本文章是对JavaScript 语言知识中 “对象、类与面向对象编程” 部分的总结,如果您已掌握下面知识事项,则可跳过此环节直接进入题目练习

  • 对象的基本构造
  • 对象声明及使用
  • 对象的结构赋值
  • 继承
  • 包装对象

如果您对某些部分有些遗忘,👇🏻 已经为您准备好了!

汇总总结

ECMA-262 将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。正因为如此(以及其他还未讨论的原因),可以把ECMAScript 的对象想象成一张散列表,其中的内容就是一组名/值对,值可以是数据或者函数。

对象的基本构造

创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法,如下例 所示:

let person = new Object()
person.name = 'XHS-rookies'
person.age = 18
person.job = 'Software Engineer'
person.sayName = function () {
   
  console.log(this.name)
}

这个例子创建了一个名为 person 的对象,而且有三个属性(nameagejob)和一个方法(sayName())。sayName()方法会显示 this.name 的值,这个属性会解析为 person.name。早期JavaScript 开发者频繁使用这种方式创建新对象。几年后,对象字面量变成了更流行的方式。前面的例子如果使用对象字面量则可以这样写:

let person = {
   
  name: 'XHS-rookies',
  age: 18,
  job: 'Software Engineer',
  sayName() {
   
    console.log(this.name)
  },
}

这个例子中的 person 对象跟前面例子中的 person 对象是等价的,它们的属性和方法都一样。这些属性都有自己的特征,而这些特征决定了它们在 JavaScript 中的行为。

对象声明及使用

综观 ECMAScript 规范的历次发布,每个版本的特性似乎都出人意料。ECMAScript 5.1 并没有正式 支持面向对象的结构,比如类或继承。但是,正如接下来几节会介绍的,巧妙地运用原型式继承可以成 功地模拟同样的行为。ECMAScript 6 开始正式支持类和继承。ES6的类旨在完全涵盖之前规范设计的基于原型的继承模式。不过,无论从哪方面看,ES6 的类都仅仅是封装了ES5.1 构造函数加原型继承的语法糖而已。

工厂模式

工厂模式是一种众所周知的设计模式,广泛应用于软件工程领域,用于抽象创建特定对象的过程。下面的例子展示了一种按照特定接口创建对象的方式:

function createPerson(name, age, job) {
   
  let o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
   
    console.log(this.name)
  }
  return o
}
let person1 = createPerson('XHS-rookies', 18, 'Software Engineer')
let person2 = createPerson('XHS-boos', 18, 'Teacher')

这里,函数 createPerson() 接收 3 个参数,根据这几个参数构建了一个包含 Person 信息的对象。可以用不同的参数多次调用这个函数,每次都会返回包含 3 个属性和 1 个方法的对象。这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。

构造函数模式

ECMAScript 中的构造函数是用于创建特定类型对象的。像 ObjectArray这 样的原生构造函数,运行时可以直接在执行环境中使用。当然也可以自定义构造函数,以函数的形式为 自己的对象类型定义属性和方法。 比如,前面的例子使用构造函数模式可以这样写:

function Person(name, age, job) {
   
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
   
    console.log(this.name)
  }
}
let person1 = new Person('XHS-rookies', 18, 'Software Engineer')
let person2 = new Person('XHS-boos', 18, 'Teacher')
person1.sayName() // XHS-rookies
person2.sayName() // XHS-boos

在这个例子中,Person() 构造函数代替了createPerson()工厂函数。实际上,Person() 内部 的代码跟 createPerson() 基本是一样的,只是有如下区别。

  • 没有显式地创建对象。

  • 属性和方法直接赋值给了 this

  • 没有 return

另外,要注意函数名 Person 的首字母大写了。按照惯例,构造函数名称的首字母都是要大写的, 非构造函数则以小写字母开头。这是从面向对象编程语言那里借鉴的,有助于在 ECMAScript 中区分构 造函数和普通函数。毕竟 ECMAScript 的构造函数就是能创建对象的函数。

要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作。

(1)在内存中创建一个新对象。

(2)这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性。

(3)构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4)执行构造函数内部的代码(给新对象添加属性)。

(5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

上一个例子的最后,person1person2 分别保存着 Person 的不同实例。这两个对象都有一个 constructor 属性指向 Person,如下所示:

console.log(person1.constructor == Person) // true
console.log(person2.constructor == Person) // true

constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型更可靠的方式。前面例子中的每个对象都是 Object 的实例,同时也是 Person 的实例,如下面调用 instanceof 操作符的结果所示:

console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true

定义自定义构造函数可以确保实例被标识为特定类型,相比于工厂模式,这是一个很大的好处。在 这个例子中,person1person2 之所以也被认为是 Object 的实例,是因为所有自定义对象都继承自 Object(后面再详细讨论这一点)。构造函数不一定要写成函数声明的形式。赋值给变量的函数表达式也可以表示构造函数:

let Person = function (name, age, job) {
   
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
   
    console.log(this.name)
  }
}
let person1 = new Person('XHS-rookies', 18, 'Software Engineer')
let person2 = new Person('XHS-boos', 18, 'Teacher')
person1.sayName() // XHS-rookies
person2.sayName() // XHS-boos
console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true

在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数:

function Person() {
   
  this.name = 'rookies'
  this.sayName = function () {
   
    console.log(this.name)
  }
}
let person1 = new Person()
let person2 = new Person()
person1.sayName() // rookies
person2.sayName() // rookies
console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true

1. 构造函数也是函数

构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。并没有把某个函数定义为构造函数的特殊语法。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new操作符调用的函数就是普通函数。比如,前面的例子中定义的 Person()可以像下面这样调用:

// 作为构造函数
let person = new Person('XHS-rookies', 18, 'Software Engineer')
person.sayName() // "XHS-rookies"
// 作为函数调用
Person('XHS-boos', 18, 'Teacher') // 添加到 window 对象
window.sayName() // "XHS-boos"
// 在另一个对象的作用域中调用
let o = new Object()
Person.call(o, 'XHS-sunshineboy', 25, 'Nurse')
o.sayName() // "XHS-sunshineboy"

这个例子一开始展示了典型的构造函数调用方式,即使用 new 操作符创建一个新对象。然后是普通函数的调用方式,这时候没有使用 new操作符调用 Person(),结果会将属性和方法添加到 window 对象。这里要记住,在调用一个函数而没有明确设置 this 值的情况下(即没有作为对象的方法调用,或 者没有使用 call()/apply()调用),this 始终指向 Global 对象(在浏览器中就是 window 对象)。 因此在上面的调用之后,window 对象上就有了一个 sayName() 方法,调用它会返回 "Greg"。最后展示的调用方式是通过 call()(或apply() )调用函数,同时将特定对象指定为作用域。这里的调用将 对象 o 指定为 Person() 内部的 this 值,因此执行完函数代码后,所有属性和 sayName() 方法都会添加到对象 o 上面。

2. 构造函数的问题

构造函数虽然有用,但也不是没有问题。构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。因此对前面的例子而言,person1person2sayName() 的方法,但这两个方法不是同一个 Function 实例。我们知道,ECMAScript中的函数是对象,因此每次定义函数时,都会初始化一个对象。逻辑上讲,这个构造函数实际上是这样的:

function Person(name, age, job) {
   
  this.name = name
  this.age = age
  this.job = job
  this.sayName = new Function('console.log(this.name)') // 逻辑等价
}

这样理解这个构造函数可以更清楚地知道,每个 Person 实例都会有自己的 Function 实例用于显 示 name 属性。当然了,以这种方式创建函数会带来不同的作用域链和标识符解析。但创建新 Function 实例的机制是一样的。因此不同实例上的函数虽然同名却不相等,如下所示:

console.log(person1.sayName == p
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值