前端面试基础-原型 & 原型链

184 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

得不得奖的无所谓希望能强迫自己闯一关╮( ̄▽ ̄)╭

前言

记录原型 & 原型链的学习总结
有误请多多指正,附上女神图保命 [手动狗头]
这里也涉及部分面向对象的知识可参考 # 前端面试基础-面向对象
学习已完成

  • 1.什么是原型 & 原型链以及一些基础概念
  • 2.原型模式
  • 3.理解原型对象
  • 4.原型关系判断
  • 5.原型属性操作
  • 6.原型对象的问题

1.什么是原型 & 原型链以及一些基础概念

概念可能比较抽象,具体可看 2-6点 实操了解

  1. JS中,函数本身也是一个包含了方法和属性的对象。

  2. 原型:prototype是每个函数中都有的一个属性,叫做原型,它是一个对象。

  3. 原型链:__proto__或者[[Prototype]]是每个对象都具有的一个属性,这个指向了它的构造函数的原型prototype,是它的引用(我们称这个属性为链接),而且每个prototype都有__proto__属性,因此本身也包含了指向其原型的链接,由此形成了一条链,我们称为原型链。

  4. Object是JS所有对象的父级对象,Object()为构造函数。

  5. Function是所有函数对象的构造对象,Function()为构造函数,所以ObjectFunction都有prototype属性。

  6. 我们可以通过constructor来查看对象的构造函数,isPrototypeOf来确定某个对象是不是我的原型,hasOwnProperty方法判断属性是否存在。

2.原型模式

新建一个function Person()函数,在浏览器console window,可以看到如下 image.png 拥有6个默认属性 arguments、caller、length、name、prototype、__proto__

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象(原型对象),而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。例如:

function Person() { } 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function () { console.log(this.name); }; 
var person1 = new Person(); 
person1.sayName(); //"Nicholas" 
var person2 = new Person(); 
person2.sayName(); //"Nicholas" 
console.log(person1.sayName == person2.sayName); //true

在上面代码中,我们将sayName方法直接添加到Personprototype属性中,构造函数为空,此时通过构造函数生成的person1person2两个实例对象,均可共享Person.prototype的属性与方法。 image.png

3.理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向该函数的原型对象。 在默认情况下,所有原型对象都会自动获得一个constructor(构造)属性,这个属性是一个指向prototype属性所在函数的指针。拿上面的代码示例来说,Person.prototype.constructor指向Person
Person.prototype.constructor === Person
上面我们通过Person.prototype可继续为原型对象添加方法与属性。

image.png 以上面使用Person构造函数创建实例对象的代码为例,上图展示了Personperson1person2及原型对象之间的关系。

image.png

image.png

image.png

image.png

  1. 创建了自定义的构造函数后,其原型对象(Person.prototype)默认只会取得constructor属性,至于其他的属性与方法,都是从Object继承而来或者之后通过Person.prototype.创建而来(如Person.prototype.name = 'Nicholas')的。
  2. 当调用构造函数创建一个新实例后(如var person1 = new Person()),该实例的内部将包含一个指针(内部属性[[Prototype]]),指向构造函数的原型对象。如下
person1.__proto__ === Person.prototype  // true
  1. 虽然在脚本中没有标准方式访问[[Prototype]],但每个对象上支持一个属性__proto__,可通过该属性访问原型对象。 __proto__中的constructor又指回构造函数Person

image.png

image.png

PS:__proto__这个连接存在于实例对象(如person2)与原型对象(如Person.prototype)之间,而不是存在于实例对象与构造函数之间。

4.原型关系判断

对于判断对象之间是否存在原型关系,有以下三种方式实现。

1、proto

console.log(person1.__proto__ == Person.prototype) //true 
console.log(person2.__proto__ == Person.prototype) //true

由于person1.__proto__person2.__proto__都指向原型对象,而Person.prototype也指向原型对象,所以返回值都为true

2、isPrototypeOf()

