面向对象编程 OOP

214 阅读5分钟

对象是什么?

  • 对象是对于单个物体的抽象
  • 对象本质就是一个容器,封装了属性 & 方法
  • 属性 ➡ 可以描述为是对象的状态 who am I
  • 方法的 ➡ 可以描述为对象的行为 what I do

为什么要面向对象编程(OOP)?

  • 特点: 逻辑迁移灵活、代码可复用性高、高度模块化

构造函数

  • 构造函数需要一个模板,代表了一类相同物体的特征,从而可以生成对象
  • 类本质也是对象模板
  • js其实本质上不是基于类,而是基于构造函数 + 原型链 constructor + prototype
function People() {
    this.name = 'James',
    this.age = 18
}
const p = new People()

image.png

new 做了什么动作?

  • 创建了一个空对象, 作为返回的对象实例
  • 将生成空对象的原型对象指向了构造函数的prototype属性
  • 上面的例子p.__proto__ === People.prototype true
  • 将当前实例对象赋给了内部this
  • 执行构造函数初始化代码

思考:构造函数,不new,可以直接使用么???

function People() {
    this.name = 'James',
    this.age = 18
}
const p = People() // 可以拿到 p对象吗

image.png

我是犟驴,我就想这么用呢

// 兼容一下就ok了
function People() {
    const isInstance = this instanceof People
    if(!isInstance){
        return new People()
    }
    this.name = 'James',
    this.age = 18
}
const p = People()
// 这样就可以正常使用了

插播:实例对像的属性和方法是相互隔离的,不会相互影响

constructor 是什么?

constructor 属性返回对创建此对象的数组函数的引用

  • 每个对象创建时会自动拥有一个构造函数属性constructor
  • constructor继承自原型对象,指向构造函数的引用 p.contructor ==== People

原型对象

  function People() {}
  const p1 = new People();
  const p2 = new People();
  1. 构造函数:用来初始化创建对象的函数 - People
    • 自动给构造函数赋予一个属性 prototype,该属性实际等于实例对象的原型对象
  2. 实例对象:p1 就是实例对象,根据原型创建出来的实例
    • 每个对象中都有个 __proto__
    • 每个实例对象都有个 constructor 属性
    • constructor 由继承而来,并指向当前构造函数
  3. 原型对象:People.prototype

在JavaScript中,prototype 对象是实现面向对象的一个重要机制。每个函数就是一个对象(Function),函数对象都有一个子对象 prototype 对象,类是以函数的形式来定义的。prototype 表示该函数的原型,也表示一个类的成员的集合

构造函数,原型对象,实例对象三者之间的关系

  • 每创建一个函数,该函数都会自动带有一个 prototype 属性。该属性是一个指针,指向一个对象,该对象称之为原型对象(后期我们可以使用这个原型对象帮助我们在js中实现继承)

  • 原型对象上默认有一个属性 constructor ,该属性也是一个指针,指向其相关联的构造函数

  • 通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。

// 定义了一个构造函数 People()
function People(){
    this.type='人'
}
// People.prototype指向原型对象,其自带属性construtor又指回了People 即 People.prototype.constructor==People
People.prototype.showType=function(){
    alert(this.type);
}
// 实例对象person由于其内部指针指向了原型对象
var person=new People();
//实例对象可以调用原型对象上面的方法
person.showType()

总结:三者的关系是,每个构造函数都有一个原型对象,原型对象上包含着一个指向构造函数的指针,而实例都包含着一个指向原型对象的内部指针。通俗的说,实例可以通过内部指针访问到原型对象,原型对象可以通过 constructor 找到构造函数。

image.png

image.png

继承

在原型对象的所有属性和方法,都能被实例所共享

  // People 类
  function People() {
    this.name = 'lol';
  }
  People.prototype.getName = function() {
    return this.name;
  }
  // Person 类
  function Person() {
      this.hobby = ['eat', 'sport']
  }
  // Person 继承 People 类
  Person.prototype = new People();
  // Person 原型对象可以通过constructor找到构造函数 Person
  Person.prototype.constructor = Person;
  const person = new Person();
  person.getName()
  // 本质:重写原型对象,将父对象的属性方法,作为子对象原型对象的属性和方法

原型链继承有什么缺点?

  1. 父类属性一旦赋值给子类的原型属性,此时属性属于子类的共享属性了
  2. 在创建 LOL 的子类的时候,无法传递参数
function People() {
  this.name = 'lol'
  this.hobby = ['s']
}
// Person 类
function Person() {}
// Person 继承Game类
Person.prototype = new People();
Person.prototype.constructor = Person;
const person1 = new Person();
const person2 = new Person();
person1.hobby.push('ss');
// 这个时候person2子类拿到的 hobby 也会改变

如何解决上面的问题呢?

构造函数继承 : 在子类构造函数内部调用父类构造函数

function People(arg) {
  this.name = 'lol';
  this.hobby = ['s'];
}
People.prototype.getName = function() {
  return this.name;
}
// Person 类
function Person(arg) {
  // 构造函数内部 this 指向实例
  People.call(this, arg);
}
// Person 继承 People 类
const person = new People();
// 解决了共享属性问题&传参问题

上面People对象上的方法getName又无法被读取继承,如何解决呢?

组合继承

function People(arg) {
  this.name = '灭霸';
  this.hobby = ['eat'];
}
People.prototype.getName = function() {
  return this.name;
}
// Person 类
function Person(arg) {
  People.call(this, arg);
}
Person.prototype = new People();
// 让Persond的构造函数指针指向自己
Person.prototype.constructor = Person;
// Person 继承 Person 类
const person = new Person();

组合继承的缺点:每次使用都会调用两次父类构造函数

  • Person.prototype = new People()
  • const person = new Person() // People.call(this, arg)

寄生组合继承

function People(arg) {
  this.name = '灭霸';
  this.hobby = ['eat'];
}
People.prototype.getName = function() {
  return this.name;
}
// Person 类
function Person(arg) {
  People.call(this, arg);
}
// 使用Object.create创建一个新对象,并让新对象的__proto__指向People的prototype,既保证了继承又不会执行父类 People 的构造函数
Person.prototype = Object.create(People.prototype);
Person.prototype.constructor = Person;
// Person 继承 Person 类
const person = new Person();

image.png

扩展小知识:js 如何实现多重继承

function People(arg) {
  this.name = '灭霸';
  this.hobby = ['eat'];
}
People.prototype.getName = function() {
  return this.name;
}

function War() {
  this.weapon = 'Ak47';
}
War.prototype.getWeapon = function() {
  return this.weapon;
}
// 想要同时继承 People 和 War 的子类 Person
function Person(arg) {
  People.call(this, arg);
  War.call(this, arg);
  
}
Person.prototype = Object.create(People.prototype);
// Person.prototype = Object.create(War.prototype);// 这种写法太没意思了吧,失去了封装多重继承的意义
// 使用 Object.assgin 合并原型上的属性
Object.assign(Person.prototype, War.prototype);
Person.prototype.constructor = Person;
// Person实例化
const person = new Person();
person.getName()
person.getWeapon()

写文章好累啊,走过路过点个赞吧🧡💛💚💙💜💜💗💖💘💝💔

image.png