在前端这块领域,原型与原型链是每个前端er必须掌握的概念。咱们屡次在面试或者一些技术博客里面看见这个概念。因而可知,这个玩意对于前端来讲有多重要。其实它自己理解起来不难,可是不少刚入行前端的同窗,看到prototype
、__proto__
理解起来仍是有点吃力,而后脑子里面就乱成一锅粥,就像我同样。可是这是很正常的事情,没什么大不了的,就像咱们想要学会跑步,那么咱们就必须先学会走路。任何事情都是有个过程的。因此如今就跟我一块儿来攻克这个难点吧。经过这篇文章你将掌握如下知识点:javascript
__proto_
;prototype
;javascript
中对象
的概念;javascript
中类
的概念;new
的实现;instanceof
的实现;javascript
的继承;javascript
这门语言的理解。这也是本篇文章的写做思路。前端
对象
那么咱们就从对象这一律念开始提及,其实对象这一律念相信你们并不陌生。有一种说法是“javasrcript中万物皆是对象”,其实这个说法是错误的,一个很简单的例子,javasript
中简单基本类型(string、boolean、number、null、undefined、symbol)自己就不是对象。其实javasript
中对象主要分为函数对象
和普通对象
。其中:java
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
这些都是函数对象,他们同时也被称为内置对象
。函数对象
自己其实就是一个纯函数,javascript
用他们来模拟类
。普通对象就很简单了,就是咱们常见的对象:面试
const obj = {
name: 'juefei',
desc: 'cool'
}
复制代码
可能说到这,你仍是没法理解到底啥是函数对象
,啥是普通对象
,那咱们就一块儿来看看下面的代码:函数
const obj1 = {};
const obj2 = new Object();
function func1() {
}
const obj3 = new func1();
const func2 = new function() {
}
const func3 = new Function()
复制代码
接着咱们来分别打印一下他们:学习
console.log(obj1); // object
console.log(obj2); // object
console.log(obj3); // object
console.log(func1); // function
console.log(func2); // function
console.log(func3); // function
复制代码
因此能够看见,obj1
、obj2
、,obj3
是普通对象,他们都是Object
的实例,而func1
、func2
、func3
则都是Function
的实例,称为函数对象
。咱们再看看:ui
console.log(typeof Object); //f unction
console.log(typeof Function); // function
复制代码
你是否是惊呆了,原来Object
和Function
都是 Function
的实例。 因此咱们得出一个结论就是:this
Function
的实例,那就是函数对象
,其他则为普通对象
。一样咱们也能够看出,不只 Object
是函数对象
,就连 Function
自己也是函数对象,由于咱们经过 console.log(typeof Function);
得知 Function
是 Function
的实例。是否是又开始有点绕了?没事,到这一步你就记住咱们刚刚的结论就算完成目标:spa
Function
的实例,那就是函数对象
,其他则为普通对象
。那么说到对象,咱们从上面能够看出,一个对象是经过构造函数 new 出来的,这其实跟原型
和原型链
有很大的关系,那么原型
和原型链
究竟是用来干吗的呢?prototype
原型
涉及到这两个概念,咱们就必须先来介绍两个东西: __proto__
和 prototype
,这两个变量能够说,在 javascript
这门语言里面随处可见,咱们无论他三七二十一,咱们先来看一张表:
对象类型 | __proto__ |
prototype |
---|---|---|
普通对象 | ✅ | ❌ |
函数对象 | ✅ | ✅ |
因此,请你先记住如下结论:
只有函数对象
有 prototype
属性,普通对象
没有这个属性。
函数对象
和 普通对象
都有 __proto__
这个属性。
prototype
和 __proto__
都是在建立一个函数或者对象会自动生成的属性。
接着咱们来验证一下:
function func (){ //func称为构造函数
}
console.log( typeof func.prototype); // object
console.log(typeof func.__proto__); // function
复制代码
const obj = {}
console.log(typeof obj.__proto__) //object
console.log(typeof obj.prototype) //undefined (看见了吧,普通对象真的没有 prototype 属性)
复制代码
因此就验证了咱们刚刚的结论:
函数对象
有 prototype
属性,普通对象
没有这个属性函数对象
和 普通对象
都有 __proto__
这个属性。prototype
和 __proto__
都是在建立一个函数或者对象会自动生成的属性。你看我又重复写了一遍,我不是为了凑字数,是为了你加深记忆,这对于咱们接下来的篇幅很重要。 接着咱们来看看下面的代码:
console.log(obj.__proto__ === Object.prototype); // true
console.log(func.__proto__ === Function.prototype); // true
复制代码
因此咱们又得出以下结论:
__proto__
属性主动指向构造的 prototype
;prototype
属性被 __proto__
属性 所指向。这就是prototype
属性和 __proto__
属性的区别与联系。 这可能又有点绕了,来多看几遍这一节,多背一下咱们的结论。咱们继续。
那么问题来了,既然func
是一个函数对象
,函数对象是有 prototype
属性的,那么func.prototype.__proto__
等于啥呢?
为了解决这个问题,咱们来思考一下:
首先,咱们看看func.prototype
是啥:
console.log(typeof func.prototype); //object
复制代码
好,咱们知道了,func.prototype
是一个对象,那既然是对象,那 func.prototype
那不就是 Object
的实例吗?那也就是说,func.prototype.__proto__
属性确定是指向 Object.prototype
咯! 好,咱们来验证一下:
console.log(func.prototype.__proto__ === Object.prototype); //true
复制代码
看见没有,就是这样的。那看到这里,咱们应该也知道当咱们这建立一个构造函数的时候,javascript是如何帮咱们自动生成__proto__
和prototype
属性的。哈哈没错就是这样:
//咱们手动建立func函数
function func() {}
//javascript悄悄咪咪执行如下代码:
func._proto = Function.prototype; //实例的 __proto__ 属性主动指向构造的 prototype
func.prototype = {
constructor: func,
__proto: Object.prototype //咱们刚刚才在上面验证的,你别又忘记了
}
复制代码
我还专门为你画了个图(够贴心不老铁):
因此prototype
又被称为显式原型对象,而__proto__
又被称为隐式原型对象。
hi,看到这里,你是否是有种脑子开了光的感受。哈哈,因此到如今你应该已经理解原型的概念了,若是你还不理解,那就把上述章节再看一遍。最好拿个纸笔出来跟着画一画,顺便拿出电脑把示例代码敲一敲。好,整理一下头脑,接下来咱们来看看什么又是原型链
。
原型链
再介绍这个概念以前,咱们先来看以下代码:
function Person = function(name,desc){
this.name = name;
this.desc = desc;
} //***1****//
Person.prototype.getName = function(){
return this.name;
}//***2****//
Person.prototype.getDesc = function(){
return this.desc;
}//***3****//
const obj = new Person('juefei','cool');//***4****//
console.log(obj);//***5****//
console.log(obj.getName);//***6****//
复制代码
接下来咱们来逐步解析一下:
Person
,此时,Person.portotype
自动建立,其中包含了 constructor
和 __proto__
两个属性;Person.prototype
新增了一个方法 getName
;Person.prototype
新增了一个方法 getDesc
;Person
新建一个实例: obj
(在建立实例的时候,构造函数会自动执行);obj
:{
name: 'juefei',
desc: 'cool'
}
复制代码
根据上面一节的结论,咱们得出:
obj.__proto__ = Person.prototype;
复制代码
obj
上面找不到 getName()
这个方法,因此它就会自动去经过自身的 __proto__
继续向上查找,结果找到了 Person.prototype
,接着它发现,恰好 Person.prototype
上面有getName()
方法,因而找到了这个方法,它就中止了寻找。 怎么样,是否是有一种环环相扣的感受?他们造成一个链了,没错,这就是原型链
。咱们得出以下结论:
在访问一个对象(假设这个对象叫obj)的属性/方法时,若在当前的对象上面找不到,则会尝试经过obj.__proto__
去寻找,而 obj.__proto__
又指向其构造函数(假设叫objCreated
)的 prototype
,因此它又自动去 objCreated.prototype
的属性/方法上面去找,结果仍是没找到,那么就访问 objCreated.prototype.__proto__
继续往上面寻找,直到找到,则中止对原型链对寻找,若最终仍是没能找到,则返回 undefined
。 一直沿着原型链寻找下去,直到找到 Object.prototype.__proto__
,指向 null
,因而返回 undefined
了。
是否是天然而然就理解了。我又给你画了个图(请对照着上面👆那个图看):
接下来咱们再来增长一些概念:
内置函数对象
自己的 __proto__
属性都指向 Function
的原型对象,即: Function.prototype
;Object.prototype.__proto__
指向 null
,全部的内置函数对象
的原型对象的 __proto__
属性 ( 内置函数对象.prototype.__proto__
),都指向Object
。咱们得出以下终极原型链的图:
针对这个图,我最终给出咱们常常看见那个原型链的图:
好好对比一下,拿出纸和笔画一画,根据上面章节的讲解,相信你很容易就能明白。
javascript
中的类
刚刚咱们终于明白什么是 原型
和 原型链
。下面咱们根据上面的概念来说解一下javascript
中的类
。 咱们知道,在面向对象的语言中,类能够被实例化
屡次,这个实例化
是指咱们能够根据构造函数去独立复制
多个独立的实例,这些实例之间是独立的。可是实际上在 javascript
却不是这样的,由于它不是这种复制机制
。咱们不能建立一个类的多个实例,咱们只能建立这个类的多个对象,由于他们都是经过原型
和原型链
关联到同一个对象。因此在 javascript
中 ,类
都是经过原型
和原型链
来实现的,它实际上是一种委托方式
。
new
的实现了解了上面javascript
中的类
的概念,那咱们应该很容易就理解new
的过程,其核心无非就是执行原型链的连接:
function myNew(Cons,...args){
let obj = {};
obj.__proto__ = Cons.prototype; //执行原型连接
let res = Cons.call(obj,args);
return typeof res === 'object' ? res : obj;
}
复制代码
instanceof
的实现那么学习了原型
和原型链
,instanceof
的实现确定也很简单了,它也是经过原型
和原型链
来实现的:
function myInstanceof(left,right){
let rightProto = right.prototype;
let leftValue = left.__proto__;
while(true){
if(leftValue === null){
return false;
}
if(leftValue === rightProto){
return true;
}
leftValue = leftValue.__proto__;
}
}
复制代码
我就不讲解过程了,由于我知道你确定能看懂,哈哈。
javascript
的继承咱们都知道继承也是经过原型
和原型链
来实现的,那我在这里介绍两种常见的继承方式:
//组合式继承
//经过call继承Parent的属性,并传入参数
//将Child的原型对象指向Parent的实例,从而继承Parent的函数
function Parent(value){
this.val = value;
}
Parent.prototype.getValue = function(){
console.log(this.val);
}
function Child(value){
Parent.call(this,value);//继承Parentd的属性
}
Child.prototype = new Parent();
复制代码
//寄生组合式继承
//经过call继承Parent的属性,并传入参数
//经过Object.create()继承Parent的函数
function Parent(value){
this.val = value;
}
Parent.prototype.getValue = function(){
console.log(this.val);
}
function Child(value){
//继承Parentd的属性
Parent.call(this,value);
}
Child.prototype = Object.create(Parent.prototype,{
constructor:{
value:Child,
writable:true,
configurable:true,
enumerable:false
}
})
复制代码
B.__proto__ = A.prototype
;B.a
,若在B中找不到a,则会在B.__proto__
中,也就是A.prototype
中查找,若A.prototype
中仍然没有,则会继续向上查找,最终,必定会找到Object.prototype
,假若还找不到,由于Object.prototype.__proto__
指向null
,所以会返回undefined
;Object.prototype.__proto__
——> null。因而可知,原型
和原型链
是如此的强大,但愿看完这篇文章,你再也不会对他们感到恐惧。 写完这篇已经近凌晨两点,若是你以为这篇文章对你有些许收获,请点赞支持!!
参考资料: << 你不知道的javascript 上卷 >>