【JS】原型和原型链:代码案例+问题引导式说明

JS的原型与原型链

以代码案例+问题引导的方式讲解原型与原型链

一、从实例+构造函数中发现原型链

构造函数constructor能够通过实例化(new),生成实例。

构造函数constructor自身有一个属性prototype,用于存储一些公用的属性和方法,来提供给自己生成的实例使用。

		//自定义一个构造函数Pokemon
		function Pokemon(name,attr,skill){
			this.name = name;
			this.attr = attr;
			this.skill = skill;
		}
		//往prototype上添加属性
		Pokemon.prototype.fight = function(){
			let {name,skill} = this;
			return `${name},使用${skill}!`
		}
		let Pikachu = new Pokemon('皮卡丘','电','十万伏特');
		//创建的实例可以使用fight这个方法。
		console.log(Pikachu.fight());// >>> 皮卡丘,使用十万伏特!
		console.log(Pikachu.toString());// >>> [object Object]

为什么实例可以使用自己构造函数prototype的方法?

因为每当定义一个带继承的实例(对象),这个对象都会存在一个__proto__(隐式原型),指向的是这个对象的构造函数的prototype(显式原型)。
在这里插入图片描述

console.log('两者相等:',Pikachu.__proto__ === Pokemon.prototype);//true

如果访问了对象自己没有的属性或者方法,就会自动通过__proto__属性进行查找,从上面的例子来看,调用Pikachu.fight()时,就访问到了它构造函数上定义的fight()函数。

那为什么构造函数prototype上没有toString()方法也能调用呢?

因为构造函数身上的prototype,这只是一个普通的对象,既然是对象,就可以把它看做一个实例,自然它也会有自己的构造函数。

JS构造函数一般都用大写字母开头做区分:

  • Object :对象的构造函数
  • Function:函数的构造函数
  • Array:数组的构造函数

  • 所以prototype,把它看做普通对象,它的构造函数自然就是Object 。而它的隐式原型:prototype.__proto__,就会指向构造函数的原型Object.prototype

所以执行Pikachu.toString()的顺序是:

  1. 自己身上没有toString,访问Pikachu.__proto__
  2. 到达构造函数原型Pokemon.prototype,发现也没有toString,继续访问Pokemon.prototype.__proto__
  3. 到达构造函数原型Object.prototype,找到了toString,执行。

那么会不会一直__proto__访问下去?

并不会,最终会指向对象源头Object构造函数的prototype,而这个最终的prototype的__proto__是null。

	// 到达Pokemon的原型,访问fight
	console.log(Pikachu.__proto__);
	// 到达prototype原型(Object.prototype),访问到toString
	console.log(Pikachu.__proto__.__proto__);
	// 到达原型链尽头(Object.prototype.__proto__) null
	console.log(Pikachu.__proto__.__proto__.__proto__);
	// 直接到达尽头:null
	console.log(Object.prototype.__proto__);

原型链定义: 对象没有的属性或者方法,会通过__proto__属性指向原型对象的prototype一层一层往上找,直到Object的原型对象位置,层层继承的链式结构就叫原型链,而null就是原型链的尽头。

二、实例、构造函数、原型三者关系

上面对于原型链的访问过程中,貌似没有构造函数constructor的身影

那么它在实例和原型之间的关系是怎么样的呢?

  • prototype:是构造函数所独有的,本质是一个普通的对象,存在的目的是为实例提供各种方法与属性。
  • __proto__:是实例对象所独有的,指向自身构造函数的原型对象prototype,本质是普通的对象,存在的目的是完成原型链的访问。
  • constructor:原型对象prototype上都有constructor属性,指向的是这个prototype关联的构造函数,存在的目的是为了让prototype能访问回构造函数

三者关系

三、原生原型链关系

我们平时创建一个数组array,创建一个对象object,创建一个函数function、set、map。这些实例都能使用它们原型对象上的方法。
比如函数的call/bind/apply,数组的filter/reduce/slice等方法。
原生的JS就已经定义好了他们的构造函数和原型对象,我们在此基础上去自定义构造函数,或者类,都是在对原型链的延长。

原生JS已经构造好的原型链关系又是怎么样的?

其实从上面就不难看出,所谓的实例,构造函数,原型都是相对的概念

  • Pikachu作为实例,有它的构造函数和原型
  • 但它自己的原型Pokemon.prototype作为实例,也有构造函数和原型

这里比较绕脑子的是,只要是对象和函数,都可以作为原型,找到自己的构造函数和原型。

  • 构造函数本身也是函数,可以看做实例
  • 原型本身就是对象,也可以看做实例

原型链图解

这里需要补充一下说明:

f-native(底层函数),是构造函数Function的原型,也就是Function.prototype访问到的目标,它本身是一个匿名函数,这里用底层函数来称呼它,关于它的特殊性,在文章末尾会说明。

console.log(Function.prototype);//输出的是ƒ () { [native code] }
console.dir(Function.prototype);//输出的是ƒ anonymous()

如果把不同的目标看做实例,可以有以下的对应关系,可以在编译器上输出验证以下的结果。

实例构造函数隐式原型(__proto__)
PikachuPokemonPokemon.prototype
PokemonFunctionf-native
ObjectFunctionf-native
f-nativeFunctionObject.prototype
FunctionFunctionf-native
Object.prototypeObjectnull

四、底层函数f native

上文说到,一般来说,Object的prototype,或者一个构造函数的prototype。其本身是向构造函数提供方法和属性的,所以prototype都是非函数,是一个普通的对象,拥有__proto__,却没有prototype。
但在图解中出现了一个特殊的角色,那就是Function的原型f native,它特殊的地方在于,它并不是一个对象,而是一个函数。

从上面的图解可以看出:

  • 构造函数Function的prototype还是一个函数
  • 构造函数Function的constructor总是指向自己本身,并且可以无限调用constructor

以下为验证过程:

console.warn('Function构造函数原型的特殊性');
console.log('Pokemon的原型是对象:',typeof(Pokemon.prototype));
console.log('Object的原型是对象:',typeof(Object.prototype));
console.log('Function的原型是底层函数,依旧是函数:',typeof(Function.prototype));

console.warn('Function的constructor是自己,并且可以无限调用:');
console.log('Function.constructor:',Function.constructor.constructor);

console.warn('Function的prototype与__proto__都指向底层函数f native:',);
console.log(Function.prototype === Function.__proto__);

构造函数原型特殊性

  • 原型链的顶端就是null,这是为了防止原型链的查找没有尽头,这是js的设计思想。
  • 函数访问的顶端是就Function,即使调用了constructor还是会指向自己,防止了函数的调用会有null的出现,这是也是js的设计思想。

第一篇文章,如果有错误请指出,让我快速纠正…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值