学习笔记--函数

106 阅读9分钟

函数

1.什么是函数

函数本质上是代码分块形式,我们来声明一个函数:

function a(a, b) {
  retrun a + b;
}

其中:

function是关键字

a是函数名称

{}内的是代码块

return是返回语句,如果没有return语句,则返回undefined

a, b是函数参数,通常具有0个或多个参数,参数之间用逗号分隔

1.1.调用函数

调用函数只需要在函数名后加上一个英文的(),例如:

function sum (a, b) {
  return a + b;
}
sum(1, 2) // 调用函数,并传入1,2为参数
> 3
1.2参数

参数分为形参和实参,形参是指在函数定义时设定的参数,实参是指在调用函数时传入的参数,如果设定了形参,但在函数调用时没有传,那么函数中的那个形参会定义为undefined。

function sum(a,b) { //这里ab为形参
  return a + b;
}
>sum(2, 3); //这里23为实参
>5

实际上我们可以使用arguments变量,该变量为内建变量,每个函数都能调用,它能返回函数所接受的所有参数。例:

function args() {
  return arguments;
}
>args();
>[]
>args(1,2,3,4,5,true, '123');
>[1,2,3,4,5,true, '123']
2.默认参数

我们可以为函数的参数设置默认值,当没有传入对应的实参时,函数的参数就为默认值,例如:

function test(a = 10, b =100) {
  console.log('a的值为:', a, ',b的值为:', b);
}
>test(1);
>'a的值为:10b的值为100'

实际上,参数传入undefined时,与不传该参数是相等的,但如果传入null则参数就为null,拿上面的函数做例子:

>test(undefined, 10);
>'a的值为:10,b的值为10'
>test(null, 10);
>'a的值为:null,b的值为10'
3.剩余参数

ES6引入了剩余参数的概念,剩余参数允许你在函数中以数组的形式获取不同个数的参数,但剩余参数只能出现在函数参数的末尾,例如:

function test(a, ...restArgs) {
  console.log(a, restArgs);
}
>test('1', '2', '3');
>'1' ['2', '3']
4.展开操作符

当你为函数传参或者用于操作数组是,可以使用展开操作符使数组转化为一个个单独的变量,例如:

function test(a, b, c) {
  return a + b + c;
}
var arr = [1,2,3];
>test(...arr);
>6

当我们想将几个旧数组拼成一个新数组时,我们可以使用展开操作符,例如:

var arr1 = [1,2,3];
var arr2 = [1];
var arr3 = [3];
var newArr = [...arr1, ...arr2, ...arr3];
console.log(newArr);
>[1,2,3,1,3]
预定义函数
  • parseInt();
  • parseFloat();
  • isNaN();
  • isFinite();
  • encodeURI();
  • decodeURI();
  • encodeURIComponent();
  • decodeURIComponent();
  • eval();

在这里不全部展开讲,有兴趣的朋友可以自行上网搜索。

parseInt

该函数会试图转换其接收到的任何输入值,转换成整数类型,转换失败则会返回NaN。例如:

>parseInt('123');
>123
>parseInt('123abc');
>123
>parseInt('1a23');
>1

parseInt的第二个参数是基数,基数负责设定函数所期望的进制类型--十进制、十六进制、二进制等。

>parseInt('FF', 10); //转换为十进制
>NaN
>parseInt('FF', 16); //转换为十六进制
>255
>parseInt('0377', 10);
>377
parseFloat

该函数功能与parseInt的功能基本相同,只是parseFloat仅支持将输入值转换为十进制

>parseFloat('123');
>123
>parseFloat('1.23');
>1.23
>parseFloat('1.2ab33');
>1.2
>parseFloat('a.bb123');
>NaN
isNaN

判断输入值是否是可以参与算术运算的数字,因而该函数可以用来检查parseInt和parseFloat运算是否成功,

而且函数也会试图将输入值转换为数字。例如:

>isNaN(NaN);
>true
>isNaN(123);
>false
>isNaN('123');
>false
>isNaN('a1.23');
>true
isFinite

该函数可以用来检查输入值是否是既非Infinity也非NaN的数字,例如:

>isFinity(Infinity);
>false
>isFinity(-Infinity);
>false
>isFinity(12);
>true
>isFinity(1e308); //科学记数法
>true
URI的编码与反编码

在URI和URL中,有一些字符是具有特殊含义的,这些字符需要转义,我们可以用到encodeURI或encodeURIComponent。前者会返回一个可用的URL,而后者则会认为我们传递的只是URI的一部分,例如:

>var url = 'http://www.***.com/script.php?q=this and that';
>encodeURI(url);
>'http://www.***.com/script.php?q=this%20and%20that'
>encodeURIComponent(url);
>'http%3A%2Fwww.***.com%2Fscript.php%3Fq%3Dthis%20and%20that'

encodeURI和encodeURIComponent还有自己分别的反编码,decodeURI和decodeURIComponent

eval

该函数会将输入的字符串当作JavaScript代码来执行,但不建议使用。例如:

>eval('var ii = 2');
>ii;
>2
5.变量的作用域

JavaScript的变量作用域不是根据代码块定义的,而是根据函数来定义的,如果某个变量是定义在函数内的,那么在函数外是不可见的,但如果是定义在if或者for中的话,在外部是可以见的。

在JavaScript中,全局变量是指定义在所有函数之外的变量,局部函数是指定义在某个函数的变量。下面举一个例子:

var global = 1;
function fn() {
  var local  = 2;
  global++;
  return global;
}
>fn();
>2
>local;
>ReferenceEorror...
  • 在函数fn可以访问变量global
  • 在函数fn外不能访问local

变量提升

当JavaScript进入新的函数时,这个函数声明的所有变量会被提升到函数的最开始的地方。如:

