前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
      • 写在前面
      • 原型
        • prototype
        • constructor与prototype联系
        • __proto__和prototype关系
          • 显示原型和隐式原型
        • 总结
        • 补充
          • Object和Function的鸡和蛋的问题
          • MDN的推荐
      • prototype chain 原型链
        • 结论:
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

原型与原型链|博客系列

# 写在前面

首先我们需要明确两点:

1️⃣__proto__和constructor是对象独有的

2️⃣prototype属性是函数独有的

# 原型

# prototype

  • 在最新ES规范里,prototype 被定义为:给其它对象提供共享属性的对象。
  • 也就是说,prototype 自己也是对象,只是被用以承担某个职能罢了。

因此,prototype 描述的是两个对象之间的某种关系(其中一个,为另一个提供属性访问权限)。

# constructor与prototype联系

  • 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)

  • 原型对象中有一个属性constructor,它指向函数对象

  • 给原型对象添加属性(一般是方法)

    • 作用:函数的所有实例对象自动拥有原型中的属性(方法)

下面通过一个例子来说明:

		function Demo() {}
        console.log(Demo.prototype.constructor === Demo)  // true
        console.dir(Demo.prototype)
@前端进阶之旅: 代码已经复制到剪贴板

![](C:\Users\DayDay\Pictures\Camera Roll\demo.png)

可以看到就是Demo函数对象的prototype原型是右边这个对象,那么Demo.prototype原型上有个constructor属性,这个属性正好指向Demo函数本身。

所有你可以理解成:

A的显示原型是B,则有:
A.prototype === B 
B.constructor === A
@前端进阶之旅: 代码已经复制到剪贴板

我觉得这样子唯一的好处在于你可以找到我,我也可以找到你。好滑稽

# __proto__和prototype关系

再次强调 :

1️⃣__proto__和constructor是对象独有的。2️⃣prototype属性是函数独有的

关于更多__proto__更深入的介绍,可以参看工业聚大佬的《深入理解 JavaScript 原型》一文。

# 显示原型和隐式原型

  • 每个函数fun都独有一个prototype, 及显式原型(属性)
  • 每个实例对象都有一个__proto__, 及隐式原型(属性)**
  • 对象的隐式原型的值 === 其构造函数的显示原型的值

怎么理解呢?我们通过内存结构图来看看吧

		function Demo() {}
        Demo.prototype.say = () => {       //给原型添加say方法
            console.log("hello world")
        }
        
        console.log(Demo.prototype.say)
        let fn = new Demo();
        
        fn.say();   // 怎么找到say方法的呢?
        console.log(fn.__proto__ === Demo.prototype)  // true
@前端进阶之旅: 代码已经复制到剪贴板

我们从图中可以看到,Demo函数的原型跟它构造函数(Demo)创建的实例fn.__proto__指向同一个对象。

那么fn是怎么找到say方法的呢?

更加具体的说就是通过隐式原型__proro__找到的,分析如下:

  • js引擎执行到fn.say()整行代码时,解析器去栈中查找fn变量
  • 发现fn变量是引用类型,就去堆内存中查找地址为0x234的实体,查到后,发现并没有say属性,接着就去找__proro__属性对应的原型
  • 接着找到内存地址为0x345对应的实体,发现该实体中有say属性,同样的操作去找地址为0x789的实体,最后执行该函数。

那么是不是可以更加准确的说明:实例是通过隐式原型__proto__查找需要调用的属性的,那么我们通过接下来的代码去验证一下。

代码:

function Demo() {}
        Demo.prototype.say = () => {       //给原型添加say方法
            console.log("hello world")
        }
        Demo.prototype.name = 'old name'
        let fn = new Demo();
        
        fn.say();   // 怎么找到say方法的呢?
        console.log(fn.name)
        console.log("为修改前",fn.__proto__ === Demo.prototype)  // true
        console.log("-------接下来修改fn的__proto__")
        fn.__proto__ = {
            say: () => {
                console.log("hello 隐式原型")
            },
            name : 'new name'
        }
        console.log("修改实例中的隐式原型",fn.__proto__ === Demo.prototype)  // true
        console.log(fn.name)
        fn.say()
        console.log("重新创建一个Demo构造函数实例")
        let demo1 = new Demo();
        console.log(Demo.prototype === demo1.__proto__)
        demo1.say()
