class与构造函数的相似性与区别

回顾

那么,在 ES6 出现之前,我们是如何实现类似于其他语言中的“类”的呢?

没错,我们是通过的构造函数,然后将方法挂在原型上面。例如:

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.sayName = function () {
      console.log(`我的名字是${this.name}`);
    };
    Person.staticFunction = function () {
      console.log("我是静态方法");
    };

    var codereasy = new Person("codereasy", 99);
    console.log(codereasy.name); // codereasy
    console.log(codereasy.age); // 99
    codereasy.sayName(); // 我的名字是codereasy
    codereasy.staticFunction(); //报错 Uncaught TypeError: codereasy.staticFunction is not a function
    Person.staticFunction(); //我是静态方法

上面的代码就是我们经常在 ES5 中所书写的代码,通过构造函数来模拟类,实例方法挂在原型上面,静态方法就挂在构造函数上。

如果使用ES6的class,应该如何书写代码呢?

    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }

      sayName() {
        console.log(`我的名字是${this.name}`);
      }

      static staticFunction() {
        console.log("我是静态方法");
      }
    }

    const codereasy = new Person("codereasy", 99);

    console.log(codereasy.name); // codereasy
    console.log(codereasy.age); // 99
    codereasy.sayName(); // 我的名字是codereasy

    try {
      codereasy.staticFunction(); // 尝试调用,但会引发错误
    } catch (error) {
      console.error(error.message); // 报错 Uncaught TypeError: codereasy.staticFunction is not a function
    }

    Person.staticFunction(); //我是静态方法

上面的代码非常的简单,我们定义了一个名为 Person 的类,该类存在 name、age 这两个实例属性,一个 sayName 的原型方法以及一个 staticFunction 的静态方法。

仿佛 ES6class 写法就是上面构造函数写法的一种语法糖,但是事实真的如此么?

区别

class上的静态方法不可枚举

接下来,我们来针对两种写法,各自实例化一个对象,代码如下:

function PersonES5(name, age) {
    this.name = name;
    this.age = age;
}

PersonES5.prototype.sayName = function() {
    console.log(`我的名字是${this.name}`);
}

PersonES5.staticFunction = function() {
    console.log('我是静态方法');
}

var personES5 = new PersonES5("es5example", 25);
for (let prop in personES5) {
    console.log(prop);  // 这里会打印 "name", "age" 以及 "sayName"
}

class PersonES6 {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayName() {
        console.log(`我的名字是${this.name}`);
    }

    static staticFunction() {
        console.log('我是静态方法');
    }
}

const personES6 = new PersonES6("es6example", 26);
for (let prop in personES6) {
    console.log(prop);  // 这里仅打印 "name" 和 "age"
}

可以看到,ES6 中的原型方法是不可被枚举的,说明 ES6 对此也是做了特殊处理的。这也是为什么许多人认为 ES6 class 语法更清晰和易于理解的原因之一,因为它提供了更加统一和预测性更强的行为。

class必须通过new调用

接下来我们来详细比较一下两种写法在细节上面的一些差异。

我们知道,构造函数也是函数,既然是函数,那么就可以通过函数调用的形式来调用该函数,例如:

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.sayName = function () {
      console.log(`我的名字是${this.name}`);
    };
    Person.staticFunction = function () {
      console.log("我是静态方法");
    };

    var a = Person();
    console.log(a); // undefined

运行上面的代码,代码不会报错,因为没有使用 new 的方式来调用,所以不会生成一个对象,返回值就为 undefined

但是如果我们这样来调用 ES6 书写的类,会直接报错:

 var a = Person();
// TypeError: Class constructorPerson cannot be invoked without 'new'

可以看到,ES6 所书写的 class ,虽然我们认为背后就是构造函数实现的,但是明显是做了特殊处理的,必须通过 new 关键字来调用

