ECMAScript核心语法结构之函数详解

一、函数的概念

函数是一段可以反复调用的代码块。接受输入的参数,不同的参数会返回不同的值。

1.函数的定义

ECMAScript中是使用function关键字来声明,后面跟一组参数以及函数体。

1) 声明函数

 function sum (num1,num2){
     return num1 + num2;
 }
//可以先使用后定义。 解析器在执行环境中加载数据时,会率先读取函数声明。

2) 函数表达式:将一个匿名函数赋值给一个变量

  var sum = function (num1,num2){
       return num1 + num2;
  }
  //必须定义后使用(否则会报错)。到解析器执行到它所在的代码行,才会真正被解析执行

3) 构造函数(几乎不用)

var sum = new Funtion(
    'num1',
    'num2',
    'return num1 + num2'
)
//等同于上面的声明函数

2.函数的调用(括号运算符)

函数可以通过其函数名调用,后面还要加上一对圆括号和参数(如果有多个参数,用逗号隔开)

function sum(num1, num2) {
    return num1 + num2;
}
sum(1, 1)  //2

3.函数的参数

1.什么是参数

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

function sum (num1,num2){
     return num1 + num2;
}
sum(1,2); //3
sum(1)    //NAN  num2 undefind;

2.参数的传递方式 - 按值传递

(1) 基本类型值的传递如同基本类型变量的复制一样。
(2) 引用类型值的传递则如同引用类型变量的复制一样(将这个值在内存中的地址复制给了一个局部变量,因此这个局部变量的变化就会反在函数的外部)

//基本类型的传递
function addTen(num) {
     num += 10;
     return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30

//引用类型的传递
var person = new Object();
function setName(obj) {
    alert(person.name); //undefined
    obj.name = "Nicholas";
    alert(person.name)  //"Nicholas"
}
setName(person);
alert(person.name); //"Nicholas"

3.arguments对象(读取函数体内部所有参数)

  • arguments对象是一个类数组(有length属性,但没有数组的任何方法)
  • 可以编写无需指定参数个数的函数
var sum=function(){
    var sum=0;
    for(var i=0;i<arguments.length;i++){
        sum += arguments[i];
    }
    return sum;
}
sum(1,2,3)

4. 函数的返回值(return)

函数可以通过return语句跟要返回的值来实现返回值。函数在执行完return语句之后停止并立即退出。因此,在return语句之后的任何代码永远都会回执行。

function sum(num1, num2) {
    return num1 + num2;
    alert('Hello world') //永远不会执行
}

return语句不是必须的,如果没有的话,该函数就不会返回任何值,或者说返undefined;

function sum(num1, num2) {
    alert (num1 + num2)
}     //undefined

5.函数没有重载(函数的重复声明)

函数重载:在其他语言(Java)中可以为一个函数体编写两个定义,只要定义的签名(接收的参数的类型和参数不同即可))
在ECMASctipt中如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

function addSomeNumber(num){
    return num + 100;
}
function addSomeNumber(num) {
   return num + 200;
}
var result = addSomeNumber(100); //300

6.函数的属性和方法

(1) name属性(返回name的名字)

 function sum (){}
 sum.name // 'sum'
 

(2) length 属性

函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

function f(a,b){
    return f.length
}
f(1,2,3,4)  //2 ==> length属性就是定义是的参数个数。不管调用时传入了多少参数,length属性的始终等于2
          

(3)apply()和call()
每个函数都包含两个非继承而来的方法:apply()和和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法接收两个参数,一个是在其运行函数体内的作用域,另一个是参数数组。其中第二个参数是数组或者arguments对象。
call()方法接收两个参数,第一个参数是this值没有变化,变化的是其余参数都是直接传递给函数(的参数必须逐个列举出来)

1.传参

  function sum(num,num2){
      return num1 + num2;
  }
  function callSum(){
      return sum.call(this,num1,num2);
  }
  alert(callSum(10,10)); //20

2.扩充函数赖以运行的作用域 (改变this指向的值)

var color = 'red';
var o = {
    color: 'blue'
}
sayColor.call();       //red(默认传递参数this)
sayColor.call(this);   //red
sayColor.call(window); //red
sayColor.call(o);      //blue

二、函数的作用域和作用域链

1.作用域

(1)什么是作用域
作用域(scope)指的是变量存在的范围。 在es5中的,Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。(es6中又新增了块级作用域)
全局作用域可以在代码的任何地方都能被访问

var color1 = 'blue';
function colorFn (){
    var color2 = 'red';
    color3 = 'yellow';
    console.log(color1);
    console.log(color2);
    console.log(color3);
}
colorFn();
console.log(color1);   //'blue'
console.log(color2);   // error
console.log(color3);   //'yellow'

//函数changeColor()的作用域链包含两个对象:它自己的变量对象(定义着arguments对象)和全局的变量的对象

2.作用域链

1.当代码在一个执行环境中执行时,就会创建一个作用域链。

2.作用域链的用途,是为了保证对执行环境有权访问的所有变量和函数的有序的访问。即全局作用域和局部作用域的变量的访问权限是由作用域链决定的。

3.作用域链的是从当前的执行环境的变量对象在这个环境中可以访问的变量对象开始,到全局执行环境的变量对象结束。(在这个环境中可以访问的变量)

4.内部环境可以通过作用域链访问所有外部环境,但是外部环境不能访问内部环境中的任何变量和函数。

5.标示符(变量、函数、属性的名字,或者函数的参数)的解析是沿着作用域链一级一级的搜索标示符的过程。搜索过程始终从作用域链的前端开始,然后逐级的向后回溯,直到找到标识符(如果找不到标识符,通常会报错)。

