简述 JavaScript 中 prototype

简述 JavaScript 中 prototype

这篇笔记主要捋一下这么几个概念:

  • JS 的继承
  • 构造函数
  • new 的作用及简易实现
  • __proto__ & prototype
  • 同样的方法,class 和 prototype 中分别是怎么实现的

基础概念

JS 是通过 prototype chaining 实现继承的语言,所有的基类都会绑定在 prototype chain 上如:

class Base {
    
    
  constructor() {
    
    }
}

class Extended extends Base {
    
    
  constructor() {
    
    
    super();
  }
}

class Descendant extends Extended {
    
    
  constructor() {
    
    
    super();
  }
}

const descedant = new Descendant();
console.log(descedant.__proto__);

__proto__ 本身就暴露了当前对象的 [[Prototype]],它所指向的是另一个对象,也就是 prototype chain 上的继承 (粗暴的理解一下就是父类)。返回的对象又可以通过调用 __proto__ 继续获得父类的 [[Prototype]],一步步向上追溯一直到 [[Prototype]]null 为止,一般到这个时候,也是获取到 Object 了——JS 之中,除了 primitive type,万物皆对象。

需要注意的是,__proto__ 返回的对象是 [[Prototype]] 也是 <prototype>,二者是一样的,只不过前者是 chrome 的称呼,后者是 Firefox 的,这里为了一致就使用 [[Prototype]]。获取当前 [[Prototype]] 的方法有两种:

  1. __proto__

    已经 Deprecated 了,deno 中甚至不支持实现

  2. Object.getPrototypeOf()/Reflect.getPrototypeOf()

    推荐使用这个函数

构造函数

class 是 ES6 新出的语法糖,在 ES6 之前都是使用构造函数去实现的,如:

function Person(name, age) {
    
    
  this.name = name;
  this.age = age;
}

// equivalent to
class Person {
    
    
  constructor(name, age) {
    
    
    this.name = name;
    this.age = age;
  }
}

实现结果都是一致的:

在这里插入图片描述

同样,constructor 也不是一定需要大写,小写也是可以实现同样的功能:

function person3(name, age) {
    
    
  this.name = name;
  this.age = age;
}

const person3Instance = new person3('Deborah', 99);
console.log(person3Instance);

在这里插入图片描述

大写只是一个约定俗成的规范。

new 关键字

new 在使用构造函数的时候是必须的,否则它只是返回了一个 undefined:

function Person(name, age) {
    
    
  this.name = name;
  this.age = age;
  this.greet = function () {
    
    
    console.log('Hi there');
  };
}

const person = Person('Tylor', 26);
console.log(person);
person.greet();

在这里插入图片描述

new 的作用在于

  1. 它创建了一个新的对象
  2. 它关联了对应的原型链继承
  3. 它绑定了 this 的指向
  4. 它执行了构造函数内的部分

一个简单的实现 new 的函数为:

function myNew(constructorFn, ...args) {
    
    
  // 创建一个新的对象
  const obj = {
    
    };
  // 关联对应的原型链继承
  Object.setPrototypeOf(obj, constructorFn.prototype);
  // 执行了构造函数,同时使用 apply 也确定了 this 的指向
  const res = constructorFn.apply(obj, args);

  return typeof res === 'object' && res !== null ? res : obj;
}

在这里插入图片描述

__proto__prototype 的区别

简单的说就是,__proto__ 作用于实例上,而 prototype 作用于构造函数上。

如,准确的说函数的实现不是像上面那样实现的,而是:

Person.prototype.greet = function () {
    
    
  console.log(`Hi there, I am ${
      
      this.name},and I am ${
      
      this.age} years old`);
};

从自动提示上也可以看到,实例化的对象是无法访问内部的 [[Prototype]] 的:

在这里插入图片描述

在这里插入图片描述

另外,所有的函数实现其实都是绑定在 prototype 上的:

在这里插入图片描述

主要的原因就是因为函数本质上也是对象,而在每次实例化的时候都创建一个新的对象,是一个非常昂贵的事情。因此 JS 会将函数绑定到 [[Prototype]] 上,这样所有的实例化的对象可以共享一个函数。

如果想要每次实例化的时候都创建一个新的函数,则可以使用 arrow function(在 class 中使用),这也是 ES6 语法的 pro and con 了。

superthis

到这一步,基本上使用 prototype 去实现 class 都实现的差不多了,除了继承这一部分,以下面代码为例:

class Parent {
    
    
  constructor() {
    
    
    this.name = 'parent';
  }

  greet() {
    
    
    console.log(`This is ${
      
      this.name}`);
  }
}

class Child extends Parent {
    
    
  constructor() {
    
    
    this.name = 'Child';
  }

  greet() {
    
    }
}

const child = new Child();

事实上 JS 会报错:

在这里插入图片描述

在当调用了 super 之后就会发现,this 的指向被绑定到了 Child 中:

class Parent {
    
    
  constructor() {
    
    
    this.name = 'parent';
  }

  greet() {
    
    
    console.log(`This is ${
      
      this.name}`);
  }
}

class Child extends Parent {
    
    
  constructor() {
    
    
    super();
    this.name = 'Child';
  }

  greet() {
    
    
    super.greet();
  }
}

const child = new Child();
child.greet();

在这里插入图片描述

接下来要模拟实现的就是这一步,其中需要注意的就是:

  1. 实现继承关系

    这点可以通过修改 prototype 进行实现,已知 [[Prototype]] 中包含了所有的 prototype chain,这里也只需要将 Child.prototype 指向 Parent.prototype,并且绑定对应的 构造函数即可。

  2. 修改 this 的指向

function Parent() {
    
    
  this.name = 'Parent';
}

Parent.prototype.greet = function () {
    
    
  console.log(`This is ${
      
      this.name}`);
};

function Child() {
    
    
  // bind child to parent
  Parent.call(this);
  this.name = 'Child';
}

// set up inheritance
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.greet = function () {
    
    
  Parent.prototype.greet.call(this);
};

const child = new Child();
child.greet();

在这里插入图片描述

这样,prototype 中的继承关系也实现了。

这也是为什么 JS 推荐使用 class 而非重新实现一个 prototype 的原因,主要还是因为使用 class 的代码更加简洁易读。

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/130759171