一文详解-es5原型和es6-class

Why You Must Stop Obsessing About These 5 Things in SEO

原型真的有用吗

有不少小伙子应该会有这个感觉 大家都在说原型 prototype 很重要,那为什么我却用不到?

原因不外乎这几个:

  1. 框架重度使用者,我们目前的前端主流业务, 几乎都是使用 vue,react,微信小程序在开发项目。这些框架封装得太过完美了,几乎不需要我们去做额外封装,哪怕有需求搞不定,变装一下,网络上寻求帮助,大把猥琐佬等着教你。
  2. 基础知识还不到家,都在使用框架做业务了,没有时间深入研究技术原理,也没有能力去封装造轮子
  3. 跳槽得太少了,没有怎么被面试官虐过,没有深刻体会过 面试造航母,工作拧螺丝 的快感。

于是乎,既然用不到,那就不用学,从自我做起,拒绝内卷。理解满分。。。。

然而残酷的真相是,只有技术是自己可以实实在在的去把控的,命运还是掌握在自己手中。

真相

刚刚想撸起袖子好好干, 一看这个神图。 “算了,上号吧”。

下面小弟尽量以最直白和简洁的图文给你梳理 ese5和原型之间的关系。

img

什么时候需要用到原型

封装!!! 当我们想抽象某些公共业务 方便复用或者使结构更加清晰的时候便会用到。 面向对象三大特征:

  1. 封装
  2. 继承 (我把继承也归类到封装里面)
  3. 多态

比如 我们想创建一个

  • 圆角的div标签
  • 点击一下自己,会变大变小

将圆角 和 点击缩放 看成是公共业务即可。

我们会这么写

2021-06-15124749


如果 我们这个时候想要创建一个图片标签,也是圆角的,也是可以点击放大缩小呢

直接写

2021-06-15125445.gif

那么我们可以看到 我们是相等于把代码复制了一次的

    const div = document.querySelector("div");
    div.onclick = function () {
      this.classList.add("scale");
    }

    // 同样给图片绑定事件
    const img = document.querySelector("img");
    img.onclick = function () {
      this.classList.add("scale");
    }
复制代码

上述代码没有体现出封装。 而且比较零散,代码和业务掺杂在一起了。(对于一些简单的业务,这么写是没有问题的,怎么简单直接怎么来。)

采用 封装后的写法

    // 实例化 div
    new CreateCurveElement("div");

    // 实例化 图片
    new CreateCurveElement("img", { src: "images/1.png" });


    // 构造函数
    function CreateCurveElement(elementName, option) {
      const element = document.createElement(elementName);
      element.onclick = function () {
        element.classList.add("scale");
      }
      option && Object.keys(option).forEach(key => element.setAttribute(key, option[key]));
      document.body.appendChild(element)
    }
复制代码

可以看到,以后想要创建带有 边框弯曲的元素,就直接 new 即可。 它有以下优势

  1. 复用了公共代码,如 createElementonclickclassList.add
  2. 隐藏了实现细节,让调用者只关注 业务本身,如 创建一个元素 new CreateCurveElement

有原型的什么事呢

上面的代码,也是存在弊端的,如 ,代码功能高度耦合,如果我们想要做任何功能的拓展的话,那么将会对代码结构产生破坏性的影响。因此,我们对于代码考虑的更多:

  • 能用
  • 性能好
  • 方便复用

基于以上需求,我们需要学习原型。

创建对象的方式

字面量

在js中,如果想要创建临时使用的对象,直接使用字面量方式即可。如

    const person = {
      name: '路飞',
      skill: "变大变小"
    }
复制代码

工厂函数

但是如果 我们想要创建多个类似结构的对象时,字面量的方式就不方便维护了。如

    const person1 = {
      name: '路飞',
      skill: "变大变小"
    }
    const person2 = {
      name: '金箍棒',
      skill: "变粗边长"
    }
    const person3 = {
      name: '熔岩巨兽',
      skill: "变壮变硬"
    }
复制代码

此时想要将 属性 name 修改为 username ,那么就需要挨个修改了。

因此我们可以使用工厂函数的方式:

    const person1 = createPerson('路飞', "变大变小")

    const person2 = createPerson('金箍棒', "变粗边长")

    const person3 = createPerson('熔岩巨兽', "变壮变硬")

    // 工厂函数
    function createPerson(name, skill) {
      return {
        name,
        skill
      }
    }
复制代码

