1. 程式人生 > >javascript oo實現

javascript oo實現

isa 類方法 創建 _屬性 hello ext constant 本質 foo

很久很久以前,我還是個phper,第一次接觸javascript覺得好神奇。跟傳統的oo類概念差別很大。記得剛畢業面試,如何在javascript裏面實現class一直是很熱門的面試題,當前面試百度就被問到了,當年作為一個小白只是網上隨便搜搜應付了下。= =現在發現當時知道的還是太少太少。今天整理了下javascript的oo實現,發現知道的越多,越發現知識真是無窮無盡。

原始時代最簡單的oo實現

javascript雖然沒有class的概念,但是它的函數卻是可以new出來一個對象的。所以一個最簡單的class就可以用function來模擬出來。

function Animal(name){
    this.name = name;
    this.run = function(){
        console.log(this.name + "is running!!");
    }
}

var pet = new Animal("pet");
pet.run();//petis running!!

這樣 pet就有了屬性,有了方法,不過這種寫法毫無繼承性,擴展性。比如我們要實現個dog類,只能把屬性方法再寫一遍。而且每個new出來的對象都有自己的方法,造成資源浪費。

在javascript裏面有個原型鏈的概念,每一個函數都有一個prototype對象屬性。這樣通過這個函數new出來的對象會自動具有__proto__屬性指向函數的prototype對象。說白了所有的實例對象都會共用一個prototype對象,並且調用一個屬性或者方法時在自己上面找不到,就會找__proto__對象有沒有,之後一直往上追溯一直到找到為止。具體表現為:

function Animal(name){
    this.name = name;
}
Animal.prototype.run = function(){
    console.log(this.name + "is running!!");
}
var a = new Animal("a");
var b = new Animal("b");
console.log(Animal.prototype) //Animal {} 
console.log(Animal.prototype instanceof Object) //true prototype是個對象
console.log(Animal.prototype.constructor == Animal)//true
console.log(a.__proto__ == Animal.prototype) //true __proto__在new的時候會自動加載在實例對象上。在現代瀏覽器裏可以看到
console.log(b.__proto__ == Animal.prototype) //true
console.log(a.__proto__.__proto__) //Object {} 最後會找到最上面的boject對象
console.log(a.__proto__.run == a.run) //true
console.log(a.__proto__.run == Animal.prototype.run) //true

所以,在prototype對象上定義的方法會被所有實例共享,這不就是復用嗎?
於是有了基於原型鏈的繼承的寫法:

function Animal(name){
    this.name = name;
}
Animal.prototype.run = function(){
    console.log(this.name + "is running!!");
}
function Dog(name){
    //調用父類的構造函數,通過改變this指向將屬性賦值到新的實例對象
    Animal.call(this,name);
}
Dog.prototype = new Animal();
var dog = new Dog("dog");
dog.run();//dog is running!!

可以看到我們將Animal的實例對象暫且叫做a,作為 Dog的prototype,這樣 Dog的實例對象dog的__proto__指向Dog的prototype也就是a,a的__proto__再指向Animal的prototype對象,這個對象上有run方法。於是我們調用dog.run()的時候會一層層的往上追溯一直找到run方法執行。於是通過原型鏈我們就讓 Dog繼承了Animal的方法run。


需要註意的是,如果在子類的prototype對象上也有run方法,就會覆蓋父類的,因為查找時在自己上面就找到了,就不會向上回溯了。

上面是原型鏈方法的繼承。而屬性我們則是通過調用父類的構造函數來賦值的。因為屬性不能所有的實例都公用,應該每個人都有自己的一份,所以不能放在原型上。

上面就是原始時代最簡單的類繼承了。

石器時代的oo實現

這個時代javascript變得比較重要了,作為非常有用的特性,oo開始被很多人研究。

首先上面的那種簡單oo實現方式,其實是有很多問題的。
1.沒有實現傳統oo該有的super方法來調用父類方法。
作為oo,怎麽能沒有super呢。作為我們前端界宗師一般的人物。Douglas 有一篇經典文章。不過貌似有很多問題。國內的玉伯分析過。在這裏

最後Douglas總結出來:

