最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一文详解-es5原型和es6-class

    正文概述 掘金(赤兔工作室)   2021-06-16   370

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

    原型真的有用吗

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

    原因不外乎这几个:

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

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

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

    真相

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

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

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

    什么时候需要用到原型

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

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

    比如 我们想创建一个

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

    我们会这么写

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


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

    直接写

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

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

        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 即可,干净利索。

    构造函数

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

        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);
    

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

    可以看到,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);
    

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

    构造函数解析

    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. 如图所示

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

    提取公共函数

    不同对象之间 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 方法 指向了同一个

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

    构造函数-原型

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

    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);
    

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

    能看到 在数组的 __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
    

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

    任何构造函数都是 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
    

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

    Function 和 Object 无绝对的关系

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

    那么间接联系呢

    Function的原型是Object原型的实例

    console.log(Function.prototype.__proto__ === Object.prototype); // true
    

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

    Object的原型和null直接的关系

    console.log(Object.prototype.__proto__ === null);
    

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

    将上图串联起来

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

    锦上添花

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

        // Object是构造函数
        const obj = new Object();
    
        console.log(Object.__proto__ === Function.prototype);
    

    上图

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

    小结

    上述则为 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>
    

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

    补充与拓展

    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>
      

    素材资料

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

    好友微信号

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

    hsian_

    最后

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


    起源地下载网 » 一文详解-es5原型和es6-class

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元