函数
函数概述
① 什么是函数
1. 函数具有某种特定功能的代码块。
2. 函数是JS中一种数据类型,属于对象类型,使用 typeof 判断可以得到 function
function String Number array
② 函数的作用
提高代码的重用性
③ 函数的组成
函数名: 函数名就是变量名; 变量的值是函数类型(function)的数据,可以称该变量是函数名。
函数体: 函数中的代码块。
参数: 用于像函数内传递数据,分为形参和实参。
返回值: 作为函数的计算结果,是函数调用表达式的值。
调用函数:
函数名();
创建函数的四种方式
① function 关键字方式
function 函数名(参数列表) {
语句...;
}
② 表达式方式
var 函数名 = function(参数列表) {
语句...;
}
③ Function 函数方式(了解)
用函数创建函数
var 函数名 = Function('函数体语句;')
var 函数名 = Function('参数1', '参数2', '参数3','函数体语句;')
④ Function 构造函数方式(了解)
var 函数名 = new Function('函数体语句;')
var 函数名 = new Function('参数1', '参数2', '参数3','函数体语句;')
prompt数据类型转换
var num = +prompt('请输入一个数');prompt前加+能把变量转化为数字型
函数的调用和返回值
① 函数调用
函数名后面跟上小括号才是调用,函数体语句才能执行
函数名后面没有小括号,在使用该变量的值
② 返回值
1. 函数调用表达式的值是返回值
2. 如何在函数中设置返回值
① 使用 return 关键字可以设置返回值,return 右边需要表达式,表达式的值就是返回的值
② 函数体中没有return,或者return后边是空的,表示该函数没有返回值,undefined
③ return 表示函数的结束,一旦执行到 return,return 下面的代码将不再执行。
3. 没有返回值的函数,函数调用表达式可以自动得到undefined
输出和返回值不是一个东西
调用和执行以及返回值,执行到就一定会执行。
函数的参数
① 形参和实参
形参: 创建函数时候使用,形参就是没有赋值的变量,形参只能在函数内部使用。
实参: 调用函数时通过实参向函数传递数据,实参用于给对应的形参赋值,实参的形式可以是变量、直接量、表达式。
② 形参和实参的数量问题
1. 如果实参数量>形参数量,实参按照顺序给形参赋值,多出的实参没有作用
2. 如果实参数量<形参数量,实参按照顺序给形参赋值,后面的形参没有被赋值,使用的时候自动undefined
③ 形参的默认值(可选参数)
ES5 设置形参默认值的方式:
function 函数名(参数1,参数2) {
if (参数2 === undefined) {
参数2 = 默认值;
}
}
有默认值的参数请放在后面!
ES6 设置形参默认值的方式:
function 函数名(参数1,参数2=默认值) {
}
有默认值的参数请放在后面!
④ arguments
1. arguments 是系统创建的变量,只能在函数中使用,函数内的变量,每一个函数中的argument都不一样。
2. arguments 的值是一个伪数组,由调用函数时所传递的实参组成
3. 可以使用 arguments 实现可变参数数量的函数,可用于获取所有可变数量的参数
// 创建函数 该函数计算所有参数的和
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);
作用域
① 变量的作用域
1. 变量的作用域指变量的可作用范围,一个变量只能在作用域内才可以使用
2. 根据作用域可以将变量分为全局变量和局部变量
① 全局变量: 在函数外面创建的变量就是全局变量,作用域范围是全局
② 局部变量: 在函数里面创建的变量就是局部变量,作用域范围是所在的函数
注意:
- 在函数内,不使用var关键字创建的变量也是全局变量,不建议这么做!
- 函数内的形参、argument 都是局部变量。
② 作用域链
寻找作用域链
作用域链式如何产生的?
在函数体代码中,也可以创建函数,函数嵌套声明,形成了作用域链
作用域链描述变量查找的过程:
1. 当使用变量的时候,先从本作用域中查找,如果有到此为止
2. 如果本作用域中没有创建该变量,再去上层作用域找,如果有到此为止,如果没有继续向上找,直到全局
3. 如果全局也没有创建该变量,报错!
从定义变量的角度看作用域链:
变量创建之后,可以在本作用域被使用,也可以在下层作用域以及更下层被使用。
变量提升
① 变量提升
1. 全局代码执行之前会预处理, 查找全局代码中的var关键字,提前创建好变量,不赋值; 当正式执行到变量声明语句的时候,仅仅进行赋值操作。
2. 函数调用的时候,执行函数体语句前也会预处理, 查找函数代码中的var关键字,提前创建好变量,不赋值;当正式执行到变量声明语句的时候,仅仅进行赋值操作。
将变量的创建提升到了所在作用域的最前面!
//代码正式执行之前,进行预处理,会查找全局代码中的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();
② 函数提升
1. 全局代码执行之前会预处理, 查找全局代码中的function关键字,提前创建好变量并赋值; 当正式执行到函数声明语句的时候,直接跳过。
2. 函数调用的时候,执行函数体语句前也会预处理, 查找函数代码中的function关键字,提前创建好变量并赋值; 当正式执行到函数声明语句的时候,直接跳过。
只有 function 关键字创建的函数才按照函数提升的规则;
如果是其他方式,提升规则与变量一致,var的规则。
// 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
//使用变量
console.log(address); // 输出值为函数本身,原因:函数预处理,创建变量并且赋值。
address(); //输出值为我是Hanser,原因:函数预处理,创建变量并且赋值。
// 使用var创建全局变量,预处理变量提升,创建变量但不赋值
var address = '北京'; // 对address变量进行赋值,同时更改address的数据类型为string
//执行到这里,直接跳过
// 创建函数
function address(){
console.log('我是Hanser');
}
//调用函数
console.log(address); //输出为 北京,
address(); //报错,因为address不是函数类型无法使用()形式。
函数的嵌套
函数体内是可以再嵌套函数的
匿名函数
1. 匿名函数就是没有名字的函数,是函数的直接量形式
2. 匿名函数适合用于立即调用的函数和回调函数,只用作一次
立即调用的函数 (IIFE )
Immediately Invoked Function Expression,简称 IIFE,译为“立即调用的函数表达式”。 IIFE 主要为了创建一个局部的作用域,避免全局变量污染。
(function() {
var address = '上海';
console.log('我是匿名的立即调用的函数!', address);
})();
两个连续的自调用函数,之间必须加分号,告诉浏览器是不同的函数,否则会有语法错误。
或者,在后面的自调用函数前加 !
等没有副作用的一元运算符。
自调用函数的作用
1)减少全局变量的使用,把自己代码或者每个特效的代码写到一个自调用函数中, 防止外部命名空间污染(全局变量污染)
2)隐藏内部代码暴露接口,实现模块化开发。
回调函数 (callback)
① 什么是回调函数
满足以下三个条件的函数就是回调函数:
1)函数是我定义的。
2)我没有调用(没有直接调用)。
3)函数最终执行了。
②函数调用的条件
函数名后边加一个();
③ 回调函数的使用场景
1. 数组的一些方法需要回调函数当参数,如 forEach遍历、sort排序、filter、map、reduce 等等
2. 定时器的回调函数
3. DOM事件的回调函数
4. Ajax 的回调函数
5. Promise 的回调函数
...
React VUE 生命周期的钩子函数
高阶函数:需要回调函数作为参数的函数
大部分回调函数的形式都是作为其他函数的参数!
常见使用回调函数的地方
作为其他函数的参数,函数的参数可以是个函数,而作为参数的那个函数就被称作回调函数。
2)事件函数
3)定时器函数
4)ajax 的回调函数
4)生命周期钩子函数
注意:
匿名函数很适合做回调函数,也就是回调函数很多时候是个匿名函数。
④forEach
var namelist = ['Hanser','yousa','aqua'];
//forEach 遍历属组中的元素
namelist.forEach(function(){});
//使用forEach遍历属组
namelist.forEach(function(item,index){
console.log(item,index);
})
大部分的匿名函数多被用作回调函数。
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 内部调用回调函数的时候,还可以给回调函数传个实参。
// 声明函数,参数的类型要求是函数
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)});
⑤高阶函数
判断是高阶函数条件:
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的值是正数时,两个变量不变
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);
递归函数
① 什么是递归函数
1. 如果函数体代码中自己调用自己,称为递归调用
2. 存在递归调用的函数就是递归函数
② 递归函数成功的条件
1. 需要要明确的结束递归的条件
2. 随着递归调用次数增加,条件要趋向于结束递归js
③ 递归函数的缺点
执行效率较低,如果可以使用循环实现,首选循环
函数递归调用很容易发生灾难(内存泄漏)而调用失败。
函数递归调用效率不高,能不用就不用。
④ 递归函数应用场景
1. 使用递归函数处理后端数据
后端的操作中有些场景必须要递归函数来完成,如:
1)删除文件夹以及里面的内容,需要递归删除(操作系统的原始接口只能删除文件和空文件夹)
2)复制文件夹以及里面的内容。
3)剪切文件夹以及里面的内容。
/*
* 实现某个数字的阶乘
* */
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
* */
//创建新的属组
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();
面向对象的三个特点
1.封装
2.继承机制=原型机制
3.多态
js基于对象的浏览器编程语言