我編寫 JavaScript 已經 8 個年頭了,從來沒有一次覺得需要使用 uber 方法。在類模式中,super 的概念相當重要;但是在原型和函數式模式中,super 的概念看起來是不必要的。現在回顧起來,我早期在 JavaScript 中支持類模型的嘗試是一個錯誤。
2.直接將父類實例作為子類的原型,簡單粗暴造成多余的原型屬性。還有construct的問題。
這個問題主要是之前代碼裏面這一句造成的:

Dog.prototype = new Animal();
//var dog = new Dog("dog");
//console.log(dog.__proto__)     Animal {name: undefined}

執行new Animal()就會執行animal的構造函數,就會在Dog.prototype生成多余的屬性值,這邊是name。而一般屬性值為了復用是不能放在原型對象上的。並且由於dog有自己的name屬性,原型上的是多余的。

還有construct的問題。

console.log(dog.constructor == Animal) //true
console.log(dog.constructor == Dog) //false

顯然這不是我們希望看到的。

所以我們要對上面做些改良:

var F = function(){};
F.prototype = Animal.prototype;
Dog.prototype = new F();
Dog.prototype.constructor = Dog;

我們可以封裝下:

function objCreate(prototype){
    var F = function(){};
    F.prototype = prototype;
    return new F();
}
function inherit(subclass,parentclass){
    subclass.prototype = objCreate(parentclass.prototype);
    subclass.prototype.constructor = subclass;
}

於是繼承可以寫成:

function Animal(name){
    this.name = name;
}
Animal.prototype.run = function(){
    console.log(this.name + "is running!!");
}
function Dog(name){
    //調用父類的構造函數,通過改變this指向將屬性賦值到新的實例對象
    Animal.call(this,name);
}
inherit(Dog,Animal);
var dog = new Dog("dog");
dog.run();//dog is running!!

當年大學畢業面試,也就到這個程度了。 = =

工業時代的oo實現

這個時代,各種javascript類庫像雨後春筍般湧現了出來。
上面最後給出的方案,使用起來還是很不便,比如需要自己手動維護在構造函數裏調用父類構造函數。同時繼承寫法對不了接原理的比較容易出錯。

這個時候湧現了一大堆的類庫的實現:

1.首先有些類庫決定跳出傳統oo的思維。不一定非要實現傳統oo的繼承。歸根到底我們是為了復用。於是出現了很多輕量級的復用方式。
比如jquery的extend:http://api.jquery.com/jQuery.extend/
還有kissy的mix:http://docs.kissyui.com/1.3/docs/html/api/seed/kissy/mix.html?highlight=mix#seed.KISSY.mix
還有kissy的argument:http://docs.kissyui.com/1.3/docs/html/api/seed/kissy/augment.html
還有很多很多,說白了都是對象級別上的混入達到復用的地步。大部分情況下已經足夠了。

2.當然還是有人對類的繼承有需求的。
下面我們看下kissy的extend的實現方式。其他類庫實現方式類似,kissy的我覺得算是比較有代表性了。為了演示,做了些小修改。

//這個就是我們之前實現的方法,為了演示做了些改動主要是處理了construct的問題
function objCreate(prototype,construct){
    var F = function(){};
    F.prototype = prototype;
    var newPro = new F();
    newPro.construct = construct;//維護構造函數的改變
    return newPro;
}
//mix是個輔助方法,這邊給個最簡單的實現,其實kissy裏面的復雜的多。這邊不考慮深度遍歷等等,只是最簡單的實現。
function mix(r, s) {
  for (var p in s) {
    if (s.hasOwnProperty(p)) {
        r[p] = s[p]
    }
  }
}
//下面是kissy的實現r代表子類 s代表父類,px代表最後會混入子類原型上的屬性,sx代表會混入子類函數上面的屬性,也就是可以當做靜態方法。
//http://docs.kissyui.com/1.3/docs/html/api/seed/kissy/extend.html?highlight=extend#seed.KISSY.extend
function extend (r, s, px, sx) {
    if (!s || !r) {
        return r;
    }
    var sp = s.prototype,
        rp;
    //針對父類生成一個原型。跟之前我們寫的一致
    rp = createObject(sp, r);
    //不是簡單的直接復制原型對象,而是先把以前原型的方法跟要繼承的合並之後再一起賦值
    r.prototype = S.mix(rp, r.prototype);
    
    //為子類增加superclass屬性,指向一個父類對象,這樣就可以調用父類的方法了。這邊是實現比較巧妙的地方
    r.superclass = createObject(sp, s);
    //下面就是往原型還有函數上混入方法了
    // add prototype overrides
    if (px) {
        S.mix(rp, px);
    }

    // add object overrides
    if (sx) {
        S.mix(r, sx);
    }

    return r;
}

