zoukankan      html  css  js  c++  java
  • 深入了解class类并使用

     

    前情提要:

    JavaScript 语言中,在使用类之前,生成实例对象的传统方法是通过使用构造函数。



    一、构造函数:

    定义:通过  new 函数名  来实例化对象的函数叫构造函数。

    主要功能:为初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。

    注意:任何的函数都可以作为构造函数存在,构造函数定义时首字母大写(规范)。

    对new的理解:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。

    1、原生构造函数的继承

    原生构造函数是指语言内置的构造函数,通常用来生成数据结构。

    大致有:

        Boolean()

        Number()

        String()

        Array()

        Date()

        Function()

        RegExp()

        Error()

        Object()

    在之前,这些原生构造函数是无法继承的,比如,不能自己定义一个Array的子类。
    例子:
    function MyArray() {
      Array.apply(this, arguments);
    }
    MyArray.prototype = Object.create(Array.prototype, {
      constructor: {
        value: MyArray,
        writable: true,
        configurable: true,
        enumerable: true
      }
    });
    var colors = new MyArray();
    colors[0] = "red";
    colors.length  // 0
    
    
    colors.length = 0;
    colors[0]  // "red"
    之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。

    这就说明了原生构造函数的this无法绑定,导致拿不到内部属性。

    2、执行一个构造函数:

    function A(name,age){   this.name = name;   this.age = age; }
    A.prototype.info = function(){
      return "姓名"+ "" + this.name + "年龄" + this.age 
    }
    let a = new A("张三",22)//实例化a
    //打印 a结果 
    A{
      name:"张三",
      age:22
    }
    //打印 a.info() 结果为 "姓名张三年龄22"

    二、class 类

    由来:因为上面构造函数的写法跟传统的面向对象语言差异很大,给很多程序员造成很多困惑,所以ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。

    通过class关键字,可以定义类。

    1、class类基本语法的使用

    class A{
      constructor(){
        //成员属性
        this.name = name
        this.age = age
      }
      //静态方法 如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。A.nihao()
      static nihao(){
        console.log("你好")
      }
      //成员方法
      info(){
        return "姓名"+ "" + this.name + "年龄" + this.age 
      }
    }
    与上面的构造函数相比之言,新的class写法让对象原型的写法更加清晰、更像面向对象编程的语法。
    注意:定义info()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了,方法与方法之间不需要逗号分隔,加了会报错。
    
    
    添加静态属性:静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
    **老式写法:**
    class A{}
    A.props=1 //A.props= 1   props就是A的静态属性 
    
    
    **新式写法:**
    class A{
       static props = 1
    }
    新写法是显式声明(declarative),而不是赋值处理,语义更好。
    
    
    **私有方法和私有属性:**
    私有方法两种写法:    
    function bar(name){
        return this.name = name
    }
    class A{
    
    
        foo(name){
    
    
            bar.call(this,name)
        }
    }
    这样写的原因是类内部的所有方法都是对外可见的。foo是公开方法,内部调用了bar.call(this, baz)。这使得bar()实际上成为了当前类的私有方法。
    
    
    **还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。**
    const bar = Symbol('bar')
    const  name = Symbol('name')
    class A{
        //公有方法
        foo(){
        this[bra](name)
      }
      //私有方法
        [bar](name){
            return this[name] = name
      }
    }
    
    
    **私有属性**
    第一种方法是在属性名之前,使用#表示。
    class A{
      #count = 0
    }
    注意:#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。
    
    
    这种写法不仅可以写私有属性,还可以用来写私有方法。
    私有属性也可以设置 getter 和 setter 方法。
    
    
    class Foo { 
        #a; 
        #b;
        #xVal = 0; constructor(a, b) { this.#a = a; this.#b = b; } #sum() { return this.#a + this.#b; } printSum() { console.log(this.#sum()); }
        get #x() { return #xValue; }
        set #x(value) {
            this.#xValue = value;
        }
    }
    私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性。
    私有属性和私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。
    ```
    ### 2、深入介绍class类
    ```
    <1>
    ES6 的类,完全可以看作构造函数的另一种写法。
    class A{}  typeof A //function  A === A.prototype.constructor // ture 可以看出,类的数据类型就是函数,类本身就指向构造函数。
    使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
    <2>
    class A{
      constructor(){}
      info(){}
      toString(){}
      toVal(){}
    }
    等同于
    A.prototype={
        info(){},
        toString(){},
        toVal(){},
    };
    构造函数的prototype属性,在类里面也存在,类的所有方法都定义在类的prototype属性上面.
    因此,在类的实例上面调用方法,其实就是调用原型上的方法。
    <3>
    Object.assign()  方法可以很方便地一次向类添加多个方法。如下:
    Object.assign(A.prototype,{
        toString(){},
        toVal(){},
    })
    <4>
    类的内部所有定义的方法,都是不可枚举的,如下:
    class A{ constructor(x, y) { // ... } toString() { // ... } } Object.keys(A.prototype) // []
    <5>
    类内部可以忽略不写constructor,因为JavaScript 引擎会自动为它添加一个空的constructor()方法。如下:
    class A{} === class A{constructor(){}} //true
    <6>
    constructor()方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
    <7>
    类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

     三、class继承

    1、extends 关键字

    class 通过extends关键字实现继承,如下代码:
     
    class A{
        constructor(){
            this.p = 1
        }
        static hello(){
            console.log("Hello World")
        }
        p(){
            return 2
        }
        getP(){
            console.log(this.p);
        }
    }
    A.prototype.num = 2
    class B extends A{
        constructor(){
            super() //关键字,super作为函数调用时,代表父类的构造函数
            this.p = 2
            super.p = 3
            console.log(super.num) //2
            console.log(super.p()) //2
    
    
            console.log(super.p) //undefined
            console.log(this.p) //3
            //super.p赋值为3,这时等同于对this.p赋值为3。而当读取super.p的时候,读的是A.prototype.p,所以返回undefined。
        }
    
    
        get n(){
            return super.p
        }
        m(){
            super.getP()
            //super()写在这里报错
        }
    } //B类继承了A类的所有属性和方法
    
    
    let b = new B() //实列化
    B.hello() //Hello World //这就反映出,父类的静态方法,也会被子类继承。
    b.n //undefined
    b.m() //2 这说明实际上执行的是super.getP.call(this)。
     
    优点:这样的继承方式非常清晰和方便
    实质:先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
    注意:
        <1><span style="color:red;">构造函数如果没有调用super方法,就会导致新建实例时报错。</span>
        <2><span style="color:green;">在子类A的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。</span>
    
    
    2、Object.getPrototypeof()
    作用:从子类上获取父类.
    例子:
    Object.getPrototypeOf(B) === A 
    //true
    因此从上面的例子看出,可以使用这个方法判断,一个类是否继承了另一个类。
    3、super 关键字
    作用:可以当作函数使用,也可以当作对象使用 注意:<span style="color:red;">在这两种不同的情况下,它的用法也完全不相同。</span> 第一种情况 super作为函数调用时,代表父类的构造函数。 ES6 要求,子类的构造函数必须执行一次super函数 注意: <span style="color:red;">super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。</span> <span style="color:red;">作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。</span> 代码可以看上面对extends的介绍** 第二种情况 super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。 普通方法中 子类B当中的super.p(),就是将super当作一个对象使用。 super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()。 因为由于super指向父类的原型对象,所以定义在父类的原型对象上,super是可以取到。 注意:<span style="color:red;">定义在父类实例上的方法或属性,是无法通过super调用的。</span> 在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
    代码可以看上面对extends的介绍 静态方法中 class Parent { static myMethod(msg) { console.log('static', msg); } myMethod(msg) { console.log('instance', msg); } } class Child extends Parent { static myMethod(msg) { super.myMethod(msg); } myMethod(msg) { super.myMethod(msg); } } Child.myMethod(1); // static 1 var child = new Child(); child.myMethod(2); // instance 2 super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。 注意:<span style="color:red;">我们使用super时,一定要表明他的使用类型是作为函数使用还是作为对象使用,不能直接打印super会报错</span>  最后,由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。 4、类的prototype属性和__proto__属性 Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。 接下来上代码解释: class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true //由此看出,子类的__proto__属性,表示构造函数的继承,总是指向父类; //子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。 // B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype); // 等同于 B.prototype.__proto__ = A.prototype; // B 继承 A 的静态属性 Object.setPrototypeOf(B, A); // 等同于 B.__proto__ = A; const b = new B(); 这两条继承链,可以这样理解:**作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。** 实例的__proto__属性 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。 const a1 = new A() const a2 = new B() a2.__proto__.__proto__ = a1.__proto__ //true 因为B继承了A,所以说B的原型的原型是A的原型 因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。 a2.__proto__.__proto__.name = function(){ console.log("张三") } a1.name()//"张三" 上面代码在B的实例a2上向A类添加方法,结果影响到了A的实例a1。

      

  • 相关阅读:
    java如何导入Excel文件
    C/S框架设计经验小结
    WebClient以POST方式发送Web请求
    如何自动拼接 Update语句,仅Update已修改的字段
    DataGridView如何快速导出Excel
    【转】基于STM32F103内部AD测量电池电压
    【转】stm8 rtc时钟
    【转】NiOS II从EPCQ256的自启动设置
    【转】验收代码寄存器和验收屏蔽寄存器
    【转】eclipse : Type Symbol 'xxx' could not be resolved 解决办法
  • 原文地址:https://www.cnblogs.com/LcxWeb/p/14203630.html
Copyright © 2011-2022 走看看