目录
一、基本概念
在系统的学习原型与原型链之前,先学习一些基本的知识作为铺垫。
原型:
js中的对象是基于原型的。它定义并实现了一个新创建对象所必须包含的属性列表(因 为它是它所创建的对象的原型)。
原型链:
每个实例化对象都有自己的原型或构造函数,这些原型和构造函数也可能会有自己 的原型或是构造函数,以此类推,这些原型组成一种类似链式的结构,这条链就是原型链。
二、原型_proto_和prototype之间的解读
想要将原型知识串起来,必须搞明白的东西。
在js中,我们经常会经常使用和操作对象,对象与原型和原型链密不可分,所以原型与原型链是必须掌握的内容。
首先,先来了解,原型都有什么。
_proto_:对象的原型对象
Prototype:函数对象的子对象,prototype表示该函数的原型。
最常用的构建对象的方法就是直接创建对象和使用构造函数创建对象,所以,相应的,函数也会加入原型之中,由于函数的加入,导致定义清晰的原型变得容易混淆,这也是原型与原型链的难点之一,突破这个难点,原型与原型链的学习将变得十分轻松,那么该如何突破呢?
不妨直接使用js为我们提供的内容进行解析。
创建一个对象和构造函数。
let a={};
function b(){
console.log("我是构造函数实例化出的对象");
};
然后查看他们的原型。
console.dir(a);
console.dir(b);
在浏览器中查看。
此时,可以明确的看到对象只含有_proto_一个原型,而函数b()不仅含有_proto_原型,还含有一个prototype。
在js的定义中,函数也是对象,所以,函数也会包含_proto_这个原型对象,而函数本身包含prototype原型,所以函数原型有两个,为_proto_和prototype。
那么,通过函数(构造函数)实例化的对象呢?
从概念上理解,原型定义并实现了一个新创建对象所必须包含的属性列表,构造函数实例化的对象包含了构造函数的属性列表,拥有此构造函数的属性列表。
同样的,函数原型prototype包含了此函数的属性列表,所以将此函数当作构造函数实例化对象时,实例化出来的对象的原型就是函数的prototype。
来更直观的感受一下,在上面的例子的基础上,使用b()来实例化一个对象。
let c=new b();
console.dir(c);
看下结果。
可以清晰的看到在b的原型_proto_中,显示的是consructor:f b()
。
Constructor是prototype中含有的一个指针,在函数实例化的对象里,这个指针指向的是实例这个对象的构造函数,那不就是b()的prototype?
再来查看函数b()的函数原型prototype都有什么。
明显的看到b()的prototype里的consructor:f b()
。
足够清晰了吧,此实例化对象的原型就是b()的prototype,即b.prototype。
敲黑板的知识点:由函数实例化的对象,此对象的原型是其构造函数的prototype。(这个知识点彻底会了,原型和原型链,也就没什么难懂难理解的了)
原型也是对象,所以无论是什么原型,都会带默认的原型,即_proto_:object
,从例子中也可以看出。
那就应该发问了,为什么非得是object????
就像为什么桌子叫桌子这种问题,只能说,是规定,再委婉一点,就是Object包含了js的对象应该拥有的基本属性列表,自带的,没毛病。
虽然问题没营养,但牵扯到一个小知识点。
那就是:Object对象的原型,是null。
由于我见识短浅,这是为什么我也不知道,有知道的大佬可以在评论区留言~~~~
插入一个小知识点:不是所有的对象都有原型,完全数据字典对象就是这样的,它只有数据,没有原型对象。
-
原型的基本语法使用
动手实践也很重要,看看例子中的语法和其它衍生知识点吧!都是重点!!!!
- 减少内存开销:
先来任意写一个构造函数,给它添加一些方法,并实例化一个对象。
function a(name){
this.name=name;
this.red=function(){
console.log(this.name+"是红色的");
}
this.pink=function(){
console.log(this.name+"是粉红色的");
}
}
let b=new a("花");
b.red();
b.pink();
这是很普通的对象创建以及方法调用,当实例化对象调用方法的时候,就需要给方法开辟一块新的内存空间,多个实例化对象及其方法的调用,就会开辟占用很多新的内存空间,从性能上看,这样做十分不可靠。
但是,在这种问题上,使用原型,就可以有效的解决内存开销的问题,并且让你的代码变得简洁优雅。
原型简化对象的创建过程,从性能来看,每当我们要使用实例对象调用方法的时候,系统都要开辟内存,那么,当实例化对象变多的时候,且它们都需要调用一次同样的方法,那么,内存的开销就会变多,由于原型的特性,即原型的属性是共享的,那么,将这些方法放在构造函数的原型中,这些方法就变成共享的了,就可以减少内存的开销。
使用原型知识,将代码修改一下。
function a(name){
this.name=name;
}
a.prototype.red=function(){//将方法添加到a.prototype
console.log(this.name+"是红色的");
}
a.prototype.pink=function(){
console.log(this.name+"是粉红色的");
}
let b=new a("花");
b.red();
b.pink();
给a.prototype中加入red()和pink()方法的时候,这两个方法就是a()原型的属性了。
打印a,看看它的原型吧!
a的prototype里,包含了这两个方法了。在实例化对象的时候,这些属性就会共享给每一个使用a()实例化的对象,由于是共享的,所以只开辟并占用了一次内存空间,性能变高不少!
可能你会说,我这样一个一个添加属性可太麻烦了,有没有办法,让我像在构造函数里添加方法一样,一次都写完?
答案是有!当然可以。写好所有的方法,然后全部添加到原型上,你可能已经想到把所有需要添加的方法集合成一个对象,然后把这个对象,加在构造函数的prototype上。
确实是这样操作的,将上面的例子继续整合,看看结果。
function a(name){
this.name=name;
}
a.prototype={//把需要整合的方法放入类中
red(){
console.log(this.name+"是红色的");
},
pink(){
console.log(this.name+"是粉红色的");
}
}
let b=new a("花");
b.red();
b.pink();
把需要整合的方法已经全部整理到类里了,是不是很方便呢,但是直接这样做,其实是没办法实现我们理想中的功能的。
原因:原型也是对象,原型中的prototype也是对象,直接使用创建对象的方式向a.prototype中放置属性,注意,是放置,不是添加,所以,a.prototype里所有的属性都将被置换,只留下当前我们添加进去的属性。
通过对prototype之前的解读,不难发现prototype属性中,一定会包含一个Constructor属性,看看官方的解释:
构造函数(Constructor)在对象创建或者实例化时候被调用的方法。通常使用该方法来初始化数据成员和所需资源。构造器,Constructor在js不能被继承,因此不能重写Overriding,但可以被重载Overloading。
总结一下:它说明了当前prototype中的构造函数是谁,在本例中,就是函数a()。
如果没有这个constructor属性,就等于构造函数没有了,就无法实例化对象了,那怎么调用red()和pink()?
所以,将Constructor人为的添加上,在这个例子中,构造函数是a(),所以就得添加constructor:a。
最终的代码就是:
function a(name){
this.name=name;
}
a.prototype={
constructor: a,
red(){
console.log(this.name+"是红色的");
},
pink(){
console.log(this.name+"是粉红色的");
}
}
let b=new a("花");
b.red();
b.pink();
不要觉得这么多篇幅讲两个小例子没有用,在实际使用中,第二个例子的内容十分重要。
它不仅简化了原型方法添加的操作,还有其他妙用,具体都有什么,后期会接着连载。。。。。
后续内容可能会在这里更新,可能会开新的文章,如有意见或建议,或是文章问题,欢迎大家评论留言~~~