JS 函数基础

159 阅读6分钟

函数的声明

方法

  1. function 声明
function add(a, b) {
    return a + b 
}
  1. 函数表达式声明
var add = function (a, b) {
    return a + b 
};

这种方法将一个匿名函数赋值给变量,等号右侧为函数表达式,需要加分号表示结束。 function 后没有函数名,如果加上函数名,该函数名只在函数体内部有效,外部无效

var print = function x () {
    console.log(typeof x)
}
x // x is not defined
print() // function
  1. Function 构造函数
var add = new Function('a', 'b', 'return  a + b');

Function 构造函数的参数依次为函数参数和函数体,如果只有一个参数,则该参数为函数体,该方法也可以不用new。该方法几乎无人使用

  1. 箭头函数
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 = 2p = 3 修改的函数内部的 p

var p = 2;

function f(o) {
    // var o = 2
    p = 3
}
f(p)
p // 3 

函数 f 运行时,函数内部声明新变量 o = 2p = 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);
}())