BeWithYou

胡搞的技术博客

  1. 首页
  2. web前端/Javascript
  3. 从Cocos2d-js来看Javascript中的面向对象

从Cocos2d-js来看Javascript中的面向对象


项目缺人,最近又被拉过去做手游客户端的HTML5化重新开发,用Cocos2d-js这个大杀器。
个人认为这样换来换去非常不好。毕业两年了,各种打杂,没有什么可以说得上“精通”的领域。下份工作真的不好找了,因为根本不知道能做什么岗位。
不过既然做了,还是要总结一些觉得有用的东西。都是泪啊。

真尼玛。

       Javascript是一种基于对象的编程语言,它里面所有东西都是对象,甚至包括数组也是对象。有点像Lua中的table。但是它并没有类的概念。我们先从一般的情景看起,后面再看Cocos2d-js中的面向对象。

Javascript中对象的表示

       我们一般这样表示一个对象:

var person = {
    name:'胡搞',
    sex:'male'
};

       可以用函数来生成对象:

function People(name, sex){
    return {name:name, sex:sex};
}
var p1 = People("hehe","female");

       看起来更面向对象一点,我们可以用函数来描述对象的原型:

function People(name, sex){
    this.name=name;
    this.sex=sex;
    this.species="Mammal";
    this.voice=function(){console.log("Hi!");};
}
var p1 = new People("hehe","female");
console.log(p1.constructor == People);//true 构造函数为People
console.log(p1 instanceof People);//true 是People原型的对象
var p2 = new People("haha","male");
console.log(p1.voice==p2.voice);//false 每个对象的方法内存地址不同

       上面的例子中,species和voice虽然每个对象取值都一样,但是却要占用单独的内存。我们可以用原型prototype的方法来指定所有对象公用的属性和方法。

People.prototype.species="Mammal";
People.prototype.voice=function(){console.log("Hi!")};
//...
console.log(p1.voice==p2.voice);//true
console.log(p1.hasOwnProperty("voice"));//false 来自于prototype不是自身的

       prototype有点类似Lua中table元表__index的概念。我们可以给对象赋值一个属性用于覆盖prototype的值,但是将其delete后,又会回到prototype的值。

p1.species="Reptile";
console.log(p1.species);//Reptile
delete(p1.species);
console.log(p1.species);//Mammal

Javascript中继承的实现

       用call或者apply将父类的构造函数添加到子类中。修改子类的prototype为父类对象。修改子类prototype的constructor为子类构造函数。

function Student(name,sex,school){
    People.apply(this, [name, sex]);//使用this来调用People构造函数,并且把参数列表传进去 或者直接传arguments
    //People.call(this,sex); 也可
    this.school = school;
}
Student.prototype = new People();
Student.prototype.constructor = Student;//修改构造函数指向 不然会变成People的构造函数,产生混乱

var p1 = new Student("haha","male","whyz");

       还有其他很多实现继承的方式,更完备。比如可以实现子类中显式调用父类的方法等。

       PS. 以上用了很多“类”的说法,其实严谨的来说应该是“对象”。

Cocos2d-js中Javascript的继承

       在Cocos2d-js中,我们用到的元素基本都继承自cc.Class。Class中的extend方法,是实现继承的关键。我们一般这么声明一个子类继承父类:

var BaseLayer = cc.LayerColor.extend({
    ctor:function(){
        this._super();//显式调用父类方法
    },
    //...其他属性和方法
});

       关于Cocos2d-js中cc.Class.extend方法的实现的代码,太长了,不想看。可以去参看这篇文章

       除去跟框架相关的内容(比如类管理器等),主要思路是这样的:创建Class类(当然是extend闭包内的,不是外面那个大的cc.Class),如果有声明ctor方法的话,就调用他this.ctor.apply(this,arguments)。从参数对象中,循环读取属性与方法,如果是方法,并且是重载了父类的方法,并且里面还有对_super的调用,则将其修改成含有父类的方法的函数,否则不改。之后将extend方法也赋值给内部的Class.extend,使其也可以自定义继承。然后将定义的Class返回给外部。

       很晦涩,而且用到了很多平时接触不到的函数。其实Cocos2d-js中关于继承的设计,可以简化为John Resiq提出的一种方法:

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  //声明Class 这个正是大的Class 相当于cc.Class
  this.Class = function(){};

  // Create a new Class that inherits from this class
  //定义extend方法
  Class.extend = function(prop) {
    //用原型给_super对象赋值
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    //实例化父类对象
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      //如果元素是函数,并且父类也有这个函数,并且子类函数中调用了_super方法
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        //则在子类函数中,将父类的方法添加进去
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    // 声明extend函数闭包内部的Class类
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
          //如果有init方法,则调用一下 相当于cocos中的ctor
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    // 修改原型
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    // 修改原型构造函数 跟上面说的一样
    Class.prototype.constructor = Class;

    // And make this class extendable
    // 把this.Class.callee赋值给将要返回给外面的Class.extend
    // 这里arguments.callee代表正在执行的extend函数
    Class.extend = arguments.callee;

    return Class;
  };
})();

       新建对象的调用方式跟Cocos2d-js中的相同,只不过把ctor换成init即可。

var A=Class.extend({
    a:null,
    init:function(a){
        this.a=a;
    },
    hehe:function(){
        console.log(this.a);
    }
});

var B=A.extend({
    b:null,
    init:function(a,b){
        this._super(a);
        this.b=b;
    },
    hehe:function(){
        this._super();
        console.log(this.b);
    },
    haha:function(){
        console.log("no super!");
    }
});

var b = new B(1,2);
b.hehe();//1\n 2
b.haha();//no super!
回到顶部