1. 程式人生 > 實用技巧 >javascript學習(二)--函式

javascript學習(二)--函式

一、在JavaScript中,定義函式的方式如下:
   1、第一種方式:
        function abs(x) {
            if (x >= 0) {
                return x;
            } else {
                return -x;
            }
        }
        上述abs()函式的定義如下:

        function指出這是一個函式定義;
        abs是函式的名稱;
        (x)括號內列出函式的引數,多個引數以,分隔;
        { ... }之間的程式碼是函式體,可以包含若干語句,甚至可以沒有任何語句。
        結果:
          
1、執行到return時,函式就執行完畢,並將結果返回 2、如果沒有return語句,函式執行完畢後也會返回結果,只是結果為undefined。 2、第二種定義函式的方式 var abs = function (x) { if (x >= 0) { return x; } else { return -x; } }; 在這種方式下,function (x) { ... }是一個匿名函式,它沒有函式名。但是,這個匿名函式賦值給了變數abs,所以,通過變數abs就可以呼叫該函式。 上述兩種定義完全等價,注意第二種方式按照完整語法需要在函式體末尾加一個;,表示賦值語句結束。
3、呼叫函式時,按順序傳入引數即可: abs(10); // 返回10 abs(-9); // 返回9 3.1、由於JavaScript允許傳入任意個引數而不影響呼叫,因此傳入的引數比定義的引數多也沒有問題,雖然函式內部並不需要這些引數: abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9 3.2、傳入的引數比定義的少也沒有問題:abs(); // 返回NaN 3.3、要避免收到undefined,可以對引數進行檢查:
function abs(x) { if (typeof x !== 'number') { throw 'Not a number'; } if (x >= 0) { return x; } else { return -x; } } 常用函式: 1、typeof 判斷元素的型別 型別 結果 String "string" Number "number" Boolean "boolean" Undefined "undefined" Object "object" function函式物件 "function" Symbol(ES6新增) "symbol" 案例:用法 // Numbers typeof 37 === 'number'; typeof 3.14 === 'number'; typeof Math.LN2 === 'number'; typeof Infinity === 'number'; typeof NaN === 'number'; // 儘管NaN是"Not-A-Number"的縮寫 typeof Number(1) === 'number'; // 但不要使用這種形式! // Strings typeof "" === 'string'; typeof "bla" === 'string'; typeof (typeof 1) === 'string'; // typeof總是返回一個字串 typeof String("abc") === 'string'; // 但不要使用這種形式! // Booleans typeof true === 'boolean'; typeof false === 'boolean'; typeof Boolean(true) === 'boolean'; // 但不要使用這種形式! // Symbols typeof Symbol() === 'symbol'; typeof Symbol('foo') === 'symbol'; typeof Symbol.iterator === 'symbol'; // Undefined typeof undefined === 'undefined'; typeof declaredButUndefinedVariable === 'undefined'; typeof undeclaredVariable === 'undefined'; // Objects typeof {a:1} === 'object'; // 使用Array.isArray 或者 Object.prototype.toString.call // 區分陣列,普通物件 typeof [1, 2, 4] === 'object'; typeof new Date() === 'object'; // 下面的容易令人迷惑,不要使用! typeof new Boolean(true) === 'object'; typeof new Number(1) ==== 'object'; typeof new String("abc") === 'object'; // 函式 typeof function(){} === 'function'; typeof Math.sin === 'function'; //NaN typeof 1/0 === 'NaN'; 2、關鍵字arguments:arguments類似Array但它不是一個Array【它只在函式內部起作用,並且永遠指向當前函式的呼叫者傳入的所有引數】 案例1: function foo(x) { console.log('x = ' + x); // 10 for (var i=0; i<arguments.length; i++) { console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30 } } foo(10, 20, 30); 案例2: 利用arguments,你可以獲得呼叫者傳入的所有引數。也就是說,即使函式不定義任何引數,還是可以拿到引數的值: function abs() { if (arguments.length === 0) { return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9 案例3: 實際上arguments最常用於判斷傳入引數的個數。你可能會看到這樣的寫法: // foo(a[, b], c) // 接收2~3個引數,b是可選引數,如果只傳2個引數,b預設為null: function foo(a, b, c) { if (arguments.length === 2) { // 實際拿到的引數是a和b,c為undefined c = b; // 把b賦給c b = null; // b變為預設值 } // ... } 要把中間的引數b變為“可選”引數,就只能通過arguments判斷,然後重新調整引數並賦值。 3、rest引數 案例: 'use static' function foo(a, b, ...rest) { console.log('85a = ' + a); console.log('b = ' + b); console.log(rest); } 呼叫函式1: foo(1, 2, 3, 4, 5); // 結果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] 呼叫函式2: foo(1); // 結果: // a = 1 // b = undefined // Array [] 結果: rest引數只能寫在最後,前面用...標識,從執行結果可知,傳入的引數先繫結a、b,多餘的引數以陣列形式交給變數rest,所以, 不再需要arguments我們就獲取了全部引數。 如果傳入的引數連正常定義的引數都沒填滿,也不要緊,rest引數會接收一個空陣列(注意不是undefined)。 3、小心你的return語句、 前面我們講到了JavaScript引擎有一個在行末自動新增分號的機制,這可能讓你栽到return語句的一個大坑: function foo() { return { name: 'foo' }; } foo(); // { name: 'foo' } 如果把return語句拆成兩行: function foo() { return { name: 'foo' }; } foo(); // undefined 要小心了,由於JavaScript引擎在行末自動新增分號的機制,上面的程式碼實際上變成了: function foo() { return; // 自動添加了分號,相當於return undefined; { name: 'foo' }; // 這行語句已經沒法執行到了 } 所以正確的多行寫法是: function foo() { return { // 這裡不會自動加分號,因為{表示語句尚未結束 name: 'foo' }; } 案例: function area_of_circle(r, pi) { if (arguments.length < 2) { pi = 3.14; return pi * (r * r); } else { return pi * (r * r); } } // 測試: if (area_of_circle(2) === 12.56 && area_of_circle(2, 3.1416) === 12.5664) { console.log('測試通過'); } else { console.log('測試失敗'); };

