Javascript之函数

简介

作用域和闭包相关参考:Javascript之作用域和闭包
变量提升和函数声明相关参考:Javascript之函数声明和变量提升

函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定
一个函数可能会有多个名字

// 写法一:函数声明(推荐写法) function sum (num1, num2) { return num1 + num2; } // 写法二:函数表达式(推荐写法) var sum = function(num1, num2) { return num1 + num2; }; // 写法三:Function 构造函数(不推荐写法) var sum = new Function("num1", "num2", "return num1 + num2");
  • 使用函数表达式时,注意函数名
var test = function test1() { var a = 1; console.log(a); } test() // 1 test1() // ReferenceError: test1 is not defined /** * 相当于: * var test = function () { * var a = 1; * console.log(a); * } */
  • 函数体内参数是沿着作用域链依次往外找的
a = 1; function test1() { var b = 2; console.log(a) // test1里面没有a,往上找,找到全局的 a = 1 function test2() { var c = 3 console.log(b); // test2里面没有b,往上找,找到test1里面的 b = 2 } test2(); console.log(c); // test1里面没有c,往上找,全局也没有。报错:ReferenceError: c is not defined } test1();

参数

  • arguments 是一个类数组对象,包含着传入函数中的所有参数。

  • arguments 还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数

  • this 引用的是函数据以执行的环境对象

  • 当在网页的全局作用域中调用函数时,this 对象引用的就是 window

function factorial(num){ // arguments 是一个类数组对象 console.log(typeof(arguments)); // object if (num <=1) { return 1; } else { // return num * factorial(num-1) // 直接用函数名当然也可以,可以使用arguments.callee消除耦合 return num * arguments.callee(num-1) } }

形参:形式参数,是函数作用域内的变量。
实参:调用函数时,传入的参数,有对应的形参时,实参会赋值给形参。

形参可以设置默认值,当有对应实参赋值的时候,默认值一般会被实参替换。
若形参设置了不为undefined的默认值,传入对应实参为undefined时,形参不会被实参替换。

function test1 (a, b) { console.log(test1.length) // 形参的长度 2 console.log(arguments.length) // 实参的长度 3 } test1(1,2,3) /** * 形参:a, b * 实参:1, 2, 3 arguments是实参集合,是一个object对象(类数组) * 形参的数量使用: 方法名.length 获取 * 实参的数量使用: arguments.length 获取 */
  • 非严格模式下,arguments值可修改
function test2(a, b) { a = 3; console.log(a, arguments[0]) // 3 3 } test2(1,2) /** * 实参第一个参数传入1, 形参使用 a 接收 -> a = 1 * a = 3 ---> arguments[0] 也跟着改了? */ function test3(a = 5, b) { a = 3; console.log(a, arguments[0]) // 3 1 } test3(1,2) /** * a 有默认值的时候 a = 3 ---> arguments[0] 为啥不跟着改了?默认严格模式? */
  • 实参数量不够时,后面的形参默认是undefined
function test4(a, b) { b = 3; console.log(b, arguments[1]); // 3 undefined } test4(1); /** * 实参只有一个,所以 b = undefined * b = 3 ---> 因为没有arguments[1] 所以b修改不会引起arguments[1]的变化 */
  • ES5ES6默认参数
// 默认参数 ES5 function test(a, b) { a = arguments[0] || 1 b = arguments[1] || 1 console.log(a) // arguments[0] = undefined ---> a = 1 console.log(b) // arguments[1] = 2 ---> b = 2 } test(undefined, 2) // 默认参数 ES6 function test(a = 1, b = 1) { console.log(a) // 实参是 undefined,使用默认参数 1 console.log(b) // 实参是 2 --> b = 2 } test(undefined, 2)
  • 非严格模式下的arguments.callee
function test6(a,b,c){ console.log(arguments.callee.length) // 3 console.log(arguments.callee === test6) // true } test6(1,2,3, 4);
  • ES6参数的解构赋值和默认值
function add([x, y]){ return x + y; } console.log(add([1, 2])) // 3 function move1({x = 0, y = 0} = {}) { return [x, y]; } move1({x: 3, y: 8}); // [3, 8] move1({x: 3}); // [3, 0] move1({}); // [0, 0] move1(); // [0, 0] function move2({x, y} = { x: 0, y: 0 }) { return [x, y]; } move2({x: 3, y: 8}); // [3, 8] move2({x: 3}); // [3, undefined] move2({}); // [undefined, undefined] move2(); // [0, 0]

立即执行函数

  • ,分割的,取后面的值
var fn = ( function test1(){ return 1; }, function test2(){ return '2'; } )(); console.log(typeof(fn)) // string console.log((1, 2)) /** * ,分割的,取后面的值 * var fn = (function test2(){ * return '2'; * })(); * 自动执行 ---> var fn = '2' * typeof(fn) ---> string */

闭包

能够访问另一个函数作用域的变量的函数

用途:

  • 可以读取函数内部的变量,
  • 让这些变量的值始终保持在内存中
function outer() { var a = '变量1' return function() { // 该函数能够访问到outer函数作用域下的a console.log(a) } }

箭头函数

  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象

  • 不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误

  • 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替

关卡

  • 经典题:输出代码
function test() { var arr = [] for(var i = 0; i < 10; i++) { arr[i] = function() { console.log(i) } } return arr } var myArr = test() for(var i = 0; i < 10; i++) { myArr[i]() // 10个10 } /** * var i,i 的作用域在 test 内 * 执行完 test 之后(i = 10), arr数组里面的每一项都是 function() { console.log(i) } * 所以输出10 */ // 改写test()方法,使之输出 0 - 9 // 方法一:var i = 0 → let i = 0 function test() { var arr = [] for(let i = 0; i < 10; i++) { arr[i] = function() { console.log(i) } } return arr } /** * let i,i 的作用域在 for 内 * function() { console.log(i) } 用到的 i 是 for循环作用域内的 i * 输出 0 - 9 */ // 方法二:使用闭包 function test(){ var arr = [] for(var i = 0; i < 10; i++) { (function(j) { arr[j] = function(){ console.log(j) } })(i) } return arr } /** * 使用闭包包裹,使用的 i 是每次循环传入的 i * 输出:0 - 9 */ // 方法三:使用闭包,可以只包裹function function test(){ var arr = [] for(var i = 0; i < 10; i++) { arr[i] = (function(j) { return function() { console.log(j) } })(i) } return arr }
  • 合并任意个数的字符串
var concat = function(){ var result = ''; for(var i = 0; i < arguments.length; i ++){ result += arguments[i]; } return result; } console.log(concat('st','on','e')); // stone
  • 输出指定位置的斐波那契数列
var fioacciSequece = function(count){ return (function(n) { if (n <= 1) { return n; } else { return arguments.callee(n - 1) + arguments.callee(n - 2); } }(--count)); } console.log(fioacciSequece(12)); // 0、1、1、2、3、5、8、13、21、34、55、[89]
  • 三维数组或 n 维数组去重,使用 arguments 重写
var arr = [2,3,4,[2,3,[2,3,4,2],5],3,5,[2,3,[2,3,4,2],2],4,3,6,2]; var unique = function(arr) { var result = []; (function(arr) { var f = arguments.callee; arr.forEach(function() { if (Array.isArray(arguments[0])) { f(arguments[0]); } else { if (result.indexOf(arguments[0]) < 0) { result.push(arguments[0]); } } }); }(arr)); return result; } console.log(unique(arr)); // [2,3,4,5,6]

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: