3.2 函数(Function)
3.2.1 定义
- 一个对象
- 可重复执行的代码块
- 完成特定功能的一段代码
- 使用typeof检查一个函数对象时,会返回function
3.2.2 作用
- 部分代码使用次数过多,封装起来,在使用时直接调用即可,方便简单
3.2.3 特点
- 封装到函数中的代码不会立即执行
- 函数中的代码会在调用时才执行
- 调用函数 语法:函数对象()
- 调用函数时,函数中封装的代码会按照顺序执行
3.2.4 基本使用
- 不常用:var fun = new Function(“console.log(‘大家好, 我是函数!’);”);
- 常用
- 声明:function 函数名([形参1,形参2, …,形参N]){函数体}
- 调用:函数名()
3.2.5 常见声明方式
1.函数声明方式:
function 函数名([形参1,形参2,…]){函数体}
2.函数表达式声明方式:
var add= function(num1,num2){
return num1+num2;
};
3.使用Function构造函数(不推荐使用):
var add = new Function(‘num1’,‘num2’,‘return num1+num2’);
3.2.6 函数的参数
- 为什么要设置参数:增强函数的功能性与拓展性,便于交互
- 形参:占位用,无实际意义
function add(a,b){return a + b ;} // a, b 为形参 - 实参:实际参与运算的值
add(1,2); //1,2为实参 - 注意点:在其它语言中实参个数必须和形参个数一致,但是JavaScript中没有函数签名的概念,实参个数和形参个数可以不相等,实参若超过形参个数,超过的都会以 undefined 传递给函数
3.2.7 函数使用中的注意点
1.调用函数时,在()中指定实参, 实参将会赋值给函数中对应的形参
2.调用函数时解析器不会检查实参的类型, 所以开发中一般需要对参数进行类型的检查
3.函数的实参可以是任意的数据类型
4.调用函数时,解析器不会检查实参的数量, 多余实参不会被赋值, 如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
3.2.8 arguments对象
-
arguments对象是一个伪数组
arguments并不是一个数组,只是与数组相似, 除了拥有length属性,数组的所有属性和方法都不具备 -
arguments对象的使用
使用特殊对象 arguments时无需明确指出参数名,就可以访问它们.
例如:在write函数中,如果需要访问其第一个参数 words1 ,不用指明其具体参数名,用 arguments[0] 就可以访问这个值(该函数第一个参数的值)。function write() {
if (arguments[0] == “hello”) {
return;
}
alert(arguments[0]);
} -
用arguments对象检测参数个数
可以用 arguments 对象检测函数的参数个数,引用属性 arguments.length 即可
eg:
function judjenum() {
console.log(arguments.length);
}
judjenum("string", 45);
// 返回 2
judjenum();
// 返回 0
judjenum(12);
// 返回 1
- 注意点:
在其它语言中实参个数必须和形参个数一致,但是JavaScript中没有函数签名的概念,实参个数和形参个数可以不相等,实参若超过形参个数,超过的都会以 undefined 传递给函数 - 用 arguments 对象判断传递给函数的参数个数
用 arguments 对象判断传递给函数的参数个数,即:模拟函数重载
eg:
function doAdd() {
if(arguments.length == 1) {
alert(arguments[0] + 5);
} else if(arguments.length == 2) {
alert(arguments[0] + arguments[1]);
}
}
doAdd(10);
//输出 "15"
doAdd(40, 20);
//输出 "60"
- 用arguments对象比较形参与实参个数是否一致
eg:
unction sum(num1,num2){
console.log(arguments.length);
// 返回 2
console.log(sum.length);
// 返回 2
console.log(arguments.length == sum.length);
// 返回 true
}
sum(10,20);
- 用 arguments.callee属性可判断/返回当前函数
callee是arguments对象的一个属性,该属性是一个指针, 指向拥有这个arguments对象的函数
eg:
function sum(num1,num2){
console.log(arguments.callee);
}
sum(10,20);
3.2.9 length属性
- 函数体中,arguments.length表示传入函数的实参数量。
- 函数本身的length属性代表的是函数定义时给出的参数(形参)个数。
eg:
function a(b, c, d){
console.info(arguments.length)
}
console.info(a.length); // 3
a(1); //1
3.2.10 函数的返回值(return)
当一个函数被调用,通常会从函数的开始执行到结束。如果想提前结束该函数的执行可以使用return语句,return语句后面的所有语句将永远不会执行。
- 一般return用于返回结果
- 注意点:
1.如果函数没有显式的使用 return语句 ,那么函数有默认的返回值:undefined
2.如果函数使用 return语句,那么跟在return后面的值,就成了函数的返回值
3.如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined
4.推荐的做法:要么让函数始终都返回一个值,要么永远都不要返回值。
3.2.11 函数直接声明与函数表达式声明的区别
1. 函数的直接声明
函数声明可以定义所命名的函数的变量,而不用给变量赋值。
eg:function func( ) { }
2. 函数的表达式声明
函数表达式声明是将函数定义为表达式语句的一部分。
eg:var func = function( ) { }
3. 两种方式的区别
1.函数的直接声明
alert(f(5,6));
function f(a, b) {
return a + b;
}
运行结果:
2. 函数表达式声明
alert(myFun);
var myFun = function (a,b){
return a + b;
}
运行结果:
4. 不同运行结果产生的原因分析:
JavaScript在执行代码时,存在一种变量声明被提升的机制,即JS解析器首先会把当前作用域的函数声明提前到整个作用域的最前面,即使写的时候是写在后面,也还是会被提至最前。因此f()函数会被提前,alert(f(5,6));
便可以执行;但是myFun()函数并不会被提前,alert(myFun);
便不会被执行。
3.2.12 匿名函数
- 没有命名的函数:function () {}
- 作用:
1.用在绑定事件的时候
eg:
document.onclick = function () {
alert(1);
}
2.定时器
eg:
setInterval(function () {
console.log(444);
},1000);
3.立即执行函数
函数定义完,立即被调用,这种函数叫做立即执行函数立即执行函数往往只会执行一次
eg:
(function(num1, num2){
console.log("mum1 = "+ num1);
console.log("num2 = "+ num2);
})(100, 101);
3.2.13 函数的数据类型
- 函数属于object类型
console.log(typeof fn); // function
console.log(typeof fn()); //undefined
3.2.14 回调函数
- 回调函数就是一个通过函数调用的函数
- 如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
eg:
function fn(num1,num2,demo){
return demo(num1,num2);
}
//定义四个规则:加减乘除
function test1(a,b){
return a+b;
}
function test2(a,b){
return a-b;
}
function test3(a,b){
return a*b;
}
function test4(a,b){
return a/b;
}
console.log(fn(10,5,test1));
console.log(fn(10,5,test2));
console.log(fn(10,5,test3));
console.log(fn(10,5,test4));
回调函数练习:
求Fibonacci数列(1 1 2 3 5 8 13 21…)的第n个数?
解:
function f1 (n) {
if (n == 1) return 1;
if (n == 2) return 1;
return f1(n-1) + f1(n-2);
}
console.log(f1(7));
求n个数的累加
解:
function getSum (n) {
if (n == 1) { return 1;}
return n + getSum(n - 1);
}
console.log(getSum(100));
从前有座山,山里有座庙, …
解:
var i = 100;
function f1 () {
i--;
//递归的结束条件
if (i >= 0) {
console.log("从前有座山,山上有座庙" + i);
//递归调用
f1();
}
}
f1();
3.2.15 变量的作用域
- 作用域:变量可以起作用的地方
- 全局变量:定义在script或者不属于某个函数的变量
- 局部变量:定义在函数内部的变量
- 注意点:
1.不使用var声明的变量是全局变量,不推荐使用(XXXX)
2.函数内部可以访问到该函数所属的外部作用域的变量(作用域链)
3.变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁 - 块级作用域:
1.在其它语言中,任何一对花括号中的语句都属于一个块,在这之中定义的所有变量在代码块外都是不可见的
2.ES6之前都没有这个概念,之前只有函数作用域,ES6中{ }才有了作用域,ES5及之前{ }无意义
ES5中只有函数有作用域 - 作用域链
-
概念:
通俗地讲,当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链。(当需要找某一个变量时,一层一层往外找)
做题的时候可以使用画图的方法(在线画图工具:ProcessOn)
-
其它语言中变量i只能在for循环内部访问(是局部变量),但是在JS中,i是全局变量
for (var i = 0; i < 10; i++) {
}
console.log(i); -
两个案例:
eg1: { var num = 5; } console.log(num); // 返回 5
eg2:
var num = 5;
if (num > 3) {
var sum = 7;
}
console.log(sum); // 返回 7
4.全局变量:
var name = "旋之华";
function f() {
name = "刘德华";
}
f();
console.log(name); // 返回 “刘德华 ”
>>>说明name是全局变量,在全局任何地方都可以使用
- 局部变量
function f(){
var str = '撩课学院';
console.log(str);
}
f(); //返回 撩课学院 (因为此处调用了f()函数)
console.log('------' + str); // 报错(因为str是局部变量)
3.2.16 预解析
-
概念:
1.JS代码的执行是由浏览器中的JS解析器来执行的
2.JS解析器执行代码的时候,分为两个过程:预解析过程和代码执行过程 -
预解析过程
1.把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值
2.把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用
3.先提升var,再提升function -
案例
①var num1 = 10 ; function fn1(){ console.log(num1); var num1 = 20; } fn1(); // 返回 undefined
结果分析: 返回 undefined 是因为 fn1()函数会先在当前作用域找 num1 ;虽然 var num1 = 20;
会被提升,但也只是提升了声明,而没有提升赋值,因此返回 undefined ;
②
console.log(fn2); // 返回fn2()函数
function fn2(){
console.log("是函数");
}
var fn2 = "是变量";
console.log(fn2); // 返回 “是变量”
结果分析:
3.2.17 变量提升
- 变量提升:定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升
- 函数提升:JavaScript解析器首先会把当前作用域的函数声明提前到整个作用域的最前面
3.2.18 其他
- 始终考虑当前作用域
- 没用var声明为全局变量
- 用var声明为局部变量
- 作用域链从里往外看
- 想不明白时,画图解决