JS原型

224 阅读3分钟

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.