理解javascript的原型链

时间:2021-06-01 14:38:34

作为已经有了一年多经验的前端的我,时常还是不能够理解javascript原型链中对象的__proto__和prototype的指向问题,以及一些时候instanceof 检测问题。所以这次我准备深入的理解清楚javascript原型链的来龙去脉,并作出自己的总结。
首先画出我总结的一张原型链图,然后我们一步步印证图中每个指向代表的关系。
理解javascript的原型链
1.对象和函数
创建对象的方式有很多种,字面量,构造函数和Object.create(__proto__) (比较特殊,把新对象的__proto__ 指向传入对象)。创建函数的方式也有很多,函数声明,函数表达式,构造函数等。javascript中函数也是一种引用类型的数据类型,所以函数也是对象。
// 函数也是对象:对象分为函数对象和普通对象
// 对象和对象的创建
var o1 = {};
var o2 = new Object();
var o3 = new f1();
var o4 = Object.create({});
 
// 函数和函数的创建
function f1() {}
var f2 = new Function();
var f3 = function() {};
 
console.log("o1", typeof o1); //object
console.log("o2", typeof o2); //object
console.log("o3", typeof o3); //object
console.log("o4", typeof o4); //object
console.log("f1", typeof f1); //function
console.log("f2", typeof f2); //function
console.log("f3", typeof f3); //function
console.log("Function", typeof Function); //function
console.log("Object", typeof Object); //function

 

2.原型链上的指针
原型链是一种链表数据结构,其上的每一个节点都有相应的指针来指向对应的节点。
  • 普通对象根本上都是由构造函数创建,
  • 普通对象在创建的时候带有constructor 和 __proto__ 两个指针,函数对象在创建的时候带有constructor , __proto__和prototype三个指针。
  • 普通对象作为一个实例其constructor指针指向其构造函数,__proto__指向其构造函数的原型
  • 函数对象其constructor指向Function函数(所有函数的构造函数),__proto__指向一个匿名函数,prototype指向带有其构造函数属性的一个原型对象(原型对象里面有一个constructor指针指回向函数对象,即函数对象的原型的构造函数是其自己)
// 普通对象都是由构造函数创建,普通对象在创建的时候带有constructor 和 __proto__ 属性
// constructor 是一个指向其构造函数的指针
// __proto__ 是一个指向构造函数原型对象的指针
 
console.log("o1.constructor", o1.constructor); //ƒ Object() { [native code] }
console.log("o2.constructor", o2.constructor); //ƒ Object() { [native code] }
console.log("o3.constructor", o3.constructor); //ƒ f1() {}
 
console.log("o1.prototype", o1.prototype); //undefined
console.log("o2.prototype", o2.prototype); //undefined
console.log("o3.prototype", o3.prototype); //undefined
 
console.log("o1.__proto__", o1.__proto__ === Object.prototype); // true
console.log("o2.__proto__", o2.__proto__ === Object.prototype); //true
console.log("o3.__proto__", o3.__proto__ === f1.prototype); //true
 
// 构造函数在创建的时候也是带有constructor __proto__ 属性 ,并且比普通对象多一个 prototype 属性
// 构造函数的constructor指针都是指向 Function
// 构造函数的__proto__指针都是指向 一个匿名函数
 
console.log("f1.constructor", f1.constructor); //ƒ Function() { [native code] }
console.log("f2.constructor", f2.constructor); //ƒ Function() { [native code] }
console.log("f3.constructor", f3.constructor); //ƒ Function() { [native code] }
 
console.log("f1.prototype", f1.prototype ); //{constructor:f f1(),__proto__:Object}
console.log("f2.prototype", f2.prototype); //{constructor:f anonymous(),__proto__:Object}
console.log("f3.prototype", f3.prototype); //{constructor:f (),__proto__:Object}
 
console.log("f1.__proto__", f1.__proto__); // ƒ () { [native code] }
console.log("f2.__proto__", f2.__proto__); //ƒ () { [native code] }
console.log("f3.__proto__", f3.__proto__); //ƒ () { [native code] }
 
3.Function函数和Object函数以及匿名函数的关系
  • Function函数最为函数对象其constructor指针指向自己,__proto__指向匿名函数,prototype也指向匿名函数。
  • Object函数的constructor指向Function函数,__proto__指向匿名函数,prototype指向其自己的原型对象(Object.prototype)。
  • 匿名函数是一种特殊的对象,没有prototype属性,其__proto__指向Object.prototype(和普通对象一样)。
// 所以在Function的原型其实是一个匿名函数
console.log("Function.prototype", Function.prototype); //ƒ () { [native code] }
// 那么Function作为构造函数其 constructor 和 __proto__ 分别指向哪里呢?
console.log("Function.constructor", Function.constructor); // ƒ Function() { [native code] }
console.log("Function.__proto__", Function.__proto__); //ƒ () { [native code] }
// 可以看到 Function 的构造函数是自己 ,原型对象是匿名函数
 
// 那么这个匿名函数有没有 constructor __proto__ prototype 三个属性呢?又分别指向哪里呢
console.log("Function.__proto__.constructor", Function.__proto__.constructor); // ƒ Function() { [native code] }
console.log("Function.__proto__.prototype", Function.__proto__.prototype); // undefined
console.log("Function.__proto__.__proto__", Function.__proto__.__proto__===Object.prototype); // true
// Function.__proto__ 是一个匿名函数 ,其是一个特殊的对象,没有prototype属性,
// 和普通对象一样只有 constructor 和 __proto__ 指针
// 我们可以得出这个特殊的匿名函数的__proto__指向是对象的原型
 
// 这个对象的原型是否还存在指针,其指向是哪里呢?
console.log("Object.prototype.constructor",Object.prototype.constructor); // ƒ Object() { [native code] }
console.log("Object.prototype.__proto__",Object.prototype.__proto__); // null
console.log("Object.prototype.prototype",Object.prototype.prototype); // undefined

 

Object.create()方法对原型链的作用
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 
console.log("o4.__proto__", o4.__proto__); // {}
console.log("o4.prototype", o4.prototype); // undefined
console.log("o4.constructor", o4.constructor); // ƒ Object() { [native code] }
 
instanceof的理解
instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
返回objectA.__proto__===  ...objectX.__proto__... === objectB.prototype 
// objectA instaceof objectB : instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
// return objectA.__proto__ === objectB.prototype
console.log("o1 instanceof Object",o1 instanceof Object) // true
console.log("o1 instanceof Function",o1 instanceof Function) // false
console.log("f1 instanceof Object",o1 instanceof Object) // true
console.log("f1 instanceof Function",o1 instanceof Function) // false
console.log("Object instanceof Function",Object instanceof Function) // true
console.log("Function instanceof Object",Function instanceof Object) // true
需要注意的是:objectA.__proto__ === objectB.prototype 并不是永远成立,当objectB的prototype的指向改变的时候,则不成立。
// 需要注意的是:objectA.__proto__ === objectB.prototype并不是永远成立,当objectB的prototype的指向改变的时候,则不成立。
function C() {}
var c = new C();
var d = {};
console.log("c instanceof C", c instanceof C);
C.prototype = d;
console.log("c instanceof C", c instanceof C);
// console.log("c instanceof d",c instanceof d) // Uncaught TypeError: Right-hand side of 'instanceof' is not callable
var e = new C();
console.log("e.__proto__", e.__proto__); // {}
 
参考:
《javascript高级程序设计》