所有的過失在未犯以前,都已定下應處的懲罰。 莎士比亞
所有的選擇在未抉擇以前,都已定下他未來的籌碼。 該佚名
js設計最出色的就是函數設計。
函數用於指定對象的行爲。
一般來說,所謂編程。。。就是將一組需求 分解爲 ① 一組函數 ② 數據結構
- 函數對象(function objects)(對象------屬性名/屬性值得集合,且擁有連接到原型對象的隱藏鏈接。)
· 對象字面量 連接到 Object.prototype
· 函數字面量 連接到 Function.prototype (每個函數在創建的時候,都會附加兩個隱藏屬性:函數的上下文和實現函數行爲的代碼)(因爲函數是對象,所以可以有方法,所以可以被保存在變量裏,對象數組中,可以被當做參數傳遞給其他函數,可以被當做返回值被return)(與對象唯一不同的是,函數可以被調用)
- 函數字面量(function literral)
函數的字面量一共包含四個部分 1 保留字 function 2 函數名 ,但是也可以被省略。 額。。。就成了匿名函數麼 (anonymous) 可以通過函數名來調用自己。 額。。。就成了遞歸函數麼 函數名特能被調試器和開發工具用來識別函數。 3 ()形參,區切,不會被初始化成undefined,是調用該函數的時候傳進來的實參的值。 4 {}函數體。 函數字面量可以出現在任何表達式可以出現的地方。 可以被定義在其他函數中。 一個內部的函數除了可以訪問自己的參數和變量,還可以自由的訪問嵌套他的父級函數的參數和變量。 創建函數對象的時候,會包含着一個鏈接上下文的鏈接,這被稱之爲閉包。(closure)
- 調用(invocation)
主函數 =》 子函數 ① 暫停主函數的執行 ② 主函數把控制權和參數傳遞給子函數 (關於參數,除了傳遞過去的形參,還有兩個附加參數:this和arguments) 【重頭戲啊】 函數中的,其實this才重要。 果不其然,無論到哪裏,想清楚自己纔是最最最重要。 js一共有四種調用方式 · 方法調用模式 · 函數調用模式 · 構造函數調用模式 · apply 調用模式 以上四種方式,在如何初始化this方面存在差異。 (反正挺老師說過,箭頭函數和普通函數中的this是不一樣的。) 調用運算符 () (逗號分隔) 調用時候的實體參數,可以和函數定義的形式參數的個數不匹配。 實體參數 》 形參 多出來的參數將會被忽略 反之 沒有被賦值得到形參,講會是undefined。 另 不會對數據類型進行檢查。。。。。。 以下 4 到 7 其實是調用的子集~~~
- 方法調用模式(the method invocation pattern)
// 創建 myObject 對象。 // 該對象有一個 value 屬性 和 一個increment 方法 // increment 方法接受一個可選參數 // 如果調用函數的時候,實體參數不是數字的場合 // 那麼會有一個默認值 數字1 // 對象體定義 var myObject = { value : 0 , increment : function (inc) { this.value += typeof inc === 'number' ? inc : 1; } } // 方法調用模式 // 當實體參數的個數少於形參的時候 // 形參將會爲undefined myObject.increment(); document.writeln(myObject.value); // 不是數字的場合,返回1 // 實體參數是數字的場合 myObject.increment(2); document.writeln(myObject.value); // 是數字的場合, // 返回 之前的值(突然發現上面返回1以後value就是1 了, // 然後+=2 ,於是現在返回的是3)
- 函數調用模式(the function invocation pattern)
// 對象體定義 var myObject = { value : 0 , increment : function (inc) { this.value += typeof inc === 'number' ? inc : 1; } } // 當實體參數的個數少於形參的時候 // 形參將會爲undefined myObject.increment(); // 實體參數是數字的場合 myObject.increment(2); ///////////////////////////////上集回顧,爲了整體可以運行哈///////////////////// // var sum = add(); // 創建一個名爲add的函數變量 var add = function(a,b){ return a + b; } // 當給一個對象賦值一個他沒有的屬性的時候 // 該對象會被擴展 // eg 給myObject 增加一個double 方法 myObject.double = function(){ var that = this; var helper = function (){ // 以函數調用模式調用了helper that.value = add(that.value , that.value); } helper(); } // 方法調用模式 調用了double myObject.double(); document.writeln(myObject.value); // 終於對了 上文提要 一句都不能少 !!! 汗-_-||
- 構造函數調用模式(the constructor invocation pattern)
// 創建一個 Quo 的構造器函數 它構造一個帶有 status 屬性的對象 // 一個函數如果創建的目的,就是爲了結合new關鍵字來調用,那麼他就是構造函數 // 但作者不推薦使用 var Quo = function (string){ this.status = string; } // 給Quo所有實例提供一個名爲 get_status的公共方法。 Quo.prototype.get_status = function(){ return this.status; } var myQuo = new Quo("confused"); document.writeln(myQuo.get_status());
- Apply調用模式(the apply invocation pattern)
// simple case // 創建一個名爲add的函數變量 var add = function(a,b){ return a + b; } var array = [3,4]; var sum = add.apply(null,array); console.log(sum);
var Quo = function (string){ this.status = string; } Quo.prototype.get_status = function(){ return this.status; } // statusObject並沒有繼承自Quo.prototype // 但我們可以在statusObject上, // 通過apply 這種方式 來調用get_status方法 // 儘管statusObject其實並沒有一個名爲get_status的方法 var statusObject = { status : "A-OK" } var status = Quo.prototype.get_status.apply(statusObject); console.log(status);
- 參數(arguments)
// 構造一個好多數相加的函數 // 注意: 該函數內部定義的sum不會影響到外部定義的sum // 該函數只能看到內部定義的那個sum var sum = function(){ var i,sum = 0; for(i=0;i<arguments.length;i++){ sum += arguments[i]; } return sum; }; console.log(sum(1,2,3,4,5,6,7,8,9,10));// (1+10)*10/2=55
- 返回(return)
子函數 () { return ; // 使子函數提前啊結束,不在執行return以下的代碼程序。 } // 結束函數體,講控制權交還給主函數 主函數 () { 調用 子函數(); } // 一個函數總會有一個返回值的,如果都沒有的時候,就會返回一個undefined // 而當是構造函數,且返回對象不是一個對象的時候,則返回this(該新對象) // 另需要注意的是 // 當返回值 比如是個json數據類型的時候 rerun { status:true; }; // 如果寫成了下面這樣,就不會返回json對象,只會返回undefined // 原因就是 js 會在return 後面自動補上 ; // 這是js的一個坑 需要注意 rerun { status:true; };
- 異常(exceptions)
var add = function (a,b){ if(typeof a !== 'number' || typeof b !== 'number' ){ throw { name : 'TypeError', message : 'add needs numbers' }; } return a + b; } var try_it = function(){ try{ add("seven"); }catch(e){ document.writeln(e.name + ": " + e.message); } } try_it();
- 擴充類型的功能(augmenting types)
// TODO 說是以前是 Object.prototype 需要在這個原型鏈上面 纔可以給對象 追加方法 // 這個還是沒太明白 還需要想想 // 通過給 Function.prototype 增加方法來使得該方法對所有函數都管用 // 通過給 Function.prototype 增加一個method的方法,下次給對象增加方法的時候就不用必須鍵入 // prototype這幾個字符 Function.prototype.method = function (name,func){ // 因爲是通過基本條件增加的方法,所以加一個條件判斷 // 在確定沒有該方法的時候,纔去追加該方法 if(!this.prototype[name]){ this.prototype[name] = func; } return this; } // js沒有專門的整數類型 取整??? // js本身的取整 有些醜陋 // 一般會通過給 Number.prototype增加一個 integer 方法來改善它 // 根據數字的正負 來判定是使用 ceiling 還是 使用 floor // 如下 Number.method('integer',function(){ return Math[this < 0 ? 'cell' : 'floor'](this); }); document.writeln((-10/3).integer()); document.writeln((10/3).integer()); // js缺少一個trim的方法 String.method('trim',function(){ return this.replace(/^\s+|\s$/g,''); }); document.writeln('"' + " neat ".trim() + '"');
- 遞歸(recursion)
// 1.漢諾塔 var hanoi = function (disc, one , two , three){ if(disc > 0){ hanoi(disc - 1,one , three , two); document.writeln('Move disc ' + disc + 'from ' + one + ' to ' + three + '<br>'); hanoi(disc - 1,two , one , three ); } }; hanoi( 3 , '①' , '②', '③' ); // 對 非常 nice // TODO 我還要做一個 圖形界面的 回頭單獨做一個 link 文章 // 因爲覺得 可能需要寫一些 css js目測 應該並不會太多
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script type="text/javascript"> /// 另外一個問題 我在想另外一個問題 遞歸是不是 和分而治之 的 那個動態規劃 // 就是那個什麼把大問題 切成 小問題 分別解決的那個事情 是一起的 // 【劃重點啊】遞歸函數 // 可以非常高效的去遍歷樹形結構的數據類型 // 比如瀏覽器短的文檔對象模型DOM // 思路就是每次遞歸調用的時候,都處理指定的樹的一小段 // 定義 wait_the_DOM 函數,它從某個指定的節點開始,按HTML源碼中的順序 // 訪問每一個節點 // 她會調用一個函數 // 並且依次傳遞每個節點給他 // wait_the_DOM 調用自身去處理每一個節點 var wait_the_DOM = function walk(node,func){ // console.log(node); // console.log(node.firstChild); func(node); node = node.firstChild; while(node){ walk(node,func); node = node.nextSilbling; } } // 定義 getElementsByAttribute 函數。 // 它以一個屬性名稱字符串和一個可選的匹配值作爲參數。 // 她調用 wait_the_DOM ,傳遞一個用來查找節點屬性名的函數作爲參數。 // 匹配的節點會累加到一個結果數組中。 var getElementsByAttribute = function (att , value) { var results = []; wait_the_DOM(document.body, function (node) { // console.log(node); var actual = node.nodeType === 1 && node.getAttribute(att); if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){ results.push(node); } }); return results; }; </script> </head> <body class='testdiv'> <div class='testdiv'> 1<div>11<div class='testdiv'>111</div></div> </div> <div>2</div> <div class='testdiv'>3</div> <script type="text/javascript"> // for(var ele in getElementsByAttribute('class','testdiv')){ // console.log(ele); // } console.log(getElementsByAttribute('class','testdiv')); </script> </body> </html>
// 尾遞歸優化 // 如果一個函數返回自身遞歸調用的結果,那麼調用過程會被替代爲一個循環,可以顯著提高速度。 // js當前沒有提供尾遞歸優化。 // 深度遞歸的函數可能會因爲 堆棧溢出 而運行失敗 // 構建一個帶尾遞歸的函數 // 因爲它會返回自身調用結果,所以是尾遞歸 // 階乘 n!=1×2×3×...×(n-1)×n var factorial = function factorial (i,a){ a = a || 1; if(i<2){ return a; } return factorial (i-1,a*i); } console.log(factorial(5));
- 作用域(scope)
// js 迷之作用域 // 。。。。。。 var foo = function (){ var a=3,b=5; var bar = function () { var b = 7,c=11; // 此時 a = 3 , b = 7 ,c = 11 // 而且 我理解 bar 中的 b 也不是 foo 中的b // b被蓋掉了 console.log("1====="," a= " + a," b= " + b," c= " + c); a += b+c; // 此時 a = 21 , b = 7 ,c = 11 console.log("2====="," a= " + a," b= " + b," c= " + c); }; // 此時 a = 3,b=5 ,c 沒有定義 console.log("3====="," a= " + a," b= " + b); bar(); // 此時 a = 21,b=5 console.log("4====="," a= " + a," b= " + b); }; foo();
- 閉包(closure)
// 作用域的好處,便是內部函數可以訪問 外部函數的參數和變量 // 除了 this 和 arguments // TODO 一個更有趣的 內部函數 擁有比外部函數 更長的生命週期 ??? var myObject = (function(){ var value = 0; return { increment:function(inc){ value += typeof inc === 'number' ? inc:1; }, getValue:function(){ return value} }; }()); console.log(myObject.getValue()); myObject.increment(5); console.log(myObject.getValue()); // 結果是 0 5 // 效果是 函數外面 看不到 value 不能直接訪問value // 當時 通過 return 出來的函數 // 可以修改 value的 value !!! // 感覺 有點像 java 裏面 封裝了那個私有的成員變量 // 當時可以 set get等等 公開化的成員函數 // 去對數據進行 修改操作的調調 ~~~ // 我要找回自己,找回我希冀中的自己 // 如果曾經錯過,我希望未來不要繼續迷失 // 我要努力遇見 // 遇見那個未來的 // 喜歡的自己 // 加油~~~
// 創建一個quo構造函數 // 她構造出帶有 get_status 方法 和status 私有屬性的一個對象。 var quo = function (status){ return { get_status : function (){ return status; } }; }; // 構造一個 quo實例 var myQuo = quo("chinsei"); console.log(myQuo.get_status());
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script type="text/javascript"> // 定義一個函數 DOM節點 漸漸變色 var fade = function(node){ var level = 1; var step = function(){ var hex = level.toString(16); node.style.backgroundColor = '#FFFF' + hex +hex; if(level < 15){ level +=1; setTimeout(step,100); // console.log(level); }else if(level > 0){ // level -=1; level = 1; // console.log('AA'+level); setTimeout(step,100); } }; setTimeout(step,100); } fade(document.body); </script> </body> </html>
// 這個 李遊 老師講過 // bug 就是 只能alert 出來 3 // 打印出來的不對 // 構造一個函數 ,用錯誤的方式給一個數組中的節點 設置事件處理程序 // 當點擊一個節點時,按照預期,應該 彈出來 123 // 但是實際上彈出來的 只是 333 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .testdiv{ width: 100px; height: 100px; background-color: orange; margin: 10px; } </style> </head> <body> <div class='testdiv'>1</div> <div class='testdiv'>2</div> <div class='testdiv'>3</div> <script type="text/javascript"> var add_the_handlers = function(nodes){ var i; for(i=0;i<nodes.length;i++){ nodes[i].onclick = function(e){ alert(i); } } } add_the_handlers(document.getElementsByClassName('testdiv')); </script> </body> </html>
// 改良以後 // 用正確的方式 綁定點擊事件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .testdiv{ width: 100px; height: 100px; background-color: orange; margin: 10px; } </style> </head> <body> <div class='testdiv'>1</div> <div class='testdiv'>2</div> <div class='testdiv'>3</div> <script type="text/javascript"> var add_the_handlers = function(nodes){ var helper = function(i){ return function(e){ alert((i+1)); }; } var i; for(i=0;i<nodes.length;i++){ // nodes[i].onclick = function(e){ // alert(i); // } nodes[i].onclick = helper(i); } } add_the_handlers(document.getElementsByClassName('testdiv')); </script> </body> </html>
- 回調(callbacks)
// 修改前 request = prepare_the_request(); response = send_request_synchronously(request); display(response); ==========>>>>>>>>>>>>>>>>> // 修改後 request = prepare_the_request(); send_request_synchronously(request,response,function(){ display(response); }); // 且 並不是一個線程 數據返回以前就死等 的效果 // 異步等待數據的獲得 // 達到 一定 收到數據以後才綁定 返回 響應的效果
- 模塊(module)
// 用來產生 序列號 的對象 // 返回一個用來產生唯一字符串的對象 // 唯一字符串的組成:前綴 + 序列號 // 該對象包含一個設置前綴的方法,一個設置序列號的方法 // 和一個產生唯一字符串的 gensym 方法 var serial_maker = function(){ var prefix = ''; var seq = 0; return { set_prefix : function(p){ prefix = String(p); }, set_seq : function(s){ seq = s; }, gensym : function(){ var rs = prefix + seq; seq++; return rs; } }; }; var seqer = serial_maker(); console.log(seqer); seqer.set_prefix('Q'); seqer.set_seq(1000); var unique = seqer.gensym(); console.log(unique);
Function.prototype.method = function (name,func){ if(!this.prototype[name]){ this.prototype[name] = func; } return this; } String.method('deentityify',function(){ // 字符實體表。她映射字符實體的名字到對應的字符。 var eentity = { quot:'"', lt:'<', gt:'>' }; // 返回deentityify方法 return function(){ // 這是deentityify方法,調用replace // 查找 ^'&' $';' 字符串 // 當這樣的字符串被找到 就被 替換成 映射表中的值 return this.replace(/&([^&;]+);/g,function(a,b){ var r = eentity[b]; return typeof r === 'string' ? r : a; }); }; }());// 最後的這個()運算法 立刻調用我們剛剛構造出來的函數。 // 這個調用所創建並返回的函數纔是deentityify方法。 console.log("<">".deentityify());
- 級聯(cascade)
有一些方法沒有 返回值 不想返回 undefined 想返回 this的時候 ,需要用到級聯!!!???
- 柯里化(curry)
Function.prototype.method = function (name,func){ if(!this.prototype[name]){ this.prototype[name] = func; } return this; } var add = function(a,b){ return a+b; } Function.method('curry',function(){ var slice = Array.prototype.slice, args = slice.apply(arguments), that = this; return function(){ return that.apply(null,args.concat(slice.apply(arguments))); }; }); var add1 = add.curry(1); document.writeln(add1(6));
- 記憶(memoization)
// 函數會記憶先前操作的結果 記錄在某個對象裏 // 從而避免重複 運算 // js 的對象和數組 實現這種優化 非常方便 // 【※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※】 var memoizer = function (memo,formula){ var recur = function(n){ var result = memo[n]; if(typeof result !== 'number'){ result = formula(recur,n); memo[n] = result; } return result; }; return recur; } var fibonacci = memoizer([0,1],function(recur,n){ return recur(n-1) + recur(n-2); }); console.log(fibonacci(10)); var factorial = memoizer([1,1],function(recur,n){ return n * recur(n-1); }); console.log(factorial(6)); // 好麼 這麼好用 ~~~ 服了 !! 還是有點不太懂 ,我還得想想 ~~~