//在局部作用域中定义的变量可以在局部环境中与全局变量中互换使用
var color = 'blue';
function changeColor(){
     var anotherColor = 'red';
     function swapColors(){
          var tempColor = anotherColor;
          anotherColor = color;
          color = tempColor;
          //这里可以访问color、anotherColor和tempColor
      }
      //这里可以访问color和anotherColor,但不能访问tempColor
      swapColors();
}
//这个只能访问color;
changeColor();                              

三、函数的其他知识点

1.闭包函数

(1) 什么是闭包函数
闭包函数是指有权访问另一个函数作用域中的变量的函数
(2) 创建闭包的常见方式,就是在一个函数内部创建另一个函数

//在函数内部可以读取函数外部的变量
var localVal = 30;
function outer(){
    return localVal;
}
outer();//30

//在函数外部自然无法读取函数内的局部变量
function outer(){
     var localVal = 30;
}
alert(localVal);//error

//通过闭包函数来读取读取outer函数内部的变量
function outer(){
     var localVal = 30;
        function inner(){
          alert(localVal);
     }
     return inner;
}
var func = outer();
func();//30

(3) 闭包的优缺点

1) 使用闭包的好处是不会污染全局环境,方便进行模块化开发,减少形参个数,延长了形参的生命周期
2) 闭包会把函数中变量的值保存下来,供其他函数使用,这些变量会一直保存在内存当中占用大量的内存,使用不当会造成内存泄漏

2.回调函数

(1) 什么是回调函数(作为值的函数)

回调函数也被称作高阶函数,是一个被作为参数传递给另一个函数的函数。
function someFunction(someNum,callback){
    return callback(someNum);
}
function add10(){
    return num + 10;
}
someFunction()

(2) 应用场景实例:

1.数组中的一些方法 sort()、reverse()、forEach()、filter()等
     var friends = ["Mike", "Stacy", "Andy", "Rick"];
     friends.forEach(function (eachName, index){
        console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
     });

2.事件的绑定
     //原生绑定事件
    document.body.addEventListener("cilck",function(){
        alert("body Clicked");
    });

    //jq
    $("#btn_1").click(function() {
      alert("Btn 1 Clicked");
    });

3.ajax的请求
    function getAjax (options, callback) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', options.url);
        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
                if(xhr.status == 200){
                    callback && callback(xhr.responseText);
                }
            }
        };
        xhr.send();
    }

3.递归函数

1.什么是递归函数
递归函数是一个在函数通过名字调用自身的情况下构成的。递归函数必须要有边界条件

function factorial(num){
   if(num <= 1){
       return 1
   }else {
       return num * factorial(num - 1);
   }
}
 

2.arguments.calls()是一个指向正在执行的函数指针 (严格模式下不支持)
//当函数名发生改变时会导致代码出错

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //出错!

function factorial(num){
  if(num == 1){
      return 1;
  } else {
      return num * arguments.callee(num-1)
  }
}

3.严格和非严格都有效

  var factorial = (function f (num){
        if(num == 1){
            return 1;
        } else {
            return f(num-1)
        }
   })
    

4.自执行函数

(1)什么是执行函数
立即执行函数,就是在定义的时候立即执行的函数,执行完以后释放,包括函数内部的所有变量(执行完毕,立即销毁其作用域链)

(2)立即执行函数的写法

1.常用写法
    (function(){
    })();

2.w3c建议方式
    (function(){
    }());

3.错误写法
    function(){
    }();
  • 函数声明后面不能跟花括号(js会把function关键字当做一个函数声明的开始)
  • 函数表达式的后可以跟圆括号。要将函数声明转变成函数表达式
  • 除圆括号外,!、+、-、=等运算符,都将函数声明转换成函数表达式

      !function(){}();
      +function(){}();
      -function(){}();
      var fn=function(){}();
    

(3) 模仿块级作用域

function outputNumbers (count){
    for(var i=0; i<count;i++){
        alert(i)
    }
    var i;
    alert(i);
    //计数 i从有定义开始,在函数内部就可以访问它,即使重新声明一个变量也不会改变它的值(不会改变?)。
}
function outputNumbers (count){
    (function(){
        for(var i=0; i<count;i++){
            alert(i)
        }
    })(); //变量会在立即执行结束后会被立即销毁
    alert(i);
}
//可以避免过多全局变量和函数导致命名冲突;这个作用域里面的变量,外面访问不到(即避免「变量污染」)。

3.应用场景

<ul class="box">
    <li>0</li>
    <li>1</li>
    <li>3</li>
    <li>4</li>
</ul>
var list = document.getElementsByTagName('li');
for (var i = 0, len = list.length; i < len; i++) {
    (function(i){   //为了获得不同的i值,使用立即调用函数
        list [i].onmouseover = function() {
            console.log('index is :' + i);
        }
    })(i);
}

参考:
JavaScript高级程序设计(第3版)
https://wangdoc.com/javascrip...
https://www.cnblogs.com/caoru... JavaScript函数之作用域 / 作用链域 / 预解析
https://www.cnblogs.com/bucho... JavaScript中作用域和作用域链的简单理解(变量提升)
https://www.cnblogs.com/jingw... 闭包
https://www.cnblogs.com/langq...
https://www.cnblogs.com/mengf...
https://www.cnblogs.com/mafei... 立即执行函数
https://baijiahao.baidu.com/s... 立即执行函数

03-06 00:10