JS原型链图解教程

2,200 阅读9分钟

JS中原型链,说简单也简单。

首先明确:

函数(Function)才有prototype属性,对象(除Object)拥有__proto__

首先,我画了一张图。

所谓原型链,指的就是图中的proto这一条指针链!

DEMO:

var Person=function(){};
var p=new Person();
var t=new Person();

p.__proto__.say=function(){
   console.log(1);
}
p.say();  //1
t.say();  //1
p.__proto__={
  say:function(){
     console.log(2);
  }
}
p.say();   //2
t.say();   //1

原型链的顶层就是Object.prototype,而这个对象的是没有原型对象的。

可在chrome的控制台里面输入:

    Object.__proto__

输出是:

    function Empty() {}

原型链,如此而已。

对于初学者来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张所谓很经典的图,上面画了各种线条,一会连接这个一会连接那个,说实话我自己看得就非常头晕,更谈不上完全理解了。所以我自己也想尝试一下,看看能不能把原型中的重要知识点拆分出来,用最简单的图表形式说清楚。

我们知道原型是一个对象,其他对象可以通过它实现属性继承。但是尼玛除了prototype,又有一个__proto__是用来干嘛的?长那么像,让人怎么区分呢?它们都指向谁,那么混乱怎么记啊?原型链又是什么鬼?相信不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,下面用三张简单的图,配合一些示例代码来理解一下。

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			function JS(x, y) {
				this.x = x;
				this.y = y;
				this.fun = function() {
					console.log(this.x + "---" + this.y);
				}
			}

			function Node(x, y) {
				JS.call(this, x, y);  //继承
				this.fun = function() {
					console.log(this.x + "***" + this.y);
				}
			}

			var a = new JS("ES5", "ES6");
			var b = new Node("Express", "Koa2");
			
			console.log(Object.prototype==JS.prototype.__proto__);  //true
			
			console.log(a.constructor==JS); //true
			console.log(a.__proto__==JS.prototype); //true
			console.log(a.prototype==undefined); //true  只有函数才有prototype,对象没有
			
			console.log(JS.prototype.__proto__==Object.prototype);  //true
			console.log(JS.prototype.constructor==JS);  //true
			console.log(JS.prototype.__proto__.__proto__==null)  //true
			
			console.log(Object.prototype.__proto__==null); //true
		</script>
	</head>

	<body>
	</body>

</html>
var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}

var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}


/*1、字面量方式*/
var a = {};
console.log(a.__proto__);  //Object {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*2、构造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}

console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外情况)

var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null


图解构造器Function和Object的关系:

Function instanceof Object;//true
Object instanceof Function;//true

这个是怎么一回事呢?要从运算符instanceof说起。


我曾经简单理解instanceof只是检测一个对象是否是另个对象new出来的实例(例如var a = new Object(),a instanceof Object返回true),但实际instanceof的运算规则上比这个更复杂。

首先w3c上有官方解释(传送门,有兴趣的同学可以去看看),但是一如既往地让人无法一目了然地看懂……

知乎上有同学把这个解释翻译成人能读懂的语言(传送门),看起来似乎明白一些了:

//假设instanceof运算符左边是L,右边是R
L instanceof R 
//instanceof运算时,通过判断L的原型链上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype ?
//如果存在返回true 否则返回false

注意:instanceof运算时会递归查找L的原型链,即L.__proto__.__proto__.__proto__.__proto__...直到找到了或者找到顶层为止。

所以一句话理解instanceof的运算规则为:

instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型。

我们再配合代码来看一下就明白了:

//①构造器Function的构造器是它自身
Function.constructor=== Function;//true

//②构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
Object.constructor === Function;//true



//③构造器Function的__proto__是一个特殊的匿名函数function() {}
console.log(Function.__proto__);//function() {}

//④这个特殊的匿名函数的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true

//⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函数
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true

当构造器Object和Function遇到instanceof

我们回过头来看第一部分那个“奇怪的现象”,从上面那个图中我们可以看到:

Function.__proto__.__proto__ === Object.prototype;//true
Object.__proto__ === Function.prototype;//true

所以再看回第一点中我们说的instanceof的运算规则,Function instanceof Object 和 Object instanceof Function运算的结果当然都是true啦!

如果看完以上,你还觉得上面的关系看晕了的话,只需要记住下面两个最重要的关系,其他关系就可以推导出来了:

1、所有的构造器的constructor都指向Function

2、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的__proto__指向Object.prototype


