JS中萬物皆對象
所以到了es6對一個對象生成器(構造函數)也定義了新的寫法——類(class)使之寫法和麪向對象的寫法更像
現如今我們定義一個一個構造函數就是定義一個類,寫法如下:
class Parent {
constructor(name,age){
this.name = name;
this.age = age;
}
saySomething() {
console.log('Hi, I am Parent');
}
}
這是一個最簡單的類,定義了實例屬性name和age和一個原型方法saySomething
我們知道es6的語法需要編譯成es5的語法後才能在瀏覽器端運行,babel這個工具就可以把我們的es6的代碼編譯成es5,
我們將這段代碼複製到babel編譯器看能得到什麼:
"use strict";
function _instanceof(left, right) {
if ( // 判斷傳進來的構造函數right不爲null並且當前Symbol類型有定義(即可用),並且構造函數的[Symbol.hasInstance]方法能訪問
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left); // 能則把實例傳進構造函數的[Symbol.hasInstance]去看是不是由該構造函數生成的
} else {
return left instanceof right; // 若沒有則直接調用instanceof來判斷
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) { // 判斷調用當前是否是用new操作符調用了函數
throw new TypeError("Cannot call a class as a 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; // 有value值則是可寫的
Object.defineProperty(target, descriptor.key, descriptor); //再調用內部方法defineProperty進行定義
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 添加原型方法
if (staticProps) _defineProperties(Constructor, staticProps); // 添加靜態方法
return Constructor;
}
// 在es5寫法中就是用變量聲明的方式定義了一個Parent的自執行函數,得到一個Parent的構造函數
var Parent = /*#__PURE__*/ (function() {
function Parent(name, age) {
_classCallCheck(this, Parent); // 在這裏爲了確認Parent是通過new方法調用的
this.name = name; // 添加name和age兩個實例方法
this.age = age;
}
// 這一步主要就是爲這個構造函數添加原型方法和靜態方法(如果有就作爲第三個參數傳遞進去,與第二個參數一樣是一個數組)
_createClass(Parent, [
{
key: "saySomething",
value: function saySomething() {
console.log("Hi, I am Parent");
}
}
]);
return Parent;
})();
這裏定義Parent構造函數時主要就是調用了兩個方法_classCallCheck和_createClass.
總結:_classCallCheck是用來確認Parent這個構造函數是不是由new調用的。_createClass.是用來掛載原型方法/靜態方法的。
現在我們理解了在es6中定義一個類實質是發生了什麼。
下一步就是看子類繼承父類(即使用extends關鍵字時)es5中又是怎麼實現的呢
// 現在我們再來定義一個子類,繼承與上面的父類
class Child extends Parent {
constructor(name,age){
super(name,age); // es6中規定子類繼承父類必須在constructor函數裏實現super方法,因爲子類沒有this,必須繼承自父類的this,所以也就是說只能在super後面使用this關鍵字
}
speak(){
console.log("I am Child");
}
}
我們把class Child和class Parent都複製到babel編譯器中,得到如下es5代碼:
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj &&
typeof Symbol === "function" &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
};
}
return _typeof(obj);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function"); // 判斷傳進來的父類是不是null或函數,如果不是則拋出錯誤
}
// 這一步就是重點,用Object.create方法,使子類的prototype的__proto__指向父類的prototype,即共享其原型上的方法/屬性
// superClass && superClass.prototype短路操作符保證存在superClass存在的情況下取superClass.prototype
// 第二個參數爲了正確設置subClass.prototype的constructor指向
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true }
});
// 最後如果superClass存在,則調用_setPrototypeOf
if (superClass) _setPrototypeOf(subClass, superClass);
}
// 這個方法主要就是使用Object.setPrototypeOf方法使子類subClass.__proto__指向父類,實現靜態方法/屬性的繼承
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf || // 如果有Object.setPrototypeOf就調這個方法,沒有就自己實現o.__proto__ = p
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a 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);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
// 構造函數定義在這裏,變量聲明式定義一個自執行函數,返回Parent構造函數,與上面無異
var Parent = /*#__PURE__*/ (function() {
function Parent(name, age) {
_classCallCheck(this, Parent);
this.name = name;
this.age = age;
}
_createClass(Parent, [
{
key: "saySomething",
value: function saySomething() {
console.log("Hi, I am Parent");
}
}
]);
return Parent;
})();
// 主要就是定義子類的時候,我們在使用關鍵字extends來繼承一個類的時候,實際上是把這個類當作參數傳入了這個自執行函數中,現在傳入的是Parent,當然你也可以class Child extends null{}來繼承null,此時就會把null傳入這個自執行函數中
var Child = /*#__PURE__*/ (function(_Parent) { //_Parent就是自執行函數拿到的父類的形參
_inherits(Child, _Parent); // 第一步就是調用_inherits方法,把子構造函數(也就是下面的function Child()函數聲明會有變量提升所以可以先調用再定義)和父構造函數(也就是上面的var Parent這個變量)
var _super = _createSuper(Child); // 這個方法以後再研究。。。hhhhhh(尷尬又不失禮貌的微笑)
function Child(name, age) {
_classCallCheck(this, Child); // _classCallCheck方法同上不在贅述
return _super.call(this, name, age);
}
_createClass(Child, [// _createClass方法同上不在贅述
{
key: "speak",
value: function speak() {
console.log("I am Child");
}
}
]);
return Child;
})(Parent);
通過理解上面的代碼和旁邊的註釋。
總結:我們知道了子類繼承父類使調用了一個最主要的方法_inherits(Child, _Parent);
在其內部實現了兩個繼承:
-
實現了Child.prototype.__proto__ = Parent.prototype的繼承
-
實現了Child.__proto__ = Parent的繼承
前者作爲構造函數調用時,子類繼承父類的原型屬性/方法。
即:
let b = new Child();
b.saySomething(); // Hi, I am Parent
後者作爲對象調用時,使子類繼承父類的靜態屬性/方法。
即:
Child.index; // 1