对一个知识点是否彻底把握,最好的校验方法就是可否用本身的语言将其表述出来。
原型与原型链一直是学习 JS 绕不过的知识点,其中proto 与 prototype 最为让人头疼,这里简单的写下我本身的理解,从原型与原型链中拆解 proto 与 prototype ,但愿能对你们有所帮助。javascript
1,定义java
在javascript中,函数能够有属性。 每一个函数都有一个特殊的属性叫做原型(prototype)
注:prototype属性是函数特有,对象没有该属性
原型(prototype) 是什么东西呢,里面又有哪些属性呢?来,让咱们拿个具体例子看下:面试
function fn() {}; console.dir(fn)
这里咱们能够看出 原型(prototype) 是一个对象,对于当前例子来讲,里面有 constructor 与 proto两个属性。那这个原型对象有什么做用呢?来,让咱们继续往下看~ 函数
2,使用学习
如今咱们知道了原型是一个对象,那原型对象有什么做用呢?实际上,原型是 ECMAScript 实现继承的过程当中产生的一个概念。这里咱们简单拿 ES5 的对象来举例子:this
function Person(name) { this.name = name; } Person.prototype.say = function() { console.log(`My name is ${this.name}`); } let person = new Person('小明'); person.say(); // My name is 小明
让咱们来逐步解释下这个例子:spa
咱们发现,person能够调用其构造函数原型里的say方法,why?让咱们看下 person 里都有什么:prototype
实例 person 虽然自身没有 say 方法,可是经过 proto 属性访问到了其原型中的 say 方法。code
为何 proto 属性会指向其构造函数的原型 prototype 呢,他们之间是什么关系呢?让咱们继续往下看~对象
1,__proto__
在介绍原型链前,让咱们先看一个属性:proto,这是一个与原型 prototype 很类似的属性,此次让咱们来完全搞懂他们之间的关系。
让咱们先来看MDN上的定义:
Object.prototype 的 proto 属性是一个访问器属性(一个getter函数和一个setter函数), 暴露了经过它访问的对象的内部[[Prototype]] (一个对象或 null)。
注:函数也是一种对象,因此其同时具备 prototype 与 __proto__ 两个属性
看不懂定义不要紧,让咱们举例说明:
let obj = {a: 1}; console.log(obj);
咱们能够看出 proto 指向了一个对象,这个对象是什么呢?来,让咱们继续看
class Parent { constructor(name) { this.name = name; } print() { console.log(this.name); } } let parent = new Parent('小明'); console.dir(parent); console.dir(Parent);
咦,有没有发现 parent.__proto__ 与 Parent.prototype所指向的对象很类似,它们知否是同一个对象呢?
parent.__proto__ == Parent.prototype // true
结果是同一个引用,这个时候咱们能够得出一个结论:实例的__proto__属性指向其构造函数的原型。那他们之间的这种关联关系有什么做用呢?这便涉及到了原型链。
2,原型链
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不单单在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
注:由于对象没有prototyp属性,因此经过__proto__属性与原型prototype进行关联
文字描述很抽象,让咱们经过ES5的继承来具体分析:
function Parent(name) { this.name = name; } Parent.prototype.print = function() { console.log(this.name); } function Child(name) { Parent.call(this, name); } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; let child = new Child('小明'); console.log(child);
让咱们来看下 child 里都有什么
这里可能有的同窗对Object.create()这个函数不太熟悉,不明白其作了什么,不要急,让咱们来看下其源码实现:
function create(proto) { function F(); F.prototype = proto; return new F(); }
声明了一个构造函数F,而后将其原型指向参数proto,返回构造函数的实例。好,让咱们将整个过程串起来看一下:
1,声明 Parent 构造函数 2,声明 Child 构造函数,并手动绑定 this 指向 3,执行 Object.create(Parent.prototype):声明一个构造函数 F,更改其原型prototype指向(F.prototype = Parent.prototype),而后返回 F 的实例 f,注意这一步,其实是 f.__proto__ == F.prototype 4,将 Child 的 prototype 指向 f.prototype 5,绑定 Child 的构造函数
让咱们再来看下 child.print() 的调用过程
1,child对象里没有 print 函数,因而便在其原型上寻找:child.__proto__ ——> f.prototype 2,进入 f.prototype 中寻找 print 函数,发现没有,因而去其原型上寻找:f.__proto__ ——> F.prototype 3,F.prototype == Parent.prototype,因而便进入 Parent.prototype 中寻找 print 函数,有 print 函数,调用成功
怎么样,是否是豁然开朗!原型链其实就是经过 proto 与 prototype 的关联关系链接起来的,这样对象即可以寻找其原型上的方法与属性。详细的关系描述以下图:
实战
让咱们来看一道面试题:
var F = function() {}; Object.prototype.a = function() { console.log('a'); }; Function.prototype.b = function() { console.log('b'); } var f = new F(); f.a(); f.b(); F.a(); F.b();
解题思路以下:
1. f.a() ——> 实例 f 调用 a 方法,自身没有,从其原型中查找:f.__proto__ == F.prototype 2. F.prototype中没有 a 方法,因而继续在其原型中查找,F.prototype.__proto__ == Object.prototype 3. Object.prototype中有a方法,无b方法,因此f.a()结果为a,f.b()调用会报错:f.b is not a function 4. F.a():构造函数调用 a 方法,自身没有,从其原型中查找:F.__proto__ == Funtion.prototype 5. Funtion.prototype中有 b 方法,因此F.b()的输出为b。没有 a 方法,继续在其原型中查找,Function.prototype.__proto__ == Object.prototype 6. Object.prototype中有 a 方法,因此F.a()的输出为a
本文对__proto__与prototype之间的关系进行了简单的梳理,写下了笔者本身的理解,给你们理解提供一个思路,固然可能存在描述不许确或错误的地方,欢迎你们留言交流,以上~