欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

javascript原型链详解(js中原型和原型链)

程序员文章站 2022-04-10 16:17:11
一、原型1.函数原型在javascript中,函数不仅仅是一个可以重用的代码块,而且还可以作为一种数据使用。在堆空间中为函数分配了它的存储空间,函数名或函数的其他形式的引用保存了这个存储空间的引用地址...

一、原型

1.函数原型

在javascript中,函数不仅仅是一个可以重用的代码块,而且还可以作为一种数据使用。在堆空间中为函数分配了它的存储空间,函数名或函数的其他形式的引用保存了这个存储空间的引用地址。所以javascript中的函数是一种引用数据类型,这就是为什么我们说javascript中的函数也是对象。

那么函数这样的对象有很多特殊的性质,原型就是其中之一。每一个函数对象都包含一个属性:prototype。当我们声明一个函数时,函数对象就创建好了。而函数对象创建的同时,系统还会同时创建一个对象,并让函数对象的prototype属性指向它。

比如说,当我们执行下面代码时:

function person(pername,perage){//…}

就声明了一个函数,系统会将这个函数对象创建出来。内存中应该是这样的情况:

javascript原型链详解(js中原型和原型链)

但是更深层次挖掘一下,我还需要知道创建函数对象的同时,还会创建一个“原型对象”,由函数对象的prototype属性指向这个原型对象。

javascript原型链详解(js中原型和原型链)

2.对象原型

函数可以和以它为构造器所创建的所有对象共享原型对象。就比如本文最初展示的代码中,per01和per02都是person函数创建的,那么per01或per02就可以通过自身的__proto__属性关联到person对象的原型。

下图表示了它们之间的关系

javascript原型链详解(js中原型和原型链)

所以既然函数的原型对象是可以为所有被创建对象共享的,那么就可以将我们要为所有对象都添加的属性添加到原型对象上。

function person(pername,perage,gender){this.pername = pername;this.perage = perage;this.tostring = function(){return “personname=”+this.pername+” personage=”+this.perage;};}var per01 = new person(“bob”, 20);var per02 = new person(“kate”, 25);person.prototype.message = “atguig is very good”;console.log(per01.tostring()+” message=”+per01.message);console.log(per02.tostring()+” message=”+per02.message);
执行结果:
personname=bob personage=20 message=atguig is very goodpersonname=kate personage=25 message=atguig is very good

javascript引擎在读取per01对象的message属性时先在当前对象本身的空间内查找,如果能找到则直接返回,如果找不到则沿着__proto__属性找到原型对象,再在原型对象中查找message属性。

javascript原型链详解(js中原型和原型链)

二、原型链

在研究了对象原型和函数原型的关系后,我们还可以进一步深入思考:既然原型对象是一个“对象”,那么这个对象有没有__proto__这个属性呢?当然有!

function person(pername,perage,gender){this.pername = pername;this.perage = perage;this.tostring = function(){return “personname=”+this.pername+” personage=”+this.perage;};}var person = new person(“tom”, 20, “male”);console.log(person.__proto__);console.log(person.__proto__.__proto__);
执行结果:
person {}object {}

说明person.__proto__所指向的对象是由person函数创建的,而
person.__proto__.__proto__所指向的对象是由object函数创建的。

就对象的本质而言,任何一个对象都是以new 构造器函数的方式创建的,所以所有对象都和构造器函数共享原型对象。这样说可能你会有疑问,我可以通过{属性名:属性值}的方式创建对象呀,这里并没有用到构造器函数呀?那么情看下面的代码:

var obj = {“myname”:”jerry”,”myage”:15};console.log(obj.constructor);console.log(obj.__proto__ === object.prototype);
执行结果:
function object()true

说明从本质上来说,任何对象的创建都依赖对应的构造器函数,当然也包含原型机制。既然如此,那么原型对象本身也是一个对象,这个对象也指向一个原型对象,那么原型对象的原型对象也是对象,可以继续指向一个原型对象……这就是原型链。

javascript原型链详解(js中原型和原型链)

但原型链并不是无止境的,到object()函数为止。

var obj = {“myname”:”jerry”,”myage”:15};console.log(obj.__proto__.__proto__);
执行结果:
null

三、原型的作用

javascript中原型有很广泛的用途,在此我们仅举两例,供大家参考。

1.格式化日期

在javascript中对日期格式化的支持不是很完善,需要我们自己弥补。但是用到日期格式化的地方又很多,这毕竟是个基础操作,那如何能够一劳永逸的解决这个问题呢?

①通过原型机制将格式化日期的函数添加到date()函数对象上

代码如下:

// 对date的扩展,将 date 转化为指定格式的string// 月(m)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,// 年(y)可以用 1-4 个占位符,毫秒(s)只能用 1 个占位符(是 1-3 位的数字)// 例子:// (new date()).format(“yyyy-mm-dd hh:mm:ss.s”) ==> 2006-07-02 08:09:04.423// (new date()).format(“yyyy-m-d h:m:s.s”) ==> 2006-7-2 8:9:4.18//var time1 = new date().format(“yyyy-mm-dd hh:mm:ss”);////var time2 = new date().format(“yyyy-mm-dd”);date.prototype.format = function(fmt) { // author: meizzvar o = {“m+” : this.getmonth() + 1, // 月份“d+” : this.getdate(), // 日“h+” : this.gethours(), // 小时“m+” : this.getminutes(), // 分“s+” : this.getseconds(), // 秒“q+” : math.floor((this.getmonth() + 3) / 3), // 季度“s” : this.getmilliseconds()// 毫秒};if (/(y+)/.test(fmt))fmt = fmt.replace(regexp.$1, (this.getfullyear() + “”).substr(4 – regexp.$1.length));for ( var k in o)if (new regexp(“(” + k + “)”).test(fmt))fmt = fmt.replace(regexp.$1, (regexp.$1.length == 1) ? (o[k]): ((“00” + o[k]).substr((“” + o[k]).length)));return fmt;}

②将上述代码保存到js文件中

③使用时引入这个js 文件即可调用format()函数格式化日期

2.模拟继承

在javascript中没有类的概念,用于创建对象的构造器函数很类似于java中的类。而面向对象中的很多思想在javascript中也只能模拟实现。

①情景举例

声明一个person构造器函数和一个student构造器函数。

function person(thename,theage){this.thename = thename;this.theage = theage;}function student(thename,theage,subject){this.thename = thename;this.theage = theage;this.subject;}

很明显,这两个函数从语义上来说存在着继承关系,学生是人的一种,student对象应该是person对象的实例。同时给姓名和年龄赋值的语句在两个函数中也是重复的。

所以模拟继承时我们需要解决两个问题:

i.将student中的重复代码使用person来代替

ii.让student对象是person的实例,即student instanceof person要返回true

②提取重复代码

function person(thename,theage){this.thename = thename;this.theage = theage;}function student(thename,theage,subject){person.apply(this, arguments);this.subject;}

③instanceof

function person(thename,theage){this.thename = thename;this.theage = theage;}function student(thename,theage,subject){person.apply(this, arguments);this.subject;}student.prototype = person.prototype;var student = new student(“tom”, 20, “java”);console.log(student);console.log(student instanceof person);

那么这是为什么呢?在javascript中,判断一个对象是否是某个构造器函数的实例,就是看分别沿着对象和函数的原型链能否找到同一个原型对象。

例如:student对象为什么能够是object的实例呢?

console.log(student instanceof object); //trueconsole.log(student.__proto__.__proto__ === object.prototype); //true

那么现在student.__proto__和person.prototype相等,student自然就可以是person的实例了。