理解js原型及原型链

217 阅读5分钟

前言

JavaScript原型原型链,是每个前端cv工程师必须掌握的知识点,也是前端面试题出现的高频面试题之一。理解好JavaScript的原型原型链,对我们更深入学习JavaScript可以提供莫大的帮助。以下为一个前端菜狗经过深入了(百)(度)之后的记录,如有错误,望诸位大佬不领指教。

构造函数

在学习原型与原型链之前,我们先了解一下构造函数。

ECMAScript中的构造函数是用于创建特定类型对象的。自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。 --摘自《JavaScript高级程序设计》(第四版)

构造函数的特点

  1. 没有显式的创建对象;
  2. 属性和方法直接赋值给了this;
  3. 没有return

js内置构造函数

Object()Array()Function()Number()String()Boolean()Date()RegExp()Error()

自定义构造函数demo与构造函数的执行顺序

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        console.log(this.name)
    }
}
let person = new Person("Greg", 20, "Doctor")
person.sayName() // Greg

调用构造函数时,会执行如下操作:

  1. 在内存中创建一个新对象;
  2. 这个新对象内部的[[Prototype]]特性被复制为构造函数的prototype属性;
  3. 构造函数内部的this被赋值为这个新对象(即this指向 新对象);
  4. 执行构造函数内部的代码(给新对象添加属性);
  5. 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象。

构造函数与普通函数的区别

  1. 按照惯例,构造函数的首字符都是要大写的,非构造函数则以小写字母开头。这有助于区分构造函数与普通函数。
  2. 构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数,任何函数只要使用new操作符调用就是构造函数,不使用new操作符调用的函数就是普通函数。

原型

原型从何而来,原型对象中有什么?

  1. 只要创建一个函数,就会为这个函数创建一个prototype属性指向原型对象。默认情况下,所有原型对象的constructor的属性指回与之关联的构造函数。
  2. 自定义构造函数时,原型对象默认只会获得constructor属性,其他所有方法都继承自Object。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox、Safari和Chrome会在每个对象上暴露__proto__属性,通过这个属性可以访问原型。

构造函数、实例以及原型三者之间的关系

  1. 实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有,如下图所示: 实例与构造函数的关系.png 00.png
    • 构造函数Personprototype属性指向构造函数的原型对象;
    • 实例person的__proto__也是指向构造函数的原型对象;
    • 原型对象的constructor属性指回构造函数。

原型的好处

使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。因此,实例中公有的属性或方法可以定义在构造函数的原型对象中,可以减少冗余代码。

function Person(){}
Person.prototype.name = 'Nick'
Person.prototype.sayName = function(){
    console.log(this.name)
}
let person1 = new Person()
person1.sayName() // 'Nick'
let person2 = new Person()
person2.sayName() // 'Nick'
console.log(person1.sayName === person2.sayName) // true

上方代码定义了一个构造函数Person,在Person的原型上定义了一个name属性和一个sayName方法。之后通过Person生成两个实例为person1person2的空对象,并通过实例调用sayName方法,返回值均为Nick。同时,person1的sayNameperson2sayName严格相等,说明两个实例调用的是同一个方法,均为原型上的sayName

原型的层级

  • 在通过对象访问属性时,会按照该对象的名称开始搜索,搜索开始于对象实例本身。如果在实例上发现了该属性名称,则返回该属性对应的指,如果没有找到这个属性,则搜索会沿着指针进入原型对象,在原型上找到属性后,再返回对应的值。
  • 虽然可以通过实例读写原型对象上的值,但不可能通过实例重写这些值,如果在实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,即便这个属性的值为null,也会遮住原型对象上的属性。
  • 使用delete操作符可以删除实例上的属性,从而恢复该属性与原型属性的联系。

原型链

一张经典的原型链图

prototype.png

拓展

函数的__proto__Function.prototype的关系

所有函数(构造函数普通函数)的__proto__都指向Function.prototype,它是一个空函数。 将函数看做一个实例,实例的__proto__指针指向构造函数的原型对象。任何函数都是来自Function.prototype,甚至包括ObjectFunction自身。

Function.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype  // true
Array.__proto__ === Function.prototype   // true
String.__proto__ === Function.prototype  // true
Number.__proto__ === Function.prototype  // true
Boolean.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype  // true
Error.__proto__ === Function.prototype   // true
Date.__proto__ === Function.prototype    // true

Function函数.png

由上图可知,Function原型,又是实例。(有点绕,

Object.__proto__.__proto__指向什么

指向.png

由上可知:Object.__proto__.__proto__指向Object的原型对象。 ObjectFunction的实例,FunctionObject的实例,Object作为构造函数,它是Function构造函数创建的js内置构造函数之一,万物皆对象,js中所有的函数(构造函数与普通函数)又是来自Object的原型对象...

如果再也不能看见你,那就祝你,早安、午安、晚安。 ~~未完,待续!感谢您的阅读。