函数定义,函数参数,函数返回值,递归,变量作用域,函数表达式,函数式编程,回调函数,高阶函数,匿名函数,立即执行函数
1. 程序中的foo、bar、baz
在学习编程的过程中,会经常看到foo、bar、baz这些名词
- 它们通常被用来作为函数、变量、文件的名词
- 目前已经编程了计算机编程的术语一部分
- 但是它们本身并没有特别的用途和意义
- 常被称之为 “伪变量”(metasyntactic variable)
它们有什么由来吗?
- 事实上,foo、bar这些名词最早从什么时候、地方流行起来的一直是由争论的
- 一种说法是通过Digital(迪吉多,数字设备公司,成立于1957年的美国电脑公司)的手册说明流行起来的
- 一种说法是说源自于电子学中的反转foo信号
- 也有一种说法是foo因为出现在了一个漫画中,漫画中foo代表“好运”,与中文的福读音类似
总之,foo、bar、baz已经是编程领域常用的名词
2. 函数
2.1. 函数的定义
什么是函数呢?
目前, 已经接触过几个函数了
- alert函数:浏览器弹出一个弹窗
- prompt函数:在浏览器弹窗中接收用户的输入
- console.log函数:在控制台输入内容
- String/Number/Boolean函数等
当谈函数时, 到底在谈些什么?
- 函数其实就是某段代码的封装,这段代码完成某一个功能
- 默认情况下JavaScript引擎或者浏览器提供一些已经实现好的函数
- 也可以编写属于自己的函数
2.2. 函数使用步骤
函数的使用包含两个步骤
- 声明函数:封装独立的功能
- 调用函数:享受封装的成果
声明函数,在JavaScript中也可以称为定义函数
- 声明函数的过程是对某些功能的封装过程
- 可以根据需求定义很多函数
调用函数,也可以称为函数调用
- 调用函数是让已存在的函数为己所用
- 这些函数可以是封装好的某个功能函数
- 也可以使用默认提供的或者其他三方库定义好的函数
函数的作用
- 在开发程序时,使用函数可以提高编写的效率以及代码的重用
2.2.1. 声明函数
声明函数使用function关键字
- 这种写法称之为函数的定义
1
2
3function 函数名(){
//函数封装的代码 ......
}注意
- 函数名的命名规则和变量名的命名规则是相同的
- 函数要尽量做到见名知意(并且函数通常是一些行为(action),所以使用动词会更多一些)
- 函数定义完后里面的代码是不会执行的,函数必须调用才会执行
2.2.2. 调用函数
- 调用函数通过函数名()即可:比如test()
2.2.3. 函数的练习
练习一:定义一个函数,打印一个人的个人信息
1
2
3
4
5
6function info(){
var name = "why";
var age = 18;
console.log(`姓名:${name},年龄:${age}`);
}
info();练习二:定义一个函数,函数中计算10和20之间数字的和,并且打印出结果
1
2
3
4
5
6
7
8function sum(){
var result = 0;
for(var i = 10; i<=20; i++){
result+=10;
}
console.log(result);
}
sum();
2.3. 函数的参数
函数的参数
- 函数把具有独立功能的代码块组织为一个小模块,在需要的时候调用
- 函数的参数,增加函数的通用性,针对相同的数据处理逻辑,能够适应更多的数据
- 在函数内部,把参数当做变量使用,进行需要的数据处理
- 函数调用时,按照函数定义的参数顺序,把希望在函数内部处理的数据,通过参数传递
形参和实参
形参:
定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用1
2// a,b就是形参
function sum(a,b){}实参:
调用函数时,小括号中的参数,是用来把数据传递到函数内部用的1
2// 10,20都是实参
sum(10,20);
有参数的函数练习
练习一:传入一个名字,对这个人say Hello
1
2
3
4function sayHello(name){
console.log(`Hello ${name}`);
}
sayHello("why");练习二:为某个朋友唱生日快乐歌
1
2
3
4function happySong(name){
console.log(`happy birthday ${name}`);
}
happySong("why");练习三:传入两个数字,计算两个数字的和,并且打印结果
1
2
3
4function sum(a,b){
console.log(`${a}+${b}=${a+b}`);
}
sum(10,20);
2.4. 函数的返回值
如prompt函数会返回用户的输入
所以说, 函数不仅仅可以有参数, 也可以有返回值
- 使用return关键字来返回结果
- 一旦在函数中执行return操作,那么当前函数会终止
- 如果函数中没有使用 return语句 ,那么函数有默认的返回值:undefined
- 如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined
函数的练习
练习一:实现一个加法计算器
1
2
3function sum(a,b){
return a+b;
}练习二:定义一个函数,传入宽高,计算矩形区域的面积
1
2
3function rectangle(width,height){
return width*height;
}练习三:定义一个函数,传入半径,计算圆形的面积
1
2
3function cycle(radius){
return (Math.PI*radius*radius).toFixed(2);
}练习四:定义一个函数,传入n(n为正整数),计算1~n数字的和
1
2
3
4
5
6
7function sum(n){
var result = 0;
for(var i=1;i<=n;i++){
result+=i;
}
return result;
}练习五:传入一个数字,可以根据数字转化成显示为亿、万文字显示的文本
1
2
3
4
5
6
7
8
9
10
11
12
13function change(num){
if(num>=1_0000_0000){
num=Math.floor(num/1_0000_0000);
return num+"亿";
}
else if(num>=10000){
num=Math.floor(num/10000);
return num+"万";
}
return num;
}
// 1亿
console.log(change(100000000));
3. 递归
3.1. 函数中调用函数
在开发中,函数内部是可以调用另外一个函数的
1
2
3
4
5
6
7
8function foo(){
console.log("foo函数被调用");
}
function bar(){
foo();
}
bar();既然函数中可以调用另外一个函数,那么函数是否可以调用自己呢?
当然是可以的
但是函数调用自己必须有结束条件,否则会产生无限调用,造成报错
1
2
3
4
5
6
7// Uncaught RangeError: Maximum call stack size exceeded
var count = 0;
function bar(){
console.log(count++);
bar();
}
bar();
3.2. 函数的递归
事实上,函数调用自己还有一个专业的名词,叫做递归(Recursion)
在语言学方面,也可以描述为递归
递归是一种重要的编程思想
- 将一个复杂的任务,转化成可以重复执行的相同任务
3.3. 实现幂函数pow
可以先用for循环来实现
1
2
3
4
5
6
7function pow(x,n){
var result = 1;
for(var i=0;i<n;i++){
result*=x;
}
return result;
}另一种实现思路是递归实现
1 | function pow_re(x,n){ |
3.4. 实现斐波那契数列
- 递归实现
1 | function fibonacci(n){ |
- for循环实现
1 | function fibonacci(n){ |
4. 变量作用域
4.1. 作用域定义
- 在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域
- 作用域(Scope)表示一些标识符的作用有效范围(所以也有被翻译为有效范围的)
- 函数的作用域表示在函数内部定义的变量,只有在函数内部可以被访问到
4.2. 外部变量和局部变量的概念
定义在函数内部的变量,被称之为局部变量(Local Variables)
定义在函数外部的变量,被称之为外部变量(Outer Variables)
全局变量
- 在函数之外声明的变量(在script中声明的),称之为全局变量
- 全局变量在任何函数中都是可见的
- 通过var声明的全局变量会在window对象上添加一个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// message在哪个范围内可以被使用,称之为message的作用域
var message = "Hello";
function foo(){
console.log(message);
}
if(true){
console.log(message);
}
// ES5之前是没有块级作用域
// var定义的变量是没有块级作用域
// 内外都可以访问到count
{
var count = 100;
console.log(count);
}
console.log(count);
// for循环的代码块也是没有自己的作用域的
for(var i = 0; i < 10; i++){
var foo = "foo";
}
// 可以访问到
console.log(foo);
console.log(i);
// ES5之前函数会形成自己的作用域
function bar(){
var a = "a";
}
// ReferenceError: a is not defined
console.log(a);
4.3. 访问变量的顺序
- 优先访问自己函数中的变量,没有找到时,在外部中访问(父函数…->全局->window对象)
1 | window.message = "Hello World"; |
- 如果删除
var message = "Hello bar";语句重新运行,结果是Hello foo - 如果再删除
var message = "Hello foo";语句重新运行,结果是Hello global - 如果再删除
var message = "Hello global";语句重新运行,结果是Hello World - 如果再删除
window.message = "Hello World";语句重新运行,结果会报错message is not defined
5. 函数声明 vs 函数表达式
5.1. 函数声明
在JavaScript中,函数并不是一种神奇的语法结构,而是一种特殊的值
前面定义函数的方式,称之为函数的声明(Function Declaration)
1 | function foo(){ |
5.2. 函数表达式
而类似变量声明的写法是函数表达式(Function Expressions)
1
2
3
4var foo = function(){
console.log("foo函数");
}
foo();注意,function 关键字后面没有函数名
- 函数表达式允许省略函数名
5.3. 两者不同
首先,语法不同
函数声明:在主代码流中声明为单独的语句的函数
函数表达式:在一个表达式中或另一个语法结构中创建的函数
其次,JavaScript创建函数的时机不同
- 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用
- 在函数声明被定义之前,它就可以被调用
- 这是内部算法的原故
- 当 JavaScript 准备运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数
开发中如何选择呢?
- 需要声明一个函数时,首先考虑函数声明语法
- 它能够为组织代码提供更多的灵活性,因为可以在声明这些函数之前调用这些函数
6. 函数式编程概念
6.1. 头等函数
头等函数(first-class function,第一级函数)是指在程序设计语言中,函数被当作头等公民
头等函数可以被赋值给变量
1
2
3var foo1 = function(){
console.log("foo1");
}头等函数可以在变量之间来回传递
1
2
3
4
5function foo(){
console.log("foo");
}
var bar = foo;
bar();头等函数可以是另外函数的参数
1
2
3
4function baz(fn){
fn();
}
barz(foo);头等函数可以作为另外函数的返回值
1
2
3
4
5
6
7function sayHello(name){
function hi(){
console.log("hi " + name);
}
return hi;
}
fun("why")();头等函数可以存储在另外一个数据结构中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var obj = {
eating: function(){
console.log("eating");
}
}
obj.eating();
// 数组
function bar1(){
console.log("bar1");
}
function bar2(){
console.log("bar2");
}
function bar3(){
console.log("bar3");
}
var arr = [bar1,bar2,bar3];
6.2. 函数式编程
通常对作为头等公民的编程方式,称之为
函数式编程- JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点
6.3. 回调函数(Callback Function)
函数可以作为一个值相互赋值,也可以传递给另外一个函数
bar函数称为回调函数
- 通过函数名加括号foo()叫做函数的调用
- 通过fn去调用bar函数的过程称之为函数的回调
1
2
3
4
5
6
7function foo(fn){
fn();
}
function bar(){
console.log("bar");
}
foo(bar);
6.4. 高阶函数(Higher-order function)
foo函数称之为高阶函数
高阶函数必须至少满足两个条件之一
- 接受一个或多个函数作为输入
- 输出一个函数
6.5. 匿名函数(anonymous)
- 如果在传入一个函数时,没有指定这个函数的名词或者通过函数表达式指定函数对应的变量,那么这个函数称之为匿名函数
1 | function request(url, callback){ |
7. 立即执行函数
7.1. 什么是立即执行函数
Immediately-Invoked Function Expression(IIFE 立即调用函数表达式)
表达的含义是一个函数定义完后被立即执行
- 第一部分是定义了一个匿名函数,这个函数有自己独立的作用域
- 第二部分是后面的(),表示这个函数被执行了
1
2
3
4var result = (function(name){
console.log("立即执行函数",name);
return "Hello World";
})("why");
7.2. 作用
会创建一个独立的执行上下文环境,可以避免外界访问或修改内部的变量,也避免了对内部变量的修改
user.js
1
2
3
4
5var userModule = (function(){
var module = {};
module.message = "Hello World";
return module;
})();index.js
1
2
3(function(){
console.log(userModule.message);
})();监听每个按钮的点击
1
2
3
4
5
6
7
8var btns = document.querySelectorAll(".btn");
for(var i = 0; i < btns.length; i++){
(function(m){
btns[m].onclick = function(){
console.log(`第{m}个按钮被点击了`);
}
})(i);
}
7.3. 其他写法
立即执行函数必须是一个表达式,不能是函数声明
- 下面的这种写法会报错,因为是一个函数声明,不是一个函数表达式
- 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明
1
2
3function foo(){
console.log("立即执行函数");
}();当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明
1
2
3(function foo(){
console.log("立即执行函数");
})();下面是一个函数表达式,所以可以执行
1
2
3
4
5
6
7
8// +-!都可以
+function foo(){
console.log("立即执行函数");
}();
(function foo(){
console.log("立即执行函数");
}());
8. 函数代码风格

- 函数名和左括号之间没有空格
pow( - 形参之间加逗号+空格
x, n - 右括号和左花括号之间有一个空格
) { - 左花括号和函数声明放在同一行
pow(x, n){ - 内部代码缩进2个空格
- for/if/while等关键字后有一个空格
for () - 操作符左右各一个空格
i < n - 每一行后加分号
result *= x; - 代码块之间隔一行
- 每行代码文本不宜太长
- else和左花括号在同一行,和if的右花括号也在同一行
} else { - 函数调用左右各一空格
alert( pow(x, n) )