本编参考: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
现在修改demo如下:
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的定义。
对于后new Room()的两条语句的结果,因为new Room()之后,javascript解析器会执行Room函数本身,返回一个具体的对象实例,该对象包含了Room() 方法体内定义的所有东西。这里new Room() 返回的对象实例跟var room = {...}的区别,请看demo:
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,充其量只是个普通的属性。
这样的意义是,此时Room相当于一个构造函数(constructor),我们在构造函数内定义属性与方法,当某天我们需要构建一种对象BabyRoom,包含Room的所有特性,此时,我们只需把Room的prototype 赋予 BabyRoom Function 就可以了,而不必重新另行定义(这是本篇后话)于是使用BabyRoom构造出的对象除了有自身特性外,还包含Room对象的prototype中的一切。
4. constructor——指创建当前对象的构造函数
上述Room就是一个constructor,demo:
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
二、javascript的继承实现
原始版:按照上述关键字的陈述,估计不难给出下面实现:
//定义一个父类 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/
分享到:
相关推荐
javascript 的基础语法 面向对象的实现 设计模式实现 模块化开 javascript 常见的疑问 jQuery NodeJs html5 Javascript based 1.对象 JavaScript 引用 2.JavaScript this 3.JavaScript 闭包 4.JavaScript 事件 ...
动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/catch语句带来的warning 微软的应试题完整版(附答案) 一个时间转换的问题,顺便谈谈搜索技巧 .net中的正则表达式使用高级技巧 (一) C#静态成员和...
序言 这篇文章主要讲解面试中的一个问题 – ...JavaScript语言的传统方法是通过构造函数定义并生成新对象,这种写法和传统的面向对象语言差异较大。所以,ES6引入了Class这个概念作为对象的模板。 constructor 效果:
• 4.1.htm 条件语句内单行代码的写法 • 4.2.htm 变量赋初值 • 4.3.htm if…else举例 • 4.4.htm 用else进行更多选择 • 4.5.htm if语句的嵌套之一 • 4.6....
• 4.1.htm 条件语句内单行代码的写法 • 4.2.htm 变量赋初值 • 4.3.htm if…else举例 • 4.4.htm 用else进行更多选择 • 4.5.htm if语句的嵌套之一 • 4.6....
Javascript Basic 1、Javascript 概述(了解) Javascript,简称为 JS,是一款能够运行在 JS解释器/引擎 中的脚本语言 JS解释器/引擎 是JS的运行环境: 1、独立安装的JS解释器 - NodeJS 2、嵌入在浏览器中的JS...
ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。 历史上,JavaScript一直没有模块(module)体系,无法将一个大程序...
(如果你是程序员,你会惊讶的发现,这与模块化面向对象编程的思想极其相似!其实网页何尝不是一种程序呢?) 正是这种区别使得XML在网络应用和信息共享上方便,高效,可扩展。所以我们相信,XML做为一种先进的数据...
jQuery 1.2(2007年9月):这一版去掉了对XPath选择符的支持,原因是相对于CSS语法它已经变得多余了。这一版能够支持对效果的更灵活定制,而且借助新增的命名空间事件,也使插件开发变得更容易。 jQuery UI(2007年...
仅仅通过反射方法没有办法知道集合元素中的类型,所以上面的两个converter能将任何集合转换成相对javascript而言有意义的对象.然而没有办法将不同的集合类类型分别采用不同的转换方法.因为没有办法完全自动进行转换,...
虽然jQuery使用简单,但它毕竟是一门新的技术,与传统的JavaScript在性能与语法上存在诸多差异,需要相应的书籍来引导开发者们迅速而有效地掌握它,并能真正付诸实践。综观现在已经出版的中文类jQuery图书,不是...
{2.1.3}面向对象的编程}{47}{subsection.2.1.3} {2.2}继承}{48}{section.2.2} {2.2.1}super(), this()}{49}{subsection.2.2.1} {2.2.2}方法重写/覆盖}{50}{subsection.2.2.2} {2.3}修饰符}{51}{section.2.3} ...
"json": 将响应作为JSON求值,并返回一个Javascript对象。 "jsonp": 使用JSONP载入一个JSON代码块. 会在URL的末尾添加"?callback=?"来指明回调函数。(jQuery 1.2以上的版本支持) "text": 文本格式的...