1.定义函数的方式
函数声明和函数表达式
函数声明
/** * 函数声明(重要特征:函数声明提升) */ demo('函数声明'); //由于函数声明提升 不会报错 function demo(str) { console.log(str); }
函数表达式
/** * 函数表达式 * @param str */ // demo('函数表达式'); //报错 var demo = function (str) { console.log(str); } demo('函数表达式');
两者的区别:
(1)函数声明有函数声明提升,无论在将函数声明放在调用前还是调用后,都不会报错;
(2) 函数表达式没有函数声明提升,必须赋值后才能调用,否则会报错
/** * 由于函数声明提升,导致函数表达式的函数把函数声明的函数所覆盖 */ demo(); //我是函数声明 var demo = function () { console.log('我是函数表达式'); }; function demo() { console.log('我是函数声明'); } // var demo = function () { // console.log('我是函数表达式'); // }; demo(); //我是函数表达式
两种函数的调用,函数表达式必须在函数表达式完整后才能调用,异步的在函数表达式之前调用,虽然在之前调用,但是还是表达式完整之后调用的 。
console.log(fun1+'-----'); console.log(fun2+'-----'); // 不好报错,var声明的由变量提升,这里访问只是没有值而已 打印 undefined----- // console.log(fun3+'------'); // es6 变量声明有块级作用域,不存在变量提升, 所以 fun3为没有 not defined, 这里就会报错 console.log('--------------'); fun1(); // fun2(); // fun2由于var声明的变量提升,默认值为undefined,不是function // fun3(); //fun3 not defined // fun4(fun2,fun3); //也不行 console.log('--------同步的情况下,在fun1中调用fun2,fun3----------'); function fun1() { console.log('我是函数声明,同步的情况下,在fun1中调用fun2,fun3'); // fun2(); // fun2由于var声明的变量提升,默认值为undefined,不是function // fun3(); //fun3 not defined console.log('----------------------'); } function fun4(f1,f2) { console.log('我是函数声明,传参的情况下,在fun1中调用fun2,fun3'); f1(); f2(); } setTimeout(function () { console.log('---------异步的情况下在fun4中调用 fun2,fun3---------------'); fun3(); fun2(); fun5(); }); var fun2 = function () { console.log('我是函数表达式'); }; let fun3 = function () { console.log('我是es6声明函数表达式'); }; let fun5 = ()=>{ console.log('我是箭头函数'); }; console.log('--------在函数表达式之后调用------------'); fun1(); fun2(); fun3();
2. 递归
(1)递归函数是在一个函数通过名字调用自身的情况下构成的。
/** * 递归 */ function demo(num) { if(num <= 1){ return 1; }else { return num*demo(num-1); } }
function demo(num) { if(num <= 1){ return 1; }else { return num*demo(num-1); } } var anotherDemo = demo; demo = null; console.log(anotherDemo(5)); //出错
(2)在非严格模式下,可以用arguments.callee来实现函数的递归调用,但在严格模式下由于不能通过脚本来访问arguments.callee则会导致错误。
arguments.callee是一个指向正在执行的函数的指针
/** * 递归---用arguments.callee实现 */ function demo(num) { if(num <= 1){ return 1; }else { return num*arguments.callee(num-1); } } console.log(demo(5));
/** * 递归---用arguments.callee实现,在严格模式下会报错 */ function demo(num) { 'use strict'; if(num <= 1){ return 1; }else { return num*arguments.callee(num-1); } } console.log(demo(5));
3.闭包
闭包是指有权限访问另一个函数作用域中的变量的函数。
/** * 闭包 * @param property * @returns {Function} */ function demo(property) { return function (obj_1,obj_2) { var v_1 = obj_1[property]; var v_2 = obj_2[property]; return v_1+'---'+v_2; } } var com = demo('name'); console.log(com({name : 'zhangsan'},{name : 'lisi'})); //zhangsan---lisi
3.1 闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
/** * 闭包只能取得包含函数中任何变量的最后一个值 * @returns {Array} */ function demo() { var result = []; for(var i = 0;i < 10;i++){ result[i] = function () { return i; } } return result; } var arr = demo(); for(var j = 0;j<arr.length;j++){ console.log(arr[j]()); //全是10 }
/** * 运用匿名函数强制让闭包的行为表现为:数组的每个函数都有一个自己的num副本 * @returns {Array} */ function demo() { var result = []; for(var i = 0;i < 10;i++){ result[i] = (function (num) { //为每一个函创建一个自己的num副本 return function () { return num; }; })(i); } return result; } var arr = demo(); for(var j = 0;j<arr.length;j++){ console.log(arr[j]()); // 0-9 }
3.2 内存泄漏
闭包会导致访问的变量永远存在内存中,不能自行回收,所以会导致内粗泄漏。
4. 模仿块级作用域域
js没有块级作用域的概念。js也从来不对多次声明的同一个变量做出反应,只是会执行后面的初始化的值。function demo(name) { for(var i = 0;i<5;i++){ console.log(i); // 0-4 } console.log('--------------'); for(var i ;i<10;i++){ console.log(i); // 5-9 } console.log('--------------'); for(var i =0;i<5;i++){ console.log(i); // 0-4 } } demo();
解决方法:模仿模块作用域 -------私有作用域的匿名函数
语法:
(function(){
})()
function demo(name) { (function () { for(var i = 0;i<5;i++){ console.log(i); // 0-4 } })(); console.log('--------------'); (function () { for(var i ;i<10;i++){ console.log(i); // 没有输出 } })(); console.log('--------------'); (function () { for(var i =0;i<5;i++){ console.log(i); // 0-4 } })(); console.log(i); //出错 i is not defined } demo();