原型与原型链的详细剖析

写在最前: 希望各位看完这篇文章后,再也不用害怕JS原型链部分的知识! -- by Fitz

一起努力,加油吧!

原型

原型分为两种显式原型prototype隐式原型__proto__

显式原型prototype

显式原型prototype存在于函数中,是函数的一个属性,它默认指向一个Object空对象(原型对象)

注意: Object空对象(原型对象)只是内容为空, 并不是真正意义上的空对象Object.create(null), 还是能够通过原型链看到Object.prototype上的各种属性、方法,例如: toString()

  1. console.log(Object.prototype)
  2. console.log(typeof Object.prototype) // object

函数原型对象上的constructor属性对应的函数对象自身

  1. console.log(Object.prototype.constructor === Object) // true
  2. /*
  3. 例如:
  4. 我定义了一个叫Test的函数
  5. Test这个函数拥有它自己的prototype原型对象
  6. 原型对象上的constructor属性对应的就是Test函数自己
  7. */
  8. console.log(test.prototype.constructor === test) // true

实例都会自动拥有其函数(构造函数)原型对象上的方法、属性

  1. function Person () {}
  2. Person.prototype.sayHello = function () {
  3. console.log('Hello')
  4. }
  5. var fitz = new Person() // fitz是Person构造函数的一个实例
  6. fitz.sayHello() // 'Hello'

隐式原型__proto__

每个实例对象都拥有隐式原型属性__proto__

  1. function Student () {
  2. // 构造函数Student
  3. }
  4. let fitz = new Student() // fitz是Student的实例对象
  5. console.log(fitz.__proto__) // {constructor: ƒ}

显式原型prototype与隐式原型__proto__的关系

  1. 构造函数的显式原型prototype默认指向一个空的(没有我们自己定义的属性、方法)Object对象
  2. 构造函数的每个实例上都有的隐式原型__proto__, 都指向着构造函数的显式原型prototype

实例对象的隐式原型属性就是其构造函数的显式原型属性

  1. function Student () {
  2. // 构造函数Student
  3. }
  4. let fitz = new Student() // fitz是Student的实例对象
  5. console.log(fitz.__proto__ === Student.prototype) // true

关于原型对象创建整体的流程

  1. function Person () {} // 函数创建的时候,JS引擎为Person函数自动添加prototype对象属性, 属性指向一个空的Object对象
  2. let fitz = new Person() // 实例对象创建的时候, JS引擎自动添加__proto__对象属性, 同时将这个__proto__指向该实例对象的构造函数的prototype
  3. /*
  4. JS引擎自动做了的事:
  5. 1. Person.prototype = new Object()
  6. 2. per1.__proto__ = Person.prototype
  7. */

原型链(隐式原型链)

原型链指的是: 在访问一个对象中的属性时,会先在自身中寻找,如果没有找到就会沿着__proto__向上寻找,如果找到就返回属性,没有就返回undefined

  1. function Student () {
  2. this.sayName = function () {
  3. console.log('Fitz')
  4. }
  5. }
  6. // 向Student的显示原型对象上添加sayAge()方法
  7. Student.prototype.sayAge = function () {
  8. console.log(21)
  9. }
  10. var a = new Student()
  11. a.sayName() // 'Fitz'
  12. a.sayAge() // 21
  13. console.log(a.toString()) // [Object object]

探寻原型链的尽头

首先,理清从自定义实例对象Object构造函数的prototype的关系

  1. // Object构造函数是JS引擎定义、生成的
  2. console.log(Object)
  3. // 查看Object的显示原型对象
  4. console.log(Object.prototype) // 能够看到toString()等方法
  5. // 自定义一个Student构造函数
  6. function Student () {}
  7. const stu = new Student() // 创建一个Student实例对象
  8. /*
  9. 因为原型链就是隐式原型链,本质上是沿着隐式原型属性__proto__
  10. 向上寻找属性、方法的一个过程
  11. */
  12. // 所以我们通过stu实例对象探寻原型链的尽头
  13. console.log(stu.__proto__) // 实例stu的隐式原型
  14. // 实例对象的__proto__ 指向 它构造函数的prototype
  15. console.log(stu.__proto__ === Student.prototype) // true
  16. // 构造函数的prototype默认是一个空的Object实例对象
  17. console.log(Student.prototype)
  18. // 空的Object实例对象的构造函数一定是Object构造函数
  19. console.log(Student.prototype.__proto__ === Object.prototype) //true
  20. /*
  21. 到这里,暂时总结一下此时的原型链状态:
  22. stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype
  23. */