變數的作用域:如果兩個不同的函式各自申明瞭同一個變數,那麼該變數只在各自的函式體內起作用。
              換句話說,不同函式內部的同名變數互相獨立,互不影響:

    案例1:    
        'use strict';
        function foo() {
            var x = 1;
            x = x + 1;
        }
        
        x=x+1 //函式體外不能呼叫內部變數

        function bar() {
            var x = 'A';
            x = x + 'B';
        }        
                
        
    案例2:
       由於JavaScript的函式可以巢狀,此時,內部函式可以訪問外部函式定義的變數,反過來則不行:

        'use strict';
        function foo() {
            var x = 1;
            function bar() {
                var y = x + 1; // bar可以訪問foo的變數x!
            }
            var z = y + 1; // ReferenceError! foo不可以訪問bar的變數y!
        }    
        
       結論:JavaScript的函式在查詢變數時從自身函式定義開始,從“內”向“外”查詢。
             如果內部函式定義了與外部函式重名的變數,則內部函式的變數將“遮蔽”外部函式的變數。        
        
        
    案例3: 變數提升
    
        JavaScript的函式定義有個特點,它會先掃描整個函式體的語句,把所有申明的變數“提升”到函式頂部:
        'use strict';
        function foo() {
            var x = 'Hello, ' + y;
            console.log(x);
            var y = 'Bob';
        }

        foo();
        雖然是strict模式,但語句var x = 'Hello, ' + y;並不報錯,原因是變數y在稍後申明瞭。但是console.log顯示Hello, undefined,
        說明變數y的值為undefined。這正是因為JavaScript引擎自動提升了變數y的宣告,但不會提升變數y的賦值。    
        
        function foo() {
            var
                x = 1, // x初始化為1
                y = x + 1, // y初始化為2
                z, i; // z和i為undefined
            // 其他語句:
            for (i=0; i<100; i++) {
                ...
            }
        }
        
    全域性作用域:
        不在任何函式內定義的變數就具有全域性作用域。實際上,JavaScript預設有一個全域性物件window,全域性作用域的變數實際上被繫結到window的一個屬性:
        'use strict';
        var course = 'Learn JavaScript';
        alert(course); // 'Learn JavaScript'
        alert(window.course); // 'Learn JavaScript'
        因此,直接訪問全域性變數course和訪問window.course是完全一樣的    
        
    了,由於函式定義有兩種方式,以變數方式var foo = function () {}定義的函式實際上也是一個全域性變數,因此,頂層函式的定義也被視為一個全域性變數,並繫結到window物件:

    
    'use strict';
    function foo() {
        alert('foo');
    }
    foo(); // 直接呼叫foo()
    window.foo(); // 通過window.foo()呼叫
    進一步大膽地猜測,我們每次直接呼叫的alert()函式其實也是window的一個變數        
    說明:說明JavaScript實際上只有一個全域性作用域。任何變數(函式也視為變數),如果沒有在當前函式作用域中找到,
          就會繼續往上查詢,最後如果在全域性作用域中也沒有找到,則報ReferenceError錯誤。
          
    名字空間:
    全域性變數會繫結到window上,不同的JavaScript檔案如果使用了相同的全域性變數,或者定義了相同名字的頂層函式,都會造成命名衝突,並且很難被發現。
    減少衝突的一個方法是把自己的所有變數和函式全部繫結到一個全域性變數中。

    案例:把自己的程式碼全部放入唯一的名字空間MYAPP中,會大大減少全域性變數衝突的可能。【許多著名的JavaScript庫都是這麼幹的:jQuery,YUI,underscore等等。】


        // 唯一的全域性變數MYAPP:
        var MYAPP = {};

        // 其他變數:
        MYAPP.name = 'myapp';
        MYAPP.version = 1.0;

        // 其他函式:
        MYAPP.foo = function () {
            return 'foo';
        };    
        
    案例:區域性作用域
        由於JavaScript的變數作用域實際上是函式內部,我們在for迴圈等語句塊中是無法定義具有區域性作用域的變數的:

        'use strict';

        function foo() {
            for (var i=0; i<100; i++) {
                //
            }
            i += 100; // 仍然可以引用變數i
        }
        為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變數:
        
    案例:
        'use strict';
        function foo() {
            var sum = 0;
            for (let i=0; i<100; i++) {
                sum += i;
            }
            // SyntaxError:
            i += 1;
        }

    常量:
        由於var和let申明的是變數,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變數來表示“這是一個常量,不要修改它的值”:
            var PI = 3.14;        
        ES6標準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:
            
            'use strict';
            const PI = 3.14;
            PI = 3; // 某些瀏覽器不報錯,但是無效果!
            PI; // 3.14
            
       對一個物件進行解構賦值時,同樣可以直接對巢狀的物件屬性進行賦值,只要保證對應的層次是一致的:
    
    案例:
        var person = {
            name: '小明',
            age: 20,
            gender: 'male',
            passport: 'G-12345678',
            school: 'No.4 middle school',
            address: {
                city: 'Beijing',
                street: 'No.1 Road',
                zipcode: '100001'
            }
        };
        var {name, address: {city, zip}} = person;
        name; // '小明'
        city; // 'Beijing'
        zip; // undefined, 因為屬性名是zipcode而不是zip
        // 注意: address不是變數,而是為了讓city和zip獲得巢狀的address物件的屬性:
        address; // Uncaught ReferenceError: address is not defined
        
    使用解構賦值對物件屬性進行賦值時,如果對應的屬性不存在,變數將被賦值為undefined,
    這和引用一個不存在的屬性獲得undefined是一致的。如果要使用的變數名和屬性名不一致,可以用下面的語法獲取:        
