• JS 中的 函数(详解,含 this 指向面试题)


    一、函数 概述

    • 特点:

      • 函数:可重用的代码块

      • 函数:可以作为参数、返回值使用

      • 函数 也是对象

    • 函数的类型:

      • typeof Functiontypeof Objecttypeof Array:值均为 "function"
    • 函数在哪个作用域内创建的,就会在哪个作用域内执行;与函数调用的环境无关

    • 函数一创建,就有了 prototype 属性,指向原型对象(Math函数除外)

    • 匿名函数 的表现形式:

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

    二、模拟 函数的重载

    • 准确的说,JS 中是没有函数的重载的: 因为 JS 中同名函数会被覆盖

    • 函数重载的理解: 可以为一个函数编写两个定义,只要这两个定义的签名(接受参数的类型和数量)不同即可;同名函数不会被覆盖

    • 模拟实现 JS函数的重载

      • 根据arguments对象的length值进行判断,执行不同的代码 参考
    function overLoading() {   
        // 根据arguments.length,对不同的值进行不同的操作
          
        switch (arguments.length) {    
            case 0:
                /*操作1的代码写在这里*/       
                break;    
            case 1:
                /*操作2的代码写在这里*/       
                break;    
            case 2:
                /*操作3的代码写在这里*/          
                //后面还有很多的case......
        }
    }
    

    三、函数 创建的创建(3种方式)

    1. 方式一:function 关键字声明

    • 预解析:整个函数体提前提前
    fn();   // 可以访问到
    
    function fn() {
        
    }
    
    console.log(fn); //输出整个函数体, 即:function fn() {}
    

    2. 方式二:函数表达式 var fn =

    • 预解析:var fn; 提前,并没有将函数体提前
    fn();   // 不能访问到,报错
    var fn = function () {
        
    }
    
    console.log(fn); //输出整个函数体, 即:function fn() {}
    

    3. 方式三:构造函数 new Function

    • 语法: new Function('新函数参数','新函数参数',....,'**函数体**')

      • 参数都是字符串

      • 最后一个字符串参数是 新函数体;前面所有参数都是 新函数的 参数;

    var fn = new Function('a', 'b', 'c', 'return a+b+c');
    
    console.log(fn(1, 2, 3));
    
    • 应用: 将字符串,转为可执行的代码
      • 模板引擎原理:new Function()

      • 能够将json 对象形式的字符串 转为 json 对象,代码如下:

    var str = '{name: "zhangxin", age: 18}';
    var aa = new Function('return' +  str);
    
    console.log(aa());
    

    四、函数的 参数

    1. 形参:定义函数时确定的参数

    • 形参相当于: 在函数最顶部声明了一个局部变量

    2. 实参:由 函数内部的 arguments 对象 统一管理

    • 实参:由arguments 统一管理,arguments 是一个伪数组,只有.length属性

    3. 定义形参:相当于在函数最顶部声明了一个局部变量;传实参:相当于给局部变量初始化

    • 参数特殊理解:

      • 传实参:给局部变量初始化

      • 不传实参:则参数值为undefined

    • 关于 函数形参函数内变量 重名问题分析?

      • 如果 形参函数内的变量名 冲突,则后者会覆盖前者

      • 如果 形参函数内的函数名(函数声明方式 创建的函数)冲突,则变量提升,形参变为函数

    function fn(m) {
        console.log(m);
        m = 2;
        console.log(m);
    }
    
    fn(10);
    // 10 2
    
    function fn(m) {
        console.log(m);
        function m() {};
        console.log(m);
    }
    
    fn(10);
        
    // 函数体 函数体
    
    function fn(m) {
        console.log(m);
        m = function () {};
        console.log(m);
    }
    
    fn(10);
        
    // 10 函数体
    

    五、函数的 返回值

    1. 函数的返回值:可有可无,不会报错

    • 有返回值: 输出函数的调用,就是输出返回值

    • 没有返回值: 输出函数的调用,结果为 undefined

    function fn1() {
        console.log("aaa");
        return;
    }
    
    console.log(fn1());  //aaa  undefined
    

    2. 函数可以作为返回值 被返回,即:闭包模型

    六、函数的 调用:函数无论在哪被调用,都要回到定义函数的环境中去执行

    • 非常重要: 函数无论在哪被调用,都要回到定义函数的环境中去执行

    七、函数的 直接属性、方法

    1. 属性:函数.length,获取 形参 的个数

    2. 属性:函数.prototype,指向原型对象

    • 原型对象: 保存函数实例的所有方法
    function fn() {}
    
    fn.prototype  ---> 指向函数的原型对象
    

    3. 属性:函数.name,获取当前的函数名(IE不支持)

    • 如果将一个 匿名函数 赋值给一个变量,ES5 的name属性,会返回空字符串

    • 匿名函数

      • function () {}

      • var fn = function () {}

    • ES6 中:

    4. 属性:函数.caller,获取 该函数在哪个函数内被调用

    • 返回值:

      • 函数内调用:返回函数名

      • 全局调用:返回 null

    • 这个属性已被遗弃: 最好不要再用

    5. 方法借用,改变 this 指向:apply()call()

    • apply()语法:

      • 参数1: this指向的对象

        • 当借用的方法不需要传参数时,apply() 的第一个参数可不传

        • 3 种情况下 this 指向 window:第一个参数 不传、传null、传 undefined

      • 参数2: 借用方法的参数 【与 call() 的区别】

        • 类型是数组

        • 借用方法的参数,都放在这个数组中

      • 返回值: 借用方法的返回值 【与 bind() 的区别】

    • call()语法:

      • 参数1: this指向的对象

        • 当借用的方法不需要传参数时,apply() 的第一个参数可不传

        • 3 种情况下 this 指向 window:第一个参数 不传、传null、传 undefined

      • 参数2、3、4: 借用方法的参数 【与 apply() 的区别】

        • 借用方法的参数单独写,作为 call() 的第2、3、4 ... 个参数
      • 返回值: 借用方法的返回值 【与 bind() 的区别】

    • 特点: 借用方法后,自动调用; apply()call() 的返回值,为调用方法后的返回值

    • 用途: 自身没有的方法,借用一下

      • 求数组中的最大值
      Math.max.apply(null, [14, 3, 77])
      
      • 借用对象中的 toString() 方法: 判断复杂数据类型的 类型
      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]
      
    • 借用方法中的 this 指向:

    function fn () {
        console.log(this);
    }
    
    fn.call();              // window
    fn.call(null);          // window
    fn.call(undefined);     // window
    

    6. 方法借用,改变 this 指向:bind()

    • 语法: 被借用的方法.bind(this指向的对象)(借用方法的参数)

      • 参数1: this指向的对象

        • 3 种情况下 this 指向 window:第一个参数 不传、传null、传 undefined
      • 返回值:借用的方法(需要手动调用)【与 apply()call() 的区别】

    // 重点研究
    
    const length = 20;
    
    var fn = function () {
        console.log(this.length);
    };
    
    var obj = {
        length: 10
    };
    
    fn.bind()();       // 0, const 声明的变量在块级作用域中,不在window中,window中length属性值为0
    
    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
    
    var length = 20;
    
    var fn = function () {
        console.log(this.length);
    };
    
    var obj = {
        length: 10
    };
    
    fn.bind()();       // 20
    fn.bind(obj)();    // 10
    

    八、函数的 内部属性: 只有在函数内部才能被 访问到

    1. arguments对象:保存实参的伪数组;arguments.callee实现 解耦式 递归

    • 每一个函数体内,都有一个 arguments 伪数组对象,只能在函数体内访问

    • 转成 真数组:

      • [].slice.apply(arguments)

      • [...arguments]

    • arguments 属性:

      • arguments.length 属性:获取传入实参的个数

      • arguments.callee:指向当前函数:解耦实现函数的递归

      • 取值: arguments[0]

    • arguments.callee实现 解耦式 递归

    function aaa() {
        console.log(arguments.callee);  // 整个函数体
        return arguments.callee();      // 实现函数的递归(解耦)
    }
    
    aaa();
    

    2. this 对象:指向调用这个函数的对象 【重要】

    • this 指向关键之处: 只看谁调用的函数,谁调用的函数,函数内部的 this 就指向谁

    • 函数调用 关键之处: 不管函数在哪里调用的,执行被调用的函数,都要回到创建函数的环境去执行

    3. this 指向模式一:函数调用模式 ---> this指向 window

    • 常见: 全局调用函数
    function aa () {
        console.log(this);
    }
    
    aa();       // Window
    
    • 特殊: setTimeoutsetInterval 等回调函数中的 this,指向 window
    setTimeout(() => {
        console.log(this);  // Window
    }, 1000);
    

    4. this 指向模式二:方法调用模式 ---> this指向 调用方法的对象

    • 特殊: DOM 绑定事件,事件回调函数中 this 指向 DOM

    • 特殊,非常重要: 涉及 arr[0] = 函数,实际上函数作为 arr对象中的方法被调用

      • 实际上:this指向数组对象,解释:【数组是对象,arr[0] ===> 数组对象.方法】

    5. this 指向模式三:构造函数调用模式 ---> this指向 新创建的对象

    6. this 指向模式四:方法借用模式 ---> this指向,参数对象

    7. this 指向 面试题

    • 第1题,输出什么
    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);
    
    • 第2题,输出什么
    var arr = [];
    arr[0] = function () {
        console.log(this.length);
    }
    
    arr[1] = 123;
    arr[2] = 22;
    arr[0]();
    
    • 第3题,输出什么
    var age = 38;
    var obj = {
        age: 18,
        getAge: function () {
            function fn() {
                console.log(this.age);
            }
    
            fn();
        }
    };
    
    obj.getAge();
    
    • 第4题,输出什么
    var obj ={
        say: function () {
            console.log(this);
        }
    };
    
    obj.say();
    (obj.say)();
    (obj.say = obj.say)();
    (false || obj.say)();
    
    • 第5题,改变this指向,让其指向 obj
    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)();
    
    • 第6题,判断this指向
     var obj = {
        fn: function () {
            console.log(this);
        }
    };
    
    setTimeout(obj.fn, 1000);
    
    • 第7题,使用setTimeout调用函数,并使 this 指向 obj
    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);
    
    • 第8题,输出什么 【重要】
    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);
    
    • 第9题,输出什么 【重要】
    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不能得到维持
    
    • 第10题,修改obj中的代码,使输出结果为 windowName
    var name = 'windowName';
    
    var obj = {
        name: 'objName',
        getName: function() {
                return this.name;
    
        }
    };
    
    console.log(obj.getName());
    
    • 第11题,输出什么
    var name = 'windowName';
    
    var obj = {
        name: 'objName',
        getName: function() {
            return function() {
                return this.name;
            }
        }
    };
    
    console.log(obj.getName()());
    
    • 第12题:修改obj中代码,是输出结果为 objName
    var name = 'windowName';
    
    var obj = {
        name: 'objName',
        getName: function() {
            return function() {
                return this.name;
            }
        }
    };
    
    console.log(obj.getName()());
    

    8. 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 会造成 全局污染

    // 将JSON字符串转化为js对象
    var jsonStr = 'var obj = {"name": "小明", "age": 19}';
        
    eval(jsonStr);
    
    console.log(obj);
    
    // 特殊:
    
    (function (a) {
        console.log(a);         // 100
        var a = function () {}
        console.log(a);         // 函数
    })(100);
    
    // 解释1:函数内的形参,相当于函数内部声明的局部变量(自定义执行函数,传参:相当于 函数内部声明形参这个局部函数,并且 在预解析之后 马上赋值)
    // 解释2:预解析 将 var a; 提前
    
    // 特殊情况
    
    (function(a) {
        console.log(a);     // 函数
        function a() {}     // 函数
        console.log(a);
    })(100);
    
    // 解释:函数形参 + 预解析 将 function a() {}; 提前
    
  • 相关阅读:
    ajax提交转码解码
    关于idea开发工具常用的快捷键
    oracle 查询某个时间段数据
    hibernate : object references an unsaved transient instance 问题
    log4j日志
    JS关键字 import
    代码正常,junit却报错原因及解决方法
    hdu 5868 Polya计数
    hdu 5893 (树链剖分+合并)
    hdu 5895 广义Fibonacci数列
  • 原文地址:https://www.cnblogs.com/zxvictory/p/8094424.html
Copyright © 2020-2023  润新知