JavaScript 函数
- content
{:toc}
本文为慕课网 JavaScript深入浅出 JavaScript 函数笔记。
概念
函数是一块JavaScript代码,被定义一次,但可执行和调用多次。
JS中的函数也是对象,所以JS函数可以像其它对象那样操作和传递。
所以我们也常叫JS中的函数为函数对象。
例如:
1 | function foo(x, y) { |
一般由3部分组成:
- 函数名
- 参数列表
- 函数体
调用方式
- 直接调用
1 | foo(); |
- 对象方法
1 | o.method(); |
- 构造器
1 | new Foo(); |
- call/apply/bind
1 | func.call(o); |
函数声明与函数表达式
函数声明
就是对函数进行普通的声明
1 | function add(a, b) { |
函数表达式
- 将函数赋值给变量
1 | //function variable |
立即执行函数
把匿名函数用括号括起来,再直接调用。
1 | // IEF(Immediately Executed Function) |
- 函数对象作为返回值
1 | return function() { |
- 命名式函数表达式
1 | //NFE(Named Function Expression) |
这里大家肯定会好奇,这个函数怎么调用?到底用哪个名字呢?
做一个测试:
1 | var func = function nfe() {}; |
那么命名函数表达式有什么使用场景呢?
- 一般用于调试方便,如果使用匿名函数,执行的时候看不到函数名,命名函数表达式是可以看到函数名的。
- 或者在递归时,使用名字调用自己。
但是这两种用法都不常见。
变量 & 函数的声明前置
举两个例子
例1,函数声明:
1 | var num = add(1,2); |
例2,函数表达式:
1 | var num = add(1, 2); |
例1中得到的结果是 3,而例2中是 Uncaught TypeError: add is not a function
。
因为函数和变量在声明的时候,会被前置到当前作用域的顶端。例1将函数声明 function add(a, b)
前置到作用域前端,例2将声明 var add
前置到其作用域的前端了,并没有赋值。赋值的过程是在函数执行到响应位置的时候才进行的。
Function 构造器
除了函数声明、函数表达式。还有一种创建函数对象的方式,是使用函数构造器。
1 | var func = new Function('a','b','console.log(a+b);'); |
Function 中前面的参数为后面函数体的形参,最后一个参数为函数体。可以看到传入的都是字符串,这样的创建函数对象的方法是不安全的。
还有一点,Function 构造器的得到的函数对象,拿不到外层函数的变量,但是可以拿到全局变量。它的作用域与众不同,这也是很少使用的原因之一。
对比
函数属性 & arguments
函数属性 & arguments
1 | function foo(x, y, z) { |
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 方法(浏览器)
1 | function foo(x, y) { |
- call/apply 的作用:调用一个对象的一个方法,以另一个对象替换当前对象(其实就是更改对象的内部指针,即改变对象的this指向的内容)。
- call/apply 的第一个参数为对象,即使不是对象,也会被包装为对象。
- call 为扁平化传参,apply 后面的参数为数组
- 传入 null/undefined 时,实际为 Window 对象
- 在严格模式下:上述代码最后两行分别输出
null
,undefined
bind 方法
bind
是 ES5 中提出的方法,所以浏览器支持为 IE9+ 及现代浏览器。
1 | this.x = 9; |
bind
主要用于改变函数中的 this
module.getX();
直接通过对象调用自己的方法,结果是 81var getX = module.getX;
将这个方法赋值给一个全局变量,这时 this 指向了 Window,所以结果为 9var boundGetX = getX.bind(module);
使用 bind 绑定了自己的对象,这样 this 仍然指向 module 对象,所以结果为 81
bind 与 currying
bind 可以使函数柯里化,那么什么是柯里化?
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
1 | function add(a, b, c) { |
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
1 | function foo() { |
对于使用了 new func()
这种方式创建对象,其返回值为一个对象。
而原函数 foo 的返回值不是对象,所以会直接忽视这个 return 方法。而是变为 return this;
。并且 this 会被初始化为一个空对象,这个空对象的原型指向 foo.prototype。所以后面的 bind 是不起作用的。
这里面这个 this 对象包含一个属性 b = 100
。所以返回的是对象 {b: 100}
。