Skip to content

函数

函数概述

① 什么是函数

js
1. 函数具有某种特定功能的代码块。
2. 函数是JS中一种数据类型,属于对象类型,使用 typeof 判断可以得到 function
function String Number array

② 函数的作用

js
提高代码的重用性

③ 函数的组成

函数名: 函数名就是变量名; 变量的值是函数类型(function)的数据,可以称该变量是函数名。

函数体: 函数中的代码块。

参数: 用于像函数内传递数据,分为形参和实参。

返回值: 作为函数的计算结果,是函数调用表达式的值。

调用函数:

js
函数名();

创建函数的四种方式

① function 关键字方式

js
function 函数名(参数列表) {
    语句...;
}

② 表达式方式

js
var 函数名 = function参数列表) {
    语句...;
}

③ Function 函数方式(了解)

用函数创建函数

js
var 函数名 = Function('函数体语句;')
var 函数名 = Function('参数1', '参数2', '参数3','函数体语句;')

④ Function 构造函数方式(了解)

js
var 函数名 = new Function('函数体语句;')
var 函数名 = new Function('参数1', '参数2', '参数3','函数体语句;')

prompt数据类型转换

var num = +prompt('请输入一个数');prompt前加+能把变量转化为数字型

函数的调用和返回值

① 函数调用

js
函数名后面跟上小括号才是调用,函数体语句才能执行
函数名后面没有小括号,在使用该变量的值

② 返回值

js
1. 函数调用表达式的值是返回值
2. 如何在函数中设置返回值
   ① 使用 return 关键字可以设置返回值,return 右边需要表达式,表达式的值就是返回的值
   ② 函数体中没有return,或者return后边是空的,表示该函数没有返回值,undefined
return 表示函数的结束,一旦执行到 return,return 下面的代码将不再执行。
3. 没有返回值的函数,函数调用表达式可以自动得到undefined

输出和返回值不是一个东西

调用和执行以及返回值,执行到就一定会执行。

函数的参数

① 形参和实参

形参: 创建函数时候使用,形参就是没有赋值的变量,形参只能在函数内部使用。

实参: 调用函数时通过实参向函数传递数据,实参用于给对应的形参赋值,实参的形式可以是变量、直接量、表达式。

② 形参和实参的数量问题

js
1. 如果实参数量>形参数量,实参按照顺序给形参赋值,多出的实参没有作用
2. 如果实参数量<形参数量,实参按照顺序给形参赋值,后面的形参没有被赋值,使用的时候自动undefined

③ 形参的默认值(可选参数)

ES5 设置形参默认值的方式:

js
function 函数名(参数1参数2) {
	if (参数2 === undefined) {
    	参数2 = 默认值;
    }
}

有默认值的参数请放在后面!

ES6 设置形参默认值的方式:

js
function 函数名(参数1参数2=默认值) {
    
}

有默认值的参数请放在后面!

④ arguments

js
1. arguments 是系统创建的变量,只能在函数中使用,函数内的变量,每一个函数中的argument都不一样。
2. arguments 的值是一个伪数组,由调用函数时所传递的实参组成
3. 可以使用 arguments 实现可变参数数量的函数,可用于获取所有可变数量的参数
js
// 创建函数 该函数计算所有参数的和
function sum() {
    // 定义变量 记录和
    var res = 0;
    // 遍历所有的参数
    for (var i = 0; i < arguments.length; i ++) {
        res += arguments[i];
    }
    // 返回计算结果
    return res;
}
sum(1,2,3,4,5,6,7,8);

作用域

① 变量的作用域

js
1. 变量的作用域指变量的可作用范围,一个变量只能在作用域内才可以使用
2. 根据作用域可以将变量分为全局变量和局部变量
   ① 全局变量: 在函数外面创建的变量就是全局变量,作用域范围是全局
   ② 局部变量: 在函数里面创建的变量就是局部变量,作用域范围是所在的函数

