• javascript 面向对象编程


    面向对象编程有三个特点: 继承,封装,多态

    1.继承

    ES6之后,一般就是类的继承。在类之前是构造函数的继承。

    构造函数:静态属性和方法,原型对象属性和方法,构造函数内this定义的属性和方法。

    对于静态属性的继承。

    for(let key in SuperClass) {
      if(SuperClass.hasOwnProperty(key)) {
        SubClass[key] = SuperClass[key];
      }
    }

    其他的属性和方法的继承方法有如下:

    1. 原型链继承-子类原型对象

    将父类的实例赋值给子类的原型对象。

    优点

    父类的属性和方法都能复用

    缺点:

    1. 子类实例化时可以向父类构造函数s a z传参进行初始化

    2.父类上的引用类型的属性会被子类的实例共享。一个修改,引起其他实例访问的时候是修改后的属性。

    语法:

    SubClass.prototype = new SuperClass();
    // 重新指定构造函数属性,否则它指向SuperClass
    SubClass.prototype.constructor = SubClass;

    示例:

    function SuperClass() {
      this.superValue = [1];
    }
    SuperClass.prototype.getSuperValue = function() {
      console.log('super');
    }
    
    function SubClass() {
      this.subValue = [2];
    }
    // 父类的实例既包含父类构造函数内的属性和方法,也包含原型对象上的属性和方法
    SubClass.prototype = new SuperClass();
    SubClass.prototype.constructor = SubClass;
    
    // 缺点1,公共属性superValue是引用类型,实例访问属性的时候访问的是原型对象上的属性
    const instance1 = new SubClass();
    instance1.superValue.push('a'); 
    console.log(instance1.superValue); //[1,'a']
    instance1.getSuperValue(); //super 可以访问原型链上的方法
    
    //在instance2未进行任何操作时访问superValue
    const instance2 = new SubClass();
    console.log(instance2.superValue); // [1,'a']

    2. 构造函数继承-创建即继承

    在子类构造函数中调用父类的构造函数。将父类构造函数上的属性和方法复制到子类实例中。

    优点: 

    1. 实例化子类时可以给父类构造函数传参进行初始化

    2. 父类上的引用类型的属性不会被子类的实例共享。

    缺点:

    1. 无法继承原型链上的方法

    2.如果想要继承原型链上的方法,必须将原型链上的方法放到构造函数中。违背了“代码共享”的原则。

    语法:

    function SubClass(props) {
      SuperClass.call(this, props);// 构造函数继承
    }

    示例:

    function SuperClass(id) {
      this.id = id;
      this.books = ['js', 'html'];
    }
    SuperClass.prototype.getSuperBooks = function() {
      console.log(this.books);
    }
    
    function SubClass(props) {
      SuperClass.call(this, props);// 构造函数继承!
    }
    
    const instance1 = new SubClass(11);
    instance1.books.push('css');
    console.log(instance1.books);// ['js', 'html', 'css']
    console.log(instance1.id); // 11 -向父类构造函数传参并初始化
    // 未继承原型链上的方法
    instance1.getSuperBooks(); // ❌instance1.getSuperBooks is not a function
    
    const instance2 = new SubClass(12);
    console.log(instance2.books); // ['js', 'html'] 不受其他实例影响
    console.log(instance2.id); // 12

    3. 组合继承-原型链+构造函数

    融合了原型链继承和构造函数继承的优点。

    缺点:

    父类的构造函数调用了两次,浪费性能

    语法:

    function SubClass(props) {
      SuperClass.call(this, props); // 第二次调用父类构造函数
    }
    SubClass.prototype = new SuperClass(); // 第一次调用父类构造函数
    SubClass.prototype.constructor = SubClass;

    示例:

    function SuperClass(name) {
      console.log(`super call->${name}`);
    }
    
    function SubClass(name) {
      // 将构造函数内部的属性和方法直接绑定到子类的实例上
      SuperClass.call(this, name);
    }
    SubClass.prototype = new SuperClass(['proto', 'Call']);//第一次
    SubClass.prototype.constructor = SubClass;
    
    const instance1 = new SubClass(['proto', 'Call']);// 第二次
    
    // 运行结果⚠️字符串模版将数组转为字符串
    // super call-> proto,Call
    // super call-> new,Call

    4. 原型式继承

    使用Object.create()方法创建以目标对象为原型的实例对象。

    语法:

    //以目标对象为原型对象创建一个实例对象;实例对象只含原型对象上的方法和属性
    const obj = Object.create(targetObject);

    5. 寄生式继承

    在原型式继承的继承上修改生成的实例对象;

    语法:

    const obj = Object.create(targetObject);
    obj.newProp = 'hello world';

    6. 寄生式组合继承

    在组合继承的继承上,加入寄生式继承。

    优点:

    解决了组合继承调用两次父类构造函数的问题。

    语法:

    function SubClass(props) {
      SuperClass.call(this, props);
    }
    const obj = Object.create(SuperClass.prototype);
    obj.constructor = SubClass;
    SubClass.prototype = obj;

    示例:

    function SuperClass(props) {
      console.log('super call-->'+props);
    }
    SuperClass.prototype.getName =function() {
      console.log('superClass-prototype-fn');
    }
    
    function SubClass(props) {
      SuperClass.call(this, props);
    }
    const obj = Object.create(SuperClass.prototype);
    obj.constructor = SubClass;
    SubClass.prototype = obj;
    
    const instance = new SubClass('newCall');
    instance.getName();
    
    // 运行结果
    // super call-->newCall  只调用一次
    // superClass-prototype-fn  能够访问原型对象上的方法

    7. 多重继承(Mixin模式)

    即一个构造函数继承多个构造函数。

    function Child() {
        Father1.call(this);
        Father2.call(this);
    }
    Child.prototype = Object.create(Father1.prototype);
    Object.assign(Child.prototype, Father2.prototype);
    Object.prototype.constructor = Child;

    8. 类的继承

    1. 子类的实例this的生成基于父类的实例,所以必须先调用super(),获取父类实例。之后才能使用this。

    2. 类的继承还会继承静态属性和方法

    class Father {}
    class Child extends Father{ constructor(props) { super(props);
    // =Father.prototype.constructor.call(this,props) } } // super的用法: 1)作为父类构造函数,只能用在构造函数中 2)作为原型对象,在普通函数中:super.xxx(); super相当于父类的原型对象(super.prototype.xxx.call(this)),里面的this指向子类实例。 取不到子类的实例上面的属性和方法!! 3)作为父类,在静态方法中使用super,相当于父类,里面的this指向子类。

    类的继承的实现原理:

    Object.setPrototypeOf(Child, Father);
    Object.serPrototypeOf(Child.prototype, Father.prototype);

    可推导出:

    Child.__proto__ === Father;
    Object.getPrototypeOf(Child) === Father;
    Child.prototype.__proto__ === Father.prototype;
    Object.getPrototypeOf(Child.prototype) === Father.prototype;

    9. 类的多重继承

    本质上是将多个被继承类的属性遍历复制到目标。

    function mix(...mixins) {// 多个类
      class Mix {
        constructor() {
          for (let mixin of mixins) {
            copyProperties(this, new mixin()); // 拷贝实例属性
          }
        }
      }
    
      for (let mixin of mixins) {
        copyProperties(Mix, mixin); // 拷贝静态属性
        copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
      }
    
      return Mix;
    }
    
    function copyProperties(target, source) {// 将被继承类的属性和方法复制到this上
      for (let key of Reflect.ownKeys(source)) {
        if ( key !== 'constructor'
          && key !== 'prototype'
          && key !== 'name'
        ) {
          let desc = Object.getOwnPropertyDescriptor(source, key);
          Object.defineProperty(target, key, desc);
        }
      }
    }

    2. 封装

    ES5中没有模块的概念,可以模拟实现将代码封装成模块。

    1. ES5封装

    将方法和属性封装在构造函数中。

    2. ES6封装

    将方法和属性封装在类中。

    3. 多态

    同一个函数的多种调用形式。根据传参不同,实现逻辑不同。

    function sum(...args) {
      const len = args.length;
      if(len === 1) {
        return 'no need'
      } else if(len === 2) {
        return args.reduce((a,b) => a+b);
      } else if(len === 3) {
        return args.reduce((a,b) => a*b)
      } else {
        return 'end'
      }
    }
    console.log(
      '
    ', sum(1), 
      '
    ', sum(1,2),
      '
    ', sum(1,2,3),
      '
    ', sum(),
    );
    // 结果如下:
    no need
    3
    6
    end
  • 相关阅读:
    gitbook
    Goland IDE使用
    go-zero RPC 框架安装 (goctl安装, protoc安装, etcd安装)
    go 打包部署
    GO redis
    go 常见异常
    go 异常处理
    go常用数据处理 (json, map, 结构体)
    Kafka日志消息
    【leetcode_easy_math】1317. Convert Integer to the Sum of Two No-Zero Integers
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11731224.html
Copyright © 2020-2023  润新知