此时,当我们想要修改 属性 name 时,直接修改 函数 createPerson 即可,干净利索。

构造函数

上述的工厂函数虽然解决了多个对象批量修改属性的问题,但是也是存在弊端的。请看以下的打印。

在javascript中,万物皆对象

    function createPerson(name, skill) {
      return {
        name,
        skill
      }
    }

    // 创建一个普通的对象
    const person1 = createPerson('路飞', "变大变小");
    console.log(person1);


    // 打印 字面量
    console.log({ name: "春卷", skill: "内卷" });


    // 创建一个日期对象
    const date = new Date();
    console.log(date);

    // 创建一个数组对象
    const array = new Array();
    console.log(array);
复制代码

image-20210615222924670

可以看到,js内置的对象 是有明显的标识的。如 Date 或者 Array,这些标识我们一看就明白。是日期数组

但是,我们自己创建的两个对象很明显,只有一个Object,而不具体其他的明显标识了。原因很简单

  1. Date,Array,FunctionRegexString,Number,Boolean 等都是 js亲 生的。
  2. 我们自己创建的对象 是野生的,所以不配有名字!

我不管,我也想要。

构造函数即可解决这个问题。

    function SuperPerson(name, skill) {
      this.name = name;
      this.skill = skill;
    }

    // 创建一个普通的对象
    const person1 = new SuperPerson('路飞', "变大变小");
    console.log(person1);
复制代码

image-20210615223740150

构造函数解析

  1. 构造函数也是一个函数
  2. 构造函数就是要被 new
  3. 构造函数内的 this 相等于 下面 person1
  4. 构造函数内不需要 return 默认就是 return this ;
  5. new 一次,就在内存中开辟一个新的空间。

构造函数的弊端

先看代码

    function SuperPerson(name) {
      this.name = name;
      this.say = function () {
        console.log("拒绝内卷,从" + this.name + " 做起");
      }
    }

    const p1 = new SuperPerson("路飞");

    const p2 = new SuperPerson("乔巴");

    console.log(p1 === p2); // false 

    console.log(p1.name === p2.name);   // false

    console.log(p1.say === p2.say); // false
复制代码

我们知道,数据类型比较的关键是

  1. 简单类型的比较 值比较 路飞乔巴

  2. 复杂类型的比较 引用地址比较

    1. p1p2
    2. p1.sayp2.say
  3. 如图所示

    image-20210615231029919

提取公共函数

不同对象之间 name 不一样 好理解,但是 他们的行为 也就是方法 -say,应该是一致的。也就是应该可以共用的,也就更能节省内存。 总之,我们想要实现

p1.say = p2.say
复制代码

这个好做,我们看看

    function say() {
      console.log(this.name);
    }

    function SuperPerson(name) {
      this.name = name;
      // 指向外部的say
      this.say = say;
    }

    const p1 = new SuperPerson("路飞");

    const p2 = new SuperPerson("乔巴");

    console.log(p1.say === p2.say); // true
复制代码

原理如图:

两个对象中的 say 方法 指向了同一个

image-20210615232137418

构造函数-原型

上述代码能够看出,虽然是解决了不同对象共享一个函数的弊端,但是代码的结构也未免太丑了,

  1. 一个功能 分成了两个入口 saySuperPerson
  2. say 方法也导致了全局污染
    function say() {  // 感情 say 这个名称就被你独占了
      console.log(this.name);
    }

    function SuperPerson(name) {
      this.name = name;
      // 指向外部的say
      this.say = say;
    }
复制代码

因此我们使用原型来解决 prototype

    function SuperPerson(name) {
      this.name = name;
    }

    // 在原型上定义方法
    SuperPerson.prototype.say = function () {
      console.log(this.name);
    }

    const p1 = new SuperPerson("路飞");

    const p2 = new SuperPerson("乔巴");

    console.log(p1.say === p2.say); // true
复制代码

看到这里伙计们应该知道了这样写法没有问题了。但是底层的原因呢,我们现在就对原型做通俗讲解

原型的通俗讲解

JavaScript 中,任何对象都有一个原型,就像每一个人都有一个爸爸?一样。接下来为了方便讲解,我们统一技术名词

  1. SuperPerson 称为 构造函数
  2. p1,p2 称为 实例
  3. prototype 称为 原型对象

其中

  1. 构造函数 SuperPerson 理解为父亲(母亲也ok)
  2. p1p2 理解为 孩子
  3. prototype 则理解为 父亲 传授给 孩子 的 那一条 DNA