console.log(Person.prototype.isPrototypeOf(person1)); //true 
console.log(Person.prototype.isPrototypeOf(person2)); //true

A.isPrototypeOf(B),判断A是否是B的原型对象。对于person1.__proto__.isPrototypeOf(person1)的返回值,也是为true,因为person1.__proto__等于Person.prototype

3、getPrototypeOf()

console.log(Object.getPrototypeOf(person1) == Person.prototype); //true 
console.log(Object.getPrototypeOf(person1).name); //"Nicholas"

Object.getPrototypeOf(person1)返回person1的原型对象。

5.原型属性操作

1. 属性读取

每当代码读取某个对象的属性时,都会执行一次搜索,目标是给定名字的属性。
搜索首先从实例对象本身开始,如果对象本身存在该属性,则直接返回该属性的值
若没有找到,则继续搜索该对象的原型对象prototype,若有就返回属性值,若都没找到,则返回undefined

查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,层层的查找关系形成了原型链

image.png

image.png

原型对象最初只包含constructor属性,而该属性也是共享的,因此可以通过实例对象访问

console.log(person2.constructor) //function Person(){}

调用person2.constructor时返回function Person(){},证明了原型对象的constructor属性确实指向构造函数。

2. 属性修改

虽然可以通过实例访问保存在原型中的值,但却不能通过实例对象重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性会屏蔽原型(prototype)中的那个属性,如下所示。

person1.name = "Greg"; 
console.log(person1.name); //"Greg" ——来自实例 
console.log(person2.name); //"Nicholas" ——来自原型对象

当为对象添加一个属性时,这个属性就会屏蔽原型对象(prototyp)中保存的同名属性,虽然原型对象中的同名属性依旧存在;想要取消屏蔽,可以使用delete操作符完全删除实例对象中的属性,然后才能访问原型中的属性,如下所示。

person1.name = "Greg"; console.log(person1.name); //"Greg" ——来自实例 console.log(person2.name); //"Nicholas" ——来自原型对象 delete person1.name; console.log(person1.name); //"Nicholas" ——来自原型对象

3. 判断属性是否存在于对象而不是存在于原型对象prototype中 用 hasOwnProperty

使用hasOwnProperty()方法可以检测一个属性是存在于实例中(该方法也是从Object中继承而来),只有在给定属性存在于实例对象中时,才会返回true。

image.png

console.log(person1.hasOwnProperty("name")); //false 
person1.name = "Greg"; 
console.log(person1.name); //"Greg"——来自实例 
console.log(person1.hasOwnProperty("name")); //true 
console.log(person2.name); //"Nicholas"——来自原型 
console.log(person2.hasOwnProperty("name")); //false 
delete person1.name; alert(person1.name); //"Nicholas"——来自原型 console.log(person1.hasOwnProperty("name")); //false

6. 原型对象的问题

1. 省略了为构造函数传递初始化参数这一环节,导致所有实例默认情况下都将取得相同的属性值。

2. 原型中的属性被很多实例共享,这对于函数来说非常合适,对于那些包含基本值得属性来说也可以,但对于引用类型来说,问题就比较突出了。

function Person(){ } 
Person.prototype = { constructor: Person, friends : ["Shelby", "Court"], }; 
var person1 = new Person(); 
var person2 = new Person();
person1.friends.push("Van"); 
console.log(person1.friends); //"Shelby,Court,Van" 
console.log(person2.friends); //"Shelby,Court,Van" 
console.log(person1.friends === person2.friends); //true

person1对属性friends的任何操作,都会立马反应到person2上。因为prototype中的属性共享

最后

以上的方式总结只是自己学习总结,有其他方式欢迎各位大佬评论
渣渣一个,欢迎各路大神多多指正,不求赞,只求监督指正( ̄. ̄)
有关该文章经常被面试问到的可以帮忙留下言,小弟也能补充完善完善一起交流学习,感谢各位大佬(~ ̄▽ ̄)~