zoukankan      html  css  js  c++  java
  • Class与构造函数的区别

    Class在语法上更贴合面向对象的写法。

    Class实现继承更加易读、易理解。

    更易于写java等后端语言的使用。

    本质是语法糖,使用prototyp。

    一、JS构造函数

    JS中的prototype:每一个构造函数都有的一个属性,能够用来向对象添加属性和方法。用来返回对象类型原型的引用。不需要显式声明,它是隐式存在的。

    object.prototype.name = value

    object.prototype.func = function() {...}

    object.prototype =object

    1、原型法设计模式:现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展。我们称B的原型为A。

    2、当一个对象被创建时,这个构造函数 将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。

    3、在外部不能通过prototype改变自定义类型的属性或方法,即当原型方法和对象方法在调用相同的属性和函数时,会执行对象方法里面的属性和函数。

    二、ES6中的构造语法——Class

    三、语法糖
    之所以叫做语法糖,不只是因为加糖前后的代码实现的功能一样,更重要的是,糖在不改变其所在位置的语法结构的前提下,实现了运行时的等价。也可以理解为,加糖前后的代码编译结果是一样的。加糖只是为了让代码的更加简洁,语义更加自然。

    在class语法中:typeof MathHandle ----->function

    MathHandle === MathHandle.prototype.constructor     // JS中没有类,class本质上还是构造函数

    function定义的方法(对象方法)有一个prototype属性,使用new生成的对象就没有这个prototype属性。也就是prototype属性是对象方法或者构造方法的专有属性。 prototype属性又指向了一个prototype对象,注意prototype属性与prototype对象是两个不同的东西,要注意区别。在prototype对象中又有一个constructor属性,这个constructor属性同样指向一个constructor对象,而这个constructor对象恰恰就是这个function函数本身。

    function Person(name)

    {

    this.name=name;

    this.showMe=function()

    {

    alert(this.name);

    }

    };

    var one=new Person('js');

    alert(one.prototype)                              //undefined

    alert(typeof Person.prototype);              //object

    alert(Person.prototype.constructor);     //function Person(name) {...};

    四、JS继承

    1、拓展原型。可以理解为Dog对象将Animal中的属性和方法全部克隆一遍,Dog能够使用Animal中的方法和属性。

    2、如果子类和父类中的方法同名,则运行时会先去本体的函数中去找,如果找到则运行,找不到则去prototype中寻找函数,理解为prototype不会克隆同名函数。

    五、ES6中的继承——Class

    ES6中的字符串占位符:

    JS中:"hello" + str + "world !"

    ES6中:hello ${str} world !

    JS语言传统创建对象的方法一般是通过构造函数,来定义生成的,下面是一个使用function生成的例子。(需要了解生成对象的方式,如工厂模式、原型模式等,以及优缺点,请参考文章:JavaScript中创建对象的7种模式)

    function Point(x,y){
    this.x=x;
    this.y = y;
    }
    Point.prototype.toString = function(){
    return '('+this.x+','+this.y+')';
    }
    var p= new Point(1,2);
    上面的例子在ES6中定义如下:

    class Point {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    toString() {
    return '(' + this.x + ', ' + this.y + ')';
    }
    }
    1.类Point中方法之间不用,号隔开,方法不用function进行定义,

    构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

    class Point {
    constructor(){
    // ...
    }
    toString(){
    // ...
    }
    toValue(){
    // ...
    }
    }

    // 等同于
    Point.prototype = {
    toString(){},
    toValue(){}
    };
    2.类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)

    3.每一个类中都有一个constructor方法该方法返回实例对象

    4.类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

    用类进行实例和用普通的构造函数进行实例:

    1、用类进行实例的必须使用new否则就会报错

    2、与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

    3、与ES5一样,类的所有实例共享一个原型对象。

    4、Class不存在变量提升(hoist),这一点与ES5完全不同

    new Foo(); // ReferenceError
    class Foo {}
    上面代码中,Foo类使用在前,定义在后,这样会报错,因为ES6不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

    {
    let Foo = class {};
    class Bar extends Foo {
    }
    }
    上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。

    Class的继承
    class中的继承使用extend
    1、子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
    2、ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
    3、
    另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

    class Point {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    }

    class ColorPoint extends Point {
    constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
    }
    }
    上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

    super.print.call(this)

    类的prototype属性和__proto__属性
    大多数浏览器的ES5实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

    (1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

    (2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

    class A {
    }

    class B extends A {
    }

    B.proto === A // true
    B.prototype.proto === A.prototype // true
    上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。

    这样的结果是因为,类的继承是按照下面的模式实现的。

    实例的__proto__属性
    子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。

    原生构造函数的继承
    原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript的原生构造函数大致有下面这些。

    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
    }
    });
    上面代码定义了一个继承Array的MyArray类。但是,这个类的行为与Array完全不一致。

    var colors = new MyArray();
    colors[0] = "red";
    colors.length // 0

    colors.length = 0;
    colors[0] // "red"
    之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行。原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。

    ES5是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。比如,Array构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。
    ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。下面是一个继承Array的例子。

    class MyArray extends Array {
    constructor(...args) {
    super(...args);
    }
    }

    var arr = new MyArray();
    arr[0] = 12;
    arr.length // 1

    arr.length = 0;
    arr[0] // undefined
    上面代码定义了一个MyArray类,继承了Array构造函数,因此就可以从MyArray生成数组的实例。这意味着,ES6可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。

    上面这个例子也说明,extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。

  • 相关阅读:
    Android 音视频开发(六): MediaCodec API 详解
    Android 音视频开发(五):使用 MediaExtractor 和 MediaMuxer API 解析和封装 mp4 文件
    Android 音视频开发(四):使用 Camera API 采集视频数据
    Android 音视频开发(三):使用 AudioTrack 播放PCM音频
    Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件
    Android 音视频开发(一) : 通过三种方式绘制图片
    Android 使用View绘制文字(DrawText)技术总结
    Mac OS 中安装 autoconf 和 automake
    Android 自定义 View 绘制
    关于 Socket 设置 setSoTimeout 误用的说明
  • 原文地址:https://www.cnblogs.com/dillonmei/p/12578530.html
Copyright © 2011-2022 走看看