XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • leaflet改变坐标原点
  • 补间动画gsap与tween
  • 文字转语音mp3文件
  • JavaScript引入
  • JavaScript高级程序设计阅读笔记
  • Javascript函数参数按值传递传递
  • JS防抖和节流
  • 手写JS常用方法
  • 手写Promise
  • JS Event Loop
  • 重排和重绘
  • em、px、rem、vh、vw 区别
  • css也需要性能优化
  • 图解 js 原型链
    • js函数参数按值传递
    • BOM浏览器对象模型
    • DOM
    • 事件
    • js对象数组sort按需排序
    • 文件上传功能技术选型和前后端实现
    • 前端图片处理
    • 让你的JavaScript代码简单又高效
    • BEM:class命名规范
    • 前端规范-CSS属性那么多(杂),怎么排序
    • TypeScript Tips
    • jsx
    • canvas基础
    • 前端日志
    • 浏览器存储
    • CSS世界阅读笔记
    • CSS揭秘阅读笔记
    • js变量命名常用规范词
    • 你不知道的JavaScript阅读笔记
    • js对象的底层数据结构
    • js判断数据类型
    • JS浮点数精度问题和解决办法
    • js作用域
    • js堆栈溢出和内存泄漏
    • 浏览器是如何渲染页面的
    • 疑难杂症和踩坑问题合集
    • 免费在线API收集
    • 原生JS实现Ajax请求
    • cookie、session、localStorage、sessionStorage的区别
    • Sass与Less
    • arrayBuffer、blob、file对象
    • TypeScript基础
    • 前端
    XingYun
    2022-02-18
    目录

    图解 js 原型链

    参考 JavaScript 高级程序设计 (第四版)

    参考 https://chamikakasun.medium.com/javascript-prototype-and-prototype-chain-explained-fdc2ec17dd04

    # 一、为什么需要原型?

    假设我们代码里需要新建一个 person 对象

    let person = {}
    person.name = 'Leo'
    person.age = 20
    person.eat = function () {
      console.log(`${this.name} is eating.`)
    }
    person.sleep = function () {
      console.log(`${this.name} is sleeping.`)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    看起来没啥问题

    假设我们需要多个 person 对象呢? 你可能想到了,用工厂函数

    function createPerson(name, age) {
      let person = {}
      person.name = name
      person.age = age
      person.eat = function () {
        console.log(`${this.name} is eating.`)
      }
      person.sleep = function () {
        console.log(`${this.name} is sleeping.`)
      }
      return person
    }
    
    // 工厂函数批量生成person对象
    const person1 = createPerson('Mike', 23)
    const person2 = createPerson('Alis', 34)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    工厂函数解决了批量生成的问题,但是如果业务代码中需要对同一类 person 做某种操作,问题出现了

    不能判断 person1 和 person2 同一种类型,因为 person1 和 person2 没有任何关联。

    或者你也可以用构造函数模式

    function Person(name, age) {
      this.name = name
      this.age = age
      this.eat = function () {
        console.log(`${this.name} is eating.`)
      }
      this.sleep = function () {
        console.log(`${this.name} is sleeping.`)
      }
    }
    
    const person1 = new Person('Mike', 23)
    const person2 = new Person('Alis', 34)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    Person()构造函数代替了 createPerson()工厂函数。实际上,Person()内部 的代码跟 createPerson()基本是一样的,只是有如下区别。

    1. 没有显式地创建对象。
    2. 属性和方法直接赋值给了 this。
    3. 没有 return。

    而反过来 这三点正是 new 关键字所做的事。

    使用 new 操作符调用构造函数会将新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性

    这步操作就标识了 person 实例对象类型为 Person

    person1 instanceof Person // true
    person2 instanceof Person // true
    
    1
    2

    标识问题完美解决了!

    构造函数虽然有用,但也不是没有问题。它主要问题在于,其定义的方法会在每个实例上 都创建一遍。

    如果需要创建几万个 person,那么将会创建几万个 sleep 和 eat 方法。 😵‍💫😵😵‍💫

    # 二、原型模式

    为了解决上述问题,Javascript 引入了原型模式。

    每个函数都会创建 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例 共享的属性和方法。

    原来在构造函数中直接赋给对象实例的值或方法,可以直接赋值给它们的原型

    function Person(name, age) {
      this.name = name
      this.age = age
    }
    
    Person.prototype.eat = function () {
      console.log(`${this.name} is eating.`)
    }
    Person.prototype.sleep = function () {
      console.log(`${this.name} is sleeping.`)
    }
    
    let person1 = new Person()
    let person2 = new Person()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    这样 person 的原型定义的属性和方法可以被对象实例共享,不管创建多少个 person 对象, sleep 和 eat 方法永远只创建一次。

    console.log(person1.eat == person2.eat) // true
    console.log(person1.sleep == person2.sleep) // true
    
    1
    2

    生成的实例是怎么和 Person 构造函数关联呢? ‘__proto__’

    这里可以看到 chrome devtool 里 显示的是 [[Prototype]], proto 是一个获取 [[Prototype]] 内部插槽值的 getter/setter,出于兼容性原因而存在。

    引入 prototype 后, prototype、构造函数 Person、实例对象 person 之间的关系如下图:

    为了让多个实例的情况更加清晰,我们 new 两个对象

    let Alex = new Person('Alex', 24)
    let Bob = new Person('Bob', 25)
    
    1
    2

    他们的原型关系图如下

    我们回顾一下原型模式做了些什么:

    1. 当我们创建一个函数时,JavaSctipt 引擎会自动为该函数创建一个 prototype 对象,并将原型属性添加到可用于访问该 Prototype 对象的 Function 对象,并添加指向 Function 对象的构造函数属性 constructor。

    2. 然后当我们从 Function 对象创建实例时,JavaScript 引擎再次将 getter/setter(即 __proto__)添加到对象的实例中,可用于访问 Function 对象的相同原型对象。

    3. 构造函数的这个 prototype 对象在使用构造函数创建的所有对象之间共享。 我们可以将方法和属性添加到这个原型对象中,然后这些方法和属性将自动可供其构造函数的实例使用。

    # 三、原型链

    理清楚 Person 原型后, 我们来看一个问题

    Person.prototype 的 __proto__ 又指向哪里呢?

    答案是 Object.prototype : JavaScript 内置对象 Object 的原型对象

    那 Object.prototype.__proto__ 又指向哪里呢? null

    为了了解其原因;首先,我们需要了解 JavaScript 中的对象查找行为。

    当我们查找对象的属性(包括函数声明)时,JavaScript 引擎将首先检查对象本身是否存在该属性。

    如果没有找到,它将转到对象的原型对象并检查该对象。如果找到,它将使用该属性。

    如果在该对象中没有找到它,那么它将查找该原型对象的原型对象,如果在那里找到它,它将使用该属性,否则查找将继续,直到它找到一个 __proto__ 属性等于 null 的对象。

    那是 JavaScript 内置对象的原型对象。这里它设置为 null 以终止原型查找链。这称为原型链。

    这就是为什么我们在任何原型类型链对象中看到未定义的值的原因。它不应该与函数的 func.prototype 属性混淆,而是指定要分配给的 [[Prototype]]当用作构造函数时,由给定函数创建的对象的所有实例。

    我们加入 null 后重新画一张原型链图

    其他内置对象(如 Array、Date、Function 等)也具有关联的原型,并且具有特定于该类型的所有附加方法。

    例如,如果我们创建一个简单的数字数组 [1, 2, 3],默认的 new Array() 构造函数会在内部被 JavaScript 引擎从幕后调用。

    JavaScript 还会添加一个 proto 指针,指向新创建的数组实例,该实例指向数组。

    原型(即 Array.prototype)对象,它具有与 Array 操作相关的所有方法,例如 concat、reduce、map、forEach 等等。

    我们在调用数组的这些方法时,实际是调用的 Array.prototype 上的公共的操作方法。

    可以注意到 Array.prototype.__proto__依然指向 Object.prototype

    我们来看一下加入 array 后的原型链

    其它内置对象的工作原理也和 Array 类似, 比如 Function, Date 等等

    简单数据类型呢?

    我们知道简单数据不是 JavaScript 中的对象,但是当我们尝试访问它们的属性时,JavaScript 引擎会自动创建一个临时包装对象,使用内置的构造函数创建,例如 String、Number、Boolean、Symbol(ES6),除了空且未定义。这些将提供与原语一起使用的其他方法。

    值 null 和 undefined 没有对象包装器。由于它们没有对象包装器,因此它们不会有额外的方法和属性,而且它们也没有任何关联的原型对象。

    # 四、总结

    我们已经了解了不少关于 JavaScript 原型和原型链的知识,现在我们把这些知识串在一起放到一张大图中来回顾一下。

    #JavaScript
    上次更新: 2023/04/05, 09:41:10
    css也需要性能优化
    js函数参数按值传递

    ← css也需要性能优化 js函数参数按值传递→

    最近更新
    01
    JavaScript-test
    07-20
    02
    二维码的原理
    07-20
    03
    利用ChatGPT优化代码
    07-20
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式