1. 程式人生 > >JS引用類型 --- 函數(含this指向面試題)

JS引用類型 --- 函數(含this指向面試題)

body 輸出 pos 返回 {} 回調 2個 重復 ....

一、函數基礎

1. 函數:可重用的代碼塊

2. 函數可以作為參數、返回值使用

3. 函數實際上是 Function 的實例,其數據類型是Object

4. 但typeof Function 值為 function; FunctionObjectArray 等都是構造函數

5. 函數沒有重載,函數名重復後會被覆蓋

  • 重載:其他語言,可以為一個函數編寫兩個定義,只要這兩個定義的簽名(接受參數的類型和數量)不同即可

  • 深入理解函數 為什麽沒有重載:函數是引用類型的,函數名可理解為函數的指針,函數名重名,即為函數重新定義指針的指向。

6. 模擬實現 JS函數的重載

  • 根據arguments對象的length值進行判斷,執行不同的代碼 參考
<script>
    function overLoading() {   
        // 根據arguments.length,對不同的值進行不同的操作
          
        switch (arguments.length) {    
            case 0:
                /*操作1的代碼寫在這裏*/       
                break;    
            case 1:
                /*操作2的代碼寫在這裏*/       
                break;    
            case 2:
                /*操作3的代碼寫在這裏*/          
                //後面還有很多的case......
        }
    }
</script>

二、函數 創建 的3種方式

1. 函數聲明 方式 function

  • 預解析是將整個函數體提前提前
<script>
    fn();   // 可以訪問到

    function fn() {
        
    }

    console.log(fn); //輸出整個函數體, 即:function fn() {}
</script>

2. 函數表達式 方式 var fn =

  • 預解析是將var fn; 提前,並沒有將函數體提前
<script>
    fn();   // 不能訪問到,報錯
    var fn = function () {
        
    }
console.log(fn); //輸出整個函數體, 即:function fn() {} </script>

3. 構造函數 方式 new Function

  • new Function(‘新函數參數‘,‘新函數參數‘,....,‘函數體‘)

  • 註意:
    • 參數都是字符串
    • 最後一個字符串參數是 新函數體;前面所有參數都是 新函數的 參數;
<script>
    var fn = new Function(‘a‘, ‘b‘, ‘c‘, ‘return a+b+c‘);

    console.log(fn(1, 2, 3));
</script>
  • 作用:將字符串,轉為可執行的代碼
    • 模板引擎原理 new Function()
    • 能夠將json 對象形式的字符串 轉為 json 對象,代碼如下:
<script>
    var str = ‘{name: "zhangxin", age: 18}‘;
    var aa = new Function(‘return‘ +  str);

    console.log(aa());
</script>

4. 函數在哪個作用域內創建的,就會在哪個作用域內執行;與函數調用的環境無關

5. 函數一創建,就有了 prototype 屬性,指向原型對象(Math函數除外)

6. 匿名函數 的表現形式

function () {};
var f = function () {};

三、函數的 參數

1. 形參

  • 形參: 函數定義的參數

  • 調用函數時,實參個數 不等於 形參個數時,不會報錯;因為,在函數體內有arguments來存儲這些實參

2. 實參

  • 實參:由arguments 統一管理,arguments 是一個偽數組,只有.length屬性

3. 參數可看做 參數函數內部的局部變量;且參數的 聲明和賦值 被提升到函數最頂端

  • 傳實參 就是 給局部變量初始化

  • 不傳實參 則參數值為undefined

4. 關於 函數形參 與 函數內變量 重名問題分析?

  • 參數 可以看做是函數內部的局部變量

    • 如果 形參 與 函數內 的變量名沖突,則後者會覆蓋前者

    • 如果 形參 與 函數內 的函數名(函數聲明方式創建的函數)沖突,則形參變為函數

<script>
    function fn(m) {
        console.log(m);
        m = 2;
        console.log(m);
    }

    fn(10);
</script>
// 10 2
<script>
    function fn(m) {
        console.log(m);
        function m() {};
        console.log(m);
    }

    fn(10);
</script>
// 函數體 函數體
<script>
    function fn(m) {
        console.log(m);
        m = function () {};
        console.log(m);
    }

    fn(10);
</script>
// 10 函數體

四、函數的 返回值

  • 函數的返回值:可有可無,不會報錯

  • 沒有返回值、沒有明確的返回值,輸出函數的調用,結果為 undefined

<script>
    function fn1() {
        console.log("aaa");
        return;
    }
    console.log(fn1());  //aaa  undefined
</script>
  • 函數可以作為返回值:被返回

五、函數的 直接屬性和方法

1. 屬性:length,獲取形參的個數

2. 屬性:prototype,指向原型對象,保存函數的所有實例方法

3. 屬性:name, 獲取當前的函數名(IE不支持)

4. 屬性:caller,(被廢棄)可以獲得當前函數在哪個函數內部被調用,全局調用,結果為null

5. 方法借用:apply()(非繼承來的方法)

6. 方法借用:call()(非繼承來的方法)

  • 方法借用 並自動調用; 改變this指向

  • apply()call() 借用方法後立即被自動調用返回值是:借用方法的返回值

  • 借用方法:apply()call()
    • 第1個參數:this指向的對象,可不傳;(不傳、傳null、傳undefined時,this都指向window);

    • 第2個參數:借用方法的參數

    • 返回值是:借用方法的返回值

  • apply()call() 區別 在於方法內的第二個參數
    • 方法.apply() : 第2個參數:數組,數組內每一項是借用方法的參數

    • 方法.call() :第2個、第3個等參數:是單獨的項,是借用方法的參數

  • apply() 借用 Math.max 求數組中的最大值

Math.max.apply(null, [14, 3, 77])
<script>
    var arr = [1, 2, 3, 4];

    var result1 = arr.toString(); // 1,2,3,4 使用了數組實例自己的toString()方法
    var result2 = Object.prototype.toString.call(arr); // 借用了對象的toString()方法
    var result3 = Object.prototype.toString.apply([arr]);
    console.log(result1);  // 1,2,3,4
    console.log(result2);  // [object Array]
    console.log(result3);  // [object Array]
</script>
<script>
    function fn () {
        console.log(this);
    }

    fn.call();              // window
    fn.call(null);          // window
    fn.call(undefined);     // window
</script>

5. 方法借用 bind() (ES5定義的)

  • 方法借用 需手動調用; 改變this指向

  • 返回值:借用的方法(需要手動調用,這就是和 apply call 的區別)

  • 用法:方法.bind(this指向的對象); (不傳、傳null、傳undefined時, this指向window)

<script>
    var bar = function(a, b) {
        console.log(this.x);
        console.log(a);
        console.log(b);
    }
    var foo = {
        x: 3
    }

    bar.bind(foo); // 無輸出,因為 借用了方法,還沒被調用
    bar.bind(foo)(1, 2);    // 3 1 2
</script>
<script>
    var length = 20;

    var fn = function () {
        console.log(this.length);
    };

    var obj = {
        length: 10
    };

    fn.bind()();       // 20
    fn.bind(obj)();    // 10
</script>
<script>
    const length = 20;

    var fn = function () {
        console.log(this.length);
    };

    var obj = {
        length: 10
    };

    fn.bind()();       // 0, const 聲明的變量在塊級作用域中,不在window中,window中length屬性值為0
</script>

六、函數的 內部屬性(成員)

1. arguments對象;保存實參;偽數組

  • 每一個函數體內,都有一個 arguments;只能在函數體內訪問

  • 偽數組,只有.length屬性;轉為真數組: [].slice.apply(arguments)

  • 是一個對象,屬性:arguments.length; 取值:arguments[0]

2. arguments.callee 指向當前函數:解耦實現函數的遞歸

  • arguments.callee 指向一個正在執行的函數的指針
<script>
    function aaa() {
        console.log(arguments.callee);  // 整個函數體
        return arguments.callee();  // 實現函數的遞歸(解耦)
    }

    aaa();
</script>

3. this對象:

this具有動態性,【誰調用函數,函數內部的this指向誰】 ; 【執行函數時,要回到創建函數的環境下執行】
(1) 函數調用模式: this指向 window
特殊:setTimeout / setInterval 等回調函數中, this 的指向:window,因為回調函數是window調用的
(2) 方法調用模式: this指向 調用方法的對象
特殊:DOM 綁定事件,事件回調函數中 this 指向 DOM
  • 涉及arr[0] = 函數,實際上函數作為 arr對象中的方法被調用

  • 實際上:this指向數組對象,解釋:【數組是對象,arr[0] ===> 數組對象.方法】

(3) 構造函數調用模式: this指向 新創建的對象
(4) 方法借用模式: this指向,參數對象 / 不傳參或者穿 null undefined 的話this 指向 window
this面試題
  • 第1題,輸出什麽
