解析 John Resig 在 Simple JavaScript Inheritance 代码的思路可以分为以下几个部分:
这是 John Resig 在 2008 年发布的一个 JavaScript 类继承的库,用来实现类的继承。
下面我们来逐行分析源代码实现:
首先,定义了一个匿名函数,并将其赋值给 Class 变量。
var Class = (function() {
// ...
})();
这里利用了函数声明的特性,定义了一个 IIFE,返回一个对象。
接下来就是对象的方法,其中,最重要的就是 extend 方法,这个方法有两个参数,第一个是一个对象,表示新的类的原型对象,第二个是一个对象,表示新的类的静态属性和方法。
// 添加方法和静态属性
Function.prototype.extend = function(prop) {
var _super = this.prototype;
function F() {};
F.prototype = _super;
var prototype = new F();
for(var name in prop) {
if(typeof prop[name] === "function" &&
typeof _super[name] === "function" &&
fnTest.test(prop[name])) {
prototype[name] = (function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]);
} else {
prototype[name] = prop[name];
}
}
function Class() {
if (this.init) {
this.init.apply(this, arguments);
}
}
Class.prototype = prototype;
Class.constructor = Class;
Class.extend = arguments.callee;
return Class;
};
在这个方法的内部中,核心部分就是以下这段代码:
var _super = this.prototype;
function F() {};
F.prototype = _super;
var prototype = new F();
这里定义了一个代理函数 F,这个函数的作用是一个中间代理层,可以用来保存原型对象 _super 的引用,从而避免对父类对象的修改。
接下来,将代理函数 F 的原型属性指向当前对象的原型属性,这样代理函数 F 就成为了一个新的对象,这个新的对象有原型属性继承自当前对象。
接着定义一个 for 循环,遍历新的类的原型对象上的属性,将方法保存至新的原型对象上。
其中,最重要的部分在于这样的一个条件判断:
typeof prop[name] === "function" &&
typeof _super[name] === "function" &&
fnTest.test(prop[name])
如果当前对象和父类对象上都拥有相同名称的方法,并且这个方法符合正则表达式 fnTest 规定的格式,则使用 closure 的思想实现闭包函数。
这里 closure 函数在 arguments 对象中做了一个小的修改,简单来说就是是利用 arguments 对象保存 this 的引用,并在函数返回的时候计算出方法的返回值。
对于非方法的属性,直接将属性保存到新的原型对象上。
最后,定义并返回一个改写过的函数 Class,用来定义新的类。
在 Class 中,还增加了一个新的方法 init,用来在对象创建时初始化对象的属性。
另外一个重要的功能就是函数中的 _super 属性,这个属性用来在子类中调用父类中相同名称的方法。
var _super = this._super || function() {};
这个属性也是利用了 closure 函数的思想,每一次将 _super 模拟为当前方法的上一个版本,避免对父类对象进行操作。
Class 这个对象是通过原型属性来定义的,因此可以用 new 关键字来创建新的对象。在对象创建的时候,可以传递参数给 init 方法初始化对象的属性。
示例 1:
// 父类
var Person = Class.extend({
init: function(name, age) {
this.name = name;
this.age = age;
},
sayName: function() {
console.log("name: " + this.name);
},
sayAge: function() {
console.log("age: " + this.age);
}
});
// 子类
var Student = Person.extend({
init: function(name, age, stuNo) {
this._super(name, age);
this.stuNo = stuNo;
},
sayStuNo: function() {
console.log("student number: " + this.stuNo);
}
});
// 创建父类对象
var p = new Person("Tom", 20);
console.log(p); // Person {name: "Tom", age: 20}
// 创建子类对象
var s = new Student("Jhon", 18, 20210001);
console.log(s); // Student {name: "Jhon", age: 18, stuNo: 20210001}
s.sayName(); // name: Jhon
s.sayAge(); // age: 18
s.sayStuNo(); // student number: 20210001
以上是一个简单 class 继承的示例,通过相同的名称的方法实现了类的继承。
示例 2:
var Animal = Class.extend({
makeSound: function() {
console.log("Animal sound...");
}
});
var Cat = Animal.extend({
makeSound: function() {
console.log("Mew...");
this._super();
}
});
var cat = new Cat();
cat.makeSound(); // Mew... \n Animal sound...
这是一个更加直观的 _super 方法的使用示例。通过先在子类中重写方法,再显示调用父类的方法实现了在子类方法中调用父类方法的使用。