自從面向對象的編程思想出現以來,這個概念已經被炒爛了,只要編程開發你們都會拿面向對象來講事,好像只要跟面向對象沾邊就會顯得逼格很高同樣,不過確實逼格提升了。要知道,面向對象只是一種手段,最終目的是爲了提升咱們項目的重用性、靈活性和擴展性。前端
爲了迎合面向對象的程序設計思想,JavaScript也經過本身的語法實現了本身的一套面向對象機制。不過我想問下,前端開發當中有多少人使用過面向對象當中的繼承?程序員
JavaScript面向對象的實現確實有點不三不四的感受。下面先簡單說明下JavaScript當中面向對象的實現,涉及的東西比較深,要對constructor、prototype有必定的理解,否則看起來會很吃力,或者你也能夠跳開這部份內容,直接看CoffeeScript面向對象的實現。編程
JavaScript的實現一個類,能夠採用下面的方式:後端
function Animal(name) { this.name = name; } Animal.prototype.printName = function () { console.log(this.name); }; var animal = new Animal('animal'); animal.printName();
C++、Java...某些程序員可能就吐槽了,我TM都連個關鍵字class都沒看到,這就是類了?app
做爲一個前端開發,我竟無言以對。。。函數
選取JavaScript當中幾種繼承方式,做一個簡單的說明,想要學習更深的內容,請經過搜索引擎吧。學習
function Animal(name) { this.name = name; this.printName = function () { console.log(this.name); }; } function Cat(name) { this.inherit = Animal; this.inherit(name); //Animal.call(this, name); //Animal.apply(this, [name]); } var cat = new Cat('cat'); cat.printName();//cat
註釋是經過call和apply實現的方式,其實本質是同樣的。繼承實現了,閒着無聊,咱們來打印下:this
console.log(cat instanceof Animal);//false
此次別說後端開發看不下去,我都忍不了了。這種方式只能說是經過JavaScript的語法機制,模擬實現繼承,看起來好像是那麼回事,否則怎麼叫對象冒充呢。搜索引擎
function Animal(name) { this.name = name; } Animal.prototype.printName = function () { console.log(this.name); }; function Cat() { } Cat.prototype = new Animal('cat'); var cat = new Cat(); cat.printName();//cat
打印:spa
console.log(cat instanceof Animal);//true
此次對了,cat也是Animal類的實例了,有點面向對象的的意思了。我又閒着無聊了(約麼?),再來打印
console.log(cat.constructor); //[Function: Animal]
咦,又不對了,爲啥cat的constructor指向Animal,不該該指向Cat麼?
問題出在Cat.prototype = new Animal('cat')上,直接給prototype賦值,改變了constructor的指向,這個時候,咱們還要作個處理
function Animal(name) { this.name = name; } Animal.prototype.printName = function () { console.log(this.name); }; function Cat() { } Cat.prototype = new Animal('cat'); Cat.prototype.constructor = Cat; var cat = new Cat(); console.log(cat.constructor); //[Function: Cat]
可是又有人吐槽了,new Cat()爲啥把名稱放到new Animal()裏面,看起來太奇怪了,綜合一下就有了第三中——混合型。
實際上instanceof的判斷原理是跟原型鏈是相關的。你們自行腦洞!
function Animal(name) { this.name = name; } Animal.prototype.printName = function () { console.log(this.name); }; function Cat(name) { Animal.call(this, name); } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat = new Cat('cat'); cat.printName();//cat
看起來舒服點了,也就是舒服那麼一點點。
直接拿混合型的繼承來講明瞭,這個比較簡單
function Animal(name) { this.name = name; } Animal.prototype.printName = function () { console.log(this.name); }; function Cat(name) { Animal.call(this, name); } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; Cat.prototype.printName = function () { console.log('The name is:' + this.name); }; var cat = new Cat('cat'); cat.printName();//The name is:cat
CoffeeScript的面向編程的語法同JavaScript比較起來,真是天上地下。一個陽春白雪,一個下里巴人。可是有一點咱們要記住:CoffeeScript只是編譯到JavaScript,它只是在JavaScript的基礎上進行了語法的抽象,本質上仍是JavaScript。
CoffeeScript採用class關鍵字聲明類,整個語法看起來更加簡明流暢。
#編譯前 class Animal constructor: (name)-> @name = name printName: -> console.log(@name) animal = new Animal 'animal' animal.printName() #animal #編譯後 var Animal, animal; Animal = (function () { function Animal(name) { this.name = name; } Animal.prototype.printName = function () { return console.log(this.name); }; return Animal; })(); animal = new Animal('animal'); animal.printName();
constructor是構造函數,就上面的例子來講,還能夠簡寫,實際上效果是同樣的
class Animal constructor: (@name)-> printName: -> console.log(@name) animal = new Animal 'animal' animal.printName() #animal
CoffeeScript將咱們習慣性的書寫方式變成豐富的語法糖。說到這裏我就想說一句了,能不能把構造函數換個字符,constructor丫太長了。
繼承使用的是extends關鍵字
#編譯前 class Animal constructor: (@name)-> printName: -> console.log(@name) class Cat extends Animal cat = new Cat 'cat' cat.printName() #cat #編譯後 var Animal, Cat, cat, extend = function (child, parent) { for (var key in parent) { if (hasProp.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; }, hasProp = {}.hasOwnProperty; Animal = (function () { function Animal(name) { this.name = name; } Animal.prototype.printName = function () { return console.log(this.name); }; return Animal; })(); Cat = (function (superClass) { extend(Cat, superClass); function Cat() { return Cat.__super__.constructor.apply(this, arguments); } return Cat; })(Animal); cat = new Cat('cat'); cat.printName();
咱們簡單分析下編譯後的extend函數,對JavaScript原型鏈不是很熟的能夠跳過這段。兩個參數分別是子類child和父類parent,第一段代碼:
for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; }
這段代碼就是將父類上面的屬性拷貝到子類上,由於JavaScript當中函數也是對象,能夠擴展屬性的。什麼意思?看代碼
class Animal constructor: (@name)-> printName: -> console.log(@name) Animal.prop = 'Animal prop' class Cat extends Animal console.log Cat.prop #Animal prop
第二段代碼:
function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor();
可能你們看不大明白,我稍微改動下,換種寫法
child.prototype = new parent(); child.prototype.constructor=child;
這裏就是咱們上面提到的原型鏈繼承。再看最後段代碼:
child.__super__ = parent.prototype;
這裏是爲了在子類中調用父類的方法,實現多態,看下面的例子就知道了。
編譯後的代碼太長,就不粘貼了,看CoffeeScript代碼更易於學習。
class Animal constructor: (@name)-> printName: -> console.log(@name) class Cat extends Animal printName: -> console.log 'Cat name:' + @name cat = new Cat 'cat' cat.printName() #Cat name:cat
class Animal constructor: (@name)-> move: (meter)-> console.log(meter) class Cat extends Animal move: -> console.log 'Cat move' super 4 cat = new Cat 'cat' cat.move() #Cat move 4
有任何問題,歡迎你們批評指出,咱們共同進步。