javascript 特殊的面向对象以及继承详解(入门篇)
学习Javascript人,大多听说一句话叫js里面一切都是对象。我刚开始接触javascript面向对象编程时候,挺乱的,我当时习惯性的把PHP的面像对象思想套用在js上面,其实js的面向对象与传统的面向对象还是有很多区别的。这里就不再去讲解基础的面向对象是什么了,看这篇文章就默认大家都知道面向对象的概念。
首先,在目前的js版本中,依然没有引入class这个关键词,js里面没有类的概念,其他语言在实例化一个对象的时候,都是使用new 类名来得到实例,而js由于没有class,因此它的面向对象也可以理解为是一种模拟的方式。首先我们来说一下js里面的函数,js里面都函数有一个特点,就是所有的函数都有返回值,如果我们没有手动编写 return。那么函数会返回一个underfind,如果写了,则返回你写的值。这种返回值是在正常的函数调用情况下出现的。比如:
function a(){
return 123;
}
a();
此时返回123。
function a(){
}
a();
此时返回underfind。
那么,我们在调用js里面的函数时候,其实不止这一种方式,常用的还用使用call或者apply方法也可以使函数执行,这个以后再说。除此之外,我们还可以通过 new的方式来调用函数,new在js里面其实是一种运算符,但凡函数是通过它来调用的,返回值就会发生变化。如果我们在函数中没有写返回值或者返回值写的不是对象类型的数据,那么这个函数都会返回一个空对象,如果我们写的返回值是一个对象,那么则返回我们写的对象。所以只要是通过new来调用函数,返回值就变成了对象。
js也充分利用了这一点,来模拟传统的面向对象,我们来看一个例子:
function Obj(){
this.name='小红';
this.age='24';
}
var poeple = new Obj();
根据我们上面说的,当我们使用new去调用函数时候,返回对象,因此我们可以得知people其实就是一个对象了。我们看到上面的代码中this.name='小红',this.age='24',这个this其实是什么呢?其实这个this就是我们返回的对象,也就是说this就是people,如果不能理解,只需记住,这是js的特点。我们接着看:
function Obj(){
this.name='小红';
this.age='24';
}
var poeple = new Obj();
alert(people.name);
alert(people.age);
运行上述代码以后,分别弹出了小红和24,这更加确切的说明this就是people.
但是这个this也不一定只是people,确切的说这个this是谁具体要看是谁在调用这个函数,看例子:
function Obj(){
this.name='小红';
this.age='24';
}
Obj();
当我这样去调用的时候,这个this指向的是谁呢?其实js是运行在浏览器中的,浏览器中有一个顶层对象叫做window,所有的变量和函数其实都是挂在他下面的,看例子:
Obj();
window.Obj();
这两种写法的效果是一样的,此时没有通过new来调用,其实就是一个普通的函数,而这个函数的调用者是window,因此这个this指向window.
所以说this的指向不是固定的,要看具体的调用方式以及是谁在调用。
我们一般把这种用来返回的对象的函数称作构造函数,他的作用就是用来创建对象的,一般为了区分构造函数与普通函数的区别,构造函数的首字母会大写。我早期学习的时候,就把这里的构造函数与其他语言里的class弄混淆了,其实他们是不同的东西。
第二个就是js的面向对象同样有继承的功能,只是它的继承与传统的继承方式也不一样,我个人认为也是一种模拟。首先说明一件事情,在js里面所有的对象都有一个叫做__proto__的属性,所有的函数都有__proto_的属性和prototype的属性,prototype属性对应的值是一个对象,这个对象可以保存很多东西的,中文名称叫做原型对象,看例子:
function Obj(){
this.name='小红';
this.age='24';
}
Obj.prototype.job='老师';
var poeple = new Obj();
alert(people.job);// 弹出 老师
刚才已经说过,此时的people就是一个对象,而Obj是一个函数,people是Obj的一个实例对象,他们之间肯定是有关系的,既然people是一个对象,那么他肯定有_proto_属性,这个属性其实指向了Obj的prototype,再详细说一下,people的__proto__保存的是一个地址,这个地址指向的是people的构造函数Obj的prototype属性。所以程序运行到people.job的时候,people这个对象在自己的属性里面开始寻找job这个东西,但是木有找到,于是接下来他就找到了自己的__proto__,顺着里面的地址找到了Obj.prototype,在这里发现了job,然后呢,他就把job的值给弹了出来。这一连串的动作专业术语叫做原型链查找,那为什么要说这个呢,主要是因为js的继承有一种方式就叫做原型继承。那上面已经把对象和构造函数的在原型方面的联系说清楚了。那么接下来我们就说一下js的原型继承,看例子:
function a(){
this.name='小红';
this.age='24';
}
function b(){
this.job='小红';
this.sex='男';
}
b.prototype=new a();
var c = new b();
alert(c.name);//小红
以上代码,做了一个功能,把b的原型对象修改成了a的实例对象,这时b的实例对象去访问name属性时候,首先在自身查找,没有发现,于是去他的构造函数b的原型里面去查找,而此时b的原型就是一个a的实例对象,a的实例对象当然有name和age属性了,因此就访问到了。这种方式成为原型继承,有一个小问题,就是这样直接把对象赋值给prototype,修改了prototype里面的constructer属性,constructer属性后面再说。
除了原型继承以外,还有一些方式也可以实现继承,最常用的就是call和apply方法,解释这个问题还需要了解另外一个特性,我们上面已经看到了,call方法可以用来调用函数,其实他的功能不止于此,最核心的功能就是改变this指向,我们来看例子:
此时,对象c拥有 name age self三个属性,前面两个就是继承下来的了。
其实,真实的编写过程,往往把上述两种继承方式同时使用,当然还有一些其他的继承方式,大家可以自己去网上查查。
这里说一下,上面那种原型继承法有一点点问题,直接把对象赋值给原型会导致原型里面的constructor出现错误,这个constructor实际上用来保存对象的构造函数的,也就是说一个对象是由那个构造函数实例化而成的,那么这个constructor就代表那个构造函数。举例:
function a(){
this.name='小红';
this.age='24';
}
var b = new a();
alert(a.constructor); // 打印出来的就是整个函数
如果直接把对象赋值给原型,这个constructor值就会被修改,所以,为了保险起见,很多人都会手动加上一句 a.prototype.constructor = a;
这里没有介绍更多的面向对象的东西,写的过程中也有遇到疑惑的,如果有错误的话,请大家多多指教!