目 录CONTENT

文章目录

JavaScript 原型 与 原型链

俊阳IT知识库
2023-01-10 / 0 评论 / 0 点赞 / 401 阅读 / 2,551 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-01-10,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
广告

文章已同步至掘金:https://juejin.cn/post/6844903901091594253
欢迎访问😃,有任何问题都可留言评论哦~

原型

什么是JS原型?

如果对JS原型解释的话,会涉及到两个概念:构造函数,原型对象

  • 构造函数

通俗的说,就是你在script标签里面声明的那个函数

<script>


function Test(){
	// 我就是构造函数
}


</script>
  • 原型对象

在声明了一个函数之后,浏览器会自动按照一定的规则创建一个对象,这个对象就叫做原型对象。这个原型对象其实是储存在了内存当中

在声明了一个函数后,这个构造函数(声明了的函数)中会有一个属性prototype,这个属性指向的就是这个构造函数(声明了的函数)对应的原型对象;原型对象中有一个属性constructor,这个属性指向的是这个构造函数(声明了的函数)

如下图:

js-prototype-6

使用构造函数创建对象

我们的构造函数使用new来创建对象的时候,就像下面这样:

<script>

 	function students(){
 		// 我就是构造函数
 	}

 	let stu = new students();

</script>

此时,stu就是那个构造函数students创建出来的对象,这个stu对象中是没有prototype属性的,prototype属性只有在构造函数students中有,看图!

js-prototype-7

js-prototype-8

可以看出,构造函数students中有prototype属性,指向的是students对应的原型对象;而stu是构造函数students创建出来的对象,他不存在prototype属性,所以在调用prototype的时候的结构是undefined,但stu有一个__proto__属性,stu调用这个属性可以直接访问到构造函数students的原型对象(也就是说,stu的__proto__属性指向的是构造函数的原型对象),看图:

js-prototype-9

说明:

  • 从上面的代码中可以看到,创建stu对象虽然使用的是students构造函数,但是对象创建出来之后,这个stu对象其实已经与students构造函数没有任何关系了,stu对象的__proto__属性指向的是students构造函数的原型对象
  • 如果使用new students()创建多个对象stu1、stu2、stu3,则多个对象都会同时指向students构造函数的原型对象
  • 我们可以手动给这个原型对象添加属性和方法,那么stu1,stu2,stu3…这些对象就会共享这些在原型中添加的属性和方法
  • 如果我们访问stu中的一个属性name,如果在stu对象中找到,则直接返回。如果stu对象中没有找到,则直接去stu对象的__proto__属性指向的原型对象中查找,如果查找到则返回。(如果原型中也没有找到,则继续向上找原型的原型—原型链)
  • 如果通过stu对象添加了一个属性name,则stu对象来说就屏蔽了原型中的属性name。 换句话说:在stu中就没有办法访问到原型的属性name了
  • 通过stu对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值。 stu.name = “李四”; 并不是修改了原型中的值,而是在stu对象中给添加了一个属性name

开始撸代码:

<script type="text/javascript">
	function students () {        
	}
	// 可以使用students.prototype 直接访问到原型对象
	//给students函数的原型对象中添加一个属性 name并且值是 "张三"
	students.prototype.name = "张三";
	students.prototype.age = 20;

	var stu = new students();
	/*
		访问stu对象的属性name,虽然在stu对象中我们并没有明确的添加属性name,但是
		stu的__proto__属性指向的原型中有name属性,所以这个地方可以访问到属性name
		就值。
		注意:这个时候不能通过stu对象删除name属性,因为只能删除在stu中删除的对象。
	*/
	alert(stu.name);  // 张三

	var stu1 = new students();
	alert(stu1.name);  // 张三  都是从原型中找到的,所以一样。

	alert(stu.name === stu1.name);  // true

	// 由于不能修改原型中的值,则这种方法就直接在stu中添加了一个新的属性name,然后在stu中无法再访问到
	//原型中的属性。
	stu.name = "李四";
	alert(stu.name); //李四
	// 由于stu1中没有name属性,则对stu1来说仍然是访问的原型中的属性。    
	alert(stu1.name);  // 张三  
</script>

与原型有关的几个方法:

  • prototype属性

