通过示例讲解JS原型链

如果还不是很清楚JS原型链的小伙伴,可以把下面的代码运行一下,细细品味~

1.基本概念

 1 // 1.构造函数
 2 function Dog(name){
 3   this.name = name
 4   this.action = function(msg){
 5     console.info(msg)
 6   }
 7 }
 8 
 9 // 2.使用构造函数Dog创建的实例对象
10 var xiaohua = new Dog('小花')
11 // 实例对象xiaohua可以使用构造函数中的属性、方法
12 console.info(xiaohua.name) // 小花
13 var xiaohei = new Dog('小黑')
14 xiaohei.action('摇尾巴') // 摇尾巴
15 
16 // 3.直接使用构造函数中的方法,每个实例对象中方法都会复制一份,造成内存浪费
17 // 如下两个实例对象相同的方法不相等
18 console.info(xiaohua.action === xiaohei.action) // false
19 
20 // 4.使用原型
21 // 每一个构造函数均有一个prototype属性,该属性是一个对象,其所有方法和属性都被构造函数所拥有,
22 // 因此,可以把不变的属性和方法定义在prototype对象上,所有的实例都可以共享这些属性和方法
23 
24 // 给构造函数Dog的原型添加属性和方法
25 Dog.prototype.favourite = '骨头'
26 Dog.prototype.barking = function (){
27   console.info('汪汪汪...')
28 }
29 // 实例中可以直接使用
30 console.info(xiaohua.favourite) // 骨头
31 // 而且原型中的方法是共享的
32 console.info(xiaohua.barking === xiaohei.barking) // true
33 
34 // 5.构造函数的原型对象和实例的关系
35 // 每个实例对象都有一个__proto__属性(原型),指向构造函数的原型对象prototype,所以实例对象可以使用构造函数原型中的方法
36 console.info(xiaohua.__proto__ === Dog.prototype) // true
37 
38 // 实例对象可以使用方法:1.自身拥有的方法,2.顺着__proto__找到的构造函数prototype的方法
39 
40 
41 // 6.原型对象prototype和构造函数的关系
42 // 原型对象prototype有constructor属性,该属性为构造函数本身
43 console.info(Dog.prototype.constructor === Dog) // true
44 // 有因为实例对象的__proto__等于构造函数的prototype,所以每个实例的__proto__.constructor即是构造函数
45 console.info(xiaohua.__proto__.constructor === Dog) // true
46 console.info(xiaohei.__proto__.constructor === Dog) // true
47 
48 // 通过实例的__proto__.constructor属性可以明确是由哪个构造函数创建出来的
49 
50 //7.特殊情况需要手动把constructor属性指回来
51 // 如重新给prototype赋值,会造成constructor丢失
52 Dog.prototype = {
53   barking: function (){
54     console.info('汪汪汪...')
55   }
56 }
57 // 因为重新给Dog.prototype赋值了一个对象
58 console.info(Dog.prototype.constructor === Dog) // false
59 
60 // 手动把constructor属性指回来
61 Dog.prototype = {
62   constructor: Dog,
63   barking: function (){
64     console.info('汪汪汪...')
65   }
66 }
67 console.info(Dog.prototype.constructor === Dog) // true
68 
69 // 8.原型对象(prototype)的原型(__proto__)
70 // 原型对象也是对象,因此也因该有原型,即Dog.prototype.__proto__
71 // 而这个原型对象的原型(Dog.prototype.__proto__)是Object构造函数的原型对象
72 console.info(Dog.prototype.__proto__ === Object.prototype) // true
73 // 也即是这个原型对象的原型的constructor属性(Dog.prototype.__proto__.constructor)为Object
74 console.info(Dog.prototype.__proto__.constructor === Object) // true
75 //这个原型对象的原型 的原型是null
76 console.info(Dog.prototype.__proto__.__proto__)  // null
77 
78 // 以上即是原型链

2.绕不开的Function和Object

 1 // 注:所有的输出均为true
 2 function foo() {}
 3 // 函数有两种身份:函数,对象:
 4 // 1.foo是一个函数,具有prototype属性,称为foo.prototype,是一个对象,
 5 // 所以foo.prototype.__proto__指向Object.prototype
 6 // 而Object.prototype就是最顶级root_prototype,一切对象的原型
 7 console.info(foo.prototype.__proto__ === Object.prototype);
 8 // 2.一切函数都是由Function构造出来的对象,
 9 console.info(foo.__proto__ === Function.prototype); // Function创建了foo对象
10 console.info(Object.__proto__ === Function.prototype); // Function创建了Object对象
11 console.info(Function.__proto__ === Function.prototype); // Function创建了Function对象,js奇怪现象之一!!!
12 // 3.因Object.prototype就是最顶级root_prototype(类似于万物起源),所以一切对象的原型最终都会指向Object.prototype
13 // 哪怕是自己创建自己,且创建了Object的Function,其原型链的顶端也是Object.prototype,
14 // 也许是因为名字起的好,语言的创建者就把root_prototype给了Object.prototype
15 let obj = new Object();
16 console.info(obj.__proto__ === Object.prototype);
17 let fo = new foo();
18 console.info(fo.__proto__.__proto__ === Object.prototype);
19 console.info(Function.__proto__.__proto__ === Object.prototype); // js奇怪现象之二!!!
20 // 谁拿到了最顶级的原型对象,谁就会站在原型链顶端,哈哈
21 foo.prototype = Object.prototype;
22 console.info(Function.__proto__.__proto__ === foo.prototype);
23 console.info(Object instanceof Function); // 这个是事实
24 console.info(Function instanceof Object); // 这个就是因为Object的prototype是root_prototype

Object和Function的先后问题可以通过下面的故事了解:

创世传说:

1.起初天地混沌,创世神天父(JS引擎)根据规则(ECMAScript 5.1),创建了一个元对象(没有__proto__,做为一切对象的起源),它还没有名字,姑且称之为root_prototype。

2.天父(JS引擎)接下来在root_prototype的基础上创建了Function.prototype,它是一个函数,但是调用后总是返回undefined,它没有prototype属性;引擎接着基于Function.prototype创造了第一个函数Function,它的prototype、__proto__均指向Function.prototype(世界刚刚开始总是有些不一样的地方)

3.天父(JS引擎)把创建世界的工作交给Function就去休息了,Function很能干,创建了Object、Number、String、Date、Boolean、Array、RegExp、Error原生函数。它们的__proto__都指向Function.prototype(血缘关系),它们都有各自的prototype,里面放着自己的独门技能,而这个prototype最终是基于root_prototype的。

4.这时世界的框架已基本厘定,原生函数各司其职,Array生成数组,Object创建所有对象,Function专职于函数(有了那么多干活的原生函数,终于轻松点了)。然而此时天父(JS引擎)宣告:Object乃吾子!Object的真实身份揭开后其prototype指向root_prototype,即Object.prototype就是root_prototype,所以一切对象都继承于Object。当然由于Object拥有最高贵的血统,所以一代元老Function也因其Function.prototype的__proto__实际指向Object.prototype,而有了Function.__proto__ 指向 Function.prototype,Function.prototype.__proto__指向Object.__proto__,所以逻辑上可以说Function instanceof Object。

参考:https://juejin.cn/post/6844903937418461198

最后附上原型链示意图:

 

 

posted on 2020-03-22 20:12  橘生淮南_  阅读(334)  评论(0编辑  收藏  举报