JavaScript继承详解(五)

本文深入剖析了John Resig的JavaScript继承实现——simplejavascriptinheritance。详细介绍了其优雅的调用方式及内部实现机制,并对比分析了自定义jclass的改造过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


======================================================
注:本文源代码点此下载
======================================================

文章截图 - 更好的排版

在本章中,我们将分析john resig关于javascript继承的一个实现 - simple javascript inheritance

john resig作为jquery的创始人而声名在外。是《pro javascript techniques》的作者,而且resig将会在今年秋天推出一本书《javascript secrets》,非常期待。

调用方式

调用方式非常优雅:

注意:代码中的class、extend、_super都是自定义的对象,我们会在后面的代码分析中详解。

var person = class.extend({

// init是构造函数

init: function(name) {

this.name = name;

},

getname: function() {

return this.name;

}

});

// employee类从person类继承

var employee = person.extend({

// init是构造函数

init: function(name, employeeid) {

//在构造函数中调用父类的构造函数

this._super(name);

this.employeeid = employeeid;

},

getemployeeid: function() {

return this.employeeid;

},

getname: function() {

//调用父类的方法

return "employee name: " + this._super();

}

});

var zhang = new employee("zhangsan", "1234");

console.log(zhang.getname());// "employee name: zhangsan"

说实话,对于完成本系列文章的目标-继承-而言,真找不到什么缺点。方法一如jquery一样简洁明了。

代码分析

为了一个漂亮的调用方式,内部实现的确复杂了很多,不过这些也是值得的 - 一个人的思考带给了无数程序员快乐的微笑 - 嘿嘿,有点肉麻。

不过其中的一段代码的确迷惑我一段时间:

fntest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

我曾在几天前的博客中写过一篇文章专门阐述这个问题,有兴趣可以向前翻一翻。

// 自执行的匿名函数创建一个上下文,避免引入全局变量

(function() {

// initializing变量用来标示当前是否处于类的创建阶段,

// - 在类的创建阶段是不能调用原型方法init的

// - 我们曾在本系列的第三篇文章中详细阐述了这个问题

// fntest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/)

// - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况

// - 不过我对ie7.0,chrome2.0,ff3.5进行了测试,此测试都返回true。

// - 所以我想这样对fntest赋值大部分情况下也是对的:fntest = /\b_super\b/;

var initializing = false, fntest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;

// 基类构造函数

// 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.class

this.class = function() { };

// 继承方法定义

class.extend = function(prop) {

// 这个地方很是迷惑人,还记得我在本系列的第二篇文章中提到的么

// - this具体指向什么不是定义时能决定的,而是要看此函数是怎么被调用的

// - 我们已经知道extend肯定是作为方法调用的,而不是作为构造函数

// - 所以这里this指向的不是object,而是function(即是class),那么this.prototype就是父类的原型对象

// - 注意:_super指向父类的原型对象,我们会在后面的代码中多次碰见这个变量

var _super = this.prototype;

// 通过将子类的原型指向父类的一个实例对象来完成继承

// - 注意:this是基类构造函数(即是class)

initializing = true;

var prototype = new this();

initializing = false;

// 我觉得这段代码是经过作者优化过的,所以读起来非常生硬,我会在后面详解

for (var name in prop) {

prototype[name] = typeof prop[name] == "function" &&

typeof _super[name] == "function" && fntest.test(prop[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]) :

prop[name];

}

// 这个地方可以看出,resig很会伪装哦

// - 使用一个同名的局部变量来覆盖全局变量,很是迷惑人

// - 如果你觉得拗口的话,完全可以使用另外一个名字,比如function f()来代替function class()

// - 注意:这里的class不是在最外层定义的那个基类构造函数

function class() {

// 在类的实例化时,调用原型方法init

if (!initializing && this.init)

this.init.apply(this, arguments);

}

// 子类的prototype指向父类的实例(完成继承的关键)

class.prototype = prototype;

// 修正constructor指向错误

class.constructor = class;

// 子类自动获取extend方法,arguments.callee指向当前正在执行的函数

class.extend = arguments.callee;

return class;

};

})();

下面我会对其中的for-in循环进行解读,把自执行的匿名方法用一个局部函数来替换, 这样有利于我们看清真相:

