问题

ES6的class关键字有没有实现真正的面向对象?

回答
ES6 的 `class` 关键字,乍看之下,确实给了 JavaScript 开发者一种“终于有了像其他主流面向对象语言一样,可以光明正大写类”的畅快感。它提供了一种声明式的、更易读的语法糖,让原型链的复杂性隐藏在了我们触手可及的 `class` 关键字之下。

那么,它实现了“真正的”面向对象吗?这个问题,其实触及了 JavaScript 语言的本质,以及我们对“面向对象”的理解。

从定义上讲,现代编程语言中普遍接受的面向对象编程(OOP)的几个核心特征,比如:

封装(Encapsulation):将数据(属性)和操作数据的方法(函数)捆绑在一起,形成一个独立的单元,并控制对这些数据和方法的访问。
继承(Inheritance):允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码的重用和“isa”的关系。
多态(Polymorphism):允许不同类的对象对同一消息(方法调用)做出不同的响应。

ES6 的 `class` 语法,在表面上,确实能够很好地支持这些概念。

封装:`class` 允许我们定义构造函数、方法和属性。虽然 ES6 并没有像 Java 或 C++ 那样严格的访问修饰符(`private`、`public`、`protected`),但通过命名约定(例如使用 `_` 前缀来表示私有属性,虽然这仅仅是约定,不是强制的),我们仍然可以实现一定程度的封装。

继承:ES6 的 `extends` 关键字,可以说是 `class` 语法的亮点之一。它允许我们创建一个新的类,继承另一个类的行为。这种继承是基于原型链的,但 `extends` 语法让这个过程变得更加清晰和直观。子类可以通过 `super` 关键字调用父类的构造函数和方法,这直接对应了传统 OOP 中的继承机制。

多态:由于 JavaScript 是一门动态类型语言,对象的方法调用在运行时才确定具体实现,这天然就支持了多态。一个父类的方法,在子类中可以被重写(override),当子类对象调用这个方法时,就会执行子类自己的实现,而不是父类的。`class` 语法只是让这种重写和调用方式更符合我们对 OOP 的认知。

但是,当我们深入探究 JavaScript 的底层机制时,就会发现 `class` 并不是凭空创造了一种新的面向对象模型。 实际上,它是在 JavaScript 原有的、基于原型(Prototype) 的面向对象模型之上,添加的一层更友好、更具表现力的语法。

JavaScript 的核心 OOP 模型是基于原型链(Prototype Chain) 的。每一个对象都可以有一个“原型”对象,而这个原型对象又可以有自己的原型,如此循环往复,形成一条链。当我们在一个对象上查找某个属性或方法时,如果对象自身没有,就会沿着原型链向上查找,直到找到或者到达链的末端(通常是 `Object.prototype`)。

ES6 的 `class` 关键字,本质上做的就是:

1. 构造函数(Constructor):`class` 声明中的 `constructor` 函数,就是 ES5 中用来实例化对象的构造函数。
2. 原型方法(Prototype Methods):在 `class` 声明中定义的方法,实际上被添加到了类对应的“prototype”对象上。这意味着,所有通过这个类创建的实例,都会共享这个 prototype 对象上的方法,实现了方法的继承。
3. 继承的实现:`extends` 关键字在底层,是通过操作对象的 `__proto__` 属性(或更规范的 `Object.setPrototypeOf`)来建立原型链的,使得子类的原型指向父类的 prototype。

所以,与其说 ES6 的 `class` 实现了“真正的”面向对象,不如说它用一种更具象、更接近我们传统 OOP 经验的方式,来表达和操作 JavaScript 原有的原型继承机制。

它解决的是“语法”和“可读性”的问题,让 JavaScript 在面向对象编程的表达上,从“像”变得更“像”。它并没有改变 JavaScript 的底层是基于原型的本质。

这意味着,JavaScript 即使没有 `class` 关键字,依然是一门支持面向对象编程的语言。 `class` 的出现,只是让这种支持更加显性、更加便捷。

那么,它是否“不真正”呢?

从某些角度来看,是的。因为它没有引入像“类”这种独立的、与实例分离的实体(在 JavaScript 中,类本身也是对象,是函数)。它也没有像一些静态类型语言那样,在编译时就确定类型和继承关系。JavaScript 的面向对象,始终是运行时动态的,是围绕着对象和它们之间的委托关系(原型链)构建的。

总的来说,ES6 的 `class` 关键字,极大地提升了 JavaScript 在编写面向对象代码时的开发体验和可维护性。它以一种清晰、声明式的方式,封装了 JavaScript 原有的原型继承模型,让开发者更容易理解和实现封装、继承、多态等 OOP 的核心概念。

但是,如果你严格地将“真正的面向对象”定义为依赖于显式的类、编译时类型检查、以及严格的访问修饰符等特征,那么 JavaScript 的 `class` 依然带有它独特的、基于原型的基因。它是一种“JavaScript 式”的面向对象,而不是其他语言面向对象的直接翻版。

