javascript(函數)

function 

函數是一段能夠重複調用的代碼塊。接收相應的參數並能夠返回對應的值javascript

函數的聲明

  javascript 有三種聲明函數的方法。function 命令函數表達式Function構造器。java

    (1)function 命令es6

     function命令聲明的代碼區塊,就是一個函數。function命令後面是函數名,函數名後面是一對圓括號,裏面是傳入函數的參數。函數體放在大括號裏面。     數組

        function add(x, y) {
            console.log(x + y)
        }
        add(3,4);//7

    上面的代碼命名了一個add函數,輸出兩個數字相加的和,之後使用add()這種形式,就能夠調用相應的代碼。這叫作函數的聲明(Function Declaration)。安全

   (2)函數表達式閉包

      除了用function命令聲明函數,還能夠採用變量賦值的寫法。   函數

        let add=function (x, y) {
            console.log(x + y)
        };
        add(3,4);

      這種寫法將一個匿名函數賦值給變量。這時,這個匿名函數又稱函數表達式(Function Expression),由於賦值語句的等號右側只能放表達式。性能

      採用函數表達式聲明函數時,function命令後面不帶有函數名。若是加上函數名,該函數名只在函數體內部有效,在函數體外部無效。    優化

        let add=function addAdd(x, y) {
            console.log(addAdd)
        };
        console.log(addAdd) //報錯  未定義
        add(3,4);// 輸出函數自己

     上面代碼在函數表達式中,加入了函數名addAdd。這個addAdd只在函數體內部可用,指代函數表達式自己,其餘地方都不可用。這種寫法的用處有兩個,一是能夠在函數體內部調用自身,二是方便除錯(函數內部報錯時,函數名存在時報錯時錯誤信息中會包含函數名,函數名不存在時會指向引用的值的名稱)。所以,下面的形式聲明函數也很是常見。 spa

  

   

        let add = function add(x, y) {
            console.log(x + y)
        };
        add(3, 4);

    須要注意的是,函數的表達式須要在語句的結尾加上分號,表示語句結束。而函數的聲明在結尾的大括號後面不用加分號。總的來講,這兩種聲明函數的方式,差異很細微,能夠近似認爲是等價的。

    (3) Function 構造器     

        let add=new Function(
            'x','y','console.log(x+y)'
        )
        add(3,4);//7
        
        let say=new Function(
            'console.log("hello")'
        )
        say();//hello

      你能夠傳遞任意數量的參數給Function構造函數,只有最後一個參數會被當作函數體,若是隻有一個參數,該參數就是函數體。    

      Function構造函數能夠不使用new命令,返回結果徹底同樣。 總的來講,這種聲明函數的方式很是不直觀,幾乎無人使用。

函數的重複聲明

  若是同一個函數被屢次聲明,後面的聲明就會覆蓋前面的聲明

        function add(x,y){
            console.log(x+y);
        }
        add(3,4);//12
        
        function add(x,y){
            console.log(x*y)
        }
        add(3,4);//12

  上面代碼中,後一次的函數聲明覆蓋了前面一次。並且,因爲函數名的提高(參見下文),前一次聲明在任什麼時候候都是無效的,這一點要特別注意。

