Js基础之 面向对象编程

166 阅读5分钟

本文以对象、原型、继承为线索编写,其中对象分为以下三种:

简单对象:是一种基本的数据结构,由属性和方法组成,用于封装一组相关的数据和行为。函数对象:是一种具有特定功能的对象,通常用来封装一组操作或行为,并且可以被调用执行。类对象:是ES6中新增的概念,用于创建具有相同属性和方法的对象实例,通过类可以更方便地实现对象的创建和管理。

其次,原型和原型链是重要的概念:原型:每个对象都有一个指向其原型对象的内部链接,原型对象包含对象的共享属性和方法。原型链:是通过原型对象之间的链接实现继承的一种机制,当访问对象的属性或方法时,JavaScript引擎会沿着原型链向上查找,直到找到对应的属性或方法。

最后,继承分为以下三种:经典继承:通过在子类的构造函数内部调用父类的构造函数,实现子类继承父类的属性。组合继承:结合原型链和经典继承的方式,解决了原型链上的共享属性和方法无法被读取继承的问题。寄生组合继承:是组合继承的一种优化方式,避免了多次调用父类构造函数的问题,提高了性能。

面向对象编程

对象作为容器,封装属性和方法,属性封装对象的状态,方法封装对象的能力和行为。

简单对象、函数对象、类对象

简单对象

    const Course = {
        teacher: 'yunyin',
        leader: 'xh',
        startCourse: name => {
            return `开始${name}课`;
        }
    }
    // A
    Course.teacher = 'xxxx';
    Course.startCourse('react');
    // B
    Course.startCourse('vue');

函数对象

    function Course() {
        this.teacher = 'yunyin';
        this.leader = 'xh';
        this.startCourse = name => {
            return `开始${name}课`;
        }
    }

上述函数,本质就是构造函数:1.函数体内使用this,指向所要生成的实例;2.生成对象用new来实例化;3.可以初始化传参。

函数对象的constructor是什么?

每个对象在创建时,会自动拥有一个构造函数属性constructor,每个constructor源自原型对象,指向了构造函数的引用,即对应的表达式为:A.prototype.constructor = A;

类对象

    class Course {
        constructor(teacher,leader){
            this.teacher = teacher;
            this.leader = leader;
        }
        startCourse(name){
            return `开始${name}课`;
        }
    }
    
    var course1 = new Course('vue');
    course1.startCourse('vue');

类对象是ES6标准中新定义的,其本质还是函数对象。放在class对象中的公共函数,对应放在函数对象的原型prototype上。类就是对象模版!Js本质并不是基于类,而是基于构造函数+原型链。

如果构造函数不初始化,可以使用具有相同能力?

无法具有。构造函数初始化指的是在创建对象的时候,使用构造函数来为对象的属性进行初始化的过程,在Js中使用new关键字调用构造函数时,会创建一个新的对象,并且调用构造函数来初始化这个对象的属性。

构造函数初始化的过程通常包括以下步骤: 在构造函数内部,使用this关键字来引用将要初始化的对象; 在构造函数内部,为对象的属性赋予初始值或执行其他必要的操作; 当使用 new 关键字调用构造函数时,会创建一个新的对象,并且调用构造函数来初始化这个对象的属性。构造函数执行完毕后,会返回新创建的对象实例。

如果在项目中需要使用,且不希望外界进行感知情况下。如何让外界直接拿到实例化后的对象?
    function Course() {
        const _isClass = this instanceof Course;
        if (!_isClass) {
            return new Course();
        }

        this.teacher = 'yunyin';
        this.leader = 'xh';
        this.startCourse = name => {
            return `开始${name}课`;
        }
    }

    // 使用方
    const course = Course();
    // 启发:编写底层api代码时,尽量做到不让外部去感知区分内部类型
