摘要:回調(diào)函數(shù)是處理異步邏輯最基礎(chǔ)的方法,但也有著各種的缺點。回調(diào)函數(shù)必須遵守的原則就是信任,但要核實。異步的進化一前面一部分已經(jīng)描述到了回調(diào)函數(shù)的兩個問題分別是缺乏順序性和缺乏可信任性。
要帶著問題學,活學活用,學用結(jié)合,急用先學,立竿見影,在「用」字上狠下功夫。
廢話少說。
這是這個專題的第二部分內(nèi)容,異步。主要總結(jié)了《你不知道的JavaScript(中卷)》中有關(guān)于異步的內(nèi)容。顯然一下子寫完三個部分的內(nèi)容不太可能,下篇會在不久之后放出。
由于前人之述備矣,所以有些地方會引用它山之石,它山之石可以攻玉嘛。 ?
首先明確,JavaScript是一種單線程語言,不會出現(xiàn)多線程。
1. 【異步的核心】程序中現(xiàn)在運行部分和將來運行部分的關(guān)系就是異步編程的核心。簡單來講,如果程序中出現(xiàn)了一部分要在現(xiàn)在運行(順序同步執(zhí)行),一部分要在將來運行(可能是設(shè)置了timeout也可能是一個ajax的異步調(diào)用后執(zhí)行的函數(shù)),那么兩者之間的關(guān)系的構(gòu)建就構(gòu)成了異步編程。
2. 【事件循環(huán)】相當于一個永遠執(zhí)行的while(true)循環(huán),循環(huán)的每一輪稱為一個tick。對于每個tick而言,如果隊列中有等待事件,那么從隊列中拿下這個事件執(zhí)行。隊列中事件就是注冊的異步調(diào)用函數(shù)。
由于事件循環(huán)的原因,setTimeout只是在timeout的時間后將函數(shù)注冊到事件循環(huán)中,因為有被其他任務(wù)阻塞的可能,所以其時間不一定準確。setInterval同理可得。
setTimeout(…,0)可以進行異步調(diào)動,將函數(shù)放在事件隊列循環(huán)的末尾,是一種hack的方法。
具體可以參閱以下blog:你所不知道的setInterval | 晚晴幽草軒
Promise的then是基于任務(wù)的。任務(wù)和事件循環(huán)的區(qū)別,可以理解為任務(wù)代表的異步函數(shù)可以插隊進入當前事件之后。所以從理論上來說,任務(wù)循環(huán)(job loop)可能導致無限循環(huán)(一個任務(wù)添加另一個不需要排隊的任務(wù),例如Promise中then的無限連接)使得無法進入到下一個tick中。
EX 事件循環(huán)和任務(wù)的認識
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
輸出是 1 2 3 5 4 而非 1 2 3 4 5
這就說明了Promise決議之后,先執(zhí)行了then的這個任務(wù)(job),這個then沒有進入事件循環(huán)中排隊,因為如果排隊,應該會在setTimeout這個先注冊的function之后調(diào)用。所以then的任務(wù)隊列的優(yōu)先級高于事件循環(huán)。并且磁力還說明了Promise的決議過程是同步執(zhí)行的。
具體的原理說明:
https://github.com/creeperyan...
有時會由于兩個ajax調(diào)用的先后順序(或者其他操作的先后順序)的原因會導致運行結(jié)果的不同,為了控制進程的執(zhí)行,有兩種控制的模式和兩種簡單的方式:
首先是門:這個可以控制兩個函數(shù)都完成之后才進行下一步工作,條件控制條件為if(a && b)
第二種是競態(tài),也可稱為門閂。就是兩個函數(shù)只有一個能夠被調(diào)用,另一個會被忽略,其控制條件是設(shè)置一個undefined的變量a,調(diào)用后設(shè)為有值,并且判斷if(!a)
回調(diào)可以說是JavaScript的基礎(chǔ)了,這里不講回調(diào)的好處,只有回調(diào)的幾個明顯缺點(否則則么顯現(xiàn)出后面的進化呢(笑)):
1. 【回調(diào)函數(shù)】回調(diào)函數(shù)封裝了程序的延續(xù)(continuation)?;卣{(diào)函數(shù)是處理JavaScript異步邏輯最基礎(chǔ)的方法,但也有著各種的缺點。
2. 【嵌套回調(diào)和鏈式回調(diào)(回調(diào)地獄)】有下列代碼:
//《你不知道的JavaScript(中卷)》 listen( "click", function handler(evt){ setTimeout( function request(){ ajax( "http://some.url.1", function response(text){ if (text == "hello") { handler(); } else if (text == "world") { request(); } } ); }, 500) ; } );
這是一個由三個函數(shù)嵌套在一起的鏈式回調(diào),每個函數(shù)代表了一個異步序列。
由于回調(diào)的特性,可能很難一下看出這個函數(shù)的執(zhí)行邏輯(缺乏順序性),所以又被稱為回調(diào)地獄或者毀滅金字塔。
【回調(diào)地獄的缺陷】:
doA( function(){ doC(); doD( function(){ doF(); } ) doE(); } ); doB();
如果函數(shù)A和D是異步執(zhí)行的,那么這個回調(diào)過程的執(zhí)行步驟是A - F - B - C - E - D
除了難以閱讀以外,回調(diào)地獄真正的問題在于一旦指定了所有的可能時間和路徑,代碼就會變得十分復雜,無法維護和更新。因為一個進行的回調(diào)要是能夠覆蓋所有路徑,可能會寫上很多并行的回調(diào)函數(shù),在代碼中看起來可能會十分凌亂和難以調(diào)試維護。
3. 【控制反轉(zhuǎn)】這牽涉到異步程序設(shè)計的信任問題。
控制反轉(zhuǎn)就是程序執(zhí)行的主動權(quán)從自己的手中交了出去。如果僅僅是簡單的ajax調(diào)用,那么這個控制切換可能不會帶來什么大問題。但如果將一個回調(diào)函數(shù)交給一個外部的API,因為無法查看的具體代碼,所以可以看做是一個黑箱。這個黑箱導致問題是無法調(diào)試,不知道這個外部程序到底怎樣調(diào)用了這個回調(diào)函數(shù),是一次都沒有,還是調(diào)用了很多次,亦或是比預想中過早過晚的調(diào)用,最終可能的后果就是程序執(zhí)行的結(jié)果不如所愿。
教科書一點的定義就是把自己程序一部分的執(zhí)行控制交給了某個第三方,且與這個第三方之間沒有一份明確表達的契約。
因為回調(diào)沒有機制來保障這個必然出現(xiàn)的控制反轉(zhuǎn)的問題,這就成為了回調(diào)的最大問題,會導致信任鏈的完全斷裂,是程序出錯。
回調(diào)函數(shù)必須遵守的原則就是:信任,但要核實。(Trust But Verify.)
4. 【error-first風格】回調(diào)函數(shù)的第一個參數(shù)留給錯誤處理,如果成功第一個參數(shù)就置為false,否則為true?;卣{(diào)執(zhí)行時先進行判斷。
但是這個風格并沒有完全解決信任的問題,如果同時成功和失敗,就要另外寫代碼來處理。
回調(diào)會有同步回調(diào)調(diào)用和異步回調(diào)調(diào)用。這樣也會產(chǎn)生程序的運行問題,見下列代碼:
function result(data) { console.log( a ); } var a = 0; ajax( "..pre-cached-url..", result ); a++;
這端代碼會有0(同步回調(diào)調(diào)用)還是1(異步回調(diào)調(diào)用)的結(jié)果就要看情況而定了
對于可能同步調(diào)用也可能異步調(diào)用給出的回調(diào)函數(shù)的第三方工具而言,這個信任問題是明顯的。雖然可以用臃腫的附加代碼來解決,但并不優(yōu)雅。
這樣的同步異步的混淆產(chǎn)生了另一條準則:
**永遠要異步調(diào)用回調(diào),即使只在事件的下一輪。
(always invoke callbacks asynchronously, even if that"s "right away" on the next turn of the event loop)**
前面一部分已經(jīng)描述到了回調(diào)函數(shù)的兩個問題分別是:缺乏順序性和缺乏可信任性。
那么這部分的Promise主要用來解決了可信任性的問題。
1. 【解決可信任問題的范式】不把程序的控制權(quán)交給第三方,而是希望第三方提供一個了解其任務(wù)何時結(jié)束的能力,然后由我們的代碼來決定接下來做什么。
2. 【未來值】A對于B有一個承諾,如果A給出了任務(wù)完成可以兌現(xiàn)承諾或者失敗不能兌現(xiàn)承諾的值,那么這個值就稱為未來值,簡單而言就是要在未來才能確定的值,但有承諾保證這個值存在。
由于未來值可能有兩個可能,要么成功,要么失敗。所以Promise值的then方法(在Promise值確定之后調(diào)用的函數(shù))就可以接收兩個參數(shù),第一個為成功的話執(zhí)行的函數(shù),第二個為失敗的話執(zhí)行的函數(shù)。
舉個例子:
把x和y相加,如果有一個值沒有準備好,那就等待。一旦全部準備好就相加返回。
為了統(tǒng)一處理將來和現(xiàn)在,就把他們?nèi)孔兂晌磥碇担腿慨惒秸{(diào)用。
回調(diào)模式下的代碼:
function add(getX,getY,cb) { var x, y; getX( function(xVal){ x = xVal; // both are ready? if (y != undefined) { cb( x + y ); // send along sum } } ); getY( function(yVal){ y = yVal; // both are ready? if (x != undefined) { cb( x + y ); // send along sum } } ); } // `fetchX()` and `fetchY()` are sync or async // functions add( fetchX, fetchY, function(sum){ console.log( sum ); // that was easy, huh? } );
Promise模式下的代碼:
function add(xPromise,yPromise) { // `Promise.all([ .. ])` takes an array of promises, // and returns a new promise that waits on them // all to finish return Promise.all( [xPromise, yPromise] ) // when that promise is resolved, let"s take the // received `X` and `Y` values and add them together. .then( function(values){ // `values` is an array of the messages from the // previously resolved promises return values[0] + values[1]; } ); } // `fetchX()` and `fetchY()` return promises for // their respective values, which may be ready // *now* or *later*. add( fetchX(), fetchY() ) // we get a promise back for the sum of those // two numbers. // now we chain-call `then(..)` to wait for the // resolution of that returned promise. .then( function(sum){ console.log( sum ); // that was easier! } );
通過比較明顯看出Promise模式的方法可以簡潔的表達一些操作。
Promise封裝了依賴于時間的狀態(tài)(等待未來值的產(chǎn)生,無論是現(xiàn)在還是未來產(chǎn)生,后續(xù)的步驟都是一樣的,解決了同步回調(diào)還是異步回調(diào)的問題),其本身與時間無關(guān),所以可以按照可預測的方式組合。但Promise一旦決議,那么永遠將會保持在這個狀態(tài),成為不變值,可以隨時查看。
3. 【revealing-constructor】一種產(chǎn)生Promise的模式,通常格式為
new Promise (function (…){…}) ,傳入的函數(shù)將會被立即執(zhí)行。
識別Promise是否為真正的Promise很重要。定義某種稱為thenable的東西,將其定義為任何具有then(..)方法的對象和函數(shù),任何這樣的值就是Promise一致的thenable。如果Promise決議遇到了這樣的thenable的值,那么就會被擱淺在這里,導致難以追蹤的bug。
5. 【Promise解決信任問題的方法】有五種回調(diào)導致的信任問題,分別來講:
調(diào)用過早: 由于一個任務(wù)有時候同步完成,有時候異步完成。如果使用回調(diào)會導致Zalgo出現(xiàn),使用Promise無論是立即決議的revealing-constructor模式,還是異步執(zhí)行的內(nèi)容,都會基于最前面所講的任務(wù)隊列來進行異步調(diào)用,這樣就解決了調(diào)用過早的問題.
調(diào)用過晚:由于同步then調(diào)用時不被允許的,所以,一個Promise被決議之后,這個Promise上所有的通過then(…)注冊的回調(diào)都會下一個異步時機點一次被立即調(diào)用。任意一個都無法影響或延誤對其他回調(diào)的調(diào)用(不能插隊)
這里第一個function第一次注冊了打印出A的then方法,打印出B的then方法,注冊完畢后進行任務(wù)隊列的處理,因為A先注冊,所以先執(zhí)行。這里又注冊了一個C的then方法,雖然p已經(jīng)被決議,但是并不能立即調(diào)用(不能同步調(diào)用),還是加入到任務(wù)隊列的最后,不中斷對B的執(zhí)行。所以執(zhí)行結(jié)果是A B C。第二個是即使是p立即決議了,但是then中的內(nèi)容還是被延遲到執(zhí)行完所有同步內(nèi)容之后運行。但是不同Promise值的回調(diào)順序是不可預測的,永遠不要依賴于不同Promise之間的回調(diào)順序來進行程序調(diào)度。
Ex:
p.then( function(){ p.then( function(){ console.log( "C" ); } ); console.log( "A" ); } ); p.then( function(){ console.log( "B" ); } ); // A B C function runme() { var i = 0; new Promise(function(resolve) { resolve(); }) .then(function() { i += 2; }); alert(i); } //0
回調(diào)未調(diào)用 : 沒有任何東西(包括JavaScript錯誤)可以組織Promise決議,它總會調(diào)用resolve和reject處理方法中的一個,即使是超時也有超時模式進行處理。(后續(xù)會講到)
調(diào)用次數(shù)過多或過少:由于Promise只能被決議一次,注冊的then只會被最多調(diào)用一次,所以過多的調(diào)用會直接無效。過少就是之前解釋的回調(diào)未調(diào)用的情況。
未能傳遞參數(shù)值、環(huán)境值:任何Promise都只能有一個決議值,如果resolve(…)或者reject(…)中傳遞了過多的參數(shù),那都只會采納第一個,而忽略其他的,如果要有多個,那么就要封裝到數(shù)組或者對象中傳遞。
吞掉錯誤或異常:如果一個Promise產(chǎn)生了拒絕值并且給出了理由,那么這個就會被傳給拒絕回調(diào),即使是JavaScript的異常也會這樣做。這里的會產(chǎn)生的另一個細節(jié)就是如果發(fā)生JavaScript錯誤會導致的同步調(diào)用,由于Promise的特性也會將其變?yōu)楫惒降恼{(diào)用。
但是試想,如果在then的正確處理函數(shù)中出現(xiàn)了錯誤會發(fā)生什么?
EX:
var p = new Promise( function(resolve,reject){ resolve( 42 ); } ); p.then( function fulfilled(msg){ foo.bar(); console.log( msg ); // never gets here :( }, function rejected(err){ // never gets here either :( } );
由于第一個then中未定義bar函數(shù),所以會產(chǎn)生一個錯誤,但是并不會立即處理,而是會產(chǎn)生另一個Promise,這個新的Promise會由于錯誤而被拒絕,并沒有吞掉錯誤。因為p已經(jīng)被決議為正確,所以不會因為fulfilled中間有錯誤而去調(diào)用rejected。
Promise.resolve()方法產(chǎn)生的Promise保證了返回內(nèi)容的可信任性:
分別考慮resolve方法的參數(shù),1)如果是一個非Promise,非thenable的 立即值,那么就會返回一個用這個值填充的Promise封裝,保證了內(nèi)容的可信任。(即使是錯誤值) 2)如果是一個Promise,那么也只會產(chǎn)生一個Promise。3)如果傳遞了一個thenable的非Promise,那么就會試圖展開這個值,直到遇到了一個符合1條件的立即值,并封裝為Promise
通過這個方法,可以保證異步返回給回調(diào)函數(shù)的值為Promise可信任的。
6. 【鏈式流】鏈式流可以應用在會進行多次異步調(diào)用的方法中,可以加強代碼的清晰度可讀性和快速定位錯誤。
參見下面兩個代碼段:
//來自:http://imweb.io/topic/57a0760393d9938132cc8da9 getUserAdmin().then(function(result) { if ( /*管理員*/ ) { getProjectsWithAdmin().then(function(result) { /*根據(jù)項目id,獲取模塊列表*/ getModules(result.ids).then(function(result) { /*根據(jù)模塊id,獲取接口列表*/ getInterfaces(result.ids).then(function(result) { // ... }) }) }) } else { //... } }) //鏈式流 getUserAdmin().then(function(reult) { if ( /*管理員*/ ) { return getProjectsWithAdmin(); } else { return getProjectsWithUser(); } }).then(function(result) { /*獲取project id列表*/ return getModules(result.ids); }).then(function(result) { /*獲取project id列表*/ return getInterfaces(result.ids) }).then(function(result) { // ... })
能夠產(chǎn)生鏈式流基于以下兩個Promise的特性:
1.每次對Promise調(diào)用then(…),它都會產(chǎn)生一個新的Promise。
2.不管從then(…)調(diào)用的完成回調(diào)(第一個參數(shù))返回的值是什么,它都會被自動設(shè)置為被連接Promise的完成,這句話表述了這個新的Promise的值就是這個then調(diào)用方法里的return語句,如果沒有,那么這個Promise的值就是undefined。
考慮以下代碼:
var p = Promise.resolve( 21 ); p .then( function(v){ console.log( v ); // 21 // fulfill the chained promise with value `42` return v * 2; } ) // here"s the chained promise .then( function(v){ console.log( v ); // 42 } );
上面的代碼充分展現(xiàn)了這兩條規(guī)則。另外兩條則充分說明了即使是返回一個Promise甚至返回中有異步調(diào)用(這里的異步調(diào)用不會被放入事件循環(huán)的最后,而是在這里直接延遲執(zhí)行,后續(xù)的then會等待其執(zhí)行完畢),這兩條規(guī)則都會正常工作:
var p = Promise.resolve( 21 ); p.then( function(v){ console.log( v ); // 21 // create a promise and return it return new Promise( function(resolve,reject){ // fulfill with value `42` resolve( v * 2 ); } ); } ) .then( function(v){ console.log( v ); // 42 } );
var p = Promise.resolve( 21 ); p.then( function(v){ console.log( v ); // 21 // create a promise to return return new Promise( function(resolve,reject){ // introduce asynchrony! setTimeout( function(){ // fulfill with value `42` resolve( v * 2 ); }, 100 ); } ); } ) .then( function(v){ // runs after the 100ms delay in the previous step console.log( v ); // 42 } );
如果鏈中有步驟出錯,會直接將這個錯誤封裝為Promise傳入到鏈中的下一個錯誤處理方法中(原因之前已經(jīng)講過)。如果這個錯誤處理return了一個值,那么這個值會被帶入到下一個then處理的正確處理方法中,如果return了一個Promise那么就有可能會使得下一個then延遲調(diào)用。如果沒有return,那就默認return undefined,同樣也是正確處理中。
默認的拒絕處理函數(shù):如果產(chǎn)生了錯誤,但沒有拒絕處理函數(shù),那么就會有默認的,默認的所做的事情就是拋出錯誤,那么這個錯誤就會繼續(xù)向下直到有顯式的拒絕處理函數(shù)。
默認的接收處理函數(shù):純粹將一個promise繼續(xù)向下傳遞。如果只有拒絕處理可以將簡寫為:catch(function(err){…})
由于Promise一旦被決議就不再更改的特性,以下代碼可能會導致沒有錯誤處理函數(shù)來處理:
var p = Promise.resolve( 42 ); p.then( function fulfilled(msg){ // numbers don"t have string functions, // so will throw an error console.log( msg.toLowerCase() ); }, function rejected(err){ // never gets here } );
幾種解決方案(除了1都未被ES6標準實現(xiàn)):
1) 在最后加catch,這樣會導致的問題就是catch中的函數(shù)如果也有錯誤就無法捕捉。
2)有個done函數(shù),就算done函數(shù)有錯誤,也傳入done中。
之前介紹了兩種并發(fā)的模式,這里有Promise來直接實現(xiàn):
1) 門:幾個均實現(xiàn)再繼續(xù)進行: Promise.all([….]),參數(shù)可以是由立即值,thenable或者Promise組成的數(shù)組。
注意:如果傳入空數(shù)組,那么接下來的內(nèi)容就會被立即設(shè)定為完成。如果有Promise.all中有任意一個被拒絕,那么整個都被拒絕,進入到拒絕處理函數(shù)。這個模式傳入到完成處理函數(shù)中的參數(shù)是一個數(shù)組,數(shù)組中的順序與all中聲明的順序相同,與其產(chǎn)生的順序無關(guān)。
2) 競態(tài):幾個中只有一個能執(zhí)行:Promise.race([…]),參數(shù)與all相同,但是如果是立即值的競爭那就會顯得毫無意義,第一個立即值會勝出。
注意:一旦有一個Promise被完成,那就全部完成,如果第一個是拒絕,那么整個都被拒絕。如果傳遞空數(shù)組,那么Promise會永遠都不會被決議。
3)超時模式的實現(xiàn):之前講到了會有超時模式,這里利用競態(tài)可以來實現(xiàn):
// `foo()` is a Promise-aware function // `timeoutPromise(..)`, defined ealier, returns // a Promise that rejects after a specified delay // setup a timeout for `foo()` Promise.race( [ foo(), // attempt `foo()` timeoutPromise( 3000 ) // give it 3 seconds ] ) .then( function(){ // `foo(..)` fulfilled in time! }, function(err){ // either `foo()` rejected, or it just // didn"t finish in time, so inspect // `err` to know which } );
4)幾種變體:
none:所有的Promise都是拒絕才是完成 any:只要有一個完成就是完成 first:只要第一個Promise完成,那么整個就是完成 last:只有最后一個完成勝出9. 【Promise的問題】
講了那么多好處。。Promise當然也有問題:
1) 順序錯誤處理:可能會有錯誤被忽略而被全局拋出
2)單一值:只能有一個完成值、拒絕值,否則只能封裝解封,這樣會顯得有些笨重。(這個問題可以通過ES6中的...運算來方便處理~)
3) 單決議:如果講一個決議綁定到會重復進行的操作上,那么這個決議只會記住重復操作的第一次結(jié)果,如:
// `click(..)` binds the `"click"` event to a DOM element // `request(..)` is the previously defined Promise-aware Ajax var p = new Promise( function(resolve,reject){ click( "#mybtn", resolve ); } ); p.then( function(evt){ var btnID = evt.currentTarget.id; return request( "http://some.url.1/?id=" + btnID ); } ) .then( function(text){ console.log( text ); } ); //第二次按下就不會有任何操作,不會再次執(zhí)行resolve
4) 慣性:已經(jīng)有很多回調(diào)的代碼不會自然的進行Promise改寫
5)無法取消:如果Promise因為某些原因懸而未決的話,無法從外部阻止其繼續(xù)執(zhí)行。
6)Promise會對性能有稍稍影響,但總體功大于過。
本文中的代碼除非有特別標注,均參考自:
https://github.com/getify/You...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84808.html
摘要:前端日報精選免費的計算機編程類中文書籍英文技術(shù)文檔看不懂看印記中文就夠了的內(nèi)部工作原理美團點評點餐前后端分離實踐讓你的動畫坐上時光機中文譯有多棒簡書譯別再使用圖片輪播了掘金譯如何在中使用掘金個讓增長成億美元公司的獨特方法眾成翻 2017-08-23 前端日報 精選 FPB 2.0:免費的計算機編程類中文書籍 2.0英文技術(shù)文檔看不懂?看印記中文就夠了!Virtual DOM 的內(nèi)部工作...
摘要:如果沒有學習過計算機科學的程序員,當我們在處理一些問題時,比較熟悉的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,數(shù)組無疑是一個很好的選擇。 showImg(https://segmentfault.com/img/bVTSjt?w=400&h=300); 1、常見 CSS 布局方式詳見: 一些常見的 CSS 布局方式梳理,涉及 Flex 布局、Grid 布局、圣杯布局、雙飛翼布局等。http://cherryb...
摘要:如果沒有學習過計算機科學的程序員,當我們在處理一些問題時,比較熟悉的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,數(shù)組無疑是一個很好的選擇。 showImg(https://segmentfault.com/img/bVTSjt?w=400&h=300); 1、常見 CSS 布局方式詳見: 一些常見的 CSS 布局方式梳理,涉及 Flex 布局、Grid 布局、圣杯布局、雙飛翼布局等。http://cherryb...
摘要:原理身體會恢復,情緒會消散,不用擔心自己任務(wù)質(zhì)量從此變差,人是無法容忍長期做無意義事情的,只要堅持,最終本能自我和感性自我會想辦法幫你把任務(wù)質(zhì)量提上來 目標說明 1.主要面對java知識總結(jié)以及英文訓練 2.實驗期一年(2019.4.9 -2020.5.1 ) 原則 目標導向,主次分明——學習知識,不是為了懂更多知識,而是為了用知識創(chuàng)造價值,讓人類文明更加進步,所以,不要奢求什么都懂...
閱讀 1323·2021-09-22 15:00
閱讀 3315·2019-08-30 14:00
閱讀 1232·2019-08-29 17:27
閱讀 1228·2019-08-29 16:35
閱讀 702·2019-08-29 16:14
閱讀 2048·2019-08-26 13:43
閱讀 2129·2019-08-26 11:35
閱讀 2314·2019-08-23 15:34