Bootstrap

【面向JS--函数】

1、函数的定义

函数(function),又称为 方法(Method),或过程(procedure)

是一段预定义好,并可以被反复使用的代码块

预定义:事先定义好,不会马上被执行
反复使用:可以被多个地方所应用
代码块:由多条可执行语句所组成的结构(函数体)

其实,函数就是一个封装代码段的对象,函数名只是一个引用函数对象的变量

函数是一个引用类型的对象

2、函数声明

语法:

function 函数名(){
    //代码块(函数体)
}

函数调用:

使用 已声明好的 函数
语法:
    函数名();
强调:函数只有被调用时,才会执行!   

定义函数的第二种方法:

var 函数名=function(参数){函数体;return 返回值}

与第一种对比:

第一种方法,整体(函数名+函数定义)提前
第二种方法,函数定义不会被提前,仅函数名提前

定义函数的第三种方法(不常用):

用new: 
var 函数名 = new Function("a","b","return a-b");
强调: 所有形参必须放在""中

3、带参数函数的声明和调用

参数变量:专门接收方法执行所必须的数据的变量

何时使用:如果一个函数,必须提供指定数据,才能正常执行时
         需要提供几个数据,就定义几个参数接收
如何定义参数:不需要var,直接在函数名后的括号中定义参数名
            多个参数,每个参数名之间用逗号分隔
何时,如何传入参数值:调用时,按照参数定义的个数和顺序传入
为什么使用参数:参数可让方法变的更灵活

语法

function 函数名(参数列表){
     参数可以参与运算
}

参数列表:由参数名称组成,多个参数的话,用 , 分隔
声明函数时定义的参数,称之为 "形参"

调用带参数函数

函数名(参数值列表);
参数值列表:由具体数值来组成,多个数值之间,用 , 分隔。调用函数时,传递的参数值,称之为 "实参"

如果传入参数个数不符,不会报错:
    个数超了,多出的没用
    个数不够,未接到值得参数变量,默认值undefined

4、带返回值的函数声明和调用

返回值:函数运行完成后,带给函数调用者的"一个"数据
语法:
    function 函数名(参数列表){
        //函数体
        return 返回值;
    }
调用:
    var result = 函数(参数列表);

    ex : var result = parseInt("35.5");

return特点:2个:
    1、return与返回值之间不能加回车
    2、return不能放在其他表达式中间使用,执行完return语句,将跳出函数

如果一个表达式或函数有结果,就可直接当一个值用

function sum(a,b){
    return a + b;
}
console.log(sum(3,4)); // 7
==>
console.log(3+4); // 7

5、函数重载overload

什么是函数重载?

相同函数名,不同参数列表的多个函数,在调用时,可自动根据传入参数的不同,调用对应的函数执行。

作用: 减轻调用者的负担
何时: 只要一项任务, 根据不同的参数,执行不同的流程时
问题: js不支持重载! 不允许多个同名函数同时存在,下面的函数会覆盖上面的函数。但可用arguments对象模拟重载效果
解决: arguments对象
 什么是arguments: 每个函数中自带的,接收所有传入参数值的类数组对象
 什么是类数组对象: 长的像数组的对象!
      vs数组: 相同: 1. 下标; 2. .length属性
             不同: 类型不同: 类数组对象不能用数组API
                 arguments  Object
                       数组   Array

类数组对象 to Array:
  var arr=Array.prototype.slice.apply/call(arguments);
function calc(){
    if(arguments.length==2){
        return arguments[0] + arguments[1];
    }else{
        return arguments[0] * arguments[0];
    }
}
console.log(calc(12,23));//35
console.log(calc(12));//144

6、匿名函数

匿名函数是指在函数创建时,不指定函数名的函数

作用: 节约内存(调用前和调用后,内存中不创建任何函数对象)
何时: 只要一个函数仅执行一次时,就必须用匿名函数
如何: 2种: 
   1、回调函数: 
      arr.sort(function(a,b){return a-b})
      str.replace(/reg/ig,function(kw){return 替换值;})
   2、自调: 创建函数后,立刻调用自己!
      何时: 如果一段代码,不希望其中的变量造成全局污染时,就要放在匿名函数中自调。
      语法: 2种: 
       +function(){...}()
       (function(){...})()

7、作用域和作用域链

1、什么是作用域(scope)

作用域指的是,变量和函数的可用范围,它控制着变量和函数的”可见性”和”生命周期”

JS中,作用域可分为:
1、函数作用域 AO(活动对象): 局部变量
    局部变量: 仅函数内可用,不可重复使用
2、全局作用域 window:全局变量
    全局变量: 随处可用,可反复使用
    缺点:容易造成全局污染

2、局部变量

局部变量离开 函数的声明范围 就无法使用

注意:
在函数内,为没有声明过的变量赋值, 变量会被自动创建在全局-->危险
强烈建议:所有变量使用前,必须用var声明       
function test(){
    var num = 15;//num 为局部变量,只能在 test()中使用
    name = "xiaoli";
    console.log(num);
}
console.log(name);//xiaoli
console.log(num);//报错

3、全局变量

注意:声明全局变量时,尽量放在所有的 function 之外,不在要function内省略var 关键字去声明。

4、内存中函数生命周期

执行环境栈:ECS(Execute Contect Stack),保存全局以及每个函数的执行环境的栈结构

执行环境:EC,调用函数时,创建的引用函数资源的对象,窗口一打开,默认ECS中压入一个全局EC,全局EC引用了window对象 VO , window对象中的变量都是全局变量

变量对象:VO,专门存储变量的对象

生命周期:

1、开始执行前:

 创建执行环境栈(数组),临时保存正在执行的函数的执行环境
 向执行环境栈中压入第一个默认函数main()
 创建全局作用域对象window

第一步:
第一步

2、定义函数时:

 创建函数对象,封装函数定义
 声明函数名变量,引用函数对象
 函数对象的scope属性引用回创建函数时的作用域

第二步 :
第二步

3、调用函数时:

 ECS中压入一个新的元素(执行环境)记录新函数的调用
 创建一个活动对象,保存本次函数调用用到的局部变量
 ECS中的新执行环境元素,引用活动对象
 活动对象中的parent属性引用函数的scope指向的父级作用域对象
 执行过程中: 优先使用活动对象中的局部变量
 局部没有,才延parent向父级作用域找

第三步1:
第三步1
第三步2:
第三步2

4、函数调用后:

 执行环境栈中本次函数的执行环境出栈
 导致活动对象被释放
 导致局部变量一同释放

第四步:
第四步

5、作用域链

由多级作用域连续引用形成的链式结果

掌管着一切变量的使用顺序: 先在局部找。如果没有,就延作用域链向父级作用域找

作用域链

8、其他

1、声明提前

JS在正式执行前,会将所有var声明的变量和function声明的函数,预读到所在作用域的顶端。但是,对变量的赋值保留在原位置。

2、按值传递

两变量间赋值或向函数中传递参数时,都是将实参变量值复制一份副本传给方法的形参作为参数

3、函数递归

递归:在函数内又调用了一次自己 
递归调用的内层函数,是在外层函数还未结束时就已经开始了,外层额函数的调用,就会被阻塞 
缺点:算法复杂度太高,且浪费内存 
解决:绝大多数的递归,都可以被循环所替代
;