有了kissy的extend我們可以這麽用:

function Animal(name){
    this.name = name;
}
Animal.prototype.run = function(){
    console.log(this.name + "is running!!");
}
function Dog(name){
    
    //Animal.call(this,name);
    //因為kissy的封裝 這邊可以這麽用
    Dog.superclass.construct.call(this,name);
}
extend(Dog,Animal,{
    wang:function(){
        console.log("wang wang!!")
    }
})
var dog = new Dog("dog");
dog.run();//dog is running!!
dog.wang();//wang wang!!

相對之前的變得清晰了很多,也更易用了。

現代科技時代的oo實現

前面的寫法,目前雖然還是有很多人用,不過也漸漸過時了。上面的寫法還是不夠清晰,定義屬性,方法都很分散,也沒有多繼承,等特性。我們需要像傳統oo一樣具有一個類工廠,可以生成一個類,屬性都定義在裏面。同時具有繼承的方法。

而隨著javascript成為前端唯一的語言,一代代大神前仆後繼。終於開始湧現出了各種神奇的寫法,下面羅列下一些我覺得特別好的實現,加上原理註釋。

John Resig的實現方式

作為jquery的作者。John Resig在博客裏記錄了一種class的實現,原文在此
調用方法:

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});
 
var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});
 
var p = new Person(true);
p.dance(); // => true
 
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true
 
// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class
源碼解讀:

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  //initializing是為了解決我們之前說的繼承導致原型有多余參數的問題。當我們直接將父類的實例賦值給子類原型時。是會調用一次父類的構造函數的。所以這邊會把真正的構造流程放到init函數裏面,通過initializing來表示當前是不是處於構造原型階段,為true的話就不會調用init。
  //fnTest用來匹配代碼裏面有沒有使用super關鍵字。對於一些瀏覽器`function(){xyz;}`會生成個字符串,並且會把裏面的代碼弄出來,有的瀏覽器就不會。`/xyz/.test(function(){xyz;})`為true代表瀏覽器支持看到函數的內部代碼,所以用`/\b_super\b/`來匹配。如果不行,就不管三七二十一。所有的函數都算有super關鍵字,於是就是個必定匹配的正則。
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
 
  // The base Class implementation (does nothing)
  // 超級父類
  this.Class = function(){};
 
  // Create a new Class that inherits from this class
  // 生成一個類,這個類會具有extend方法用於繼續繼承下去
  Class.extend = function(prop) {
    //保留當前類,一般是父類的原型
    //this指向父類。初次時指向Class超級父類
    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
      //這邊其實就是很簡單的將prop的屬性混入到子類的原型上。如果是函數我們就要做一些特殊處理
      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方法用來調用父類的同名方法
            this._super = _super[name];
           
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            //因為上面的賦值,是的這邊的fn裏面可以通過_super調用到父類同名方法
            var ret = fn.apply(this, arguments);  
            //離開時 保存現場環境,恢復值。
            this._super = tmp;
           
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
   
    // 這邊是返回的類,其實就是我們返回的子類
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
   
    // 賦值原型鏈,完成繼承
    Class.prototype = prototype;
   
    // 改變constructor引用
    Class.prototype.constructor = Class;
 
    // 為子類也添加extend方法
    Class.extend = arguments.callee;
   
    return Class;
  };
})();

相當簡單高效的實現方式,super的實現方式非常亮

P.js的實現

源地址:https://github.com/jneen/pjs
pjs的一大亮點是支持私有屬性,他的類工廠傳遞的是函數不是對象。

調用方式:

