JavaScript中的原型和原型链问题,始终是困扰老手乃至于宽广前端工程师的重要问题,确实,绝对于一般语法来说,它会更加难以了解,在日常开发过程中也不常见。然而……它的重要性是显而易见的。上面咱们就来探索一下。

进入主题

1.三个重要属性

要了解原型问题,先要理解以下三个属性。

  • prototype
  • __protp__
  • constructor

进入逐个解说阶段:

1.1 prototype

JavaScript中的每个构造函数都有prototype对象。所有实例对象须要共享的属性和办法都放在这个对象里。而那些不须要共享的,就放在构造函数中。应用过程中,不须要咱们手动申明一个prototype属性。有了它,咱们就能够将所有须要共享的办法提取到一处,防止冗余。通过一个小栗子来看下

// 定义一个动物的构造函数。function Animal(name) {  // 构造函数中寄存的是不须要共享的办法和属性  this.name = name; // 定义每个动物的名字}// 将每个动物须要共享的办法和属性放到一块Animal.prototype.eat = function () {  console.log('吃货色')}// 输入本人的名字Animal.prototype.sayName = function () {  console.log(this.name)}let dog = new Animal('狗'); // 实例化一个狗的对象let cat = new Animal('猫'); // 实例化一个猫的对象dog.sayName(); // 狗cat.sayName(); // 猫dog.eat();cat.eat();

这里,咱们通过Animal实例化的dogcat类,都没有申明sayName函数,然而它们都有这个函数能够执行。而实例对象一旦创立,将主动援用prototype对象的属性和办法。

置信你曾经get到了这个知识点,别急,持续看上面这种状况。

// 再次定义Animal。function Animal() {}Animal.prototype.eat = function () {  console.log('吃货色')}let dog = new Animal();dog.eat = function () {  console.log('我只吃骨头!')}dog.eat(); // 我只吃骨头

留神:

上述例子阐明:只有在dogcat上找不到eat办法的时候才会向Animal查找,如果能查找到,则不会执行Animal的办法。这种状况也叫做办法的重写

下面的例子置信你曾经看的很明确了,不明确也问题不大,上面的解说咱们还会通过理论的问题来介绍。接下来看第二个重要的属性。

看的累了就劳动会儿吧,上面的内容更精彩…………

1.2 __proto__

很多人容易将 prototype__proto__(双下划线) 混同,因为它们之间的指向有点儿绕。不过,一通则百通????,等你真正了解了,就会发现,其实挺简略的。

在很多状况下,__proto__能够了解为 结构器的原型,这是为什么呢?接着看小栗子吧。

function Animal () {}let dog = new Animal();// 重点来了…………console.log(dog.__proto__ === Animal.prototype) // true

能够看到,dog.__proto__Animal.prototype是齐全相等的。好了,根本应用看完了,咱们来探索下__proto__的指向问题吧。

1.2.1 __proto__的指向问题

可能大家不太了解,为什么__proto__还有指向。这里须要做个阐明,通过不同形式创立进去的对象,它的__proto__指向是不同的。

1.字面量形式创建对象

这个大家比拟容易了解,日常工作中咱们也是应用的比拟多。

let dog = {}console.log(dog.__proto__) // Object {} --> Object的原型// 验证是否相等console.log(dog.__proto__ === Object.prototype) // true

这里因为间接给dog赋值为了全局的对象,所以__proto__指向了Object.prototype????。

2.结构器形式创立

话不多说,间接上栗子。

function Animal() {}let dog = new Animal()console.log(dog.__proto__) // A {} --> A的原型// 验证console.log(dog.__proto__ === Animal.prototype) // true

这是因为dogAnimal的一个实例,所以dog__proto__指向了Animal.prototype

3.Object.create 形式创立
let Animal = {  c: 1 // 为了辨别,咱们这里加一个自定义属性。}let dog = Object.create(Animal)console.log(dog.__proto__) // Animal {c: 1}//验证console.log(dog.__proto__ === Animal) // true

留神:

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。也就是说,这里间接将Animal作为了dog__proto__

1.3 constructor

这个属性不用多说,指的就是构造函数,每个对象都会有一个constructor属性来指向本人的构造函数

function Animal() {} // 持续应用这个应用了很屡次的Animal构造函数console.log(Animal === Animal.prototype.constructor) // true

这里的Animal.prototype.constructor 就指向了 Animal 函数。

重点来了????????????????????????????

2.__proto__、prototype和constructor的关系

关系太简单,只能通过图解来阐明了。

<center><img src="./1.jpeg"></center>

3.原型链

晓得了三者的概念,理解了它们的用处,接下来想弄懂原型链就会变得简略了。原型链就是无数个上图的串联。咱们还是通过图解的形式来看。

<center><img src="./5.jpeg"></center>

图太长了,不过千万别被图吓到哟。原型链就是从实例对象开始,通过 __proto__来向上查找,直到找到null为止。

附加项:

看到这里,大家可能会有一个疑难。每个构造函数都有 prototype对象, 那么这个对象到底指向到了哪儿?

其实,每个prototype对象的最终指向都是Object.prototype。为什么这么说,是因为每个构造函数在创立的时候会调配一块儿空间来存储须要共享的办法和属性,所以 prototype 是通过对象创立来的,那么它的最终指向必定也是Object.prototype。老规矩,咱们来通过栗子来验证一下。

function dog(){}function cat(){}function monkey(){}console.log(dog.prototype.__proto__ === Object.prototype) // trueconsole.log(cat.prototype.__proto__ === Object.prototype) // trueconsole.log(monkey.prototype.__proto__ === Object.prototype) // true

4.面试题了解

4.1 请查看下列程序,会输入什么
function F() {}Object.prototype.a = function () {}Function.prototype.b = function () {}var f = new F()F.a()F.b()f.a()f.b()

解析:

F.a F.b f.a 执行没问题,因为即便在构造函数总找不到这两个函数,然而通过原型链查找到。

  • F.a 函数的查找程序是 F构造函数自身 --> Function.prototype --> Function.prototype.__proto__
  • F.b 函数的查找程序是 F构造函数自身 --> Function.prototype
  • f.a 函数的查找程序是 f自身 --> f._proto__(Function.prototype) --> f.__proto__._proto__(Object.prototype)

f.b 则会呈现执行出错的状况

起因是这样的:

查找程序和f.a雷同,然而,在f.__proto__._proto__,也就是Object.prototype上查找不到 b 函数,最终导致查找失败,呈现谬误。

聪慧的你必定学会了,接下来挑战一下吧!!!

function User() {}User.prototype.hello = function () {}let u1 = new User()let u2 = new User()console.log(u1.hello === u2.hello)console.log(User.prototype.constructor)console.log(User.prototype === Function.prototype)console.log(User.__proto__ === Function.prototype)console.log(User.prototype === Function.__proto__)console.log(u1.__proto__ === u2.__proto__)console.log(u1.__proto__ === User.__proto__)console.log(Function.__proto__ === Object.__proto__)console.log(Function.prototype.__proto__ === Object.prototype.__proto__)console.log(Function.prototype.__proto__ === Object.prototype)