面向對象
對象是什么?為什么要面向對象?
通過代碼抽象,進而藐視某個種類物體的方式
特點:邏輯上遷移更加靈活、代碼復用性更高、高度的模塊化
對象的理解
- 對象是對于單個物體的簡單抽象
- 對象是容器,封裝了屬性 & 方法
- 屬性:對象狀態
- 方法:對象的能力 & 行為
//簡單對象
const O = {name:'zhangsan',changeName: name => `新的${name}`
}
//函數對象
function O(){this.name = '張三'this.changeName = name => `新的${name}`
}
構造函數 - 生成對象 => 實例
- 需要一個模板
- 類即對象模板
- js本質并不是基于類,而是基于構造函數(創建對象的特殊函數)+原型鏈
function Course() {this.name = 'zhangsan';this.changeName = name => `新的${name}`
}
const course = new Course(args);
Course本質就是構造函數
- 函數體內使用的this,指向要生成的實例
- 生成對象用new來實例化
- 可以初始化傳參
如果構造函數不初始化,可以使用具有相同功能嗎? --無法具有
如果在項目中需要使用,且不希望外界感知的情況下,如何拿到實例后的對象? => 單例模式 (編寫底層api代碼時,盡量做到不讓外部去感知區分內部類型)
function Course(){const _isClass = this instanceof Courseif(!_isClass){return new Course()}this.name = 'zhangsan'this.changeName = name => `新的${name}`
}
//使用
const course = Course()
思考:new是什么?/ new的原理? / new時候做了什么?
function Course(){}
const course = new Course()
- 結構上:創建了一個空對象,作為返回的對象實例
- 屬性上:將生成空對象的原型對象指向了構造函數的prototype屬性(course._ proto _=== Course.prototype)
- 關系上:將當前對象實例賦給了內部的this
- 生命周期上:執行了構造函數的初始化代碼
function usernew(obj,...args){const newObj = Object.create(obj.prototype)const result = obj.apply(newObj,args)return typeof result === 'object' ? result : newObj
}
追問:實例屬性影響 —— 獨立的
function Course(teacher, leader) {this.teacher = teacher;this.leader = leader;
}
const course1 = new Course('張三', '王五'); // course1.leader => 王五
const course2 = new Course('李四', '趙六'); // course2.leader => 趙六
course2.teacher = 'xxxx'; // course1.teacher => 李四
constructor是什么?
function Course(teacher, leader) {this.teacher = teacher;this.leader = leader;}const course = new Course('張三', '李四');
-
每個對象在創建時,會自動擁有一個構造函數屬性constructor
-
constructor源自原型對象,指向了構造函數的引用
-
實例獲得了模版的屬性 => (大膽點)繼承了類的屬性
繼承
js如何實現繼承
在原型對象的所有屬性方法,都可以被實例所共享
function Game() {this.name = 'lol';}Game.prototype.getName = function() {return this.name;}// LOLfunction LOL() {};LOL.prototype = new Game();LOL.prototype.constructor = LOL;const game = new LOL();// 本質:重寫了原型對象方式,將父對象的屬性方法,作為自對象原型對象的屬性方法,同時重寫構造函數
追問:原型鏈直接繼承有什么缺點
function Game() {this.name = 'lol';this.skin = ['s'];}Game.prototype.getName = function() {return this.name;}// LOLfunction LOL() {};LOL.prototype = new Game();LOL.prototype.constructor = LOL;const game1 = new LOL();const game2 = new LOL();game1.skin.push('ss');// 本質:重寫了原型對象方式,將父對象的屬性方法,作為自對象原型對象的屬性方法,同時重寫構造函數
-
- 父類屬性一旦賦值給到子類的原型屬性,此時屬性屬于子類的共享屬性了
-
- 實例化子類時,無法向父類進行傳參
解決方法:構造函數繼承
經典繼承:在子類的構造函數內部調用父類的構造函數
function Game(arg) {this.name = 'lol';this.skin = ['s'];}Game.prototype.getName = function() {return this.name;}function LOL(arg) {Game.call(this, arg);}const game3 = new LOL('arg');// 解決了共享屬性問題 + 子向父傳參的問題
追問:原型鏈上的共享方法無法被讀取繼承,如何解決?
組合繼承
function Game(arg) {this.name = 'lol';this.skin = ['s'];}Game.prototype.getName = function() {return this.name;}function LOL(arg) {Game.call(this, arg);}LOL.prototype = new Game();LOL.prototype.constructor = LOL;const game4 = new LOL('arg');
追問:組合繼承方式就沒有缺點嗎? 問題在于:無論何種場景,都會調用兩次父類的構造函數
解決方案:寄生組合繼承
function Game(arg) {this.name = 'lol';this.skin = ['s'];}Game.prototype.getName = function() {return this.name;}function LOL(arg) {Game.call(this, arg);}LOL.prototype = Object.create(Game.prototype);LOL.prototype.constructor = LOL;const game5 = new LOL('arg');
拔高:如何實現多重繼承?
function Game(arg) {this.name = 'lol';this.skin = ['s'];}Game.prototype.getName = function() {return this.name;}function Store() {this.shop = 'steam';}Game.prototype.getPlatform = function() {return this.shop;}function LOL(arg) {Game.call(this, arg);Store.call(this, arg);}LOL.prototype = Object.create(Game.prototype);Object.assign(Store.prototype,LOL.prototype);LOL.prototype.constructor = LOL;
瀏覽器原理
Promise - 可以處理回調地獄
-
- promise狀態 - pending | fulfilled | rejected
executor: new Promise的時候立即執行,接收兩個參數 resolve | reject
- promise狀態 - pending | fulfilled | rejected
-
- promise默認狀態?狀態是如何流轉的? - 默認:pending 狀態流轉:pending => fufilled | pending => rejected
內部維護成功value:undefined | thenable | promise
內部維護失敗變量reason
- promise默認狀態?狀態是如何流轉的? - 默認:pending 狀態流轉:pending => fufilled | pending => rejected
-
- promise返回值? - then方法:接收onFulfilled 和 onRejected
如果then時,promise已經成功,執行onFulfilled,參數value
如果then時,promise已經失敗,執行onRejected,參數reson
如果then中有異常,執行onRejected
- promise返回值? - then方法:接收onFulfilled 和 onRejected
Promise 方法
- Promise.all 全部執行完成
- Promise.race 有一個執行完成
手寫promise
const PENDING = 'PENDING';const FULFILLED = 'FULFILLED';const REJECTED = 'REJECTED';class Promise {constructor(executor) {// 1. 默認狀態 - PENDINGthis.status = PENDING;// 2. 內部維護的變量值this.value = undefined;this.reason = undefined;// 成功的回調let resolve = value => {// 單向流轉if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}}// 失敗的回調let reject = reason => {// 單向流轉if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;}}try {executor(resolve, reject);} catch (error) {reject(error);}}then(onFulfilled, onRejected) {if (this.status === FULFILLED) {onFulfilled(this.value);}if (this.status === REJECTED) {onRejected(this.reason);}}}// 追問:異步怎么辦?const PENDING = 'PENDING';const FULFILLED = 'FULFILLED';const REJECTED = 'REJECTED';class Promise {constructor(executor) {// 1. 默認狀態 - PENDINGthis.status = PENDING;// 2. 內部維護的變量值this.value = undefined;this.reason = undefined;// 存放回調this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];// 成功的回調let resolve = value => {// 單向流轉if (this.status === PENDING) {this.status = FULFILLED;this.value = value;this.onResolvedCallbacks.forEach(fn => fn());}}// 失敗的回調let reject = reason => {// 單向流轉if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;this.onRejectedCallbacks.forEach(fn => fn());}}try {executor(resolve, reject);} catch (error) {reject(error);}}then(onFulfilled, onRejected) {if (this.status === FULFILLED) {onFulfilled(this.value);}if (this.status === REJECTED) {onRejected(this.reason);}if (this.status === PENDING) {// 存放隊列this.onResolvedCallbacks.push(() => {onFulfilled(this.value);});this.onRejectedCallbacks.push(() => {onRejected(this.reason);});}}}