然后就是最关键的部分: 着重理清Object构造函数的prototype往后部分的所有内容

  1. // Object构造函数是JS引擎定义、生成的
  2. console.log(Object)
  3. // 查看Object的显示原型对象
  4. console.log(Object.prototype) // 能够看到toString()等方法
  5. // 最为关键的一步, 这一步直接揭示了原型链的尽头在哪
  6. console.log(Object.prototype.__proto__) // null

由此我们就能知道,原型链的尽头就是: Object.prototype

  1. // Object构造函数是JS引擎定义、生成的
  2. console.log(Object)
  3. // 查看Object的显示原型对象
  4. console.log(Object.prototype) // 能够看到toString()等方法
  5. // 自定义一个Student构造函数
  6. function Student() { }
  7. const stu = new Student() // 创建一个Student实例对象
  8. /*
  9. 因为原型链就是隐式原型链,本质上是沿着隐式原型属性__proto__
  10. 向上寻找属性、方法的一个过程
  11. */
  12. // 所以我们通过stu实例对象探寻原型链的尽头
  13. console.log(stu.__proto__) // 实例stu的隐式原型
  14. // 实例对象的__proto__ 指向 它构造函数的prototype
  15. console.log(stu.__proto__ === Student.prototype) // true
  16. // 构造函数的prototype默认是一个空的Object实例对象
  17. console.log(Student.prototype)
  18. // 空的Object实例对象的构造函数一定是Object构造函数
  19. console.log(Student.prototype.__proto__ === Object.prototype) //true
  20. /*
  21. 到这里,暂时总结一下此时的原型链状态:
  22. stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype
  23. */
  24. // 最为关键的一步, 这一步直接揭示了原型链的尽头在哪
  25. console.log(Object.prototype.__proto__) // null
  26. /*
  27. 到这里,我们就能总结出原型链的尽头就是Object.prototype的结论:
  28. stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype => null
  29. */

完整详尽的分析原型链

基于这一张图,我们就能够比较全面的掌握JavaScript中原型链的概念,在分析前,小伙伴们可以看这张图先自己思考一遍

接下来是全面总结、分析原型链知识的部分

  1. /*
  2. 根据上面这张图由易入难,完整分析原型链
  3. */
  4. // ===============第一部分: 自定义的构造函数及其实例============
  5. function Foo () {} // 1. 构造函数Foo
  6. var f1 = new Foo() // 2. 实例对象f1
  7. // 3. 实例对象的隐式原型 指向 其构造函数的显示原型
  8. console.log(f1.__proto__ === Foo.prototype) // true
  9. // 4. 构造函数的显式原型是一个空的object对象
  10. // 5. 这个空的object对象是Object构造函数的实例
  11. console.log(Foo.prototype.__proto__ === Object.prototype) // true
  12. // 6. 自定义构造函数是 Function构造函数的实例
  13. // 换句话说: Foo这个构造函数,是new Function()出来的
  14. console.log(Foo.__proto__ === Function.prototype) // true
  15. // ===============第一部分: 自定义的构造函数及其实例============
  16. // =============第二部分: Object构造函数及原型链的尽头============
  17. console.log(Object) // 1. ƒ Object() { [native code] }
  18. // 2. 实例对象o1、o2
  19. var o1 = new Object()
  20. var o2 = {}
  21. // 3. Object构造函数也是Function构造函数的实例
  22. // 换句话说: Object这个构造函数,也是new Function()出来的
  23. console.log(Object.__proto__ === Function.prototype) // ture
  24. // 4. Object构造函数的显式原型(Object.prototype)就是原型链的尽头
  25. console.log(Object.prototype.__proto__) // 5. null
  26. // =============第二部分: Object构造函数及原型链的尽头============
  27. // =================第三部分: 特殊Function构造函数================
  28. console.log(Function) // 1. ƒ Function() { [native code] }
  29. // 2. Function构造函数的原型对象跟其他普通的构造函数一样 隐式原型指向空object对象
  30. console.log(Function.prototype.__proto__ === Object.prototype) // true
  31. // 3. 重点特殊的地方: Function构造函数是自己的实例
  32. // 换句话说: Function构造函数,是new Function()自己出来的, 即我生出我自己
  33. console.log(Function.__proto__ === Function.prototype) // true
  34. // 4. Function.prototype是一个函数,而不是像其他函数一样是一个空的Object对象
  35. console.log(typeof Function.prototype) // function
  36. // =================第三部分: 特殊Function构造函数================

这张图配合上面的代码

关于原型链的补充总结

所有函数都是 Function构造函数 的实例对象,包括Function构造函数自己

标题换句话表达, 所有函数的__proto__都指向Function.prototype

