基于原型编程

在面向对象的编程语言中,类和对象的关系是铸模和铸件的关系,对象总是从类创建而来,比如Java中,必须先创建类再基于类实例化对象。

而在基于原型编程的思想中,类并不是必须的,对象都是通过克隆另外一个对象而来,这个被克隆的对象就是原型对象。

基于原型编程的语言通常遵循下面的规则:

  • 所有的数据都是对象
  • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
  • 无法自己直接创建一个对象,对象都是克隆产生,必须有一个根对象,然后克隆它才可以创建对象
  • 对象会记住它的原型
  • 如果对象无法响应某个请求,它会把这个请求委托给自己的原型

函数和对象

JavaScript是基于原型的语言,但JavaScript并非严格遵循原型编程的第一条规则,其中基本类型undefined、number、string、boolean就不是对象,其模仿Java,分为了基本类型和对象类型,当然和Java一样,基本类型可以使用包装的形式变为对象。

这里先探讨一个问题,有助于后面理解原型和原型链,那就是“所有的数据都是对象,函数也是对象“,虽然函数是对象,但是函数拥有很多普通对象不具备的特性:

  1. 可执行性:函数可以被调用执行
  2. 闭包:函数可以创建闭包
  3. 构造函数:函数可以作为构造函数,去创建对象实例,理论上,除了 Object.create 创建的对象,都是通过构造函数创建的对象。
  4. 原型对象属性:除了箭头函数,每个函数都有一个 prototype 属性,它是一个对象,用于存储通过该构造函数创建的所有实例的共享属性和方法。

JavaScript原型

从上面可以得知,函数都有 prototype 属性,称之为原型,也称为原型对象,原型对象里面可以存放属性和方法,共享给实例对象使用,用于实现继承效果。

这里的函数指的是构造函数,即可以通过new创建对象的函数,在JavaScript中普通函数都可以是构造函数,除了箭头函数。

箭头函数没有 prototype 属性是为了保持它们的设计简洁性,避免与原型链相关的复杂性和潜在问题。箭头函数同时不能使用new实例化对象。如果你需要定义一个构造器函数,应该使用普通函数而不是箭头函数。

举例说明:

const arr = new Array(1, 2, 3)
arr.reverse()
arr.sort()

在上面的代码中 Array 就是一个构造函数,reverse和sort都是 Array.prototype 原型对象上的函数。

这里有个问题,为何要将这些方法放在 prototype 原型属性上,而不是放在构造函数内部呢?

我们来看下面的代码:

function Person() {
this.getName = function () {
console.log('person')
}
}
Person.prototype.getProtoName = function () {
console.log('proto person')
}
const person = new Person()
const person1 = new Person() console.log(person.getName === person1.getName) // false
console.log(person.getProtoName === person1.getProtoName) // true

很明显,虽然在实例化对象上都可以调用这些方法,但两者有本质的不同,将方法放在 prototype 原型对象上,之后实例化的对象使用的都是同一个方法,类似于Java的类方法,而构造函数内的方法,则会重新创建,也就是实例对象方法。

JavaScript原型链

了解完原型之后,再来看原型链就非常简单了。

  1. 对象可以调用其构造函数的 prototype 原型对象的属性和方法
  2. 原型对象也是对象,同样也可以调用其构造函数的prototype原型对象的属性和方法
  3. 一层一层的形成一条链路就是原型链

如图所示:

解读:

  1. 在浏览器中,通常使用对象的 __proto__ 即可找到对象的原型对象,每个对象都有 __proto__ 属性(还是要去除Object.create创建的),用于指向它的原型对象。
  2. 前面基于原型编程有一个规则是,必须有一个根对象,JavaScript中根对象是:Object.prototype,其是一个空对象。

原型链经典图片

最后结合上面的内容,来看看下面的经典原型链图:

从上图可以看出左边是实例对象,中间是构造函数,右边是原型对象,图中还包含了一些其它内容:

函数是一个函数,同时也是一个对象:

构造函数作为一个函数时,拥有原型对象属性 prototype

同时函数也是一个对象,函数都是由构造函数 Function 构造出来的,包括 Function 函数本身,可以看上图中 function Foo()function Object()function Function()__proto__ 都是指向 Funtion.prototype,这是函数比较特殊的一点。

这个地方有点绕,再理一下,函数的 prototype 属性是这个函数自己的属性,而函数的 __proto__ 指向的是其构造函数的原型对象,可以理解为父级的属性,两者是不同的。

总结

  1. JavaScript是基于原型编程,创建对象是通过克隆对象的形式,不是通过类创建。
  2. 函数都拥有 prototype 原型属性,实例化对象的 __proto__ 属性指向这个原型属性,对象可以直接调用原型对象的方法和属性,不用写 __proto__ 再调用,两者效果一致。
  3. 对象的 __proto__ 指向构造函数的 prototype,构造函数的 prototype 同样是对象,其 __proto__ 指向上一层原型对象,直到 Object.prototype,形成原型链。