//可以生成一個可繼承的對象,P接收一個函數,這個函數會傳入生成後的class的原型。
var Animal = P(function(animal) {
  animal.init = function(name) { this.name = name; };

  animal.move = function(meters) {
    console.log(this.name+" moved "+meters+"m.");
  }
});
//繼承Animal。後面的snake,animal分別是前面Snake和Animal的原型。程序直接把這些對象暴露給你了。於是靈活度很高。
var Snake = P(Animal, function(snake, animal) {
  snake.move = function() {
    console.log("Slithering...");
    animal.move.call(this, 5);
  };
});

var Horse = P(Animal, function(horse, animal) {
  //真正的私有屬性,外面沒法調用到
  var test = "hello world";
  horse.move = function() {
    console.log(test);
    console.log("Galloping...");
    //調用父類的方法,so easy!!
    animal.move.call(this, 45);
  };
});
//工廠方式生成對象,可以不用new
var sam = Snake("Sammy the Python")
  , tom = Horse("Tommy the Palomino")
;

sam.move()
tom.move()

源碼解讀:

var P = (function(prototype, ownProperty, undefined) {
  return function P(_superclass /* = Object */, definition) {
    // handle the case where no superclass is given
    if (definition === undefined) {
      definition = _superclass;
      _superclass = Object;
    }

    //最後返回的類就是這個,也就是我們需要的子類。這個類可以用new生成實例,也可以直接調用生成實例
    function C() {
      //判斷,是new的話this instanceof C就是true。否則我們自己手動new一下Bare。Bare就是為了實現這種類工廠的生成類的方式
      var self = this instanceof C ? this : new Bare;
      self.init.apply(self, arguments);
      return self;
    }

    //這個就是用來實現不用new生成類的方式
    function Bare() {}
    C.Bare = Bare;

    //將父類的原型賦值給Bare
    //這邊prototype就是個字符串“prototype”變量,主要為了壓縮字節少點,所以作者還單獨傳成變量進來 = =
    var _super = Bare[prototype] = _superclass[prototype];
    //再生成這個空函數的實例賦值給C,Bare的原型,同時在C.p存下來
    //這樣C,Bare都公用一個原型
    var proto = Bare[prototype] = C[prototype] = C.p = new Bare;
    
    var key;
    //改變constructor指向
    proto.constructor = C;
//上面幾部其實還是實現的通用的繼承實現方式,新建個空函數,將父類的原型賦給這個空函數再生成實例賦值給子類的原型。萬變不離其宗。原理都一樣
    //增加extend方法。這是個語法糖,本質上還是調用P來實現,只不過第一個參數是調用者C
    C.extend = function(def) { return P(C, def); }
    //下面是最關鍵的地方,寫的有點繞。這邊分為這幾步
    //傳入definition 執行 function(def){} 
    // 執行C.open = C
    // return C.open 其實就是 renturn C 返回最終的生成類
    return (C.open = function(def) {
      if (typeof def === 'function') {
        // call the defining function with all the arguments you need
        // extensions captures the return value.
        //是函數的話就傳入 一些屬性包括子類原型,父類原型,子類構造函數,父類構造函數
        def = def.call(C, proto, _super, C, _superclass);
      }

      // 如果是對象,就直接混入到原型
      if (typeof def === 'object') {
        for (key in def) {
          if (ownProperty.call(def, key)) {
            proto[key] = def[key];
          }
        }
      }

      //確保有init函數
      if (!('init' in proto)) proto.init = _superclass;

      return C;
    })(definition);
  }

})('prototype', ({}).hasOwnProperty);

阿拉蕾的實現方式

這是支付寶的庫阿拉蕾的實現,我覺得是最不錯的一種方式:
源地址:https://github.com/aralejs/class/blob/master/class.js

// The base Class implementation.
function Class(o) {
  //這個判斷用來支持 將一個已有普通類轉換成 阿拉蕾的類
  if (!(this instanceof Class) && isFunction(o)) {
    //原理是給這個函數增加extend,implement方法
    return classify(o)
  }
}
//用來支持 commonjs的模塊規範。
module.exports = Class


