函数的定义语法
function 函数名(){
代码段
}
function是一个关键字,函数的名字自定义,但取名的规则和变量定义的规则相同。定义好的函数并不会自己执行,而是需要我们进行调用。
函数的调用
语法:
函数名()
举例:
// 定义函数:求两个数的和
function fn(){
var a = 1;
var b = 2;
var c = a + b;
console.log(c);
}
// 函数定义好以后,不会自动执行,需要手动调用
fn();
赋值式函数
将一个函数代码段赋值给一个变量,这个变量的类型是function类型,变量的值为函数的代码,可以正常的调用,但如果给他赋值的函数是有名称的,那么这个函数名称失效(调用后报错 函数名 is not defined)一般可以在赋值时省略这个名称。
例子:
var a = function zhubi() {
console.log('zhubiluohuihuang');
}
a();
console.log(a);
console.log(typeof a);
zhubi()
控制台查看结果如下:
匿名函数
没有名字的函数叫做匿名函数。匿名函数在空间中不能单独的存在,必须用小括号将它包裹。
(function(){
console.log(13)
})
匿名函数用一种特殊的方式调用,引出了新的概念,自调用函数。
自调用函数
调用匿名函数的语法:
(function(){
console.log(13)
})()
这样的函数在定义好后被立即调用,同时自调用函数还有两种不同调用的写法,不给函数加小括号而是改用~
和!
!function(){
console.log(14);
}()
~function(){
console.log(14);
}()
PS:自调用的函数也可以传递参数。
(function(a,b){
var c = a + b;
document.write(c);
})(1,2);
带参数的函数
在函数中的变量需要根据需要发生变化时,就可以定义带参数的函数。形参声明的方法将变量写在定义函数的()中,多个变量用,
隔开。
例如:
function zizeng(a){ // 叫做形参 - 形式上的参数
var b = a + 1;
console.log(b);
}
调用时,需要给形参赋值,在调用的()
中从左到右一一对应的写入需要付给形参的值,这些值被称为实参。
zizeng(1); // 实参 - 实际上的参数,实参其实就是给形参赋值
如果没有实参给形参赋值,形参默认是undefined,如果实参数量超出了形参,超出的实参是无效的
函数的本质
当我们去调用函数的时候,通过函数的名称就可以调用到,那说明我们在定义函数的时候,函数就已经保存起来了,所以下面才能调用出来。
调用函数,就相当于将这段代码拿出来执行。
函数的优点
- 实现了代码的可重用性
- 实现了模块化编程
带返回值的函数
如果我们需要一个函数调用之后得到的结果,就需要带返回值的函数。返回函数的结果,在函数中使用return
关键字,后面跟上要返回的结果,可以使一个变量也可以是其他。
例如:
function zhubi(a, b) {
var c;
c = a + b;
return c;//返回c为函数计算的结果
}
var d = zhubi(3,5) * 4;//执行这里的计算需要return的到函数计算的结果
console.log(d);//返回结果为32
return关键字除了可以给函数调用返回结果,还可以结束函数运行:
虽然在函数中有一个字符串输出的代码,但因为return结束了函数的进程,所以代码并没有被输出。
return只能返回一个结果,不能返回多个。
若函数中没有加return,那么函数的返回值是undefined,而如果加了return而return后面没有数值,函数返回的也是undefined。
预解析(重要)
js解析器执行代码过程:
1.预解析js代码
预解析的过程就是在代码中查找var与function两个关键字,找到关键字后将,变量定义与函数赋值的过程提升到整一个代码的最前端。所以此时变量的值为undefined因为预解析只是将变量定义放到了代码的最前端,而没有赋值的变量值为undefined。函数为初始值的代码段。
2.执行代码
开始按顺序一行一行解读代码
预解析题目:
题目:
console.log(a)
var a = 10;
test()
function test(){
console.log("this is test function")
}
题目解析:
var a
function test(){
console.log("this is test function")
}
console.log(a) // 前面有定义过变量a,没有赋值,所以变量的值为undefined
a = 10; // 将a的值改变为10
test() // 前面有定义过函数,内存中能找到,所以调用成功
一些题目:
// 第1题
console.log(num)
var num = 100
//输出undefined
// 第2题
fn();
function fn() {
console.log(123);
}
//输出123 因为预解析
// 第3题
console.log(fn)
fn()
var fn = function () {
console.log(123);
}
//第一个输出undefined
//第二个报错 fn is not a function
//相当于 预解析先定义了一个变量fn 但fn为undefined但不是一个函数 不可以用来调用
// 第4题
fun()
var fn = function () {
console.log('我是 fn 函数')
}
function fun() {
console.log('我是 fun 函数')
}
fn()
fn = 100
fn()
//
// 第5题
fn()
function fn() {
console.log('我是一个 fn 函数')
}
fn()
var fn = 100
fn()
//我是一个fn函数
//我是一个fn函数
//报错
//
/*function fn() {
console.log('我是一个 fn 函数')
}
var fn
函数和变量的定义词同名 保留函数 忽视变量声明声明
fn()
执行函数
*/
// 第6题
var fun = 200
fun()
var fun = function () {
console.log('我是一个 fun 函数')
}
fun()
// 第7题
var a = b
a = 0
b = 0
console.log(a)
console.log(b)
//没有定义变量b所以报错 报错后 后面的代码不执行
// 第8题
console.log(num)
if (false) {
var num = 100
}
//输出undefined
预解析的总结:
1.如果变量名和函数名重名了,则保留函数预解析,而忽视变量预解析。
2.不执行的代码中如果有变量或者函数的定义,这些函数与变量也会被预解析。因为预解析在执行之前。
3.如果js的代码报错,则之后的代码不再执行。
4.赋值式函数在预解析时算作定义变量,而不算定义函数。
5.函数会在函数内部进行预解析
函数的调试
作用域
作用域的定义:能起到作用的区域,因为定义在不同区域的变量,其作用域是不同的。
作用域的分类
全局作用域:
不在任何一个函数中定义的变量叫做全局变量,全局变量的作用域是整一个script区域。
<script>
var a = 6; //全局变量a,作用域范围也包括了下一个script
console.log(a);
function fn(){
console.log(a);
}
fn()
</script>
局部作用域:
在函数内部定义的变量,他的作用域的范围就是这个函数,在函数外不能使用这个变量。
<script>
function fn(){
var a = 6;
console.log(a);
}
fn() //输出6
console.log(a);//报错a is not defined
</script>
如果全局和局部有相同的变量名,局部输出时先考虑当前的作用域。
<script>
var a = 12;
function fn(){
var a = 6;
console.log(a);
}
fn() //输出6
console.log(a);//输出12
</script>
作用域链
函数是写在全局当中的,所以局部作用域是嵌套在全局作用域之中的,如果再在函数中嵌套另一个函数,则在局部作用域中又会再嵌套一个局部作用域。这样就形成了作用域的嵌套,由作用域的嵌套引发的链式结构就叫做---作用域链。
var a = 10;
function fn() {
var b = 20;
fun()
function fun() {
var c = 30;
console.log(b);//输出20
}
console.log(a);//输出10
}
fn()
作用域链的规则
- 当使用一个变量时,包括:用变量作为值进行赋值、用变量进行计算、输出变量,会先在当前作用域中寻找变量是否有定义过变量,若定义过,正常使用;若没有在这个定义域中定义,则按照作用域链,返回这个定义域的上一级寻找,同理若找到正常使用,若没有找到则重复这个操作直到全局作用域中。若在全局定义域中有,则还是可以正常使用,若没有则在使用中报错:变量名 is not defined.
- 当给一个变量进行赋值时,现在当前作用域中查找是否定义过这一个变量,如果定义过则给这个变量赋值。若没有,则和使用变量的原理一样,直到全局作用域。不同的是若是全局作用域也没有定义过这个变量,则规则是在全局定义这个变量,并对他赋值,值为你书写的值。
一些练习:
// function fn() {
// console.log(num) // undefined,原因为在函数中也会进行预解析
// return
// var num = 100
// }
// fn()
// console.log(num); // 报错
// var num = 10;
// fn1();
// function fn1() {
// console.log(num);
// var num = 20;
// }
/*
预解析:
var num
function fn1() {
var num
console.log(num); // undefined
num = 20;
}
num = 10;
fn1();
函数中也会预解析
*/
// var a = 18;
// fn2();
// function fn2() {
// var b = 9;
// console.log(a); // 18
// console.log(b); // 9
// }
// fn3();
// console.log(c);
// console.log(b);
// console.log(a);
// function fn3() {
// var a = b = c = 9; // b=9
// console.log(a); // 9
// console.log(b); // 9
// console.log(c); // 9
// }
// var a = b = c = 9
/*
相当于:
var a = 9
b = 9
c = 9
*/
// console.log(a,b,c);
// function fn(a) {
// // var a = 10
// // -------------------
// function a() {
// console.log('我是函数 a')
// }
// console.log('我是 fn 函数')
// a()
// }
// fn(10)
习题总结:
- 全局中有预解析,局部中也有预解析,局部的预解析只在局部中进行对全局没有影响。
- 在使用
var a = b = c =9
这种方式给变量赋值时,只有第一个变量是有定义的过程也就说只有第一个变量定义等同于var a=9
其余都是直接给变量赋值。 - 函数定义好以后,函数名就跟变量名一样,可以使用函数名修改这个空间中的值。
- 局部的预解析会在形参赋值之后,预解析中的函数会覆盖掉形参赋的值。
递归函数
递归函数就是在函数内部调用自己,形成循环。
使用递归函数时,应该注意要有一个停止函数的条件,否则会形成死循环。
写递归函数的步骤:
- 先书写一个空函数,并调用
- 调用时传入实参,定义形参
- 书写大致的代码
- 根据想要实现的效果给递归函数一个停止的条件
用递归函数实现效果:
// 求10的阶和,即:10+9+8+...+1
function facSum(num){
if(num == 1){
return 1;
}
return num + facSum(num-1);
}
var res = facSum(10);
console.log(res); // 55