原型与原型链的详细剖析

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

一起努力,加油吧!

原型

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

显式原型prototype

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

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

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

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

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

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

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

隐式原型__proto__

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

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

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

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

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

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

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

function Person () {}   // 函数创建的时候,JS引擎为Person函数自动添加prototype对象属性, 属性指向一个空的Object对象

let fitz = new Person() // 实例对象创建的时候, JS引擎自动添加__proto__对象属性, 同时将这个__proto__指向该实例对象的构造函数的prototype

/*
JS引擎自动做了的事:
1. Person.prototype = new Object()
2. per1.__proto__ = Person.prototype
*/

原型链(隐式原型链)

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

function Student () {
this.sayName = function () {
console.log('Fitz')
}
}
// 向Student的显示原型对象上添加sayAge()方法
Student.prototype.sayAge = function () {
console.log(21)
} var a = new Student()
a.sayName() // 'Fitz'
a.sayAge() // 21
console.log(a.toString()) // [Object object]

探寻原型链的尽头

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

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

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

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

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

// Object构造函数是JS引擎定义、生成的
console.log(Object)
// 查看Object的显示原型对象
console.log(Object.prototype) // 能够看到toString()等方法 // 自定义一个Student构造函数
function Student() { }
const stu = new Student() // 创建一个Student实例对象 /*
因为原型链就是隐式原型链,本质上是沿着隐式原型属性__proto__
向上寻找属性、方法的一个过程
*/ // 所以我们通过stu实例对象探寻原型链的尽头
console.log(stu.__proto__) // 实例stu的隐式原型
// 实例对象的__proto__ 指向 它构造函数的prototype
console.log(stu.__proto__ === Student.prototype) // true
// 构造函数的prototype默认是一个空的Object实例对象
console.log(Student.prototype)
// 空的Object实例对象的构造函数一定是Object构造函数
console.log(Student.prototype.__proto__ === Object.prototype) //true
/*
到这里,暂时总结一下此时的原型链状态:
stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype
*/ // 最为关键的一步, 这一步直接揭示了原型链的尽头在哪
console.log(Object.prototype.__proto__) // null /*
到这里,我们就能总结出原型链的尽头就是Object.prototype的结论:
stu.__proto__ => Student.prototype => object.__proto__ => Object.prototype => null
*/

完整详尽的分析原型链

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

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

/*
根据上面这张图由易入难,完整分析原型链
*/ // ===============第一部分: 自定义的构造函数及其实例============
function Foo () {} // 1. 构造函数Foo
var f1 = new Foo() // 2. 实例对象f1
// 3. 实例对象的隐式原型 指向 其构造函数的显示原型
console.log(f1.__proto__ === Foo.prototype) // true
// 4. 构造函数的显式原型是一个空的object对象
// 5. 这个空的object对象是Object构造函数的实例
console.log(Foo.prototype.__proto__ === Object.prototype) // true
// 6. 自定义构造函数是 Function构造函数的实例
// 换句话说: Foo这个构造函数,是new Function()出来的
console.log(Foo.__proto__ === Function.prototype) // true
// ===============第一部分: 自定义的构造函数及其实例============ // =============第二部分: Object构造函数及原型链的尽头============
console.log(Object) // 1. ƒ Object() { [native code] }
// 2. 实例对象o1、o2
var o1 = new Object()
var o2 = {}
// 3. Object构造函数也是Function构造函数的实例
// 换句话说: Object这个构造函数,也是new Function()出来的
console.log(Object.__proto__ === Function.prototype) // ture
// 4. Object构造函数的显式原型(Object.prototype)就是原型链的尽头
console.log(Object.prototype.__proto__) // 5. null
// =============第二部分: Object构造函数及原型链的尽头============ // =================第三部分: 特殊Function构造函数================
console.log(Function) // 1. ƒ Function() { [native code] }
// 2. Function构造函数的原型对象跟其他普通的构造函数一样 隐式原型指向空object对象
console.log(Function.prototype.__proto__ === Object.prototype) // true
// 3. 重点特殊的地方: Function构造函数是自己的实例
// 换句话说: Function构造函数,是new Function()自己出来的, 即我生出我自己
console.log(Function.__proto__ === Function.prototype) // true
// 4. Function.prototype是一个函数,而不是像其他函数一样是一个空的Object对象
console.log(typeof Function.prototype) // function
// =================第三部分: 特殊Function构造函数================