@前端进阶之旅: 代码已经复制到剪贴板

首先的说明的是:

通过查阅相关的文档,ES6之前不能直接操作隐式原型,也不推荐你这么做。

通过修改fn的隐式原型,让它指向一个新的对象。那么fn.proto 不等于Demo.prototype. 这个例子也能证明一点,实例对象调用属性时,实例对象不具有该属性时,是通过隐式原型去找的该属性的,找不到的话,在它的隐式原型对象的隐式原型对象上找。

这也就是我们常说的,在原型上添加属性或者方法,实例可以共享,原因就在于我们并不推荐去修改实例的__proto__属性,这样子也就是会有一下结果:

function Demo() {
		// 内部语句 this.prototype = {}
}
let fn = new Demo(); // 内部语句: fn.`__proto__` = Demo.prototype

// 实例化一个对象隐式原型会默认赋值: fn.__proto__ = Demo.prototype
// 定义函数时: 显式原型也会默认添加: Demo.prototype = new Object()
@前端进阶之旅: 代码已经复制到剪贴板

这里我们需要知道的是,__proto__是对象所独有的,并且__proto__是一个对象指向另一个对象,也就是他的原型对象。我们也可以理解为父类对象。它的作用就是当你在访问一个对象属性的时候,如果该对象内部不存在这个属性,那么就回去它的__proto__属性所指向的对象(父类对象)上查找,如果父类对象依旧不存在这个属性,那么就回去其父类的__proto__属性所指向的父类的父类上去查找。以此类推,知道找到 null。而这个查找的过程,也就构成了我们常说的原型链。

# 总结

  • 那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

  • 函数的prototype属性:在定义函数时自动添加prototype,默认是一个空Object对象

  • 对象的__proto__属性:创建一个对象实例时,默认值是构造函数的prototype属性值,也就是上面所说的

  • 实例的构造函数属性(constructor)指向构造函数

  • 一般而言,可以直接操作显式原型,不能直接操作隐式原型(ES6)

  • 更多规范,移步 MDN

# 补充

# Object和Function的鸡和蛋的问题

**最后总结: ** 先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数继承Function.prototype而产生。

# MDN的推荐

使用__proto__是有争议的,也不鼓励使用它。因为它从来没有被包括在ECMAScript语言规范中,但是现代浏览器都实现了它。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用, 现在更推荐使用 Object.getPrototypeOf/ Reflect.getPrototypeOf 和 Object.setPrototypeOf/ Reflect.setPrototypeOf(尽管如此,设置对象的[[Prototype]]是一个缓慢的操作,如果性能是一个问题,应该避免)。

proto 属性也可以在对象文字定义中使用对象[[Prototype]]来创建,作为 Object.create() 的一个替代。

# prototype chain 原型链

a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.

如上,在 ECMAScript 2019 规范里,只通过短短的一句话,就介绍完了 prototype chain

原型链的概念,仅仅是在原型这个概念基础上所作的直接推论。

既然 prototype 只是恰好作为另一个对象的隐式引用的普通对象。那么,它也是对象,也符合一个对象的基本特征。

每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找...... 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
@前端进阶之旅: 代码已经复制到剪贴板

# 结论:

  • __proto__ 是原型链查询中实际用到的,它总是指向 prototype

  • prototype 是函数所独有的**,**在定义构造函数时自动创建,它总是被 proto 所指。

  • 所有对象都有__proto__属性,函数这个特殊对象除了具有__proto__属性,还有特有的原型属性prototype。prototype对象默认有两个属性,constructor属性和__proto__属性。prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式。constructor,这个属性包含了一个指针,指回原构造函数。

← 前后端分离之数据Mock原生JS补给(上) →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
      • 写在前面
      • 原型
        • prototype
        • constructor与prototype联系
        • __proto__和prototype关系
          • 显示原型和隐式原型
        • 总结
        • 补充
          • Object和Function的鸡和蛋的问题
          • MDN的推荐
      • prototype chain 原型链
        • 结论:
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合