圓括號運算符,return 語句和遞歸

  調用函數時,要使用圓括號運算符。圓括號之中,能夠加入函數的參數。

        function add(x,y){
            return x+y
       console.log('result')//不會執行 } let result
=add(3,4); console.log(result)//7 

  上面代碼中,函數名後面緊跟一對圓括號,就會調用這個函數。 函數體內部的return語句,表示返回。JavaScript 引擎遇到return語句,就直接返回return後面的那個表達式的值,後面即便還有語句,也不會獲得執行。也就是說,return語句所帶的那個表達式,就是函數的返回值。return語句不是必需的,若是沒有的話,該函數就不返回任何值,或者說返回undefined。

  函數能夠調用自身,這就是遞歸(recursion)。下面就是經過遞歸,計算數字的階乘 

        function fact(i) {
            if (i == 1) {
                return 1
            }
            return i * fact(i - 1)
        }
        console.log(fact(3)) //6

  上面的代碼就是

fact(3)==>3*fact(3-1)==>3*fact(2)
fact(2)==>2*fact(2-1)==>3*fact(1)
fact(1)==>1
fact(3)==>3*2*1==>6

第一等公民

  一等公民:通常來講,若是某程序設計語言中的一個值能夠做爲參數傳遞,能夠從子程序中返回,能夠賦值給變量

  二等公民:能夠做爲參數傳遞,可是不能從子程序中返回,也不能賦給變量

  三等公民:它的值連做爲參數傳遞都不行  

  JavaScript 語言將函數看做一種值,與其它值(數值、字符串、布爾值等等)地位相同。凡是可使用值的地方,就能使用函數。好比,能夠把函數賦值給變量和對象的屬性,也能夠看成參數傳入其餘函數,或者做爲函數的結果返回。函數只是一個能夠執行的值,此外並沒有特殊之處。 因爲函數與其餘數據類型地位平等,因此在 JavaScript 語言中又稱函數爲第一等公民

  

        function add(x, y) {
            console.log(x + y);
        }

        // 將函數賦值給一個變量
        let operator = add;
        operator(3, 4); //7


        // 將函數做爲參數和返回值
        function addOperat(fn) {
            return fn;
        }
        addOperat(add)(3, 4) //7

函數名的變量提高

  JavaScript 引擎將函數名視同變量名,因此採用function命令聲明函數時,整個函數會像變量聲明同樣,被提高到代碼頭部、(另外兩種聲明是不會存在這種狀況的)因此,下面的代碼不會報錯。 

        let operator = add;
        operator(3, 4); //7
        function add(x, y) {
            console.log(x + y);
        }

下面這兩種狀況都會報錯 

  

        // add 未定義
        add();
        let add=function(){
            console.log(1)
        }    

  

        // add 不是一個函數
        let add;
        add();
        add=function(){
            console.log(1)
        }

  所以,若是同時採用function命令和賦值語句聲明同一個函數,最後老是採用賦值語句的定義。由於函數表達式聲明的函數不會存在函數名的變量提高  

        // 這裏不使用 let f
        var f=function(){
            console.log(1)
        };
        function f(){
            console.log(2)
        }
        f();//1

函數的屬性和方法

  name屬性

    獲取函數的原始名稱

        function f(){}
        console.log(f.name);//f
        
        let f2=function (){};
        console.log(f2.name);//f2
        
        let f4=function f3(){};
        console.log(f4.name);//f3

  length屬性

    函數的length屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數

    

        function f(){}
        console.log(f.length);//0
        
        let f2=function (a,b){};
        console.log(f2.length);//2
        
        let f4=function f3(a){};
        console.log(f4.length);//1

  toString()

    輸出函數的內容(一切內容、包括註釋)

    

        function add(x,y){
            let a=x;
            let b=y;
            console.log(x+y)
        }
        console.log(add.toString())// 輸出函數本省
        console.log(Math.ceil.toString())
        //對於那些原生的函數,toString()方法返回function (){[native code]}

函數做用域

  做用域(scope)指的是變量存在的範圍。在 ES5 的規範中,JavaScript 只有兩種做用域:一種是全局做用域,變量在整個程序中一直存在,全部地方均可以讀取;另外一種是函數做用域,變量只在函數內部存在。ES6 又新增了塊級做用域。

  全局做用域,在任何地方均可以訪問

        let age='18';
        function getAge(){
            console.log(age)
        }
        console.log(age);//18
        getAge();// 18  age全局做用域 函數內部也可訪問

  函數做用域,只在函數內部能夠訪問

  

        function getAge(){
            var age='qzy';
            console.log(age)
        }
        getAge()//qzy
        console.log(age)// age未定義 函數內部定義的變量 只有函數內部能夠訪問

  塊級做用域 es6 新增的 let 聲明變量 

        {
            let age='25'
        }
        console.log(age) // age未定義

  函數內部定義的變量,會在該做用域內覆蓋同名全局變量。

        let age=18;
        function getAge(){
            let age=25
            console.log(age)
        }
        console.log(age);//18
        getAge();//25

  注意,對於var命令來講,局部變量只能在函數內部聲明,在其餘區塊中聲明,一概都是全局變量。 

        if(true){
            var age=25;
        }
        console.log(age);//25

函數內部的變量提高

  與全局做用域同樣,函數做用域內部也會產生「變量提高」現象。var命令聲明的變量,無論在什麼位置,變量聲明都會被提高到函數體的頭部。

  

        function ageLevel(age){
            if(age>100){
                var level='高手'
            }
            console.log(level)
        }
        ageLevel(101);//高手
        
        // 等價於
        function ageLevelEqual(age){
            var level;
            if(age>100){
                level='高手'
            }
            console.log(level)
        }
        ageLevelEqual(101);//高手

函數自己的做用域

  函數自己也是一個值,也有本身的做用域它的做用域與變量同樣,就是其聲明時所在的做用域,與其運行時所在的做用域無關  

        let age=25;
        function getAge(){
            console.log(age)
        }
        
        function setAge(){
            let age=26;
            getAge();
        }
        
        setAge();//25

  getAge函數是在最外層聲明的,因此它綁定的做用域是最外層,在函數內部變量age不會影響到其綁定的最外層的值

 

  一樣的,函數體內部聲明的函數,做用域綁定函數體內部。 

        let age = 25;

        function setAge() {
            let age = 26;

            function getAge() {
                console.log(age)
            }
            // 將聲明的函數返回
            return getAge
        }
        setAge()();// 26

  正是這種機制,構成了下文要講解的「閉包」現象。

函數的參數

  函數運行的時候,有時須要提供外部數據,不一樣的外部數據會獲得不一樣的結果,這種外部數據就叫參數。函數參數不是必需的,JavaScript 容許省略參數。

        function square(x){
            return x*x
        }
        
        console.log(square(2));//4
        console.log(square(3));//9

  函數的參數傳遞方式分爲兩種:傳值傳遞、傳址傳遞

    傳值傳遞:函數參數若是是原始類型的值(數值、字符串、布爾值),則採用這種方式,此時在函數體內修改參數值,不會影響到函數外部    

        let age=25;
        function setAge(age){
            age=26;
        }
        console.log(age);//25

    傳址傳遞:若是函數參數是複合類型的值(數組、對象、其餘函數),則採用這種方式,傳入函數的原始值的地址,此時在函數體內修改參數值,會影響到函數外部(有特殊的)  

        let person={
            age:25
        };
        function setPerson(person){
            person.age=26;
        }
        setPerson(person);
        console.log(person.age);//26

    person 爲一個對象 在函數背部修改其中的屬性,外面的變量中的值也作相應的改變

 

    注意,若是函數內部修改的,不是參數對象的某個屬性,而是替換掉整個參數,這時不會影響到原始值。   

        let person={
            age:25
        };
        function setPerson(person){
            person={
                age:26
            };
        }
        setPerson(person);
        console.log(person.age);//25

    在函數f內部,參數對象person被整個替換成另外一個值。這時不會影響到原始值。這是由於,形式參數person的值原本是是參數person的地址,從新對person賦值致使person指向另外一個地址,保存在原地址上的值固然不受影響。

同名參數

  若是有同名的參數,則取最後出現的那個值。 

        function add(a,a){
            console.log(a)
        }
        add(1);// undefined 形參爲兩個 同名取最後一個 可是未傳遞 因此未定義 
        add(1,2);// 2
        add(1,2,3);// 2

arguments 對象

  因爲 JavaScript 容許函數有不定數目的參數,因此須要一種機制,能夠在函數體內部讀取全部參數。這就是arguments對象的由來。 arguments對象包含了函數運行時的全部參數,arguments[0]就是第一個參數,arguments[1]就是第二個參數,以此類推。這個對象只有在函數體內部,纔可使用。  

        function add(one){
            let len=arguments.length;// 獲取傳入的參數個數
            for(let i=0;i<len;i++){
                console.log(arguments[i])
            }
        }
        add(1,2,3,4,5)

  注:arguments對象能夠在運行時修改,修改後對應的形參就是修改後的值;可是在開啓嚴格模式下能夠修改,可是不會影響到形參, 

        var f = function(a, b) {
            'use strict'; // 開啓嚴格模式
            arguments[0] = 3;
            arguments[1] = 2;
            return a + b;
        }

        console.log(f(1, 1)) // 2

  與數組的關係,須要注意的是,雖然arguments很像數組,但它是一個對象。數組專有的方法(好比slice和forEach),不能在arguments對象上直接使用。 若是要讓arguments對象使用數組方法,真正的解決方法是將arguments轉爲真正的數組。下面是兩種經常使用的轉換方法:slice方法和逐一填入新數組。

        function add(one){
            let args = Array.prototype.slice.call(arguments);
            args.forEach(list=>{
                console.log(list)
            })
        }
        add(1,2,3,4,5) 
        function add(one) {
            let args = [];
            for (let i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            args.forEach((list)=>{
                console.log(list)
            })
        }
        add(1, 2, 3, 4, 5)

  均可以使用forEach 輸出因此的參數值

callee 屬性

  arguments對象帶有一個callee屬性,返回它所對應的原函數,能夠經過arguments.callee,達到調用函數自身的目的。這個屬性在嚴格模式裏面是禁用的,所以不建議使用 

        function add() {
            console.log(arguments.callee==add)
        }
        add();//true

閉包

  閉包(closure)是 JavaScript 語言的一個難點,也是它的特點,不少高級應用都要依靠閉包實現。 理解閉包,首先必須理解變量做用域。前面提到,JavaScript 有兩種做用域:全局做用域和函數做用域。函數內部能夠直接讀取全局變量。

  函數內部能夠讀取函數外部的變量,可是函數外部是不能讀取到函數內部的變量的,這個在上面已經實驗過了

  若是出於種種緣由,須要獲得函數內的局部變量。正常狀況下,這是辦不到的,只有經過變通方法才能實現。那就是在函數的內部,再定義一個函數  

        function person(){
            let age=18;
            function getAge(){
                return age
            }
            return getAge
        }
        let p=person();// 接收getAge
        let pAge=p();
        console.log(pAge);// 18

  上面的代碼 咱們在person內部定義了一個age字段 ,可是咱們想在外部取不到這個值,這個時候咱們能夠在函數內部定義一個函數,這個函數在person 函數內部,是能夠取到person內部的值的,而後咱們再把函數暴露出來,就能夠獲取到這個值了,那此時getAge函數就是閉包,即可以讀取其餘函數內部變量的函數。因爲在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

  閉包的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量始終保持在內存中,即閉包可使得它誕生環境一直存在。請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果  

        function createIn(count){
            return function (){
                console.log(++count);
            }
        }
        
        let result=createIn(0);
        result();//1
        result()//2
        result()//3

 

  上面代碼中,count是函數createIn的內部變量。經過閉包,count的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中能夠看到,閉包result使得函數createIn的內部環境一直存在。因此,閉包能夠看做是函數內部做用域的一個接口。

  爲何會這樣呢?緣由就在於result始終在內存中,而result的存在依賴於createIn,所以也始終在內存中,不會在調用結束後,被垃圾回收機制回收

  閉包的另外一個用處,是封裝對象的私有屬性和私有方法。  

        function Person(name) {
            var _age;

            function setAge(n) {
                _age = n;
            }

            function getAge() {
                return _age;
            }

            return {
                name: name,
                getAge: getAge,
                setAge: setAge
            };
        }

        var p1 = Person('張三');
        p1.setAge(25);
        console.log(p1.getAge()) // 25

  上面代碼中,函數Person的內部變量_age,經過閉包getAge和setAge,變成了返回對象p1的私有變量。

  注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題

當即調用的函數表達式(IIFE) 

  在 JavaScript 中,圓括號()是一種運算符,跟在函數名以後,表示調用該函數。好比,print()就表示調用print函數。 有時,咱們須要在定義函數以後,當即調用該函數。這時,你不能在函數的定義以後加上圓括號,這會產生語法錯誤。

        function say(){
            /* code */
        }();// Uncaught SyntaxError: Unexpected token )

  產生這個錯誤的緣由是,function這個關鍵字便可以看成語句,也能夠看成表達式。

        // 語句
        function f() {}

        // 表達式
        var f = function f() {}

  爲了不解析上的歧義,JavaScript 引擎規定,若是function關鍵字出如今行首,一概解釋成語句。那不是的就是函數表達式嘍。所以,JavaScript 引擎看到行首是function關鍵字以後,認爲這一段都是函數的定義,不該該以圓括號結尾,因此就報錯了。

  下面函數表達式後面跟上圓括號是能夠當即執行的。

        // 表達式 直接輸出1
        var f = function () {
            console.log(1)
        }()

  解決方法就是不要讓function出如今行首,讓引擎將其理解成一個表達式。最簡單的處理,就是將其放在一個圓括號裏面。

        (function (){console.log(1)})();//1
        (function (){console.log(2)}());//2

  上面兩種寫法都是以圓括號開頭,引擎就會認爲後面跟的是一個表示式,而不是函數定義語句,因此就避免了錯誤。這就叫作「當即調用的函數表達式」(Immediately-Invoked Function Expression),簡稱 IIFE。 注意,上面兩種寫法最後的分號都是必須的。若是省略分號,遇到連着兩個 IIFE,可能就會報錯 

        (function (){console.log(1)})()//1
        (function (){console.log(2)}())//2
        // Uncaught TypeError: (intermediate value)(...) is not a function

  上面代碼的兩行之間沒有分號,JavaScript 會將它們連在一塊兒解釋,將第二行解釋爲第一行的參數

  推而廣之,任何讓解釋器以表達式來處理函數定義的方法,都能產生一樣的效果,只要讓引擎認爲它是表達式便可。

        let i =function (){console.log(1)}();//1
        
        true && function (){console.log(2)}();//2
        
        +function (){console.log(3)}();//3

  一般狀況下,只對匿名函數使用這種「當即執行的函數表達式」。它的目的有兩個:一是沒必要爲函數命名,避免了污染全局變量;二是 IIFE 內部造成了一個單獨的做用域,能夠封裝一些外部沒法讀取的私有變量。

eval 命令

  eval命令接受一個字符串做爲參數,並將這個字符串看成語句執行。若是參數字符串沒法看成語句運行,那麼就會報錯。放在eval中的字符串,應該有獨自存在的意義,不能用來與eval之外的命令配合使用。舉例來講,下面的代碼將會報錯。若是eval的參數不是字符串,那麼會原樣返回。

        eval('var a=1');
        console.log(a);//1
        console.log(eval(123));//123
     eval('3x');//報錯
     eval('return')//報錯

  eval沒有本身的做用域,都在當前做用域內執行,所以可能會修改當前做用域的變量的值,形成安全問題 

        var a=0;
        eval('var a=1');
        console.log(a);//1

  爲了防止這種風險,JavaScript 規定,若是使用嚴格模式,eval內部聲明的變量,不會影響到外部做用域。

       'use strict'
        var a=0;
        eval('var a=1');
        console.log(a);//0

  若是我不聲明,直接修改a,能夠看到仍是能夠修改爲功的。

       'use strict'
       var a=0;
       eval('a=1');
       console.log(a);//1

  上面代碼中,嚴格模式下,eval內部仍是改寫了外部變量,可見安全風險依然存在。一般狀況下,eval最多見的場合是解析 JSON 數據的字符串,不過正確的作法應該是使用原生的JSON.parse方法。

        let str = '{"name": "qzy", "age": 25}';
        let obj = eval('('+str+')');//let obj=JSON.parse(str);
        console.log(obj); // Object {name: "hanzichi", age: 10}

  eval 解析時爲何要加圓括號呢 ?

        let obj='{}';
        console.log(eval(obj));//undefined
        console.log(eval('('+obj+')'));//{}

  上面說過eval 會執行輸入的字符串,obj是以{開頭、}結尾的。那js 引擎就會把這當成代碼塊,執行裏面的語句。

eval 的別名調用

  前面說過eval不利於引擎優化執行速度。更麻煩的是,還有下面這種狀況,引擎在靜態代碼分析的階段,根本沒法分辨執行的是eval。

        "use strict"
        let my=eval;
        my('var a=1');
        console.log(a);//1

  爲了保證eval的別名不影響代碼優化,JavaScript 的標準規定,凡是使用別名執行eval,eval內部一概是全局做用域。詳細的看下一篇對象的講解 

        var a = 1;

        function f() {
            var a = 2;
            var e = eval;
            e('console.log(a)');
        }

        f() // 1

  就一句話,沒事別用eval。

相關文章
相關標籤/搜索