(function() {

var initializing = false, fntest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;

this.class = function() { };

class.extend = function(prop) {

var _super = this.prototype;

initializing = true;

var prototype = new this();

initializing = false;

// 如果父类和子类有同名方法,并且子类中此方法(name)通过_super调用了父类方法

// - 则重新定义此方法

function fn(name, fn) {

return function() {

// 将实例方法_super保护起来。

// 个人觉得这个地方没有必要,因为每次调用这样的函数时都会对this._super重新定义。

var tmp = this._super;

// 在执行子类的实例方法name时,添加另外一个实例方法_super,此方法指向父类的同名方法

this._super = _super[name];

// 执行子类的方法name,注意在方法体内this._super可以调用父类的同名方法

var ret = fn.apply(this, arguments);

this._super = tmp;

// 返回执行结果

return ret;

};

}

// 拷贝prop中的所有属性到子类原型中

for (var name in prop) {

// 如果prop和父类中存在同名的函数,并且此函数中使用了_super方法,则对此方法进行特殊处理 - fn

// 否则将此方法prop[name]直接赋值给子类的原型

if (typeof prop[name] === "function" &&

typeof _super[name] === "function" && fntest.test(prop[name])) {

prototype[name] = fn(name, prop[name]);

} else {

prototype[name] = prop[name];

}

}

function class() {

if (!initializing && this.init) {

this.init.apply(this, arguments);

}

}

class.prototype = prototype;

class.constructor = class;

class.extend = arguments.callee;

return class;

};

})();

写到这里,大家是否觉得resig的实现和我们在第三章一步一步实现的jclass很类似。 其实在写这一系列的文章之前,我已经对prototype、mootools、extjs、 jquery-simple-inheritance、crockford-classical-inheritance这些实现有一定的了解,并且大部分都在实际项目中使用过。 在第三章中实现jclass也参考了resig的实现,在此向resig表示感谢。

下来我们就把jclass改造成和这里的class具有相同的行为。

我们的实现

将我们在第三章实现的jclass改造成目前john resig所写的形式相当简单,只需要修改其中的两三行就行了:

(function() {

// 当前是否处于创建类的阶段

var initializing = false;

jclass = function() { };

jclass.extend = function(prop) {

// 如果调用当前函数的对象(这里是函数)不是class,则是父类

var baseclass = null;

if (this !== jclass) {

baseclass = this;

}

// 本次调用所创建的类(构造函数)

function f() {

// 如果当前处于实例化类的阶段,则调用init原型函数

if (!initializing) {

// 如果父类存在,则实例对象的baseprototype指向父类的原型

// 这就提供了在实例对象中调用父类方法的途径

if (baseclass) {

this._superprototype = baseclass.prototype;

}

this.init.apply(this, arguments);

}

}

// 如果此类需要从其它类扩展

if (baseclass) {

initializing = true;

f.prototype = new baseclass();

f.prototype.constructor = f;

initializing = false;

}

// 新创建的类自动附加extend函数

f.extend = arguments.callee;

// 覆盖父类的同名函数

for (var name in prop) {

if (prop.hasownproperty(name)) {

// 如果此类继承自父类baseclass并且父类原型中存在同名函数name

if (baseclass &&

typeof (prop[name]) === "function" &&

typeof (f.prototype[name]) === "function" &&

/\b_super\b/.test(prop[name])) {

// 重定义函数name -

// 首先在函数上下文设置this._super指向父类原型中的同名函数

// 然后调用函数prop[name],返回函数结果

// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,

// 此函数中可以应用此上下文中的变量,这就是闭包(closure)。

// 这是javascript框架开发中常用的技巧。

f.prototype[name] = (function(name, fn) {

return function() {

this._super = baseclass.prototype[name];

return fn.apply(this, arguments);

};

})(name, prop[name]);

} else {

f.prototype[name] = prop[name];

}

}

}

return f;

};

})();

// 经过改造的jclass

var person = jclass.extend({

init: function(name) {

this.name = name;

},

getname: function(prefix) {

return prefix + this.name;

}

});

var employee = person.extend({

init: function(name, employeeid) {

//调用父类的方法

this._super(name);

this.employeeid = employeeid;

},

getemployeeidname: function() {

// 注意:我们还可以通过这种方式调用父类中的其他函数

var name = this._superprototype.getname.call(this, "employee name: ");

return name + ", employee id: " + this.employeeid;

},

getname: function() {

//调用父类的方法

return this._super("employee name: ");

}

});

var zhang = new employee("zhangsan", "1234");

console.log(zhang.getname());// "employee name: zhangsan"

console.log(zhang.getemployeeidname()); // "employee name: zhangsan, employee id: 1234"

just cool!


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值