在网上面搜了一下“什么是js的原型,以及什么是_proto_”啥的。。。。,然后出来的全是一堆什么
alert(xxx.prototype===yyy._proto_)//true//false啥的,有毒吧!!还有原型链就更加不用说了,看着就头疼,反正我是看不懂,全靠死记,就算当时记住了,过一会就忘了,完全起不到作用,所以这篇博客从另外一个方面(ECMAScript标准层面)谈一下js中的原型
js的原型:(prototype),每一个对象都有一个prototype属性,注意不是对象的实例,你或许听过js的面向对象是基于原型的,但是你可能没有仔细琢磨这句话,这句话的意思就是:一个对象的原型的方法和属性会被其子类继承,而非原型的方法和属性不会被子类继承!!,换句话说:某对象原型的方法和属性会传递给该对象的所有实例。如果不理解,请看完整篇博客
前提:
- 了解js中的构造函数
- 了解js中this关键字
- 了解js中的call(),apply()函数
这个前提条件有点多哈!下面就简单的介绍一下先
1)、js中的构造函数
这个就不用多说了,js很灵活,一个函数你可以把它当成一个普通只用来调用的函数,也可以将它看做用来创建它自己的实例的构造函数,如果把它当成构造函数,就不得不牵扯到this关键字了,请接着看
2)、js中的this关键字
this关键字,简单理解就是实例本身!!这么说可能有点抽象,请看下面代码!
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
这是一个构造函数,使用了this关键字定义了三个属性,这样接着看下面代码
var ze1 = new F1("ze1", "01038");
ze1.name;//ze1
ze1.id;//01038
ze1.nicknameArray;//xiaoze,huahua,zehua
这样我们可以通过new一个F1的实例:ze1,然后就可以通过 . 的方式得到相应的属性,前面说过,this关键字指的是实例本身,那么,这里的实例本身就是ze1!!!所以实际上上面两端代码等价于下面这段代码:
var ze1 = {};
function F1(arg1, arg2) {
ze1.name = arg1;
ze1.id = arg2;
ze1.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
F1("ze1", "01038");
ze1.name;//ze1
ze1.id;//01038
ze1.nicknameArray;//xiaoze,huahua,zehua
说完了this,call(), apply()函数也不得不说了!,因为他们不仅可以加深对于this的理解,还有很多其他的用途,本篇博客所讲的原型就会用到它,所以请继续往下看!!!
3)、js中的call(), apply()函数
网上说他们是加深、中函数调用的一种方式,的确,他们函数调用的一种方式,当时,本篇博客谈到的他们两的用途并不是作为函数调用,它配合this关键字,实现js中的继承!!这些在本片博客中都会讲到,先来看下这两个函数的基本用法:
<1>、cal()函数
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
var ze1 = {};
//call函数的第一个参数(某对象的实例)将会取代对象(F1)构造函数中的this关键字
//而其后的参数将是与构造函数的参数相对应
F1.call(ze1, "ha", "01038");
ze1.name;//ze1
ze1.id;//01038
alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/
其实可以看到,和前面的this的讲解。本质上是一样的,而后面,我们将会看到call()函数的第一个参数是this关键字的用法
<2>、apply()函数
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
var ze1 = {};
//apply函数的第一个参数(某对象的实例)将会取代对象(F1)构造函数中的this关键字
//而其后的参数是一个数组,是与构造函数的参数相对应
F1.apply(ze1, ["ha", "01038"]);
ze1.name;//ze1
ze1.id;//01038
alert(ze1.nicknameArray);//xiaoze,huahua,zehua*/
可以发现,apply和call函数极其的相似,第一个入参的含义相同,后面的参数在本质上面还是一样的
正式:
1、ECMAScript标准
既然涉及到了ECMAScript这个词,就不得不说一下它是个什么东西了:我想,能搜到这篇博客的读者都是听说过javascript的,那么,javascript和ECMAScript是什么关系呢?简单的说就说:ECMAScript是javascript这类脚本语言的标准,而javascript是这个标准的实现和扩展(是先有javascript,后又ECMAScript的,想详细了解的可以其ECMAScript官网查看官方文档),标准的魅力这是让人愉悦,马士兵(编者的java的启蒙老师,必须赞一波)说过,一流公司买标准,二流公司买服务,三流公司卖产品(题外话)。
2、原型
一、以对象中属性和方法的定义为例(需要特别注意的是,这并不是继承,这和继承一点关系都没有,创建对象的实例并不是继承)
在不使用原型(prototype)定义对象的属性和方法:也就是只使用构造函数的方式来定义属性和方法
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
this.func = function () {
alert(this.nicknameArray);
}
}
var ze1 = new F1("ze1", "01038");
var ze2 = new F1("ze2", "01038");
ze1.func();//xiaoze,huahua,zehua
ze2.func();//xiaoze,huahua,zehua
可以发现,我们new了两个F1的实例,但是,这样就会带来一个问题:func()这个函数会因为我们new了两个实例而被创建两次!这样不合理,而js的原型可以解决这个问题!下面看只利用js的原型来定义对象的方法和属性
只利用js的原型来定义对象的方法和属性
function F1(arg1, arg2) {
}
F1.prototype.name = "ze1";
F1.prototype.id = "01038";
F1.prototype.nicknameArray = new Array("xiaoze", "huahua", "zehua");
F1.prototype.func = function () {
alert(this.nicknameArray);
};
var ze1 = new F1("ze1", "01038");
var ze2 = new F1("ze2", "01038");
ze1.nicknameArray[0] = "zeze";
ze1.func();//zeze,huahua,zehua
ze2.func();//zeze,huahua,zehua
可以发现两个问题:
1)、现在不能通过构造函数来传递参数,可以发现,这里的属性是写死的!
2)、使用prototype的方式传递给实例的是指针,所以正如上面的例子,某个实例的属性改变了,其他的该对象的实例的属性都会发生相应的变化
所以,我们通常使用构造函数/原型的混合模式来定义对象的属性和方法,也就是使用构造函数定义对象的非函数属性,而使用原型定义对象的函数属性,看到这里,有木有对js中的原型有更好的理解呢?如果嫌不够,那就继续往下看——基于原型的js继承机制
使用构造函数定义对象的非函数属性,而使用原型定义对象的函数属性
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
F1.prototype.func = function () {
alert(this.nicknameArray);
};
var ze1 = new F1("ze1", "01038");
var ze2 = new F1("ze2", "01038");
ze1.nicknameArray[0] = "zeze";
ze1.func();//zeze,huahua,zehua
ze2.func();//xiaoze,huahua,zehua
可以发现,通过这种方式,完美的解决了上面的所有问题
1、使用原型定义对象的函数属性,该函数只会被创建一遍!
2、当改变某实例的属性值的时候,并不会影响到该对象的其他实例
3、可以通过构造函数来传递参数
二、基于原型的js继承机制
最基本是:将某函数对象(父类)直接赋予给某对象的属性(子类)
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
this.alertName = function () {
alert(this.name);
};
}
function F2(arg1,arg2, arg3) {
//注意,这里的F1是被当做普通的函数进行传递,而不是构造函数
this.f1 = F1;
this.f1(arg1, arg2);
this.school = arg3;
this.alertSchool = function () {
alert(this.school);
};
}
var f1 = new F1("ze1", "01038");
var f2 = new F2("ze2", "01038", "SDU");
f1.alertName();//ze1
f2.alertName();//ze2
f2.alertSchool();//SDU
可以看到,这里大量用到this关键字,注意上面的那行注释!!所有,这行代码:this.f1 = F1等价于下面的代码:
this.f1 = function (arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
this.alertName = function () {
alert(this.name);
};
};
可以发现,这个就是F1的源码!!而当new出F2时,这些this将全部被f2这个实例给替换,成为f2的属性和方法!,
基于call()函数的继承
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
this.alertName = function () {
alert(this.name);
};
}
function F2(arg1,arg2, arg3) {
//注意,这里传入了this作为call函数的第一个参数
F1.call(this, arg1, arg2);
this.school = arg3;
this.alertSchool = function () {
alert(this.school);
};
}
var f1 = new F1("ze1", "01038");
var f2 = new F2("ze2", "01038", "SDU");
f1.alertName();//ze1
f2.alertName();//ze2
f2.alertSchool();//SDU
注意到上面的那行注释,以this关键字作为call函数的第一个参数,想必,现在读应该知道这个this在F2实例化的时候会被替换成什么对象了吧!没错,在这个例子中,this被f2替换,这样,是不是更好的理解了call函数呢!
基于apply()函数的继承
因为这个函数和call函数极其的相似,在这里就请读者参照上面的例子自行完成吧!
基于原型的继承
终于进入正题了,看下面例子
function F1(arg1, arg2) {
this.name = arg1;
this.id = arg2;
this.nicknameArray = new Array("xiaoze", "huahua", "zehua");
}
F1.prototype.alertName = function () {
alert(this.name);
};
function F2(arg1,arg2, arg3) {
//利用这种方式(有些地方成为“对象冒充”),继承非函数属性
F1.call(this, arg1, arg2);
this.school = arg3;
this.alertSchool = function () {
alert(this.school);
};
}
//注意,这里讲F1的实例传递给F2的原型对象
//注意,这里的F1是不用带参数的
//我们使用这种方式(原型),继承函数属性
F2.prototype = new F1();
var f1 = new F1("ze1", "01038");
var f2 = new F2("ze2", "01038", "SDU");
f1.alertName();//ze1
f2.alertName();//ze2
f2.alertSchool();//SDU
注意到上面的那些注释!,以及前面内容的铺垫,这些代码是不是很容易理解!通过这行代码可以看出(F2.prototype = new F1();),F2的原型对象(也就是F2.prototype)是F1的实例,这样,F1的原型对象的所有的方法和属性都将传递给F2.prototype,这样,上面的那句代码就等价于下面这句代码:
//在这个例子中F2.prototype = new F1();等价于下面代码
//因为F1中只有一个alertName是原型方法(这里只算自己定义的,其实还有很多是继承而来的)
F2.prototype.alertName = function () {
alert(this.name);
};
这样写,是不是就好理解了,就理解了为什么F2的实例f2可以使用alertName方法了
这里有一个小坑,就是:因为我们是将F2的原型对象重新赋予为F1的实例,这样在F2.prototype = new F1()这句代码之前的所有为F2.prototype赋值的代码(例如F2.prototype.xxx=yyy)都将失效,所以一个解决方法是:将这些新的赋值代码写在F2.prototype = new F1()这句代码之后就行了。
说了这么多,是不是对js中的原型在应用层面有了较好的了解了呢!
注意:这篇博客中的实例是基于ECMAScript规范来的,并不是javascript
版权声明:本博客只许被链接,拒绝任何形式的copy