方法:
   在一個物件中繫結函式,稱為這個物件的方法。
   案例:
        var xiaoming = {
            name: '小明',
            birth: 1990,
            age: function () {
                var y = new Date().getFullYear();
                return y - this.birth;
            }
        };

        xiaoming.age; // function xiaoming.age()
        xiaoming.age(); // 今年呼叫是25,明年呼叫就變成26了
        
    重點概念:this
    在一個方法內部,this是一個特殊變數,它始終指向當前物件,也就是xiaoming這個變數。
                    所以,this.birth可以拿到xiaoming的birth屬性。

    讓我們拆開寫:    
    案例:  
       function getAge() {
            var y = new Date().getFullYear();
            return y - this.birth;
        }

        var xiaoming = {
            name: '小明',
            birth: 1990,
            age: getAge
        };

        xiaoming.age(); // 25, 正常結果
        getAge(); // NaN
    
    ECMA決定,在strict模式下讓函式的this指向undefined,因此,在strict模式下,你會得到一個錯誤:
    
        'use strict';
        var xiaoming = {
            name: '小明',
            birth: 1990,
            age: function () {
                var y = new Date().getFullYear();
                return y - this.birth;
            }
        };

        var fn = xiaoming.age;
        fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined    
        
    我們用一個that變數首先捕獲this:
    
        'use strict';
        var xiaoming = {
            name: '小明',
            birth: 1990,
            age: function () {
                var that = this; // 在方法內部一開始就捕獲this
                function getAgeFromBirth() {
                    var y = new Date().getFullYear();
                    return y - that.birth; // 用that而不是this
                }
                return getAgeFromBirth();
            }
        };

        xiaoming.age(); // 25
        用var that = this;,你就可以放心地在方法內部定義其他函式,而不是把所有語句都堆到一個方法中。    
    
案例:apply
    雖然在一個獨立的函式呼叫中,根據是否是strict模式,this指向undefined或window,不過,我們還是可以控制this的指向的!

    要指定函式的this指向哪個物件,可以用函式本身的apply方法,它接收兩個引數,第一個引數就是需要繫結的this變數,第二個引數是Array,表示函式本身的引數。

    用apply修復getAge()呼叫:

    function getAge() {
        var y = new Date().getFullYear();
        return y - this.birth;
    }

    var xiaoming = {
        name: '小明',
        birth: 1990,
        age: getAge
    };

    xiaoming.age(); // 25
    getAge.apply(xiaoming, []); // 25, this指向xiaoming, 引數為空
    另一個與apply()類似的方法是call(),唯一區別是:

    apply()把引數打包成Array再傳入;

    call()把引數按順序傳入。

    比如呼叫Math.max(3, 5, 4),分別用apply()和call()實現如下:

    Math.max.apply(null, [3, 5, 4]); // 5
    Math.max.call(null, 3, 5, 4); // 5    
    
        
    裝飾器:
    利用apply(),我們還可以動態改變函式的行為。
    JavaScript的所有物件都是動態的,即使內建的函式,我們也可以重新指向新的函式