概念 调用方式 函数声明与函数表达式 函数声明 函数表达式 变量 & 函数的声明前置 Function 构造器 对比 函数属性 & arguments 函数属性 & arguments apply/call 方法(浏览器) bind 方法 bind 与 currying bind 与 new 本文为慕课网 JavaScript深入浅出 JavaScript 函数笔记。 概念 函数是一块JavaScript代码,被定义一次,但可执行和调用多次。 JS中的函数也是对象,所以JS函数可以像其它对象那样操作和传递。 所以我们也常叫JS中的函数为函数对象。 例如: function foo(x, y) { if (typeof x === 'number' && typeof y === 'number') { return x + y; } else { return 0; } } foo(1, 2); // 3 一般由3部分组成: 函数名 参数列表 函数体 调用方式 直接调用 foo(); 对象方法 o.method(); 构造器 new Foo(); call/apply/bind func.call(o); 函数声明与函数表达式 函数声明 就是对函数进行普通的声明 function add(a, b) { return a + b; } 函数表达式 将函数赋值给变量 //function variable var add = function(a, b) { // body... }; 立即执行函数 把匿名函数用括号括起来,再直接调用。 // IEF(Immediately Executed Function) (function() { // body... })(); 函数对象作为返回值 return function() { // body... }; 命名式函数表达式 //NFE(Named Function Expression) var add = function foo(a, b) { // body... }; 这里大家肯定会好奇,这个函数怎么调用?到底用哪个名字呢? 做一个测试: var func = function nfe() {}; console.log(func === nfe); // 在 IE6~8,得到 false // 在 IE9+ 及现代浏览器中 Uncaught ReferenceError: nfe is not defined 那么命名函数表达式有什么使用场景呢? 一般用于调试方便,如果使用匿名函数,执行的时候看不到函数名,命名函数表达式是可以看到函数名的。 或者在递归时,使用名字调用自己。 但是这两种用法都不常见。 变量 & 函数的声明前置 举两个例子 例1,函数声明: var num = add(1,2); console.log(num); function add(a, b) { return a + b; } 例2,函数表达式: var num = add(1, 2); console.log(num); var add = function(a, b) { return a + b; }; 例1中得到的结果是 3,而例2中是 Uncaught TypeError: add is not a function。 因为函数和变量在声明的时候,会被前置到当前作用域的顶端。例1将函数声明 function add(a, b) 前置到作用域前端,例2将声明 var add 前置到其作用域的前端了,并没有赋值。赋值的过程是在函数执行到响应位置的时候才进行的。 Function 构造器 除了函数声明、函数表达式。还有一种创建函数对象的方式,是使用函数构造器。 var func = new Function('a','b','console.log(a+b);'); func(1,2);//3 var func2 = Function('a','b','console.log(a+b);'); func2(1,2);//3 Function 中前面的参数为后面函数体的形参,最后一个参数为函数体。可以看到传入的都是字符串,这样的创建函数对象的方法是不安全的。 还有一点,Function 构造器的得到的函数对象,拿不到外层函数的变量,但是可以拿到全局变量。它的作用域与众不同,这也是很少使用的原因之一。 对比 函数属性 & arguments 函数属性 & arguments function foo(x, y, z) { arguments.length; // 2 arguments[0]; // 1 arguments[0] = 10; x; // change to 10 arguments[2] = 100; z; // still undefined!!! arguments.callee === foo; // true } foo(1, 2); foo.length; // 3 foo.name; //"foo" foo.name 函数名 foo.length 形参个数 arguments.length 实参个数 未传参数时,arguments[i] 相应的位置仍然是 undefined。 严格模式下,代码中的改变实参失效。即 x 仍为 1。同时 callee 属性失效。 关于 callee callee 属性的初始值就是正被执行的 Function 对象。 callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名函数的递归或者保证函数的封装性,例如下边示例的递归计算1到n的自然数之和。而该属性仅当相关函数正在执行时才可用。还有需要注意的是callee拥有length属性,这个属性有时用于验证还是比较好的。 arguments.length是实参长度,arguments.callee.length是形参长度,由此可以判断调用时形参长度是否和实参长度一致。 apply/call 方法(浏览器) function foo(x, y) { console.log(x, y, this); } foo.call(100, 1, 2); //1 2 Number {[[PrimitiveValue]]: 100} foo.apply(true, [3, 4]); //3 4 Boolean {[[PrimitiveValue]]: true} foo.apply(null); //undefined undefined Window foo.apply(undefined); //undefined undefined Window call/apply 的作用:调用一个对象的一个方法,以另一个对象替换当前对象(其实就是更改对象的内部指针,即改变对象的this指向的内容)。 call/apply 的第一个参数为对象,即使不是对象,也会被包装为对象。 call 为扁平化传参,apply 后面的参数为数组 传入 null/undefined 时,实际为 Window 对象 在严格模式下:上述代码最后两行分别输出 null, undefined bind 方法 bind 是 ES5 中提出的方法,所以浏览器支持为 IE9+ 及现代浏览器。 this.x = 9; var module = { x: 81, getX: function() { return console.log(this.x); } }; module.getX(); //81 var getX = module.getX; getX(); //9 var boundGetX = getX.bind(module); boundGetX(); //81 bind 主要用于改变函数中的 this module.getX(); 直接通过对象调用自己的方法,结果是 81 var getX = module.getX; 将这个方法赋值给一个全局变量,这时 this 指向了 Window,所以结果为 9 var boundGetX = getX.bind(module); 使用 bind 绑定了自己的对象,这样 this 仍然指向 module 对象,所以结果为 81 bind 与 currying bind 可以使函数柯里化,那么什么是柯里化? 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。 function add(a, b, c) { return a + b + c; } var func = add.bind(undefined, 100); func(1, 2); //103 var func2 = func.bind(undefined, 200); func2(10); //310 add 函数拥有 3 个参数。我们想先传入一个参数,再去传其他参数。 var func = add.bind(undefined, 100); add 函数对象调用 bind 方法,由于不需要将 this 指向原来的 add 函数对象,所以第一个参数写为 undefined 或 null。第二个参数 100 传给了 add 函数中的形参 a,并赋值给一个新的函数对象 func。 这时,func(1, 2) 即相当于传入后两个参数,所以结果为 103。 同理,基于 func 可以创造一个函数 func2。它只用传最后一个参数。 bind 与 new function foo() { this.b = 100; return this.a; } console.log(foo()); //undefined var func = foo.bind({ a: 1 }); console.log(func()); //1 console.log(new func()); //foo {b: 100} 对于使用了 new func() 这种方式创建对象,其返回值为一个对象。 而原函数 foo 的返回值不是对象,所以会直接忽视这个 return 方法。而是变为 return this;。并且 this 会被初始化为一个空对象,这个空对象的原型指向 foo.prototype。所以后面的 bind 是不起作用的。 这里面这个 this 对象包含一个属性 b = 100。所以返回的是对象 {b: 100}。