前端知识总结系列笔记三:原型与原型链

前言

原型与原型链是面试的常考点之一,所以颇有必要理解并掌握,本文尝试去弄清原型与原型链的关系,并经过图解的方式去帮助自身创建起原型与原型链的知识体系,使本身能在面试中能与面试官侃侃而谈,嘻嘻~面试

关于原型

JavaScript常被描述为一种基于原型的语言(prototype-based language)---每一个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。所以若是往原型对象上添加属性和方法,那么全部由该对象实例化的实例对象均可以共享该原型上的属性和方法。bash

// 代码块1
function SuperType() {}; // 构造函数
SuperType.prototype; // 原型对象

SuperType.prototype.sayHello = function() {
    console.log('Hello World!');
}

const s1 = new SuperType(); // 实例对象1
const s2 = new SuperType(); // 实例对象2

// 实例s1和实例s2均可以共享该原型上的方法sayHello()
s1.sayHello();
s2.sayHello();
复制代码

每一个构造函数都有一个指向原型对象的指针,经过.prototype属性访问,而原型对象都包含一个指向构造函数的指针,经过.constructor访问,以下图所示,其实就是一个循环引用。函数

// 接上述代码块1
SuperType.prototype.constructor === SuperType; // true
s1.__proto__ === SuperType.prototype; // true
复制代码

// 接上述代码块1
s1.constructor === SuperType; // true
复制代码

从上述代码咱们能够猜测:经过构造函数new出来的实例对象,是否也有一个指向实例化自身的构造函数的指针,可经过.constructor访问呢? 咱们尝试把实例对象s1打印出来看看!this

从打印结果咱们能够看出,实例对象s1自己并无constructor属性,而是经过原型向上查找__proto__,共享原型上的constructor属性,该属性最终指向SuperType。spa

所以构造函数、实例对象、原型对象三者的关系以下:prototype

关于原型链

咱们来回顾一下构造函数、原型和实例的关系:指针

每一个构造函数都有一个原型对象,而原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(proto)。code

而什么是原型链呢? 咱们知道,每一个对象都拥有一个原型对象,经过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也有可能拥有原型,这样一层一层,最终指向null。这就是原型链(prototype chain),经过原型链一个对象会拥有定义在其余对象中的属性和方法。cdn

接下来简单展现下原型链的运做机制:对象

function Person(name) {
    this.name = name;
}
const p = new Person('jiaxin');

p; // Person {name: 'jiaxin'}
p.__proto__ === Person.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
复制代码

图解原型链

上面只是简单地介绍了原型链的概念以及运做机制,而完整的原型链远远没有那么简单,来看一张图:

从上图能够看出,对象除了普通的对象(有__proto__属性,没有prototype属性)、原型对象(有constru属性指向构造函数,也有__proto__指向原型)外,还有函数对象,经过new Function()建立的都是函数对象,也有__proto__属性指向Function.prototype,Function.prototype也有原型,__proto__指向Object.prototype,最终指向null。

const f = new Function();
f; // 函数对象
f.__proto__ === Function.prototype; // true
f.__proto__.__proto__ === Object.prototype; // true
f.__proto__.__proto__.__proto__ === null; // true
复制代码

看下函数对象f的打印结果

所以能够获得另一条原型链,以下图所示:

原型上的属性和方法是定义在prototype对象上的,而非对象实例自己。当访问一个对象的属性/方法时,它不单单在该对象上查找,还会查找对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性/方法或到达原型链的末尾(null)。

function Person(name) {
    this.name = name;
}

function Girl(name) {
    Person.call(this, name);
}

const girl = new Girl('jiaxin');
girl.call(); // Uncaught TypeError: girl.call is not a function
复制代码

如上述代码所示,Person函数里面没有定义call()方法,为何Person能够访问call()方法呢?咱们把Person打印出来看一下,发现原来是在Person的原型上即Function.prototype上有call()方法能够访问。

而Girl函数里也没有定义call()方法,那么为何执行girl.call()却会报错呢?根据原型链的思想,咱们girl首先会看自身是否有call()方法,没有则会一直沿着原型链即__proto__向上查找,直到到达原型链的末端(null)还找不到,则会报错。咱们把girl打印出来看看,发现girl实例对象的原型链上的确没有call方法(实际上是由于girl是一个对象,因此没有函数原型上的call方法)。

总结

一、每一个构造函数都有一个原型对象,而原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(proto)。

二、每一个对象拥有一个原型对象,经过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为原型链。

三、当访问一个对象的属性 / 方法时,它不单单在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。

相关文章
相关标签/搜索