Function.__proto__指向Function.prototype

  1. // 所有函数都是 Function构造函数 的实例对象
  2. /* 换句话说: 无论是
  3. 普通函数、方法
  4. 自定义构造函数
  5. Object等一些JS引擎内置的构造函数
  6. Function构造函数本身(我生我自己)
  7. 都是Function构造函数的实例对象
  8. */
  9. const sayHello = function () {console.log('hello')} // 自定义函数
  10. const Student = function (name) { // 自定义构造函数
  11. this.name = name
  12. }
  13. console.log(sayHello.__proto__=== Function.prototype)
  14. console.log(Student.__proto__=== Function.prototype)
  15. console.log(Object.__proto__=== Function.prototype)
  16. console.log(Date.__proto__=== Function.prototype)
  17. // 最为特殊的Function(我生我自己)
  18. console.log(Function.__proto__=== Function.prototype)

Function.prototype是一个函数对象

  1. console.log(typeof Function.prototype) // function
  2. console.dir(Function.prototype)

所有函数的显式原型prototype,都指向Object.prototype,Object构造函数的显式原型除外

  1. console.log(Function.prototype instanceof Object) // true
  2. console.log(Function.prototype.__proto__ === Object.prototype) // true
  3. console.log(Date.prototype instanceof Object) // true
  4. console.log(Date.prototype.__proto__ === Object.prototype) // true
  5. // object构造函数的原型对象除外的理由, Object.prototype是原型链的尽头
  6. console.log(Object.prototype instanceof Object) // false
  7. console.log(Object.prototype.__proto__ === Object.prototype) // false
  8. console.log(Object.prototype.__proto__) // null

原型链的应用

读取实例对象的属性值时,会先在自身中寻找,如果自身没有会到原型链中找

  1. function Student () {}
  2. Student.prototype.person = 'Fitz'
  3. var f = new Student()
  4. // person属性是原型对象上的,而不是实例本身的
  5. console.log(f.person) // 'Fitz'

对实例的属性进行操作时,不会影响(查找)原型链,如果实例中没有当前属性,会自动添加

  1. function Student () {}
  2. Student.prototype.person = 'Fitz'
  3. var f = new Student()
  4. // 如果实例中没有当前属性,会自动添加
  5. f.person = 'Lx'
  6. f.age = 21
  7. /*
  8. 属性只会在实例上,与原型链无关
  9. 可以运用前面的 引用数据类型的知识理解
  10. */

利用原型链,将实例的方法添加在原型对象上,实例的属性添加在实例自身

好处: 避免了每次实例化对象时,都创建出一模一样的方法,节省内存

  1. function Person (name, age){
  2. this.name = name
  3. this.age = age
  4. }
  5. // 实例的方法统一放在构造函数的原型对象上
  6. // 这样实例在调用方法时,可以通过原型链顺利找到该方法
  7. Person.prototype.printInfo = function () {
  8. console.log(`name: ${this.name}`)
  9. console.log(`age: ${this.age}`)
  10. }
  11. var fitz = new Person('fitz', 21)
  12. fitz.printInfo()

原型链继承

尝试使用原型链来模拟类的继承

实现的关键是: 子类的原型是父类的实例

思路来源于: 既然所有的实例对象都能调用toString()方法那就看看为什么,

  1. toString()方法在Object.prototype显式原型对象上
  2. 实例对象的隐式原型__proto__ 指向其 构造函数的显式原型prototype
  3. 而关键就是,构造函数的显式原型是Object.prototype的实例对象
  1. // 模拟父类
  2. function Father() {
  3. _Fathername = '我是父类'
  4. this.name = 'Father'
  5. }
  6. Father.prototype.getFathername = function () {
  7. console.log(_Fathername)
  8. }
  9. Father.prototype.getName = function () {
  10. console.log(this.name)
  11. }
  12. // 模拟子类
  13. function Son() {
  14. _SonName = '我是子类'
  15. this.name = 'Son'
  16. }
  17. // 实现子类继承父类
  18. Son.prototype = new Father()
  19. Son.prototype.getSonName = function () {
  20. console.log(_SonName)
  21. }
  22. // 需要实现的目标
  23. var son = new Son()
  24. // 能在子类使用父类的方法
  25. son.getFathername() // '我是父类'
  26. son.getName() // 'Son'
  27. console.log(son)

