親自上手es6的class

新一代標準已經提出有些日子了,最近在寫react的時候,react用了大量的class的語法去寫組件,對于class的理解不是很深刻,于是這里把從babel轉換的代碼分享給大家。

類=構造函數+原型

es6標準的類和其他語言的類很相似,但是這只是一種語法糖,底層還是通過原型繼承實現的,首先看一個簡單的類的形式

 class A {
   constructor(){
     this.name='xiaoming'
   }
   sayHello(){
     console.log('hello')
   }
 }

包含一個構造函數,和一個類里面的函數,如果大家對原型繼承有所了解的話,這種形式可以近似寫成

function A(){
  this.name='xiaoming';
}
A.prototype={
  sayHello:function(){
    console.log('hello')
  }
}

這就是類的雛形,但是實際操作上還是有些不同的,下面就是babel翻譯的es5的語法

'use strict';

var _createClass = function () { 
function defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i]; 
    descriptor.enumerable = descriptor.enumerable || false; 
    descriptor.configurable = true; 
    if ("value" in descriptor)
      descriptor.writable = true; 
    Object.defineProperty(target, descriptor.key, descriptor);
   } 
}
return function (Constructor, protoProps, staticProps) {
  if (protoProps) 
    defineProperties(Constructor.prototype, protoProps);
  if (staticProps) 
    defineProperties(Constructor, staticProps); 
return Constructor; 
}}();

function _classCallCheck(instance, Constructor) { 
  if (!(instance instanceof Constructor))  {
    throw new TypeError("Cannot call a class as a function");
  } 
}

var A = function () {
  function A() {
    _classCallCheck(this, A);
    this.name = 'xiaoming';
  }

  _createClass(A, [{
    key: 'sayHello',
    value: function sayHello() {
      console.log('hello');
    }
  }]);

  return A;
}();

這個代碼感興趣大家可以看看,其中有幾個地方需要注意,這個類A不能當成函數去調用,A()這種方法調用會報錯,可以

let a = new A();

這樣去實例化一個類,當然類都是需要繼承的,新版本中繼承用extends來實現,考慮這樣一個類B繼承類A

 class A {
  constructor(){
    this.name='xiaoming'
  }
  sayHello(){
    console.log('hello')
  }
}
  class B extends A{
  constructor(){
    super() 
    this.age=12
  }
  sayHello(){
    console.log('hello')
  }
}

這是加完繼承后的代碼

'use strict';