为什么会有这种区别呢?在最初的JavaScript版本中,并没有真正的“类”的概念。我们需要使用函数来模拟类的行为。当我们调用一个函数(不使用new关键字),它只是一个普通的函数调用。而当你使用new关键字调用它时,JavaScript引擎会为你做一些额外的事情,例如创建一个新的对象实例,并将this绑定到该新对象。所以在ES5中,JavaScript引擎无法判断这个是否是一个构造函数,我们都是用函数来模拟类的行为,所以干脆就直接不做校验,不管是通过new调用,还是不通过new调用,都不会报错。

ES6的class引入:

在ES6(也称为ES2015)中,class关键字使得我们可以更加正式、更加结构化的方法来定义类。尽管在底层仍然使用函数和原型,但是由于定义的方法不同,我们明确地使用了class关键字来调用,所以JavaScript引擎就可以利用这一点来做校验,在没有使用new关键字的情况下调用类会抛出错误。这样做的好处避免一些常见的错误,例如不小心在全局对象上设置属性。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var person1 = Person("codereasy", 99);
console.log(window.name);  // 输出 "codereasy"
console.log(window.age);   // 输出 99

第三个区别就是class上的原型方法不可作为构造函数,但是需要理解这一点我们需要有一个前置知识,内部方法[[Construct]]。在下一节课我们会详细讲解什么是内部方法[[Construct]],以及它为什么导致class上的原型方法不可作为构造函数。

class上的原型方法不可作为构造函数(另写新文章)

为什么会提到这一点呢?因为在大多数人的认知中,只要是个普通函数(非箭头函数),那么我们就可以把它当作构造函数用。

    var test = function () {
      console.log("我是test函数");
    };
    var a = new test();

所以构造函数的原型方法当然能被当作构造函数使用。只不过当我们使用new obj.sayName()的时候,得到的是一个空对象,但是它不会报错。

    function PersonES5(name, age) {
      this.name = name;
      this.age = age;
    }

    PersonES5.prototype.sayName = function () {
      console.log(`我的名字是${this.name}`);
    };

    PersonES5.staticFunction = function () {
      console.log("我是静态方法");
    };

    let obj = new PersonES5();

    let a = new obj.sayName();
    console.log("a是什么", a);

但是在ES6中,class里的原型方法就不能当构造函数使用

    class PersonES6 {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }

      //原型方法
      sayName() {
        console.log(`我的名字是${this.name}`);
      }

      //静态方法
      static staticFunction() {
        console.log("我是静态方法");
      }
    }

    let obj = new PersonES6(); //obj.sayName is not a constructor

    let a = new obj.sayName();
    console.log("a是什么", a);

为什么ES5任何函数都能当构造函数,但是ES6不行?

ES5:

在ES5中,函数可以被当作普通的函数调用,也可以使用new关键字作为构造函数调用。当使用new关键字调用一个函数时,JavaScript会为该函数创建一个新的执行上下文,其中this关键字绑定到一个新的空对象。

如果该函数没有显式地返回一个对象,则JavaScript会自动返回那个新的空对象(即this对象)。所以,当你尝试使用new关键字调用sayName这样的非构造函数时,你实际上会得到一个新的空对象,而JavaScript不会抛出错误。

ES6:

ES6引入了类语法,这为JavaScript带来了真正的面向对象编程特性。

因此,当你尝试使用new关键字调用一个非构造函数时(例如:obj.sayName),JavaScript会认为你正在尝试将一个不具有[[Construct]]内部方法的函数当作构造函数来调用,从而抛出一个错误。

总之,这种行为差异源于JavaScript在ES6中引入类语法后对类成员的处理方式。ES6的类语法对JavaScript的面向对象特性进行了严格和结构化的规定,使得只有真正的构造函数(如类的constructor方法)可以使用new关键字调用,而类中的其他方法则不行。而在ES5和之前,这种严格性并不存在。

内部方法[[Construct]] 【单独章节】

class中的严格模式【单独章节】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codereasy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值