zoukankan      html  css  js  c++  java
  • 学习笔记—前端基础之构造函数与类

    日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。

    构造函数

    new 关键字来调用的函数,称为 构造函数

    构造函数中一般有两个属性,一个是 原型上的属性,一个是 实例上的属性

    function Animal(name) {
      this.name = name;
      this.arr = [1, 2, 3]
    }
    let a1 = new Animal('小狗');
    let a2 = new Animal('小猫');
    console.log(a1.arr === a2.arr); // false
    

    实例的属性指向不同的存储空间(堆内存),所以输出结果是 false,也就是实例本身的属性。

    通过定义原型上的属性,可以使实例拥有 原型上的属性

    Animal.prototype.address = {location:'家里'}
    console.log(a1.address === a2.address) // true
    

    原型与原型链

    首先我们要清楚 类(构造函数)原型constructor 之间的关系。

    • 每一个类(函数)都具有 prototype,并且属性值是一个 Object
    • 对象上天生具有一个属性 constructor,指向类本身
    • 每一个对象(普通对象prototype实例函数 等 )都具备 __proto__
    function Foo() {...};
    let f1 = new Foo();
    

    __proto__:当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__ 属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null,真正的空值。

    prototype:包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的 prototype 对象。

    constructor:指向该对象的构造函数,每个对象都有构造函数。若对象本身不具备constructor属性,则会通过__proto__向原型链进行查找,找到原型链中constructor后,确定其指向,并进行继承。

    原型

    关于 原型链查找机制,我个人是这么理解的。

    首先实例的__proto__会始终指向其构造函数的prototype属性(f.__proto__ === Fn.prototype),构造函数和其所有父类(FnFunctionObject)均指向Function.prototypeFn.prototype.__proto__指向的是Object.prototype,而Function.prototype.__proto__指向Object.prototypeObject.prototype.__proto__指向null,就是此原型链的终点。

    原型链

    关于原型链,可以将上图好好理解一下,这张图更直观的表述了prototype__proto__constructor 之间的关系。没事的时候也可以将他们画一下。

    类的继承

    构造函数 其实 就是类 的一种。

    function Animal(name) {
      this.name = name;
      this.eat = '吃肉';
    }
    Animal.prototype.address = {location: '山里'}
    function Tiger(name) {
      this.name = name;
      this.age = 10;
    }
    Tiger.prototype.say = function () {
      console.log('说话');
    }
    

    在这里我们模拟一个 父类 Animal 和一个子类 Tiger

    1. 继承父类实例上的属性

      我们只需要子类上加一个 .call 改变一下 this 的指向即可

      function Tiger(name) {
        this.name = name;
        this.age = 10;
        Animal.call(this); //将父类的this指向子类,使子类继承父类中的属性。
      }
      let tiger = new Tiger();
      console.log(tiger.eat); // 吃肉
      
    2. 继承父类原型上的方法

      上述做法我们只继承了父类实例上的属性,并没有继承其原型上的属性。

      console.log(tiger.address); // undefined
      

      这里我们有这么几种解决方案

      • 父类.prototype.__proto__ = 子类.prototype

        我们的子类和父类的 prototype 分别指向不同的方法和对象。所以我们为了使子类的原型继承父类原型上的方法,可以让子类的 prototype.__proto__ 指向父类的 prototype

        Tiger.prototype.__proto__ = Animal.prototype;
        console.log(tiger.address); // {location: '山里'}
        

        这样我们就实现了其中一种继承方法。

      • Object.create

        Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

        Tiger.prototype = Object.create(Animal.prototype);
        console.log(tiger.address); // {location: '山里'}
        
      • Object.setPrototypeOf

        使用 Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null

        Object.setPrototypeOf(Tiger.prototype, Animal.prototype);
        

      注:以上方法需要在子类的原型方法绑定前添加。

      这种方法我们无法向父类传参,只能给子类传参。

    ES6中的类(class)

    首先,ES6与ES5类的实现思路相同,同样是利用 原型链 来进行实现的。

    class Animal {
      constructor(name) {
        this.name = name;
        this.eat = '吃肉';
      }
      say() {
        console.log('say');
      }
    }
    

    上面就是最简单的一种类的实现方式。

    但是 ES6的类 和ES5有几点不同

    • 类不可以被当做函数调用。

      Animal() // Class constructor Animal cannot be invoked without 'new'
      

      ES6的类需要使用new作为关键字来进行实例化

      同理,如果我们调用原型上的方法,可以将类实例化出来后,直接进行调用。

      let a = new Animal();
      a.__proto__.say(); // say
      

      注:ES6规范中,若单独调用原型上的方法,this是不存在的

      比如我们直接将原型上的方法实例化出来

      // 暂时修改一下Animal类上的say()方法,测试完后再将此方法改回
      say() {
        console.log(this);
      };
      
      let say = a.__proto__.say;
      say(); // undefined
      

      返回的结果就是 undefined

    • 包含静态方法(ES7中的静态属性)

      ES6中允许类存在 私有方法(ES7中的私有属性)

      class Animal {
        // ES6
        static flag() {
          return 'test';
        }
        // ES7
        static flag = 'test'
      }
      console.log(Animal.flag()) // test
      

      注:在ES6的环境中,不能使用ES7的写法

      调用时,需要直接用类来进行调用,实例不能进行调用。

    • 使用 extends 关键字实现继承

      extends 可以直接实现继承。

      class Tiger extends Animal {}
      let t = new Tiger('老虎')
      t.say(); // say
      console.log(t.eat); // 吃肉
      console.log(t.name); // 老虎
      

      首先,父类的方法与原型 直接继承给了子类。

      随后,传递的值 老虎 ,被直接传递给了父类的 constructor ,直接输出了结果。

      class Tiger extends Animal {
          constructor(){ } // 报错
      }
      

      这里我们还有一个需要注意的,父类存在自己的 constructor ,子类不能再定义 constructor

      如果想要实现此功能,需要使用关键字 super

      class Tiger extends Animal {
        constructor(name){ // Animal.call(this, name) 
          super(name)
        }
      }
      

      提示:我们可以去 babel 上查看ES6中class的实现方式,加深对class的理解

    本篇文章由莫小尚创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
    您也可以关注我的 个人站点博客园掘金,我会在文章产出后同步上传到这些平台上。
    最后感谢您的支持!

  • 相关阅读:
    织梦dedecms上传漏洞uploadsafe.inc.php修复
    dedecms漏洞修复大全含任意文件上传漏洞与注入漏洞
    DEDECMS批量导入excel数据到后台文章系统的开发教程
    使用DEDE织梦计划任务功能定时更新首页
    如何解决织梦DedeCms文章标题字数长度限制的方法教程
    织梦后台点击网站主页跳转到../index.php?upcache=1删除方法
    dedecms漏洞修复大全含任意文件上传漏洞与注入漏洞
    dedecms模板中联动菜单高级使用技巧
    解决dede的loop中无法使用limit的方案+文章前数字序号
    DEDECMS 又一种隔行换色和分组加线的方法
  • 原文地址:https://www.cnblogs.com/moxiaoshang/p/14916742.html
Copyright © 2011-2022 走看看