JavaScript 函数
为什么要有函数?
- 如果要在多个地方求某个数的约数个数,应该怎么做
函数的概念
- 函数(function),也叫作功能、方法,函数可以将一段代码一起封装起来,被封装起来的函数具备某一项特殊的功能,内部封装的一段代码作为一个完整的结构体,要执行就都执行,要不执行就都不执行。
- 函数的作用就是封装一段代码,将来可以重复使用
函数声明
- 函数声明又叫函数定义,函数必须先定义然后才能使用
- 如果没有定义函数直接使用,会出现一个引用错误
- 函数声明语法:
function 函数名 (参数) {
封装的结构体;
}
特点:函数声明的时候,函数体并不会执行,只有当函数被调用的时候才会执行
函数调用
- 调用方法:函数名();
- 函数调用也叫作函数执行,调用时会将函数内部封装的所有的结构体的代码立即执行
- 函数内部语句执行的位置,与函数定义的位置无关,与函数调用位置有关
- 函数可以一次调用,多次执行
函数的参数1
- 我们希望函数执行结果不是一成不变的,可以根据自定义的内容发生一些变化
- 函数预留了一个接口,专门用于让用户自定义内容,使函数发生一些执行效果变化
- 接口:就是函数的参数,函数参数的本质就是变量,可以接收任意类型的数据,导致函数执行结果根据参数不同,结果也不同
- 一个函数可以设置 0 个或者多个参数,参数之间用逗号分隔
案例:累加求和函数
// 函数:封装了一段可以重复调用执行的代码块,通过代码块可以实现大量代码的重复使用
// 1、声明一个累加求和函数
// num1~num2之间所有数之和
function getSum(num1,num2) {
var sum = 0;
for (var i = num1; i <= num2; i++) {
sum += i;
}
console.log(sum);
}
// 2、调用函数
getSum(1,100);
getSum(11,1100);
getSum(321,1212);
函数的参数2
- 函数的参数根据书写位置不同,名称也不相同
- 形式参数:定义的 () 内部的参数,叫做形式参数,本质是变量,可以接收实际参数传递过来的数据。简称形参
- 实际参数:调用的 () 内部的参数,叫做实际参数,本质就是传递的各种类型的数据,传递给每个形参,简称实参
- 函数执行过程,伴随传参的过程
函数的参数优点
- 不论使用自己封装的函数,还是其他人封装的函数,只需要知道传递什么参数,执行什么功能,没必要知道内部的结构是什么
- 一般自己封装的函数或者其他人封装的函数需要有一个 API 接口说明,告诉用户参数需要传递什么类型的数据,实现什么功能
函数的返回值
- 函数能够通过参数接收数据,也能够将函数执行结果返回一个值
- 利用函数内部的一个 return 的关键字设置函数的返回值
- 作用 1 :函数内部如果结构体执行到一个 return 的关键字,会立即停止后面代码的执行
- 作用 2 : 可以在 return 关键字后面添加空格,空格后面任意定义一个数据字面量或者表达式,函数在执行完自身功能之后,整体会被 return 矮化成一个表达式,表达式必须求出一个值继续可以参加程序,表达式的值就是 return 后面的数据
案例:求和函数
var num1 = Number(prompt("请输入第一个数:"));
var num2 = Number(prompt("请输入第二个数:"));
function sum(a,b) {
return a + b;
}
console.log(sum(num1,num2));
函数的返回值应用
- 函数如果有返回值,执行结果可以当成普通函数参与程序
- 函数如果有返回值,可以作为一个普通数据赋值给一个变量,甚至赋值给其他函数的实际参数
- 注意:如果函数没有设置 return 语句,那么函数有默认的返回值 undefined ; 如果函数使用 return 语句,但是 return 后面没有任何值,那么函数的返回值也是 undefined
// 1、return 终止函数
function getSum(num1, num2) {
return num1 + num2;
console.log('return除了返回值还起到终止函数的作用,所以在return后面的代码均不执行!');
}
console.log(getSum(10, 20));
// 2、return 只能返回一个值
function fn(num1,num2) {
return num1, num2; //返回的结果是最后一个值
}
console.log(fn(10, 20));
// 3、 我们求任意两个数 加减乘除 的结果
function getResult(num1, num2) {
return ['求和:' + (num1 + num2), '求差:' + (num1 - num2), '求积:' + (num1 * num2), '求商:' + (num1 / num2)];
}
re = getResult(10, 20);
console.log(re);
// 想要输出多个值可以利用数组
// 4、我们的函数如果有return 则返回的是 return后面的值 如果函数没有 return 则返回undefined
函数表达式
- 函数表达式是函数定义的另外一种方式
- 定义方法:就是将函数的定义、匿名函数赋值给一个变量
- 函数定义赋值给一个变量,相当于将函数整体矮化成了表达式
- 匿名函数:函数没有函数名
- 调用函数表达式,方法是给变量名加 () 执行,不能使用函数名加 () 执行
// 函数的两种声明方式
// 1、利用函数关键字自定义函数(命名函数)
function fn() {
}
fn();
// 2、函数表达式(匿名函数)
// var 变量名 = function() {};
var fun = function(aru) {
console.log('我是函数表达式');
console.log(aru);
}
fun('我是默默!');
// (1)fun是变量名 不是函数名
// (2)函数表达式声明方式跟声明变量差不多,只不过变量里面存的是值 而 函数表达式里面存的是函数
函数数据类型
- 函数是一种独特的数据类型 function -- 是 object 数据类型的一种,函数数据类型
- 由于函数是一种数据类型,可以参与其他程序
- 例如,可以把函数作为另外一个函数的参数,在另一个函数中调用
- 或者,可以把函数作为返回值从函数内部返回
// 函数是一种数据类型,可以当成其他函数的参数
setInterval(function() {
console.log(1);
},1000)
//每隔 1s 输出一个 1
arguments 对象
- JavaScript 中,arguments 对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。arguments 是一个伪数组,因此及可以进行遍历
- 函数的实参个数和形参个数可以不一致,所有的实参都会存储在函数内部的 arguments 类数组对象中
/*
当我们不确定有多少个参数传递的时候 可以用arguments来获取 在JS中 arguments其实是当前函数的
一个内置对象 所有函数都内置了一个arguments对象 arguments对象中存储了传递的所有实参
*/
// arguments的使用
function fn() {
console.log(arguments); //里面存储了所有的实参
}
fn(1, 2, 3);
/*
arguments展示形式是一个伪数组,因此可以进行遍历,伪数组有如下特点:
具有length属性
按照索引方式存储数据
不具有数组的 push pop 等方法
*/
案例:利用 arguments 求一组数最大值
function getMax() {
var max = arguments[0];
var arry = arguments;
for (var i = 0; i < arry.length; i++) {
if (arry[i] > max) {
max = arry[i];
}
}
return max;
}
console.log(getMax(1, 2, 5, 11, 3));
console.log(getMax(1, 2, 5, 11, 3, 100, 111));
console.log(getMax(1, 2, 5, 11, 3, 1212, 22, 222, 2333));
函数递归
- 函数内部可以通过函数名调用函数自身的方式,就是函数递归现象
- 递归的次数太多容易出现错误:超出计算机的计算最大能力
- 更多时候,使用递归去解决一些数学的现象
- 例如可以输出斐波那契数列的某一项的值
// 函数,如果 传入的参数1,返回1,如果传入的是 1 以上的数字,让他返回参数 + 函数调用上一项
function fun (a) {
if (a === 1) {
return 1;
} else {
return a + fun(a - 1);
}
}
// 调用函数
console.log(fun(1));
console.log(fun(2));
console.log(fun(3));
console.log(fun(100));
// 这样我们就用递归做出了 n 以内数累加求和的函数
案例:输出斐波那契数列任意项
// 斐波那契数列(每一项等于前两项之和 1,1,2,3,5,8,13,21,34,55 ···)
// 参数:正整数
// 返回值:对应的整数位置的斐波那契数列的值
function fibo(a) {
if (a === 1 || a === 2) {
return 1;
} else {
return fibo(a - 1) + fibo(a - 2);
}
}
console.log(fibo(1));
console.log(fibo(2));
console.log(fibo(3));
console.log(fibo(4));
作用域
- 作用域:变量可以起作用的范围
- 如果变量定义在一个函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域
- 任何一对花括号 {} 中的结构体都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域
- 在 es6 之前没有块级作用域的概念,只有函数作用域,现阶段可以认为 JavaScript 没有块级作用域
// js现阶段没有块级作用域 js作用域:局部作用域 全局作用域 现阶段我们js没有块级作用域
// js在ES6的时候新增块级作用域的概念
// 块级作用域就是{}中的区域
if (3 > 2) {
var num1 = 10;
}
console.log(num1);//10
/*
说明js没有块级作用域,外部可以调用{}内声明的变量
*/
全局变量和局部变量
- 局部:变量:定义在函数内部的变量,只能在函数作用域被访问到,在外面没有定义的
- 全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局,
- 在整个 js 程序任意位置都能被访问到
- 局部变量退出作用域之后会被销毁,全局变量关闭页面或浏览器才会销毁
函数参数也是局部变量
- 函数的参数本质是一个变量,也有自己的作用域,函数的参数也是属于函数自己内部的局部变量,只能在函数内部被使用,在函数外面没有定义
函数的作用域
- 函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了作用域不能被访问
- 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写
作用域链
- 只有函数可以制造作用域结构,那么只要是代码,就至少有一个作用域,即全局作用域。凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用领域
- 将这样的所有的作用域列出来,可以有一个结构:函数内指向函数外的链式结构。就称作作用域链
遮蔽小于效应
- 程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名字的变量,一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变量定义会按照顺序从本层往外依次查找,直到第一个变量定义。整个过程中会发生内层变量的效果,叫做“遮蔽效应”
/*
1、只要是代码就至少有一个作用域
2、写在函数内部的局部作用域
3、如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
4、根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,
就被称作作用域链
*/
// 作用域链 : 内部函数访问外部函数的变量 采取的是链式查找的方式来决定取哪个值 这种结构我们称作用域链
// 就近原则
var num = 10;
function fn() {//外部函数
var num = 20;
function fun() {//内部函数
console.log(num);//20
}
fun();
}
fn();
不写 var 关键字的影响
- 在函数内部想要定义新的变量,如果不使用关键字 var ,相当于定义的全局变量。如果全局变量也有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量
- 注意:每次定义变量时都必须写 var 关键字,否则就会定义在全局,可能污染全局
function fn() {
a = 2;
}
console.log(a);//2
预解析
- JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行 JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程
- 预解析过程:
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用
- 先提升 var ,再提升 function
- Javascript 的执行过程:在预解析之后,根据新的代码顺序,从上往下按照既定规律执行 js 代码
变量声明提升
- 在与解析过程中,所有定义的变量,都会将声明的过程提升到所在的作用域最上面,在将来的代码执行过程中,按照先后顺序会先执行被提升的声明变量过程
- 提升过程中,只提升声明过程,不提升变量赋值,相当于变量定义未赋值,变量内存储 undefined 值
- 因此,在 js 中会出现一种现象,在前面调用后定义的变量,不会报错,只会使用 undefined值
函数声明提升
- 在与解析过程中,所有定义的函数,都会将声明的过程提升到所在的作用域最上面,在将来的代码执行过程中,按照先后顺序会先执行被提升的函数声明过程
- 在预解析之后的代码执行过程中,函数定义过程已经在最开始就会执行,一旦函数定义成功,后续就可以直接调用函数
- 因此,在 js 中会出现一种特殊现象,在前面调用后定义的函数,不会报错,而且能正常执行函数内部的代码(如果使用 var 声明的函数,在定义函数之前调用函数,会直接报错)
/*
1、
console.log(num);报错
*/
// 2、
console.log(num);//undefined
var num = 10;
// 3、
fn();//11
function fn() {
console.log(11);
}
// 4、
/*
fun();//报错
var fun = function() {
console.log(22);
}
*/
/*
1、我们js引擎运行js 分为两步: 预解析 代码执行
(1) 预解析 js引擎会把js 里面所有的 var 和 function 提升到当前作用域的最前面
(2) 代码执行 按照代码书写的顺序从上往下执行
2、预解析分为 变量预解析(变量提升) 函数与解析(函数提升)
(1) 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作
(2) 函数提升 把所有的函数声明提升到当前作用域的最前边 不调用函数
*/
提升顺序
- 预解析过程中,先提升 var 变量声明,在提升 function 函数声明
- 假设出现变量名和函数名相同,那么后提升的函数名标识符会覆盖先提升的变量名,那么在后续代码种出现调用标识符时,内部是函数的定义过程,而不是 undefined
- 如果调用标识符的过程在源代码函数和变量定义的后面,相当于函数名覆盖了一次变量名,结果在执行到变量赋值时,又被新值覆盖了函数的值,那么在后面再次调用标识符,用的就是变量存的新值
- 建议:不要书写相同的标识符给变量名或函数名,避免出现覆盖
函数声明提升的应用
- 函数声明提升可以用于调整代码的顺序,将大段的定义过程放到代码最后,但是不影响代码执行效果
IIFE 自调用函数
- IIFE:immediately-invoked function expression,叫做即时调用的函数表达式,也叫做自调用函数表达式,表示函数在自定义时就立即调用
- 函数调用方式:函数名或函数表达式的变量名后面加 () 运算符
- 函数名定义的形式不能实现立即执行自调用,函数使用函数表达式形式可以实现立即执行,原因是因为函数表达式定义过程中,将函数矮化成表达式,后面加 () 运算符就可以立即执行
- 启发:如果想实现 IIFE ,可以想办法将函数矮化成表达式
// 关键字定义的方式,不能立即执行
// function fun() {
// console.log(1);
// }();
// 函数表达式,可以立即调用
var foo = function () {
console.log(2);
}();
- 函数矮化成表达式,就可以实现自调用
- 函数矮化成表达式的方法,可以让函数参与一些运算,也就是说给函数前面加一些运算符。
数学运算符:+ - ()
逻辑运算符:!非运算
- IIFE 结构可以封住函数的作用域,在结构外面是不能调用函数的
- IIFE 最常用的时 () 运算符,而且函数可以不写函数名,使用匿名函数
// 通过前面添加操作符可以将我们的函数矮化成表达式
+ function fun() {
console.log(1);
}();
- function fun() {
console.log(1);
}();
(function fun() {
console.log(1);
})();
!function fun() {
console.log(1);
}();