JS 中的 函数(详解,含 this 指向面试题)
一、函数 概述
-
特点:
-
函数:可重用的代码块
-
函数:可以作为参数、返回值使用
-
函数 也是对象
-
-
函数的类型:
typeof Function
、typeof Object
、typeof 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
- 3 种情况下
-
返回值:借用的方法(需要手动调用)【与
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
- 特殊:
setTimeout
、setInterval
等回调函数中的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() {}; 提前