本编参考:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 系列,总结如下
一.几个重要关键字
1.this —— 指当前的对象,若在全局范围,则指当前页面对象window,如果是函数中使用this,则指的是调用这个函数的对象。firebug一下demo:
function sayHi() { console.debug(this); } var obj = { sayHi: sayHi, sayHi2: function() { return function() { console.debug(this); } } }; sayHi(); // 结果为window对象 obj.sayHi(); // 结果为obj对象 obj.sayHi2()(); // 结果为window对象
解析:在javascript当中,一切皆为对象,function也是,所以可以看到全局函数sayHi可以像变量一般pass给obj的sayHi属性。三个函数调用的,前两个不用多解析,看第三个,其实等价于下面写法:
var f = obj.sayHi2(); f();
sayHi2函数返回一个函数对象,赋予变量f,f就是个函数了,然后 f 被window调用执行,如此就这么回事了。
再看一个demo:
function TestThis () { this.oham = 'oham'; console.debug(this); } TestThis(); // window对象 new TestThis(); // TestThis 对象实例本身
对于TestThis();的结果不奇特,而new TestThis();之神奇在于new,其内部机制在下就不懂了...只可以看出,通过new XXX();这样调用,XXX方法中的this就指向XXX构造出的对象实例本身了。
2.apply与call
接着上面的例子,介绍apply 与 call,它们均是全局函数,归Function所有(Function属于javascript内部对象,不是Function的对象是没有这两个方法的)。 接上文的demo:
... obj.sayHi2()(); //结果为window对象 obj.sayHi2().apply(obj); // 结果为obj对象 obj.sayHi2().call(obj); // 结果为obj对象
apply 与 call 的 作用是改变函数中this的指向,即使函数看起来好像被谁调用一样,其作用域也是那个谁的。
apply 与 call 的区别,仅仅是参数定义不同,请看如下demo:
function ml(me, mm) { console.log(this); if( this === window ) { console.log(me + ',' + mm + ' not possible in public'); }else { console.log(me + ',' + mm + ' so happy'); } } var hotel = {}; ml('me', 'mm'); ml.apply(hotel, ['me', 'mm']); ml.call(hotel, 'me', 'mm');
请实践一下,代码,便知知道,apply的定义的参数列表是个数组,call是参数列(估计是function call(obj, params...))。
**至此深觉有必要介绍javascript中闭包的概念。闭包,指的是语法上表示包含不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
demo(以下摘自w3cschool):
var sMessage = "hello world"; function sayHelloWorld() { alert(sMessage); } sayHelloWorld();
在上面这段代码中,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。
再看看自己的一个demo:
var outSide = 'madom'; var room = { mm : 'mm', watch: function(me) { return function () { if(this.outSide) { //尝试 console.debug(me + ' can watch ' + this.outSide); }else { console.debug(me + ' can\'t watch outSide, but never mind.'); } if(this.mm) { //尝试 console.debug(me + ' can watch ' + this.mm); }else { console.debug(me + ' can\'t watch mm, not happy'); } } }, touch: function (me) { if(this.outSide) { //尝试 console.debug(me + ' can touch ' + this.outSide + ', but won\'t do that'); }else { console.debug(me + ' can\'t touch outSide, but never mind.'); } if(this.mm) { //尝试 console.debug(me + ' can touch ' + this.mm + ', so cool...'); }else { console.debug(me + ' can\'t touch mm, not happy'); } } }; var watchFunc = room.watch('I'); //全局调用 watchFunc(); console.log('-------------------------------------'); //room范围调用 watchFunc.apply(room); console.log('============================================'); //room范围调用 room.touch('I'); console.log('----------------------------------------'); //全局调用 room.touch.apply(this, ['I']);
结果为:
"I can watch madom"
"I can't watch mm, not happy"
"-------------------------------------"
"I can't watch outSide, but never mind."
"I can watch mm"
"============================================"
"I can't touch outSide, but never mind."
"I can touch mm, so cool..."
"----------------------------------------"
"I can touch madom, but won't do that"
"I can't touch mm, not happy"
这个结果好解析,this为谁调用就是谁,接着请尝试把demo中标有//尝试的if 语句中的"this."删除,
情况会如下:
"I can watch madom"
"Uncaught ReferenceError: mm is not defined (line 15)"
执行到watch函数的if(mm) 这句出错了,无论apply部apply结果都一样,原因具体我就不清楚了,这里只是证实了,对于一个变量如name, name 并不是等价于this.name(跟java大不同) ,JS的解释程序不会为你做这个,当函数被调用的时候,它只会先从当前函数范围去找,若无命中变量,则直接到全局范围去找。可以把mm部分的代码去掉,只留outSide的,结果是无论apply与否,结果都能访问的变量outSide。
3.prototype——对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例(w3c定义)。
prototype本质上还是一个JavaScript对象,个人觉得上述定义不太妥当,实际上prototype感觉更像对象的一个模板,而非实例, 并且每个函数都有一个默认的prototype属性。 以demo为证:
var room = { mm: 'mm', watch: function () { } }; console.log(room.prototype); // undefined console.log(room.valueOf()); // room object
var outSide = 'madom'; function Room () { this.mm = 'mm'; } console.log(Room.prototype); //Room{} console.log(Room.mm); // undefined console.log(new Room().prototype); //undefined console.log(new Room().mm); // mm console.log(new Room().valueOf()); // Room {mm: "mm"} console.log(Room.valueOf()); // function Room(){this.mm = 'mm';}解析,当声明 function Room的时候,Room便是javascript的固有对象Function,Function 有自己的一个空的prototype,同时也说明Room.mm为什么是undefined,因为Room此时仅仅是个Function对象,javascript的Function对象当中必定没有mm的定义。
function Room () { this.mm = 'mm'; } var room = { mm : 'mm' }; console.log(new Room().valueOf()); // Room {mm: "mm"} console.log(room.valueOf()); // Object {mm: "mm"}可见,Room 是一个类型为Room的实例, 而room是一个Object的实例。当然此时两者的prototype都为空,以上的demo我拿mm属性来测试,是为了说明prototype本质也是个对象,可以像个普通的对象属性那般玩,只是javascript会对名为prototype的属性特别对待,那prototype到底为何用?demo:
function Room () { this.mm = 'mm'; } Room.prototype.watch = function(){}; // Room 本身为Function,有个空的prototype属性,js解析器执行new 操作的时候会特别对待prototype属性 var room = { mm : 'mm' }; room.prototype.watch = function() {}; //报错 room 本身为Object 类型对象,不具备prototype属性的定义 console.log(new Room()); // Room {mm: "mm", watch: function}从上面的结果可以看出,prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例;对于Object,充其量只是个普通的属性。
function Room () { this.mm = 'mm'; } Room.prototype.watch = function(){}; var room = { mm : 'mm' }; console.log(new Room().constructor == Room); // true console.log(room.constructor == Object); // true当constructor 与 prototype 相遇, demo:
function Room () { this.mm = 'mm'; } Room.prototype.watch = function(){}; console.log(new Room().constructor === Room); // Room对象的实例的构造为Room无疑 console.log(Room.prototype.constructor === Room);// 构造函数Room的prototype的构造函数是自己本身 console.log(new Room().constructor.prototype.constructor === Room); /合并上两句得此结论现在修改demo如下:
function Room () { this.mm = 'mm'; } Room.prototype = { //改了这里 watch: function() {} }; console.log(new Room().constructor === Room); //false , Room的对象实例的构造不再是Room了 console.log(Room.prototype.constructor === Room); // 不再是Room console.log(new Room().constructor.prototype.constructor === Room); // false console.log(new Room().constructor === Object); //true console.log(Room.prototype.constructor === Object); //true console.log(new Room().constructor.prototype.constructor === Object); //true原因是修改之后的Room的prototype的声明形式等同于:
Room.prototype = new Object({ watch: function() {} });如此看来,prototype的构造当然是Object了,这就造成用Room构造的对象实例的构造也成了Object了。这样引起的问题倒没什么,只是prototype作为一个对象模板,应用在实现继承的时候,其构造却不对应类构造函数本身,这有点说不过去。
Room.prototype.constructor = Room; // 个人猜测是子类对象调用instanceOf 时能够有正确的结果demo:
function Room () { this.mm = 'mm'; } Room.prototype = { watch: function() {} }; Room.prototype.constructor = Room; // 重新覆盖Room.prototype.constructor console.log(new Room().constructor === Room); //true console.log(Room.prototype.constructor === Room); //true console.log(new Room().constructor.prototype.constructor === Room); //true console.log(new Room().constructor === Object); //false console.log(Room.prototype.constructor === Object); //false console.log(new Room().constructor.prototype.constructor === Object); //false console.log(new Room() instanceof Room ); //无论覆盖Room.prototype.constructor与否, 结果都为true
//定义一个父类 Huamn function Human(name, gender) { this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); } Human.prototype = { ml: function () { if(this.gender == 'male'){ console.log(this.name + ' is feeling hign'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }; //继承 父类Huamn,定义一个Superman类 function Superman(name, gender) { this.name = name; this.gender = gender; } //为了把prototype中的一切以及Huamn的一些属性都继承过来(如skills),因而不是Superman.prototype = Human.prototype // 那为什么不都在prototype把属性定义完?这里做个mark,回头给你看看 Superman.prototype = new Human(); Superman.prototype.constructor = Superman; Superman.prototype.fly = function () { console.log(this.name + ' is flying'); } var sMM = new Superman('mm', 'female'); sMM.ml(); //调用了Human的ml方法 sMM.fly(); //调用子类Superman的方法先说明为什么不都在prototype把属性定义完,看看下面修改后的demo:
//定义一个父类 Huamn function Human(name, gender) { this.name = name; this.gender = gender; //this.skills = new Array('talk', 'walk', 'sleep'); } Human.prototype = { ml: function () { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } }, skills: new Array('talk', 'walk', 'sleep') // 在prototype中定义skills }; //继承 父类Huamn,定义一个Superman类 function Superman(name, gender) { this.name = name; this.gender = gender; } Superman.prototype = new Human(); Superman.prototype.constructor = Superman; Superman.prototype.fly = function () { console.log(this.name + ' is flying'); } var sMe = new Superman('me', 'male'); var hMM = new Human('mm', 'female'); console.log(sMe.skills); // "talk", "walk", "sleep" console.log(hMM.skills); // "talk", "walk", "sleep" sMe.skills.push('fly'); console.log(sMe.skills); // "talk", "walk", "sleep", "fly" console.log(hMM.skills); //"talk", "walk", "sleep", "fly" ,这就不对了这里只对sMe实例的skills push了一个'fly',但hMM的skills却同时多了'fly',这样是不对的。所谓对象模板,即所有的实例都共享这一模板,而且本身又是个对象,于是就所以然了。
- Superman.prototype = new Human(); 实属不妥,我构建Superman的类,却先实例化一个Huamn对象,如此而然,当new Superman()的时候,实例sMe里会有两套this.name ,this.gender——因为Superman.prototype = new Human(),之后,等价于:
function Superman(name, gender){ this.name = name; this.gender = gender; prototype = function Human() { this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); ... } }
所以可以预言,即便Human的skills不放在prototype中,对于两个不同的Superman实例仍会出现demo2 的问题,demo://定义一个父类 Huamn function Human(name, gender) { this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); } Human.prototype = { ml: function () { if(this.gender == 'male'){ console.log(this.name + ' is feeling hign'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }; //继承 父类Huamn,定义一个Superman类 function Superman(name, gender) { this.name = name; this.gender = gender; } Superman.prototype = new Human(); Superman.prototype.constructor = Superman; Superman.prototype.fly = function () { console.log(this.name + ' is flying'); } var sMe = new Superman('me', 'male'); var hMM1 = new Human('mm1', 'female'); var hMM2 = new Superman('mm2', 'female'); console.log(sMe); // "talk", "walk", "sleep" console.log(hMM1.skills); // "talk", "walk", "sleep" console.log(hMM2.skills); // "talk", "walk", "sleep" sMe.skills.push('fly'); //sMe超人的skills才包含fly console.log(sMe.skills); // "talk", "walk", "sleep", "fly" console.log(hMM1.skills); //"talk", "walk", "sleep" console.log(hMM2.skills); //"talk", "walk", "sleep", "fly" ,这就不对了
- 感觉很挫,说是继承,但Superman需要知道太多Human的细节,感觉还不如重新单独搞个Superman,然后拿Human的copy 来的优雅。
改良版:必须解决原始版的Superman.prototype = new Human();问题,然后封装继承的细节:
// 将继承的细节封装到一个全局自定义函数Class // 用法是: var Human = Class({inti:function(name, gender){...}, ml:function(){...}, ...}); // 即inti是必须要得,初始化属性用的 function Class(parentCls, props) { if( (typeof parentCls) === 'object' ) { // 创建类构造函数时的情形,而非继承一个类 props = parentCls; parentCls = null; // 把parentCls为null做成一个flag,使得下面的处理为创建类,而非继承类 } this._initCls = false; //无论是构建类还是继承类,Class终究要返回一个类的构造函数 //cFunc将作为最终的返回值 function cFunc() { if( !_initCls && this.init ) { if(parentCls) this.baseprototype = parentCls.prototype; //拿着父类的prototype,有方无便 // _initCls用作继承时的情形,避免init分别被父子类调用两次 // 因为实现继承需要cFunc.prototype = new parentCls(); // 当new YourClass()去构建类对象实例时,无疑会造成上述原始版中提到的属性重复以及prototype定义属性问题,这里设定为当真正new cFunc()的时候才调用init方法 // 注意 无论parentCls 还是YourCls,本质都是这里的有Class返回的cFunc this.init.apply(this, arguments); } } if( parentCls ){ //从parentCls 继承一切, 所以设_initCls 为false避免下面new parentCls()时执行parentCls的init函数 this._initCls = true; cFunc.prototype = new parentCls(); cFunc.constructor = cFunc; //把_initCls设置false,当真正调用new cFunc();的时候才调用init函数 //因为只用那时候才能执行YourCls自己的init函数 this._initCls = false; } //配置cFunc函数,把props的一切传入cFunc for( var name in props ) { if( props.hasOwnProperty(name) ) { //覆盖父类parentCls的同名函数 if( parentCls && (typeof props[name]) === 'function' && (typeof cFunc.prototype[name]) === 'function' ) { cFunc.prototype[name] = (function(name, fn) { //这是闭包用法,返回一个函数 return function() { this.base = parentCls.prototype[name]; // 存储父类的同名函数,相当于java的super(),供子类调用父类方法用 return fn.apply(this, arguments); // 调用子类自己定义的方法, 这里用return是考虑到有些方法是有返回值得 } })(name, props[name]); } else { cFunc.prototype[name] = props[name]; } } } return cFunc; } // ----------------测试:创建一个类Huamn,一个子类Superman-------------------- var Human = Class({ init: function(name, gender){ this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); }, ml: function() { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }); var Superman = Class(Human, { pangzi: 'red', //定义一个变量胖次(内裤),默认为红色,次变量将放入prototype中 // 此处没定义init方法,当new Superman()的时候会调用父类的,把name跟gender设置好 ml: function() { this.base(); console.log('Power up'); }, fly: function() { console.log('fly up high'); } }); var sMe = new Superman('me', 'male'); var hMM = new Human('mm', 'female'); var sMe2 = new Superman('me2', 'male'); console.log(sMe); console.log(hMM); sMe.skills.push('fly'); console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"] console.log(sMe2.skills); // ["talk", "walk", "sleep"] console.log(hMM.skills); // ["talk", "walk", "sleep"] sMe.pangzi = 'none'; //脱掉 console.log(sMe.pangzi); // none console.log(sMe2.pangzi); // red sMe.ml(); hMM.ml();整理一下思路,1.根据传入的参数判断是创建类还是继承创建类。
2. 定义初始的类构造函数cFunc —— 可以理解为一个未成型的胚胎,里面主要是配置使得真正被调用是调用传进的init方法去初始化属性值
3. 若是继承操作,通过形如Subxxx.prototype = new Superxxx(); 将父类的一切传给子类
4. 配置子类自己定义的属性以及方法。
注意一点,看看上面的内裤(pangzi),变量,没有将其放入init方法去初始化,那么它将藏入prototype中,上文提到,prototype的非方法属性问题,(好比java的一个静态变量),但上面测试的我把它脱了,按道理sMe2的也会脱了才对。。。若现在把pangzi的定义改为如下:
pangzi: {color:'red'},
...
//测试代码改为
sMe.pangzi.color = 'none';
console.log(sMe.pangzi); //none
console.log(sMe2.pangzi); //none
这里我估计原因是javascript当中对于基本数据类型的处理方式,回顾上文的skills数组,也是个Object,也是出问题。具体的在下是不懂了。
改良版有两个问题:
- 引入了全局变量_initCls。
- 请看上面一段代码:
cFunc.prototype[name] = (function(name, fn) { //这是闭包用法,返回一个函数 return function() { this.base = parentCls.prototype[name]; // 存储父类的同名函数,相当于java的super(),供子类调用父类方法用 return fn.apply(this, arguments); // 调用子类自己定义的方法, 这里用return是考虑到有些方法是有返回值得 } })(name, props[name]);
注意this.base,当子类调用父类的同名方法的时候,this指的是具体的子类对象,即,每调用一下父类同名方法,this.base就指向父类的同名方法了,在下不清楚javascript有无并发机制,或者某些js框架有无,若有,则这种写法就不行了,若无,则就调用结果而言,并无大碍,但总觉有点扯蛋。
现在介绍优雅版,解决改良版的两个问题,先看看是如何调用的:
var Human = Class.extends({ init: function(name, gender){ this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); }, ml: function() { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }); var Superman = Human.extends({ pangzi: 'red', ml: function() { this._super(); console.log('Power up'); }, fly: function() { console.log('fly up high'); } });
优雅版的实现(John Resig关于JavaScript继承的一个实现,John Resig,jQuery的创始人)
(function() {// js 一加载便执行该方法,即方法中的this引用的是window对象,这样做主要是运用闭包把改良版中的全局变量消除 var _initCls = false; // fnTest为一正则表达式,用于判断子类中有无对父类的引用 var fnTest; if( /xyz/.test(function(){ xyz; }) ) { //先看看浏览器对test的支持如何 fnTest = /\b_super\b/; }else { alert('Sorry, Web Browser not support.'); } this.Class = function() {}; //此实现优雅之所在 //这里看看上文的调用demo便知Class指的是父类,所以在调用的extends方法中,this指的是Class,即父类 Class.extends = function (props){ var _super = this.prototype; _initCls = true; var prototype = new this(); _initCls = false; for( var name in props ) { // 判断当前方法是否对父类的同名方法有覆盖 if ( (typeof props[name]) === 'function' && (typeof _super[name]) === 'function' && fnTest.test(props[name]) ) { prototype[name] = (function(name, fn) { return function() { //此处先把_super缓存起来,因为这里要执行子类的父类同名方法, //于是在fn.apply之前,把this._super先指向父类的同名方法, //如此就可以在子类的方法体内使用this._super()去调用父类的方法 var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); //当方法执行完毕,马上把this._super设置回原始值 // 但这样并无完全解决改良版中的问题2,要是js将来引进并发机制,问题依旧, //只是,比起改良版中的做法,这里就不扯蛋了 this._super = tmp; return ret; } })(name, props[name]); } else { prototype[name] = props[name]; } } //类的构造函数 function ClsFunc() { if( !_initCls && this.init ) this.init.apply(this, arguments); } ClsFunc.prototype = prototype; ClsFunc.constructor = ClsFunc; // 子类自动获取extends方法,arguments.callee指向当前正在执行的函数,即Class.extends ClsFunc.extends = arguments.callee; return ClsFunc; }; })(); //------------------------------测试demo---------------------------------------- var Human = Class.extends({ init: function(name, gender){ this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); }, ml: function() { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }); var Superman = Human.extends({ pangzi: 'red', ml: function() { this._super(); console.log('Power up'); }, fly: function() { console.log('fly up high'); } }); var sMe = new Superman('me', 'male'); var hMM = new Human('mm', 'female'); var sMe2 = new Superman('me2', 'male'); console.log(sMe); console.log(hMM); sMe.skills.push('fly'); console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"] console.log(sMe2.skills); // ["talk", "walk", "sleep"] console.log(hMM.skills); // ["talk", "walk", "sleep"] sMe.pangzi = 'none'; //脱掉 console.log(sMe.pangzi); // none console.log(sMe2.pangzi); // red sMe.ml(); hMM.ml();
关于优雅版如何除掉改良版中的全局变量在此作简述,首先看看下面一段demo:
//这里的test方法的局部变量_init,作用范围为test方法体,包括retFunc,执行retFunc方法的时候,javascript解析器会先从retFunc方法体找_init,找不到,再从初始化retFunc的作用域范围找(即,test(),而非function test(){...}) function test(p) { var _init = false; if(p) { //若有参数传入,设_init为true _init = true; } function retFunc() { //运用闭包,使之可以使用_init变量 if( _init == true ) { console.log('log me true'); }else { console.log('log me false'); } } return retFunc; } function objFunc() { var _inti = false; function ownRet() { if( _inti == true ) { console.log('log me true'); }else { console.log('log me false'); } } return ownRet; }; var ret = test(); ret(); // log me false, 此时ret的初始化作用域为test(), 域中_inti为false var ret2 = test('3'); //log me true, 此时ret的初始化作用域为test('3'), 域中_inti为true ret2(); objFunc.ret = ret2; //log me true,方法亦对象,ret2致死其初始化作用域都为test('3'),干爹可以变,生爹无法变 objFunc.ret(); objFunc()(); //log me false, 此处执行的是objFunc的ownRet,初始化作用域为objFunc()
现在可以回头理解优雅版如何解决改良版的问题1了。
其实可以根据优雅版的实现,对改良版进行稍稍的修改,推出改良版2:
(function () { //js加载即启动,参照优雅版的 this.Class = function(){}; //照搬优雅的核心 var _initCls = false; // 不再是全局变量 Class.extends = function(props){ //将原来的类函数构造逻辑移入extends中 var parentCls = null; if( this !== Class ) { parentCls = this; } function cFunc() { if( !_initCls && this.init ) { if( parentCls ) this.baseprototype = parentCls.prototype; this.init.apply(this, arguments); } } cFunc.extends = arguments.callee; if( parentCls ) { _initCls = true; cFunc.prototype = new parentCls(); cFunc.prototype.constructor = cFunc; _initCls = false; } for( var name in props ) { if( props.hasOwnProperty(name) ) { if( parentCls && (typeof props[name]) === 'function' && (typeof cFunc.prototype[name]) === 'function' && /\b_super\b/.test(props[name]) ) { //参照优雅版的逻辑 cFunc.prototype[name] = (function(name, fn) { return function() { this._super = parentCls.prototype[name]; //参照优雅版的逻辑 var ret = fn.apply(this, arguments); this._super = null; return ret; } })(name, props[name]); } else { cFunc.prototype[name] = props[name]; } } } return cFunc; }; })(); //------------------------------测试demo---------------------------------------- var Human = Class.extends({ init: function(name, gender){ this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); }, ml: function() { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }); var Superman = Human.extends({ pangzi: 'red', ml: function() { this._super(); console.log('Power up'); }, fly: function() { console.log('fly up high'); } }); var sMe = new Superman('me', 'male'); var hMM = new Human('mm', 'female'); var sMe2 = new Superman('me2', 'male'); console.log(sMe); console.log(hMM); sMe.skills.push('fly'); console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"] console.log(sMe2.skills); // ["talk", "walk", "sleep"] console.log(hMM.skills); // ["talk", "walk", "sleep"] sMe.pangzi = 'none'; //脱掉 console.log(sMe.pangzi); // none console.log(sMe2.pangzi); // red sMe.ml(); hMM.ml();
最后,尝试理解库类级源码对继承的实现——源自Prototypejs
先看调用方式:
var Human = Class.create({ initialize: function(name, gender){ this.name = name; this.gender = gender; this.skills = new Array('talk', 'walk', 'sleep'); }, ml: function() { if(this.gender == 'male'){ console.log(this.name + ' is feeling high'); }else if(this.gender == 'female' ){ console.log(this.name + ' is screaming'); }else{ console.log('-_-!...'); } } }); var Superman = Class.create(Human, { pangzi: 'red', ml: function($super) { //此处将$super作为父类方法对象引用,然后当成参数传入子类同名方法体内 $super(); // 于是就可以这样调用父类的方法了 console.log('Power up'); }, fly: function() { console.log('fly up high'); } }); var sMe = new Superman('me', 'male'); var hMM = new Human('mm', 'female'); var sMe2 = new Superman('me2', 'male'); console.log(sMe); console.log(hMM); sMe.skills.push('fly'); console.log(sMe.skills); // ["talk", "walk", "sleep", "fly"] console.log(sMe2.skills); // ["talk", "walk", "sleep"] console.log(hMM.skills); // ["talk", "walk", "sleep"] sMe.pangzi = 'none'; //脱掉 console.log(sMe.pangzi); // none console.log(sMe2.pangzi); // red sMe.ml(); hMM.ml();
看看其继承的实现方式:
var Prototype = { emptyFunction: function() { } }; // 用于把参数转换为数组形式 function $A(iterable) { if( !iterable ) return []; if( iterable.toArray ) return iterable.toArray(); var length = iterable.length || 0; var results = new Array(length); while ( length-- ) results[length] = iterable[length]; return results; } // 直接将source的own properties复制给destination Object.extend = function(destination, source) { for( var property in source ) destination[property] = source[property]; return destination; }; // 定义一组通用方法,类比java的Object类的equals,hashCode 等方法 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"; }, // 判断对象undifined isUndefined: function(object) { return typeof object == "undefined"; } }); Object.extend(Function.prototype, { //此方法运用正则表达式抽取function的参数名,具体: // 对于regexp: /^[\s\(]*function[^(]*\(([^\)]*)\)/,match出来一个数组, // 下标为1的就是function(param1, param2, ...) 中的"param1, param2, ...“字符串 // .replace(/\s+/g, ''), 将字符串的空白字符除掉,最后根据","split成数组 argumentNames: function() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, // 此方法用于,举例,将方法meFunc通过 meFunc.bind(mmSession); // 邦定到mmSession去准备执行,与apply类似,不过这里只拿第一个参数作邦定 // 与apply不一样的是,这里将方法对象返回,而不会执行_method bind: function() { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var _method = this; var args = $A(arguments); var object = args.shift(); //返回一个方法对象 return function() { // 这里为何 args.concat($A(arguments))? 只因此时此处是处于一个方法对象的闭包当中, // 所以此$A(arguments) 不同于上面的$A(arguments), 这里是当方法对象被执行时的参数 return _method.apply(object, args.concat($A(arguments))); } }, // 首先要明确这个方法是约定由function调用,作用是把调用wrap的那个方法 // 经过邦定后将其作为参数传给wrapper方法,其意图是用于重写父类的同名方法时, // 子类的方法中拥有了父类方法的引用了 wrap: function(wrapper) { var _method = this; return function() { return wrapper.apply(this, [_method.bind(this)].concat($A(arguments))); } } }); // 扩展Array,加入一个first方法,返回第一个元素 Object.extend(Array.prototype, { first: function() { return this[0]; } /* ,...其他方法定义 */ }); // 定义全局变量Class var Class = { // 基本跟前面讲的继承实现一致 create: function() { var parent = null; var 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 = []; // 这里为创建类时不调用父类的构造函数提供了一种新的途径 // 使用一个中间过渡类, 与前面用一个boolean的做法较之,这里优雅多了 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++ ) // 注意此处的addMethods klass.addMethods(properties[i]); if( !klass.prototype.initialize ) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { // 此方法用于将source中的方法取出,然后放入子类的prototype中, // 另外,实现父类的同名方法重写 addMethods: function (source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); // 对于IE8 ... for ( var property in source ) 是遍历不出toString的, // 所以此code是针对IE8的 if( !Object.keys({toString: true}).length ) properties.push('toString', 'valueOf'); //遍历属性 for( var i=0, length=properties.length; i<length; i++ ) { var property = properties[i]; // 属性名 var value = source[property]; // 属性值 // 判断若有对父类方法的重写, 则使得参数中的 $super 指向父类的同名方法 if( ancestor && Object.isFunction(value) && value.argumentNames().first() == '$super' ) { // 这是子类的方法 var method = value; // 分析此句:运用闭包,获取父类同名方法对象ancestor[m], 将其包裹与一个方法对象返回, // 目的是使得父类同名方法对象ancestor[m]在子类范围“this”中执行 // 返回的方法对象调用wrap,目的是将经过包裹的父类方法对象传给子类方法method // 按上分析,这里用 value = ancestor[property].wrap(method); value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method); // 此二句code目的是将vauleOf和toString还原, // 因为上面调用了wrap,看看wrap方法实现,其返回一个包裹方法对象, // 所以此时的valueOf与toString是描述包裹方法的,而不是method, // 此时绑定一下就是使其变回描述method value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } /* ,...还有其他的方法定义 */ };
若理解上述,试着参照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 Class(parentCls, props) { if( (typeof parentCls) === 'object' ) { props = parentCls; parentCls = null; } function cFunc() { if(parentCls) this.baseprototype = parentCls.prototype; this.initialize.apply(this, arguments); } // 盗用prototypejs的优雅写法,以消去全局变量 if( parentCls ) { var subclass = function(){}; subclass.prototype = parentCls.prototype; cFunc.prototype = new subclass(); cFunc.prototype.constructor = cFunc; } //配置cFunc函数,把props的一切传入cFunc for( var name in props ) { if( props.hasOwnProperty(name) ) { //覆盖父类parentCls的同名函数 if( parentCls && (typeof props[name]) === 'function' && argumentNames(props[name])[0] === "$super" ) { cFunc.prototype[name] = (function(name, fn) { return function() { var method = this; $super = function() { return parentCls.prototype[name].apply(method, arguments); } // 此处无深意,只是技巧地,像Java中调用静态方法一样,调用concat将$super // 整合如方法参数中 return fn.apply(this, Array.prototype.concat.apply($super, arguments)); } })(name, props[name]); } else { cFunc.prototype[name] = props[name]; } } } return cFunc; }
测试demo就不写了,不能再让mm scream 了。。。
在下承认确实费了吃奶的劲去弄懂上述,总结出把握js的闭包运用是关键。玩弄透彻javascript的继承,对一些js框架如dojo,extJs,以及正在学的Angular,即使当中很多跟本篇不搭干,想必也不会觉其太诡异,太晦涩。
最后介绍一个很帅的在线前端测试工具:http://jsfiddle.net/