只要是函数,就会有
prototype
和__proto__
;只要是对象,就会有__proto__
。
文章目录
所有对象的原型链尽头:Object.prototype
所有(构造)函数的次级原型链尽头:Function.prototype
源代码深度剖析原型链
// 创建一个构造函数
function Fn() {}
// 实例化对象
var fn = new Fn();
// 构造函数的显式原型===其实例化对象的隐式原型,它们都指向同一个空的object对象
// 这个空的object对象就是Object构造函数的实例化对象(不懂先记住,看完后面就懂了)
console.log(Fn.prototype === fn.__proto__); // true
console.log(typeof Fn.prototype); // object
console.log(typeof fn.__proto__); // object
/*
因为构造函数的显式原型和其实例化对象的隐式原型指向的是同一块空间,
所以当我们向构造函数的显式原型中添加属性时,
其实例化对象的隐式原型也能共享这些属性。
*/
Fn.prototype.test = function () {
console.log('我是Fn函数的显式原型中新增的属性');
}
fn.__proto__.test(); // 我是Fn函数的显式原型中新增的属性
/*
原型链,按照隐式原型一层一层往下找,
先找fn自身,有test方法则使用,没有则去fn.__proto__中查找,
如果找到了,则使用,这里fn.__proto__中有test方法,
但是假设我们没有,则还继续往下找,那么就是找fn.__proto__.__proto__,
那么疑问就来了,这个fn.__proto__.__proto__是谁呢?
这里先记住,这个fn.__proto__.__proto__是fn.__proto__(Object构造函数的实例化对象)的隐式原型,
我们再继续,如果还找不到test方法,则去fn.__proto__.__proto__.__proto__中找,
这里也先记住fn.__proto__.__proto__.__proto__的值为null,
也就意味着fn.__proto__.__proto__是原型链的尽头,因为再去找它的原型找到的值为null,就没了,
原型链的尽头存在着许多对象的方法,这里的方法就是内置的方法。
*/
fn.test(); // 我是Fn函数的显式原型中新增的属性
// 构造函数的显式原型中有一个属性叫constructor,通过它可以找回构造函数本身
console.log(Fn.prototype.constructor); // function Fn() {}
// 那么可以有以下等式成立
console.log(Fn === Fn.prototype.constructor); // true
console.log('------------------------------------------------------------');
console.log(Object); // function Object() {} // 这是Object构造函数
// 上一句输出完,我们会发现,在代码刚开始执行时,Object构造函数就会自动加载进来
// 跟第11行类似,Object构造函数也有显式原型prototype
// 那么我们开始实例化对象
var obj1 = new Object();
var obj2 = {};
// 还是应证了那句话:构造函数的显式原型===其实例化对象的隐式原型
console.log(Object.prototype === obj1.__proto__); // true
console.log(Object.prototype === obj2.__proto__); // true
// 构造函数的显式原型中有一个属性叫constructor,通过它可以找回构造函数本身
console.log(Object.prototype.constructor === Object); // true
console.log('------------------------------------------------------------');
// 我们看一下Object构造函数的实例化对象是什么类型
console.log(typeof obj1, typeof obj2); // object object
// 然后看一下之前定义的Fn构造函数的显式原型及其实例化对象的隐式原型是什么类型
console.log(typeof Fn.prototype); // object
console.log(typeof fn.__proto__); // object
// 因为这两个原型都是object类型,所以也相当于Object构造函数的实例化对象
// 还是应证了那句话:构造函数的显式原型===其实例化对象的隐式原型,同时也给予了第24行答案
console.log(Object.prototype === Fn.prototype.__proto__); // true
console.log(Object.prototype === fn.__proto__.__proto__); // true
// 这里还是要说一下Object构造函数的显式原型的__proto__值为null
console.log(Object.prototype.__proto__); // null
// 所以原型的尽头是:Object.prototype
// 之前在第28行,我们也说了原型链的尽头,所以fn.__proto__.__proto__.__proto__的值也为null
// 那么可以有以下等式成立:
console.log(Object.prototype.__proto__ === fn.__proto__.__proto__.__proto__); // true
console.log('------------------------------------------------------------');
// 我们继续进行实验
console.log(Function); // function Function() {} //这是Function构造函数
// 还是应证了那句话:构造函数的显式原型===其实例化对象的隐式原型
// 那么我们需要知道Function构造函数的实例化对象是什么
// 实验开始
function Foo() {} // 创建一个构造函数
var foo = new Foo(); //实例化对象
console.log(Foo.prototype === foo.__proto__); // 这个应该没问题,值为:true
console.log(Function.prototype === Foo.__proto__); // true
console.log(Function.prototype === Function.__proto__); // true
console.log(Function.prototype === Object.__proto__); // true
/*
通过观察上面三条语句,我们发现,所有的(构造)函数(包括Function本身),
它们都有隐式原型,且它们的隐式原型都和Function的显式原型相等,指向的都是同一块空间。
这就意味着所有的(构造)函数,包括Function本身,都是Function这个构造函数的实例化对象。
*/
console.log('------------------------------------------------------------');
// 那么这块空间同样也是一个空的object对象,构造函数的显式原型===其实例化对象的隐式原型。
console.log(Object.prototype === Function.prototype.__proto__); // true
// 复习知识
// 构造函数的显式原型中有一个属性叫constructor,通过它可以找回构造函数本身
console.log(Function.prototype.constructor === Function); // true
小小实验
输入代码
console.log(Object.prototype);
我们看一下原型链的尽头有哪些内置的方法
那就意味着,原型链尽头中的方法,所有的对象都可以直接使用,并且当我们向其中新增方法时,所有的对象也可以共享这些方法。
// 实例化对象
var a = {};
// 使用内置的方法
console.log(a.toString.name); // "toString"
console.log(typeof a.toString.name); // string
// 给对象的原型链尽头添加test方法
Object.prototype.test = function() {
console.log("Hello World!");
}
a.test();
Object.test(); // Object构造函数本身也是对象,因为函数也是对象
Function.test(); // Function构造函数本身也是对象
var arr = [];
arr.test(); // 数组本身也是对象
之前总结过:所有的(构造)函数,包括Function本身,都是Function这个构造函数的实例化对象。还是应证了那句话:构造函数的显式原型===其实例化对象的隐式原型。那么说明所有的(构造)函数的次级原型链尽头就是
Function.prototype
(最终的原型链尽头还是Object.prototype
),所以当我们向Function的显式原型中添加方法时,所有的(构造)函数都能共享这个方法。
Function.prototype.test = function() {
console.log("How are you?");
}
Object.test(); // Object构造函数可以调用
Function.test(); // Function构造函数可以调用
function Fn() {}
Fn.test(); // 自定义(构造)函数可以调用
原型链补充说明
/*
1. 所有(构造)函数的显式原型指向的对象默认是空的Object实例化对象,除了Object构造函数不满足,
也就是说Object构造函数的显式原型指向的对象不是空的Object对象,因为里面有内置的方法,
且它也不是Object构造函数的实例化对象,因为它的__proto__值为null。
*/
function Fn() {}
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
// 2. 所有函数都是Function的实例化对象(包含Function本身)
console.log(Function.__proto__ === Function.prototype) // true
// 3. Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__) // null
原型链的属性问题
1.读取对象的属性值时: 会自动到原型链中查找。
2.设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值。
3.方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上。
小练习:
function Fn() {
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1)
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)
var p2 = new Person('Jack', 13)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__ === p2.__proto__) // true
instanceof是如何判断的?
- 表达式:
A instanceof B
- 判断 A 是不是 B 的实例化对象
- 如果【B函数的显式原型对象】在A对象的原型链上, 返回true, 否则返回false
案例一:
function Foo() {}
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true
案例二:
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
function Foo() {}
console.log(Object instanceof Foo) // false
原型链最终示意图(重要)!!!
面试题
试题一:
function A() {
}
A.prototype.n = 1
var b = new A()
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m)
试题二:
function F() {}
Object.prototype.a = function () {
console.log('a()')
}
Function.prototype.b = function () {
console.log('b()')
}
var f = new F()
f.a()
// f.b()
F.a()
F.b()