函数的声明
方法
function声明
function add(a, b) {
return a + b
}
- 函数表达式声明
var add = function (a, b) {
return a + b
};
这种方法将一个匿名函数赋值给变量,等号右侧为函数表达式,需要加分号表示结束。
function 后没有函数名,如果加上函数名,该函数名只在函数体内部有效,外部无效
var print = function x () {
console.log(typeof x)
}
x // x is not defined
print() // function
Function构造函数
var add = new Function('a', 'b', 'return a + b');
Function 构造函数的参数依次为函数参数和函数体,如果只有一个参数,则该参数为函数体,该方法也可以不用new。该方法几乎无人使用
- 箭头函数
var add = (a, b) => {return a + b}
注意: 箭头函数返回对象
var f = x => {name: x}
f('zzj')
// undefined
var f1 = x => ({name: x})
f1('zzj')
// { name: 'zzj' }
函数名提升
JS 引擎将函数名视为变量名,使用function 命令声明函数时,函数会像变量声明一样,被提升到代码头部。
以下代码不会报错:
f()
function f() {}
但是如果采用表达式声明,则会报错:
f()
var f = function () {};
// f is not a function
以上代码等同于:
var f
f() // f 被声明但还没被赋值,f is not a function
f = function () {}
例子:
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
第一等公民
JS 将函数看作一种值,与其他值(数字,布尔,字符串)地位相同,在 JS 中又称函数为第一等公民。凡是使用值的地方都可以使用函数。比如函数可以作为对象的属性,作为参数传入其他函数,或者作为函数的返回值。
作用域
作用域(scope)指变量存在的范围,JS 中有三种作用域,全局作用域,函数作用域,和块级作用域(ES 6)。JS 中存在特有的“链式作用域”结构,子级作用域指向父级作用域,使得子级对象可以一级一级地向上访问父级对象的变量,反之则不成立。
对于顶层函数来说,函数外部声明的变量叫全局变量,在函数内部声明的变量叫局部变量。
函数内部可以读取外部的变量
var v = 1
function f() {
console.log(v)
}
f() // 1
但外部不能读取函数内部变量:
var v = 1
function f() {
var v = 2
}
console.log(v) // 1
对于 var 命令,局部变量只能在函数内部声明,在其他区块中声明,一律为全局变量
var v = 1
{
var v = 2
}
console.log(v) // 2
上面代码中,变量v为全局变量,可用 let 声明产生块级作用域:
let v = 1
{
let v = 2
}
console.log(v) // 1
函数本身的作用域
函数本身也是一个值,也有自己的作用域,它的作用域就是其声明时所在的作用域,与其运行时所在的作用域无关:
var a = 1;
var g = function () {
console.log(a)
};
var f = function () {
var a = 2;
g()
};
f(); // 1
上面代码中,执行f()调用 g 函数,g 函数在全局作用域中定义,所以输出全局变量 a = 1,与运行作用域中的变量 a 无关。
function f () {
var x = 1
function g () {
console.log(x)
}
return g
}
var x = 2
var foo = f()
foo() // 1
函数 g 在函数 f 内部声明,作用域绑定函数 f 内部,输出 x = 1。这种机制使函数外部可以访问函数内部变量,叫做 “闭包”
函数的参数
形式参数
函数调用时会声明新变量,变量名为函数的形式参数名,该变量指向传入参数指向的值
var p = 2;
function f(p) {
// var p = 2
p = 3
}
f(p)
p // 2
调用函数 f 时,函数内部声明新变量 p = 2,p = 3 修改的函数内部的 p
var p = 2;
function f(o) {
// var o = 2
p = 3
}
f(p)
p // 3
函数 f 运行时,函数内部声明新变量 o = 2,p = 3 修改的函数外部的 p
arguments 对象
arguments 对象包含了所有传入的实际参数, 这个对象只能在函数内部才能使用
var f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
正常模式下,arguments 对象可修改,实际参数改变
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 5
严格模式下,修改 arguments 不会影响实际参数
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 2
通过 arguments.length 可以得到实际参数的个数, f.length 则返回形式参数的个数
function f(a, b) {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
f.length // 2
注意: arguments 是对象不是数组, 转为数组的两种方法
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
闭包
闭包的作用
JS 中函数内部可以读取函数外部的变量。但是外部无法读取函数内部变量, 可以在函数内部再定义一个函数,再返回这个内层函数,就可以从外部访问该函数内的变量。
function f () {
var n = 123
return function () {
console.log(n)
}
}
f()() // 123
// 或者
var res = f()
res() // 123
- 上面代码中,闭包就是返回的函数, 它能够读取 f 函数的内部变量
- 可以把闭包理解为“定义在一个函数内部的函数”
- 函数执行完毕后,其环境会被销毁,导致变量无法访问。
- 闭包函数可以始终保存其诞生环境, 下面代码中,变量 start 的状态被保存了,闭包 Inc 使其父函数的内部环境始终保存在内存中,不会在调用结束后被垃圾回收机制回收。闭包可看作是函数内部作用域的接口
function createIncrementor(start) {
return function () {
return start ++
}
}
var Inc = createIncrementor(1)
Inc() // 1
Inc() // 2
Inc() // 3
闭包的另一个用处,是封装对象的私有属性和私有方法。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');
p1.setAge(25);
p1.getAge()
上面代码中,函数 Person 的内部变量 _age,通过闭包 getAge 和 setAge,变成了返回对象 p1 的私有变量。
注意闭包占用内存,不能滥用闭包,会造成网页性能问题。
立即执行的函数表达式(IIFE)
IIFE:Immediately Invoked Function Expression
JavaScript 中()是一种运算符,跟在函数名之后表示调用这个函数。但是定义函数后立刻调用该函数会报错。
function f() {}();
// SyntaxError: Unexpected token (
这是因为function可以被当作表达式也可以被当作语句,为避免歧义,JS 引擎规定如果function出现在行首,一律解释为语句。语句不应该以圆括号结尾所以报错。
解决办法:不要让function出现在行首,在其前面加一个运算符使其变为表达式。可以是:!,~,(),+,-
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
// 或者
!function(){/* code */}()
以上函数就是 IIFE,注意 IIFE 后可以加;,表示表达式结束,否则两个 IIFE 连着写可能会报错。推荐加!,不会报错
作用:
- 不必为函数命名,避免污染全局变量
- IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
// 写法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 写法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}())