前言
JavaScript原型与原型链,是每个前端cv工程师必须掌握的知识点,也是前端面试题出现的高频面试题之一。理解好JavaScript的原型与原型链,对我们更深入学习JavaScript可以提供莫大的帮助。以下为一个前端菜狗经过深入了(百)解(度)之后的记录,如有错误,望诸位大佬不领指教。
构造函数
在学习原型与原型链之前,我们先了解一下构造函数。
ECMAScript中的构造函数是用于创建特定类型对象的。自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。 --
摘自《JavaScript高级程序设计》(第四版)
构造函数的特点
- 没有显式的创建对象;
- 属性和方法直接赋值给了this;
- 没有
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
调用构造函数时,会执行如下操作:
- 在内存中创建一个新对象;
- 这个新对象内部的
[[Prototype]]特性被复制为构造函数的prototype属性; - 构造函数内部的
this被赋值为这个新对象(即this指向 新对象); - 执行构造函数内部的代码(给新对象添加属性);
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象。
构造函数与普通函数的区别
- 按照惯例,构造函数的首字符都是要
大写的,非构造函数则以小写字母开头。这有助于区分构造函数与普通函数。- 构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数,任何函数只要使用
new操作符调用就是构造函数,不使用new操作符调用的函数就是普通函数。
原型
原型从何而来,原型对象中有什么?
- 只要创建一个函数,就会为这个函数创建一个
prototype属性指向原型对象。默认情况下,所有原型对象的constructor的属性指回与之关联的构造函数。 - 自定义构造函数时,原型对象默认只会获得
constructor属性,其他所有方法都继承自Object。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox、Safari和Chrome会在每个对象上暴露__proto__属性,通过这个属性可以访问原型。
构造函数、实例以及原型三者之间的关系
实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有,如下图所示:- 构造函数
Person的prototype属性指向构造函数的原型对象; 实例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生成两个实例为person1与person2的空对象,并通过实例调用sayName方法,返回值均为Nick。同时,person1的sayName和person2的sayName严格相等,说明两个实例调用的是同一个方法,均为原型上的sayName。
原型的层级
- 在通过对象访问属性时,会按照该对象的名称开始搜索,搜索开始于对象实例本身。如果在实例上发现了该属性名称,则返回该属性对应的指,如果没有找到这个属性,则搜索会沿着指针进入原型对象,在原型上找到属性后,再返回对应的值。
- 虽然可以通过实例读写原型对象上的值,但不可能通过实例重写这些值,如果在实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,即便这个属性的值为
null,也会遮住原型对象上的属性。 - 使用
delete操作符可以删除实例上的属性,从而恢复该属性与原型属性的联系。
原型链
一张经典的原型链图
拓展
函数的__proto__与Function.prototype的关系
所有函数(
构造函数与普通函数)的__proto__都指向Function.prototype,它是一个空函数。 将函数看做一个实例,实例的__proto__指针指向构造函数的原型对象。任何函数都是来自Function.prototype,甚至包括Object及Function自身。
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原型,又是实例。(有点绕,囧)
Object.__proto__.__proto__指向什么
由上可知:
Object.__proto__.__proto__指向Object的原型对象。Object是Function的实例,Function是Object的实例,Object作为构造函数,它是Function构造函数创建的js内置构造函数之一,万物皆对象,js中所有的函数(构造函数与普通函数)又是来自Object的原型对象...
如果再也不能看见你,那就祝你,早安、午安、晚安。~~未完,待续!感谢您的阅读。