// Create a new Class.
//
//  var SuperPig = Class.create({
//    Extends: Animal,
//    Implements: Flyable,
//    initialize: function() {
//      SuperPig.superclass.initialize.apply(this, arguments)
//    },
//    Statics: {
//      COLOR: 'red'
//    }
// })
//
//

//用於創建一個類,
//第一個參數可選,可以直接創建時就指定繼承的父類。
//第二個參數也可選,用來表明需要混入的類屬性。有三個特殊的屬性為Extends,Implements,Statics.分別代表要繼承的父類,需要混入原型的東西,還有靜態屬性。
Class.create = function(parent, properties) {
  //創建一個類時可以不指定要繼承的父類。直接傳入屬性對象。
  if (!isFunction(parent)) {
    properties = parent
    parent = null
  }

  properties || (properties = {})
  //沒有指定父類的話 就查看有沒有Extends特殊屬性,都沒有的話就用Class作為父類
  parent || (parent = properties.Extends || Class)
  properties.Extends = parent

  // 子類構造函數的定義
  function SubClass() {
    // 自動幫忙調用父類的構造函數
    parent.apply(this, arguments)

    // Only call initialize in self constructor.
    //真正的構造函數放在initialize裏面
    if (this.constructor === SubClass && this.initialize) {
      this.initialize.apply(this, arguments)
    }
  }

  // Inherit class (static) properties from parent.
  //parent為Class就沒必要混入
  if (parent !== Class) {
    //將父類裏面的屬性都混入到子類裏面這邊主要是靜態屬性
    mix(SubClass, parent, parent.StaticsWhiteList)
  }

  // Add instance properties to the subclass.
  //調用implement將自定義的屬性混入到子類原型裏面。遇到特殊值會單獨處理,真正的繼承也是發生在這裏面
  //這邊把屬性也都弄到了原型上,因為這邊每次create或者extend都會生成一個新的SubClass。所以倒也不會發生屬性公用的問題。但是總感覺不大好
  implement.call(SubClass, properties)

  // Make subclass extendable.
  //給生成的子類增加extend和implement方法,可以在類定義完後,再去繼承,去混入其他屬性。
  return classify(SubClass)
}

//用於在類定義之後,往類裏面添加方法。提供了之後修改類的可能。類似上面defjs實現的open函數。
function implement(properties) {
  var key, value

  for (key in properties) {
    value = properties[key]
    //發現屬性是特殊的值時,調用對應的處理函數處理
    if (Class.Mutators.hasOwnProperty(key)) {
      Class.Mutators[key].call(this, value)
    } else {
      this.prototype[key] = value
    }
  }
}


// Create a sub Class based on `Class`.
Class.extend = function(properties) {
  properties || (properties = {})
  //定義繼承的對象是自己
  properties.Extends = this
  //調用Class.create實現繼承的流程
  return Class.create(properties)
}

//給一個普通的函數 增加extend和implement方法。
function classify(cls) {
  cls.extend = Class.extend
  cls.implement = implement
  return cls
}


// 這裏定義了一些特殊的屬性,阿拉蕾遍歷時發現key是這裏面的一個時,會調用這裏面的方法處理。
Class.Mutators = {
  //這個定義了繼承的真正操作代碼。
  'Extends': function(parent) {
    //這邊的this指向子類
    var existed = this.prototype
    //生成一個中介原型,就是之前我們實現的objectCreat
    var proto = createProto(parent.prototype)

    //將子類原型有的方法混入到新的中介原型上
    mix(proto, existed)

    // 改變構造函數指向子類
    proto.constructor = this

    // 改變原型 完成繼承
    this.prototype = proto

    //為子類增加superclass屬性,這樣可以調用父類原型的方法。
    this.superclass = parent.prototype
  },
  //這個有點類似組合的概念,支持數組。將其他類的屬性混入到子類原型上
  'Implements': function(items) {
    isArray(items) || (items = [items])
    var proto = this.prototype, item

    while (item = items.shift()) {
      mix(proto, item.prototype || item)
    }
  },
  //傳入靜態屬性
  'Statics': function(staticProperties) {
    mix(this, staticProperties)
  }
}


// Shared empty constructor function to aid in prototype-chain creation.
function Ctor() {
}

