继续这个东西在Javascript中特别庞杂,我掌握得也不好,找工作口试的时刻在这个问题上栽过跟头。Javascript的继续体式格局属于原型式继续,异常天真。因而Javascript的继续体式格局除了基于类的继续以外另有基于原型的原型式继续。
继续是什么
看了这个词的第一回响反映我遐想到了财富继续,在平常情况下,父母的遗产由后代继续,也就是说后代将会取得财富的运用权。而Javascript中的继续与现实生活中的财富继续照样有区分的。起首,Javascript中的父类和子类可以同时并存,而遗产么,什么样的财富叫遗产呢?其次,财富是花了就没了,而JS里的继续则是子类可以继续父类的要领,可以反复运用,削减代码量。当几个类都须要一个类似的要领时,运用继续可以从同一个类中继续雷同的要领,而不必对每一个类反复地复制粘贴了。
不必在定义上胶葛过久,下面议论一下完成继续的一些要领。
继续的基础要领
开首便说到了JS的继续体式格局分为基于类的继续(简称类式继续)和基于原型链的继续(简称原型式继续)。
类式继续
类式继续的特性就是运用函数声明类,经由过程new
关键字建立实例。
建立类的要领很简单,平常可以写成如许:
function Blog(address) {
this.address = address;
}
Blog.prototype.getAddress = function() {
return this.address;
}
建立该类的实例经由过程new
关键字即可:
var blog = new Blog('classicemi.github.io');
blog.getAddress(); // 'classicemi.github.io'
当运用new
关键字时,它和经由过程平常体式格局实行函数的区分在于函数的实行体式格局会转变。当运用new
关键字实行Blog
类的组织函数时,体系起首建立一个新对象,这个对象会继续自组织函数Blog
的原型对象(新对象的原型就是组织函数的prototype
属性)。再将this
关键字绑定到新对象上,再返回该新对象。也就是说,组织函数用来对天生的新对象举行一些处置惩罚,使这个新对象具有某些特定的属性。
建立一个继续Blog
的类就须要手工完成和new
运算符类似的工作了。
function MyBlog(address, author) {
Blog.call(this, address);
this.author = author;
}
当经由过程new
关键字挪用MyBlog
组织函数时,体系会建立一个新对象(这步是自动的,this
即为这个新对象),然后在this
上挪用超类Blog
的组织函数,再给this
增加一些MyBlog
类特有的不是继续自Blog
类的属性,末了将新对象this
返回(这步也是自动的)。
下面为了继续Blog
类的要领,须要设置原型链,使MyBlog
类可以在原型链上找到继续自Blog
类的要领。
MyBlog.prototype = new Blog(); // MyBlog类的原型是Blog类的一个实例
MyBlog.prototype.constructor = MyBlog; // 上一步实行后prototype的constructor属性会变成Blog,须要修正返来
MyBlog.prototype.getAuthor = function() { // 给MyBlog增加自身的要领
return this.author;
}
经由过程这些操纵后,MyBlog
类就声明好了,它继续了Blog
类的属性和要领,建立MyBlog
类实例的要领和建立Blog
类实例的要领一样,直接运用new
关键字挪用组织函数即可。
原型式继续
之前的类式继续是为了模拟其他一些面向对象言语的特性而制造的,并没有真正表现Javascript言语自身的特性,下面要说的原型式继续则是应用JS的原型特性而完成的继续体式格局。
运用原型式继续时,不须要像类式继续一样用一个类(组织函数)来定义对象的构造,而可以用对象字面量的体式格局直接建立一个对象,这个对象是作为原型存在的,被称作原型对象(prototype object)。就像工场临盆车间里的模具一样,为今后临盆出的零件供应了参考原型。
照样以之前的Blog
和MyBlog
类为例:
// Blog原型对象
var Blog = {
address: 'classicemi.github.io', // 属性只是作为默认值,平常都会被改写
getAddress: function() {
return this.address;
}
};
这里没有像用一个组织函数来定义Blog
类的构造,将要领增加在Blog.prototype
上。这里定义的Blog
对象只是作为原型存在,为继续Blog
类的对象供应一些要领。
如今原型对象有了,要建立继续该原型对象对应的类的新类应当怎么做呢?应用JS的原型链特性,只要将新类的原型设为该原型对象即可。根据这类思绪可以写出子类MyBlog
的建立要领:
function MyBlogConstrucFunc() {}
MyBlogConstrucFunc.prototype = Blog;
var MyBlog = new MyBlogConstrucFunc();
经由过程new
运算符挪用MyBlogConstrucFunc
函数,返回的是一个空对象,这个空对象的prototype
属性指向原型对象Blog
。在返回的空对象中,还可以增加MyBlog
类自有的属性和要领。
不过经由过程这三行代码完成子类对超类的继续照样有些冗余,我们可以完成一个要领来完成对超类的继续,将超类作为该要领的参数传入并在末了将空对象返回即可。
function clone(object) {
function F() {}
F.prototype = object;
return new F();
}
Mixin Class
以上所议论的是比较严厉的继续体式格局,有的时刻,我们能够只想对某个函数举行重用,并不须要完整的继续,那末我们可以将函数以扩大的体式格局在类之间举行同享。关于重用频次比较高的要领,我们可以将它们归并在一个类中,然后用这个类去扩大其他的类。这类要领称为掺元类(mixin class)。这类处置惩罚要领在许多JS库(比方jQuery
和Underscore
)中都有用到,是一种扩大东西函数的好要领。
var Mixin = function() {}
Mixin.prototype = {
serialize: function() {
var output = [];
for(key in this) {
output.push(key + ': ' + this[key]);
}
return output.join(', ');
}
...
};
为了能方便地将Mixin
类中的要领增加到其他类中,我们可以扩大东西函数augment
:
function augment(receivingClass, givingClass) {
if (arguments[2]) { // 可接受三个参数,第三个参数为需增加的要领,多个要领可用数组将要领名传入
if (arguments[2] instanceOf Array) {
for (var i = 0, len = arguments[2].length; i < len; i++) {
if (!receivingClass.prototype[arguments[2][i]]) {
receivingClass.prototype[arguments[2][i]] = givingClass.prototype[arguments[2][i]];
}
}
} else if (typeof arguments[2] === 'string') {
if (!receivingClass.prototype[arguments[2]]) {
receivingClass.prototype[arguments[2]] = givingClass.prototype[arguments[2]];
}
}
} else {
for (methodName in givingClass.prototype) {
if (!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
}
}
}
这时候我们假如要给其他类增加Mixin
中的要领的话可以直接如许写:
augment(MyBlog, Mixin);
类式继续和原型式继续的对照
类式继续存在的意义很大一部分是为了满足对Javascript的特性还不熟习的程序员,毕竟这类要领是强行为了模拟其他面向对象言语的特性而制造的。Javascript的原型式特性在父类和子类之间建立了一种双向的联络,这是JS区分于其他言语的特性。
原型式继续发挥了JS的特性,一切的子类继续的要领会经由过程原型链逐级向父类查找,因而用于继续的要领在内存中只会保留一份,如许可以勤俭内存。只要在对子类的某个要领举行直接设置,将继续而来的要领掩盖的时刻才会对新要领零丁天生副本。
封装对继续的影响
一个经由封装的类,它的公用要领和特权要领可以被继续下来,由于它们是增加在原型链上的,在作为组织函数的时刻可以继续给子类。而私用要领相当于作为了闭包中的变量,与原型链无关,因而不会被继续。
父类中的特权要领可以接见父类中的私用属性,而特权要领会被子类继续,因而子类也可以经由过程继续的特权要领间接接见父类的私用属性。但子类中新增加的特权要领不能接见父类中的私用属性,由于缺少了抵达父类内部的原型链“通道”。