《你所不知道的JavaScript》读书笔记(三):对象和类(上)

347 阅读5分钟

0. 前言

这篇文章是《你所不知道的JavaScript》读书笔记系列的第四篇文章。在这篇文章中,我们来聊一聊JavaScript中的类和对象。可能文章内容较多,会分成上下两篇来写。

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中的对象是一些键值对的集合,对象的键被称为对象的属性。有两种访问对象的属性的方法:

  1. 属性访问:利用.操作符进行访问
  2. 键访问:利用[]操作符进行访问 举个栗子:
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 遍历

  1. 使用for遍历
var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
    console.log( v );
}
  1. 自定义@@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)
    }
}

在上述为代码中,我们定义了构造函数,并且将构造函数传入的参数赋给为CoolGuyspecialTrick这个全局属性,在需要的时候调用这个属性。

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]]原型链和行为委托机制了。关于这两种机制,我们下一篇文章继续介绍。