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() {}; 提前
posted @ 2017-12-23 21:05  执着的程序员~  阅读(583)  评论(0编辑  收藏  举报