- 樓盤(pán)表
核心功能: 文件上傳,文件預(yù)覽,查看樓盤(pán)表數(shù)據(jù)
我做的事情:面包屑公共組件開(kāi)發(fā),文件上傳公共組件開(kāi)發(fā)
項(xiàng)目難點(diǎn):
實(shí)現(xiàn)批量文件上傳,可配置化文件上傳組件
可點(diǎn)擊跳轉(zhuǎn)and刷新頁(yè)面的面包屑并且保留參數(shù),自定義路由關(guān)系自動(dòng)映射到面包屑
根據(jù)部署方式,調(diào)整打包方式
權(quán)限系統(tǒng)
面包屑
公共tabs組件
面包屑和公共tabs組件與路由關(guān)聯(lián)
項(xiàng)目挑戰(zhàn),難度在于它是一個(gè)長(zhǎng)期的過(guò)程。
大量的類(lèi)似頁(yè)面,如何提高項(xiàng)目開(kāi)發(fā)效率,快速迭代,這些頁(yè)面分布在不同的系統(tǒng)中,封裝了大量頁(yè)面級(jí)別的業(yè)務(wù)組件。
針對(duì)大量類(lèi)似頁(yè)面,我們需要做哪些事情前期溝通
產(chǎn)品,交互在不影響用戶(hù)體驗(yàn)的前提下,多個(gè)版本盡量保持一致
設(shè)計(jì),設(shè)計(jì)會(huì)和前端形成一套設(shè)計(jì)規(guī)范,設(shè)計(jì)組件規(guī)范
后端,接口一致性,多個(gè)項(xiàng)目迭代,接口數(shù)據(jù)結(jié)構(gòu)一致性,字段盡量一致
前端,架構(gòu)搭建,組件和頁(yè)面組件的編寫(xiě)。tree
如何部署,靜態(tài)資源部署在cdn,html部署在weblogic
隨筆技術(shù)
- seo
服務(wù)端渲染
直出,在服務(wù)器端獲取數(shù)據(jù)并渲染完成頁(yè)面后給到前端,用戶(hù)能夠直接看到頁(yè)面,這樣既解決了首屏問(wèn)題(服務(wù)器端獲取數(shù)據(jù)速度快)也解決了seo的問(wèn)題。
同構(gòu),瀏覽器端會(huì)在執(zhí)行一次,所以要進(jìn)行一次判斷,如果數(shù)據(jù)已經(jīng)獲取,那么就不要再獲取數(shù)據(jù)。
在當(dāng)前作用域之外執(zhí)行的函數(shù)就是閉包,定義時(shí)確定作用域,運(yùn)行時(shí)確定this,作用域鏈?zhǔn)腔谡{(diào)用棧的。
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('')
}
}
let args = Array.prototype.slice.call(arguments, 1),
fToBind = this, // this指向需要被綁定this的函數(shù)
fNOP = function() {},
fBound = function() {
}
}
函數(shù)是對(duì)象的子類(lèi)型,可以像使用對(duì)象一樣使用函數(shù),new 出來(lái)的實(shí)例可以通過(guò)instance判斷是否是new 出來(lái)的實(shí)例
- [[ Prototype ]]
js的對(duì)象中有一個(gè)特殊的[[Prototype]]內(nèi)置屬性,其實(shí)就是對(duì)于其他對(duì)象的引用。當(dāng)你試圖引用對(duì)象屬性時(shí)會(huì)觸發(fā)[[GET]]操作,對(duì)于默認(rèn)的get來(lái)說(shuō),第一步是檢查對(duì)象本身是否有這個(gè)屬性,如果有就使用它。
對(duì)于默認(rèn)的get操作來(lái)說(shuō),如果無(wú)法在對(duì)象本身找到需要的屬性,就會(huì)繼續(xù)訪(fǎng)問(wèn)protorype鏈,Object.create會(huì)創(chuàng)建一個(gè)對(duì)象并把這個(gè)對(duì)象的prototype關(guān)聯(lián)到指定的對(duì)象。當(dāng)使用各種語(yǔ)法進(jìn)行屬性查找時(shí)都會(huì)查找prototype鏈。
給一個(gè)對(duì)象設(shè)置屬性不僅僅是添加一個(gè)新屬性或者修改已有屬性.
不存在于自有對(duì)象,但是存在prototype中,會(huì)有三種情況
如果在原型鏈上存在屬性,并且它是一個(gè)setter,那就一定會(huì)調(diào)用這個(gè)setter,屬性不會(huì)被添加到對(duì)象上。
- 如果在prototype鏈上層存在foo,但是他被標(biāo)記為只讀,那么無(wú)法修改已有屬性,或者創(chuàng)建屏蔽屬性。只讀會(huì)阻止鏈下層隱式創(chuàng)建同名屬性。這個(gè)限制只會(huì)存在于=賦值中,使用Object.defineProperty并不會(huì)受到影響。
a++,首先通過(guò)原型查找到a,然后再對(duì)象自身的屬性上創(chuàng)建一個(gè)值,原型的值不會(huì)發(fā)生改變。
類(lèi)
原型繼承本質(zhì)是創(chuàng)建一個(gè)新的對(duì)象并且和原型建立關(guān)聯(lián)構(gòu)造函數(shù)
Foo.prototype 默認(rèn)有一個(gè)公有并且不可枚舉的屬性constructor,這個(gè)屬性引用的是對(duì)象關(guān)聯(lián)的函數(shù)。new Foo 創(chuàng)建的對(duì)象也有一個(gè)constructor,指向創(chuàng)建這個(gè)對(duì)象的函數(shù)。
實(shí)例上的constructor被委托到了原型的constructor,
原型與原型之間可以通過(guò)object.create關(guān)聯(lián),也可以通過(guò)new關(guān)聯(lián)檢查類(lèi)的關(guān)系
假設(shè)有對(duì)象a,如何尋找對(duì)象a委托的對(duì)象呢?檢查一個(gè)實(shí)例的繼承祖先通常被稱(chēng)為反射。
instanceof 操作符的左側(cè)是一個(gè)普通對(duì)象,右操作數(shù)是一個(gè)函數(shù)。在左操作數(shù)的[[prototype]]中是否有指向右操作數(shù)的prototype的對(duì)象
Foo.prototype.isPrototypeOf(a) 在a的整條proto中是否出現(xiàn)了Foo.prototype對(duì)象關(guān)聯(lián)
如果在對(duì)象上沒(méi)有找到需要的屬性或者方法,引擎就會(huì)繼續(xù)在proto關(guān)聯(lián)的對(duì)象上查找。創(chuàng)建關(guān)聯(lián)
使用Object.create不會(huì)生成prototype和constructor
proxy是方法無(wú)法找到的行為比較思維模型
function Foo(who) {
this.me = who
}
Foo.prototype.identify = function() {
return 'i am' + this.me
}
function Bar(who) {
Foo.call(this, who)
}
Bar.prototype = Object.create(Foo.prototype)
事件循環(huán)
js引擎并不是獨(dú)立運(yùn)行的,它運(yùn)行在宿主環(huán)境,對(duì)多數(shù)開(kāi)發(fā)者通常就是web瀏覽器。
它們都提供了一種機(jī)制處理程序中多個(gè)塊的執(zhí)行,且執(zhí)行時(shí)調(diào)用js引擎,這種機(jī)制被稱(chēng)為事件循環(huán)。
瀏覽器會(huì)偵聽(tīng)網(wǎng)絡(luò)的響應(yīng),拿到數(shù)據(jù)后,就會(huì)把回調(diào)函數(shù)插入到事件循環(huán)
事件循環(huán)是一個(gè)用做隊(duì)列的數(shù)組,是一個(gè)持續(xù)運(yùn)行的循環(huán),循環(huán)的每一輪稱(chēng)為一個(gè)tick。對(duì)每個(gè)tick而言,如果隊(duì)列中存在等待事件,那么就會(huì)從隊(duì)列中摘下事件并執(zhí)行。這些事件就是你的回調(diào)函數(shù)。
setTimeout并沒(méi)有把回調(diào)函數(shù)掛在事件循環(huán)隊(duì)列中。它所做的是設(shè)定一個(gè)定時(shí)器,定時(shí)器到時(shí)后,環(huán)境會(huì)把你的回調(diào)函數(shù)放在事件循環(huán)中,這樣在未來(lái)某個(gè)時(shí)刻的tick會(huì)摘下并執(zhí)行這個(gè)回調(diào)。并行和線(xiàn)程
事件循環(huán)把自身的工作分成一個(gè)個(gè)任務(wù)并順序執(zhí)行,不允許對(duì)共享內(nèi)存的訪(fǎng)問(wèn)和修改。通過(guò)分立線(xiàn)程中彼此合作的事件循環(huán),并行和順序執(zhí)行可以共存。
js一次只能處理一個(gè)事件任務(wù)
有一個(gè)新的概念建立在事件循環(huán)隊(duì)列之上,叫做任務(wù)隊(duì)列,它是掛在事件循環(huán)隊(duì)列的每個(gè)tick之后的一個(gè)隊(duì)列。可能出現(xiàn)的異步動(dòng)作不會(huì)導(dǎo)致一個(gè)完整的新事件添加到事件循環(huán)隊(duì)列當(dāng)中,而會(huì)在當(dāng)前tick的任務(wù)隊(duì)列末尾添加一個(gè)任務(wù)。
事件循環(huán)類(lèi)似于有一個(gè)活動(dòng)免費(fèi)領(lǐng)禮物,你領(lǐng)完禮物之后需要排隊(duì)才能再領(lǐng)。而任務(wù)隊(duì)列類(lèi)似于領(lǐng)了之后,插隊(duì)繼續(xù)領(lǐng)禮物。
一個(gè)任務(wù)可能引起更多任務(wù)被添加到同一個(gè)隊(duì)列末尾,所以,理論上說(shuō),job event可能無(wú)限循環(huán)。任務(wù)處理是在當(dāng)前tick循環(huán)結(jié)尾處。
一旦有事件需要運(yùn)行,事件循環(huán)就會(huì)運(yùn)行,直到隊(duì)列清空。事件循環(huán)的每一輪稱(chēng)為一個(gè)tick。用戶(hù)交互、IO和定時(shí)器會(huì)向事件隊(duì)列中加入事件。
不管then回調(diào)返回的是什么,都會(huì)被自動(dòng)設(shè)置為promise的完成。元編程
元編程是對(duì)程序編程的編程
舉例來(lái)說(shuō),如果想要查看對(duì)象a和另一個(gè)對(duì)象b的關(guān)系是否是prototype鏈接的。用for in循環(huán)枚舉對(duì)象的鍵,或者檢查對(duì)象是否是某個(gè)類(lèi)構(gòu)造器的實(shí)例。
元編程關(guān)注以下幾點(diǎn):代碼查看自身,代碼修改自身,代碼修改默認(rèn)語(yǔ)言特性,以此影響其他代碼。
元編程的目標(biāo)是利用語(yǔ)言自身的內(nèi)省能力使代碼的其余部分更具描述性、表達(dá)性和靈活性。如果函數(shù)設(shè)定了name值,那么這個(gè)值通常就是開(kāi)發(fā)者工具中棧蹤跡使用的名稱(chēng)。你的代碼在某些情況下可能想要了解自身,想要知道某個(gè)函數(shù)的名稱(chēng)是什么。元屬性
代理
代理是一種由你創(chuàng)建的特殊的對(duì)象,它封裝了另一個(gè)普通對(duì)象,或者說(shuō)擋在這個(gè)普通對(duì)象前面。你可以在代理對(duì)象上注冊(cè)特殊的處理函數(shù),代理上執(zhí)行各種操作時(shí)會(huì)調(diào)用這個(gè)程序。
你可以在代理上定義的trap處理函數(shù)的一個(gè)例子是get,當(dāng)你試圖訪(fǎng)問(wèn)對(duì)象屬性的時(shí)候,它攔截get運(yùn)算。
在跟蹤console.log之后,我們把對(duì)obj的操作通過(guò)reflect.get轉(zhuǎn)發(fā)。每個(gè)可用的代理trap都有一個(gè)對(duì)應(yīng)的同名Reflect函數(shù)即可。
這里的映射是有意對(duì)稱(chēng)的。每個(gè)代理處理函數(shù)在對(duì)應(yīng)的元編程任務(wù)執(zhí)行的時(shí)候進(jìn)行攔截.
我們把對(duì)obj的操作通過(guò)Reflect.get()轉(zhuǎn)發(fā)。每個(gè)代理處理函數(shù)都有一個(gè)自動(dòng)調(diào)用相應(yīng)的Reflect工具的默認(rèn)定義。代理局限性
可以在對(duì)象上執(zhí)行的很廣泛的一組基本操作都可以通過(guò)這些元編程處理函數(shù)trap。但有一些操作是無(wú)法攔截的。可取消代理
使用代理
通過(guò)元編程的proxy,我們可以攔截對(duì)對(duì)象處理的幾乎所有行為。
- 代理在先,代理在后
我們通??梢园汛砜醋魇菍?duì)目標(biāo)對(duì)象的包裝。代理成為了代碼交互的主要對(duì)象,而實(shí)際的目標(biāo)對(duì)象保持被保護(hù)的狀態(tài)。
代理hack[[ prototype ]]鏈
[[ prototype ]]機(jī)制主要是通過(guò)[[ Get ]]運(yùn)算,當(dāng)直接對(duì)象沒(méi)有屬性時(shí),get會(huì)把這個(gè)運(yùn)算交給[[ prototype ]]。Reflect對(duì)象就是一個(gè)平凡對(duì)象
它持有對(duì)應(yīng)于各種可控的元編程任務(wù)的靜態(tài)函數(shù)。這些函數(shù)一對(duì)一對(duì)應(yīng)著代理可以定義的處理函數(shù)方法。
Reflect和Object的對(duì)應(yīng)工具行為方式類(lèi)似。但是,如果第一個(gè)參數(shù)不是對(duì)象,Object會(huì)試圖把它轉(zhuǎn)換成對(duì)象。而Reflect則會(huì)拋出一個(gè)錯(cuò)誤。
Reflect的元編程能力提供了模擬各種語(yǔ)法特性的編程等價(jià)事物,把之前隱藏的抽象操作暴露出來(lái)。比如,可以利用這些能力擴(kuò)展功能和API,以實(shí)現(xiàn)領(lǐng)域特定語(yǔ)言(DSL)。屬性排序
對(duì)于es6,擁有屬性的列出順序由算法定義,可以順序只對(duì)特斯你給的ownKeys等方法有保證
其順序?yàn)椋?/p>
- 首先,按照數(shù)字上升排序,枚舉所有整數(shù)索引擁有的屬性;
- 然后,按照創(chuàng)建順序枚舉其余的擁有的字符串屬性名;
- 最后,按照創(chuàng)建順序枚舉擁有的符號(hào)屬性;
尾遞歸調(diào)用
通常,在一個(gè)函數(shù)內(nèi)部調(diào)用另一個(gè)函數(shù)的時(shí)候,會(huì)分配第二個(gè)棧zhen來(lái)獨(dú)立管理第二個(gè)函數(shù)調(diào)用的變量/狀態(tài)。這個(gè)分配不但消耗處理時(shí)間,也消耗額外內(nèi)存。
當(dāng)進(jìn)行遞歸編程時(shí),調(diào)用棧的深度很容易達(dá)到成百上千,甚至更多。如果內(nèi)存的使用無(wú)限制增長(zhǎng),將會(huì)導(dǎo)致問(wèn)題。
有一種稱(chēng)為尾調(diào)用的函數(shù)調(diào)用模式,可以以避免額外棧幀分配的方式進(jìn)行優(yōu)化。如果可以避免額外的分配,就沒(méi)有理由任意限制調(diào)用棧的深度。
尾調(diào)用是一個(gè)return函數(shù)調(diào)用的語(yǔ)句,除了調(diào)用后返回其返回值之外沒(méi)有任何其他動(dòng)作。通過(guò)這種方式不需要分配額外的棧幀。引擎不需要對(duì)下一個(gè)函數(shù)調(diào)用創(chuàng)建一個(gè)新的棧幀。這能夠工作是因?yàn)橐粋€(gè)函數(shù)不需要保留任何當(dāng)前狀態(tài)。非tco優(yōu)化
把每個(gè)部分的結(jié)果用一個(gè)函數(shù)表示,這些函數(shù)或者返回另外一個(gè)部分結(jié)果函數(shù),或者返回最終結(jié)果。然后只需要循環(huán)直到得到的結(jié)果不是函數(shù),得到的就是最終結(jié)果。
function trampoline(res) {
while(typeof res == 'function') {
res = res()
}
return res
}
let foo = (() => {
function _foo(acc, x) {
if (x <= 1) return acc;
return () => {
return _foo((x/2)+acc, x-1)
}
}
return (x) => {
return trampoline(_foo(1, x))
}
})()
這個(gè)重寫(xiě)需要最小的改動(dòng)把遞歸轉(zhuǎn)化為trampoline中的循環(huán)。
- 首先,把 return _foo .. 一行封裝在·partial表達(dá)式中
- 然后,把_foo調(diào)用封裝在trampoline中
function foo(x) {
let acc = 1;
while(x>1) {
acc = (x/2)+acc
x--
}
return acc
}
- 元在何處
可以在運(yùn)行時(shí)判斷引擎支持哪些特性。其中就包括TCO,盡管方法十分暴力
try {
(function foo(x) {
if (x < 5E5) return foo(x+1)
})(1)
}
catch (err) {
}
在非TCO引擎中,遞歸循環(huán)最終會(huì)失敗,拋出一個(gè)異常被捕獲。
所以我們可以通過(guò)這種特性測(cè)試來(lái)決定是加載使用遞歸的應(yīng)用代碼版本,還是轉(zhuǎn)換為不需要遞歸的版本。
自適應(yīng)代碼
function foo(x) {
function _foo() {
if (x > 1) {
acc = acc + (x/2);
x--
return _foo()
}
}
var acc=1;
while(x>1) {
try {_foo()} catch(err){}
}
return acc
}
foo(123456)
這個(gè)算法盡可能多的使用了遞歸,但是是通過(guò)作用域內(nèi)的變量x和acc保持進(jìn)展?fàn)顟B(tài)。如果整個(gè)問(wèn)題都可以不出錯(cuò)的通過(guò)遞歸解決,那么很好。如果引擎在某處殺死了遞歸,我們就會(huì)在catch中捕獲到,然后再試一次,繼續(xù)我們其余的工作。
這種形式可以看做一種元編程,理由是在運(yùn)行時(shí)探索引擎的能力來(lái)遞歸地完成任務(wù),并可能為引擎局限提供替代版本。
小結(jié)
元編程是指把程序的邏輯轉(zhuǎn)向關(guān)注自身,要么是為了查看自己的結(jié)構(gòu),要么是為了修改它。元編程的主要價(jià)值是擴(kuò)展語(yǔ)言的一般機(jī)制來(lái)提供額外的新功能。
從匿名函數(shù)到函數(shù)名推導(dǎo),到提供了構(gòu)造器調(diào)用方式這樣的信息的元屬性,你可以比過(guò)去更深入查看程序運(yùn)行時(shí)的結(jié)構(gòu)。通過(guò)公開(kāi)符號(hào)可以覆蓋原本特性,代理可以攔截并自定義對(duì)象的各種底層操作,Reflect提供了工具來(lái)模擬它們。異步函數(shù)
async function 本質(zhì)上就是生成器+promise+run模式的語(yǔ)法糖
async function有一個(gè)沒(méi)有解決的問(wèn)題,因?yàn)樗环祷匾粋€(gè)promise,所以沒(méi)有辦法從外部取消一個(gè)正在運(yùn)行的async function實(shí)例。