函数
函数定义
函数是一组执行特定任务(具有特定功能)的,可以重复使用的代码块,前面几节中用到的 alert()、write() 就是 JavaScript 中内置的函数。
除了使用内置函数外,我们也可以自行创建函数(自定义函数),然后在需要的地方调用这个函数,这样不仅可以避免编写重复的代码,还有利于代码的后期维护。
JS 函数声明需要以 function 关键字开头,之后为要创建的函数名称,function 关键字与函数名称之间使用空格分开,函数名之后为一个括号
( ),括号中用来定义函数中要使用的参数(多个参数之间使用逗号,分隔开),一个函数最多可以有 255 个参数,最后为一个花括号{ },花括号中用来定义函数的函数体(即实现函数的代码)。
function functionName(parameter_list) {
// 函数中的代码
}
function sayHello(){
console.log('hello');
}
匿名函数
函数名可以省略,此时叫做匿名函数
// 函数表达式
var getSum = function(num1, num2) {
var total = num1 + num2;
return total;
};
书写形式:
var myfunction = function (parameter_list){
// 函数中的代码
};
参数说明如下:
- myfunction:变量名,可以通过它来调用等号之后的函数;
- parameter_list:为参数列表,一个函数最多可以有 255 个参数。
函数的调用
一旦定义好了一个函数,我们就可以在当前文档的任意位置来调用它。调用函数非常简单,只需要函数名后面加上一个括号即可,例如 alert()、write()。注意,如果在定义函数时函数名后面的括号中指定了参数,那么在调用函数时也需要在括号中提供对应的参数。
输出结果:
函数声明的提升
js所有代码执行前,会有一个预解析阶段(函数就会被提升),都会有一个声明提升。
函数表达式不能提升(匿名函数)
函数可以被提升
变量的声明提升,只提升变量,不提升后面的值
面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
fun();
var fun = function (){
alert('A');
}
function fun() {
alert('B');
}
fun();
</script>
</body>
</html>
函数会优先提升,然后变量的定义才会提升(并且变量的提升,只提升定义不提升值),函数是会覆盖函数表达式(匿名函数)的
习题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fun() {
};
console.log(fun); // 没有调用函数,会输出函数体
console.log(fun()); // 调用空函数默认返回undefined
</script>
</body>
</html>
js的解析机制---预解析
变量名和函数名相同时,预解析会提升函数,有多个同名函数时,会提升最后一个函数
函数的参数和返回值
参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function square(x) {
return x * x;
}
square(2) // 4
square(3) // 9
上式的x就是square函数的参数。每次运行的时候,需要提供这个值,否则得不到结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function add(a, b) {
return a + b;
}
console.log(add(1, 2));
</script>
</body>
</html>
形参和实参个数不同的情况
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function add(a, b) {
var sum = a + b;
console.log(sum);
}
add(1, 2, 3);
</script>
</body>
</html>
习题
假如我们要分别计算1到10、5到12、14到35的整数和
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function sum(num1, num2) {
var sum = 0;
for (var i = num1; i <= num2; i++) {
sum += i;
}
return sum;
};
console.log(sum(1, 10));
</script>
</body>
</html>
arguments( )
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。
- 是一个类似数组的对象
- 可以访问实参的每一项
- 可以像数组一样使用下标方式访问实参的某一项(但不能调用数组的方法)
arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
正常模式下,arguments对象可以在运行时修改。
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fun() {
console.log(arguments);
console.log(arguments[0]);
console.log(arguments[1]);
// 加和操作
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
};
var ret = fun(1, 2, 3, 4);
console.log('sum:' + ret);
</script>
</body>
</html>
函数的返回值
如果return后面没有返回值,默认返回undefined,且执行完return语句,后面的语句不会执行了。
遇见return,即退出函数
函数奇偶判断
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function isParity(num) {
if (num % 2 == 0) return '偶数';
return '奇数';
}
console.log(isParity(88));
</script>
</body>
</html>
算法面试题
寻找喇叭花数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function calcFac(num) {
var ret = 1;
for (var i = 1; i <= num; i++) {
ret *= i;
}
return ret;
};
for (var i = 100; i <= 999; i++) {
var g = i % 10;
var s = parseInt((i / 10) % 10);
var b = parseInt(i / 100);
if (calcFac(g) + calcFac(s) + calcFac(b) == i) {
console.log(i);
}
}
</script>
</body>
</html>
sort( )方法
sort()方法用于对数组的元素进行排序。
排序顺序可以是字母或数字,并按升序或降序。
默认排序顺序为按字母升序。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [1, 2, 3, 4, 8, 7, 6, 5, 0];
arr.sort(function (a, b) {
return a - b; //当a大于b时相减自然是正数,反之。(若想降序排序,只需(b-a) )
});
console.log(arr);
</script>
</body>
</html>
注意它是数组的方法
递归与克隆
递归
递归的要素
- 边界条件: 确定递归到何时终止,也称为递归出口
- 递归模式: 大问题是如何分解为小问题的,也称为递归体
递归求n的阶乘
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function calcFac(n) {
if (n < 2) return 1;
return n * calcFac(n - 1);
};
console.log(calcFac(4));
</script>
</body>
</html>
求1-100的和(递归)
通过递归算法,求1+2+3+4+5+6......100的和。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 通过递归算法,求1+2+3+4+5+6......100的和。
function sum(n) {
if (n == 1) return 1;
return n + sum(n - 1);
};
console.log(sum(100));
</script>
</body>
</html>
求第n个斐波那契数(递归)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fib(n) {
if (n <= 2) return 1;
return fib(n - 1) + fib(n - 2);
};
console.log(fib(4));
</script>
</body>
</html>
递归习题
小明入职新公司,月薪一万元。工资每年涨幅5%。计算出小慕工作20年后,月薪为多少? (递归实现)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var year = 20;
var t = 0.05;
function fun(y) {
if (y == 1) return 10000;
return t * fun(y - 1) + fun(y - 1);
};
console.log(fun(year));
</script>
</body>
</html>
月薪一万元,工资每年涨幅5%。那么一年后的工资为10000+10000 * 0.05=10500, 两年后的工资为10500+10500 * 0.05=11025
后面以此类推,得出的规律就是: 当年的月薪 = 上一年的月薪 + 上一年的月薪 * 0.05。
y表示的是年, fn(y -1) 获取的是上一年的月工资,fn(y - 1) * 0.05 表示上一年的涨幅。
以第4年为例,当n等于4的时候,递归执行的过程如下:
fn(4) = fn(3) + fn(3) * 0.05
fn(3) = fn(2) + fn(2) * 0.05
fn(2) = fn(1) + fn(1) * 0.05
fn(1) = 10000
得到fn(1)的值之后,再去推算fn(4)的值,如下:
fn(1) = 10000
fn(2) = fn(1) + fn(1) * 0.05 = 10000 + 10000 * 0.05 = 10500
fn(3) = fn(2) + fn(2) * 0.05 = 10500 + 10500 * 0.05 = 11025
fn(4) = fn(3) + fn(3) * 0.05 = 11025 + 11025 * 0.05 = 11576.25
递归单身狗
对于程序猿来说,万物皆可递归。来看下面的单身狗,不对是表情包~ 童靴们有没有看出来什么玄机呢?没错啦,“吓得我抱起了抱着抱着抱着我的小鲤鱼的我的我的我”这句话中,就暗藏递归。那么我们赶紧动手敲一敲代码,实现一下这个递归语句吧!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fun(n) {
if (n == 0) return '我的小鲤鱼';
return '抱着' + fun(n - 1) + '的我';
};
console.log('吓得我抱起了' + fun(3));
</script>
</body>
</html>
实现深克隆
浅克隆(shallow clone)
深克隆(deep clone)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [1, 2, 3, [55, 66], [88, 99]];
function deepClone(arr) {
var retArr = [];
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
retArr.push(deepClone(arr[i]));
} else {
retArr.push(arr[i]);
}
}
return retArr;
};
var ret = deepClone(arr);
console.log(ret);
console.log(ret == arr); // false 说明不是同一个内存地址
</script>
</body>
</html>
快速排序
什么是快速排序?
快速排序指的是:从数组中选定一个基数,然后把数组中的每一项与此基数做比较,小的放入一个新数组,大的放入另外一个新数组。然后再采用这样的方法操作新数组。直到所有子集只剩下一个元素,排序完成。
简单理解快速排序:选取中间数,然后遍历数组,小于中间的数放在一个新数组,大的放在另外一个新数组,不断递归,然后拼接每个数组
快速排序的思路(基本步骤)
-
从数组中选择一个元素作为基准点
-
排序数组,所有比基准值小的元素摆放在左边,而大于基准值的摆放在右边。每次分割结束以后基准值会插入到中间去。
-
最后利用递归,将摆放在左边的数组和右边的数组在进行一次上述的步骤①和步骤②的操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
function quickSort(arr) {
if (arr.length <= 1) { // 递归出口 如果数组长度<=1,则直接返回
return arr;
}
var indexPivot = Math.floor(arr.length / 2); //获取基准点的下标 将数组的长度除2向下取整得到的一个数值(获取pivot的基准索引)
var pivot = arr.splice(indexPivot, 1)[0]; // 把基准从原数组删除(splice返回的是数组)
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] <= pivot) {
left.push((arr[i]));
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
console.log(quickSort(arr));
</script>
</body>
</html>
作用域(Scope)
在 JavaScript 中,您可以在任意位置声明变量,但不同的位置会影响变量的可用范围,这个范围称为作用域。作用域可以大致分为两种类型,分别是全局作用域和局部作用域。
局部变量
- 在函数内部声明的变量具有局部作用域,拥有局部作用域的变量也被称为“局部变量”,局部变量只能在其作用域中(函数内部)
- 函数内部 加var是局部变量
- 函数内部不加var是全局变量
示例:
function myFun(){
var str = "Hello World!";
document.write(str); // 输出:Hello World!
}
document.write(str); // 报错:str is not defined
在函数内定义的局部变量只有在函数被调用时才会生成,当函数执行完毕后会被立即销毁。
形参也是局部变量
全局变量
全局作用域是指变量可以在当前脚本的任意位置访问,拥有全局作用域的变量也被称为“全局变量”。
- 在任何作用域都可以使用、生效
示例:
var str = "Hello World!";
function myFun(){
document.write(str); // 输出:Hello World!
}
myFun();
document.write(str); // 输出:Hello World!
实际情况下,所有具有全局作用域的变量都会被绑定到 window 对象中,成为 window 对象的一个属性,如下例所示:
var str = "JavaScript";
document.write(str); // 输出:JavaScript
document.write(window.str); // 输出:JavaScript
document.write(str === window.str); // 输出:true
简单理解:
遮蔽效应
如果函数中也定义了和全局同名的变量,则函数内的变量会将全局的变量“遮蔽”
面试题
习题
作用域链
示例:
习题1
解析如下:
习题2
闭包(closure)
闭包(closures)是 Javascript 语言的一个难点,也是它的特色,很多高级应用都是依靠闭包实现的。闭包与变量的作用域以及变量的生命周期密切相关。
什么是闭包?
所谓闭包,指的就是一个函数。当两个函数彼此嵌套时,内部的函数就是闭包。
例如在函数 A 中定义了函数 B,然后在函数外部调用函数 B,这个过程就是闭包。
闭包的形成条件是内部函数需要通过外部函数 return 给返回出来
JavaScript中函数会产生闭包( closure)。
闭包是函数本身和该函数声明时所处的环境状态的组合。
解析:
一个函数被调用,局部变量一般会被释放,但是在当前代码中,fun函数调用返回内部的innerFun函数,全局变量inn接收函数。inn全局变量不会被释放,innerFun也就不会被释放,innerFun函数中用了局部变量name,所以fun函数中的局部变量name也就不会被释放。
简单理解: 函数A嵌套函数B,并返回函数B,全局变量接收函数B的这种格式,就可以看做是闭包。
闭包非常实用
- 闭包很有用,因为它允许我们将数据与操作该数据的函数关联起来。这与“面向对象编程”有少许相似之处。
- 闭包的功能: 记忆性、模拟私有变量。
闭包特性
-
函数嵌套函数
-
函数内部可以引用外部的参数和变量
-
参数和变量不会被垃圾回收机制回收
闭包用途
闭包的记忆性
function funOne(){ // 外部函数
var num = 0; // 局部变量
function funTwo(){ // 内部函数
num++;
return num;
}
return funTwo;
}
var fun = funOne(); // 返回函数 funTwo
以上代码就构成了一个闭包,其实就是函数 fun。
在介绍闭包的作用之前,我们先来了解一下 JavaScript 中的 GC(垃圾回收)机制。
在 JavaScript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收,否则这个对象会一直保存在内存中。在上面的例子中,内部函数 funTwo() 定义在外部函数 funOne() 中,因此 funTwo() 依赖于 funOne(),而全局变量 fun 又引用了 funTwo(),所以 funOne() 间接的被 fun 引用。因此 funOne() 不会被 GC 回收,会一直保存在内存中,如下例所示:
function funOne(){
var num = 0;
function funTwo(){
num++;
console.log(num);
}
return funTwo;
}
var fun = funOne();
fun(); // 输出:1
fun(); // 输出:2
fun(); // 输出:3
fun(); // 输出:4
num 是外部函数 funOne() 中的一个变量,它的值在内部函数 funTwo() 中被修改,函数 funTwo() 每执行一次就会将 num 加 1。根据闭包的特点,函数 funOne() 中的变量 num 会一直保存在内存中。
当我们需要在函数中定义一些变量,并且希望这些变量能够一直保存在内存中,同时不影响函数外的全局变量时,就可以使用闭包。
当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function createTempCheck(standTemp) {
function tempCheck(n) {
if (n > standTemp) {
console.log('体温过高');
} else {
console.log('体温正常');
}
};
return tempCheck;
};
// 标准体温1
var normalTemp = createTempCheck(37);
normalTemp(38);
normalTemp(37);
// 标准体温2
var normalTemp = createTempCheck(36);
normalTemp(36);
normalTemp(38);
</script>
</body>
</html>
模拟私有变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fun() { //封装一个函数,这个函数的功能就是私有化变量
var num = 0; // 定义一个局部变量num
return {
getNum: function () {
return num;
},
numAdd: function () {
return num++;
},
numPow: function () {
return num *= 2;
}
}
};
var obj = fun();
console.log(obj.getNum()); // 如果想在fun函数外面使用变量num,唯一的方法就是调用getNum()方法
obj.numAdd()
obj.numAdd()
obj.numAdd()
console.log(obj.getNum());
obj.numPow();
console.log(obj.getNum());
</script>
</body>
</html>
使用闭包的注意点
- 不能滥用闭包,否则会造成网页的性能问题,严重时可能导致内存泄露。
- 所谓内存泄漏是指程序中己动态分配的内存由于某种原因未释放或无法释放。
闭包面试题
答案:
1
1
2
2
立即执行函数IIFE
立即执行函数的名字 语法中规定是不需要加的 可以添加函数名字,如果加的话,代码也不会报错,
但是代码在执行时会自动忽略函数名,后面也无法通过添加名称调用该函数,否则代码会出现报错。
IIFE作用1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var age = 18;
var sex = '男';
var title = (function () {
if (age < 18) {
console.log('小朋友');
} else {
if (sex == '男') {
console.log('男士');
} else {
console.log('女士');
}
}
})();
</script>
</body>
</html>
IIFE作用2
- 将全局变量变为局部变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [];
for (var i = 0; i < 5; i++) { // i是全局变量,数组中的每一个函数都共用同一个值i
arr.push(function () { // i最后出去的时候是5,不满足条件跳出循环。
alert(i);
});
}
// 弹出来的都是5,因为他们是共享一个全局变量i
arr[0]();
arr[1]();
arr[2]();
arr[3]();
arr[4]();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [];
for (var i = 0; i < 5; i++) {
(function (i) { // 这里用局部变量的形参来接收(形成了一个闭包)
arr.push(function () {
alert(i);
});
}(i)); // IIFE传实参i
}
// 会依次弹出0,1,2,3,4
arr[0]();
arr[1]();
arr[2]();
arr[3]();
arr[4]();
</script>
</body>
</html>