JavaScript的函数
文章目录
- 函数类型
- arguments对象
- 构造函数
- 作用域
- 闭包
函数,其实也是一种对象,每一个函数都是Function类型的实例,可以定义不同类型的属性和方法。
函数类型
函数分为函数声明、函数表达式和构造函数
- 函数声明,是直接使用function关键字接一个函数名,
// 函数声明式
function sum(param1, param2) {
return param1 + param2;
}
- 函数表达式,类似于一个普通变量的初始化,
let sum = function(param1, param2) {
return param1 + param2;
}
- Function构造函数,通过new操作符,调用Function构造函数。
const add = new Function('a','b','return a + b')
console.log(add(1,2));
Function构造函数用得比较少,因为Function构造函数的每一次执行,都会解析函数主体,创建一个新的函数对象,如果是一个频繁调用的函数,这样效率非常低。
另一个原因是Function创建的函数一直作为顶级函数来执行,如果我们在一个函数A中调用Function构造函数,其中的函数体不能访问函数A的局部变量,如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var y = 'global'; // 全局环境定义的y值
function constructFunction() {
var y = 'local'; // 局部环境定义的y值
return new Function('return y'); // 无法获取局部环境定义的值
}
console.log(constructFunction()()); // 输出'global'
</script>
</body>
</html>
arguments对象
arguments对象是所有函数都具有的一个内置局部变量,表示是函数实际接收的参数,arguments是一个类数组的结构。arguments对象只能在函数体内使用,如下代码:
(function (a, b) {
console.log(arguments); // [Arguments] { '0': 2 }
})(2)
arguments是表示实际参数的,所以就由实际参数决定,如上代码中,定义的形参有两个,实参却只有一个,那么arguments对象的值也只有一个。
构造函数
构造函数和和普通函数的区别:
- 构造函数的函数名的首字母是大写;
- 构造函数体的this关键字,表示是实例对象,构造函数一般不会返回任何值,默认是返回this;比如:
function Do(){
this.name = 'play'
}
const aa = new Do()
console.log(aa.name); // play
- 构造函数调用的时候,需要与new操作符配合使用
作用域
一个变量的定义和调用都是在一个范围内,这个范围就是作用域。
作用域分为全局作用域、函数作用域和块级作用域
全局作用域和块级作用域比较简单,这里仅仅总结一下函数作用域。
在函数内部使用var关键字声明变量的时候,函数中会存在变量提升的问题,如下代码:
(function(){
console.log(a); // undefined
var a =908;
})()
在变量a声明之前打印,输出结果为undefined。这段代码等同于:
(function(){
var a ;
console.log(a); // undefined
})()
这就是变量提升,将变量的声明提升到函数的顶部位置,但是变量的赋值并没有提升。
只有通过var关键字声明的变量才会出现提升的问题。
使用函数声明方式来定义一个函数,也会出现函数提升问题,通过函数表达式声明函数不会出现函数提升问题。
add(1, 2); // 3
function add(a, c) {
console.log(a + c)
}
闭包
闭包是一个拥有多个变量和绑定这些变量执行上下文环境的表达式,一般都是一个函数。
函数拥有外部变量的引用,在函数执行完成后,该变量没有被销毁,处于活跃状态;
当闭包作为一个函数返回的时候,它的执行上下文环境没有被销毁;
这样闭包就导致大量消耗内存,闭包使用越多,内存消耗就越大。
但是闭包也有一些有点:
- 结果缓存,比如有一个很耗时的函数对象,在每次调用都消耗很长时间。这时候我们可以将函数的处理结果在内存中缓存,这样在执行代码的时候,如果内存中存有结果,就直接返回,否则再执行一次函数并且更新缓存结果。比如以下代码:
var cachedBox = (function () {
// 缓存的容器
var cache = {};
return {
searchBox: function (id) {
// 如果在内存中,则直接返回
if (id in cache) {
return '查找的结果为:' + cache[id];
}
// 经过一段很耗时的dealFn()函数处理
var result = dealFn(id);
// 更新缓存的结果
cache[id] = result;
// 返回计算的结果
return '查找的结果为:' + result;
}
};
})();
// 处理很耗时的函数
function dealFn(id) {
console.log('这是一段很耗时的操作');
return id;
}
// 两次调用searchBox()函数
console.log(cachedBox.searchBox(1));
console.log(cachedBox.searchBox(1));
- 封装,在进行代码模块化封装的时候,需要把一些具有一定特征的属性封装在一起,只需要对外暴露对应的函数就好,不用担心内部逻辑的实现。比如数据结构中的栈:
let stack=(
function() {
let items = [];
return {
push:function (element) {
items.push(element)
},
pop:function () {
return items.pop();
},
size:function () {
return items.length;
}
}
}
)()
stack.push(90);
stack.push(100);
console.log(stack.size());// 2
总结闭包的优点:
- 保护函数内变量的安全,实现封装,防止变量流入其他环境发生命名冲突
- 在适当的时候,可以在内存中维护变量缓存,提升执行效率
闭包缺点:
- 内存消耗,一般情况下函数的活动会随着执行上下文环境一起被销毁,但是闭包引用的是外部的函数活动对象,在闭包函数执行完后,这个活动对象没有销毁,导致闭包比一般的函数更消耗内存;
- 内存泄漏,如果闭包中的作用域链中有DOM对象,那么这个DOM对象无法销毁,造成内存泄漏。