通过示例讲解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
最后附上原型链示意图: