javascript中匿名函数和普通函数的区别
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript中匿名函数和普通函数的区别相关的知识,希望对你有一定的参考价值。
匿名函数在声明时不用带上函数名, 可以把匿名函数当作一个function类型的值来对待声明一个普通的函数 function func() ... 可以认为和var func = function() ... ;相同, 但是普通函数会自动把函数名"提到"作用域的最前面, 基本上普通函数和匿名函数就只有这一个区别
function xxx()
foo(); //foo被提到了作用域的最前面, 于是这里可以正常调用foo函数
bar(); //这里会报错bar是个undefined
function foo()
var bar = function();
参考技术A JS中的匿名函数的用法及优缺点
匿名函数可以有效的保证在页面上写入javascript,而不会造成全局变量的污染。
这在给一个不是很熟悉的页面增加Javascript时非常有效,也很优美。
一、什么是匿名函数?
在Javascript定义一个函数一般有如下三种方式:
函数关键字(function)语句:
function fnMethodName(x)alert(x);
函数字面量(Function Literals):
var fnMethodName = function(x)alert(x);
Function()构造函数:
var fnMethodName = new Function('x','alert(x);')
上面三种方法定义了同一个方法函数fnMethodName,
第1种就是最常用的方法,后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。
实际上,相当多的语言都有匿名函数。
二、函数字面量和Function()构造函数的区别
虽然函数字面量是一个匿名函数,但语法允许为其指定任意一个函数名,当写递归函数时可以调用它自己,使用Function()构造函数则不行。
var f = function fact(x) if (x < = 1) return 1; else return x*fact(x-1); ;
Function()构造函数允许运行时Javascript代码动态的创建和编译。在这个方式上它类似全局函数eval()。
Function()构造函数每次执行时都解析函数主体,并创建一个新的函数对象。所以当在一个循环或者频繁执行的函数中调用Function()构造函数的效率是非常低的。相反,函数字面量却不是每次遇到都重新编译的。
用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。
var y = "global";
function constructFunction() var y = "local"; return new Function("return y"); // 无法获取局部变量 alert(constructFunction()()); // 输出 “global”和函数关键字定义相比Function()构造器有自己的特点且要难以使用的多,
所以这项技术通常很少使用。
而函数字面量表达式和函数关键字定义非常接近。
考虑前面的区别,虽然有消息说字面量的匿名函数在OS X 10.4.3下的某些webkit的引擎下有bug,
但我们平常所说的匿名函数均指采用函数字面量形式的匿名函数。
三、匿名函数的代码模式
错误模式:其无法工作,浏览器会报语法错。
function() alert(1); ();
函数字面量:首先声明一个函数对象,然后执行它。
(function() alert(1); ) ( );
优先表达式:
( function() alert(2); ( ) );
void操作符:
void function() alert(3); ()这三种方式是等同的,hedger wang因为个人原因比较喜欢第3种,而在实际应用中我看到的和使用的都是第1种。
四、匿名函数的应用
《Javascript的一种模块模式》中的第一句话就是“全局变量是魔鬼”。
配合var关键字,匿名函数可以有效的保证在页面上写入Javascript,而不会造成全局变量的污染。
这在给一个不是很熟悉的页面增加Javascript时非常有效,也很优美。
实际上,YUI以及其相应的范例中大量使用匿名函数,其他的Javascript库中也不乏大量使用。
Javascript的函数式编程(functional programming)的基石。
具体请看《用函数式编程技术编写优美的 JavaScript》和《函数式JavaScript编程指南》。
JavaScript中的匿名函数立即执行函数和闭包
匿名函数是没有函数名的,不能单独使用;
立即执行函数是基于匿名函数实现的,也没有函数名,会在定义后立即执行;
闭包是有权访问另一个函数作用域中的变量的函数。匿名函数、立即执行函数只要满足 有权访问另一个函数作用域中的变量 这一个条件,就成了闭包。
匿名函数
匿名函数:没有函数名的函数
匿名函数不能单独定义与使用
function foo() {
console.log(‘普通函数‘);
}
// 去掉函数名 foo
function () { // SyntaxError: Function statements require a function name
console.log(‘匿名函数‘);
}
匿名函数的应用场景
- 用于函数表达式
- 作为返回值
- 用于定义对象方法
- 作为回调函数
- 用于立即执行函数
- 用于DOM元素注册事件
- 其他 ...
用于函数表达式
var sum = function (num1, num2) {
return num1 + num2;
};
console.log(sum(2, 3));
作为返回值
function sum(sum1, sum2) {
return function() {
return sum1 + sum2;
}
}
console.log(sum(2, 3)); // [Function]
console.log(sum(2, 3)()); // 5
用于定义对象方法
var obj = {
name: ‘uakora‘,
age: 27,
foo: function() {
console.log(this.name + ‘ ‘ + this.age);
}
};
obj.foo(); // uakora 27
作为回调函数
setTimeout(function() {
console.log(‘匿名函数作为回调函数‘);
}, 1000);
用于立即执行函数
(function() {
console.log(‘立即执行函数是基于匿名函数创建的‘);
}());
用于DOM元素注册事件
<input type="button" value="Click me!" id="btn">
<script>
var btn = document.querySelector("#btn");
//给按钮注册点击事件
sub.onclick = function(){
console.log(‘Click event‘);
}
</script>
立即执行函数
立即执行函数(IIFE,Immediately-Invoked Function Expression),是一种在定义后就会立即执行的函数,其实质是一种语法。
立即执行函数的形式
两种常用形式
常用形式一:将匿名函数包裹在一个括号运算符中,后面再跟一个括号
(function () {
console.log(‘立即执行函数‘);
})(); // !!!特别说明:若此立即执行函数后面立马又跟着一个立即执行函数,一定要在结尾加分号,否则后面的立即执行函数会报错!
// 上一个立即执行函数不加分号,下行代码将报错:TypeError: (intermediate value)(...) is not a function
(function (a, b, c) { // 形参
console.log(a + b + c); // 6
})(1, 2, 3); // 实参
常用形式二:匿名函数后面跟一个括号,再将整个包裹在一个括号运算符中
(function () {
console.log(‘立即执行函数‘);
}());
(function (a, b, c) { // 形参
console.log(a + b + c); // 6
}(1, 2, 3)); // 实参
其他形式
可以用 !、+、-、~ 运算符代替常用形式一中的第一个括号
!function (a, b, c) {
console.log(a + b + c); // 6
}(1, 2, 3);
+function (a, b, c) {
console.log(a + b + c); // 6
}(1, 2, 3);
-function (a, b, c) {
console.log(a + b + c); // 6
}(1, 2, 3);
~function (a, b, c) {
console.log(a + b + c); // 6
}(1, 2, 3);
立即执行函数的作用
立即执行函数最本质的作用是:创建一个独立的作用域。利用这一功能,可以
- 初始化数据和页面(只执行一次)
- 模块化开发中,定义私有变量,防止污染全局(独立作用域)
- 解决闭包中的状态保存问题;(常见的一个函数内部返回多个函数,调用这些函数,打印父函数内部变量的问题)
案例分析
var liList = document.getElementsByTagName(‘li‘);
for(var i = 0; i < liList.length; i++) {
liList[i].onclick = function() {
alert(i); // 结果:alert 出来的总是 6, 而不是0、1、2、3、4、5
}
}
期望结果:0、1、2、3、4、5
实际结果:alert 出来的总是 6
原因分析:没有形成独立作用域,每次点击执行函数都是去访问同一个 i 值(且是循环结束后的值 6)
解决方案:使用立即执行函数为每个 li 元素的点击事件函数创建独立作用域
var liList = document.getElementsByTagName(‘li‘);
for (var i = 0; i < 6; i++) {
(function (i) {
liList[i].onclick = function () {
alert(i); // 0、1、2、3、4、5
}
})(i);
}
闭包
??闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。闭包可以有函数名,也可以是匿名函数(没有函数名);
闭包形式一:直接以匿名函数的形式作为外部函数的返回值(普遍用法)
function outer() {
var n = 1;
return function() {
n++; // 访问 outer 函数作用域中的变量 n,形成闭包
console.log(n);
}
}
outer()();
闭包形式二:在外部函数内定义一个内部函数,并返回内部函数名
function outer() {
var n = 1;
function inner() {
n++; // 访问 outer 函数作用域中的变量 n,形成闭包
console.log(n);
}
return inner;
}
outer()();
闭包形式三:在外部函数内定义一个立即执行函数
function outer() {
var n = 1;
(function() {
n++; // 访问 outer 函数作用域中的变量 n,形成闭包
console.log(n);
})();
}
outer();
闭包的作用
作用一:可以直接访问函数作用域中的变量
作用二:可以将函数作用域中的变量如全局变量一样始终保存在内存中 (其实能否一直保存局部变量,跟闭包的执行形式有关)
说明:作用二的解析为个人理解
以下示例为证明观点:闭包能否一直保存局部变量,跟闭包的执行形式有关
闭包执行方式一:在返回的闭包后面紧跟一个 () ,立即执行
function outer() {
var n = 1;
function inner() {
n++;
console.log(n);
}
return inner;
}
outer()(); // 2
outer()(); // 2
闭包执行方式二:将返回的闭包赋值给一个全局变量,全局变量运行 () 操作,执行闭包
function outer() {
var n = 1;
function inner() {
n++;
console.log(n);
}
return inner;
}
var inner = outer();
inner(); // 2
inner(); // 3 (是在 2 的基础上加 1)
以上两种执行方式单独使用,只能说明 第二种执行方式,能使闭包一直保存局部变量,但还不能证明闭包的第一种执行方式不能一直保存局部变量,因为每次执行 outer()函数时,变量 n 都被重新初始化了。
请继续看以下代码,同时使用了闭包的两种执行方式:
function outer() {
var n = 1;
function inner() {
n++;
console.log(n);
}
return inner;
}
var inner = outer();
outer()(); // 2
outer()(); // 2 (Flag1)
inner(); // 2 (Flag2)
inner(); // 3
可以看到,Flag1 跟 Flag2 的值都是 2,假设 outer()()
这样的闭包执行方式会一直保存局部变量在内存的话,那 Flag2 处的值应该是 3。
显然假设不成立。
从而说明:闭包并非就一定可以一直保存局部变量在内存,还跟执行方式有关。
主动释放内存
function outer() {
var n = 1;
function inner() {
n++;
console.log(‘n = ‘, n);
}
return inner;
}
var inner = outer();
inner(); // 2
inner(); // 3
inner = null; // 解除对闭包的引用,以便释放内存
闭包的应用场景
- 保护函数局部变量的安全
- 在内存中维持一个变量
- 通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)
使用闭包的注意点
??由于闭包会携带 包含它的函数 的作用域,因此会比其他函数占用跟多的内存。过度使用闭包可能会导致内存占用过多
以上是关于javascript中匿名函数和普通函数的区别的主要内容,如果未能解决你的问题,请参考以下文章