ES6 中 class 和 extends 實現原理
在學習中,我們通常會遇到這種場景,在閱讀某段實現原始碼時我們往往能看懂大部分程式碼,但是卻卡在其中的一兩個點,導致無法繼續閱讀。 所以在這裡我會先列出 class 和 extends 需要的預備知識。
屬性描述符
Object.getOwnPropertyDescriptor()
方法獲取元素的屬性描述,
/**
* 知識點:屬性描述符
* 從 ES5 開始,所有的屬性都具備了屬性描述符
*
*/
var obj = {
name: "jason"
};
/**
* getOwnPropertyDescriptor
* 獲取屬性描述符
*/
var des = Object.getOwnPropertyDescriptor(obj, "name");
console.log(des);
/**
* {
* value: 'jason',
* writable: true, // 可寫
* enumerable: true, // 可列舉
* configurable: true // 可配置
* }
*/
複製程式碼
Object.defineProperty()
定義物件上屬性的一些特性
/**
* Object.defineProperty
* 該方法定義物件上屬性的一些特性
*
* 第一個引數,目標物件
* 第二個引數,要新增或編輯的屬性
* 第三個引數,描述
*/
var obj2 = {};
Object.defineProperty(obj2, "age", {
value: 2,
writable: true,
configurable: true,
enumerable: true
});
console.log(obj2.age); // 2
複製程式碼
writable:
物件上的屬性是否可以修改。相容模式下編輯不可寫元素會編輯失敗,嚴格模式下會報錯。
var obj3 = {};
Object.defineProperty(obj3, "age", {
value: 2,
writable: false,
configurable: true,
enumerable: true
});
obj3.age = 23;
console.log(obj3.age); // 2 不可修改
複製程式碼
configurable:
物件上的屬性是否可以配置
var obj4 = {};
Object.defineProperty(obj4, "age", {
value: 22,
writable: true,
configurable: false, // 不可配置
enumerable: true
});
/**
* 再次定義
* 此時會報錯
*/
// Object.defineProperty(obj4, "age", {
// value: 22,
// writable: true,
// configurable: false,
// enumerable: true
// });
delete obj4.age;
console.log(obj4.age); // 22 屬性刪除失敗,應為屬性不可配置
複製程式碼
enumerable:
當 enumerable 為 false 之後,for...in 不會遍歷到該物件
/**
* enumerable
* 當 enumerable 為 false 之後,for...in 不會遍歷到該物件
*/
var obj5 = {
weight: "66kg"
};
Object.defineProperty(obj5, "height", {
value: "176cm",
writable: true,
configurable: true,
enumerable: false // 不可列舉
});
let keys = Object.keys(obj5);
console.log(keys); // [ 'weight' ] 屬性 height 不可遍歷
複製程式碼
prototype,constructor,__proto__ 三者之間的關係
es6 中類的實現 和 es5 比較
es6 中類的實現
class Parent {
constructor(name) {
this.name = name;
}
/**
* 靜態方法,通過 Parent 呼叫
*/
static getMe() {
console.log("this is super");
}
/**
* 公共方法,在 prototype 上
*/
getName() {
console.log(this.name);
}
}
複製程式碼
es5
實現方式
/**
* 立即執行閉包
* @param {*} Constructor 建構函式
* @param {*} protoProps 原型屬性
* @param {*} staticProps 靜態屬性
* @return {function}
*/
var _createClass = (function() {
/**
* 為屬性新增描述符
* @param {object} target 目標物件
* @param {Array} props 屬性
*/
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);
}
}
/**
* @param {Object} Constructor
* @param {array} protoProps 原型屬性
* @param {array} staticProps 靜態屬性
*/
return function(Constructor, protoProps, staticProps) {
// 原型上屬性, 新增到Constructor 的原型上
if (protoProps) defineProperties(Constructor.prototype, protoProps);
// 類上的屬性, 新增到Constructor 類上
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
/**
* 檢測函式
* es6 中 類不能直接呼叫
* @param {*} instance 例項
* @param {*} Constructor 建構函式
*/
function _classCallCheck(instance, Constructor) {
console.log(instance.__proto__);
console.log(Constructor.prototype);
// instance 是否是 Constructor 的例項
console.log(instance.__proto__ === Constructor.prototype);
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
/**
* 立即執行閉包
* @return {function}
*/
var Parent = (function() {
function Parent(name) {
// 驗證是否是例項化類
// es6不允許類直接呼叫,直接呼叫丟擲異常
_classCallCheck(this, Parent);
this.name = name;
}
_createClass(
Parent,
[
{
key: "getName",
value: function getName() {
console.log(this.name);
}
}
],
[
{
key: "getMe",
value: function getMe() {
console.log("this is super");
}
}
]
);
return Parent;
})();
複製程式碼
es6 中繼承的實現 和 es5 比較
es6
中繼承的實現
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayHello() {
console.log(`hello my age is ${this.age}`);
}
}
複製程式碼
es5
繼承關鍵字實現
/**
* 繼承
* @param {*} subClass 子類
* @param {*} superClass 父類
*/
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
);
}
/**
* Object.create 接受兩個引數
* 指定原型建立物件
* @param {*} 目標原型
* @param {*} 新增屬性
*/
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass, // subClass.prototype.constructor 指向 subClass
enumerable: false, // constructor 不可列舉
writable: true,
configurable: true
}
});
/**
* Object.setPrototypeOf 方法
* 設定子類的 __proto__ 屬性指向父類
* @param {*} 子類
* @param {*} 父類
*/
if (superClass) {
// 設定子類的__proto__ 讓 Child 能訪問父類靜態屬性
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
}
複製程式碼
es5
中繼承的實現
/**
* 呼叫父類的 Constructor 建構函式
*/
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;
}
/**
* @param {*} _Parent 傳入父類
*/
var Child = (function(_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
_classCallCheck(this, Child);
/**
* 呼叫父類的 Constructor 建構函式
*/
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name) // 呼叫父類Constructor
);
_this.age = age;
return _this;
}
_createClass(Child, [
{
key: "sayHello",
value: function sayHello() {
console.log("hello my age is " + this.age);
}
}
]);
return Child;
})(Parent);
複製程式碼
完結
以上就是 es6 class 和 extends 的 es5 實現方式,以上程式碼有一些隱藏細節還需要自學,以下是用到的知識點
- 屬性描述符
- Object.create 引用原型鏈需要指定 Constructor 屬性
- new 關鍵字的實現方式 其中需要設定 __proto__ 屬性
- Object.setPrototypeOf 實現原理 讓子類的__proto__ = 父類
- instanceof 判斷 子類的__proto__ === 父類
- 呼叫子類的 Constructor 也會呼叫父類的 Constructor
- 設定子類的 __proto__ = 父類,讓子類可以呼叫父類靜態方法