- 浏览: 41522 次
- 性别:
- 来自: 上海->杭州
最新评论
-
ronalke:
...
(转)数据库查询速度慢的原因 -
halk:
常想,如果计算机室中国人发明的,会是什么样子?
关于用户与电脑交互方式的思考 -
faylai:
终于找到一篇能看懂的文章了。
(转)深入研究ReentrantLock(重入锁)之引出话题篇 -
xlongbuilder:
<div class="quote_title ...
(转)JavaScript继承详解(六) -
incredible:
我靠 原来都是转帖转帖还不标注作者和出处 鄙视你
(转)JavaScript继承详解(六)
在本章中,我们将分析Prototypejs中关于JavaScript继承的实现。 Prototypejs是最早的JavaScript类库,可以说是JavaScript类库的鼻祖。 我在几年前接触的第一个JavaScript类库就是这位,因此Prototypejs有着广泛的群众基础。 不过当年Prototypejs中的关于继承的实现相当的简单,源代码就寥寥几行,我们来看下。
早期Prototypejs中继承的实现
源码:
var Class = { // Class.create仅仅返回另外一个函数,此函数执行时将调用原型方法initialize create: function() { return function() { this.initialize.apply(this, arguments); } } }; // 对象的扩展 Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; };调用方式:
var Person = Class.create(); Person.prototype = { initialize: function(name) { this.name = name; }, getName: function(prefix) { return prefix + this.name; } }; var Employee = Class.create(); Employee.prototype = Object.extend(new Person(), { initialize: function(name, employeeID) { this.name = name; this.employeeID = employeeID; }, getName: function() { return "Employee name: " + this.name; } }); var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"
很原始的感觉对吧,在子类函数中没有提供调用父类函数的途径。
Prototypejs 1.6以后的继承实现
首先来看下调用方式:
// 通过Class.create创建一个新类 var Person = Class.create({ // initialize是构造函数 initialize: function(name) { this.name = name; }, getName: function(prefix) { return prefix + this.name; } }); // Class.create的第一个参数是要继承的父类 var Employee = Class.create(Person, { // 通过将子类函数的第一个参数设为$super来引用父类的同名函数 // 比较有创意,不过内部实现应该比较复杂,至少要用一个闭包来设置$super的上下文this指向当前对象 initialize: function($super, name, employeeID) { $super(name); this.employeeID = employeeID; }, getName: function($super) { return $super("Employee name: "); } }); var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"
这里我们将Prototypejs 1.6.0.3中继承实现单独取出来, 那些不想引用整个prototype库而只想使用prototype式继承的朋友, 可以直接把下面代码拷贝出来保存为JS文件就行了。
var Prototype = { emptyFunction: function() { } }; var Class = { create: function() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { addMethods: function(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } }; Object.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } Object.extend(Object, { keys: function(object) { var keys = []; for (var property in object) keys.push(property); return keys; }, isFunction: function(object) { return typeof object == "function"; }, isUndefined: function(object) { return typeof object == "undefined"; } }); Object.extend(Function.prototype, { argumentNames: function() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, bind: function() { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } }, wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } } }); Object.extend(Array.prototype, { first: function() { return this[0]; } });首先,我们需要先解释下Prototypejs中一些方法的定义。
- argumentNames: 获取函数的参数数组
function init($super, name, employeeID) { } console.log(init.argumentNames().join(",")); // "$super,name,employeeID"
- bind: 绑定函数的上下文this到一个新的对象(一般是函数的第一个参数)
var name = "window"; var p = { name: "Lisi", getName: function() { return this.name; } }; console.log(p.getName()); // "Lisi" console.log(p.getName.bind(window)()); // "window"
- wrap: 把当前调用函数作为包裹器wrapper函数的第一个参数
var name = "window"; var p = { name: "Lisi", getName: function() { return this.name; } }; function wrapper(originalFn) { return "Hello: " + originalFn(); } console.log(p.getName()); // "Lisi" console.log(p.getName.bind(window)()); // "window" console.log(p.getName.wrap(wrapper)()); // "Hello: window" console.log(p.getName.wrap(wrapper).bind(p)()); // "Hello: Lisi"
对这些函数有了一定的认识之后,我们再来解析Prototypejs继承的核心内容。
这里有两个重要的定义,一个是Class.extend,另一个是Class.Methods.addMethods。
var Class = { create: function() { // 如果第一个参数是函数,则作为父类 var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); // 子类构造函数的定义 function klass() { this.initialize.apply(this, arguments); } // 为子类添加原型方法Class.Methods.addMethods Object.extend(klass, Class.Methods); // 不仅为当前类保存父类的引用,同时记录了所有子类的引用 klass.superclass = parent; klass.subclasses = []; if (parent) { // 核心代码 - 如果父类存在,则实现原型的继承 // 这里为创建类时不调用父类的构造函数提供了一种新的途径 // - 使用一个中间过渡类,这和我们以前使用全局initializing变量达到相同的目的, // - 但是代码更优雅一点。 var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } // 核心代码 - 如果子类拥有父类相同的方法,则特殊处理,将会在后面详解 for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; // 修正constructor指向错误 klass.prototype.constructor = klass; return klass; } };
再来看addMethods做了哪些事情:
Class.Methods = { addMethods: function(source) { // 如果父类存在,ancestor指向父类的原型对象 var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); // Firefox和Chrome返回1,IE8返回0,所以这个地方特殊处理 if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); // 循环子类原型定义的所有属性,对于那些和父类重名的函数要重新定义 for (var i = 0, length = properties.length; i < length; i++) { // property为属性名,value为属性体(可能是函数,也可能是对象) var property = properties[i], value = source[property]; // 如果父类存在,并且当前当前属性是函数,并且此函数的第一个参数为 $super if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value; // 下面三行代码是精华之所在,大概的意思: // - 首先创建一个自执行的匿名函数返回另一个函数,此函数用于执行父类的同名函数 // - (因为这是在循环中,我们曾多次指出循环中的函数引用局部变量的问题) // - 其次把这个自执行的匿名函数的作为method的第一个参数(也就是对应于形参$super) // 不过,窃以为这个地方作者有点走火入魔,完全没必要这么复杂,后面我会详细分析这段代码。 value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); // 因为我们改变了函数体,所以重新定义函数的toString方法 // 这样用户调用函数的toString方法时,返回的是原始的函数定义体 value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } };上面的代码中我曾有“走火入魔”的说法,并不是对作者的亵渎, 只是觉得作者对JavaScript中的一个重要准则(通过自执行的匿名函数创建作用域) 运用的有点过头。
value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method);
其实这段代码和下面的效果一样:
value = ancestor[property].wrap(method);
我们把wrap函数展开就能看的更清楚了:
value = (function(fn, wrapper) { var __method = fn; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } })(ancestor[property], method);
可以看到,我们其实为父类的函数ancestor[property]通过自执行的匿名函数创建了作用域。 而原作者是为property创建的作用域。两则的最终效果是一致的。
我们对Prototypejs继承的重实现
分析了这么多,其实也不是很难,就那么多概念,大不了换种表现形式。
下面我们就用前几章我们自己实现的jClass来实现Prototypejs形式的继承。
// 注意:这是我们自己实现的类似Prototypejs继承方式的代码,可以直接拷贝下来使用 // 这个方法是借用Prototypejs中的定义 function argumentNames(fn) { var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function jClass(baseClass, prop) { // 只接受一个参数的情况 - jClass(prop) if (typeof (baseClass) === "object") { prop = baseClass; baseClass = null; } // 本次调用所创建的类(构造函数) function F() { // 如果父类存在,则实例对象的baseprototype指向父类的原型 // 这就提供了在实例对象中调用父类方法的途径 if (baseClass) { this.baseprototype = baseClass.prototype; } this.initialize.apply(this, arguments); } // 如果此类需要从其它类扩展 if (baseClass) { var middleClass = function() {}; middleClass.prototype = baseClass.prototype; F.prototype = new middleClass(); F.prototype.constructor = F; } // 覆盖父类的同名函数 for (var name in prop) { if (prop.hasOwnProperty(name)) { // 如果此类继承自父类baseClass并且父类原型中存在同名函数name if (baseClass && typeof (prop[name]) === "function" && argumentNames(prop[name])[0] === "$super") { // 重定义子类的原型方法prop[name] // - 这里面有很多JavaScript方面的技巧,如果阅读有困难的话,可以参阅我前面关于JavaScript Tips and Tricks的系列文章 // - 比如$super封装了父类方法的调用,但是调用时的上下文指针要指向当前子类的实例对象 // - 将$super作为方法调用的第一个参数 F.prototype[name] = (function(name, fn) { return function() { var that = this; $super = function() { return baseClass.prototype[name].apply(that, arguments); }; return fn.apply(this, Array.prototype.concat.apply($super, arguments)); }; })(name, prop[name]); } else { F.prototype[name] = prop[name]; } } } return F; };调用方式和Prototypejs的调用方式保持一致:
var Person = jClass({ initialize: function(name) { this.name = name; }, getName: function() { return this.name; } }); var Employee = jClass(Person, { initialize: function($super, name, employeeID) { $super(name); this.employeeID = employeeID; }, getEmployeeID: function() { return this.employeeID; }, getName: function($super) { return "Employee name: " + $super(); } }); var zhang = new Employee("ZhangSan", "1234"); console.log(zhang.getName()); // "Employee name: ZhangSan"
评论
3 楼
xlongbuilder
2009-08-19
incredible 写道
我靠 原来都是转帖
转帖还不标注作者和出处 鄙视你
转帖还不标注作者和出处 鄙视你
请看JavaScript继承详解(一) 已经注明出处了
另外每个转字 写的很清楚
2 楼
incredible
2009-08-17
我靠 原来都是转帖
转帖还不标注作者和出处 鄙视你
转帖还不标注作者和出处 鄙视你
1 楼
incredible
2009-08-17
I admire u
发表评论
-
(转)JavaScript继承详解(五)
2009-08-04 11:48 1186在本章中,我们将分析John Resig关于JavaScrip ... -
(转)JavaScript继承详解(四)
2009-08-04 11:45 1252在本章中,我们将分析Douglas Crockford关于Ja ... -
(转)JavaScript继承详解(三)
2009-08-04 11:43 1709var Employee = jClass(Person, { ... -
(转)JavaScript继承详解(二)
2009-08-04 11:41 1401这一章我们将会重点介绍JavaScript中几个重要的属性(t ... -
(转)JavaScript继承详解(一)
2009-08-04 11:35 1318面向对象与基于对象 ...
相关推荐
在本章中,我们将分析Prototypejs中关于JavaScript继承的实现。 Prototypejs是最早的JavaScript类库,可以说是JavaScript类库的鼻祖。 我在几年前接触的第一个JavaScript类库就是这位,因此Prototypejs有着广泛的...
JavaScript中的继承是面向对象编程的重要概念,它允许一个对象(子对象)获取另一个对象(父对象)的属性和方法,从而实现代码复用和多态性。JavaScript支持多种继承实现方式,包括以下四种: 1. **构造函数继承**...
在JavaScript编程中,继承是面向对象编程的核心概念之一,它允许开发者构建出层级化的对象结构。在JavaScript中实现继承有多种方法,其中构造函数和原型链的方式是较为传统的一种,但在实际应用中存在一些问题和陷阱...
Classical Inheritance in JavaScript。 Crockford是JavaScript开发社区最知名的...首先让我们看下使用Crockford式继承的调用方式: 注意:代码中的method、inherits、uber都是自定义的对象,我们会在后面的代码分析
// "ZhangSan" 在JavaScript中,继承是通过原型链机制实现的,这里的原型就是prototype属性。每个函数都有一个prototype属性,它指向一个对象,这个对象就是实例化该函数时,新创建的对象所继承的属性和方法的来源...
5. **寄生组合继承**:结合寄生继承和组合继承,避免了冗余的构造函数调用,通常被认为是JavaScript中实现继承的最佳实践。 6. **ES6的类继承**:ES6引入了`class`语法糖,使得JavaScript的继承看起来更像传统的...
本文将深入探讨JavaScript继承的实现方式,以及其中的问题和解决方案。 首先,我们来看混合方式的实现,这种方式结合了原型链和对象冒充。在JavaScript中,构造函数是用于创建特定类型对象的函数。例如,`Employee`...
这里我们将深入探讨JavaScript继承的特性以及实践应用。 首先,JavaScript的继承基于原型链。每个对象都有一个`__proto__`属性,指向创建它的构造函数的原型对象。当试图访问对象的一个属性时,JavaScript会沿着...
JavaScript 中的原型和继承详解 在 JavaScript 中,原型和继承是两个非常重要的概念。这篇文章将详细解释 JavaScript 中的原型和继承机制,并通过图文方式帮助读者更好地理解这些概念。 一、对象和类 在 ...
JavaScript,是一种广泛应用于Web开发的轻量级编程语言,它主要负责实现客户端的动态...总结,《JavaScript使用详解》这本书将深入讲解这些内容,帮助读者从基础到进阶全面掌握JavaScript,为Web开发打下坚实的基础。
JavaScript中的继承机制是其面向对象编程的一个核心特性。在JavaScript中,可以使用多种方式来实现继承,其中之一就是通过函数来模拟继承。本文将详细介绍如何利用函数实现JavaScript的继承,并结合属性特性和描述符...
本文将深入探讨JavaScript继承的实现,并分析其潜在的问题和解决方案。 首先,我们来看混合方式的实现,这是一种常见的继承策略,结合了原型链和构造函数继承。在JavaScript中,对象的属性和方法可以通过原型链进行...
资源名称:javascript闭包详解 中文word版 内容简介: Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C 的程序员来说是一个新的...
本文实例讲述了JavaScript继承与聚合。分享给大家供大家参考,具体如下: 一、继承 第一种方式:类与被继承类直接耦合度高 1. 首先,准备一个可以被继承的类(父类),例如 //创建一个人员类 function Person(name)...
本教程《最好的javascript学习教程-JavaScript使用详解》涵盖了以上所有内容,通过阅读和实践,你将能够熟练掌握JavaScript,并运用到实际的Web开发中去。无论你是初学者还是经验丰富的开发者,都能从中受益匪浅。