它更像是一种“现代化”的 OOP 表达方式,而不是对 OOP 概念的“颠覆”或“全新实现”。所以,它的“真”与“不真”,取决于你对“真”的定义有多么严格,以及你是否将其与 JavaScript 的原型继承本质剥离开来看待。

最终,它的价值在于让开发者能够用更少的心智负担,更直观地编写出符合面向对象思想的代码,而不是纠结于底层原型链的细节。

网友意见

user avatar

很多“纯面向对象厨”还吐槽像Java、C#的静态方法不面向对象呢。“真正的面向对象”语言里所有操作都应该有receiver,静态方法没有receiver所以不面向对象。

——你们想怎么定义哪种模型更面向对象都罢,俺只管用,怎么样好用都行…

回到题主的问题,每个问题都可以换个角度来争辩:<- 再次声明,俺对这些争辩不感冒,既不支持题主的论点也不是说下面争辩的观点就对。只要是规定得规整的设计俺都OK。

1. 类没有静态属性;

也可以说有静态属性更不面向对象。

类自身是对象,类对象可以有字段,这就很足够了,非常面向对象。

2. 类的静态方法,实例无法调用;

在“真正的面向对象”语言里,类也是一个对象,所谓“静态方法”不过就是挂在类对象上的方法而已。要调用它们就指定该类为receiver去调用即可。ES6的class正好就可以做到这点,很面向对象。

像Java、C#的静态方法没有receiver,是一种比较不面向对象的设计。要从面向对象的角度看,它们可以看作是依赖静态类型系统来去省略了作为receiver的类参数的折衷设计,而它们其实也是要通过“ClassName.methodName(...)”的方式来调用的,只不过Java和C#在名字查找规则上对静态方法做了特殊处理,允许在实例方法里省略类名去调用本类的静态方法。在像ES6这样动态的语言里,这种隐式指定receiver的做法反而不现实也不直观。

Java的语法里还有一种非常恶心的调用静态方法的办法——通过一个实例的引用去调用:

       public class Foo {   static void bar() { }   void baz() {     this.bar(); //=> actually compiles into Foo.bar()   }    public static void main(String[] args) {     Foo obj = null;     obj.bar(); //=> actually compiles into Foo.bar(), no NPE thrown   } }     

这里 Foo.bar() 是一个静态方法,但我们可以用调用实例方法的语法(this.bar()、obj.bar())去调用它——而实际的语义跟那个引用一点关系都没有,纯粹是一个静态方法调用而已。这就是为什么当引用是null的时候用这种语法调用静态方法还是可以正常编译并运行,而不会在运行时抛出NullPointerException。这个设计非常糟糕,很容易就把读代码的人骗了,以为这里调用的是实例方法。这绝对不是什么好的面向对象设计。

3. 只能在方法内定义属性;

这跟是否面向对象其实关系不大,而是跟语言的动态性更有关系。

看看同样也很动态的Python和Ruby,它们也都是只能在实例方法里定义实例字段。像Ruby的attr_accessor只是声明了某个名字的getter / setter(合起来叫做accessor),但却没有定义实例字段自身。这只不过是一种动态的设计风格,谈不上面向对象与否。

类似的话题

  • 回答
    ES6 的 `class` 关键字,乍看之下,确实给了 JavaScript 开发者一种“终于有了像其他主流面向对象语言一样,可以光明正大写类”的畅快感。它提供了一种声明式的、更易读的语法糖,让原型链的复杂性隐藏在了我们触手可及的 `class` 关键字之下。那么,它实现了“真正的”面向对象吗?这个.............
  • 回答
    在前端开发领域,TypeScript 和 ES6(ECMAScript 2015)已经成为绕不开的话题,它们之间的关系和区别,可以说是很多校招生在面试中会被问到的经典问题。理解透彻这一点,不仅能让你在面试中游刃有余,更能让你在未来的开发实践中写出更健壮、更易维护的代码。首先,咱们得明确一个概念:ES.............
  • 回答
    这个问题,其实得从“标准”这个词本身说起。要理解为什么 JavaScript 需要 ES6 这样的“标准”,我们首先得明白,任何一门编程语言,要能被广泛接受、可靠地使用,并且持续发展,都需要一个清晰、稳定、被普遍认可的规范。你可以把 JavaScript 想象成一门正在不断成长、变化的孩子。一开始,.............
  • 回答
    讲到 ES6,它的出现就像给 JavaScript 这门语言注入了新的生命力,让开发者们能以更优雅、更高效的方式来编写代码。你想想看,在 ES6 之前,JavaScript 的一些写法确实有点让人头疼,比如处理异步操作时 callback hell 的困扰,或者定义类和继承时那种相对繁琐的方式。ES.............
  • 回答
    很多朋友可能在用 `async/await` 的时候,习惯性地把 `await` 语句写在 `try...catch` 块里,感觉这样很安全。确实,在 JavaScript 的世界里,处理异步操作的错误就像处理同步操作的错误一样重要,而 `try...catch` 又是我们最熟悉的错误处理机制。那为.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有