oop作为一种编程范式,它将真实世界各种复杂的关系抽象为一个个对象,然后由对象之 间的分工与合作来完成对真实世界的模拟。

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而 是为了描述某个事物在整个解决问题的步骤中的行为。

面向对象有三大特性: 封装继承多态

js中,es5是通过模拟class概念来实现的。类的本质是保存一个函数的变量,该函 数有自己的属性property和方法method

生成对象

new

第一步是如何生成一个对象,在javascript里,对象不是基于class的(es5),而是基 于构造函数(constructor)和原型链(propertype)。

一个基本的生成对象过程如下:

function Person() {
    this.attributes = 'homoSapiens'
    this.say = function () {
        console.log(this.attributes)
    }
}

let F = new Person()
F.say()

我们用一个首字母大写的函数名来区分于普通函数,attributesPerson类的属性 ,say则是其方法。其中new做了下面几件事:

  1. 创建一个空对象,作为将要返回的对象实例。
  2. 将这个空对象的原型,指向构造函数的prototype属性。
  3. 将这个空对象赋值给函数内部的this关键字。
  4. 开始执行构造函数内部的代码。

this总是指向当前对象,通过构造函数添加属性和方法也就是通过this添加的方法和属 性。当我们实例化一个新的对象时,this指向的方法和属性会重新在内存中创建。于是我 们可以使用原型链来让每个实例的属性和方法指向同一个地址(当一些属性和方法不需要修 改时)。

function Person(name) {
    this.name = name
}
Person.prototype.attributes = 'homoSapiens'
Person.prototype.say = function () {
    console.log(this.attributes)
}

这样做的好处是减少了实例化对象的内存开销,但当修改某个对象的属性和方法时会影响到 由同一个构造器生成的其他对象。

es6中,class是构造函数的语法糖,上面代码改写之后:

class Person {
    constructor(name) {
        this.name = name
        this.attributes = 'homoSapiens'
    }
    say() {
        console.log(this.attributes)
    }
}
typeof Person // 'function'
Person === Person.prototype.constructor // true

class本质上是构造函数的变形。类本身指向构造函数。同时类的所有方法都定义在类 的prototype属性上。

原型与原型链

js中所有的对象由构造函数而来,也都继承构造函数的原型对象中的方法和属性。

在对象内部我们通过以下两种方式访问其继承的原型:

  • obj.constructor.prototype
  • obj._proto_ (浏览器暴露的,非es`方法)

js中所有对象都来自于构造函数,即继承某个原型,而原型本身也会继承其他的原型,最 后形成一条原型链。原型链的工作机制与作用域的类似。

所有的原型链最终都指向Object.prototypeObject.prototype本身也是对象 ,Object.prototype.__proto__是什么呢?

答案是null,大概是为了中止原型链吧。同样也解释通了

typeof null === 'object'

Object.create

Object.create(null) // 唯一不会继承任何原型方法的对象创建