当 JavaScript在2015年发布了ES6标准时,引入了一项重要的功能------类(class)。然而,我们必须明确一点:JavaScript本质上仍然是基于原型的面向对象语言,而类只是一种语法糖,用于更方便地创建和组织对象。本文将来讨论ES6中的类和函数。
首先,让我们明确一点:JavaScript没有传统意义上的类。在JS中,一切都是对象,而函数则是一等公民。函数既可以作为普通函数执行,又可以作为构造函数使用。这就是为什么我们常常听到"JavaScript是一门基于原型的面向对象语言"的说法。而类(class)的引入是为了使JavaScript更适合企业级开发,并以更类似于其他大型语言的方式来实现面向对象编程。
类
ES6中引入的类是一种语法糖,它实际上并没有改变JavaScript的原型继承机制 (如果对js的原型还不了解,可以先去看一下这篇文章 JavaScript原型与原型链:理解核心概念,构建强大的前端技能)。类只是一种更简洁、更易读的语法形式,用于定义对象的结构和行为。类的声明方式如下:
javascript
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const person = new Person("John");
person.sayHello(); // 输出:Hello, my name is John.
上面的代码定义了一个名为Person的类,具有一个构造函数constructor和一个sayHello的方法。构造函数负责实例化对象并初始化其属性,而方法则定义了对象的行为。我们使用 "new" 关键字创建了一个名为 "person" 的类实例,并调用了它的 "sayHello" 方法。我们需要注意的是,这些方法都是定义在类的原型对象上的,而不是实例本身。这意味着所有类的实例共享相同的方法,这样可以节省内存,因为每个实例可以不用单独保存方法。
函数
在ES6之前,我们使用传统的构造函数、原型对象和实例的方式来创建对象。通过构造函数创建实例时,构造函数内部的this指向新创建的实例。原型对象则通过实例的__proto__属性来保持原型关系,而constructor属性告诉实例它是由哪个构造函数创建的。
javascript
function Person(name){
this.name = name;
}
Person.prototype.sayHello = function(){
return console.log('hello,I am ' + this.name);
}
const person = new Person("John");
console.log(Person.prototype===person.__proto__); // 输出true
person.sayHello(); // 输出:Hello, my name is John.
这段代码和上面类的那段代码作用是一样的。
当我们遍历一个对象上的属性时,可以发现原型对象上的属性是可遍历的,但constructor属性除外。我们可以使用Object.keys()方法来获取对象上可遍历的属性。我们到浏览器的控制台上使用Object.keys(Person.prototype)可以看到:
而直接打印Person.prototype,则可以看到它上面还有的constructor属性:
为什么说Class是语法糖
实际上,class的底层原理和Function是一样的,我们换到之前类的那段代码,再去控制台打印Person.prototype,可以看到:
它也有原型,也是通过原型继承来实现对象之间的关系。
另一个需要注意的是,类所声明的方法不可被遍历(枚举),我们使用Object.keys(Person.prototype)得到的是空数组。
类声明的方法默认是不可枚举的。这是因为类的方法通常用于定义对象的行为,而不是作为对象的数据属性。为了避免将类方法错误地当作普通属性进行遍历和操作,类方法被设计为不可枚举。
原型继承可以比喻为对象之间的委托关系,一个对象可以通过指向另一个对象作为其原型,从而继承原型对象的属性和方法。
虽然原型继承灵活且功能强大,但使用起来相对复杂,需要手动设置原型链、构造函数等。这导致了代码可读性不高,编写和阅读对象之间关系的代码变得困难。
ES6 引入了 class 关键字,提供了一种更直观、更易理解的方式来定义对象和对象之间的关系。class 通过将对象和继承的概念封装在一个简洁的语法中,使代码结构更清晰、易于维护。
当我们使用 class 声明一个类时,实际上是在底层使用原型继承来实现类的行为。class 的方法会被添加到构造函数的原型对象上,并通过原型链的方式进行继承。
因此,将 class 称为语法糖是指它只是一种更便捷的语法,封装了底层的原型继承机制,使代码更易于理解和编写。虽然它提供了更类似传统面向对象编程语言的语法,但实质上仍然使用原型继承来实现对象之间的关系。