这张图配合上面的代码

关于原型链的补充总结

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

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

Function.__proto__指向Function.prototype

// 所有函数都是 Function构造函数 的实例对象
/* 换句话说: 无论是
普通函数、方法
自定义构造函数
Object等一些JS引擎内置的构造函数
Function构造函数本身(我生我自己)
都是Function构造函数的实例对象
*/
const sayHello = function () {console.log('hello')} // 自定义函数
const Student = function (name) { // 自定义构造函数
this.name = name
}
console.log(sayHello.__proto__=== Function.prototype)
console.log(Student.__proto__=== Function.prototype)
console.log(Object.__proto__=== Function.prototype)
console.log(Date.__proto__=== Function.prototype)
// 最为特殊的Function(我生我自己)
console.log(Function.__proto__=== Function.prototype)

Function.prototype是一个函数对象

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

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

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

原型链的应用

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

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

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

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

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

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

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

原型链继承

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

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

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

  1. toString()方法在Object.prototype显式原型对象上
  2. 实例对象的隐式原型__proto__ 指向其 构造函数的显式原型prototype
  3. 而关键就是,构造函数的显式原型是Object.prototype的实例对象
// 模拟父类
function Father() {
_Fathername = '我是父类'
this.name = 'Father'
}
Father.prototype.getFathername = function () {
console.log(_Fathername)
}
Father.prototype.getName = function () {
console.log(this.name)
} // 模拟子类
function Son() {
_SonName = '我是子类'
this.name = 'Son'
} // 实现子类继承父类
Son.prototype = new Father() Son.prototype.getSonName = function () {
console.log(_SonName)
} // 需要实现的目标
var son = new Son()
// 能在子类使用父类的方法
son.getFathername() // '我是父类'
son.getName() // 'Son' 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. AtCoder Beginner Contest 165

    比赛链接:https://atcoder.jp/contests/abc165/tasks A - We Love Golf 题意 区间 $[a, b]$ 中是否存在 $k$ 的倍数. 代码 #inc ...

  2. 【uva 1615】Highway(算法效率--贪心 区间选点问题)

    题意:给定平面上N个点和一个值D,要求在x轴上选出尽量少的点,使得对于给定的每个店,都有一个选出的点离它的欧几里德距离不超过D. 解法:先把问题转换成模型,把对平面的点满足条件的点在x轴的直线上可得到 ...

  3. hdu5643 King's Game(约瑟夫环+线段树)

    Problem Description In order to remember history, King plans to play losephus problem in the parade ...

  4. hdu3480 Division

    Problem Description Little D is really interested in the theorem of sets recently. There's a problem ...

  5. 单源最短路问题 Dijkstra 算法(朴素+堆)

    选择某一个点开始,每次去找这个点的最短边,然后再从这个开始不断迭代,更新距离. 代码: 朴素(vector存图) #include <iostream> #include <cstd ...

  6. service配置文件

    [Unit]Description="itcp Service"After=network.target cs_tcp.service [Service]Type=simpleGu ...

  7. 数理统计8:点估计的有效性、一致最小方差无偏估计(UMVUE)、零无偏估计法

    在之前的学习中,主要基于充分统计量给出点估计,并且注重于点估计的无偏性与相合性.然而,仅有这两个性质是不足的,无偏性只能保证统计量的均值与待估参数一致,却无法控制统计量可能偏离待估参数的程度:相合性只 ...

  8. Java15变量竟然没什么区别,八大基本数据类型你知道吗?

    变量是什么? 变量是用来为不同数据类型在内存中分配的空间用来储存该数据. 不同于python这样的弱类型语言,变量声明不需要定义数据类型,就和写数学方程式一般,谁等于谁即可.而Java这个发展了多个版 ...

  9. 牛客网-n的约数【dfs】

    题目描述:戳这里 解题思路:这题思路好想,n最多也就是20个不同的素数相乘,把所有可能的素数找到,然后枚举素数个数就行了. n = p1^q1 + p2^q2 + p3 ^q3 + ... + pi ...

  10. Linux 驱动框架---input子系统框架

    前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...