CoffeeScript类

2018-08-25 13:54 更新
在js中是否要模拟传统编程语言的类,是个一直以来都有争议的话题,不同的项目,不同的团队,在类的使用上会有不同的看法,不过,一旦决定要使用类,那么至少需要一套良好的实现,CoffeeScript在语言内部实现了类的模拟,我们来看一看一个完整的例子


class Gadget
  @CITY = "beijing"


  @create: (name, price) ->
    new Gadget(name, price)


  _price = 0


  constructor: (@name, price) ->
    _price = price


  sell: =>
    "Buy #{@name} with #{_price} in #{Gadget.CITY}"


iphone = new Gadget("iphone", 4999)
console.log iphone.name #=> "iphone"
console.log iphone.sell() #=> "Buy iphone with 4999 in beijing"


ipad = Gadget.create("ipad", 3999)
console.log ipad.sell() #=> "Buy ipad with 3999 in beijing"
这个Gadget类具有通常语言中类的功能:


constructor是构造函数,必须用这个名称,类似ruby中的initialize
name是实例变量,可以通过iphone.name获取
构造函数中如果给实例变量赋值,直接将@name写在参数中即可,等价于在函数体中的@name = name
_price是私有变量,需要赋初始值
sell是实例方法
create是类方法,注意这里使用了@create,这和ruby有些像,在定义时的this指的是这个类本身
CITY是类变量
要注意的是,对于实例方法,要用=>来绑定this,这样可以作为闭包传递,比如


iphone = new Gadget("iphone", 4999)
$("#sell").click(iphone.sell())
如果不用=>,闭包被调用时就会丢失实例对象的值(iphone)


对于熟悉基于类的面向对象编程的人,CoffeeScript的类是一目了然的,下面来看看对应的js代码


  var Gadget,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };


  Gadget = (function() {
    var _price;


    Gadget.name = 'Gadget';


    Gadget.CITY = "beijing";


    Gadget.create = function(name, price) {
      return new Gadget(name, price);
    };


    _price = 0;


    function Gadget(name, price) {
      this.sell = __bind(this.sell, this);
      this.name = name;
      _price = price;
    }


    Gadget.prototype.sell = function() {
      return "Buy " + this.name + " with " + _price + " in " + Gadget.CITY;
    };


    return Gadget;


  })();


以上的代码有很多值得注意的地方


整体上来说,CoffeeScript的类模拟使用的是一个*构造函数闭包*,这是最常用的模拟类的模式,好处是可以完整地封装内部变量,且可以使用new来生成实例对象
_price就是被封装在闭包内部的私有变量
sell这样的实例方法是原型方法,并且在初始化时使用自定义的bind函数绑定实例(用=>定义的情况)
create和CITY这样的类成员使用构造函数的属性实现,重复一下,在CoffeeScript类定义中的this指的是整个闭包Gadget

Gadget.name是额外定义的类名属性



类的继承

CoffeeScript中为方便地实现类的继承也定义了自己的语法,我们把上面的类简化,来看一下如何继承:

class Gadget
  constructor: (@name) ->
  sell: =>
    "Buy #{@name}" 


class IPhone extends Gadget
  constructor: -> super("iphone")
  nosell: =>
    "Don't #{@sell()}"


iphone = new IPhone
iphone.nosell() #=> Don't Buy iphone
使用extends关键字可以继承父类中的所有实例属性,比如sell
super方法可以调用父类的同名方法
如果不覆盖constructor,则她被子类默认调用
来看一下对应的js代码,这有一些复杂,我们把和上边类定义中重复的地方去掉,只留下继承的实现部分


  var Gadget, IPhone,
    __extends = function(child, parent) { 
      for (var key in parent) { 
        if ({}.hasOwnProperty.call(parent, key)) 
          child[key] = parent[key]; 
      } 


      function ctor() { this.constructor = child; } 


      ctor.prototype = parent.prototype; 
      child.prototype = new ctor; 
      child.__super__ = parent.prototype; 


      return child; 
    };


  IPhone = (function(_super) {


    __extends(IPhone, _super);


    IPhone.name = 'IPhone';


    function IPhone() {
      this.nosell = __bind(this.nosell, this);
      IPhone.__super__.constructor.call(this, "iphone");
    }


    IPhone.prototype.nosell = function() {
      return "Don't " + (this.sell());
    };


    return IPhone;


  })(Gadget);


这里重点有三个,

__extends函数使用了代理构造函数ctor来实现继承,这是非常普遍的js中对象继承的实践模式,进一步解释一下
使用代理构造函数的目的是为了避免子类被更改时父类受到影响
使用ctor.prototype = parent.prototype的意义是只继承定义在prototype上的公用属性
父类的类成员被直接引用拷贝到子类,而不是原型继承
super的实现方法是parent.prototype.constructor.call(this)

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号