只要父亲生了孩子(理解为 new 对象),那么孩子就一定会有一条 DNA,只要父亲在DNA上做了任何 属性或者方法的声明,那么孩子就一定可以获取到。

    function SuperPerson(name) {
      this.name = name;
    }

    // DNA 上定义属性
    SuperPerson.prototype.say = function () {
      console.log(this.name);
    }
    SuperPerson.prototype.jump = function () {
      console.log("you  jump I jump ");
    }

    const p1 = new SuperPerson("路飞");

    // 孩子可以获得
    console.log(p1.say);
    console.log(p1.jump);
复制代码

原型的深入讲解

我们平时使用的字符串方法,数组方法,日期方法,以及正则方法等都是通过原型来获得的。

通过数组举例子

    const list = [1, 2, 3, 4];
    console.log(list);
复制代码

image-20210615235101470

能看到 在数组的 __protp__ 属性上存到大量我们常用的方法。

简单解释下 __proto__(一共四个下划线) 是什么。

  1. __proto__ 也叫原型。
  2. 它是非标准属性,存在实例上。
  3. 它的作用只是方便我们在浏览器上查看原型,我们不要对它有任何操作,只能看不能摸。
  4. 构造函数的prototype 等于 实例的 proto

简单来说,我们是可以通过 prototype__proto__ 自下而上 找到 JavaScript的老祖宗的。

构造函数的prototype 等于 实例的 proto

    // SuperPerson 是构造函数
    function SuperPerson() {

    }

    const p1 = new SuperPerson();// p1 是实例

    console.log(p1.__proto__ === SuperPerson.prototype); // true
复制代码

image-20210616001541525

任何构造函数都是 Function的实例

只要满足条件 构造函数的prototype 等于 实例的 proto 我们就能顺藤摸瓜,寻找老祖宗

    const array = new Array(); // Array 是构造函数
    console.log(Array.__proto__ === Function.prototype); // true

    const date = new Date();// Date 是构造话剧
    console.log(Date.__proto__ === Function.prototype); // true

    function SuperPerson() { }
    const p1 = new SuperPerson();// SuperPerson 是构造函数
    console.log(SuperPerson.__proto__ === Function.prototype); // true
复制代码

image-20210616001634724

Function 和 Object 无绝对的关系

很多伙计多了这一层就过不去了,一直在找两者的直接联系。 这里需要明确,两者直接没有直接联系!!!

那么间接联系呢

一定要记住 构造函数的prototype 等于 实例的 proto

Function的原型是Object原型的实例

console.log(Function.prototype.__proto__ === Object.prototype); // true
复制代码

image-20210616002537163

Object的原型和null直接的关系

一定要记住 构造函数的prototype 等于 实例的 proto

console.log(Object.prototype.__proto__ === null);
复制代码

image-20210616002750526

将上图串联起来

image-20210616003031993

锦上添花

任何的构造函数,都是 Function 的实例

    // Object是构造函数
    const obj = new Object();

    console.log(Object.__proto__ === Function.prototype);
复制代码

上图

image-20210616003643718

小结

上述则为 JavaScript中令人闻风丧胆的原型链

我们判断构造函数和实例直接的关键是 一句话

任何构造函数都是Function的实例

以上你会画了没有

原型实际应用

学习原理,是为了更加方便的运用技术。

我们来改造下最开头的那个 创建圆角元素的案例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {

      width: 100px;
      height: 100px;
      background-color: aqua;
      margin: 100px auto;
    }

    .rds {
      border-radius: 50%;
    }

    @keyframes scale {
      to {
        transform: scale(2);
      }
    }

    .scale {
      animation: scale 1s linear infinite alternate;
    }
  </style>
</head>

<body>
  <script>

    // 构造函数
    function CreateCurveElement(elementName) {
      const element = document.createElement(elementName);
      this.element = element;
      this.addClass("rds");
    }

    // 指定该元素插入到哪里
    CreateCurveElement.prototype.appendTo = function (parent) {
      document.querySelector(parent).appendChild(this.element);
    }

    // 绑定事件
    CreateCurveElement.prototype.on = function (eventName, cb) {
      this.element.addEventListener(eventName, cb.bind(this));
    }
    CreateCurveElement.prototype.addClass = function (clsName) {
      this.element.classList.add(clsName);
    }

    const div = new CreateCurveElement("div");
    div.appendTo("body");
    div.on("click", function () {
      this.addClass("scale");
    })

  </script>
