JavaScript :JavaScript中构造函数与类的区别

ES5中构造函数

ECMAScript中的构造函数可以用来创建特定类型的对象,像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。
此外,我们还可以创建自定义的构造函数,从而自定义自定义对象类型的属性和方法。例如:

复制代码
function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job
  this.sayName = function(){
    console.log(this.name)
  }
}
var person1 = new Person('张三','23',''teacher);
复制代码

特点:

  1. 没有显示的创建对象。
  2. 所有属性和方法赋值给this对象。
  3. 没有return语句。
  4. 按照惯例,构造函数的方法名首字母应该使用大写字母,用于区分普通函数,其实构造函数也是函数,其主要功能是用来创建对象。
  5. 创建对象时使用new操作符。这里,会有一道JS经典面试题,new操作符执行了那些步骤:

    1、创建一个空对象;
    2、将构造函数的作用域赋值给新对象
    3、执行构造函数中的代码(为新对象添加属性)
    4、返回新对象
  6. 此处再多说一点,JS中的任何函数(普通函数,构造函数)只要通过new操作符来调用,那么他就可以当做构造函数,如果不用new操作符调用,和普通函数没有什么区别。

    // 当做构造函数来调用
    var person1 = new Person('张三','23',''teacher);
    person1.sayName(); // '张三'
    // 当做普通函数
    Person('李四','23','doctor')
    window.sayName(); // '李四'
  1. 每个构造函数都有一个原型属性prototype,该属性指向一个该构造函数的原型对象,原型对象中有一个constructor属性,该属性指向改构造函数;
    实例对象中包含一个指针([[Prototype]]),指向构造函数的原型对象,浏览器厂商给每个实例属性都加上了一个__proto__属性,用于获取构造函数的原型对象。

缺点:

  1. 使用构造函数的缺点就是,每个方法都要在实例上重新创建一遍,如果我们根据Person构造函数创建两个实例,person1和person
  2. 都有一个sayName()的方法,但这两个方法是不同的Function的实例,因为Function也是对象,因此每定义一个函数,就会创建一个对象。

解决方式:

  1. 将共享属性和方法放到原型对象上。每个构造函数都有一个原型属性prototype,该属性指向一个包含该类型所有实例共享属性和方法的对象,即原型对象。

    复制代码
    function Person(){};
    Person.prototype.name = '张三';
    Person.prototype.sayName = function(){
      console.log(this.name);
    }
    var person1 = new Person();
    var person2 = new Person();
    person1.sayName();  // '张三'
    person2.sayName();  // '张三'
    // 说明:调用sayName()时,先去找实例的方法,没有找到找原型方法,找到原型方法,调用。
    复制代码

ES6中的类

对于面向对象编程,ES6提供了一种接近传统面向对象编程语言的写法,即Class(类)。通过class关键字,我们可以自己定义类。 class是本质上一个语法糖,它的绝大部分功能,ES5都可以实现,只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

复制代码
class Point{
  constructor(x,y){
   this.x = x;
   this.y = y;
  }
  toString(){
    return '(' + this.x +','+ this.y + ')';
  }
}

var p=new Point(1,2);
p.toString(); // (1,2)
复制代码