var _createClass = function () { 
function defineProperties(target, props) { 
...//和前面一樣
 }();

function _possibleConstructorReturn(self, call) { if (!self) {
  throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

function _inherits(subClass, superClass) { 
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function,not " + typeof superClass); 
  } 
  subClass.prototype = Object.create(superClass && superClass.prototype, {
  constructor: {
    value: subClass, 
    enumerable: false,
    writable: true,
    configurable: true 
  }
 }); 
if (superClass) 
  Object.setPrototypeOf ?
  Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) { 
  throw new TypeError("Cannot call a class as a function");
  } 
}

var A = function () {
  function A() {
    _classCallCheck(this, A);

    this.name = 'xiaoming';
    this.say = function () {
      console.log("123");
    };
  }

  _createClass(A, [{
    key: 'sayHello',
    value: function sayHello() {
      console.log('hello');
    }
  }]);

  return A;
}();

var B = function (_A) {
  _inherits(B, _A);

  function B() {
    _classCallCheck(this, B);

    var _this = _possibleConstructorReturn(this, (B.__proto__ || Object.getPrototypeOf(B)).call(this));

    _this.age =12;
    return _this;
  }

  _createClass(B, [{
    key: 'sayHello',
    value: function sayHello() {
      console.log('hello');
    }
  }]);

  return B;
}(A);

代碼很長,都不用看,只看_inherits這個函數,從這一句

subClass.prototype = Object.create(superClass && superClass.prototype, {
  constructor: { 
    value: subClass, 
    enumerable: false, 
    writable: true, 
    configurable: true 
  }
});

這里有個小技巧,&&當計算前面為false時,就不計算后面的表達式了,當然返回的是false或者最后一個值,這是從左向右計算的,這里意思就是如果 superClass 存在,那就計算 superClass.prototype,當然也就是存在的,這一句就是將B的原型設為一個以 A 的 prototype 為原型的對象,也就是說

B.prototype.__proto__=A.prototype

proto VS prototype

這里__proto__這個屬性是每個對象都有的,就是因為這個屬性的存在,對象可以繼承很多不是他自己的屬性或方法,比如toString()。雖然toString()不是這個對象自己的方法,但是去調用一個對象的這個方法時,這個對象自己沒有,就會去找這個對象的__proto__,在它的__proto__所指向的對象中去找,如果找不到,就會繼續去在這個__proto____proto__中去找,這就形成了一個原型鏈,直到找到為止,找不到就會報錯。
prototype__proto__之間的區別很明顯,prototype是函數對象所特有的,他作為一個屬性指向另一個對象,即這個函數的原型對象,它存在的目的只是為了生產對象,通過這個函數new出來的對象都有一個__proto__屬性指向這個函數的原型對象,從下面代碼就可以看出來

function A(){
  this.name='xiaoming'
}
A.prototype={
  sayhi:function(){
    console.log('hi')
  }
}
var a=new A();
console.log(a.__proto__===A.prototype)
// true

也就是說,對象的__proto__屬性指向那個制造這個對象的構造函數的原型對象,通過對象字面量形式創建的對象的__proto__就是Object.prototype,

o = {};// 以字面量方式創建的空對象就相當于:
o = Object.create(Object.prototype);

那么繼續之前的話題,_inherits函數中有這一句

if (superClass) 
Object.setPrototypeOf ? 
Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 

這樣子類的__proto__就指向了父類,就將原型鏈頭指向了父類,也就是在B中可以使用A的方法和屬性,注意是在B這個構造函數內,B的原型對象在之前的代碼已經解釋了,通過Object.create方法把B的原型對象綁定到了A的原型上,B的原型對象可以通過原型鏈原型繼承使用A的原型對象的屬性和方法。
總之最后的情況是這樣的

B.__proto__=A
B.prototype=對象xx
對象xx:{
__proto__:A.prototype
constructor:B
...
}

當使用new去創建一個B的實例b時會發生這樣的過程
在constructor中會得到以this.xx,比如this.age this.age...
等等還有一個不能忘了在B的構造函數中會有個super(),這樣A的構造函數也會執行了,不然沒有name屬性
然后就是將這個對象的__proto__指向那個對象xx,也就是B這個構造函數的原型對象,這樣就能訪問這個原型鏈上的屬性和方法了。

總結

新版本的類也是基于原型繼承的,所以只要把基礎打好了,遇到新的東西也理解的比較清楚,class中constructor對應的還是以前的構造函數,整個類里面的內容就是這個構造函數的原型對象的內容,如果有繼承還要加上繼承的對象的內容,我們依然可以用類名xx來指代以前的構造函數,xx.prototype來指代原型對象。新的語法形式,對外隱藏了實現的細節,寫起來更加簡潔,還有會在不正當時使用時的錯誤提示。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • class的基本用法 概述 JavaScript語言的傳統方法是通過構造函數,定義并生成新對象。下面是一個例子: ...
    呼呼哥閱讀 4,129評論 3 11
  • 官方中文版原文鏈接 感謝社區中各位的大力支持,譯者再次奉上一點點福利:阿里云產品券,享受所有官網優惠,并抽取幸運大...
    HetfieldJoe閱讀 3,023評論 4 14
  • 特別說明,為便于查閱,文章轉自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 1,160評論 0 4
  • 在JavaScript中,原型鏈作為一個基礎,老生長談,今天我們就來深入的解讀一下原型鏈。 本章主要講的是下面幾點...
    Devinnn閱讀 1,418評論 1 6
  • 大概有四年了,連暗戀都沒有過,愛情匱乏得像一片荒蕪的沙漠。但是最近,最近好像喜歡上了一個人,想起他,嘴角就不自覺上...
    淳安閱讀 250評論 0 1