JS 从原型与继承理解对象

来源:互联网 发布:手机可以做淘宝客吗 编辑:程序博客网 时间:2024/06/05 18:17

在JS中,对象和原型的概念常常容易让初学者头疼,对于它的理解,我们可以找一本书仔细看一看,但是书里面的内容多偏于晦涩和抽象(尤其对于初学者),难以贯穿起来。因此,最好的方法,就是学一点,然后反复练习,反复出错,反复改错,不断否定自己,再重新构建,这个过程中,不但会加深对于知识的理解,更重要的是可以培养出自己的一种思维方式。我们不去提倡一定要跟别人的想法不同,重要的是一种独立思考的能力。
现在我试图在原型的角度去理解对象的概念。那么进入正题:


1,如何理解构造函数


(1)构造函数与普通函数的区别
我们来举一个普通的例子:

//定义一个叫Person的构造函数function Person () {}//实例化一个叫p的对象,无参可省略括号var p = new Person ;console.log(Person) ;//打印出来是一个叫做Person的构造函数console.log(p) ;//打印出来是一个对象 而p是这个对象的引用

写到这里有人就会疑惑为什么Person是一个函数,怎么p就是一个对象了呢?那么我们有必要简单说一下构造函数的运行过程。事实上,Person自始至终就是一个函数,它没有创建出对象,是它前面这个new关键字创建了一个对象(new这个关键字就有这个功能,这是系统写好了的,我们无法改动,就像var是声明的意思一样),而Person一旦前面加上这个new 它就会默认返回这个对象(这里当然还涉及到this指针,不详述),这样才有这个p的对象。那么也就是说,如果前面不加new,Person函数也不会有默认返回值,也就跟普通的函数没有任何区别,换句话说,任何函数前面加上这个new,它就变成构造函数了,默认返回new创建的那个对象(这跟函数名大小写没有关系,一般构造函数首字母大写只是人为的为了好区分)。那么总结为一句话就是:本来没区别,只有前面加上new才有区别。
(2)自定义构造函数和内置构造函数
首先我们先分析一下Object,大家知道它叫做“对象”,但实际上它是一个函数,确切的说是个函数名。我们可以做下面的验证:

console.log(Object) ;//打印出来直接是一个叫做Object的构造函数

也就是说Object是构造函数fucntion Object ( ) { } 的函数名或者说引用, 同样,我们打印String Number Date 等等(Math除外,最后补充),出来都是一个对应名字的构造函数,
那么,我们回想一下我们自己定义的构造函数的过程,然后打印出来:

//定义一个叫Person的构造函数function Person () {}//实例化一个叫p的对象,无参可省略括号var p = new Person ;console.log(Person) ;//打印出来就是一个叫做Person的构造函数

那么,我们自己定义的名字叫Person构造函数和名字叫Object String······的构造函数有什么区别呢?区别就是:Object String······这些叫做内置构造函数,是系统给我们创建好的,而Person是我们自己创建的,叫自定义构造函数,在功能上有两点不同:
1)在前面没有new的情况时,自定义构造函数没有默认返回值(或者说返回值为undifined),与普通函数无异,而内置构造函数有默认返回值,比如:

console.log(Object()) ;//返回一个空对象{}console.log(Array()) ;//返回一个空数组[]console.log(String()) ;//返回一个空字符串""

2)有new的情况下,内置构造函数构建出来的对象会继承相应类型原型对象中的方法和属性,这就要引出我 们下面要着重要讲的内容:原型。


2关于原型和原型链


在这里先说一下,JS中的数据类型分为简单类型和引用类型(两者区别这里不详述):
简单类型:string,number,boolean,undefined,null
引用类型:Object,Array,Function,Date,String,Number,Boolean,RegExp等等
而我们平时说的“对象”这个词其实比较宽泛,它泛指一种引用类型,事实上,像数组,正则······这些都是对象,但是细分的话,我们就应该说Array是数组类型的对象,String是字符串类型的对象······结合上面的构造函数知识,用Object构造函数new(创建)出来的是Object类型的对象,用Array构造函数new(创建)出来的是Array类型的对象·······,同样,用自定义的Person构造函数new出来的是Person类型的对象,可以通过下面代码加以验证:

//定义一个叫Person的构造函数function Person () {}//实例化一个叫p的对象var p = new Person ;console.log(p instanceOf Person) ;//true

到这里之后,大家应该清楚了,JS中所有的对象其实都是由构造函数创建的,即使是字面量的比如:
var obj = {
name:“pig”,
age:”52”
}
或者
var arr = [1,2,2,3,12] ;
这样的对象,其实内部也是分别调用了构造函数Object和Array,
好了, 有了这些铺垫之后将原型和原型链就非常好理解了,我们来在回想一下系统的内置构造函数,比如var arr = new Array()构建出来的数组对象,它的原型我们可以用arr.proto(主要调试时使用)来访问,我们叫做arr对象的原型对象,还可以从它的构造函数角度来表示,即Array.prototype,我们叫做构造函数的原型属性,当然这只是从两个不同的角度去称呼,实际它们指的是同一个对象。举例:

