• 【重学前端】004-JavaScript:我们真的需要模拟类吗


    【重学前端】004-JavaScript:我们真的需要模拟类吗

    一、曾经的“模拟面向对象”

    1、“模拟面向对象”

    思维导图

    早期情况概述

    早期的 JavaScript 程序员一般有过使用 JavaScript **“模拟面向对象”**的经历;

    实际上,JavaScript 本身就是正统的面向对象语言

    描述对象的方式 = 基于类(Java等) + 基于原型(JavaScript等);

    基于类的描述方式更成功,导致人们错误地认为这样的方式才是面向对象。

    公司政治原因

    这模仿得不够彻底啊!如果 Java 的写法都能在这里使用,那对于 Java 程序员岂不是一件大好事!

    如果 JavaScript 也是 sun 公司写的就好了,或者直接使用 Java 写前后端,岂不妙哉!

    就像 TypeScript 的语法,和微软自家的 C# 就很像!

    当然,JavaScript 本身也有很多实用而 Java 没有的特性!

    由于公司的一些政治原因,JavaScript 在推出之时,被要求去模仿 Java ,因此有了 newthis等语言特性,使其看起来更像 Java !

    2、ES6 提供了 class 关键字

    ES6 提供了 class 这个关键字来定义类,尽管其仍然是基于原型运行时系统的模拟,但修正了一些常见的“坑”,统一了社区方案,这对语言的有很大好处!

    二、什么是原型

    0、思维导图

    1、顺应人类自然思维的产物

    原型是顺应人类自然思维的产物。

    比如“照猫画虎”,这就是一种基于原型的思维!

    再比如电影中的“外星人”,他们和人类很相似,这也是一种基于原型的思维!人们想象不出来外星人到底是什么样子,就在自身的基础上创造出“外星人”的样子!这就是基于原型的思维!

    再比如电影、电视剧、小说中的故事,我们常说主人公的原型就是谁谁谁,我认为这也是基于原型的思维!

    说了这么多,原型的含义也没啥复杂的了!多举例子是表达的妙诀!

    2、基于“原型”描述对象

    此时,我们就更倾向于描述一个东西像什么,比如老虎像大猫!

    创建对象的方式:复制过来,改一改!

    JavaScript 实现复制操作的两种方式:

    **引用方式:**新对象持有一个原对象的引用;

    **真复制:**直接复制原对象,新对象与原对象无任何关系!

    3、基于“类”描述对象

    比较基于“原型”,我们是把这一类对象抽象成一个模型,然后基于这个模型来描述具体的对象!比如学生这一类对象,我们把他们共有的状态和行为抽象成一个学生类,比如都有姓名、学号、年龄、性别等等,然后每一个具体的学生根据类这个模型创建出来,这就达到了描述对象的效果!

    基于“类”比基于“原型”更抽象一点!

    创建对象的方式:基于类模型,创建!

    三、JavaScript 的原型

    1、对原型系统的 2 条概括

    抛开模拟 Java 类的复杂语法设施,原型系统非常简单!

    • 如果所有对象都有私有字段[[prototype]],就是对象的原型;
    • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找不到为止

    2、访问和操作原型

    这个原型在 ES6 以来提供了一系列内置函数,以便更为直观地访问和操作原型

    3、三个方法

    • Object.create:根据指定的原型创建新对象,原型可以是 null ;
    • Object.getPrototypeOf:获得一个对象的原型;
    • Object.setPrototyoeOf:设置一个对象的原型。

    创建新对象

    代码演示

    // 创建一个对象作为原型
    var 父亲 = {
      名字: "刘备",
      年龄: 63,
      民族: "汉族",
      : function () {
        console.log("我是刘备");
      },
    };
    console.log(父亲);
    父亲.();
    
    // 创建一个新对象
    var 儿子 = Object.create(父亲);
    console.log(儿子);
    儿子.名字 = "刘禅";
    儿子.年龄 = 16;
    儿子. = function () {
      console.log("我是刘禅");
    };
    console.log(儿子);
    儿子.();
    console.log(儿子.民族); // 注意:汉族
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行结果

    如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找不到为止!

    PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
    { '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
    我是刘备
    {}
    { '名字': '刘禅', '年龄': 16, '说': [Function (anonymous)] }
    我是刘禅
    汉族
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    获得一个对象的原型

    方法说明

    Object.getPrototypeOf(obj) :返回指定对象的原型(内部[[Prototype]]属性的值)。
    obj:要返回其原型的对象。
    返回值:给定对象的原型。如果没有继承属性,则返回 null 
    
    • 1
    • 2
    • 3

    代码演示

    // 创建一个对象作为原型
    var 父亲 = {
      名字: "刘备",
      年龄: 63,
      民族: "汉族",
      : function () {
        console.log("我是刘备");
      },
    };
    console.log(父亲);
    父亲.();
    
    // 获得对象的原型
    console.log(Object.getPrototypeOf(父亲));
    
    // 创建一个新对象
    var 儿子 = Object.create(父亲);
    console.log(儿子);
    儿子.名字 = "刘禅";
    儿子.年龄 = 16;
    儿子. = function () {
      console.log("我是刘禅");
    };
    console.log(儿子);
    儿子.();
    console.log(儿子.民族); // 注意:汉族
    
    // 获得对象的原型
    console.log(Object.getPrototypeOf(儿子));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果

    PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
    [Object: null prototype] {}
    { '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
    
    • 1
    • 2
    • 3

    设置一个对象的原型

    方法说明

    Object.setPrototypeOf(obj1, obj2):将 obj1 的原型设置为 obj2
    
    • 1

    代码演示

    // 创建一个对象作为原型
    var 父亲 = {
      名字: "刘备",
      年龄: 63,
      民族: "汉族",
      : function () {
        console.log("我是刘备");
      },
    };
    
    // 获得对象的原型
    console.log(Object.getPrototypeOf(父亲));
    
    // 创建一个新对象
    var 儿子 = {};
    // 设置对象的原型
    Object.setPrototypeOf(儿子, 父亲);
    
    // 获得对象的原型
    console.log(Object.getPrototypeOf(儿子));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行结果

    PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
    [Object: null prototype] {}
    { '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
    
    • 1
    • 2
    • 3

    四、ES6 中的类

    1、class 关键字

    ES6 加入了新特征 class ,在任何场景下都推荐使用 ES6 语法来定义类,令 function 回归原本的函数语义。

    ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了 JavaScript 的官方编程范式

    2、基本写法

    代码示例

    在现有的类语法中,getter/setter 和 method 是兼容性最好的。

    class Rectangle {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
      // Getter
      get area() {
        return this.calcArea();
      }
      // Method
      calcArea() {
        return this.height * this.width;
      }
    }
    
    console.log(new Rectangle(3, 4).area); // 12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    说明

    类的写法实际上也是由原型运行时来承载的,逻辑上 JavaScript 认为每个类是有共同原型的一组对象,类中定义的属性和方法被写在原型对象上

    3、类的继承

    代码示例

    class Animal { 
      constructor(name) {
        this.name = name;
      }
      
      speak() {
        console.log(this.name + ' makes a noise.');
      }
    }
     
    class Dog extends Animal {
      constructor(name) {
        super(name); // 调用父类构造函数并传入 name 参数
      }
     
      speak() {
        console.log(this.name + ' barks.');
      }
    }
     
    let d = new Dog('Mitzie');
    d.speak(); // Mitzie barks.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    说明

    使用 extends 关键字自动设置了 constructor,并且会自动调用父类的构造函数,这是一种更少坑的设计。

    一些激进的观点认为,class 关键字和箭头运算符可以完全替代旧的 function 关键字,它更明确地区分了定义函数和定义类两种意图,这是有一定道理的。

  • 相关阅读:
    Codeforces Round 895 (Div. 3) C. Non-coprime Split
    多级缓存自用
    0020__如何获取windows系统的串口列表
    华为数通方向HCIP-DataCom H12-821题库(单选题:181-200)
    java毕业生设计医院医患管理系统计算机源码+系统+mysql+调试部署+lw
    android——自定义加载按钮LoadingButton
    JRUY-G3交流三相电压继电器
    从php页面插入MySQL的数据变为乱码如何解决?
    Nginx基于Basic Auth实现静态资源的访问权限控制
    微信小程序基础加强总结
  • 原文地址:https://blog.csdn.net/qq_29689343/article/details/125627391