JS函数相关

JS 函数相关 👀

⭕ 函数的定义及调用

1
2
3
4
5
6
7
8
9
10
11
12
// 函数的定义
function fun () {
console.log('A')
}
// 调用函数
fun() // A

// 函数表达式(匿名函数)
var fun = function(){
console.log('B')
}
fun() // B

⭕ 函数声明提升

1
2
3
4
5
// 函数声明提升
fun1() // C
function fun1 (){
console.log('C')
}

❗ 注意:匿名函数会报错,本质上是变量的声明提升

1
2
3
4
5
// 匿名函数会报错,本质上是变量的声明提升
fun2() // 报错,实则变量 fun2 进行了声明提升,进行了undefined() 的调用(错误的)
var fun2 = function () {
console.log('D')
}

💡 拓展:函数优先提升(优先级;函数 > 变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 拓展:函数优先提升 (优先级:函数>变量)
// 当两种函数的定义方式同时进行定义和调用时
// 变量声明提升,无法覆盖提升的函数
fun3() // E
function fun3(){
console.log('E')
}
var fun3 = function(){
console.log('F')
}
fun3() // F
// ==============代码实际的运行情况==============
// 预解析阶段
function fun3(){
console.log('E')
}
var fun3
// 运行代码
fun3() // E
fun3 = function(){
console.log('F')
}
fun3() // F

⭕ 函数内的类数组对象:arguments

1
2
3
4
5
6
7
8
9
10
11
// 函数内arguments表示它接收到的实参列表,它是一个类数组对象
// 类数组对象:所有属性均为从0开始的自然数序列,并且有length属性,和数组类似可以用方括号书写下标访问对象的某个属性值,但是不能调用数组的方法
function fun1(){
// console.log(arguments)
var sum = 0
for(var i = 0; i < arguments.length; i++){
sum += arguments[i]
}
console.log(sum)
}
fun1(1,2,3,4,5,6)

🔔 函数算法:寻找喇叭花数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 寻找喇叭花数
// 喇叭花数是这样的三位数:其每一位数字的阶乘之和恰好等于它本身。即abc=a!+b!+c!,其中abc表示一个三位数。试寻找所有喇叭花数。
// 封装阶乘的方法
function factorial(a) {
var n = 1;
for (var i = 2; i <= a; i++) {
n *= i;
}
return n;
}
// 穷举寻找喇叭花数
for (var i = 100; i <= 999; i++) {
var i_num1 = parseInt(i / 100);
var i_num2 = parseInt((i % 100) / 10);
var i_num3 = i % 10;
if (i === factorial(i_num1) + factorial(i_num2) + factorial(i_num3)) {
console.log(i);
}
}

⭕JS 内置函数 sort()方法——数组排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var arr=[33,22,55,11];
arr.sort(function(a,b){
});
// 这个函数中的a、b分别表示数组中靠前和靠后的项,如果需要将它们交换位置,则返回任意正数;否则就返回负数。

var arr=[33,22,55,11];
arr.sort(function(a,b){
if(a > b) {
return 1
}else {
return -1
}
});
// 改写
var arr=[33,22,55,11];
arr.sort(function(a,b){
return a - b
});

⭕ 递归函数

​ 函数的内部语句可以调用这个函数自身,从而发起对函数的一次迭代。在新的迭代中,又会执行调用函数自身的语句,从而又产生一次迭代。当函数执行到某一次时,不再进行新的选代,函数被一层一层返回,函数被递归。

​ ❗递归的要素

1.边界条件:确定递归到何时终止,也称为递归出口

2.递归模式:大问题是如何分解为小问题的,也称为递归体

1
2
3
4
5
6
7
8
9
10
// 计算某个数的阶乘
//函数的功能是计算n的阶乘,n!不就是n*(n-1)!么??
function factorial(n){
//这个就是递归的出口,如果计算1的阶乘,可以不用递归了,直接返回1
if (n == 1) return 1
//如果不是1的阶乘,就返回n*(n-1)!
return n * factorial(n-1)
}
var result = factorial(6)
alert(result)

⭕ 递归实现快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 递归实现快排
// 递归函数
function quickSort(arr) {
// 递归出口
if (arr.length <= 1) return arr;
// 获取数组的中间数,并将这一项从数组中取出
var middle = arr.splice(Math.floor(arr.length / 2), 1)[0];
// 定义空数组,分别存放左右两边的数组
var left = [];
var rigth = [];
// 循环遍历数组,依次与中间数比较
for (var i = 0; i < arr.length; i++) {
// 比中间数小的数,添加到左边的数组中
// 比中间数大的数,添加到右边的数组中
arr[i] < middle ? left.push(arr[i]) : rigth.push(arr[i]);
}
// 每一项比较完后,将左右数组和中间数进行数组拼接,并返回
// 左右数组如果长度大于1的话,继续进行递归
return quickSort(left).concat([middle], quickSort(rigth));
}
// 定义一个数组
var arr = [22, 45, 15, 67, 34, 92, 73];
// 调用排序算法,打印结果
console.log(quickSort(arr));

⭕ 浅克隆与深克隆

问题:数组是引用数据类型,是无法通过 var arr2 = arr1 的方式实现克隆。

实现:准备一个空的结果数组,然后使用 for 循环遍历原数组,将遍历到的项都推入结果数组。

1
2
3
4
5
6
7
8
9
10
var arr1 = [23,45,76,82,44]
var arr2 = []
// 循环遍历数组,对每一个数组项进行克隆
for (var i = 0; i < arr1.length ; i ++) {
arr2[i] = arr1[i]
}
console.log(arr2)
arr1.push('新增')
console.log(arr1,arr2)
console.log(arr1 === arr2) // false , 浅克隆成功

不足:浅克隆只克隆数组的一层,如果数组是多维数组,则克隆的项会“藕断丝连

💡 解决方法:使用深克隆

使用递归思想,整体思路和浅克隆类似,但稍微进行一些改动:如果遍历到项是基本类型值,则直接推入结果数组;如果遍历到的项是又是数组,则重复执行浅克隆的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var arr = [[1,2,3,4],[5,6,7],[8,9,[10,11]],12,13,[14,[15,[16]]]]
// 递归函数
function deepClone (arr) {
// 定义空数组,接收结果
var result = []
// 循环遍历数组
for (var i = 0 ; i < arr.length ; i ++) {
// 递归出口,如果该项不是数组类型,则将该项推入到结果数组中
if (!Array.isArray(arr[i])) {
result.push(arr[i])
} else {
// 如果是数组类型,将该数组(并调用deepClone函数)推入到结果数组
result.push(deepClone(arr[i]))
}
}
// 循环结束,返回结果数组
return result
}
// 调用深克隆
var newArr = deepClone(arr)
console.log(newArr)
console.log(arr === newArr) // false

⭕ 全局变量和局部变量

全局变量:如果不将变量定义在任何函数的内部,此时这个变量就是全局变量,它在任何函数内都可以被访问和更改。

1
2
3
4
5
6
7
8
// 定义全局变量a
var a = 5
function fun () {
a++
console.log(a) // 6
}
fun()
console.log(a) // 6

局部变量:JavaScript 是函数级作用域编程语言;变量只在其定义时所在的 function 内部有意义

1
2
3
4
5
6
7
function fun () {
// 在函数体内定义局部变量a
var a = 5
console.log(a) // 5
}
fun()
console.log(a) // 报错

​ ❗遮蔽效应

如果函数中也定义了和全局同名的变量,则函数内的变量会将全局的变量“遮蔽”

1
2
3
4
5
6
7
8
9
10
// 定义全局变量a
var a = 5
function fun () {
a++ // 此时a声明提升:undefined++ ==> NaN
//定义局部变量
var a = 10
console.log(a) // 10 , a++的作用域在函数体内,a为局部变量,本质是函数体内的a在预解析阶段进行了变量声明提升
}
fun()
console.log(a) // 5

​ 💡形参也是局部变量

1
2
3
4
5
6
7
8
// 定义全局变量a
var a = 10
function fun (a) {
a++
console.log(a) // 3 , 形参a是局部变量
}
fun(2)
console.log(a) // 10

⭕ 作用域链

函数的嵌套:一个函数内部也可以定义一个函数。和局部变量类似,定义在一个函数内部的函数是局部函数。

1
2
3
4
5
6
7
function a () {
function b () {
console.log('局部函数')
}
b() // 调用内部函数
}
a() // 调用外部函数

作用域链定义:在函数嵌套中,变量会从内到外逐层寻找它的定义

1
2
3
4
5
6
7
8
9
10
11
var a = 10
var b = 20
function fun(){
var c=30
function inner(){
var a = 40
var d=50
console.log(a,b,c,d) // 变量会从内到外逐层寻找它的定义
}
inner()
fun()

​ ❗不加 var 将定义全局变量

1
2
3
4
5
function fun () {
a = 2
}
fun()
console.log(a) // 2

⭕ 闭包

JavaScript 中函数会产生闭包(closure)。闭包是函数本身该函数声明时所处的环境状态的组合。

简单来说,闭包是函数的一种性质。函数能够“记忆住”其定义时所处的环境,即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。

闭包截图

​ ❗闭包的用途

1.记忆性:当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 闭包的记忆性案例---案例来源:慕课网
// 创建体温检测函数checkTemp(n),可以检查体温n是否正常,函数会返回布尔值。
// 但是,不同的小区有不同的体温检测标准,比如A小区体温合格线是37.1℃,而B小区体温合格线是37.3℃,应该怎么编程呢?

// 定义外部函数(standerTemp:体温标准,n:体温)
function creatCheckTemp (standerTemp) {
return function checkTemp (n) {
alert(n < standerTemp ? '您的体温正常' : '您的体温过高')
}
}
// 创建A标准
var checkTemp_A = creatCheckTemp(37.1)
checkTemp_A(37.0)
checkTemp_A(37.2)
// 创建B标准
var checkTemp_B = creatCheckTemp(37.3)
checkTemp_B(37.4)
checkTemp_B(37.2)

2.模拟私有变量:保证某个变量只能执行指定的操作,不能通过其他方式操作变量

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
// 闭包模拟私有变量 --- 来源:慕课网
// 案例:请定义一个变量a,要求是能保证这个a只能被进行指定操作(如加1、乘2),而不能进行其他操作,应该怎么编程呢?

// 定义外部函数
function fun() {
// 定义局部变量
var a = 0;
// 返回内部对象
return {
get: function () {
console.log(a);
},
add: function () {
a++;
},
pow2: function () {
a *= 2;
},
};
}
// 外部调用
var obj = fun()
obj.get() // 0
obj.add()
obj.add()
obj.add()
obj.get() // 3
obj.pow2()
obj.get() // 6

​ ❗闭包的缺点(注意点、危害)

不能滥用闭包,否则会造成网页的性能问题,严重时可能导致内存泄露。所谓内存泄漏是指程序中己动态分配的内存由于某种原因未释放或无法释放。

⭕ 立即执行函数

lFE(Immediately Invoked Function Expression,立即调用函数表达式)是一种特殊的 JavaScript 函数写法,一旦被定义,就立即被调用

​ ❗形成 IIFE 的方法

1.函数不能直接加圆括号调用

即:function fun() { 函数体 } () // 这样是错误的写法

2.函数必须转化为函数表达式才能被调用

即:( function fun() { 函数体 } ) ()

或 +function fun() { 函数体 } ()

或 -function fun() { 函数体 } ()

​ ❗IIFE 的作用

1.为变量赋值:当给变量赋值需要一些较为复杂的计算时(如 if 语句),使用 IFE 显得语法更紧凑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IIFE作用1:为变量赋值,使语法更紧凑
var name = '小明'
var age = 13
var gender = '男'
// title 需要根据用户的年龄和性别判断决定
// 如果直接通过if判断给变量title赋值,语法比较松散
var title = (function () {
if (age < 18) {
return gender === '男' ? '男生' : '女生'
} else {
return gender === '男' ? '先生' : '女士'
}
})()
console.log(title) // 男生

2.将全局变量转化为局部变量

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
// var arr = []
// for (var i = 0 ; i < 5 ; i++) {
// arr.push(function () {
// console.log(i) // i是全局变量,所有函数都共享内存中的同一个变量i
// })
// }
// arr[0]() // 5
// arr[1]() // 5
// arr[2]() // 5
// arr[3]() // 5
// arr[4]() // 5

// 使用IIFE将全局变量转化为局部变量
var arr = [];
for (var i = 0; i < 5; i++) {
// 形成闭包
(function (i) {
arr.push(function () {
console.log(i); // i 是局部变量,等于传入的实参值
});
})(i); // 传入实参i
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2
arr[3](); // 3
arr[4](); // 4