本章目标
- 能够理解函数的作用
- 能够自己定义函数
- 理解函数的参数和返回值
- 能够理解预解析和作用域
本章任务
- 通过函数实现求圆的面积
- 通过函数求最大值
- 通过函数判断一个数字是否是水仙花数
- 通过函数求数组最大值
- 通过函数对数组排序
- 能够使用函数重构仿 siri 项目
函数
函数概述
为什么要学习函数
- 假设在一个页面中,有如下多个需求:
- 获取1-100之间所有数字的和
- 获取100到200之间所有数字的和
- 获取200到300之间所有数字的和 我们利用之前所学实现代码如下:
// 获取1-100之间所有数字的和
let sum = 0;
for (let i = 1; i <= 100; i++) {
sum += i;
}
console.log(sum);
// 获取100到200之间所有数字的和
let sum1 = 0;
for (let i = 100; i <= 200; i++) {
sum1 += i;
}
console.log(sum1);
// 获取200到300之间所有数字的和
let sum2 = 0;
for (let i = 200; i <= 300; i++) {
sum2 += i;
}
console.log(sum2);
求一个范围之间所有数字的和,上述代码中的循环部分代码是重复的,有没有办法能够避免这种重复的代码呢?当然也就是我们接下来要学习的函数。
- 我们先来体验一下用函数重新实现上述功能:
let sum = 0;
for (let i = start; i <= end; i++) {
sum += i;
}
console.log(sum);
}
// 获取1-100之间所有数字的和
getSum(1, 100);
// 获取100到200之间所有数字的和
getSum(100, 200);
// 获取200到300之间所有数字的和
getSum(200, 300);
- 用函数实现上述功能后
-
- 代码量变少了并且代码不再重复
- 让代码更容易维护
-
-
- 比如:现在求[1, 100),[100, 200),[200, 300) 之间所有数字的和并且取不到最大的值,第一段代码需要修改三处,而第二段代码只需要修改一处代码。
-
-
什么是函数
- 函数就好像一个黑盒子里面封装了特定的功能,我们可以直接拿过来使用,就像遥控器。我们只需要拿到遥控器控制电视、空调就可以,而不需要关心内部的实现,坏了换一个就好。
- 之前我们用过的 Math.random() 也是一个函数,我们直接调用就可以实现生成随机数的功能,也相当于一个黑盒子,我们不需要关心内部的实现。
- 函数就是把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,起个名字(函数名),在后续开发中可以反复调用。
- 函数的作用就是封装一段代码,将来可以重复使用同时让代码更容易维护。
-
函数的基本使用
-
函数的定义
-
- 语法
-
// 函数的名字一般是动词形式,表示一个行为 // 函数声明 function 函数名 () { 函数体 } -
- 示例
- 求1到100之间所有数字的累加和。
-
function getSum () { let sum = 0; for (let i = 1; i <= 100; i++) { sum += i; } console.log(sum); } -
- 特点
-
-
- 函数定义之后,函数体并不会执行,只要当函数被调用的时候才会执行。函数一般都用来干一件事情,函数名称一般使用动词
-
-
函数的调用
-
- 语法
-
函数名(); -
- 示例
-
function getSum () { let sum = 0; for (let i = 1; i <= 100; i++) { sum += i; } console.log(sum); } getSum(); getSum(); getSum(); -
- 特点
-
-
- 函数体只有在调用的时候才会执行,调用需要()进行调用。可以调用多次(重复使用)
-
-
函数的参数
-
- 为什么要有参数
- 上一个示例中我们封装了一个求1-100之间所有数和的函数 getSum,虽然 getSum 可以重复调用,但是只能计算1-100之间的数字和。如果能实现一个计算 n - m 之间所有数和的函数,会让代码更灵活。这个时候就需要用到函数的参数。
- 函数内部是一个封闭的环境,可以通过参数的方式,把外部的值传递给函数内部
-
- 语法
-
// 带参数的函数声明 // 形参:形式参数,起到占位的作用 function 函数名(形参1, 形参2, 形参3, ......) { 函数体 } // 带参数的函数调用 // 实参:实际参数,给形参赋值用 函数名(实参1, 实参2, 实参3, ......); // 在函数内部 形参1 = 实参1 形参2 = 实参2 形参3 = 实参3 // 也就是实参使用来给形参赋值的 -
- 形参和实参
-
-
- 形式参数:在声明一个函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值。我们可以给函数设置参数。这个参数没有具体的值,仅仅起到一个占位置的作用,我们通常称之为形式参数,也叫形参。
-
-
-
- 实际参数:如果函数在声明时,设置了形参,那么在函数调用的时候就需要传入对应的参数,我们把传入的参数叫做实际参数,也叫实参。
-
-
- 示例
- 实现一个函数求n-m之间所有数字的和
-
// start 和 end 分别是形参,占位用 function getSum (start, end) { let sum = 0; for (let i = start; i <= end; i++) { sum += i; } console.log(sum) } // 1和100是实参,给形参赋值用 getSum(1, 100); getSum(110, 150);
函数的返回值
- 当函数执行完的时候,并不是所有时候都要把结果打印。我们期望函数给我一些反馈。
- 假设现在让你实现一个求员工公司的函数,员工工资 = 基本工资 + 绩效工资。把结果计算出来之后,不能直接打印,因为以后还需要计算所有员工的总工资、最高工资等,所以函数中需要把计算的结果返回。
- 这个时候就需要让函数返回一个结果,我们称为函数的返回值,函数内部可以通过 return 关键字返回一个返回值。
-
- 语法
-
// 声明一个带返回值的函数 function 函数名(形参1, 形参2, 形参3...) { // 函数体 // 通过 return 关键字返回值 return 返回值; } // 可以通过变量来接收这个返回值 let 变量 = 函数名(实参1, 实参2, 实参3...); -
- 示例
- 实现一个模拟计算工资的返回,返回计算好的工资
-
function getSalary (n1, n2) { // 省略计算工资的算法 return n1 + n2; } let salary = getSalary(5000, 2000); - 最开始定义函数的时候我们没有写 return,那不写 return 的时候函数返回什么呢?是不是没有返回值,我们来尝试下:
-
// start 和 end 分别是形参,占位用 function getSum (start, end) { let sum = 0; for (let i = start; i <= end; i++) { sum += i; } console.log(sum) } let r = getSum(1, 100); // 打印的结果是 undefined console.log(r); - 当函数内部省略 return 的时候也是有返回值的,只不过返回值是 undefined
- return 关键字除了在函数内部返回具体的值意外,还有一个作用就是终止函数的运行。
-
function say () { console.log('hello'); console.log('hello'); return; console.log('hello'); console.log('hello'); } let r = say(); // 打印的是 undefined console.log(r); - return 终止函数的执行,后面的代码不会执行。
- 当 return 后面不跟内容的时候返回的也是 undefined,函数默认返回的是 undefined
- 函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。例如:对员工工资进行求和。
-
let salaries = getSalary(5000, 2000) + getSalary(4000, 1000); -
- 总结
-
-
- 函数的默认返回值的 undefined
- 可以通过 return 关键字指定函数返回的结果
-
-
-
- return 关键字可以终止函数运行
- return 后不跟内容,函数默认返回 undefined
-
-
-
- 函数的调用结果就是返回值,因此函数的调用可以直接参与运算(函数表达式)
-
基础代码规范
-
- 命名规范
-
-
- 变量、函数的命名,必须要有意义
- 变量的名称一般用名词
-
-
-
- 函数的名称一般用动词
-
-
- 变量规范
-
-
- 操作符的前后要有空格
-
-
let userName = 'zs'; let sum = 5 + 6; let arr = [1, 2, 3, 4]; -
- 注释规范
-
// 这里是注释,斜线后要有一个空格 -
- 空格规范
-
if (true) { } for (var i = 0; i <= 100; i++) { } -
- 换行规范
-
let arr = [1, 2, 3, 4]; if (a > b) { } for (var i = 0; i < 10; i++) { } function fn () { }
函数的案例
-
- 写一个函数求任意圆面积
- 写一个函数求两个数中的最大值
-
- 写一个函数求三个数的最大值(要使用上一题中的函数)
- 写一个函数判断一个数字是否是水仙花数
-
-
- 水仙花数:一个三位数其各位数字的立方和等于该数本身,例如153是“水仙花数”,因为:153 = 13 + 53 + 33。
-
-
- 写一个函数求数组最大数
- 明确要实现的功能(例如:求最大值、求n个数的和)(分析过程)
- 分析实现功能所需要的变化数据,作为参数(分析过程)
- 分析函数的功能,作为函数体(分析过程)
- 按照函数的语法格式书写函数,已经所需参数
- 按照功能实现函数体
-
argumens
- 我们求三个数的和可以调用求两个数的和的函数,那么要求四个数的和也调用求三个数的和的函数,这样如果要求n个数的和,就要写n-1个函数了,这样是很麻烦的
- 在 JavaScript 中,函数的实参和形参的个数是允许不同的
-
function fn (n1, n2) { return n1 + n2; } // 代码可以正常执行 fn(1, 2, 3, 4); - 但是我们如果得到在实参里多出的3和4呢?JavaScript 中提供了一个在函数中专用的,获取所有实参的对象:arguments
-
function fn () { console.log(arguments); } fn(1); fn(1, 2); fn(1, 2, 3, 4, 5); - arguments 这个看起来像数组,但是其实不是一个数组,我们管它叫 伪数组。它具有数组的长度和顺序等特征。
- 注意: arguments这个东西只能在函数内部使用,也不需要我们声明,只要我们在函数中直接使用就行
- arguments 可以解决我们要求任意个数的数字和的问题:
-
function getSum(){ let sum = 0; for (let i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } getSum(1,2,3); getSum(1,2,3,4,5);
函数补充
函数表达式
- JavaScript 中定义函数的方式不只一种,过去我们定义函数的方式叫做 函数声明, 语法如下:
-
function 函数名 () { 函数体 } - 下面我们再来学习一种定义函数的方式,函数表达式,语法如下:
-
let 函数名 = function (形参列表) { 函数体 }; - 示例,求两个数的和
-
let getSum = function (a, b) { return a + b; }; getSum(11, 22); - 你仔细观察会发现,这其实跟定义变量类似,等号左边是变量名,等号右边是值,不过此时的值比较特殊是 function 开头的,也就是一个函数。
匿名函数
- 还有一种特殊的函数,是没有名字的函数,我们称作:匿名函数。
-
function (形参列表) { 函数体 } - 在 JavaScript 中上面的代码是错误的,因为匿名函数不能单独存在,其实在函数表达式中我们已经使用过匿名函数。
-
let getSum = function (a, b) { return a + b; }; - 等号右边的部分就是匿名函数。
函数也是一种数据类型
-
function fn () {} console.log(typeof fn); // 打印 function - function 是一种数据类型,那我们定义的函数 fn 就是这种类型的值,fn 是值就可以赋值给另一个变量。
-
function fn () { console.log('我是fn'); } // 注意把 fn 函数本身作为一个值,赋给另一个变量的时候不带小括号,否则就是函数调用了 let f = fn; f(); - 上面的代码形式似曾相识?我们再来修改下,我把给 f 赋值的位置修改一下
-
function fn () { console.log('我是fn'); } // 给变量 f,赋值一个函数 let f = function () { console.log('我是f') }; f(); - 你会发现代码又变回函数表达式了,函数之所以能赋值给变量的核心原因就是 函数也是一种数据类型。
- 我们还可以把一个函数作为另一个函数的参数传递。
-
function f1 (a, fn){ console.log(a); fn(); // 函数的调用,在函数名的后面加括号 } function f2 () { console.log('f2函数执行了'); } f1(10, f2);// 输出 10 和 'f2函数执行了' - 像这种作为函数的参数,并在函数之内调用的函数,我们称为回调函数。
- 回调函数 以参数的形式,传入另一个函数,并在其内被调用的函数
作用域和预解析
- 在学习作用域和预解析之前,我们先来观察两段代码的运行结果:
-
// 案例1 var a = 25; function fn () { console.log(a); var a = 10; } fn(); // 案例2 let b = 100; function fun () { console.log(b); let b = 10; } fun(); - 两段代码都很简单,执行的结果是什么呢?你可以先猜一下,然后运行代码来验证。
- 验证完之后可能跟你猜的结果完全不同,这是为什么呢?为了解释上面两段代码执行的结果,首先我们需要来学习下面两个概念,作用域和预解析。
- 为了方便解释上面的两段代码,我们把 var 和 let 分开来解释。让我们先回到过去,回到2015年以前,那时候还没有 let,定义变量只可以通过关键字 var 定义。
作用域
- 我们再来看一段简单的代码:
-
var a = 10; function fn1 () { console.log(a); } fn1(); function fn2 () { var b = 20; } fn2(); console.log(b); - 上面这段代码打印的结果分别是什么?打印 a 的值,根据直觉可以猜到是 10,b 的结果是什么呢?稍后解释,我们先来学习作用域的概念。
- 作用域:变量或者函数能够使用的范围。在JavaScript 中(2015年以前) 作用域分为两种:全局作用域和局部作用域。
- 全局作用域:全局作用域定义的变量或者函数,可以在程序的任何位置访问。在全局作用域中定义的变量称为全局变量
- 局部作用域:在创建函数的时候,函数内部可以产生局部作用域,也就是在函数内部定义的变量或者函数,只能在该函数内部访问。在函数内部定义的变量称为局部变量
- 上述代码中的变量 a 在全局作用域中定义的,是全局变量,可以在当前程序的任何位置使用,在函数 fn1 和 fn2 内部都可以访问到。所以打印 a 的值为 10。
- 变量 b 是在函数 fn2 内部定义的,在局部作用域中定义的局部变量,变量 b 只能在函数 fn2 内部访问(范围被限制在 fn2 里面)。在全局作用域中打印变量 b 会出现错误( b is not defined 变量b未定义),因为全局作用域中没有变量 b。
作用域链
- 学习完作用域之后,我们再来看一段代码。
-
var a = 25; function fn () { var a = 10; console.log(a); } fn(); - 这段代码打印的结果是什么呢?根据直觉你可以猜到打印的变量a的值是 10。为什么呢?要解释这个问题我们需要了解作用域链。
- 只要有人的地方就有江湖,只有有代码的地方就有作用域,至少有一个作用域,即全局作用域,我们的 script 标签内部就是全局作用域。另外我们知道一个函数可以创建一个作用域,作用域可以嵌套,例如如下代码:
-
var a = 10; function f1 () { function fn2 () { var a = 20; console.log(a); console.log(b); } } - 最外层的作用域是全局作用域,全局作用域中定义了函数 f1,当 f1 执行的时候创建了一个局部作用域,f1 的内部作用域。再调用 f2 的时候,又在 f1 内部创建了一个嵌套的作用域 f2作用域。
- 在 f2 内部访问变量 a 的时候,会先在当前作用域,也就是f2 的作用域中找变量 a,如果当前作用域中找到变量 a 立即执行打印。在打印变量 b 的时候,先在当前作用域查找变量 b,没有找到,再去上一级作用域 f1的作用域中找,又没有找到,最后再去 f1 的上一层作用域也就是全局作用域中找变量 b,又没有找到,这个时候会发生错误,变量 b 未定义。
- 这样的作用域嵌套就称作作用域链。
- 注意: 函数有自己的作用域,你不能从函数外部访问一个函数内部的变量值,除非你在全局声明了该变量(即不在任何函数内)
预解析
-
预解析执行过程
- 我们再把之前的代码位置稍作调整,再来分析执行的结果。
-
var a = 25; fn(); function fn () { console.log(a); var a = 10; } - 在 JavaScript 中代码执行的顺序是从上到下依次执行的。我们把 fn 的调用放到 fn 函数的定义上面,fn 函数依然可以执行。
- 这是因为 JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行代码之前分为两个过程:预解析过程和代码执行过程。
- 代码 var a = 10; 解析器会把这条语句分为两个过程执行,首先是声明 var a; 然后是赋值 a = 10;
- 声明是在预解析阶段(编译)进行,赋值是在代码执行阶段执行。
- 在执行上述代码之前,首先进行预解析(编译),把 var 和 function 的声明提升到当前作用域的最顶端,然后再执行代码。var 和 function 的提升,我们可以叫做变量提升和函数提升。
- 注意: 提升的只是声明,变量赋值和函数调用不会被提升,否则代码执行的顺序会混乱。
-
// 在 script 标签中书写的代码 var a = 25; fn(); function fn () { console.log(a); var a = 10; } // 首先扫描当前作用域中的 var 和 function 声明 // 并把声明提升到当前作用域的最顶端 function fn () { console.log(a); var a = 10; } var a; // 提升了声明变量赋值和函数调用的代码留在原地,保持代码原有的执行顺序 a = 25; fn(); // 这是全局作用域中预解析和代码执行的过程。 // 当调用 fn() 的时候进入函数作用域,同样先进行预解析再执行代码 // 下面我们来拆解 fn() 中执行的过程 // 函数 fn 的定义 function fn () { console.log(a); var a = 10; } // 执行 fn 的时候首先进行预解析,在执行代码 function fn () { // 把定义提前到作用域最顶端 var a; // 其它代码保持位置不变 console.log(a); a = 10; } - 注意: 每个作用域中都有提升操作。
练习
-
var a = 18; f1(); function f1 () { var b = 9; console.log(a); console.log(b); var a = '123'; }
函数声明和函数表达式的区别
-
foo(); bar(); // 函数声明 function foo () { console.log('foo'); } // 函数表达式 var bar = function () { console.log('bar'); }; - 上述代码中先调用 foo() 和 bar(),然后再分别用函数声明的方式定义 foo,函数表达式的方式定义 bar。那执行的结果是什么样子呢?
- 首先会打印 foo,然后报错:Uncaught TypeError: bar is not a function(未捕获的类型错误:bar 不是一个函数)。
- 我们刚刚在讲提升的时候演示过,当前作用域中的代码在执行之前首先会进行变量提升和函数提升,你现在是解析器,你开始去扫描这段代码中的 function 和 var,扫描的结果如下:
-
// 函数声明 function foo () { console.log('foo'); } // 函数表达式 var bar; bar = function () { console.log('bar'); }; foo(); bar(); - 可以看到只有函数声明被提升,函数表达式本身就是通过 var 定义的变量,所以只提升变量声明的部分 var bar,赋值部分留在原地。
- 所以函数声明和函数表达式的区别就是:
-
- 函数声明会进行提升,函数表达式不会提升。
函数优先
- 在进行提升的过程中如果遇到函数的名称和变量的名称相同会怎么样的呢?如下代码:
-
fn(); var fn = 1; function fn () { console.log('fn'); } console.log(fn); - 这段代码被提升后的结果如下:
-
function fn () { console.log('fn'); } fn(); fn = 1; console.log(fn); - 这段代码最终执行的结果,先打印 fn,再打印1。
- 注意:var fn 尽管出现在 function fn() 的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。函数声明也相当于在当前作用域中声明了一个变量,这里是 fn;
- 如果函数声明和变量声明同名的情况下,变量声明会被忽略。那如果两个函数声明同名的情况呢?
-
fn(); function fn () { console.log('bar'); } function fn () { console.log('fn'); } - 如果函数声明同名的情况下,后面的函数声明覆盖前面的函数声明(相当于 fn 函数被重新赋值),这里打印的结果是 fn。
总结
-
- 代码在执行之前首先会进行变量和函数的提升
- 变量和函数提升,就是把当前作用域中的所有声明提升到当前作用域的最顶端
-
- 提升只提升声明部分,赋值和调用不会被提升,函数表达式不会被提升
- 当函数声明和变量声明同名的情况下,函数优先
-
- 当函数声明同名的情况下,后声明的函数会覆盖先声明的函数
块级作用域/let
- 我们已经学习过全局作用域和局部作用域,在 Java/C# 这样的语言中还有一种作用域叫做块级作用域。在 ES6 中(2015年) 新增了块级作用域。
- 首先来解释代码块,一个大括号引起来的范围就是一个代码块,在过去是没有块级作用域的。在代码块中通过 var 定义的变量,在外部作用域中同样可以访问。
-
{ var a = 10; console.log(a); } console.log(a); - ES6 中通过 let 和 const 定义的变量和常量是由块级作用域的。
-
{ let a = 10; const b = 3; console.log(a); console.log(b); } console.log(a); console.log(b); - 在代码块中通过 let 和 const 定义的变量是由块级作用域的,在外部无法访问。
- 同理在 if 、while、for 语句的代码块中通过 let、const 定义的变量和常量在外部是无法访问的。
var/let/const 区别
- let 和 const 非常相似,他们唯一的区别是 let 定义的是变量可以重新赋值,const 定义的是常量,定义的时候必须给初始值,一旦赋值后续无法更改。
- var 和 let/const 的区别是:var 没有块级作用域,let/const有块级作用域。
- 还有一个问题是 var 定义的变量会被提升,那么 let/const 定义的变量/常量是否会提升呢?let 和 const 是类似的,我们通过 let 来进行测试:
-
console.log(a); let a = 10; - 我们知道上面这段代码如果通过 var 来定义 a,打印的结果是 undefined,通过 let 定义的变量 a,此时运行的结果报错:
-
// 未捕获引用错误:在初始化之前无法访问 'a' Uncaught ReferenceError: Cannot access 'a' before initialization - 这样没办法解释 let 定义的变量 a 是否被提升了,我们换一种方式来打印 a
-
console.log(a); - 不定义变量 a ,直接打印,此时报错:
-
// 未捕获引用错误:a未定义 Uncaught ReferenceError: a is not defined - 通过这两个演示我们发现,通过 let 定义的变量 a,其实是有提升的,只不过提升之后 a 被设定为未初始化,并且在赋值之前无法访问通过 let 定义的变量 a。
- ES6中关于上述演示的解析:在某个作用域中用 let/const 声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问,如果访问就会抛出错误。因此,进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时性死区。
- 简单来说就是通过 let/const 声明变量(会被提升) 到 let/const 定义的变量被赋值这段时间内,变量不能被访问,这段时间叫做暂时性死区。
- 所以最终 var 有变量提升,let/const 也有变量提升,同时 let/const 还有一个暂时性死区的概念。
作业
- 写一个函数,实现翻转数组 -写一个函数,可以实现对数组进行从大到小排序
- 输入一个年份,判断是否是闰年[闰年:能被4整数并且不能被100整数,或者能被400整数] -写一个函数输入某年某月某日,判断这一天是这一年的第几天? (要使用上一题中的函数)
- 定义变量存储输入的当月天数
- 循环输入的月份,判断这个月份有几天,累加每个月的天数
- 如果是2月,判断是闰年还是平年
- 写一个函数,可以求任意个数的和,参数个数不固定(提示:使用 arguments)
仿 siri 项目 - 函数版
- 函数封装的目的:
- 提高代码的可维护性
- 代码重用
封装主函数
项目的入口,让代码结构更清晰:
function main () {
let running = true;
while (running) {
let msg = prompt('您好,我是您的私人助理 siri \n' +
'请输入编号或《关键词》选择功能,按q退出 \n' +
'1. 计算《总和》\n' +
'2. 获取《时间》\n' +
'3. 讲个《笑话》\n' +
'4. 来个《抽奖》'
);
switch (msg) {
case '1':
case '总和':
let nums = prompt('请输入你想求和的各个数字,请使用英文逗号分隔,如"1,2,3,4"');
// 求和的封装
break;
case '2':
case '时间':
// 获取当前时间封装成函数
break;
case '3':
case '笑话':
// 生成随机笑话
break;
case '4':
case '抽奖':
// 随机抽奖
break;
case 'q':
case null:
running = false;
break;
default:
console.log('您说什么,我听不太懂');
break;
}
}
}
封装求和的函数
// 求和
function getSum (nums) {
let arr = nums.split(',');
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += parseFloat(arr[i]);
}
return sum;
}
封装获取当前时间的函数
// 获取当前时间
function getTime () {
let date = new Date();
// 获取年份
let year = date.getFullYear();
// 获取月份,注意:月份返回的是从0开始的数字 0-11,需要加1处理
let month = date.getMonth() + 1;
// 获取当月第几天
let day = date.getDate();
// 获取小时
let hour = date.getHours();
// 获取分钟
let minute = date.getMinutes();
// 获取秒
let second = date.getSeconds();
month = month < 10 ? '0' + month : month;
day = day < 10 ? '0' + day : day;
hour = hour < 10 ? '0' + hour : hour;
minute = minute < 10 ? '0' + minute : minute;
second = second < 10 ? '0' + second : second;
let dateStr = year + '年' + month + '月' + day + '日' + ' ' + hour + '时' + minute + '分' + second + '秒';
return dateStr;
}
```
# 封装生成随机笑话的函数
function getJoke () { let jokes = [ '每天把牢骚拿出来晒晒太阳,心情就不会缺钙', '明明是傻,却说是逆向思维。', '第八套广播体操,我练了三年了,什么时候能打通任督二脉啊。', '“师傅,教我分身术吧。”“首先,你要有把电锯。。。”', 'a:你说我这穷日子过到啥时侯是个头啊?b:那得看你能活多久了。', '每次看古装片听人说:愿闻其翔,都觉得怪怪的。。。', '“我有一个看家本领。” “什么?” “看家。”', '今天客户来银行取钱,坐下一句话说的我石化了:“你好,我死期到了。”', '做人最失败的莫过于唐僧,身边的人不管是敌是友都想送你上西天。', '那天在家发呆呢,突然飞来一只小鸟儿,撞窗户上了,我想:上帝在玩“愤怒的小鸟”吧,可是,不对碍…上帝觉得我是猪???' ];
let index = parseInt(Math.random() * jokes.length); return jokes[index]; }