注意:

  1. 在函数内,不使用var关键字创建的变量也是全局变量,不建议这么做!
  2. 函数内的形参、argument 都是局部变量。

② 作用域链

寻找作用域链

作用域链式如何产生的?

js
在函数体代码中,也可以创建函数,函数嵌套声明,形成了作用域链

作用域链描述变量查找的过程:

json
1. 当使用变量的时候,先从本作用域中查找,如果有到此为止
2. 如果本作用域中没有创建该变量,再去上层作用域找,如果有到此为止,如果没有继续向上找,直到全局
3. 如果全局也没有创建该变量,报错!

从定义变量的角度看作用域链:

js
变量创建之后,可以在本作用域被使用,也可以在下层作用域以及更下层被使用。

image-20240311114709761

变量提升

① 变量提升

js
1. 全局代码执行之前会预处理, 查找全局代码中的var关键字,提前创建好变量,不赋值; 当正式执行到变量声明语句的时候,仅仅进行赋值操作。
2. 函数调用的时候,执行函数体语句前也会预处理, 查找函数代码中的var关键字,提前创建好变量,不赋值;当正式执行到变量声明语句的时候,仅仅进行赋值操作。
js
将变量的创建提升到了所在作用域的最前面!
js
//代码正式执行之前,进行预处理,会查找全局代码中的var关键字,提前将变量创建好,不赋值。
console.log(address); // underfined,按照常理逻辑,这里应该报错,原因来自变量提升
// 正式执行到变量声明语句时,仅仅进行赋值操作
var address = '上海'; // 创建变量
age = 10; //不带关键字var,不对变量进行提升。
console.log(address); // 上海


//创建函数
function func(){
    console.log(num);
    // 正式执行到变量创建语句时 才赋值
    var num = 100;
    console.log(num);
}
//函数调用的时候,在正式执行之前也会进行预处理,会查找函数代码中的var关键字,提前将变量创建好,不赋值。
func();

② 函数提升

js
1. 全局代码执行之前会预处理, 查找全局代码中的function关键字,提前创建好变量并赋值; 当正式执行到函数声明语句的时候,直接跳过。
2. 函数调用的时候,执行函数体语句前也会预处理, 查找函数代码中的function关键字,提前创建好变量并赋值; 当正式执行到函数声明语句的时候,直接跳过。

只有 function 关键字创建的函数才按照函数提升的规则;

如果是其他方式,提升规则与变量一致,var的规则。

js
// function关键字 声明的变量 预处理阶段,创建并赋值--许多开发者所用
// 使用变量
console.log(func01); //输出 func01函数体
func01(); //输出 func01
console.log(func02); // 输出 undefined,只有数据类型是函数式才可以用()

// function关键字创建函数
// 正式执行到该语句的时候,什么都不干,直接跳过
function func01(){
    console.log('func01');
}

//var关键字创建函数
var func02 = function(){
    console.log('func02');
}
//调用函数
console.log(func02); //输出 func02 函数体本身
func02(); //输出 func02

预解析

变量和函数之所以会提升,是因为程序在代码执行之前会先进行预解析

与解析遵循如下规则:

  • 预解析先去解析函数声明定义的函数,整体会被提升。
  • 再去解析带 var 的变量。
  • 函数重名会覆盖,变量重名会忽略。
  • 变量如果不带var,变量是不会进行预解析的;只有带var的变量才会进行预解析。
  • 表达式方式和构造函数方式定义的函数也是当做变量去解析。

function与var

js
//使用变量
console.log(address); // 输出值为函数本身,原因:函数预处理,创建变量并且赋值。
address(); //输出值为我是Hanser,原因:函数预处理,创建变量并且赋值。


// 使用var创建全局变量,预处理变量提升,创建变量但不赋值
var address = '北京'; // 对address变量进行赋值,同时更改address的数据类型为string


//执行到这里,直接跳过
// 创建函数
function address(){
    console.log('我是Hanser');
}

//调用函数
console.log(address); //输出为 北京,
address(); //报错,因为address不是函数类型无法使用()形式。

