一 背景node
js函數的兩個特色:1 函數是第一類對象(first-class object);2 函數能夠提供做用域數組
1 函數是對象:瀏覽器
1 函數能夠在運行時動態建立,還能夠在程序執行過程當中建立緩存
2 能夠被賦值給變量,還能夠被刪除閉包
3 能夠做爲參數傳遞給別的函數,能夠做爲返回值,被別的函數返回,app
4 能夠擁有本身的屬性和方法dom
2 因爲JS沒有塊級做用域的概念,所以在涉及到控制變量做用域的時候,函數是必不可少的工具異步
1.1 消除術語歧義:咱們來看一下命名函數表達式、大家函數表達式以及函數聲明的定義ide
1 通常的函數表達式即爲匿名函數表達式,簡稱匿名函數,即var add=function(){};函數
2 命名函數表達式:var add1=function add(){};
3 命名函數表達式是函數表達式的一種特殊狀況,命名函數表達式與函數表達式的區別在於:其name屬性
4 函數聲明:function add(){}
var add1=function add(){};//命名函數表達式 console.log(add1.name);//add //函數表達式/匿名函數表達式/匿名函數 var add2=function (){}; console.log(add2.name);//"" //函數聲明 function add3(){} console.log(add3.name);//add3
1.2 聲明VS表達式:名稱與變量聲明提高
函數聲明只能出如今「程序代碼」中,即函數聲明只能存在於其餘函數體內或者全局空間中,它們不能分配給變量或者某個屬性,也不能做爲參數出如今函數調用中
//命名函數表達式 callMe(function me(){ }); //匿名函數表達式 callMe(function(){ }); //函數表達式 var obj={ say:function(){ } };
1.3 函數的name屬性
函數的name屬性是隻讀的
name屬性的用途:調試代碼的時候,能夠根據name屬性做爲一個標識符;能夠用於自身的遞歸運算
1.四、函數的提高
雖然函數聲明與函數命名錶達式很類似,可是兩者之間仍是有很大區別的,這個區別就是函數的提高
咱們知道對於全部變量,不管是在函數體的何處進行的聲明,都會在後臺被提高到函數的頂部,函數聲明也同樣,不管函數聲明在何處,都會被提高到頂部,
而函數表達式不會獲得提高,下面咱們來看一個例子
var name="Jim"; fun();//函數聲明! var fun=function(){ alert("函數表達式!"); } fun();//函數表達式! function fun(){ alert("函數聲明!"); } fun();//函數表達式!
二 回調模式
2.1 回調模式:將函數A做爲參數傳遞給函數B,且A在函數B中獲得執行,那麼函數A就被稱之爲回調函數,該模式稱之爲回調模式
function A(){ console.log("I am a callback !"); } function B(callback){ if(typeof callback!=="function"){ callback=false; } if(callback){ callback(); } } B(A);//I am a callback !
2.2 回調示例
假設咱們須要抓取頁面上的DOM樹,並返回相應的DOM數組,對該數組裏的DOM進行操做,例如隱藏
根據函數的通用性,一個函數是找到相應的DOM,並返回數組(findNodes),另一個函數是隱藏功能(hide)
先執行findNodes,而後獲取到返回的DOM數組,再進行for循環進行遍歷,隱藏,效率有些低下,咱們採起回調模式
var findNodes(callback){ var nodes=[],found; if(typeof callback!="function"){ callback=false; } while(條件爲true){ find;//......查找相關節點的操做 if(callback){ callback(found); } } return nodes; }; findNodes(hide);
2.三、回調模式與做用域
問題:在某些狀況下,咱們的回調函數不是一次性的匿名函數也不是全局函數,而是某個對象的方法,若是在該方法中使用了this來引用它所屬的對象,將會出現如下問題
color="black";
//自定義對象myapp var myapp={ color:"red", paint:function(arg){ arg=this.color; //將myapp的color賦值給參數的color屬性 console.log(arg); } }; function B(callback){ if(typeof callback!=="function"){ callback=false; } var B_color={}; if(callback){ callback(B_color); } } B(myapp.paint);//black //因爲B函數是一個全局函數,所以,對象中的this指向全局對象window,而不是預期的myapp
解決方案:將回調函數及其所屬對象一併傳遞給函數B
color="black";
var myapp={ color:"red", paint:function(arg){ arg=this.color; console.log(arg); } }; //將回調函數callback與回調函數所屬的對象一併傳遞進去 function B(callback,callback_obj){ var B_color; if(typeof callback==="function"){ callback.call(callback_obj,B_color); //注意,這裏再也不只是單純的直接調用回調函數,而是回調函數所屬的對象對回調函數加以調用 } } B(myapp.paint,myapp);//red
進一步優化方案:由上面能夠看出,在調用myapp的paint方法時,須要輸入兩次對象名myapp,咱們能夠對其進行以下優化,將該方法做爲字符串來傳遞,無需兩次輸入該對象的名稱
function B(callback,callback_obj){ var B_name={}; if(typeof callback==="string"){ callback=callback_obj[callback]; } if(typeof callback==="function"){ callback.call(callback_obj,B_name); } } B("paint",myapp);//red
6、回調函數的用途
1 異步事件監聽器
例如:給頁面元素提供一個回調函數的指針,使得當該事件觸發時能夠獲得調用,該模式支持異步方式,即容許以亂序的方式運行
2 超時調用
當使用window提供的超時方法:setTimeout和setInterval中的參數是一個函數指針,也使用了回調模式,在某一個時刻觸發
3 JS庫中回調模式的應用
在設計JS庫的時候,回調模式能夠幫助JS庫實現通用性,使開發者沒必要預測和實現每個功能
由於一方面過多的功能會使JS庫過於龐大,另外一方面,有些功能能夠絕大多數用戶永遠也不會使用到。
所以在開發js庫的時候:專一於核心功能的開發,提供「掛鉤」形式的回調函數,使得JS庫能夠很容易的擴展。
三 返回函數:函數是一個對象,所以能夠做爲返回值
1 應用場景:一個函數執行一部分工做,這些工做可能包含一些一次性的初始化,後續調用它的時候,是其返回值,其返回值也是一個函數,後續操做就由其返回函數來執行
function init(){ console.log(1); return function(){ console.log(2); }; }; var s=init();//1 s();//2
注意:init函數返回了一個匿名函數,即建立了一個閉包,(這裏提到閉包的一個做用:建立私有數據,只有該匿名函數能夠訪問,外部代碼不能訪問),下面咱們來看一下對該特色的應用
function init(){ var count=0; return function(){ return count+=1; }; }; var s=init(); console.log(s());//1 console.log(s());//2 console.log(s());//3
四 自定義函數(惰性函數模式):一個函數有一些初始化的準備工做,且只須要執行一次,使用自定義函數模式可使從新定義的函數執行更少的工做
function init(){ console.log("Boo!"); init=function(){ console.log("Boo Boo!"); }; }; init();//Boo! init();//Boo Boo! init();//Boo Boo!
2 該模式又被稱爲惰性函數模式,即該函數直到第一次使用纔會被正肯定義,而且具備後向惰性,即獲得正肯定義後,會執行更少的工做
3 該模式的缺陷:當它從新定義自身時已經添加到原函數的任何屬性都會丟失,若是再將其賦值給其餘變量,那麼使用新的變量來調用該函數的話,重定義的部分永遠也得不到執行
var a,b; a=b=function(){ console.log("1"); b=function(){ console.log("2"); } }; b.age=18; a();//1 a();//1 b();//2 b();//2 console.log(b.age);//undefined
五 即時函數:函數建立後當即執行該函數的語法
1 該模式的實現方法:使用函數表達式定義一個函數;在該函數表達式末尾加一組括號,讓其當即執行;將這個包裝到括號中
(function(){ console.log("Oops!"); })();
2 應用場景:該模式提供了一個做用域沙箱,當頁面加載時,代碼必須執行一些設置任務,例如設置事件監聽器、建立對象等,但這些工做只須要執行一次,所以沒有 必要定義一個可複用的函數,若是寫在全局做用域下,有可能初始化操做還須要一些臨時變量,這些變量可能會污染全局做用域,所以咱們可使用即時函數模式, 將全部臨時變量包裝到它的局部做用域中
(function(){ var days=['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], today=new Date(), msg="今天"+days[today.getDay()];//這裏days,today以及msg都是臨時變量 console.log(msg);// 今天星期五 })();
5.1 即時函數的參數:這裏不建議過多的參數傳遞給即時函數,避免形成閱讀負擔
(function(who,when){ var days=['星期日','星期一','星期二','星期三','星期四','星期五','星期六']; console.log("我在"+days[when.getDay()]+"碰見了"+who); })("個人偶像",new Date());//我在星期五碰見了個人偶像
4 另外,應該注意全局對象也能夠做爲參數傳遞給即時函數,爲了使代碼能夠在瀏覽器以外的環境有更好的互操做性,這裏不建議在即時函數內部使用window
(function(global){ //經過global訪問全局變量 })(this);
5.2 即時函數的返回值:
var dd=(function(){ var count=0; return function(){ count++; console.log(count); }; })(); dd();//1 dd();//2 dd();//3 console.log(count);//ReferenceError: count is not defined
5.3 優勢和用法
不會污染全局空間,用於書籤工具,由於書籤工具能夠在任何網頁上運行,並保持全局命名空間的整潔;確保頁面在存在或不存在該代碼的兩種狀況下都能良好運行
六 即時函數對象化
1 保護全局做用域不被污染的方法,除了上面的即時執行函數,還有即時對象初始化模式
2 該模式的init方法在建立對象後將會當即執行,init方法負責全部的初始化任務
3 缺點:js壓縮不能有效的縮減代碼
({ max:600, min:400, getMax:function(){ return this.max; }, init:function(){ console.log(this.getMax()); return this;//若是想保存對該對象的一個引用,能夠返回this } }).init();
七 初始化時分支:加載時分支
當知道某個條件在整個程序的生命週期內是不會發生改變的時候,僅對該條件進行一次測試便可,例如瀏覽器嗅探(瀏覽器版本的檢測)等
第一個demo,咱們在使用utils.addListener()函數的時候,每次調用該函數都須要去typeof window.addEventListener....
可是第二個demo,咱們在使用utils.addListener()函數的時候,咱們只須要進行一次究竟是window.addEventListener仍是document.attachEvent仍是el["on"+type]
/*每次調用utils.addListener方法給dom元素綁定事件的時候,都須要對其進行檢測 typeof window.addEventListener typeof document.attachEvent */ var utils={ addListener:function(el,type,fn){ if(typeof window.addEventListener==="function"){ el.addEventListener(type,fn,false); }else if(typeof document.attachEvent==="function"){ el.attachEvent('on'+type,fn); }else{ el["on"+type]=fn; } } };
/* typeof window.addEventListener typeof document.attachEvent 只須要執行一次就能夠了 */ var utils={ addListener:null }; if(typeof window.addEventListener==="function"){ utils.addListener=function(el,type,fn){ el.addEventListener(type,fn,false); }; }else if(typeof document.attachEvent==="function"){ utils.addListener=function(el,type,fn){ el.attachEvent('on'+type,fn); } }else{ utils.addListener=function(el,type,fn){ el["on"+type]=fn; } }
八 函數屬性---備忘模式
1 函數是對象,所以能夠擁有屬性,使用任何語法定義的函數都會自動獲取一個length的屬性,該屬性是函數指望的參數的數量
2 自定義一個屬性,用來緩存函數的結果,下次調用函數的話就不須要作潛在的繁重的計算了,這種緩存函數就誒過的方式稱之爲備忘
3 可是若是有兩個名稱一致,可是值不一致的話,就會得不到想要的結果
var fun=function(param){ if(!fun.cache[param]){ var result={}; //...計算 fun.cache[param]=result; } return fun.cache[param]; }
fun.cache={};
九 配置對象
隨着需求的不斷變化,咱們所須要的參數可能不斷增多,這樣咱們向構造函數傳遞的參數也愈來愈多,參數會愈來愈長,實參與形參的順序,也必須保持一致
例如addPerson(firstName,lastName,age,gender,address,telphone,birthday,.......);
配置對象模式
addPerson(conf); conf={ firstName:"...", lastName:"...", age:"...", gender:"...", address:"...", telphone:"...", birthday:"..." }; /*優勢: 不須要記住衆多參數及其順序, 能夠忽略可選參數 易於閱讀和維護 易於添加和刪除 缺點: 須要記住參數的名稱 屬性名稱不能壓縮 */
十 curry
什麼是curry,術語:一個轉換過程,即咱們執行函數轉換的過程
當咱們調用某一個函數的時候,發現多個調用函數的參數大部分都一致,咱們想這些函數只執行一遍,執行其中相同的一部分,而後再各自執行剩餘的部分
例如add(1,2,3,4,5,6,10);add(1,2,3,4,5,6,100);add(1,2,3,4,5,6,1000);add(1,2,3,4,5,6,10000),其中前面的6個參數都是一致的,這種狀況下,咱們先執行add(1,2,3,4,5,6),而後再各自與add(1,2,3,4,5,6)相加
function add(a,b,c,d,e,f,g){ return a+b+c+d+e+f+g; } add(1,2,3,4,5,6,10); add(1,2,3,4,5,6,100); add(1,2,3,4,5,6,1000); add(1,2,3,4,5,6,10000); /*下面只是一個示意圖或者說咱們想要的一個效果圖,
並非真正的要這樣計算
*/ var newAdd=add(1,2,3,4,5,6); newAdd(10); newAdd(100); newAdd(1000); newAdd(10000);
10.1 Curry化
/*特殊函數的curry化*/ function add(x,y){ if(typeof y==="undefined"){ return function(y){ return x+y; }; } return x+y; } var newAdd=add(1); var result=newAdd(2); console.log(result);//3
/*特殊函數的curry化*/ function add(a,b,c,d,e,f,g){ return a+b+c+d+e+f+g; } /*下面是通用的curry化*/ function schonfinkelize(fn){ var slice=Array.prototype.slice, stored_args=slice.call(arguments,1); return function(){ var new_args=slice.call(arguments), args=stored_args.concat(new_args); return fn.apply(null,args); }; } var newAdd=schonfinkelize(add,1,2,3,4,5,6); console.log(newAdd(10));//31 console.log(newAdd(100));//121 console.log(newAdd(1000));//1021 console.log(newAdd(10000));//10021
10.2 什麼時候使用Curry化
當發現正在調用同一個函數,且傳遞的參數大多數是相同的,那麼這個函數可能用於Curry化。能夠經過將一個函數參數部分應用到函數中,從而動態的建立一個新函數,這個新函數將會保存重複的參數,所以沒必要每次都傳遞這些重複的參數