原文地址:https://github.com/catchonme/blog/issues/3
我们知道Class
最大的作用就是能够实现继承,这样子类通过继承父类,从而尽可能的对代码进行复用。
那么问题来了
- 如何实现子类继承父类的属性和函数,
- 子类继承父类时,为什么子类的同名函数不会覆盖父类的同名函数
我们通过分析prototype.js 来看看ES5
是如何实现Class
的
prototype.js
书写Class
的方法
var Animal = Class.create({
initialize: function(name) {
this.name = name;
}
})
var myAnimal = new Animal('jack');
console.log(myAnimal.name);// jack
var Cat = Class.create(Animal, {
initialize: function($super, name, age) {
$super(name);
this.age = age;
}
})
var myCat = new Cat('mia', 13);
console.log(myCat.name); // mia
console.log(myCat.age); // 13
复制代码
创建类:通过Class.create
创建Class
, initialize
函数初始化Class
的属性
继承:子类在Class.create
的第一个参数传入父类名称,在函数中第一个参数传入$super
,函数内使用$super(args)
来调用父类的同名函数
然后我们来看prototype.js
内部实现Class
,以下是大致的结构,主体是三个函数,包含一个空函数
var Class = (function(){
var subclass() {}; // 空函数
function create() { // code... }
function addMethods() { // code... }
return {
create: create,
Methods: {
addMethods: addMethods
}
}
})();
复制代码
我们挨个解析,create
函数做了什么呢?
function create() {
var parent = null, properties = [].slice.call(arguments);
// 传入的第一个参数是否是函数,如果是,就说明是当前类的父类
if (isFunction(properties[0]))
parent = properties.shift();
// 新建 klass 函数,并执行initialize 函数
function klass() {
this.initialize.apply(this, arguments);
}
// extend 函数将 Class.Methods的属性赋给 klass
extend(klass, Class.Methods);
// 设定当前类为 parent
klass.superclass = parent;
klass.subclasses = [];
// 如果当前类有父类,就需要把父类的属性和函数赋给当前类
if (parent) {
// 父类的 prototype 赋给 空函数 subclass.prototype
subclass.prototype = parent.prototype;
// 通过实例化 subclass 再赋给 klass.prototype,这样 klass 就能够拥有父类的属性和函数了
klass.prototype = new subclass;
parent.subclasses.push(klass)
}
// addMethods 为将所有传入的参数赋给 klass 的 prototype 中,该函数作用后面讲解
for (var i=0, length=properties.length; i<length; i++) {
klass.addMethods(properties[i]);
}
if (!klass.prototype.initialize) {
klass.prototype.initialize = emptyFunction
}
klass.prototype.constructor = klass;
return klass
}
复制代码
create
函数做了哪些事呢- 新建
klass
函数,执行initialize
函数 - 判断是否传入了父类,如果是,就将父类的属性和函数赋给当前类
- 使用
addMethods
方法,将用户的函数赋给klass
的prototype
中
- 新建
继承父类的作用已经实现了,用户创建类时的方法需要通过addMethods
赋给当前类的prototype
中,接下来的问题就是,父类和子类的同名函数,该如何执行呢,父类的同名函数已经赋给子类的prototype
中了,子类增加同名函数,为什么不会覆盖掉呢?
我们看看addMethods
函数
function addMethods(source) {
var ancestor = this.superclass && this.superclass.prototype,
properties = Object.keys(source);
// 遍历传递过来的函数,分别赋给当前的 prototype 中
for (var i=0, length=properties.length; i<length; i++) {
var property = properties[i], value = source[property];
// 判断当前类中的函数,第一个参数否是是 $super
if (ancestor && isFunction(value)
&& value.argumentNames()[0] == "$super") {
var method = value;
// 这里是为什么子类能够执行父类的同名函数的关键地方
value = (function (m) {
return function () {
// 通过将函数名传递给父类,父类执行一次该同名函数,然
return ancestor[m].apply(this, arguments);
}
})(property).wrap(method); // wrap(method) 又执行一次当前类的同名函数
// 重写当前函数的 valueOf
value.valueOf = (function (method) {
return function () {
return method.valueOf.call(method);
}
})(method);
// 重写当前函数的 toString
value.toString = (function (method) {
return function () {
return method.toString.call(method);
}
})(method);
}
// 将函数赋给当前类的 prototype
this.prototype[property] = value;
}
}
复制代码
addMethods
又做了哪些事呢?- 遍历传过来的函数
- 如果该函数的第一个参数为
$super
,就调用父类的同名函数执行一次,在调用当前类的同名函数执行一次,这样就能够实现使用$super
调用父类的函数了 - 如果该函数的第一个参数不为
$super
,就直接赋给当前类的prototype
中
- 如果该函数的第一个参数为
- 遍历传过来的函数
上述函数中使用的其他工具函数,如isFunction
, extend
等,因为我已经把prototype,js
中的Class
单独剥离出来了,通过点击 这里 查看,里面会有更详细的注释。
所以我们回到最初的问题
- 如何继承父类的属性和函数?
- 通过中间变量实例化父函数,然后赋给子函数的
prototype
中,这样子函数就拥有父函数的属性和函数了
- 通过中间变量实例化父函数,然后赋给子函数的
- 子类为何不会覆盖掉父类的同名函数?
- 子函数设定属性
superclass
为父函数,在当前类中所有自定义的函数赋给当前类的prototype
时,会通过superclass
找到父类的同名函数,这样执行子类的同名函数时,即为执行父类的同名函数一次,子类的同名函数一次。
- 子函数设定属性