《重学前端》学习笔记(2)

JavaScript原型

早期的 JavaScript 程序员一般都有过使用 JavaScript“模拟面向对象”的经历。

JavaScript 本身就是面向对象的,它并不需要模拟,只是它实现面向对象的方式和主流的流派不太一样,所以才让很多人产生了误会。
那些“模拟面向对象”,实际上做的事情就是“模拟基于类的面向对象”。

什么是原型?

原型是顺应人类自然思维的产物。

在不同的编程语言中,设计者利用各种不同的语言特性来抽象描述对象。

  • 最为成功的流派是使用“类”的方式来描述对象,这诞生了诸如 C++、Java 等流行的编程语言。这个流派叫做基于类的编程语言。

    “基于类”的编程提倡使用一个关注分类和类之间关系开发模型。在这类语言中,总是先有类,再从类去实例化一个对象。类与类之间又可能会形成继承、组合等关系。类又往往与语言的类型系统整合,形成一定编译时的能力。

  • 还有一种就是基于原型的编程语言,它们利用原型来描述对象。我们的 JavaScript 就是其中代表。

    “基于原型”的编程看起来更为提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象,划分到最近的使用方式相似的原型对象,而不是将它们分成类。基于原型的面向对象系统通过“复制”的方式来创建新对象。一些语言的实现中,还允许复制一个空对象。这实际上就是创建一个全新的对象。
    基于原型和基于类都能够满足基本的复用和抽象需求,但是适用的场景不太相同。

基于原型和基于类都能够满足基本的复用和抽象需求,但是适用的场景不太相同。

原型系统的“复制操作”有两种实现思路:

  • 一个是并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用;
  • 另一个是切实地复制对象,从此两个对象再无关联。
    历史上的基于原型语言因此产生了两个流派,显然,JavaScript 显然选择了前一种方式。

JavaScript 的原型

如果抛开 JavaScript 用于模拟 Java 类的复杂语法设施(如 new、Function Object、函数的 prototype 属性等),原型系统可以说相当简单,我可以用两条概括:

  • 如果所有对象都有私有字段 [[prototype]],就是对象的原型;
  • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

这个模型在 ES 的各个历史版本中并没有很大改变,但从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:

  • Object.create根据指定的原型创建新对象,原型可以是 null;
  • Object.getPrototypeOf 获得一个对象的原型;
  • Object.setPrototypeOf设置一个对象的原型。

ES6 中的类

在 ES6 中加入了新特性 class,new 跟 function 搭配的怪异行为终于可以退休了(虽然运行时没有改变),在任何场景,我都推荐使用 ES6 的语法来定义类,而令 function 回归原本的函数语义。下面我们就来看一下 ES6 中的类。

ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了JavaScript 的官方编程范式。

class Rectangle {
 constructor(height, width) {
 this.height = height;
 this.width = width;
 }
 // Getter
 get area() {
 return this.calcArea();
 }
 // Method
 calcArea() {
 return this.height * this.width;
 }
}

在现有的类语法中,getter/setter 和 method 是兼容性最好的。
我们通过 get/set 关键字来创建 getter,通过括号和大括号来创建方法,数据型成员最好写在构造器里面。
类的写法实际上也是由原型运行时来承载的,逻辑上 JavaScript 认为每个类是有共同原型的一组对象,类中定义的方法和属性则会被写在原型对象之上。

此外,最重要的是,类提供了继承能力。我们来看一下下面的代码。

class Animal {
 constructor(name) {
 this.name = name;
 }

 speak() {
 console.log(this.name + ' makes a noise.');
 }
}
class Dog extends Animal {
 constructor(name) {
 super(name); // call the super class constructor and pass in the name parameter
 }
 speak() {
 console.log(this.name + ' barks.');
 }
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.

以上代码创造了 Animal 类,并且通过 extends 关键字让 Dog 继承了它,展示了最终调用子类的 speak 方法获取了父类的 name。
比起早期的原型模拟方式,使用 extends 关键字自动设置了 constructor,并且会自动调用父类的构造函数,这是一种更少坑的设计。
所以当我们使用类的思想来设计代码时,应该尽量使用 class 来声明类,而不是用旧语法,拿函数来模拟对象。
一些激进的观点认为,class 关键字和箭头运算符可以完全替代旧的 function 关键字,它更明确地区分了定义函数和定义类两种意图,我认为这是有一定道理的。

发布了33 篇原创文章 · 获赞 73 · 访问量 2776

猜你喜欢

转载自blog.csdn.net/weixin_46124214/article/details/104194513
今日推荐