var a = 123;
function f() {
  alert(a);
  var a = 1;
  alert(a);
}
​
>f();
>undefined;
>1;
6.块作用域

ES6提供了新的声明变量的作用域,引用了两个新的关键字let和const来声明变量。

使用let关键字声明的变量只会在块作用域生效,每个大括号之间视为一个块作用域,例如:

var a = 1;
{
  let a = 2;
  console.log(a); // 2
}
console.log(a); // 1

注意:let声明的变量没有变量提升。

通过const可以创建一个只读的引用值,但这并不代表指向的值本身是不可变的,然而变量标识符是不可变的。例如:

const cat = {};
cat.leg = 4;

上述代码是可以正常运行,我们可以对cat对象进行修改,但如果对cat重新赋值的话是不能正常执行的。

7.函数也是数据

在JavaScript中,函数也是一种数据,我们可以把函数赋值给一个变量,例如

var a = function test() {
...
}
7.1匿名函数

我们可以这样定义一个函数:

var f = function(a) {
  return a;
}

这样定义一个函数有两个用法:

  1. 可以将匿名函数当作参数传递给其他函数,接收方函数可以利用这个函数来完成某些事情
  2. 你可以定义某个匿名函数来完成某些一次性任务
7.2回调函数
function invokeAdd(a, b) {
  return a() + b();
}
​
>invokeAdd(function() {return 1;}, function() {return 2;})
>3

回调函数的优点:

  1. 节省变量名的使用
  2. 我们可以将一个函数调用操作委托给另一个函数(意味着可以节省一些代码编写工作)
  3. 有助于提高性能
7.3即时函数
(function (a) {
 return a;
})(1);
​
>1
​
(function (a) {
  return a;
}(1))
>1

使用即时函数的优点是,不会产生任何的全局变量,但缺点是不能重复执行,除非放到循环或其他函数里,所以即时函数很适合进行初始化或一次性的任务。

7.4内部(私有)函数

我们可以在函数内部定义另一个函数,例如:

var outer = function(a) {
  var inner = function(b) {
    return 'b';
  }
  return console.log('the result is' + inner(a));
}
>outer(1);
>'the result is 1'

使用私有函数有两个好处:

  1. 可以确保全局命名空间的纯净性
  2. 可以私有化内部函数,使外部不能随意使用或修改内部的函数
7.5返回函数的函数

函数都会返回一个返回值,即时不是显式返回,也会隐式返回一个undefined,那么我们可以使函数返回另一个函数

function a() {
  alert('a');
  return function b() {
    alert('b');
  }
}
>var a1 = a(); // 'a'
>a1(); // 'b'
7.6能重写自己的函数

由于一个函数可以返回另一个函数,所以我们可以将返回的函数重新赋值给旧的的函数,来实现重写自身。上面的例子,我们也可以实现重写自身:

>a = a();

我们不仅可以在函数外面实现重写,还可以在函数内实现重写,比如:

function a() {
  alert('A!');
  a = function () {
    alert('B!');
  };
}

当第一次被调用时,会执行alert('A!'),然后将a重新赋值新的函数。

8.闭包
8.1作用域链

JavaScript中不存在大括号作用域,但有函数作用域,也就是说,在函数内定义的变量,在定义的函数外是不可见的。但在if或者for中定义的变量,在括号外是可见的。

我们举个例子:

var global = 1;
function outer() {
  var outer_local = 2;
  function inner() {
    var inner_local = 3;
    return inner_local + outer_local + global;
  }
  return inner();
}
>outer();
>6

在这个例子中,inner函数既可以访问到定在其内部的变量,也能访问到outer函数定义的变量以及全局变量,这样就形成了一条作用域链,而该链的长度和深度则取决于我们的需要。

8.2 利用闭包突破作用域链

本来在全局作用域中,无法访问函数作用域的变量,但可以通过将内部函数作为返回值传递给全局变量或者将内部函数变为全局变量,就可以在函数定义的外部访问到内部的变量。

  1. 闭包创建方法1:
var a = 'global';
var F = function() {
  var b = 'outer';
  var N = function() {
    var c = 'inner';
    return b; // 访问F函数作用域
  }
  return N; // 返回到全局变量
}
>var inner = F();
>'outer'
  1. 方法2: 直接在函数体内创建一个新的全局函数inner
var inner;
var F = function() {
  var b = 'local';
  var N = function() {
    return b;// 访问F函数作用域
  }
  inner = N;
}
>F();
>inner();
>'local'
getter与setter

有时我们想保护函数内的某些变量,不想将他暴露在外部,防止外部对变量随意修改,我们可以保护在函数内部,然后定义两个额外的函数,一个是用于获取变量的值,一个是用于重新设定变量值。

var getValue, setValue;
(function() {
  var secret = 0;
  getValue = function() {
   return secret;
  }
  
  setValue = function(newSecret) {
    if(typeof newSecret === 'number') {
      secret = newSecret;
    }
  }
}());
​
>getValue();
>0
>setValue(123);
>getValue();
>123
>setValue(false);
>getValue();
>123
迭代器

闭包的应用: 需要较为复杂的数据结构,通常与数组不同的序列结构,这时候就需要将一些“谁是下一个”的复杂逻辑封装成易于使用的next()函数。然后,我们只需要简单的调用next()就能实现相关的遍历操作。

function setup(x) {
  var i = 0;
  return function() {
    return x[i++];
  }
};
​
>var next = setup([1,2,3]);
>next();
>1
>next();
>2
>next();
>3
IIFE与作用域

ES5本身不提供块级作用域,一种应用广泛的创建块级作用域的方式是使用’立即调用函数表达式‘

而ES6提供let或者const声明创建