一文让你对js的原型与原型链不再害怕、迷惑的更多相关文章

  1. JS原型与原型链(好文看三遍)

    一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object ,Function 是JS自带的函数对象. 下面举例说明: function ...

  2. 一文读懂JS中的原型和原型链(图解)

    讲原型的时候,我们应该先要记住以下几个要点,这几个要点是理解原型的关键: 1.所有的引用类型(数组.函数.对象)可以自由扩展属性(除null以外). 2.所有的引用类型都有一个’_ _ proto_ ...

  3. js基础例子dom+原型+oop基础知识记录01

    //oo:概念是计算机中对于现实世界的理解和抽象的方法 //由计算机利用编程技术发展到现在的产物 //面向对象几要素 //对象:由属性和方法组成的集合 //属性:保存数据,存储在对象内存空间中的唯一的 ...

  4. 深入JS原型与原型链

    要了解原型和原型链,首先要理解普通对象和函数对象. 一.普通对象和函数对象的区别 在Javascript的世界里,全都是对象,而对象之间也是存在区别,我们首先区分一下普通对象和函数对象,如下代码: f ...

  5. jquery实现点击展开列表同时隐藏其他列表 js 对象操作 对象原型操作 把一个对象A赋值给另一个对象B 并且对象B 修改 不会影响 A对象

    这篇文章主要介绍了jquery实现点击展开列表同时隐藏其他列表的方法,涉及jquery鼠标事件及节点的遍历与属性操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下 本文实例讲述了jquery实现点击 ...

  6. JS原型、原型链、构造函数、实例与继承

    https://cloud.tencent.com/developer/article/1408283 https://cloud.tencent.com/developer/article/1195 ...

  7. 一文带你了解js数据储存及深复制(深拷贝)与浅复制(浅拷贝)

    背景 在日常开发中,偶尔会遇到需要复制对象的情况,需要进行对象的复制. 由于现在流行标题党,所以,一文带你了解js数据储存及深复制(深拷贝)与浅复制(浅拷贝) 理解 首先就需要理解 js 中的数据类型 ...

  8. Js 原型和原型链

    Js中通过原型和原型链实现了继承 Js对象属性的访问,首先会查找自身是否拥有这个属性 如果查到,则返回属性值,如果找不到,就会遍历原型链,一层一层的查找,如果找到就会返回属性值 直到遍历完Object ...

  9. 【repost】JS原型与原型链终极详解

    一. 普通对象与函数对象  JavaScript 中,万物皆对象!但对象也是有区别的.分为普通对象和函数对象,Object ,Function 是JS自带的函数对象.下面举例说明 function f ...

随机推荐

  1. 四十三:漏洞发现-WEB应用之漏洞探针类型利用修复

    已知CMS 如常见的dedecms,discuz,wordpress等源码结构,这种一般采用非框架开发,但是也有少部分采用框架类开发,针对此类源码程序的安全监测, 我们要利用公开的漏洞进行测试,如不存 ...

  2. 【bzoj 3333】排队计划(线段树)

    n个数,求一次逆序对.接着有m次修改操作,把每次输入的位置p的数之后<=它的数取出来,从小到大排序后再放回空位里,求逆序对.(N,M<=500,000 , Ai<=10^9)思路:1 ...

  3. L2-007 家庭房产 (25分) 并查集

    题目链接 题解:并查集把一个家的并在一起,特殊的一点是编号大的并到小的去.这个题有个坑编号可能为0000,会错数据3和5. 1 #include<bits/stdc++.h> 2 usin ...

  4. S - Layout (最短路&&差分约束)

    Like everyone else, cows like to stand close to their friends when queuing for feed. FJ has N (2 < ...

  5. 2.PowerShell概述

    PowerShell PowerShell命令窗一般随系统带着,运行->输入:powershell,即可打开命令窗口. 命令 Powershell有诸多命令,兼容cmd命令 语法和命令 在此我推 ...

  6. 基于CentOS-7的redis下载和安装

    1.下载和安装 在我安装的虚拟机中,我把所有自己安装的软件都放在了/ph/install 目录下,具体以自己实际情况为准. [root@localhost ~]$ cd /ph/install #进入 ...

  7. SSH服务连接

    SSH基本概述 SSH是一个安全协议,在进行数据传输时,会对数据包进行加密处理,加密后在进行数据传输.确保了数据传输安全. SSH服务 ssh: secure shell, protocol, 22/ ...

  8. 转载-cookie和session的窃取

    一.cookie的基本特性 如果不了解cookie,可以先到 wikipedia 上学习一下. http request 浏览器向服务器发起的每个请求都会带上cookie: GET /index.ht ...

  9. HTTP1.0和HTTP1.1以及HTTP2.0的区别

    (1)连接方面 HTTP1.0使用非持久连接,即在非持久连接下,一个tcp连接只传输一个Web对象.每次请求和响应都需要建立一个单独的连接,每次连接只是传输一个对象,严重影响客户机和服务器的性能. H ...

  10. Leetcode(9)-回文数

    判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 示例 1: 输入: 121 输出: true 示例 2: 输入: -121 输出: false 解释: 从左向 ...