`
oham_一1一
  • 浏览: 49676 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Javascript 面向对象化写法——语法篇

阅读更多

本编参考: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',这样是不对的。所谓对象模板,即所有的实例都共享这一模板,而且本身又是个对象,于是就所以然了。
这里的原始版的继承实现不得不指出:
  1. 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"   ,这就不对了
    
     
  2. 感觉很挫,说是继承,但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,也是出问题。具体的在下是不懂了。

 

改良版有两个问题:

  1. 引入了全局变量_initCls。
  2. 请看上面一段代码:
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原理资源,分享出来.zip

    javascript 的基础语法 面向对象的实现 设计模式实现 模块化开 javascript 常见的疑问 jQuery NodeJs html5 Javascript based 1.对象 JavaScript 引用 2.JavaScript this 3.JavaScript 闭包 4.JavaScript 事件 ...

    asp.net知识库

    动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/catch语句带来的warning 微软的应试题完整版(附答案) 一个时间转换的问题,顺便谈谈搜索技巧 .net中的正则表达式使用高级技巧 (一) C#静态成员和...

    ES6中的class是如何实现的(附Babel编译的ES5代码详解)

    序言 这篇文章主要讲解面试中的一个问题 – ...JavaScript语言的传统方法是通过构造函数定义并生成新对象,这种写法和传统的面向对象语言差异较大。所以,ES6引入了Class这个概念作为对象的模板。 constructor 效果:

    精通javascript

    • 4.1.htm 条件语句内单行代码的写法 • 4.2.htm 变量赋初值 • 4.3.htm if…else举例 • 4.4.htm 用else进行更多选择 • 4.5.htm if语句的嵌套之一 • 4.6....

    精通JavaScript

    • 4.1.htm 条件语句内单行代码的写法 • 4.2.htm 变量赋初值 • 4.3.htm if…else举例 • 4.4.htm 用else进行更多选择 • 4.5.htm if语句的嵌套之一 • 4.6....

    javascript入门笔记

    Javascript Basic 1、Javascript 概述(了解) Javascript,简称为 JS,是一款能够运行在 JS解释器/引擎 中的脚本语言 JS解释器/引擎 是JS的运行环境: 1、独立安装的JS解释器 - NodeJS 2、嵌入在浏览器中的JS...

    ES6新特性之模块Module用法详解

    ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。 历史上,JavaScript一直没有模块(module)体系,无法将一个大程序...

    XML轻松学习手册--XML肯定是未来的发展趋势,不论是网页设计师还是网络程序员,都应该及时学习和了解

    (如果你是程序员,你会惊讶的发现,这与模块化面向对象编程的思想极其相似!其实网页何尝不是一种程序呢?) 正是这种区别使得XML在网络应用和信息共享上方便,高效,可扩展。所以我们相信,XML做为一种先进的数据...

    jquery插件使用方法大全

    jQuery 1.2(2007年9月):这一版去掉了对XPath选择符的支持,原因是相对于CSS语法它已经变得多余了。这一版能够支持对效果的更灵活定制,而且借助新增的命名空间事件,也使插件开发变得更容易。 jQuery UI(2007年...

    DWR.xml配置文件说明书(含源码)

    仅仅通过反射方法没有办法知道集合元素中的类型,所以上面的两个converter能将任何集合转换成相对javascript而言有意义的对象.然而没有办法将不同的集合类类型分别采用不同的转换方法.因为没有办法完全自动进行转换,...

    jQuery权威指南-源代码

    虽然jQuery使用简单,但它毕竟是一门新的技术,与传统的JavaScript在性能与语法上存在诸多差异,需要相应的书籍来引导开发者们迅速而有效地掌握它,并能真正付诸实践。综观现在已经出版的中文类jQuery图书,不是...

    Java学习笔记-个人整理的

    {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} ...

    jquery-1.1.3 效率提高800%

    "json": 将响应作为JSON求值,并返回一个Javascript对象。 "jsonp": 使用JSONP载入一个JSON代码块. 会在URL的末尾添加"?callback=?"来指明回调函数。(jQuery 1.2以上的版本支持) "text": 文本格式的...

Global site tag (gtag.js) - Google Analytics