Javascript模式(第四章函數)------讀書筆記

一 背景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化。能夠經過將一個函數參數部分應用到函數中,從而動態的建立一個新函數,這個新函數將會保存重複的參數,所以沒必要每次都傳遞這些重複的參數

相關文章
相關標籤/搜索