引发思考: new是什么?/ new的原理?/ new时候做了些什么?
    function Course() {};
    const course = new Course();
      1. 结构上:创建了一个空对象,作为返回的对象实例
      1. 属性上:将生成空对象的原型对象指向了构造函数的prototype属性
      1. 关系上:将当前实例对象赋给了内部的this
      1. 生命周期上:执行了构造函数的初始化代码
    function usernew(obj, ...args) {
        const newObj = Object.create(obj.prototype);
        const result = obj.apply(newObj, args);

        return typeof result === 'object' ? result : newObj;
    }

原型和原型链

原型

  • 原型对象
    function Course() {}
    const course1 = new Course();
    const course2 = new Course();
    * 1. Course - 用来初始化创建对象的函数 | 类
    course1.__proto__ === Course.prorotype

    * 2. course1 - 根据原型创建出来的实例
    course1.constructor === Course
prototype是什么?
    function Course() {
        this.teacher = 'yunyin';
        this.leader = 'xh';
    }
    const course1 = new Course();
    const course2 = new Course();

    Course.prototype.startCourse = name => {
        return `开始${name}课`;
    }
原型对象有自己的原型吗?

course1.proto.proto === Object.prototype

Course.prorotype.proto === Object.prototype

course1.proto.proto.proto === null

原型链

原型链:【属性查找】js中实现继承的一种机制,每个对象都有一个指向其原型对象的内部链接,这个链接称为原型链。当访问一个对象的属性时,如果对象本身没有这个属性,js就会顺着原型链向上查找,直到找到属性或原型链顶端。

原型链详解: 对象:People

【People.prototype = People.prototype】

对象原型:People.prototype

【People.proyotype.constructor = People】

对象实例:people

【people = new People()】

【people.proto = People.prototype】

继承

js如何实现继承

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

    function Game() {
        this.name = 'lol';
    }
    Game.prototype.getName = function() {
        return this.name;
    }

    // LOL
    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;
    }

    // LOL
    function LOL() {};
    LOL.prototype = new Game();
    LOL.prototype.constructor = LOL;
    const game1 = new LOL();
    const game2 = new LOL();
    game1.skin.push('ss');
    // 本质:重写了原型对象方式,将父对象的属性方法,作为自对象原型对象的属性方法,同时重写构造函数
    1. 父类属性一旦赋值给到子类的原型属性,此时属性属于子类的共享属性了
    1. 实例化子类时,无法向父类进行传参

解决方法:构造函数继承

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

    function Game(arg) {
        this.name = 'lol';
        this.skin = ['s'];
    }
    Game.prototype.getName = function() {
        return this.name;
    }

    function LOL(arg) {
        Game.call(this, arg);
    }

    const game3 = new LOL('arg');
    // 解决了共享属性问题 + 子向父传参的问题
追问:原型链上的共享方法无法被读取继承,如何解决?
  • 组合继承
    function Game(arg) {
        this.name = 'lol';
        this.skin = ['s'];
    }
    Game.prototype.getName = function() {
        return this.name;
    }

    function LOL(arg) {
        Game.call(this, arg);
    }
    LOL.prototype = new Game();
    LOL.prototype.constructor = LOL;
    const game4 = new LOL('arg');
追问:组合继承方式就没有缺点吗?

问题在于:无论何种场景,都会调用两次父类的构造函数

  • 寄生组合继承
    function Game(arg) {
        this.name = 'lol';
        this.skin = ['s'];
    }
    Game.prototype.getName = function() {
        return this.name;
    }

    function LOL(arg) {
        Game.call(this, arg);
    }
    LOL.prototype = Object.create(Game.prototype);
    LOL.prototype.constructor = LOL;
    const game5 = new LOL('arg');
如何实现多重继承?
    function Game(arg) {
        this.name = 'lol';
        this.skin = ['s'];
    }
    Game.prototype.getName = function() {
        return this.name;
    }

    function Store() {
        this.shop = 'steam';
    }
    Game.prototype.getPlatform = function() {
        return this.shop;
    }

    function LOL(arg) {
        Game.call(this, arg);
        Store.call(this, arg);
    }

    LOL.prototype = Object.create(Game.prototype);
    Object.assign(
        Store.prototype,
        LOL.prototype
    );
    LOL.prototype.constructor = LOL;