<script>
    var length = 10;
    function fn() {
        console.log(this.length);
    }
    var obj = {
        length: 3,
        method: function (fn) {
            (false || arguments[0])();
        }
    };

    obj.method(fn, 123, 14, 12, 4);
</script>
  • 第2題,輸出什麽
<script>
    var arr = [];
    arr[0] = function () {
        console.log(this.length);
    }

    arr[1] = 123;
    arr[2] = 22;
    arr[0]();
</script>
  • 第3題,輸出什麽
<script>
    var age = 38;
    var obj = {
        age: 18,
        getAge: function () {
            function fn() {
                console.log(this.age);
            }

            fn();
        }
    };

    obj.getAge();
</script>
  • 第4題,輸出什麽
<script>
    var obj ={
        say: function () {
            console.log(this);
        }
    };

    obj.say();
    (obj.say)();
    (obj.say = obj.say)();
    (false || obj.say)();
</script>
  • 第5題,改變this指向,讓其指向 obj
<script>
    function fn() {
        console.log(this);
    }

    var obj = {};

    // 方法1:
    obj.fn = fn;
    obj.fn();

    // 方法2:
    fn.apply(obj);

    // 方法3:
    fn.call(obj);

    // 方法4:
    fn.bind(obj)();
</script>
  • 第6題,判斷this指向
<script>
    var obj = {
        fn: function () {
            console.log(this);
        }
    };

    setTimeout(obj.fn, 1000);
</script>
  • 第7題,使用setTimeout調用函數,並使 this 指向 obj
<script>
    var obj = {
        fn: function () {
            console.log(this);
        }
    };

    // 方法1:
    setTimeout(obj.fn.bind(obj), 1000);
    setTimeout(obj.fn.apply(obj), 1000);
    setTimeout(obj.fn.call(obj), 1000);

    // 方法2:
    setTimeout(function () {
        obj.fn();
    }, 1000);
</script>
  • 第8題,輸出什麽 【重要】
<script>
    var length = 10;
    function fn() {
        console.log(this.length);
    }
    var obj = {
        length: 5,
        method: function (f) {
            f();
            arguments[0]();
            arguments[0].call(this);
            arguments[0].call();
        }
    };

    var obj2 = {
        method: (function () {
            console.log(this);
        })()
    };

    obj.method(fn);
</script>
  • 第9題,輸出什麽 【重要】
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
                return this.name;

        }
    };

    console.log(obj.getName());
    console.log((obj.getName)());
    console.log((obj.getName = obj.getName)()); // 先賦值,再執行賦值後的結果;因為賦值表達式的值是本身,所以this不能得到維持
</script>
  • 第10題,修改obj中的代碼,使輸出結果為 windowName
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
                return this.name;

        }
    };

    console.log(obj.getName());
</script>
  • 第11題,輸出什麽
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
            return function() {
                return this.name;
            }
        }
    };

    console.log(obj.getName()());
</script>
  • 第12題:修改obj中代碼,是輸出結果為 objName
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
            return function() {
                return this.name;
            }
        }
    };

    console.log(obj.getName()());
</script>
this面試題答案
第1題: 10
第2題: 3
第3題: 38
第4題: obj  obj  window  window
第6題: window
第8題: window  10  1  5  10
第9題: objName  objName  windowName

第10題:
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
            
            return (function () {
                return this.name
            })()
        }
    };

    console.log(obj.getName()); // objName
</script>

第11題:windowName

第12題:
<script>
    var name = ‘windowName‘;

    var obj = {
        name: ‘objName‘,
        getName: function() {
            var that = this;
            return function() {
                return that.name;
            }
        }
    }

    console.log(obj.getName()());
</script>

七、JS中 將字符串轉為 可執行的代碼 eval()Function()

eval( ) 將字符串轉為可執行的代碼、和Function相同功能,區別在哪?

  • Function 和 eval 的區別和聯系:

    • 相同點:都能夠將字符串轉為js 代碼來執行

    • 不同點:eval效率相對高、但全局汙染比較嚴重,Function 反之

  • 權衡利弊

    • 考慮效率使用eval

    • 考慮安全性用Function

    • eval 會造成 全局汙染

<script>
    // 將JSON字符串轉化為js對象
    var jsonStr = ‘var obj = {"name": "小明", "age": 19}‘;
    
    eval(jsonStr);

    console.log(obj);
</script>

JS引用類型 --- 函數(含this指向面試題)