特点:

  1. 类里面有一个constructor方法,即构造方法;this关键字代表实例对象。也就是说ES5的构造函数Point,对应ES6的Point的构造方法。
  2. 对于普通方法如toString()方法,不需要加上function关键字,直接把函数定义放进去即可。
  3. 方法之间不需要逗号分隔,加了会报错。
  4. 使用的时候,直接对类使用new命令,跟构造函数的用法完全一致,并且类必须用new调用,否则会报错
  5. (1)类的所有方法都是定义在类的prototype属性上面。上面构造函数中定义的方法等同于下面的写法:

    Point.prototype = {
      constructor(){},
      toString();
    }

    (2)在类的实例上面调用方法,其实就是调用原型上面方法!即:

    p.toString === Point.prototype.toString  // true

    (3)prototype对象上面的constructor属性,直接指向‘类’本身,这与ES5的行为一致。

    Point.prototype.constructor === Point  // true
  1. 类内部的方法都是不可枚举的。

  2. 类里面必须有一个constructor方法,如果没有显示指定,将自动添加一个空的constructor方法,通过new命令生成对象实例时,自动调用该方法,默认返回实例对象this。

  3. 实例属性除非显式定义在本身(即定义在this对象上),否则都定义在原型上,所有实例共享原型对象。

  4. 可以使用get和set关键字,对某个属性设置存值函数与取值函数,拦截该属性的存取行为。

  5. 类和模块内部默认是严格模式,不需要使用use strict指定运行模式。

  6. 类不存在变量提升。如:

    new Foo(); // ReferenceError
    class Foo {}
  1. 类方法内部,如果含有this,它默认指向类的实例。
  2. 静态方法:类相当于实例的原型,类中定义的方法都会被实例继承,如果在一个方法前加上static关键字,表示该方法不会被实例继承,而是通过类来调用,这就称为‘静态方法’。
    复制代码
    class Foo {
      static classMethod() {
        return 'hello';
      }}
    
    Foo.classMethod() // 'hello'
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
    复制代码

    注意:如果静态方法中包含this关键字,这个this指的是类,而不是实例。

  3. 实例属性除了可以定义在constructor方法里面的this上面,还可定义在类的最顶层,如下所示:
    复制代码
    class IncreasingCounter {
      constructor() {
        this._count = 0;    // _count为实例属性
      }
      get value() {
        console.log('Getting the current value!');
        return this._count;
      }
      increment() {
        this._count++;
      }}
    }
      
    class foo {
      bar = 'hello';   // 实例属性
      baz = 'world';  // 实例属性
      constructor() {
        // ...
      }}
    }
    复制代码

    所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性

  4. 静态属性:指的是Class本身的属性,目前的写法为

    class Foo {}
    Foo.prop = 1;
    Foo.prop // 1

    新提案的写法为在属性前面加上关键字static

    class MyClass {
      static myStaticProp = 42;
      constructor() {
        console.log(MyClass.myStaticProp); // 42
      }}
    }
  1. 私有属性和私有方法:在属性和方法前面加上#,就表示私有属性或者方法,在内部调用时,要加上#

继承:

ES6中Class可以通过extends关键字实现继承,比ES5的通过原型链实现继承清晰和方便的多。如下代码:

复制代码
class Point {}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }}
}
复制代码

特点:

  1. 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
  2. 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错,这是因为子类实例的构建是基于父类实例的,只有super方法才能调用父类实例。
  3. ES5的继承,实质上先创建子类的实例对象this,然后再将父类的方法添加到this上面(parent.apply(this))。ES6的继承机制完全不同,实质是先将父类实例对象的属性和方法加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
  4. Object.getPrototypeOf()方法可以用来从子类上获取父类,可以使用这个方法判断一个类是否继承了另一个类
    Object.getPrototypeOf(ColorPoint) === Point
    // true
  1. Super关键字既可以用来当做函数使用,也可以当做对象来使用。

    1、Super作为函数: 代表父类构造函数,ES6要求子类构造函数必须执行一次super函数。只能用在子类的构造函数中,用在其他地方会报错!

    class A {}
    class B extends A {
      constructor() {
        super();
      }}
    }

    注意: super虽然代表了父类A的构造函数,但是返回的子类B的实例,即super内部的this指的是B的实例,因此super()相当于A.prototype.constructor.call(this).

    2、作为对象: 在普通方法中指向父类的原型对象,只能调用原型上的属性和方法,对于父类实例上的方法和属性,无法通过super调用;在静态方法中,指向父类。 **注意:**在子类普通方法中通过super调用父类的方法时,方法内部的this指向子类实例。
    复制代码
    class A {
      constructor() {
        this.x = 1;
      }
      print() {
        console.log(this.x);
      }}
    }
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      m() {
        super.print();
      }}
    }
    let b = new B();
    b.m() // 2
    复制代码

    在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类实例。

    复制代码
    class A {
      constructor() {
        this.x = 1;
      }
      static print() {
        console.log(this.x);
      }}
    }
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      static m() {
        super.print();
      }}
    } B.x
    = 3; B.m() // 3
    复制代码
  1. 类的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属性)是父类的原型对象(prototype属性)的实例。
  2. extends关键字后面可以跟多种类型的值。

    class B extends A {}

    上面代码的A,只要是一个有prototype属性的函数,就能被B继承。由于函数都有prototype属性(除了Function.prototype函数),因此A可以是任意函数。

  1. 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
    var p1 = new Point(2, 3);
    var p2 = new ColorPoint(2, 3, 'red');
    
    p2.__proto__ === p1.__proto__ // false
    p2.__proto__.__proto__ === p1.__proto__ // true
  1. ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承。

posted on   梁飞宇  阅读(171)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
< 2025年3月 >
23 24 25 26 27 28 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
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示