JS中的原型链和原型的认识
这篇文章主要是学习一下JavaScript中的难点------原型和原型链
自定义一个对象
我们学习一门编程语言,必然要使用它完成一些特定的功能,而面向对象的语言因为符合人类的认知规律,在这方面做得很好,今天我以JS为例,探索一下JS不同于其他面向对象的语言的地方-------原型和原型链
- 首先,假设你在做一个项目,要造一个新的对象,标准库里面没有。那你只能用构造函数去构造一个
function Person(){
//构造函数
}
以上Person
就是一个构造函数,可以用来生成小明
小红
等等人类的实例。
var person = new Person() //构造出一个对象
person.name = 'xiaoming'
console.log(person.name) // 'xiaoming'
那原型在哪呢,认识原型先认识一下prototype
属性
prototype
先看一段代码
Person.prototype.name = 'god'
var person2 = new Person()
console.log(person2.name) //person2的名字是啥呢???
从上面三行代码,猜一猜person2的名字是啥,没错,就是god
person2没有自己规定名字,但是Person构造函数的prototype上绑定了name,所以由Person构造函数构造的实例对象都默认有一个god
的名字。
- 而且这个prototype属性只存在构造函数上,也就是说prototype是构造函数的属性!!!
- 那这个prototype指向了哪里,那个地方就是--------调用构造函数而生成的对象实例的原型,存的是这个原型的地址。
以上就是构造函数和原型之间的关系,构造函数内部的prototype属性指向了实例对象原型的地址。
- 原型里面存的是所有实例化对象的共有属性,比如这个例子的
name
。
上面红框是Person实例的原型,如果不直观的话,下面直接看Array实例的原型
红框的都是大家熟悉的数组的方法吧,他们都放在数组的共有属性里面。
上面的两幅原型图里面,我们竟然发现有共同点,都有一个熟悉 constructor
属性,待会研究一下这个属性。
现在,我们已经知道了构造函数和原型的关系了,那person
person2
这些实例对象和原型有啥关系呢
__proto__
属性
每一个构造的实例对象,内部有一个__proto__
属性,它指向了实例原型,存的是原型的地址。
person.__proto__ === Person.prototype
true
-
__proto__
是对象的属性,而且是浏览器强逼着ECMAScript
加上的这个规范。
以上是构造函数、实例、实例原型之间的关系,不过方向是单向的,哪能不能让它循环起来呢,原型可不可以指向构造函数或者实例呢?
constructor
- 还记得上面我们发现的那个 不同的原型 都有的一个共同的属性
constructor嘛
构造函数、实例、实例原型之间的关系的方向可以循环的关键就在这里了。我们一直叫构造函数,构造函数的,为什么这么叫呢,对,就是这个原型里面的constructor
属性。
- 不过原型是无法指向实例的,只可以通过
constructor
属性指向 构造函数
Person === Person.prototype.constructor
true
上面就是经典的铁三角了。
由以上知识得出一个小公式
对象.__proto__ === 构造函数.prototype
而Person这个构造函数也是对象,那么
Person.__proto__ === ???
上面的问号填啥呢,我们按公式填空,应该是构造函数.prototype
,Person构造函数的构造函数是谁呢?没错,就是Function
。
Person.__proto__ === Function.prototype
true
在控制台验证确实如此。
所以有些以前的疑惑也解开了
Array.__proto__ === Function.prototype
true
String.__proto__ === Function.prototype
true
- 那问题又来了,
构造函数.prototype
也是对象啊,它指向谁 - 既然是对象,那么里面就有
__proto__
属性
Person.prototype.__proto__ === ???
问号填什么呢,原型是由谁构造的呢,我们想到了所有对象的根----------Object
原型的原型
在控制台验证如下
Person.prototype.__proto__ === Object.prototype
true
Array.prototype.__proto__ === Object.prototype
true
String.prototype.__proto__ === Object.prototype
true
既然引出了Object,我们来看一下所有对象的祖宗的原型吧
Object.prototype.__proto__ === null
true
特殊的Function
前面我们看到了Function构造方法构造除了所有的函数,包括普通的构造函数。
那么他自身也是一个函数,所以也是由Function构造函数构造的。所以由总结的公式可以知道
Function.__proto__ === Function.prototype
而且,下面这个很重要,易错
Function.prototype === Object.__proto__ //哈哈,这个老别扭了吧,还给你倒过来写,很容易错的
解释:Object也是构造函数啊,属于对象。Object构造函数也是由Function把它构造出来的,所以是结果是true
完整的总结
- 当你new一个构造函数的时候,创建一个函数实例,那么 『
函数实例.__proto__ === 该构造函数.prototype
』- 所有的函数都是由
Function
构造出来的,那么 『被构造出来的其他函数.__proto__ === Function.prototype
』- 所有的构造函数的原型对象都是由Object构造出来的,那么 『
所有的构造函数.prototype.__proto__ === Object.prototype
』
instanceof
运算符的实质
首先这有几个题
Object instanceof Function
Function instanceof Object
Function instanceof Function
Object instanceof Object
- 能不假思索的说出来吗,大声告诉我,答案是什么。
- 没错,全是
true
虽然 instanceof
运算符算是我们的老朋友了,不过背后是咋判断的呢
规范是这么写的
object instanceof constructor
参数
object
要检测的对象.
constructor
某个构造函数
instanceof
运算符用来检测constructor.prototype
是否存在于参数object
的原型链上
- 对于
Object instanceof Function
,Object.__proto__ === Function.prototype
为true
,解决 - 对于
Function instanceof Object
,Function.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype
为true
,解决。 - 对于
Function instanceof Function
,Function.__proto__ === Function.prototype
为true
,解决 - 对于
Object instanceof Object
,Object.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype
为true
,解决
只要上面的推导,任一环节你写错或者压根写不出来(在今天之前我也是瞎搞,运气好了蒙对了),说明你就不是真懂原型链,面试问到稍微变形的题还是易错。
- 在上面的各种原型的变换中,其实难点就在于
Function
Object
构造函数也是对象
原型对象等所有对象都由Object构造
这四个点。
- 而且看待问题的角度不同,对事实的认知影响很大。比如
Object
Function
你把它们看成构造函数或者对象,结果不同的。不同的场合,换不同的角度去认识它们,事物具有两面性。大概就是背了多年的同一性,巴拉巴拉一堆的哲学原理吧。 - 各种原型的分析过程,让我回忆起了,被数学支配的恐惧。逻辑必须合理、一步是一步、抽丝剥茧看本质,大概是这么多年应试教育留在脑子里面的深深烙印吧。
- JS越来越有意思了,感觉应该快入门了。
原型链
- 咦,在哪呢。
- 原型链就在上面啊。每个对象,沿着
__prto__属性
有一条链子,找呀找呀,一直找到Object.prototype
为止
感谢冴羽大神和若愚大神的文章。
原文地址:https://www.cnblogs.com/homehtml/p/12054070.html
- 18.CSS
- R语言可视化——ggplot图表系统中的形状
- [机器学习Lesson 1 Introduction] 机器学习的动机与应用
- 洛谷P3377 【模板】左偏树(可并堆)
- Numpy 修炼之道 (12)—— genfromtxt函数
- 19.JavaScript
- 20.DOM
- 洛谷P3273 [SCOI2011]棘手的操作
- [机器学习Lesson 2]代价函数之线性回归算法
- Docker初尝试1.What is Docker?2.Try It3.Use It
- 洛谷P1456 Monkey King
- 21.jQuery
- Session分布式共享 = Session + Redis + Nginx
- 22.jQuery(实例)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- pytest 自动化测试框架(二)
- Web | Django 与数据库交互,你需要知道的 9 个技巧
- 商机负责人与商机团队负责人不一致时更新团队负责人为商机负责人语句
- mysql常用语句集合(仅供工作日常学习参考)
- 新一代Notebook神器出现,Jupyter危险了!
- vmstat 监视内存使用情况
- Android开发重写Animation实现下拉图片后弹射回去效果示例
- Android 中RxPermissions 的使用方法详解
- Android构建Material Design应用详解
- Android中DrawerLayout实现侧滑菜单效果
- Android获取系统储存以及内存信息的方法(一)
- Android开发实现读取excel数据并保存为xml的方法
- 详解Kotlin 中使用和配置 Dagger2
- Android开发使用自定义View将圆角矩形绘制在Canvas上的方法
- Android编程双重单选对话框布局实现与事件监听方法示例