// 這個方法就是我們之前實現的objectCreat,用來使用一個中介者來處理原型的問題,當瀏覽器支持`__proto__`時可以直接使用。否則新建一個空函數再將父類的原型賦值給這個空函數,返回這個空函數的實例
var createProto = Object.__proto__ ?
    function(proto) {
      return { __proto__: proto }
    } :
    function(proto) {
      Ctor.prototype = proto
      return new Ctor()
    }


// Helpers 下面都是些輔助方法,很簡單就不說了
// ------------

function mix(r, s, wl) {
  // Copy "all" properties including inherited ones.
  for (var p in s) {
    //過濾掉原型鏈上面的屬性
    if (s.hasOwnProperty(p)) {
      if (wl && indexOf(wl, p) === -1) continue

      // 在 iPhone 1 代等設備的 Safari 中,prototype 也會被枚舉出來,需排除
      if (p !== 'prototype') {
        r[p] = s[p]
      }
    }
  }
}


var toString = Object.prototype.toString

var isArray = Array.isArray || function(val) {
    return toString.call(val) === '[object Array]'
}

var isFunction = function(val) {
  return toString.call(val) === '[object Function]'
}

var indexOf = Array.prototype.indexOf ?
    function(arr, item) {
      return arr.indexOf(item)
    } :
    function(arr, item) {
      for (var i = 0, len = arr.length; i < len; i++) {
        if (arr[i] === item) {
          return i
        }
      }
      return -1
    }

萬變不離其宗,本質上還是我們之前的繼承方式,只是在上面再封裝一層,更加清晰,明白了。
還有很多很多的實現,這邊就不一一列舉了。

未來科技的oo實現

其實 es6已經開始重視emcsript的oo實現了。不過還沒定案,就算定案了,也不知道嘛時候javascript會實現。再加上一大堆瀏覽器的跟進。不知道什麽時候才能用的上。不過了解下最新的規範還是很有必要的。

目前nodejs裏面已經實現了 inherite方法用來實現類繼承,類似我們上面的那種實現。

而es6(harmony)實現了class關鍵字用來創建類,並且具有類該有的一系列方法。如下:

class Monster {
  // The contextual keyword "constructor" followed by an argument
  // list and a body defines the body of the class’s constructor
  // function. public and private declarations in the constructor
  // declare and initialize per-instance properties. Assignments
  // such as "this.foo = bar;" also set public properties.
  constructor(name, health) {
    public name = name;
    private health = health;
  }
 
  // An identifier followed by an argument list and body defines a
  // method. A “method” here is simply a function property on some
  // object.
  attack(target) {
    log('The monster attacks ' + target);
  }
 
  // The contextual keyword "get" followed by an identifier and
  // a curly body defines a getter in the same way that "get"
  // defines one in an object literal.
  get isAlive() {
    return private(this).health > 0;
  }
 
  // Likewise, "set" can be used to define setters.
  set health(value) {
    if (value < 0) {
      throw new Error('Health must be non-negative.')
    }
    private(this).health = value
  }
 
  // After a "public" modifier,
  // an identifier optionally followed by "=" and an expression
  // declares a prototype property and initializes it to the value
  // of that expression. 
  public numAttacks = 0;
 
  // After a "public" modifier,
  // the keyword "const" followed by an identifier and an
  // initializer declares a constant prototype property.
  public const attackMessage = 'The monster hits you!';
}

可以看到具有了傳統oo裏面的大部分關鍵字,私有屬性也得到了支持。

繼承也很容易:

class Base {}
class Derived extends Base {}
 
//Here, Derived.prototype will inherit from Base.prototype.
 
let parent = {};
class Derived prototype parent {}

原文在這裏:http://h3manth.com/content/classes-javascript-es6

結語

雖然es6已經實現了正規的class關鍵字。不過等到真正能用上也不知道是何年馬月了。不過規範提供了方向,在es6還沒出來之前,n多大神前仆後繼實現了自己的class方式,分析源碼可以學到的還是很多,僅僅一個類的實現就可以摳出這麽多的類容,程序員還是應該多探索,不能只停留在表面。

原文地址:https://github.com/purplebamboo/blog/issues/14

javascript oo實現