函数的嵌套

函数体内是可以再嵌套函数的

匿名函数

js
1. 匿名函数就是没有名字的函数,是函数的直接量形式
2. 匿名函数适合用于立即调用的函数和回调函数,只用作一次

立即调用的函数 (IIFE )

Immediately Invoked Function Expression,简称 IIFE,译为“立即调用的函数表达式”。 IIFE 主要为了创建一个局部的作用域,避免全局变量污染。

js
 (function() {
     var address = '上海';
     console.log('我是匿名的立即调用的函数!', address);
 })();

两个连续的自调用函数,之间必须加分号,告诉浏览器是不同的函数,否则会有语法错误。

或者,在后面的自调用函数前加 ! 等没有副作用的一元运算符。

自调用函数的作用

1)减少全局变量的使用,把自己代码或者每个特效的代码写到一个自调用函数中, 防止外部命名空间污染(全局变量污染)

2)隐藏内部代码暴露接口,实现模块化开发。

回调函数 (callback)

① 什么是回调函数

满足以下三个条件的函数就是回调函数:

js
1)函数是我定义的。
2)我没有调用(没有直接调用)。
3)函数最终执行了。

②函数调用的条件

js
函数名后边加一个();

③ 回调函数的使用场景

js
1. 数组的一些方法需要回调函数当参数,如 forEach遍历、sort排序、filter、map、reduce 等等
2. 定时器的回调函数
3. DOM事件的回调函数
4. Ajax 的回调函数
5. Promise 的回调函数
...
React VUE 生命周期的钩子函数

高阶函数:需要回调函数作为参数的函数

大部分回调函数的形式都是作为其他函数的参数!

常见使用回调函数的地方

作为其他函数的参数,函数的参数可以是个函数,而作为参数的那个函数就被称作回调函数

2)事件函数

3)定时器函数

4)ajax 的回调函数

4)生命周期钩子函数

注意:

匿名函数很适合做回调函数,也就是回调函数很多时候是个匿名函数。

④forEach

js
var namelist = ['Hanser','yousa','aqua'];
//forEach 遍历属组中的元素
namelist.forEach(function(){});
//使用forEach遍历属组
namelist.forEach(function(item,index){
    console.log(item,index);
})

大部分的匿名函数多被用作回调函数。

js
delete Arrar[0]; 
delete可以删除对象中的属性,
也可以删除数组元素,删除数组元素时,只删除数组中的元素,并且用空数据填充索引,后续元素索引不会变化。
forEach遍历时会自动跳过索引数据为空的元素。

ES5 方法

方法名含义
forEach(callback[, thisArg])为数组中的每个元素执行一次回调函数,用于数组遍历
filter(callback[, thisArg])将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回,用于数组过滤。
map(callback[, thisArg])返回一个由回调函数的返回值组成的新数组。
every(callback[, thisArg])如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。
some(callback[, thisArg])如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。
reduce(callback[, initialValue])从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
reduceRight(callback[, initialValue])从右到左为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
indexOf(searchElement[, fromIndex])返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。
lastIndexOf(searchElement[, fromIndex])返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

实现一个回调函数

1)声明一个函数 fn,函数的参数类型要求是函数。

2)fn 的函数体内,调用传递进来的回调函数。

3)fn 内部调用回调函数的时候,还可以给回调函数传个实参。

js
// 声明函数,参数的类型要求是函数
function fun(callback) {
  //使用一下参数 调用参数 调用回调韩
  callback();
}
// 调用fun,传一个匿名函数进去
fun(function(){
  console.log('啊,作为一个回调函数,我被调用了');
});

// -------------------------------------

// 声明函数,参数的类型要求是函数 
function demo(callback) {
  callback(100, 200); // 调用回调函数的时候,还给回调函数传个实参
}
// 接收匿名函数作为参数,回调函数自己得定义两个形参
demo(function(a,b){
  console.log(a, b);
});
//接收非匿名函数作为参数
demo(fn1);
function fn1(a, b) {
  console.log(a+b);
}

