JavaScript的原型
JavaScript在ECMA6的class这些关键字出来之前,面向对象编程的方式是基于函数和原型链来实现的。这边涉及到两个东西:contructor和prototype。
本文首先从如何构造一个JavaScript的对象,再到构造函数,再到原型链的顺序来阐述。
如何构造一个JavaScript的object
在JavaScript中构造一个对象对便捷的方式如下:
var object = { |
上述代码能够直接创建一个对象。利用typeof可以得到object的类型:
console.log(typeof object); // "object" |
这样做可以非常便捷的得到一个object,但是问题来了,如果需要创建多个属性和方法均一样的对象,使用上述的方法,就会出现问题了。代码就会变成如下这样:
var object1 = { |
如上代码所示,这样创建对象是非常灾难的,并且没有一个固定的模板可以用于创建对象,每次创建对象时需要从已经创建的对象中拷贝属性再进行修改,非常不科学。
于是,这时候就需要用到构建函数了。
构造函数
在别的语言中,有class这个概念,但是在JavaScript中没有(在ECMA6之前没有)。JavaScript的对象体系是基于构造函数(contructor)和原型链(prototype)的。构造函数可以作为对象的模板。
eg:
var User = function (username, description) { |
上述代码将User作为一个构造函数,然后通过new操作符创建了一个user1的对象。
可以看出构造函数的声明和普通函数一样。只是函数名的首字母作了大写,这个并不是硬性的,只是为了表示和普通函数的区别。因此普通函数也可以跟new操作符配合使用作为构造函数。当然,如果构造函数不和new操作符一起使用,则会被当做一个普通函数。此处不再赘述。
这边需要注意的是:
1.构造函数的内部用this
来表示即将创建的对象。
2.操作符new用于根据构造函数创建一个对象
那么这边虽然实现了利用模板(构造函数)来创建对象的问题,但是仍然面临一个问题。
这边有这么个情况:
var User = function (username, description) { |
上述构造函数在执行构造函数代码的时候才将doSomething方法的方法体赋值给doSomething这个属性。那么,在每次执行构造函数代码的时候都会走这个逻辑。于是,每生成一个新的对象,都会生成一个新的doSomething方法。让我们测试一下:
user1.doSomething === user2.doSomething; // false |
通过上述代码的运行结果,我们可以得知,user1和user2中的doSomething方法并不是同一个。在其他语言,比如Java中,我们的实例方法,都是指向同一个地址的方法的。那么在JavaScript中的这一现象,显然不是那么的友好。试想一下,如果有1000个User对象,那么就会有1000个doSomething方法,而doSomething的方法体都是一样的,这样造成了不必要的开销。
那么这时候,就需要用到JavaScript中的原型(prototype)了
原型
prototype
JavaScript的每个函数都会有一个prototype
属性,这个属性将会指向一个对象。这个对象的所有属性和方法,都能被实例对象所共享。那么,如果将上一节中的doSomething方法放在prototype
中,就可以处理掉不必要的开销:
var User = function (username, description) { |
这边使用了prototype之后,会有这么一个现象,就是对象在调用某个方法或使用某个属性的时候,会先查找自身有没有这个方法或者属性,如果有则直接使用,否则才回去原型对象中查找。如果原型对象没有这个方法或属性,则会再想原型对象的原型对象去查找,直到找到需要的方法或属性,或者一直追溯到了Object的原型。object的原型对象的原型对象是null的。
这个就是原型链了。然后这边如果追溯的层级过深的话是比较消耗性能的。
如果对象具有跟prototype所指向的对象名字一样的属性,那么在获取该属性时,会读取对象自身的属性值。
eg:
var object = { |
通过将构造函数的prototype属性指向一个对象,能够让通过该构造函数创建的实例对象,能够使用prototype属性所指向的这个对象的属性和方法。比如,如果将prototype指向一个数组,那么实例对象就可以调用数组的方法。
这里借鉴一下阮一峰老师的例子:
var MyArray = function () {}; |
这边的这个操作,就相当于使MyArray继承了Array类了。这也就是后面要提的利用原型链做继承。
contructor
上述代码还提到了contructor属性。 这个属性用于指向构造函数。默认的情况下指向的是当前prototype指向的对象所在的构造函数。在修改了原型对象之后,一般会同时修改contructor属性。例如,上述代码中,在执行完MyArray.prototype = new Array();
这句时,contructor会指向Array。之后我们将它指回MyArray。
contructor属性用于告知我们实例对象是由哪个构造函数创建的。
另外,有了contructor属性之后,就可以从一个实例对象直接创建另一个实例对象了:
var User = function (username, description) { |
构造函数的继承
让一个构造函数继承另一个构造函数:
function Super() { |
1.上述代码,在Sub构造函数开始的时候调用Super, 执行完毕后走自己的逻辑。 Super.call(this)
表示将将传入的this作为执行函数中this的指向,然后调用Super的逻辑。也就是说这里是为了将Super中的this指向Sub中的this。这是因为JavaScript的this指向的是当前调用环境的this。如果不做这样的处理,Super在调用的时候,this可能会指向全局作用域,导致不必要的异常。
然后将Sub的prototype指向由Super为原型创建的对象,之后将contructor指向自己。在将Sub的prototype指向Super的时候,contructor会自动指向Super,所以之后需要将其赋值为自身,也就是Sub
到此,原型和原型链的东西差不多就说完了。这里总结一下构造函数,prototype,contructor和实例对象的关系:
function Super() { |