回顾
那么,在 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 的静态方法。
仿佛 ES6 的 class 写法就是上面构造函数写法的一种语法糖,但是事实真的如此么?
区别
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中的严格模式【单独章节】