JavaScript进阶: 1.2 OOP与继承

本文详细探讨了JavaScript中的面向对象编程,涉及原型与原型链、继承机制,以及多种对象创建方法(如`Object.create()`),比较了构造函数继承、组合继承和组合寄生式继承,并讨论了React和Spring中的面向对象思想。
摘要由CSDN通过智能技术生成
知识体系

在这里插入图片描述

核心知识点

JS面向对象编程

原型与原型链

继承

面向对象

编程思想
  • 面向过程-C

    // 面向过程-C
    const nameObject = {
      nameList: [
        {text: 'lu'},
        {text: 'li'},
        {text: 'zhang'},
      ]
    }
    
    export function getUserName(obj) {
      const text =  obj.nameList.reduce((total, item) => {
        // 之前累加的数据
        console.log('total:', total);
        // 当前的数据
        console.log('item:', item);
        return `${total.text}.${item.text}`;
      });
      return text;
    }
    getUserName(nameObject);
    
    export function getUserSex(obj) {
      if(obj.hasSth) {
        return 'MALE';
      }else {
        return 'FEMALE';
      }
    }
    /* 面向过程-getUserName与getUserSex都是一个对数据的pipeline,只是一个单独对数据的处理过程 */
    

    eg:

    // 面向过程实例
    init();
    whitePlay();
    repaint();
    
    check();
    
    blackPlay();
    repaint();
    check();
    
  • 面向对象-Java

    // 面向对象-Java
    class User {
      constructor(obj){
        this.obj = obj;
      };
      getName(){
        this.obj.nameList.reduce((total, item) => `${total.text}.${item.text}`);
      };
      getSex(){
        if(this.obj.hasSth){
          return "MALE";
        }else {
          return "FEMALE";
        }
      }
    };
    
    const user = new User(obj);
    user.getName();
    user.getSex();
    /* 面向对象-构建了一个抽象的东西-对对象的属性与方法进行了封装 */
    

    eg:

    // 面向对象实例
    const checkerBoard = new CheckerBoard();
    const whitePlayer = new Player('white');
    const blackPlayer = new Player('black');
    
    whitePlayer.start();
    checkerBoard.repaint();
    checkerBoard.check();
    
    blackPlayer.start();
    whitePlayer.rollback();
    
    • React中面向对象思想

      /* React中面向对象思想 */
      class Button extends Component {
        render() {
          return <div></div>
        }
      }
      
      new Button().render();
      
      function Card() {
        return <div></div>
      }
      Card();
      // 每次render的时候函数都要执行,因此用useMemo和useCallback控制执行时机
      
  • 面向切面-Spring

JS中的面向对象

JS中创建对象的三种方式:

Object.create()
var bar = {}
var p = new Person();

三种方式创建原型链示意图

