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关键字不仅可以用来继承类,还可以用来继承原生的构造函数。因此可以在原生数据结构的基础上,定义自己的数据结构。

  • 相关阅读:
    URAL 2067 Friends and Berries (推理,数学)
    URAL 2070 Interesting Numbers (找规律)
    URAL 2073 Log Files (模拟)
    URAL 2069 Hard Rock (最短路)
    URAL 2068 Game of Nuts (博弈)
    URAL 2066 Simple Expression (水题,暴力)
    URAL 2065 Different Sums (找规律)
    UVa 1640 The Counting Problem (数学,区间计数)
    UVa 1630 Folding (区间DP)
    UVa 1629 Cake slicing (记忆化搜索)
  • 原文地址:https://www.cnblogs.com/dillonmei/p/12578530.html
Copyright © 2011-2022 走看看