当前位置: 首页 > news >正文

简述 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 parentParent.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 的代码更加简洁易读。

http://www.lryc.cn/news/68675.html

相关文章:

  • 一觉醒来Chat gpt就被淘汰了
  • 13款JavaScript图像处理库,建议收藏备用
  • uniapp m3u8格式视频加载
  • iOS描述文件(.mobileprovision)一键申请
  • 进行性能压力测试的原因、目的和好处
  • 【计算机视觉】如何利用 CLIP 做简单的人脸任务?(含源代码)
  • 基于显扬科技3D视觉相机的医疗试管分拣系统
  • 编译zlib
  • 如何让“ChatGPT自己写出好的Prompt的“脚本在这里
  • 菜单选择shell
  • Redis高可用性详解
  • MySQL(1) ---- 数据库介绍与MySQL概述
  • 面试题之软件测试流程
  • MyBatis中#{}与${}的区别,与各自的应用场景
  • 泛型类相关
  • 一文速学数模-季节性时序预测SARIMA模型详解+Python实现
  • 二叉树与图(C++刷题笔记)
  • STM32-ADC多通道输入实验
  • javaIO流之文件流
  • DMA-STM32
  • 代码随想录算法训练营第二十七天|39. 组合总和、40.组合总和II、131.分割回文串
  • 泛型(Generic) <? extends T>,<? super T>
  • 数云融合|数字化转型中的利器:揭秘云技术的重要角色
  • Linux篇2
  • 《微服务实战》 第九章 Gitlab使用
  • KMP匹配算法
  • ClickHouse笔记: Ubuntu/Centos下的安装, 配置和用户管理
  • 网络编程——UDP编程
  • linux内核篇-进程及其调度
  • C#开发的OpenRA游戏之基地工程车执行部署命令