面向對象相信對你們來講確定不陌生,基本上現代web
開發不會這玩意以及無法寫東西了javascript
可是呢,用歸用,相信你們日常工做中須要寫原生對象的地方並無多少,本篇文章來對面向對象
進行一個總結和梳理html
我們先來講一下概念
,由於不一樣人叫面向對象名字也不一樣,這裏屬性
和變量
意思都是同樣的,我們不去糾結他們具體叫什麼vue
對象組成能夠分爲兩大類:java
Math
這種基本全是靜態方法的類str.length
不能直接寫成String.length
)Math.xxx
),無需實例化就能夠用打個比方let oDate = new Date()
這個Date
就是類,而這個oDate
就是實例node
而絕大部分人再說對象的時候都是在說
實例
react
抽象
這個詞相信你們已經快聽吐了,可是又不是很好理解,這裏大概說一下ios
抽象
react
舉個例子react
中寫組件須要class App extnds React.Component...
而這裏面必需要有一個render
方法,若是沒有,它就會給你報錯hook
什麼的,別槓我,我懼怕react
中的這個Component
就是實現了一個抽象類
這個詞兒也是被用爛了,你們無論是從別的文章看過也好,仍是找過什麼資料,看了一大堆,仍是不明白啥叫面向對象思想
es6
封裝的最基本的目的是——爲了保護我們類
或者實例
當中的各類成員
web
事實上來講我們無論寫什麼
面向對象
也好,面向過程
也好,面向切片
等等等等,最終的目的就是爲了讓這個程序出錯少,效率高,易於調試和拓展面試
而接下來我們就能想到,程序都是人寫的,人確定就會犯錯,容易偷懶或者僥倖心理
而我們封裝的類,每每會有不少的數據須要內部處理,好比有一個請求隊列的列表,你在你內部聲明好了,而後若是想取消須要用戶直接調你的方法,可是若是不保護的化,可能有人犯懶直接就看到這是個數組直接就操做了,這時候若是出錯了或者這個類有其餘的地方沒作相應的操做就很難查找
因此這個封裝大概有四重目的
任何一個類,能夠不用從零開始寫,能夠在一個原有的類的基礎之上作一些修改等等這就叫繼承
這個繼承相信你們仍是沒問題的,這裏很少贅述
多態也屬於一種抽象,也是一種比較經常使用的思想,好比如今公司有一個轉帳的功能,支持多個國家互相轉的,那這個錢不同匯率也就不同,以及其餘的一些問題
固然這時候直接用if
判斷確定也是能夠的,可是這不是感受不夠裝*
麼,這時候就能夠用多態,把這個錢統一處理,至於具體匯率什麼的細節,分別交給他們本身來處理,這個思想,就是多態
相信看到這你們應該已經明白大概的意思了,不過對於初學者來講仍是不太明白到底具體到項目能怎麼寫,別慌,一步一步來
有一點須要確認,你們可千萬別覺得
面向對象是語言所獨有的東西
,好比java
是面向對象的,c
是面向過程的,其實也不是,面向對象思想幾乎在全部的地方都有用
好比說數據庫,也有面向對象數據庫——ORM
,它裏面存的就不是像excel
那樣的數據了,而是一個對象,有相應匹配的操做等等一系列的
首先我們要知道,任何一種類,都須要構造函數
,什麼叫構造函數
呢,很簡單,就是當你這個類實例化的時候,須要作一些初始化
的工做
在es6
以前,類和構造函數是不分的,這也是很很差的一點,如今若是想實現一個類,直接拽一個function
在這很難分辨就是是一個函數
仍是類
,因此,這個函數既是構造函數也是類
我們來直接寫一個看看
function A() {
console.log('hello');
}
var a = new A();
複製代碼
能夠看到,在我們實例化的過程中,就會運行函數內的代碼
以及,我們若是相加屬性和方法也是很簡單的
屬性和es6
版本同樣,直接加this
就行
方法放在prototype
上,這個沒什麼可說的
es6
以前沒給我們提供直接繼承的方法,因此得我們手動操做,因此我們得明白繼承是幹什麼 繼承就是把父類的方法+屬性拿過來就能夠了
首先,我們先來隨便寫一個類出來,像這樣
而後搞一個子類,而且新增一個參數,而後我們把參數先拿過來,怎麼拿呢? 如今我們的類是一個函數,是函數就能執行,我們只須要把A
拿過來執行一下而且call
到本身身上,再把A
須要的參數傳過去,這樣就能拿到全部的參數了,像這樣
好的,屬性我們如今已經能夠拿過來了,那麼方法怎麼拿過來了,方法在哪,在A
的prototype
上,那能直接B.prototype = A.prototype
麼?首先確定一點,這麼寫,東西確定能出來,不過問題你們也知道,js
裏存在引用
,給子類添加方法父類也被修改了這確定不行
不囉嗦了
我們能夠直接B.prototype = new A()
我們能夠看到,東西是能出來的,父類的方法也沒有被污染到,這是爲何呢,這裏就不得不搞出來原型鏈這個概念了
有大白話講就是 找一個實例要東西,它會先從本身實例身上找,找不到的話再找本身的prototype
,可是我們如今這個prototype
指向的就是A
的實例,因此從B
實例找不到後去找prototype
的時候,找不到就回去找A
的實例,A
的實例找不到就回去找A
的prototype
而後就能夠了
好玩不
相信看到這,你們應該已經明白es6
以前的寫法缺陷了,功能確定是都能實現,可是太亂了,一個團隊好幾十人,你搞你的,我搞個人就亂套了
我們es6
寫class
就簡單多了,直接提供了一個關鍵字class
,直接寫就好了,不過本質上來講只是語法糖而已,因此建議你們仍是看看es6
以前的寫法
寫法也是很簡單的,注意一點,這裏面的方法並非函數的簡寫,寫一個function
的話,反而會報錯
繼承es6
也有專門的關鍵字來講明,就叫extends
很簡單對不對?
注意⚠️ 在子類什麼都不寫的狀況下,默認會給你加一個
constructor
,若是你本身寫了constructor
,就至關於你須要本身來了
主要要乾的就是須要把父類
的屬性拿過來,es6
有一個super
關鍵字,就至關於直接把父類
的constructor
直接在子類裏執行了一遍,不用像以前同樣要麼apply
,要麼call
的,很亂
注意⚠️ 在我們構建子類的時候,須要完成父類的構建,也就是那個
super
,若是先用this
後super
的時候會報錯
this
相信你們確定常常用,而且一會變成這個,一會變成那個,特別的亂 this
取決於誰在調用,this
就是誰 而this
是用於函數
/方法
內部,而誰在調用這個函數,this
就指向誰
js
自己又是全局的東西都屬於window
,因此我們這麼寫
function fn() {
console.log(this);
}
fn();
複製代碼
就徹底等價於這麼寫
function fn() {
console.log(this);
}
window.fn();
複製代碼
因此console
出來的this
也是window
我們可能看過不少面試題都有相似這樣的題目
總的來講就是一個函數給這個對象
那個對象的,而後打印this.xxx
,其實想明白這件事,一切都會變得很簡單了
如今這個arr.fn
= fn
了,而調用的時候,是arr
在調用,因此這個this
就是arr
固然了,事件
也是同樣的
很簡單對不對?
順便一說,js的
this
這麼亂是由於做者自己想讓它變得更簡單,誰調就是誰,多好呀~ 不過這每每在寫一些大項目的時候會有一堆問題
而js
做者後來又出了一個嚴格模式,由於全局的東西都屬於window
這事兒原本也不靠譜,畢竟js
運行場景已經不少了,好比nodejs
,就沒有window
的概念
而用嚴格模式也很簡單,直接在script
里加一個"use strict"
就能夠,像這樣
這時候console
出來的就是undefined
了
不過這個this
還收一點影響,也就是定時器
能夠看到我如今是開着嚴格模式
的狀況下,可是console
的仍是window
不過這卻是也好解釋,畢竟這個定時器
是window
的,是瀏覽器調的,也是經過window
間接的來執行到了這個函數
js
中有兩種操做this
的方法
函數的方法
new
了一個Function
類,因此這麼寫function fn() {
alert(this);
}
複製代碼
徹底等價於
var fn = new Function('alert(this)');
複製代碼
call
fn();
,用了這個直接在括號前加一個call
就能夠,像這樣
this
就是什麼,隨便傳,想這個例子,console
的this
就是"aaa"
apply
apply
就很簡單了,和call
基本一摸同樣,區別在於call
跟參數是直接堆在後面,apply
的其餘參數是放在一個數組裏的,像這樣
bind
bind
跟前二者不同,call
和apply
都是直接運行了,bind
的做用是返回一個新函數
this
都是不變的箭頭函數
this
永遠跟剪頭函數外部的this
一致,也就是上下文
document.onclick
裏的函數的this
指向HTMLDocument
,因此找不到aaa
,會console
出undefined
,這個很好理解
class A
裏,這就沒問題了bind
也是能夠的,這不是方便麼typeof更適合檢測基本類型:number、boolean、string、function、Object、undefined
這個相信你們也都用過,也就很少說了,檢測基本類型表明我們分不清一個對象究竟是數組仍是json
或者是map
等等 全部對象全都是object
,那這個確定知足不了需求
這個instanceof
是能夠檢測具體類型的,並且不光能夠檢測子類,對子類的父類也能檢測到
其實這不能算是問題,由於正常來講子類原本就>=
父類
可是有時候咱們就是不想對父類也有反應,就是想檢測是否是屬於我這個類,這是有直接用constructor
來判斷就很方便 constructor
是能夠用於精確匹配的
這個constructor
極少的狀況會用到,自己並非用來判斷類型的,而是返回實例的構造器
,不過正好我們能夠間接作一個類型判斷而已 正常狀況用typeof
和instanceof
就已經足夠了
固然了,可能有人看到es6
以前的class
寫法已經要噴我了,由於那麼寫用constructor
判斷又有問題,由於那麼寫子類
的constructor
就會變成父類
了,因此還須要重置一下
等等等等把,es6
以前的寫法等等須要處理細節方面還不少,由於如今實在是不經常使用,因此本文就很少贅述了
高階類這個詞可能有人沒聽過,這裏簡單描述一下
通常狀況下,都是子類繼承父類,而後子類可使用父類當中的東西,而這個高階類,能夠反過來,父類使用子類的東西
順便一說,不是隻有
js
纔有高階類的概念,幾乎全部語言都有,只是方不方便的問題
我們直接來寫一個
能夠看到,直接在class A
上,並無bbb
這個東西,而直接子類去繼承的時候,子類身上有,間接着就能夠調用了
這就是所謂的高階類
,賊簡單吧
不過直接這麼說你們可能感受不到有什麼應用場景,其實也不太經常使用,在寫一些工具或者框架的時候可能會用到,這裏簡單寫個小例子,多個類之間如何用高階類共享數據 固然了,高階類的用途有不少,這只是其中一種而已
class Store {
constructor() {
this.state = {};
}
get(key) {
return this.state[key];
}
set(key, value) {
this.state[key] = value;
}
conect(cls) {
let _this = this;
return new (class extends cls {
constructor(...args) {
super(args);
this.get = _this.get.bind(_this);
this.set = _this.set.bind(_this);
}
})();
}
}
let store = new Store();
let a = store.conect(class {});
a.set('aaa', '我是aaa');
let b = store.conect(class {});
console.log(b.get('aaa'));
複製代碼
這個例子很簡單,主要就是要分清誰是父類,我們把數據全存到store
類裏,而後接受一個類參數,內部繼承一個新的類,而後添加出新的方法,而在使用者來講,也就是a
類和b
類,這些都不用管,他們本身就是父類,來調用子類的方法
是否是很簡單?
大概就是這種感受,至於工做當中怎麼用,就看你們本身的業務場景了
所謂的可相應對象就是你操做這個對象的時候能夠收到通知,好比vue
,你們應該都知道vue
頁面,就算直接在瀏覽器f12
控制檯中,直接vm.xxx=xxx
對某一項數據修改,頁面中就會發生變化,這就是可相應對象
這個訪問器就是在原型的方法前加get
或者set
,至於內部要幹什麼,你本身決定,像這樣
用過ts
版的vue2.x
的你們都知道,computed
就是用這玩意的原生語法
訪問器仍是比較簡單的,在一些小的場景中用的比較多
defineProperty
接受三個參數(data,key, option)
這個data
就是你的數據源,這個key
就是你要監聽哪一個屬性,剩下的具體的操做放在option
裏,好比get
和set
等等,我們直接來試試
var json = {
_num: 0,
};
Object.defineProperty(json, 'num', {
get() {
console.log('get');
return this._num;
},
set(val) {
console.log('set');
this._num = val;
return this._num;
},
});
console.log(json.num);
json.num++;
console.log(json.num);
複製代碼
是否是很簡單,固然了,細節上還有不少,好比delete
,我們知道正常狀況下js
中的json
是能夠直接delete
某個key
的,可是我們如今直接這麼寫是不行的
我們能夠給一個參數configurable: true
,默認的狀況是false
更多參數介紹你們能夠看MDN文檔中的defineProperty,這裏就很少贅述
我們這裏用的只是json
,你們可能會感受跟訪問器
區別不大,並且,平時我們也不是直接對json
用,而是像vue 2.x
同樣又能操做實例,又能this
直接訪問或修改屬性
vue2.x
中能夠這麼修改屬性
知道了這點以後我們就能夠幹不少事兒了,我們能夠直接監聽這個類
來進行return
這樣我們就能夠監聽到變化了,能監聽到變化了天然就能夠緊接着幹其餘的事了,好比從新渲染頁面等等
固然了,真正
Vue
中可徹底不是這麼寫的,由於它東西不少,並且全部數據都須要監聽,我們如今只是單單監聽一個對象裏的key
,方法我們也知道,循環+遞歸
麼,不過要把這些東西整理出來其實也是很複雜的,好比頁面屬性怎麼跟你的data
綁定,組件間的傳值,渲染、虛擬dom等等
等等吧,若是真拓展成一個方便的框架或者小工具仍是有不少工做的, 這裏只是說明方法,有什麼更方便的用法和場景還須要你們本身鑽研
用過vue
的你們都知道vue
中是不能直接用下標修改數組中的某一條數據的,vue
做者也推出了$set
解決這個問題,這個就是defineProperty
的問題
json
對象沒有的key
賦值你們能夠看到其實還好,雖然問題不大,不過沒有確定比有強,proxy
沒這個問題,爲啥不直接用proxy
呢
和defineProperty
有點不一樣,defineProperty
是操做監聽的原始對象,而proxy
是操做返回出來的新對象
有兩個參數,一個是數據,一個是對應的操做,這裏面有幾個經常使用的參數
js
中的in
操做
"a" in {a: 12}
這麼判斷data
就是原始數據,基本上每一個方法裏都會把原始數據給你,很方便delete
,只不過delete
是關鍵字,因此取了個這個名字
apply
其實頗有用,它能夠監聽一個函數,在編寫一個axios這樣的庫 我也用到了,有興趣你們能夠看一下
apply
有三個參數,第一個仍是data
,可是因爲我們是監聽函數,因此就是那個函數自己,第二個就是誰在調用的那個this
,上文講過誰調用函數this
就指向誰,因此這個第二個參數就是那個this
,第三個參數就是我們運行這個函數的時候傳進來的參數
class
,一個是參數,其實用起來感受跟高階類有點類似proxy
,由於要監聽我們通常都是要監聽這個類上的屬性有沒有改變之類的,因此還要單獨再監聽一遍實例返回出去剩下的參數你們感興趣的話也能夠去MDN中的Proxy去了解,也很少贅述
看到這你們應該都對js
中的可監聽對象瞭解的差很少了,這裏附上一個相對完成一點的監聽例子
相似vue
中的data
,data
能夠是值,也能夠是json
,也能夠是數組,數組裏套json
,json
套數組等等,我們能夠分別判斷一下而後套一個遞歸就搞定了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script> function createProxy(data, cb) { let res; if (data instanceof Array) { res = []; for (let i = 0; i < data.length; i++) { if (typeof data[i] == 'object') { res[i] = createProxy(data[i], cb); } else { res[i] = data[i]; } } } else { res = {}; for (let key in data) { if (typeof data[key] == 'object') { res[key] = createProxy(data[key], cb); } else { res[key] = data[key]; } } } return new Proxy(res, { get(data, name) { return data[name]; }, set(data, name, val) { data[name] = val; cb(name); return true; }, }); } let _json = { arr: [ { a: [1, 2, 3], }, 321, ], json: { aaa: { bbb: { ccc: 111, }, }, }, name: 'name', }; var p = createProxy(_json, function(name) { console.log('set'); }); </script>
</body>
</html>
複製代碼
若是有問題的話能夠直接在評論區留言或者加我微信一塊兒溝通