1)、什么是原型
- 中文中有个成语叫做“照猫画虎”,这里的猫看起来就是虎的原型;
- 在不同的编程语言中,设计者也利用各种不同的语言特性来抽象描述对象;
- 使用“类”的方式来描述对象,如 C++、Java 等。这个流派叫做基于类的编程语言;
- 使用“原型”的方式来描述对象,如 JavaScript 等,这个流派叫做于原型的编程语言;
- “基于类”的编程,总是先有类,再从类去实例化一个对象。类与类之间又可能会形成继承、组合等关系。类又往往与语言的类型系统整合,形成一定编译时的能力;
- “基于原型”的编程看起来更为提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象,划分到最近的使用方式相似的原型对象,而不是将它们分成类。其实际是通过“复制”的方式来创建新对象;
- 一些语言的实现中,还允许复制一个空对象。这实际上就是创建一个全新的对象,原型系统的“复制操作”有两种实现思路:
- 切实地复制对象,从此两个对象再无关联;
- 并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用,例如 JavaScript。
2)、JavaScript 的原型
- 如果抛开 JavaScript 用于模拟 Java 类的复杂语法设施(如 new、Function、Object、函数的 prototype 属性等),原型系统可以说相当简单,我可以用两条概括:
- 如果所有对象都有私有字段[[prototype]],就是对象的原型;
- 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。
- 这个模型在 ES 的各个历史版本中并没有很大改变,但从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:
- Object.create 根据指定的原型创建新对象,原型可以是 null;
- Object.getPrototypeOf 获得一个对象的原型;
- Object.setPrototypeOf 设置一个对象的原型。
- 利用这三个方法,我们可以完全抛开类的思维,利用原型来实现抽象和复用。我用下面的代码展示了用原型来抽象猫和虎的例子。
var cat = { say() { console.log("meow~"); }, jump() { console.log("jump"); }, }; var tiger = Object.create(cat, { say: { writable: true, configurable: true, enumerable: true, value: function () { console.log("roar!"); }, }, }); var anotherCat = Object.create(cat); anotherCat.say(); var anotherTiger = Object.create(tiger); anotherTiger.say();
3)、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.