//例1var obj = new Object() ;//obj对象的原型对象为:obj.__proto__ ;//Object构造函数的原型属性为:Object.prototype ;console.log(obj.__proto__===Object.prototype) ;//true //例2function Person () {}var p = new Person ;console.log(p.__proto__===Person.prototype) ;//true

那么现在,我们用Array构造函数来构造一个对象arr1,同时就会有Array.prototype生成,并且arr1可以继承Array.prototype的方法和属性:

var arr1 = new Array(1,60,5,4,8) ;//比如Array.prototype中的join()方法console.log(arr1.join(",")) ;//输出“1,60,5,4,8” 字符串

而每一个原型对象中都有constructor属性,指向这个原型对象的构造函数,因此上面的例子的原型链图为:

这里写图片描述

我们用自定义的构造函数同样来做一遍上面的例子:

function Pig(name,feature) {    this.name = name ;    this.feature = feature ;}var pig1 = new Pig("jam","fat")  ;

它的原型链图应该是:

这里写图片描述

这两次不一样的是,用Pig实例化出来的pig1对象不能够使用join()方法,这也正是内置构造函数和自定义构造函数的第二点不同了,就是内置对象比如Array的.prototype原型对象中,系统已经事先写入了一些方法和属性,因此用Array实例化出来的对象就可以通过原型链的继承直接调用原型中的方法,而自定义的Pig构造函数,它的原型属性中需要我们按照我们的需求通过替换、混入等方法自行写入,然后通过Pig实例化出来的对象才能通过继承来调用,当然这也就突出了内置构造函数的意义了,就是让我们更方便的创建对象,使用其相应的方法。
让我们还是以Array为例,Array.prototype是一个对象,我们可以通过.proto属性访问到它的原型,进而通过Array.prototype.proto.constructor来确定是哪个构造函数创建了它:

var arr1 = new Array(1,60,5,4,8) ;console.log(Array.prototype.__proto__.constructor) ;//输出的是Object构造函数

这个过程可以用watch监视器更为简单(因是简单的论证不详述),那么我们的原型链图可以改为:

这里写图片描述

那么如果是自定义的Pig,自然可以有这样的结论:

这里写图片描述

也就是说其实自定义的构造函数和内置的构造函数是在同一个级别上被创建出来的,所以我一直强调的一点就是,他们两个没有太大的差别,基本是可以等同来看待的 。
那么到这里,普通的对象的原型链都基本清楚了,如果想创建更多的实例,无法是在图上增加内容,原理不变。
我们完成了普通的对象的原型,现在的另一个问题就摆在眼前,那些构造函数也是对象,他们也应该有自己的原型和构造他们的函数,这就引出了下一个内容:Function 。


3 大boss:Function


在JS中,一切皆对象,那么我们现在想研究函数对象的原型,实际上,所以的函数都是由Function创建的,我们很容易验证这一点:

console.log(Object.constructor);//输出Functionconsole.log(Array.constructor);//输出Functionconsole.log(RegExp.constructor);//输出Functionconsole.log(Pig.constructor);//输出Function

事实再一次证明,自定义构造函数和内置构造函数同是被Function创建出来(具体创建过程不详述),如图:

这里写图片描述

但是,前面我们已经通过Array的例子了解,arr1对象最终继承Object.prototype,因此原型链图可以完善为:

这里写图片描述

好了,我们现在还是要研究Function的问题,因为它创建了Object , Array等等的函数,我们如果把它当做构造函数来理解,那么它也应该有自己的原型对象和构建它的函数,我们做如下验证:

console.log(Function.prototype);//输出function () { [native code] }console.log(Function.__proto__);//输出function () { [native code] }console.log(Function.__proto__ === Function.prototype);//true

看来我们确实走到了最顶端了,但是现在得出的反馈没有解决任何问题,因此我们还有继续往下找,看看究竟Function.prototype.proto这个对象是什么,因为Function.prototype就继承自它,因此我们再做如下代码:

console.log(Function.prototype.__proto__.constructor);//得到Object构造函数console.log(Function.prototype.__proto__);//得到一个对象console.log(Function.prototype.__proto__ === Object.prototype);//true 

最终我们知道,Function的原型链也继承自Object.prototype,那么我们来看最终的原型链图:

这里写图片描述

到这里,我们应该更深刻的了解为什么一切都是对象,因为所以的实例最终都继承Object.prototype这个对象(它的原型为null),包括Function,因此都具有Object的属性!!!这是我们创建任何一个对象都遵循的一个原型链图,变的只是左下角的构造函数等等,over······

原创粉丝点击