Skip to content

面向对象

一般来说,对象的创建有两种,字面量和构造函数

javascript
let obj = {} //字面量
obj = new Object() //构造函数

两种方法的本质是一样的,都是新建了一个对象赋值给了 obj,

如果我们每使用一次都会创建一个对象。那么就会生成大量重复的对象。并且每次生成的时候包括方法在内都会重复。

可以利用 JavaScript 的原型的特点,来构造出一个包含共同方法和私有属性的对象。这个特性就是 JS 对象的原型。

原型

new

构造函数生成实例化对象的方式是 new 关键字。

内部用 this 指代需要生成的实例化对象,添加属性与方法。

构造函数函数名通常首字母大写。

javascript
function Person(name) {
    this.name = name
    this.say = function() {
        console.log(this.name)
    }
}
let p1 = new Person("Gintama")
p1 //{name: "Gintama",say:function(){console.log(this.name)}}
p1.say() //"Gintama"

原本 JavaScript 是没有类的,只有基于原型,在构造函数中通过 new 关键字可以生成对象,和基于类的语言非常类似,所以我们称构造函数也是一个类。

prototype

只有(构造)函数 原型 prototype 对象,可以直接访问修改原型的属性,

原型对象的方法可以让实例化对象使用

javascript
function Person(name) {
    this.name = name
}
Person.prototype.say = function() {
    //注意,一定要写在实例化之前
    console.log(this.name)
}
let p1 = new Person("Gintama")
let p2 = new Person("狗蛋")
p1.say() //"Gintama"
p2.say() //"狗蛋"
Person.prototype.say = function() {
    //注意,一定要写在实例化之前
    console.log("shut up!")
}
p1.say() //"shut up!"

__proto__

任何(实例化)对象拥有一个属性 constructor,构造器。它指向当前对象的构造函数。

javascript
function Person() {}
let p = new Person()
p.constructor === Person //true
let o1 = {}
o1.constructor === Object //true

Object.getPrototypeOf()

实例化对象拥有隐式原型[[prototype]],可以通过 __proto__ 访问构造函数的原型

(实际上有的浏览器不支持这个属性)。推荐使用Object.getPrototypeOf()访问。

javascript
p.__proto__ //{say: ƒ, constructor: ƒ}
Object.getPrototypeOf(p) //{say: ƒ, constructor: ƒ}
javascript
p.__proto__ === Object.getPrototypeOf(p) //true

new 关键字做了什么

javascript
function Foo() {}
//let o = new Foo();等价于做了以下
o = new Object() //新建一个对象
o.__proto__ = Foo.prototype //修改对象原型指向
Foo.call(o) //将this指向o执行

自实现 new

javascript
function myNew(fn) {
    const obj = Object(fn.prototype)
    result = fn.apply(obj, [...arguments].slice(1))
    return typeof result === "object" ? result : obj
}

instanceof

判断是否是实例或子类

javascript
p1 instanceof Person //true

Object 是对象,同时也是构造函数。(可以使用 new Object)

javascript
Object instanceof Object //true
Object instanceof Function //true

因此,任何的对象都是可以看作 Object 的实例。

寄生组合式继承

继承我们主要要干几件事:

  • 把父类的属性和方法绑定到子类上
  • 把父类添加到子类的原型链上

子类与父类之间的继承关系,和实例与类下关系差不多。寄生组合式继承是原型式继承的终极写法

javascript
function inheritPrototype(subType, superType) {
    //1.创建了超类(父类)原型的(副本)浅复制
    var prototype = Object(superType.prototype)
    //2.修正子类原型的构造函数属性
    prototype.constructor = subType
    //3.将子类的原型替换为超类(父类)原型的(副本)浅复制
    subType.prototype = prototype
}

Object(.create)不同于 new 它只是生成一个拷贝了原型的对象

ES6 class

es6 新语法糖,构造函数和继承方法细节隐藏,但是很直白

声明使用 class 关键字 也可以使用匿名 class 表达式

javascript
class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height
        this.width = width
    }
    // Getter
    get area() {
        return this.calcArea()
    }
    // Method
    calcArea() {
        return this.height * this.width
    }
}
const square = new Rectangle(10, 10)

console.log(square.area)
// 100

上式结构分明,一目了然。要注意没有逗号

extends

继承。class 子类 extends 父类

super指代超类,顺着__proto__查找超类。

javascript
class Animal {
    constructor(name) {
        this.name = name
    }
    speak() {
        console.log(`${this.name} makes a noise.`)
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name) // 调用超类构造函数并传入name参数
    }
    speak() {
        console.log(`${this.name} barks.`)
    }
}

var d = new Dog("Mitzie")
d.speak() // 'Mitzie barks.'

static

定义一个类的一个静态方法,调用静态方法不需要实例化。方法中也不使用 this

javascript
class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    static displayName = "Point"
    static distance(a, b) {
        const dx = a.x - b.x
        const dy = a.y - b.y
        return Math.hypot(dx, dy)
    }
}

Symbol.species

覆盖默认的构造函数

javascript
class MyArray extends Array {
    // Overwrite species to the parent Array constructor
    static get[Symbol.species]() {
        return Array
    }
}
var a = new MyArray(1, 2, 3)
var mapped = a.map((x) => x * x)

console.log(mapped instanceof MyArray)
// false
console.log(mapped instanceof Array)
// true

private

私有。相当于类中的局部变量。(ts 用 private,js 用#属性名)

javascript
class Rectangle {
    #height = 0
    #width
    constructor(height, width) {
        this.#height = height
        this.#width = width
    }
}

包装对象

字面量:js 中的基础类型都是字面量声明的值类型,而不是 new 出来的,却又可以使用对象的方法。

javascript
;
(2).toString() //"2"

值类型之所以能够像对象一样进行属性设置操作,这是因为这行代码执行时临时生成了一个包装对象

Number(2)并对它进行操作返回值,然后销毁。

javascript
"123" * 1 //123
//本质上是
Number("123").valueOf() * 1 //123
let str = "asd"
str.length //3
//本质上是
String(str).length //3
str.length = 4
str.length //3

这就是为什么这里只能访问 str 的值却无法修改的原因了,因为每次都会生成一个新的包装对象

Java 和 JavaScript 不同,Java 的原始类型需要装箱,否则不是Object 的子类

而 JavaScript 和 Kotlin(另一个 JVM 上运行的语言,取代 Java 作为了安卓 APP 构建语言)在设计上避免了装箱拆箱。