在这里插入图片描述

  • Object.create / {}

    • Object.create() 与{}的区别

      单原型链与多原型链的区别

      // 创建
      var bar = {};
      var baz = Object.create({});
      //原型链属性关系
      bar.prototype === Object.prototype;
      bar.prototype.constructor === Object;
      Object.prototype.isPrototypeOf(bar);
      //两种创建方式的区别
      bar.__proto__ === Object.prototype;
      baz.__proto__.__proto__ === Object.prototype;
      
    • 用Object.create()创建出与var bar = {}相同效果的对象

      console.log(Object.create({}));//{}
      // Object.create({});本身创建的结果就是一个{}, 此处产生一个原型链,
      // var baz = {}; 又创建了一个对象,又产生了一个原型链
      // 所以经过两个原型链之后指向同一个对象
      var foz = Object.create(Object.prototype);
      // 创建了一个单层链表的对象,通过Object.prototype
      foz.toString();
      // 因为foz通过原型链继承了Object的属性与方法,因此可以直接调用父类Object的属性方法
      
    • 创建null对象-null对象无原型链

      /*-- 使用Object.create(null)创建的null对象是没有原型链的--*/
      var foo = Object.create(null);
      // 此处创建了一个null对象,什么属性都没有,连原型链都没有
      // 而null对象无属性无原型链,因此需要调用的话,用call借用
      Object.prototype.toString.call(foo);
      /*-- 使用const bar = {}创建的对象是有原型链的--*/
      const bar = {};
      bar.toString();
      
      /* 第一反应没反应过来的问题 */
      let v = {};
      Object.create(v.__proto__) === Object.create(Object.prototype);//false
      // 因为创建的两个对象,地址不对
      
    • –proto–

      本质是原型链关系,JavaScript这门语言,本质设计的时候,就是原型链关系。

      A.proto === B 的时候。

      A.xxx, 如果 A上面没有xxx, 我可以去 B上面去找,如果B上面有,我就用 B 的。

  • new关键字

    • 使用

      function Person(name, age) {
          this.name = name;
          this.age = age;
          // this.e = function() {}
          
      };
      // 为什么不直接挂在Person上?
      // 对象实例化之前就已经确定具备的能力,而直接加在Person上,实例化时每个实例重复拥有相同的能力,浪费内存
      // 既然能力在实例化对象之前就具备,那么直接从原型上取就可以
      Person.prototype.eat = function() {
      
      };
      
      Person.prototype.walk = function() {
      
      };
      
      const p = new Person("麓一", 35);
      
      // 三件事
      // 1. new 创建了一个对象,这个对象的原型链,指向了构造函数的原型。
      p.__proto__ === Person.prototype;
      
      // 2. Person 有个原型,原型上有个constructor, 是 Person 自己
      Person.prototype.constructor === Person;
      
      // 3. p 这个对象,是构造函数构造的,p 的构造是谁呢?很显然,是 Person.
      p.constructor === Person;
      
      
      // p 要有能力
      p.eat();  p.walk();
      
    • 作用

      • 创建了一个对象;
      • 这个对象的原型,指向了这个 Person / Function 的prototype
      • 该对象实现了 person 的方法;
      • 根据一些特定情况返回对象
        • 如果这个构造函数没有返回值,或者返回一个非对象类型,则new 最后返回创建的这个对象(p);
        • 如果这个构造函数明确返回了一个对象,则返回这个对象;
      // 实现new关键字
      function newFunc(Person, ...rest) {
          if(typeof Person !== "function") {
              throw new Error('new operator function the frist param must be a function');
          }
          
          var obj = Object.create(Person.prototype);
          var result = Person.apply(obj, rest);
          return result && typeof result === 'object' ? result : obj;
      }
      

      实现object.create()

      // 犀牛书,例 6.1 
      function inherit(p) {
          if(p === null) throw TypeError();
          if(Object.create) {
              return Object.create(p)
          };
          
          if( typeof p !== "object" && typeof p !== "function") throw TypeError();
          
      	// 此处可能需要拐个弯,其实主要的作用就是将object.create({})的多原型链改为单原型链    
          function f() {};
          f.prototype = p;
          return new f();
      }				
      

继承

继承,是描述类和类之间的关系

原型继承
/*原型继承 */
function Parent(name) {
   this.name = name;
   this.nameList = ["lu", "yi"];
}
Parent.prototype.getName = function(){
  console.log(this.name);
}

function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

const c = new Child();
// Child没法传参,只能通过Person传参
// 如果Parent有属性是引用类型,一旦其中一个实例修改了该属性,全部实例包括父对象全部都受影响。
// 问题点演示
c.nameList.pop();
const c1 = new Child();
console.log(c1.nameList);
构造函数继承
/* 构造函数继承 */
function Parent(name) {
  this.name = name;
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child(name) {
  // 将父函数的属性都添加到Child中
  Parent.call(this, name);
}
const child = new Child();
// child的原型连指向Object,但是已经把Parent函数中的属性添加过来了
// 存在的问题:无法访问原型链
组合继承
/* 组合继承 */
function Parent(name) {
  this.name = name;
}

Parent.prototype.getUserName = function() {
  console.log(this.name);
}

function Child(name) {
  // 继承属性
  Parent.call(this, name);
}

// 继承原型链方法
Child.prototype = new Parent();
Child.prototype.constructor = Child;

const ch = new Child('李四');

// 存在的问题: new Parent()的时候只想构建一个原型链的关系,并不需要再将属性初始化一遍。
/* 
??将属性值重新初始化一遍会存在什么问题呢?
相比于组合继承,主要区别就是原型Parent是否带了属性字段,因为第一种是new Parent()实现的,所以属性值被初始化了。
第二种方法是直接构造原型链,所以并没有执行Parent(),所以其属性值没有初始化。
 */
组合寄生式继承
/* 组合寄生式继承 */
function Parent(name) {
  this.name = name;
}

Parent.prototype.getUserName = function() {
  console.log(this.name);
}

function Child(name) {
  Parent.call(this, name);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const chi = new Child('王五');
组合寄生与class的区别

loose 模式,babel 进行降级编译的时候,考虑到体积等,只实现核心功能

Loose 模式应该差不多,主要是这两个区别:

Class 继承,会继承静态属性;

子类中,必须在 constructor中调用 super, 因为子类自己的 this 对象,必须先通过父类的构造函数完成。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值