</body>

</html>
复制代码

原型的继承

原型的继承是通过改造 原型对象来实现的!

我们新建一个子构造函数来继承父构造函数的功能 。

    // 创建一个子构造函数  圆角图片 只传入图片路径即可,其他交给父构造函数实现
    function CreateCurveImg(src) {
      // 调用父构造函数  实现帮忙给this设置值
      CreateCurveElement.call(this, "img");
      this.element.src = src;
    }

    // 子构造函数 或者到了 父构造函数的原型上的功能  (存在弊端)
    CreateCurveImg.prototype = CreateCurveElement.prototype;
    const img1 = new CreateCurveImg("images/1.png");
    img1.appendTo("body");
    img1.on("click",function(){
      this.addClass("scale");
    })
复制代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {

      width: 100px;
      height: 100px;
      background-color: aqua;
      margin: 100px auto;
    }

    .rds {
      border-radius: 50%;
    }

    @keyframes scale {
      to {
        transform: scale(2);
      }
    }
    .scale {
      animation: scale 1s linear infinite alternate;
    }
  </style>
</head>

<body>
  <script>

    function CreateCurveElement(elementName) {
      const element = document.createElement(elementName);
      this.element = element;
      this.addClass("rds");
    }

    CreateCurveElement.prototype.appendTo = function (parent) {
      document.querySelector(parent).appendChild(this.element);
    }

    CreateCurveElement.prototype.on = function (eventName, cb) {
      this.element.addEventListener(eventName, cb.bind(this));
    }
    CreateCurveElement.prototype.addClass = function (clsName) {
      this.element.classList.add(clsName);
    }


    // 创建一个子构造函数  圆角图片 只传入图片路径即可,其他交给父构造函数实现
    function CreateCurveImg(src) {
      // 调用父构造函数  实现帮忙给this设置值
      CreateCurveElement.call(this, "img");
      this.element.src = src;
    }

    // 子构造函数 或者到了 父构造函数的原型上的功能  (存在弊端)
    CreateCurveImg.prototype = new CreateCurveElement();
    const img1 = new CreateCurveImg("images/1.png");
    img1.appendTo("body");
    img1.on("click",function(){
      this.addClass("scale");
    })

  </script>
</body>

</html>
复制代码

2021-06-16010011.gif

补充与拓展

  1. 判断 实例和构造函数之间的关系,我们使用 运算符 instanceof 即可

        // Array 是Function的实例
        console.log(Array instanceof Function); // true
    复制代码
  2. 目前基本都是使用 es6的 class 代替es5 的原型。

    1. class 称之为 类。 负责将定义对象相关的代码全部包装在一起

    2. extend 继承 表示要继承谁。相等于

      CreateCurveImg.prototype = new CreateCurveElement();
      复制代码
    3. super 调用父类构造函数

      CreateCurveElement.call(this, "img");
      复制代码
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
        div {
          border-radius: 50%;
          width: 100px;
          height: 100px;
          background-color: aqua;
          margin: 100px auto;
        }
    
        @keyframes scale {
          to {
            transform: scale(2);
          }
        }
    
        .rds {
          border-radius: 50%;
        }
    
        .scale {
          animation: scale 1s linear infinite alternate;
        }
      </style>
    </head>
    
    <body>
      <script>
    
    
        class CreateCurveElement {
          constructor(elementName) {
            const element = document.createElement(elementName);
            this.element = element;
            this.addClass("rds");
          }
          appendTo(parent) {
            document.querySelector(parent).appendChild(this.element);
          }
          on(eventName, cb) { this.element.addEventListener(eventName, cb.bind(this)); }
          addClass(clsName) {
            this.element.classList.add(clsName);
          }
        }
    
        // const div = new CreateCurveElement("div");
        // div.appendTo("body");
        // div.on("click", function () {
        //   console.log("那一夜~");
        // })
    
        class CreateCurveImg extends CreateCurveElement {
          constructor(src) {
            super("img");
            this.element.src = src;
          }
        }
    
        const img1 = new CreateCurveImg("images/1.png");
        img1.appendTo("body");
        img1.on("click", function () {
          this.addClass("scale");
        })
      </script>
    </body>
    
    </html>
    复制代码

素材资料

image-20210616011839203

好友微信号

添加大佬微信 和上千同伴一起提升技术交流生活

hsian_

最后

码字不容易 你的点击关注点赞留言就是我最好的驱动

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享