认识函数
+ 函数是一个 js 的数据类型, 叫做 Function
+ 是一个复杂数据类型(地址/引用)
+ 私人: 就是一个 "盒子", 这个 "盒子" 可以承载一段代码
+ 涉及到两个过程
=> 函数定义阶段: 把代码装进盒子的过程
=> 函数调用阶段: 把盒子内的代码执行的过程
函数的设计函数:需要站在用户调用者的角度去思考
1.功能
2.是否需要参数
3.是否需要返回值
函数定义阶段
+ 就是一个把代码装进 "盒子" 的过程
+ 在这个过程中, 被装进去的代码是不会执行的
+ 方式1: 声明式函数
=> 语法: function 函数名() { 你要装进盒子的代码 }
-> function 定义函数的关键字
-> 函数名 你给这个盒子起的一个名字, 遵守变量命名规则和规范
-> () 书写参数的位置, 欠着
-> {} 代码段, 你要装进盒子的代码
+ 方式2: 赋值式函数(函数表达式), 就是把函数当做一个表达式赋值给另外的内容
=> 语法: var fn = function () {}
函数的嵌套调用:
一个函数在定义时,函数体内调用了其他的函数
主调函数 f1
被调函数 f2
function f1() {
f2();
console.log("f1");
}
function f2() {
f3();
console.log("f2");
}
function f3() {
f4();
console.log("f3");
}
function f4() {
f5();
console.log("f4");
}
function f5() {
console.log("f5");
}
f1();
函数调用阶段
+ 两种定义函数的方式, 他们的 调用方式 是一样的
=> 语法: 函数名()
-> 函数名 表示你需要哪一个函数内的代码执行
-> () 把函数内的代码执行一遍,
+ 两种定义函数的方式, 他们的 调用时机 是不一样的
=> 声明式函数可以先调用也可以后调用
=> 赋值式函数(函数表达式) 只能后调用, 先调用会报错
// 声明式定义函数
// 声明了一个叫做 fn 的函数
// function fn() {
// console.log('你好 世界')
// }
// console.log(fn)
// // 赋值式定义函数
// var fn2 = function () {
// console.log('hello world')
// }
// console.log(fn2)
函数的调用阶段
// fn()
// fn2()
// 函数调用的时机问题
// 在函数声明以前调用
fn()
function fn() { console.log('我是一个声明式函数') }
// 在函数声明以后调用
fn()
// 在函数声明以前调用, 直接报错
// fn2() // fn2 is not a function
var fn2 = function () { console.log('我是一个赋值式函数') }
// 在函数声明以后调用
fn2()
函数的参数
函数的参数
+ 在函数内, 参数有两种
+ 形参
=> 书写在函数定义阶段的 () 内
=> 就是一个只能在函数内部使用的变量
=> 可以书写多个, 书写多个的时候中间使用 逗号(,) 分隔
=> 形参的值有函数调用时传递的实参决定
+ 实参
=> 书写在函数调用阶段的 () 内
=> 就是按照顺序依次给形参进行赋值的数据
=> 可以书写多个, 书写多个的时候中间使用 逗号(,) 分隔
函数的参数默认值
+ 给函数的形参添加一个默认值
+ 当你没有传递实参的时候, 可以使用默认值
+ 语法: 直接给形参进行赋值即可
例如
// function fn(a, b) {
// // 相当于在函数 fn 内定义了两个变量
// // a 和 b, 只不过现在没有赋值
// console.log('a : ', a)
// console.log('b : ', b)
// }
// // 本次调用没有传递实参, 没有数据给形参 a 和 b 赋值
// // 本次调用 a 是 undefined, b 是 undefined
// fn()
// // 本次调用只传递了一个实参
// // 10 被赋值给了 a 形参
// // b 依旧是 undefined
// fn(10)
// // 本次调用传递了两个实参
// // 按照顺序, 100 被赋值给了 a, 200 被赋值给了 b
// fn(100, 200)
// 函数的参数默认值
function fn(a = 100, b = 200) {
console.log('a : ', a)
console.log('b : ', b)
}
// 本次调用没有传递实参, a 和 b 形参都使用默认值
fn()
// 本次调用传递了一个实参给到 a, 那么 a 就是被赋值为 1, b 依旧是用默认值
fn(1)
// 本次调用传递了两个实参分别给到 a 和 b 了
fn(10, 20)
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
案例1: 求任意两个数字的和, 把结果显示在控制台
*/
// function fn1(a, b) {
// // a 和 b 就是要求和的两个数字
// // 1. 求和
// var res = a + b
// // 2. 输出
// console.log(res)
// }
// // 将来想求 10 和 20 的和
// fn1(10, 20)
/*
案例2: 求任意三个数字的和, 把结果显示在控制台
*/
// function fn2(a, b, c) {
// var res = a + b + c
// console.log(res)
// }
// fn2(10, 20, 30)
/*
案例3: 求任意个数字的和, 把结果显示在控制台
+ 当可变的内容数量不可控的时候
+ 我们的参数不好决定了
+ 可以利用 arguments 解决问题
=> 遍历 arguments, 把每一个数据叠加
*/
function fn3() {
// 1. 准备变量接受求和结果
var sum = 0
// 2. 遍历 arguments
for (var i = 0; i < arguments.length; i++) {
// 3. 把 arguments 内的每一个数据叠加到 sum 身上
sum += arguments[i]
}
// 4. 把 sum 输出
console.log(sum)
}
fn3(10, 20, 30, 40, 50)
fn3(10, 20, 30, 40)
fn3(10, 20, 30)
fn3(10, 20)
fn3(10)
fn3()
</script>
</body>
</html>
arguments
arguments
+ 是一个在函数内使用的变量
+ 是函数的天生自带的变量
+ 是一个数据集合(承载数据的盒子)
=> 承载的就是该函数被调用的时候传递的所有 实参
=> 函数实参的集合
arguments 的基本操作
1. length 属性
+ 语法: arguments.length
+ 表示: 该数据集合内有多少个数据, 也就是你传递了多少个实参
+ 得到: 是一个数值类型(Number)
2. 索引属性
+ arguments 内的数据是按照顺序依次排列的
+ arguments 内的每一个数据都有一个自己的 "序号"(索引/下标)
+ 索引(下标): 从 0 开始, 依次 +1
+ 可以利用索引去访问 arguments 内的每一个数据
+ 语法: arguments[索引]
=> 得到的就是该索引位置对应的数据
=> 如果没有该索引位置, 那么就是 undefined
+ 最后一位的索引一定是 arguments.length-1
3. 遍历 arguments
+ 从到到位获取到每一个数据
+ arguments 内可以利用索引去访问每一个数据
+ arguments 内的索引是一组有规律的数字(从 0 开始, 依次 +1)
+ 循环可以给我们提供一组有规律的数字
+ 可以利用 循环 去遍历 arguments
-> 使用循环控制变量去充当索引访问 arguments 内的每一个数据
// function fn() {
// console.log(arguments)
// console.log('length : ', arguments.length)
// console.log('arguments[0] : ', arguments[0])
// }
// fn()
// fn(10)
// fn(10, 20)
// fn(10, 20, 30)
// 遍历
function fn() {
console.log(arguments)
// 需要循环拿到 0 ~ 7 的数字(为了和 arguments 的索引配套)
// 开始: 0, 索引从 0 开始
// 结束: length - 1, 最后一位的索引一定是 length-1
// 步长: +1, 索引是依次 +1
for (var i = 0; i < arguments.length; i++) {
// 可以把循环控制变量 i 当做索引来使用
console.log(arguments[i])
}
}
fn(10, 20, 30, 40, 50, 60, 70, 80)
函数的 return
+ 作用:
1. 给函数添加一个返回值
2. 打断函数: 书写在 return 后面行的代码不会继续执行了
函数的返回值
+ 在函数内以 return 关键字确定该函数的结果
+ return 数据
注意事项:
1.函数有return才有返回值,没有return就没有返回值
2.只能返回一个值
3.当函数遇到return语句时,则直接返回值,跳出该函数
函数的问题:
+ fn 和 fn() 分别表示什么意思 ?
=> fn 就是一个变量名, 存储的是该函数的 "地址", 书写这句代码的时候, 不会执行函数内的代码
=> fn() 表示把 fn 这个函数内的代码从上到下的执行一遍, 并且会根据 return 来决定是否得到返回值
// function fn(a, b) {
// var res = a + b
// // 添加一个返回值
// // 将来你只要调用 fn 这个函数, 那么调用完毕你就能得到一个结果是 100
// // return 100
// // 不是把变量给到函数外面, 而是把这个变量保存的值给到函数外面
// return res
// }
// var a = fn(10, 20)
// console.log(a)
// 10 + 20 结果是 30
// 'a' + 'b' 结果是 'ab'
// fn() 这是一个表达式, 这个表达式也应该有一个结果
// 如何接受 10 + 20 的结果 var a = 10 + 20
// 如何接受 fn() 的结果 var b = fn()
// 1. 求两个数字的和, 把结果输出在控制台
// function fn1(a, b) {
// var res = a + b
// console.log(res)
// }
// fn1(10, 20)
// 2. 求两个数字的和, 把结果输出在页面上
// function fn2(a, b) {
// var res = a + b
// document.write(res)
// }
// fn2(100, 200)
// 在函数内只进行求和一个事情, 把输出的事情放在函数外面
// 就要求我们的函数能把求和的结果给到我, 拿出来(return)
// function fn(a, b) {
// var res = a + b
// return res
// }
// // 1. 求两个数字的和, 把结果输出在控制台
// var r1 = fn(10, 20)
// console.log(r1)
// // 2. 求两个数字的和, 把结果输出在页面上
// var r2 = fn(100, 200)
// document.write(r2)
// console.log(fn)
// console.log(fn(1, 2)) // 在控制台打印的是 fn 函数内 return 后面的 返回值
// // 把 fn 变量存储的函数地址赋值给了 a
// // 从此以后 a 也是 fn 这个函数的另一个名字了
// var a = fn
// console.log(a)
// console.log(fn)
// console.log(fn === a)
// // 把 fn 函数调用一遍, 把 fn 函数内的返回值赋值给 b
// var b = fn(1000, 2000)
// console.log(b)
// 打断函数
// function fn() {
// console.log(1)
// console.log(2)
// console.log(3)
// return
// console.log(4)
// console.log(5)
// }
// fn()
// function fn() {
// for (var i = 0; i < 10; i++) {
// console.log(i)
// if (i === 5) return
// }
// }
// fn()
案例:封装函数判断一个数字是不是质数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
封装函数判断一个数字是不是质数
*/
// 参数: 1个, 要判断的数字
function isPrime(n) {
// 1. 考虑如果 n 不是一个数字
// isNaN(n) // true 说明不是数字, 那么后续的代码没必要
if (isNaN(n)) return false
// 2. 转换成数值类型
n = Number(n)
// 3. 判断这个数字是不是质数
for (var i = 2; i <= n / 2; i++) {
if (n % i === 0) break
}
// 4. 确定返回值
// 如果 i > n / 2 为 true, 说明是质数, 应该给当前函数定义返回值为 true
// 如果 i > n / 2 为 false, 说明不是质数, 应该给当前函数定义返回值为 false
// 直接返回 i > n / 2 的结果
return i > n / 2
}
var r1 = isPrime('abc')
console.log(r1)
var r2 = isPrime(17)
console.log(r2)
var r3 = isPrime(12)
console.log(r3)
</script>
</body>
</html>
案例:判断范围内的函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="./utils.js"></script>
<script>
/*
判断范围内的质数
+ 需要判断一个数字是不是质数
*/
// 需求: 在控制台输出 100 ~ 200 之间所有的质数
// isPrime 可以判断一个数字是不是质数
for (var i = 100; i <= 200; i++) {
// 通过调用 isPrime 来判断每一个数字
var res = isPrime(i)
if (res) console.log(i)
}
</script>
</body>
</html>
小练习:求阶乘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
封装小练习
+ 封装一个函数, 求一个数字的阶乘
*/
// function fn(n) {
// // n 就是要求阶乘的数字
// // 1. 判断是不是一个数字
// if (isNaN(n)) return 0
// // 2. 把参数转换成数值类型
// n = n - 0
// // 3. 求 n 的阶乘
// var total = 1
// for (var i = n; i >= 2; i--) {
// total *= i
// }
// // 4. 把 total 当做返回值
// return total
// }
// console.log(fn(8))
</script>
</body>
</html>
自调用函数
自调用函数
+ 函数表达式的一种特殊使用方式
+ 不严格区分函数的定义阶段和调用阶段
=> 直接定义函数, 定义完毕以后直接调用, 并且不能进行二次调用
语法:
(function () {})()
~function () {}()
!function () {}()
(function () {
var n = 100
console.log('我被执行了', n)
})()
回调函数
回调函数 callback
+ 概念: 把 函数A 当做实参传递到 函数B 内
在 函数B 内以形参的方式调用 函数A
此时, 我们就说 函数A 是 函数B 的回调函数
+ 作用:
1. 封装和遍历相关的内容
2. 封装和异步相关的内容
思考:
+ 封装: 为了把一个内容(行为) 放在一个函数内, 为了复用(灵活的复用)
+ 遍历: 使用循环从头到尾的获取到数组内的每一个数据
=> 我随着循环, 每次都做一样的事情, 但是可能有细节不一样
例子:
+ 你作为我封装的一个对象
+ 功能: 跑腿
*/
// function a(cb) {
// // 这里的 cb 形参接受的就是全局函数 b 的存储地址
// // 此时这里的形参 cb 和全局变量 b 操作的是一个函数地址
// // cb() // 等价于在调用 全局b 函数
// for (var i = 0; i < 10; i++) {
// cb()
// }
// }
// function b() {
// console.log('我是全局 b 函数')
// // 在这里做出你每次想做的事情就可以了
// }
// // 把 b 函数当做参数传递到 a 函数内了
// // 变量 b 保存的是 一个函数的地址
// // 这里当做实参传递进去的也就是一个函数的地址
// a(b)
// function sort(cb) {
// var arr = [ 10, 20, 30, 40, 50 ]
// for (var i = 0; i < arr.length - 1; i++) {
// // 此时的 cb 就是在调用 b 函数
// // cb() 函数调用阶段, 书写的叫做实参
// var res = cb(arr[i], arr[i + 1])
// if (res >= 0) {
// var tmp = arr[i]
// arr[i] = arr[i + 1]
// arr[i + 1] = tmp
// }
// }
// }
// // 这个 b 函数的调用, 就是写在 sort 函数内 cb(arr[i], arr[i + 1])
// // 所以这里 return 出去的内容, 就应该再 cb 前面接受返回值
// sort(function b(param1, param2) { return param2 - param1 })
1. 时间格式化函数
+ 初始内容 : Tue Mar 01 2022 17:19:07 GMT+0800 (中国标准时间)
+ 格式化 : xxxx 年 xx 月 xx 日 上午/下午 6 时 12 分 13 秒 星期x
2. 模拟获取验证码按钮
+ 一个按钮, 原始内容 "点击获取验证码"
+ 点击以后, 内容改变 "再次获取(60s)" 随时间变化
+ 60s 到达以后, 变回初始内容
+ 坑(快速点击)
*/
//时间格式化函数
Date.prototype.format = function (format) {
var o = {
"M+": this.getMonth() + 1, //month
"d+": this.getDate(), //day
"h+": this.getHours(), //hour
"m+": this.getMinutes(), //minute
"s+": this.getSeconds(), //second
"q+": Math.floor((this.getMonth() + 3) / 3), //quarter
"S": this.getMilliseconds() //millisecond
}
if (/(y+)/.test(format)) format = format.replace(RegExp.$1,(this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) if (new RegExp("(" + k + ")").test(format))format = format.replace(RegExp.$1,RegExp.$1.length == 1 ? o[k] :("00" + o[k]).substr(("" + o[k]).length));
return format;
时间格式化
// 准备一个时间节点
var time = new Date()
// 1. 把需要的相关信息拿出来
var year = time.getFullYear()
var month = time.getMonth()
var date = time.getDate()
var hours = time.getHours()
var minutes = time.getMinutes()
var seconds = time.getSeconds()
var day = time.getDay() // 0 1 2 3 4 5 6
// 2. 对时间信息进行操作
// 2-1. 修改月份信息 +1
month++
// 2-2. 判断上午还是下午
var str = hours >= 13 ? '下午' : '上午'
// 2-3. 小时位置
if (hours >= 13) hours -= 12
// 2-4. 设置星期几
var weekStr = '日一二三四五六'
var week = '星期' + weekStr[day]
console.log(month, str, hours, week)
// 3. 组装完整字符串
var res = `${ year } 年 ${ month } 月 ${ date } 日 ${ str } ${ hours } 时 ${ minutes } 分 ${ seconds } 秒 ${ week }`
console.log(res)
事件与函数的关系
①事件: 用户用鼠标或者键盘操作网页时的某种发动作。
系统先前定义好了很多动作行为
onclick单击
ondblclick双击
onmouseover划入
onmouseout划出
②函数和事件的关系
函数就是事件触发时执行的功能模块,可以理解为是通过某个动作调用了函数
③函数和元素事件绑定
方法一:通过html元素的事件属性直接绑定
<body>
<button οnclick="f1()" οnmοuseοver="f2()">点击</button>
</body>
<script>
var f1 = function() {
console.log("heiheihei");
}
function f2() {
console.log("haha");
}
</script>
方法二:通过js实现事件和函数的绑定
第一步:所有的HTML元素都可以转换成js对象
使用:document.getElementById("id名"):通过ID名获取html元素,返回该元素的js对象形式
第二步:如何通过js对象操作该元素的属性
js对象.属性名 = 属性值
例如:
<body>
<button id="btn">点击</button>
</body>
<script>
var oBtn = document.getElementById("btn");///第一步
//以下是第二步
var f3 = function() {
console.log("yingyingying");
}
oBtn.onclick = f3;
oBtn.onmouseover = function() {
console.log("xixi");
}
</script>
变量的声明提升
<script>
当遇到没有定义的变量时,会自动进行声明提升
系统会偷偷在首行定义该变量且赋值为undefined
//var a = undefined;
a = 123;
console.log(a);
</script>
变量的作用域
:①作用域指变量能够使用的范围
②全局变量: 定义时没有被任何括号括起来的变量, 作用域为整个页面
1.全局变量可以在整个页面间共享传递数据
2.全局变量的生命周期和页面相同,大量使用全局变量可能会造成内存问题
3.大量使用全局变量,会降低函数的独立性
4.通常全局变量在事件绑定的函数中使用
③局部变量:被任何括号括起来的变量,作用域为函数的{}之间
④如何在函数体外使用函数的局部变量?
1.传参
function f1() {
var a = 123;
f2(a); //将a作为f2的参数
}
function f2(x) {
}
2.返回值
function f1() {
var a = 123;
return a;
}
var x = f1();
3. 作用域链:js允许函数嵌套定义函数
在函数嵌套定义中,各种变量的作用域关系
<script>
function f1(a) {
var b = 2;
//子函数可以使用父函数变量
var f2 = function(c) {
var d = 4;
console.log(a, b, c, d);
}
f2(3);
//父函数不能使用子函数的内部变量
// console.log(d);错误
}
f1(1);
</script>