0. 前言
这篇文章是《你所不知道的JavaScript》读书笔记系列的第四篇文章。在这篇文章中,我们来聊一聊JavaScript中的类和对象。可能文章内容较多,会分成上下两篇来写。
- 《你所不知道的JavaScript》读书笔记(一):作用域和闭包(上)
- 《你所不知道的JavaScript》读书笔记(一):作用域和闭包(下)
- 《你所不知道的JavaScript》读书笔记(二):this指向问题
1.对象
关于JavaScript对象,首先要了解的是他的定义方式。js对象的有两种定义的方式:字面形式和构造形式。
- 字面形式的对象语法为花括号包含的键值对形式,例子如下:
var myObj = {
key: value,
...
}
- 构造形式的对象语法为利用
new
调用Obiect()
函数,例子如下:
var myObj = new Object()
myObj.key = value
...
接下来,我们将从JS的数据类型、内置对象,以及JS对象中包含的内容来了解JS对象。
1.1 数据类型
JS有六种数据类型:
- string
- number
- boolean
- null
- undefined
- object 这几种数据类型中,前面的五种属于简单基本类型,本身不是对象,但是在程序处理的时候,有时候可以自动把这些类型转换成对象来处理。
1.2 内置对象
在JS中,有几个内置对象:
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
在这些内置对象中,String、Number、Boolean和数据类型中的string、number、boolean名字是相同的,在程序处理的时候,数据类型转换成的对象就是它们。因此,这三个对象,既有字面形式又有构造形式,在必要的时候,字面形式会自动转换成构造形式。
Object、Function、Array、RegExp,这四个对象构造形式和字面形式都是对象。
Date只有构造形式,没有字面形式。
Error则很少在代码中显式创建,一般是在抛出异常的时候自动创建。
1.3 “对象”
1.3.1 属性名
我们知道,JS中的对象是一些键值对的集合,对象的键被称为对象的属性。有两种访问对象的属性的方法:
- 属性访问:利用
.
操作符进行访问 - 键访问:利用
[]
操作符进行访问 举个栗子:
var myObject = {
a: 2
}
myObject.a // 属性访问
myObject['a'] // 键访问
除了固定的属性名,ES6中新增了可计算属性名,可以使用[]
包裹一个表达式来当作属性名。
1.3.2 属性描述符
关于JS对象的属性,还有一个属性描述符,用来配置属性的读写、枚举等特性。属性描述符包括以下几项内容:
- value:该属性对应的值
- writeable:属性是否可写
- enumberable:属性是否可枚举
- configurable:属性是否可配置
一个属性的属性描述符可以通过
Object.getOwnPropertyDescriptor()
来获取。使用方法如下:
var myObject = {
a: 2
}
Object.getOwnPropertyDescriptor(myObject, 'a')
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }
此外,我们还可以通过defineProperty()
来修改或者显式定义一些特性:
var myObject = {}
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 定义该属性为不可写
configurable: true,
enumerable: true
} );
myObject.a = 3; // 重写该属性的值为3
myObject.a; // 访问这个属性的结果是2,证明了我们之前重写的属性值无效
还可以通过给属性添加set和get来设置或者获取属性的值。set和get分别需要一个函数。
1.3.3 遍历
- 使用
for
遍历
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
console.log( v );
}
- 自定义
@@iterator
对象的next()
方法遍历
var myObject = {
a: 2,
b: 3
}
Object.defineProperty(myObject, Symbol.iterator, {
enumberable: false,
writable: false,
configurable: true
value: function() {
var o = this
var idx = 0
var ks = Object.keys( o )
return {
next: function() {
return {
value: o[ks[idx++]]
done: (idx > ks.length)
}
}
}
}
})
在这段代码中,很显然形成了一个闭包。在外层函数中,三个变量被记录下来:
o
:表示这个对象本身idx
: 表示当前遍历到的属性指针,永远指向下一个ks
: 表示对象的属性 内层函数返回当前的值,以及是否遍历完成两个信息。
2. 类
有面向对象编程经验的小伙伴们应该对“类”这个概念耳熟能详。在这篇文章中,或者应该说在软件的设计模式中,类是一种可选的设计模式,而不是必须要进行的抽象。众所周知,类的设计模式有三个要素:实例化、继承和多态。然而JS本身并没有类这个概念,但是可以通过对象的关联来进行模拟类的机制。
2.1 实例化
在类理论中,类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。它的工作就是初始化实力需要的所有信息。为了方便理解,我们来看下列伪代码:
class CoolGuy{
specialTrick = nothing
CoolGuy( trick ) {
specialTrick = trick
}
showOff() {
output("Here's my trick:", specialTrick)
}
}
在上述为代码中,我们定义了构造函数,并且将构造函数传入的参数赋给为CoolGuy
的specialTrick
这个全局属性,在需要的时候调用这个属性。
2.2 继承
类的继承关注的是两个类之间的关系。继承关系的两端分别有两个类,被继承的那个称为父类,继承的那个类被称为子类。下面的伪代码展示了一个非常经典的继承的例子:
class Vehicle {
engines = 1
ignition() {
output( "Turning on my engine." );
}
drive() {
ignition();
output( "Steering and moving forward!" )
}
}
class Car inherits Vehicle {
wheels = 4
drive() {
inherited:drive()
output( "Rolling on all ", wheels, " wheels!" )
}
}
class SpeedBoat inherits Vehicle {
engines = 2
ignition() {
output( "Turning on my ", engines, " engines." )
}
pilot() {
inherited:drive()
output( "Speeding through the water with ease!" )
}
}
2.3 多态
多态的定义是父类的通用行为可以被子类用更特殊的行为重写。这里需要注意的是子类得到的 仅仅是继承自父类行为的一份副本。子类对继承得到的一个方法进行“重写”,不会影响到父类中的方法。
3. 总结
这篇文章中,我们介绍了JS对象的基本知识以及简单的通用类理论。正如这篇文章中提到的,JS是不通过类直接创建对象的语言。那么,它是如何模拟类的设计模式呢?这就需要用到JS中的 [[ProtoType]]
原型链和行为委托机制了。关于这两种机制,我们下一篇文章继续介绍。