完整DEMO:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			function JS(x, y) {
				this.x = x;
				this.y = y;
				this.fun = function() {
					console.log(this.x + "---" + this.y);
				}
			}

			function Node(x, y) {
				JS.call(this, x, y); //继承
				this.fun = function() {
					console.log(this.x + "***" + this.y);
				}
			}

			var a = new JS("ES5", "ES6");
			var b = new Node("Express", "Koa2");

			console.log(Object.prototype == JS.prototype.__proto__); //true

			console.log(a.constructor == JS); //true
			console.log(a.__proto__ == JS.prototype); //true
			console.log(a.prototype == undefined); //true  只有函数才有prototype,对象没有

			console.log(JS.prototype.__proto__ == Object.prototype); //true
			console.log(JS.prototype.constructor == JS); //true
			console.log(JS.prototype.__proto__.__proto__ == null) //true

			console.log(Object.prototype.__proto__ == null); //true

			console.log("----------------------------------------");

			function Fun() {}
			//我创造了一个函数Fn
			//这个函数由Function生成(Function作为构造函数)
			var fn = new Fun()
				//我创建了一个函数fn
				//这个函数由Fn生成(Fn作为构造函数)

			console.log(fn.__proto__ === Fun.prototype) //true
				//fn的__proto__指向其构造函数Fun的prototype
			console.log(Fun.__proto__ === Function.prototype) //true
				//Fun的__proto__指向其构造函数Function的prototype
			console.log(Function.__proto__ === Function.prototype) //true
				//Function的__proto__指向其构造函数Function的prototype
				//构造函数自身是一个函数,他是被自身构造的
			console.log(Function.prototype.__proto__ === Object.prototype) //true
				//Function.prototype的__proto__指向其构造函数Object的prototype
				//Function.prototype是一个对象,同样是一个方法,方法是函数,所以它必须有自己的构造函数也就是Object
			console.log(Fun.prototype.__proto__ === Object.prototype) //true
				//与上条相同
				//此处可以知道一点,所有构造函数的的prototype方法的__都指向__Object.prototype(除了....Object.prototype自身)
			console.log(Object.__proto__ === Function.prototype) //true
				//Object作为一个构造函数(是一个函数对象!!函数对象!!),所以他的__proto__指向Function.prototype
			console.log(Object.prototype.__proto__ === null) //true
				//Object.prototype作为一切的源头,他的__proto__是null

			//下面是一个新的,额外的例子

			var obj = {}
				//创建了一个obj
			console.log(obj.__proto__ === Object.prototype) //true
				//obj作为一个直接以字面量创建的对象,所以obj__proto__直接指向了Object.prototype,而不需要经过Function了!!

			//下面是根据原型链延伸的内容
			//还有一个上文并未提到的constructor,  constructor在原型链中,是作为对象prototypr的一个属性存在的,它指向构造函数(由于主要讲原型链,这个就没在意、);

			console.log(obj.__proto__.__proto__ === null) //true
			console.log(obj.__proto__.constructor === Object) //true
			console.log(obj.__proto__.constructor.__proto__ === Function.prototype) //true
			console.log(obj.__proto__.constructor.__proto__.__proto__ === Object.prototype) //true    
			console.log(obj.__proto__.constructor.__proto__.__proto__.__proto__ === null) //true
			console.log(obj.__proto__.constructor.__proto__.__proto__.constructor.__proto__ === Function.prototype) //true

			//以上,有兴趣的可以一一验证  F12搞起.
		</script>
	</head>

	<body>
	</body>

</html>



js中的hasOwnProperty()和isPrototypeOf()

这两个属性都是Object.prototype所提供:Object.prototype.hasOwnProperty()Object.prototype.isPropertyOf()


先讲解hasOwnProperty()方法和使用。在讲解isPropertyOf()方法和使用

看懂这些至少要懂原型链

一、Object.prototype.hasOwnProperty()

概述

hasOwnProperty()方法用来判断某个对象是否含有指定的自身属性

语法

obj.hasOwnProperty("属性名");//实例obj是否包含有圆括号中的属性,是则返回true,否则是false

描述

所有继承了Object.prototype的对象都会从原型链上继承到hasOwnProperty方法,这个方法检测一个对象是否包含一个特定的属性,
in不同,这个方法会忽略那些从

原型链
上继承的属性。

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			function A() {
				this.a = "张三";
				this.b = "李四";
			}

			function B() {
				A.call(this);
				this.c = "王五";
				this.d = "赵六";
			}
			var a1=new A();
			var b1=new B();
			 
			
			console.log(b1.hasOwnProperty("a")); //true
			console.log(b1.hasOwnProperty("b")); //true
			console.log(b1.hasOwnProperty("c")); //true
			console.log(b1.hasOwnProperty("d")); //true
		</script>
	</head>

	<body>
	</body>

</html> 

实例1.使用hasOwnProperty()方法判断某对象是否含有特定的自身属性