/**
 * @param num1
 * @param num2
 * @param call
*/
function progress(num1, num2, call) {
  call(num1, num2);
}

progress(100, 200, function(a,b){console.log(a+b)});
progress(100, 200, function(a,b){console.log(a*b)});

⑤高阶函数

js
判断是高阶函数条件:
1、参数是函数
2、返回值是一个函数

 // -------------------------------------------------
// 高阶函数 返回值是一个函数
function func() {
    var res = 100 * 2;
    return function() {
        console.log(res * 2);
    }
}
func()();
console.log('');


// -------------------------------------------------
// 高阶函数 参数是个函数
function func01(cb) {
    cb(100, 200);
}
func01(function(a,b) {
    console.log('我是回调函数!', a, b);
});
console.log('');

// -------------------------------------------------
// 高阶函数 参数是函数 返回值也是函数
function func02(num01, num02, cb) {
    return cb(num02, num01);
}
var res = func02(100, 200, function(a,b) {
    return a - b;
});
console.log(res);
console.log('');


// -------------------------------------------------
// 回调函数和作用域
// 作用域于调用位置无关
var num = 10;
function fn01(fn) {
    var num = 20;
    fn();
}
function fn02() {
    console.log(num);  // 10
}
fn01(fn02);

再复杂的代码都是由简单的代码组成的。

⑥sort方法

sort方法的特点,当return的是负数时,两个变量交换位置,当return的值是正数时,两个变量不变

js
var nums = [23,101,12,45,89,56,78,18];
console.log(nums);
console.log('');

// 使用sort方法
// nums.sort();
// console.log(nums);
// console.log('');

// 使用sort方法 自定义排序规则 升序
nums.sort(function(next, prev){
    // if (prev > next) {
    //     return -100;
    // }
    return next - prev;
});
console.log(nums);
console.log('');


// 使用sort方法 降序
nums.sort(function(next, prev) {
    return prev - next;
});
console.log(nums);

递归函数

① 什么是递归函数

js
1. 如果函数体代码中自己调用自己,称为递归调用
2. 存在递归调用的函数就是递归函数

② 递归函数成功的条件

js
1. 需要要明确的结束递归的条件
2. 随着递归调用次数增加,条件要趋向于结束递归js

③ 递归函数的缺点

js
执行效率较低,如果可以使用循环实现,首选循环

函数递归调用很容易发生灾难(内存泄漏)而调用失败。
函数递归调用效率不高,能不用就不用。

④ 递归函数应用场景

js
1. 使用递归函数处理后端数据

后端的操作中有些场景必须要递归函数来完成,如:

1)删除文件夹以及里面的内容,需要递归删除(操作系统的原始接口只能删除文件和空文件夹)

2)复制文件夹以及里面的内容。

3)剪切文件夹以及里面的内容。
js
/*
  * 实现某个数字的阶乘
  * */
function fn(n) {
  // 当 n<=1 的时候,就结束了,不再进行递归了。
  if (n <= 1) {
    return 1;
  }
  return n * fn(n-1);
}

console.log(fn(3));


/**
 *  调用 fn(3)
 *       3 * fn(2)
 *       调用fn(2)
 *           2 * fn(1)  结果2
 *           调用fn(1)
 *               return 1
 *           调用完fn(1)
 *        调用完fn(2)
 *   调用完 fn(3)  结果6
 
 * */
js
//创建新的属组
var newNums = [];
//使用递归函数遍历数组
function flatArray(arr){
    //使用循环遍历
    for(var i = 0; i < arr.length;i++){
        if(arr[i] instanceof Array){
            //递归调用
            flatArray(arr[i]);
        }else{
            //将元素添加到新数组中
            newNums.push(arr[i]);
        }
    }
}
//调用函数
flatArray();

面向对象的三个特点

js
1.封装
2.继承机制=原型机制
3.多态

js基于对象的浏览器编程语言