面向对象

76 阅读5分钟

课程目标

  • 面向对象
  • 原型和原型链

知识点

  • 对象是什么
  • 构造函数
  • 原型对象
  • 实现继承以及不同继承方式

对象是什么?为什么要面向对象

特点:面向对象---逻辑迁移更加灵活、代码复用性高、高度的模块化

对象的理解

  • 对象是对于物体的简单抽象
  • 对象是一个容器,封装了属性 & 方法
  • 属性:对象的状态
  • 方法:对象的行为
//简单对象
const pet = {
    name: '小菜',
    age: 18
    getName: function(name){
        return name;
    }
}
//函数对象
function pet(name) {
    this.name = '小菜'
    this.age = '18'
    this.getName = function(name) {
        return name;
    }
}

构造函数--生成对象

  • 需要一个模板--表征了一类物体的共同特征,从而生成对象
  • 类即对象模板
  • js其实本质上并不是基于类,而是基于构造函数 + 原型链
  • constructor + prototype
function Course(name){
    this.name = name;
    this.age = 18;
    this.getName = function(name) {
        return name;
    }
}
const course = new Course('小菜');
  • Course 本质就是构造函数
  1. 函数体内使用的this,指向所要生成的实例
  2. 生成对象用new来进行实例化
  3. 可以做初始化传参

提问:如果构造函数不初始化,可以使用吗?-- 答:无法使用

提问:如果项目中需要使用,通常(不被外界感知)如何解决?

function Course(){
    //
    const _isClass = this instanceof Course;
    if(!_isClass) {
        return new Course();
    }
    this.name = '小菜';
    this.age = 18;
    this.getName = function(name) {
        return name;
    }
}
const course = new Course();
  • 启发:如果编写底层的api代码时,尽量做到不需要让外部感知内部类型

new是什么?| new 的原理 | new时候做了些什么?

  1. 创建了一个空对象,作为返回的对象实例
  2. 将生成空对象的原型对象指向了构造函数的prototype属性
  3. 将当前实例对象赋给了内部this
  4. 执行构造函数的初始化代码
提问:实例属性影响?-- 每个实例之间的属性是独立的,因为内部this指向

constructor是什么?

  1. 每个对象在创建时,会自动拥有一个构造函数属性constructor
  2. constructor继承自原型时,指向了构造函数的引用
提问:使用构造函数。没有问题吗?| 会有什么性能上的问题?

构造函数中的方法,会存在于每一个生成的实例里,重复的挂载其实是会导致资源浪费

原型对象

function Course(){}
const course1 = new Course();
const course2 = new Course();
  1. 构造函数:用来初始化创建对象的函数 -- Course
  • 自动给构造函数赋予了一个属性prototype,该属性等于实例对象的原型对象
  1. 实例对象:conrse1是实例对象,根据原型对象创建出来的实例
  • 每个对象中都有一个_proto_
  • 每个实例对象都有一个constructor
  • constructor有继承而来,并指向当前的构造函数 image.png
  1. 原型对象:Course.prototype
function Course(){}
Course.prototype.name = '小菜'const course1 = new Course();
const course2 = new Course();

//刚提到的使用构造函数会重复的挂载现在就可以解决了
//方法挂载于prototype上,属性也可以挂载在prototype上
function Course(){
    this.name = name;
    this.age = 18;
}
Course.prototype.getName = function(name){
    return name;
}
const course1 = new Course('小菜');
const course2 = new Course('小心');

继承

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

function Game(){
    this.name = 'lol';
}
Game.prototype.getName = function() {
    return this.name
}
function LOL() {}
//重写原型对象方式,本质是将父对象的属性方法,作为子对象原型对象的属性和方法
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game = new LOL();

提问:原型链继承有什么缺点呢?

function Game(){
    this.name = 'lol';
    this.skin = ['s']
}
Game.prototype.getName = function() {
    return this.name
}
function LOL() {}
//重写原型对象方式,本质是将父对象的属性方法,作为子对象原型对象的属性和方法
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');

看下game1的输出结果:

image.png

是没问题的,那么看下game2

image.png

那么问题就来了,修改game1的时候父级的Game被修改,game2拿的就是Game被修改后的值。

  1. 父类属性一旦赋值给子类的原型对象,此时属性属于子类的共享属性
  2. 实例化子类时,无法向父类做传参

解决方法:构造函数继承

(经典继承):在子类构造函数内部调用父类构造函数

function Game(){
    this.name = 'lol';
    this.skin = ['s']
}
Game.prototype.getName = function() {
    return this.name
}
function LOL(arg) {
    //子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参
    //问题(arg)
    console.info(arg);
    Game.call(this, arg);
}
//或者用class实现
class LOL extends Game{
    constructor (){
        super();
    }
}
const game3 = new LOL('arg');

追问:原型链上的共享方法无法被读取继承,如何解决?

解决方案:组合继承

function Game(){
    this.name = 'lol';
    this.skin = ['s']
}
Game.prototype.getName = function() {
    return this.name
}
function LOL(arg) {
    //子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
    //问题(arg)
    Game.call(this, arg);//第二次调用父类
}
LOL.prototype = new Game();//第一次调用父类
LOL.prototype.constructor = LOL;
const game = new LOL();

追问:组合继承还有什么缺点?问题在于:无论何种场景都会调用两次父类构造函数

1.初始化子类原型时 2.子类调用函数内部call父类的时候

解决方案:寄生组合继承

function Game(){
    this.name = 'lol';
    this.skin = ['s']
}
Game.prototype.getName = function() {
    return this.name
}
function LOL(arg) {
    //子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
    //问题(arg)
    Game.call(this, age);
}
LOL.prototype = Object.create(Game.prototype);
LOL.prototype.constructor = LOL;

提高:如何实现多重继承?

function Game(){
    this.name = 'lol';
    this.skin = ['s']
}
Game.prototype.getName = function() {
    return this.name
}
function Store() {
    this.shop = 'steam';
}
Store.prototype.getPlatform = function() {
    return this.shop;
}
function LOL(arg) {
    //子类执行父类,构造函数的this指向了实例,就解决了父类共享属性的问题,以及子向父传参的
    //问题(arg)
    Game.call(this, arg);
    Store.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(LOL.prototype, Store.prototype);
LOL.prototype.constructor = LOL;
const game = new LOL();

输出结果:game 包含了Game、Store两个父类里属性,如下图:

image.png