1.函数的prototype
1.函数的prototype属性
-
每个函数都有一个prototype属性,它默认指向一个Object空对象(原型对象)
-
原型对象中有一个属性constructor,它指向函数对象(如下图)
2.给原型对象添加属性(一般为方法)
-
作用:函数的所有实例对象都会拥有原型中的属性(方法)
function Fun(){ } const fun = new Fun() console.log(fun.__proto__===Fun.prototype);//true
2.显式原型与隐式原型
-
每个函数function都有一个protorype,即显示原型
-
每个构造函数的实例对象都有一个__ proto __,即隐式原型
-
对象的隐式原型的值为其对应构造函数的显式原型的值
-
内存结构
-
总结
- 函数的prototype属性:在定义函数时js内部自动添加,默认值是一个空Object对象
- 对象的__ proto __属性:在创建对象时js内部自动添加,默认值是构造函数的prototype属性值
- 程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)
3.原型链
1.定义
解释:原型链就是根据对象的__ proto __指向,一层一层连接起来的具有关联性的对象集合
-
访问一个对象的属性时
- 先在自身属性中查找,找到返回
- 如果没有找到,再沿着__ proto __这条链返回
- 如果最终没找到,返回indefined
-
别名:隐式原型链
-
作用:查找对象的属性(方法)
2.分析
原型链就是根据对象的__ proto __指向,一层一层连接起来的具有关联性的对象集合
当我们调用一个对象的方法时,js内部会根据原型链进行查找,如果当前层没有此方法,则往它的__ proto __指向的对象查找,直到找到这个方法
Object原型对象是原型链的尽头,它的__ proto __指向为空,如果在Object的原型对象中仍然没有找到此方法,则说明对象中不包含这个方法
function Fun{}
const fun = new Fun()
const Object = new Object()
如上图所示,假设我现在想在fun函数中查找toString()方法,那么查询的顺序是这样的:
fun—>fun. __ proto __ (没找到,往下层寻找)—>fun. __ proto __ . __ proto __ (没找到,往下层寻找)—>
fun. __ proto __ . __ proto __ . __ proto __(到达Object的原型对象,找到了toString()方法)
如果查找一个不存在的方法,会返回undefined
4.构造函数-原型-实例对象之间的关系
1.定义
- 每个构造函数都有一个原型对象,这个原型对象又是另一个原型对象的实例
- 每个构造函数的实例也有一个原型对象,这个原型对象也是另一个原型对象的实例
- 构造函数的显式原型等于这个构造函数的实例的隐式原型
- 构造函数以及他的实例最终都指向Object的原型对象
2.解释
我们画图来描述
如上图
fun是Fun的实例,即 const fun = new Fun()
Fun又是Function的实例,即const Fun = new Function()
注意:Function是引用属性,一切函数都是Function的实例对象
我们先来分析fun函数:
fun是Fun实例对象,它只有隐式原型属性 __ proto __,它的隐式原型指向它的构造函数Fun的原型对象,
这也印证了我们之前说的一句话:构造函数的显式原型等于这个构造函数的实例的隐式原型,
然后Fun的原型对象又指向Object的原型对象
故而我们可以知道,Fun的原型对象实际上是Object的实例对象
Object的原型对象是原型链的终点,它的原型属性指向null
我们来分析Fun函数:
Fun本身是一个构造函数,同时他也是Function的实例对象,故而Fun既有隐式原型也有显式原型
它的显式原型等于fun的隐式原型,指向的是Fun的原型对象,Fun的原型对象又是Object的实例对象,他最终指向Object的原型对象
它的隐式原型等Function的显式原型,指向的是Function的原型对象,而Function的原型对象实际上也是Object的实例对象,它的原型指向Object的原型对象
我们可以得出结论:一切函数的原型都指向Object的原型对象
需要特别注意的一点是Function的原型关系:Function的隐式原型等于它的显式原型
这怎么解释呢?
实际上可以理解为 Function的构造函数就是它本身,即 const Function = new Function()
通俗一点就是:所有的鸡(函数)都是由鸡妈妈(Function)生的,而鸡妈妈(Function)也是鸡,所以鸡妈妈(Function)也是鸡妈妈(Function)生的
5.探索instanceof属性
1.定义
instanceof:判断对象的具体类型,返回值是布尔值
instanceof是如何进行判断的?
- 表达式:A instanceof B:如果B的显式原型对象在A的原型链上,返回true,否则返回false
2.解释
我们来探讨instanceof判断对象的具体过程
还是这个示例图,我们判断以下表达式
const obj = {}
function Fun(){}
const fun = new Fun()
console.log(obj instanceof Fun)//false
obj instanceof Fun的过程:
对于Fun,我们要查找它的显式原型对象:
Fun—>Fun.prototype(Object的实例对象)
对于obj,我们要沿着它的原型链查找:
obj—>obj.__ proto __(Object的原型对象)
从以上查找路径我们可知,Fun的显式原型对象在obj的原型链中并没有出现,故而返回false
6.面试题
1.题目
//第一题 判断输出的结果
function A(){
}
A.prototype.n = 1
const b = new A()
A.prototype = {
n:2,
m:3
}
const c = new A()
console.log(b.n,b.m,c.n,c.m);//1 undefined 2 3
// 第二题 判断输出的结果
const F = function(){}
Object.prototype.a = function(){
console.log('a()');
}
Function.prototype.b = function(){
console.log('b()');
}
const f = new F()
f.a() // a()
f.b() // undefined
F.a() // a()
F.b() // b()
2.题解
先抛图
我们定义构造函数A,并给A.prototype(A的原型对象)定义变量n为1
创建构造函数A的实例b,此时b的原型链为:
b–>b.__ proto __ (b的隐式原型对象,与A.prototype相等,故而b. __ proto __ .n = 1)–>b. __ proto __ . __ proto __(Object的原型对象)
在定义完b之后,我们修改了A的原型对象,此时A的原型对象中的数据已经被替换,此时n的值为2
创建构造函数A的实例c,此时c的原型链为:
c–>c.__ proto __ (c的隐式原型对象,与A.prototype相等,故而c. __ proto __ .n = 2,c. __ proto __ .m = 3)–>c.__ proto __ . __ proto __(Object的原型对象)
由上可得
我们输出的结果为 1 indefined 2 3
我们给object的原型对象赋值a方法,再给Function的原型对象赋值b方法
此时object的原型链为:
object–>object.prototype(Object的原型对象,并且拥有方法a)
此时Function的原型链为:
Function–>Function.prototype(Function的原型对象,它拥有方法b)–>Function.prototype.__ proto __ (Object的原型对象,它拥有方法a)
我们创建构造函数F的实例f,此时F和f的原型链分别为
F: F–>F. __ proto __ (F的原型对象)–>F. __ proto __ . __ proto __ (Function的原型对象,它拥有方法b)–>F.prototype.prototype.__ proto __(Object的原型对象,它拥有方法a)
f: f–>f.__ proto __ (f的隐式原型对象)–>f. __ proto __ . __ proto __ (Object的原型对新昂,它拥有方法a)
由上可得
我们输出的结果为 a() indefined a() b()
注意:构造函数的实例对象的原型链不会指向Function原型对象,而构造函数的原型链指向Function原型对象