1-1、函数的返回值
函数的返回值
- 函数体内可以使用return关键字表示
“函数的返回值” - 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方
遇见return即退出函数
- 调用函数时,
一旦遇见return语句则会立即退出函数,将执行权交还给调用者 - 结合if语句的时候,往往不需要写else分支了
- 比如题目:请编写一个函数,判断一个数字是否是偶数
代码案例
<!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>1-1、函数的返回值.</title>
</head>
<body>
<script>
// 函数的功能就是返回两个数的和
function sum(a, b) {
// 函数的返回值
return a + b;
}
// 函数的返回值可以被变量接收
var result = sum(3, 5);
// 调用一个有返回值的函数,可以以被当作一个普通值,从而可以出现在任何可以书写值的地方
var result1 = sum(3, 4) * sum(2, 6);
var result2 = sum(3, sum(4, 5));
console.log(result);
console.log(result1);
console.log(result2);
// 调用函数时,一旦遇见return语句则会立即退出函数,将执行权交还给调用者
function fun() {
console.log("A");
return "B";
console.log("C");
}
console.log(1);
var char = fun();
console.log(char);
console.log(2);
// 书写一个函数,函数的功能判断一个数字是否为偶数
function checkEven(n) {
// 但判断语句只有一行代码的时候,可以在一行上写
if (n % 2 == 0) return true;
return false;
}
var result3 = checkEven(6);
console.log(result3)
</script>
</body>
</html>
1-2、函数的参数与返回值
函数的参数
- 参数是函数内的一些
待定值,在调用函数时,必须传入这些参数的具体值 - 函数的
参数可多可少,函数可以没有参数,也可以有多个参数,多个参数之间需要用逗号隔开
arguments
- 函数内arguments表示它接收到的实参列表,它是一个类数组对象 类数字对象:所有属性均为0开始的自然数序列,并且有length属性,和数字类似可以用方括号书写下标访问对象的某个属性值,但是不能调用数组的方法
代码案例
<!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>1-2、函数的参数与返回值</title>
</head>
<body>
<script>
// 圆括号中定义形式参数
function sum(a, b) {
var w = a + b;
console.log(w)
}
// 调用函数时传入实际参数
sum(3, 5);
// arguments
// 函数内arguments表示它接收到的实参列表,它是一个类数组对象
// 类数组对象:所有属性均为从0开始的自然数序列,并且有length属性,和数组类似可以用方括号书写下标访问对象的某个属性值,但是不能调用数组的方法
// 不管用户传入多少个实际参数,永远能够计算它们的和
function fun() {
var sum1 = 0;
for (var i = 0; i < arguments.length; i++) {
sum1 += arguments[i];
}
console.log("所有参数的和是"+sum1);
// console.log(arguments);
// console.log(arguments[0]);
// console.log(arguments[1]);
// console.log(arguments[2]);
}
fun(33,44,23,34)
fun(33)
fun(-3,4)
</script>
</body>
</html>
1-3、函数的调用
函数的调用
- 执行函数体中的所有语句,就称为“调用函数”
- 调用函数非常简单,只需在函数名字后书写圆括号对即可
语句执行顺序
- 函数不调用就不会执行
- 调用后就会将调用权移交给函数
- 函数体内所有语句执行完毕,将语句执行权交还给主程序
函数声明的提升
- 和变量声明提升类似,函数声明也可以被提升
函数表达式不能提升
- 如果函数是用函数表达式的写法定义的,则没有提升特性
函数优先提升
- 函数会优先提升
- 变量声明提升,无法覆盖提升的函数
代码案例
<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>1-3、函数的调用</title>
</head>
<body>
<script>
// 定义函数,定义的函数是不会直接执行的
function fun () {
console.log("你好");
console.log("今天天气真好");
}
// 函数必须要等到调用的时候才能被执行
fun();
fun();
// 不调用就不会执行
function fun1 () {
console.log("A");
console.log("B");
console.log("C");
}
console.log(1);
console.log(2);
console.log(3); // 1,2,3
// 程序的执行权(调用权)移交给函数
fun1();
// 函数体内所有语句执行完毕,将语句执行权交还给主程序
console.log(4);
console.log(5);
console.log(6); // 1,2,3,"A","B","C",4,5,6
// 函数声明的提升 和变量声明类似,函数声明也可以被提升
fun2();
// 在预解析阶段会被提升 即函数先调用后声明也是合法的
function fun2() {
alert("函数被执行")
}
// 函数表达式不能提升 如果函数是用函数表达式的写法定义的,则没有提升特性
// 用函数表达式的写法只是声明了一个fun1变量,然后把一个匿名函数赋值给这个变量而已,而变量提升是只提升定义不提升值,那么就可以理解这个时候只是把fun1提升了,但是里面的function值并没有被提升,所以fun1的值是undefined
fun3(); // 引发错误
var fun4 = function () {
alert("函数被执行")
}
fun3()
// 函数优先提升
fun5(); // B
// 变量声明提升,无法覆盖提升的函数
var fun5 = function () {
alert("A");
}
// 函数优先提升
function fun5 () {
alert("B");
}
// 在这个时候fun就已经是函数表达式的内容了,因为函数是在预解析阶段加载的,在阶段后就会被表达式覆盖了,从而导致函数fun不会再执行,只会执行函数表达式fun
fun5(); // A
</script>
</body>
</html>
1-4、函数的定义与调用
函数的定义
- 和变量类似,函数
必须先定义然后才能使用 - 使用
function关键字定义函数,function是“功能”的意思
函数的调用
- 执行函数体中的所有语句,就称为“调用函数”
- 调用函数非常简单,只需在函数名字后书写圆括号对即可
代码案例
<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>1-4、函数的定义与调用</title>
</head>
<body>
<script>
// 定义函数
function fun() {
}
// 函数表达式
var fun1 = function () {
}
// 调用函数
fun();
fun1();
</script>
</body>
</html>
1-5、什么是递归
递归
- 函数的内部语句可以
调用这个函数自身,从而发起对函数的一次迭代。在新的迭代中,又会执行调用函数自身的语句从而又产生一次迭代。当函数执行到某一次时,不再进行新的迭代,函数被一层层返回,函数被递归。 - 递归是一种较为
高级的编程技巧,他把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归的要素
- 边界条件:确定递归到何时终止,也称为
递归出口 - 递归模式:大问题是如何分解为小问题的,也称为
递归体
<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>1-5、什么是递归</title>
</head>
<body>
<script>
// 书写一个函数,这个函数内部会自己调用自己,从而形成递归
function factorial(n) {
// 函数的功能就是求n的阶乘,n!不就是n*(n-1)!么??
// 这个就是递归的出口,如果计算1的阶乘,可以不用递归了,直接告诉你答案就是1
if (n==1) return 1;
// 如果询问的不是1的阶乘,就返回 n * (n - 1)!
return n * factorial(n - 1);
// return n == 1 ? 1 : n * factorial(n - 1);
}
var result = factorial(6);
alert(result);
</script>
</body>
</html>
1-6、实现深克隆
复习引用类型值的相关知识
- JavaScript中的数据类型有两类,基本类型和引用类型值
引用类型值举例还有函数,正则表达式等
复习浅克隆
- 使用
var arr2 = arr1这样的语句不能实现数组的克隆 - 浅克隆:准备一个空的结果数组,然后使用for循环遍历原数组,将遍历到的项都推入结果数组
- 浅克隆
只克隆数组的一层,如果数组是多维数组,则克隆的项会“藕断丝连”。
实现深克隆
- 使用
递归思想,整体思路和浅克隆类似,但稍微进行一些改动;如果遍历到项是基本类型值,则直接推入结果数组;如果遍历到的项又是数组,则重复执行浅克隆的操作
代码案例
<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>1-6、实现深克隆</title>
</head>
<body>
<script>
// js种的数据类型有两类,基本类型和引用类型值
// 基本类型值 举例:数字型、字符串型、布尔型、undefined型
// 当var a = b变量传值时:内存中产生新的副本
// 当用==比较时:比较值是否相等
// 引用类型值 举例:对象、数组、函数、正则表达式
// 当var a = b变量传值时:内存中不产生新的副本,而是让新变量指向同一个对象
// 当用==比较时:比较内存地址是否相同,即比较是否是同一个对象
// 浅克隆
// 准备原数组
var arr1 = [33, 44, 55, 11, 22,[77,88]];
// 准备一个结果数组
var result = [];
// 测试一下这样的语句能否克隆数组 不能,这种方法只不过是引用了同一个数组,更改其中一个的数值,另一个也会发生变化
// var arr2 = arr1;
// arr1.push(66);
// console.log(arr2);
// console.log(arr1 == arr2);
// 遍历原数组,将遍历到的项都推入到结果数组中
for (var i = 0; i <arr1.length;i++) {
result.push(arr1[i]);
}
// console.log(result);
// 测试是否实现了克隆,就是说本质上是内存中的不同数组了
// console.log(arr1 == result);
// arr1.push(666);
// // 测试这样的克隆是浅克隆,数组套数组时会直接引用过去,简称“藕断丝连”
// arr1[5].push(99);
// console.log(arr1);
// console.log(result);
// 实现深克隆
// 原数组
var arr1 = [11,22,33,44,55,[66,77,[88,99]]];
// 函数,这个函数会被递归
function deepClone(arr) {
// 结果数组,“每一层”都有一个结果数组
var result = [];
// 遍历数组的每一项
for(var i = 0; i < arr.length; i++) {
// 类型判断,如果遍历到的项是数组
if (Array.isArray(arr[i])) {
// 递归
result.push(deepClone(arr[i]));
} else {
// 如果遍历到的项不是数组,是基本类型值,就直接推入到递归的出口
// 相当于是递归的出口
result.push(arr[i]);
}
}
// 返回结果数组
return result;
}
// 测试一下
var arr2 = deepClone(arr1);
console.log(arr2);
// 是否藕断丝连
console.log(arr1[5] == arr2[5]);
console.log(arr1[5][2] == arr2[5][2]);
arr1[5].push(99)
console.log(arr1);
console.log(arr2);
</script>
</body>
</html>
1-7、全局变量与局部变量
变量作用域
- JavaScript是
函数级作用域编程语言:变量只在其定义时所在的function内部有意义
全局变量
- 如果不将变量定义在任何函数的内部,此时这个变量就是
全局变量,它在任何函数内都可以被访问和更改
遮蔽效应
- 如果函数中也定义了和全局同名的变量,则函数内的变量会将全局的变量“遮蔽”
注意考虑变量声明提升的情况
<!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>1-7、全局变量与局部变量</title>
</head>
<body>
<script>
// 全局/局部变量
var a = 10;
function fun() {
// var a = 10;
a++;
// console.log(a);
}
fun();
console.log(a);
// 遮蔽效应
// 全局变量
var b = 10;
function fun() {
// 局部变量b将全局变量b遮蔽了,不能理解为覆盖,因为在这个函数外b依然是全局变量
var b = 5;
b++;
console.log(b); // 输出6
}
fun();
console.log(b); //输出10
// 注意考虑变量声明提升的情况
var c = 10;
function fun() {
// 这里的c其实是下一行声明的局部变量c的自增一,因为涉及到预解析 变量声明提升,所以可以理解为var c = c++;同时属性并不会提升,这时候的c是undefined,而undefined自增一为NaN
c++; // NaN
// 到了这里c又重新赋值为5,所以之后的控制台输出c其实是5
var c = 5; // 5
console.log(c);
}
fun();
console.log(c); // 10
</script>
</body>
</html>
1-8、形参也是局部变量
作用域链
- 先来认识函数的嵌套:一个函数内部也可以定义一个函数。和局部变量类似,定义在一个函数内的函数的局部函数
- 在函数嵌套中,变量会从内到外逐层寻找它的定义
不加var将定义全局变量
- 在初次给变量赋值时,如果没有加var,则将定义全局变量
代码案例
<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>1-8、形参也是局部变量</title>
</head>
<body>
<script>
var a = 10;
// 形参a也是函数内部的局部变量
function fun(a) {
a++;
console.log(a); // 8
}
fun(7);
console.log(a); // 10
// 作用域链
function fun3() {
// 局部函数
function inner() {
console.log("你好");
}
inner(); // 调用内部函数
}
fun3() //调用外部函数
// 在函数嵌套中,变量会从内到外逐层寻找它的定义
var a1 = 10;
var b = 20;
function fun2() {
var c = 30;
function inner() {
var a1 = 40;
var d = 50;
console.log(a1, b, c, d); // 使用变量是,js会从当前层开始逐层向上寻找定义
}
inner();
}
fun2();
// 不加var将定义全局变量
function fun1() {
w = 3;
}
fun1();
console.log(w); // 3
var a = 1;
var b = 2;
function fun4() {
// 字母c没有加var关键字定义,所以他就变为了全局变量
c = 3;
var b = 4;
b++;
console.log(b); // 5
c++;
}
fun4()
console.log(b); // 2
// 在函数外部,是可以访问变量c的
console.log(c); // 4
</script>
</body>
</html>
1-9、闭包
什么是闭包
- JavaScript中函数会产生
闭包(closure)。闭包是函数本身和该函数声明是所处的环境状态的组合
函数能够“记忆住”其定义时所处的环境,即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。
观察闭包现象
- 在JavaScript中,
每次创建函数时都会创建闭包。 - 但是,闭包特性往往需要将函数“换一个地方”执行,才能被观察出来
闭包非常实用
- 闭包很有用,因为
它允许我们将数据与操作该数据的函数关联起来。这与“面向对象编程”有少许相似之处。 - 闭包的功能:记忆性、模拟私有变量
闭包用途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>1-9、闭包</title>
</head>
<body>
<script>
// 创建一个函数
function fun() {
// 定义局部变量
var name = "慕课网";
function innerFun() {
alert(name);
}
// 返回一个局部函数
return innerFun // 返回了内部函数
}
// 调用外部函数,就能得到内部函数,用inn来接收
var inn = fun(); // 内部函数被移动到了外部执行
// 定义一个全局变量
var name = "ABC";
// 执行inn函数,就相当于再fun函数的外部,执行了内部函数
inn()
</script>
</body>
</html>