下面的例子检测了对象 o 是否含有自身属性 prop:

var o =new Object();
o.prop="exists";

function change(){
  o.newprop=o.prop;
  delete o.prop;
}

o.hasOwnProperty("prop")//true
change()//删除o的prop属性
o.hasOwnProperty("prop")//false
//删除后在使用hasOwnProperty()来判断是否存在,返回已不存在了

2.自身属性和继承属性的区别

下面的列子演示了hasOwnProperty()方法对待自身属性和继承属性的区别。

var o =new Object();
o.prop="exists";
o.hasOwnProperty("prop");//true 自身的属性
o.hasOwnProperty("toString");//false 继承自Object原型上的方法
o.hasOwnProperty("hasOwnProperty");//false 继承自Object原型上的方法

3.修改原型链后hasOwnProperty()的指向例子

下面的列子演示了hasOwnProperty()方法对待修改原型链后继承属性的区别

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var o = {
				name: 'jim'
			};

			function Person() {
				this.age = 19;
			}
			Person.prototype = o; //修改Person的原型指向
			var p = new Person();
			console.log(p.hasOwnProperty("name")); //false 无法判断继承的name属性
			console.log(p.hasOwnProperty("age")); //true;
		</script>
	</head>

	<body>
	</body>

</html>

4.使用hasOwnProperty()遍历一个对象自身的属性

下面的列子演示了如何在遍历一个对象忽略掉继承属性,而得到自身属性。

注意· forin 会遍历出对象继承中的可枚举属性

var o={
  gender:'男'
}
function Person(){
  this.name="张三";
  this.age=19;
}
Person.prototype=o;
var p =new Person();
for(var k in p){
  if(p.hasOwnProperty(k)){
    console.log("自身属性:"+k);// name ,age
  }else{
    console.log("继承别处的属性:"+k);// gender
  }
}

5.hasOwnProperty方法有可能会被覆盖

如果一个对象上拥有自己的hasOwnProperty()方法,则原型链上的hasOwnProperty()的方法会被覆盖掉

var o={
  gender:'男',
  hasOwnProperty:function(){
    return false;
  }
}

o.hasOwnProperty("gender");//不关写什么都会返回false
//解决方式,利用call方法
({}).hasOwnProperty.call(o,'gender');//true
Object.prototype.hasOwnProperty.call(o,'gender');//true
<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<script type="text/javascript">
			var o = {
				o1: {
					o2: {
						name: "SB JS"
					}
				}
			};
			console.log(o.hasOwnProperty("o1"));  //true
			console.log(o.hasOwnProperty("o2"));   //false
			console.log(o.o1.hasOwnProperty("o2"));  //true
			console.log(o.o1.hasOwnProperty("name"));  //false
			console.log(o.o1.o2.hasOwnProperty("name"));  //true
		</script>
	</head>

	<body>
	</body>

</html>

二、Object.prototype.isPrototypeOf()

概述

isPrototypeOf()方法测试一个对象是否存在另一个对象的原型链上

语法

//object1是不是Object2的原型,也就是说Object2是Object1的原型,,是则返回true,否则false
object1.isPrototypeOf(Object2);

描述

isPrototypeOf()方法允许你检查一个对像是否存在另一个对象的原型链上

实例1.利用isPrototypeOf()检查一个对象是否存在另一个对象的原型上

var o={};
function Person(){};
var p1 =new Person();//继承自原来的原型,但是现在已经无法访问
Person.prototype=o;
var p2 =new Person();//继承自o
console.log(o.isPrototypeOf(p1));//false o是不是p1的原型
console.log(o.isPrototypeof(p2));//true  o是不是p2的原型

2.利用isPropertyOf()检查一个对象是否存在一另一个对象的原型链

var o={};
function Person(){};
var p1 =new Person();//继承自原来的原型,但是现在已经无法访问
Person.prototype=o;
var p2 =new Person();//继承自o
console.log(o.isPrototypeOf(p1));//false o是不是p1的原型
console.log(o.isPrototypeof(p2));//true  o是不是p2的原型

console.log(Object.prototype.isPrototypeOf(p1));//true
console.log(Object.prototype.isPrototypeOf(p2));//true

p1的原型链结构是p1=>原来的Person.prototype=>Object.prototype=>null

p2的原型链结构是p2=> o =>Object.prototype=>null

p1p2都拥有Object.prototype所以他们都在Object.Prototype的原型链上


三、总结

  1. hasOwnProperty:是用来判断一个对象是否有你给出名称的属性或对象。不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
  2. isPrototypeOf是用来判断要检查其原型链的对象是否存在于指定对象实例中,是则返回true,否则返回false。