彻底搞懂JavaScript原型和原型链的更多相关文章

  1. 一张图搞懂 Javascript 中的原型链、prototype、__proto__的关系 转载加自己的总结

    1. JavaScript内置对象 所谓的内置对象 指的是:JavaScript本身就自己有的对象 可以直接拿来就用.例如Array String 等等.JavaScript一共有12内置对象    ...

  2. 来一轮带注释的demo,彻底搞懂javascript中的replace函数

    javascript这门语言一直就像一位带着面纱的美女,总是看不清,摸不透,一直专注服务器端,也从来没有特别重视过,直到最近几年,javascript越来越重要,越来越通用.最近和前端走的比较近,借此 ...

  3. 一张图彻底搞懂JavaScript的==运算

    一张图彻底搞懂JavaScript的==运算 来源 https://zhuanlan.zhihu.com/p/21650547 PS:最后,把图改了一下,仅供娱乐 : ) 大家知道,==是JavaSc ...

  4. 一张图带你搞懂Javascript原型链关系

    在某天,我听了一个老师的公开课,一张图搞懂了原型链. 老师花两天时间理解.整理的,他讲了两个小时我们当时就听懂了. 今天我把他整理出来,分享给大家.也让我自己巩固加深一下. 就是这张图: 为了更好的图 ...

  5. 彻底搞懂JavaScript中的继承

    你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- "继承"就和"原型链"这一概念息息相关.甚至可以说,所谓的"原型链&q ...

  6. 彻底搞懂Javascript的“==”

    本文转载自:@manxisuo的<通过一张简单的图,让你彻底地.永久地搞懂JS的==运算>. 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容让人犯错,从而 ...

  7. 三张图搞懂JavaScript的原型对象与原型链

    对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张 ...

  8. 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)

    摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...

  9. 一张图搞懂javascript原型链

    js高级里面原型链对于新手来说并不友好,总的来说就是 任何函数都有自己的原型对象(prototype),任何实例对象都__proto__指向构造函数的原型 先来个最简单的原型三角关系 var fn = ...

  10. 一次搞懂JavaScript对象

    索引 目录 索引 1. 对象与类 2.对象使用 2.1 语法 2.2 属性 3.对象特性 4.对象的创建 4.1 字面量 4.2 工厂函数 4.3 构造函数 4.4 class类 4.5 对象与单例模 ...

随机推荐

  1. Pandas对象(数据结构)

    Pandas是Python的一个扩展程序库,是在Numpy基础上建立的,提供高性能.易使用的数据结构和数据分析工具. Pandas 可以从各种文件格式比如 CSV.JSON.SQL.Excel 等中导 ...

  2. Numpy线性计算

    Numpy内置方法以及numpy.linalg模块可实现矩阵乘法.矩阵分解.矩阵行列式等线性代数的计算. In [1]: import numpy as np In [2]: x = np.arang ...

  3. Python从 requirements.txt 安装库

    pip install -r requirements.txt

  4. redis 简单整理——哨兵简单介绍[二十八]

    前言 简单介绍一下哨兵模式. 正文 Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人 工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多 应用场景这种故障处理的方式是 ...

  5. c# 优化代码的一些规则——什么情况下应该使用new[七]

    前言 new 在重构这本书中写道new就是坏代码的味道,说明使用new的情况并不多. 在这里我指的new 是方法修饰符,而不是指实例. 正文 看下new的作用: new 修饰符可以重新定义从基类继承下 ...

  6. kong管理界面konga的安装

    kong网关自身的管理界面属于付费的应用,而第三方界面又非常少,konga算是相对比较好的一款了,虽然也有一些问题,但整体的功能还比较全,github仓库为:https://github.com/pa ...

  7. 如何使用Delta Lake构建批流一体数据仓库

    ​简介:Delta Lake是一个开源存储层,它为数据湖带来了可靠性.Delta Lake提供了ACID事务.可扩展的元数据处理,并统一了流式处理和批处理数据处理.Delta-Lake运行在现有数据湖 ...

  8. 开源微服务运行时 Dapr 发布 1.0 版本

    简介: Dapr 是 2019 年 10 月开源的分布式运行时.早在 Dapr 开源初期,阿里云就开始参与 Dapr 社区建设和代码开发,目前已有两位 Dapr 成员,是 Dapr 项目中除微软之外代 ...

  9. 面对大规模 K8s 集群,如何先于用户发现问题?

    简介: 怎样才能在复杂的大规模场景中,做到真正先于用户发现问题呢?下面我会带来我们在管理大规模 ASI 集群过程中对于快速发现问题的一些经验和实践,希望能对大家有所启发. 作者 | 彭南光(光南)来源 ...

  10. 汽车之家:基于 Flink + Iceberg 的湖仓一体架构实践

    简介: 由汽车之家实时计算平台负责人邸星星在 4 月 17 日上海站 Meetup 分享的,基于 Flink + Iceberg 的湖仓一体架构实践. 内容简要: 一.数据仓库架构升级的背景 二.基于 ...