prototype 存在于构造函数中 (其实任意函数中都有,只是不是构造函数的时候prototype我们不关注而已) ,他指向了这个构造函数的原型对象

  • constructor属性

constructor属性存在于原型对象中,他指向了构造函数

如下代码:

<script type="text/javascript">
    function students () {
    }
    alert(students.prototype.constructor === students); // true
</script>
  • _ _ proto_ _ 属性(注意:左右各是2个下划线)

用构造方法创建一个新的对象之后,这个对象中默认会有一个属性__proto__, 这个属性就指向了构造方法的原型对象


原型链

说完原型后,趁热打铁,说一下原型链

在js中,万物皆对象,对象可以说是重中之重了。每一个对象都拥有自己的属性。但是在这个世界中有很多东西都是相似的,可以归为一类,他们有共同的方法和属性。不可能让每一个对象都定义一个属性吧。那样太消耗内存了。所以,在js中怎么才能让多个对象共享一个或多个方法呢?原型的出现就是为了解决这个问题。

  • prototype和__proto__的区别

一切皆对象,函数是特殊的对象。
所有的引用类型(函数、数组和对象)都拥有__proto__属性(隐式原型)
所有函数拥有prototype属性(显示原型)
原型对象:拥有prototype属性的对象,在定义函数时就被创建

  • 构造函数

大多数情况下,__proto__可以理解为“构造器的原型”(__proto__ === constructor.prototype)

	function Word(words){
		this.words = words
	}
	Word.prototype = {
			alert(){
				alert(this.words)
			}
	}

	var w = new Word("Hello Word");
	w.alert();
	console.log(w.__proto__ === Word.prototype) // true

实例w的隐式原型指向它构造函数的显示原型。(指向即恒等于)

w.__proto__ === Word.prototype // true

当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身没有该属性或方法,则会去它的__proto__属性中调用查找,也就是构造方法的prototype属性中查找。实例通过这样的方法继承构造函数的方法和属性。

实例通过__proto__属性指向构造方法的prototype的属性实现方法和属性的继承。


  • 原型链

由于__proto__属性是任何对象都有的属性,在js中一切皆对象,所以会形成一条__proto__连接起来的链条,递归访问__proto__必须最终到头,并且值为null。

js-prototype-1

	Function.prototype.a = 'a';
	Object.prototype.b = 'b';
	function Person(){}
	console.log(Person);
	let p = new Person();
	console.log(p); // Person {} 对象
	console.log(p.a); // null
	console.log(p.b);  // b

说明: p是Person()的实例,是一个Person对象,它拥有一个属性值__proto__,并且__proto__是一个对象,包含两个属性值constructor和__proto__,

js-prototype-2

会发现p.__proto__===Person.prototype,  
p.__proto__.constructor ===Person // true,即p.__proto__.constructor指向了构造函数本身。

Person.prototype的__proto__属性,指向了Object 的prototype属性。即p.b的打印结果为b。则实例w通过__proto__属性继承了Object 的属性b。通过__proto__属性一直向上查找,一直到null为止。

那么构造函数Person的__proto__指向了Function.prototype。

js-prototype-3

  1. 查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
  2. p.__proto__.constructor == function Person(){}
  3. p.__proto__proto__== Object.prototype
  4. p.__proto____proto__.__proto__== Object.prototype.__proto__== null
  5. 通过__proto__形成原型链而非protrotype

js-prototype-4

事实上,js里完全依靠"原型链"模式来实现继承。

再简单说一下__proto__、prototype、constructor

  • _ _proto _ _:事实上就是原型链指针
  • prototype:这个是指向原型对象的
  • constructor:每一个原型对象都包含一个指向构造函数的指针,就是constructor

继承的实现方式:

为了实现继承,_ _proto _ _会指向上一层的原型对象,而上一层的结构依然类似,那么就利用proto一直指向Object的原型对象上!Object.prototype. _ _ proto _ _ = null;表示到达最顶端。如此形成了原型链继承。

下面有个图非常经典:

js-prototype-5

大家可以自己动手画一下,加深自己的理解

大致总结一下:

1.Object是作为众多new出来的实例的基类 function Object()

2.Function是作为众多function出来的函数的基类 function Function()

3.构造函数的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype

4.原型对象的__proto__都指向Object.prototype

5.Object.prototype._ _ proto _ _指向null


^_<

0

评论区