JavaScript基本数据类型之函数-学习笔记

在这里插入图片描述

1 基本概念及语法

简单地说,函数就是一段可以反复调用的代码块

1.1 函数的声明

函数的声明(Function Declaration)有3种方法:

  1. function命令:function命令声明的代码区块就是一个函数。写法如下:

    function 函数名(参数...){
        函数体
    }
    
  2. 函数表达式:采用的是变量赋值的写法,声明的是一个匿名函数function命令后不带函数名,且大括号末尾需加上分号(注意:上面第一种方法声明的函数末尾则不用加分号)。

    由于等号右侧只能放表达式,因此匿名函数又被称为函数表达式

    var 变量=function (参数...){
        函数体
    };
    

    若采用函数表达式声明函数时加上了函数名,则函数名仅在函数体内部有效,并且函数名指代函数表达式本身。而函数名在函数体外部是无效的。

  3. Function构造函数:如下例子,若有多个参数,则最后一个参数为函数体,前面的为参数。若仅有一个参数,则该参数就是函数体。

    var add=new Function(
    	'x',
        'y',
        'return x+y'
    );
    
    //等同于
    function add(x,y){
        return x+y;
    }
    

总结:前两种声明方式经常使用,第三种声明方式几乎无人使用。

注:若同一个函数被多次声明,后面的声明会覆盖前面的声明。

1.2 函数的调用及返回值

  1. 调用函数要使用圆括号运算符,圆括号中还可以加入函数的参数。调用函数的写法为:函数名(参数...);,例如调用加法运算函数add(1,2);

  2. 函数体内部的return语句,表示返回,return语句后面的表达式或值就是函数的返回值。

  3. return语句不是必须的,若没有则表示该函数不返回任何值,或者返回undefined

  4. return语句后面还有语句,则不会得到执行。

1.3 递归

函数可以调用自身,这就是递归(recursion)。

1.4 函数的本质

  1. JavaScript将函数看作一种,和数值、字符串、布尔值一样。
  2. 除了可以将函数赋值给变量或对象的属性,还可以将其当作参数传入其他函数,或者作为函数的结果返回

1.5 函数名的提升

JavaScript基础语法-学习笔记_lovewhoilove-CSDN博客一文中提到过变量提升,而采用function命令声明函数时,函数就跟变量声明一样,被提升到代码的头部,这便是函数名的提升。因此,函数的声明可以写在调用函数语句的后面。

若采用函数表达式(即匿名函数)的声明方式则不会产生函数名提升的效果,此时若在匿名函数声明前面调用函数则会报错。

f();
var f=function(){};

//上面的代码实际执行效果如下所示,f只是被声明而未被赋值
var f;
f();
f=function(){};

再来看一个特例:

var f = function () {
  console.log('1');
}

function f() {
  console.log('2');
}

f()//输出结果为1

上面实际执行的代码为:

function f(){
    console.log('2');
}
var f;
f=function(){
    console.log('1');
};

f();

函数表达式声明的变量f首先被提升,然后function命令声明的函数又进行函数提升,后提升的反而到了第一行而被先执行,然后执行的的反而是后声明的匿名函数,又由于为同名函数,后声明的函数会覆盖前面声明的函数,因此,最终执行的的反而是后声明的匿名函数。

2 函数的属性和方法

2.1 name属性

函数的name属性返回函数的名字

2.2 length属性

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

拓展知识length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)。

2.3 toString()方法

函数的toString()方法返回一个字符串,内容是函数的源码,包含换行符在内。

注:针对JavaScript 引擎提供的原生函数,toString()方法会返回原生代码的提示。

3 作用域

作用域(scope)指的是变量存在的范围

3.1 作用域与变量

  1. 在ES5规范中,JavaScript有两种作用域:
    • 全局作用域:变量在整个程序中一直存在,所有地方都可以读取;

    • 函数作用域:变量只在函数内部存在。

注:在ES6中,新增了块级作用域

  1. 根据作用域,可将其内的变量分为:全局变量局部变量

    • 对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
    • 在函数内部定义的变量,函数外部是无法读取的,称为局部变量(local variable)。
  2. 函数内部定义的变量,会在该作用域内覆盖同名全局变量。

    测试如下:变量v同时在函数的外部和内部有定义,此时局部变量v覆盖了全局变量v

    var v=1;
    function f(){
        var v=2;
        console.log(v);
    }
    
    f() //输出结果:2
    v  //输出结果:1
    
  3. 对于var命令来说,局部变量只能在函数内部声明,在其他区块中的声明,一律是全局变量。

    测试如下:变量x在条件判断区块之中声明,结果表明它就是一个全局变量,可以在区块之外读取。

    if(true){
        var x=5;
    }
    console.log(x); //输出结果:5
    

3.2 函数内部的变量提升

函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

function f(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

上面的代码等同于:

function f(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

3.3 函数自身的作用域

由于函数自身也是一个值,因而它的作用域与变量是一样的,就是与其声明时所在的作用域有关,但注意与其运行时所在的作用域无关。

而在函数体内声明的函数,作用域会绑定在函数体内部。

测试如下:在函数foo内部声明了一个函数bar,此时bar的作用域就绑定在foo函数体内部;当在foo外部取出bar函数执行时,变量x指向的是foo内部的x,而不是foo外部的x。也正是这种机制才构成了所谓的闭包现象。

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

4 闭包

4.1 闭包的定义

简单地说,闭包(closure)就是定义在一个函数内部的函数

举个例子:我们为了能在f1函数外部读取到其内部的变量n,那么我们在函数f1内部定义一个函数f2,那么只要将f2作为返回值,这样就可以了。

闭包就是f2,它是能读取f1函数内部变量的函数。因此本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 输出结果为:999

4.2 闭包的作用

  1. 通过上一节的例子可以知道:闭包的作用是可以读取外层函数内部的变量

  2. 而另一个作用是:闭包可以让外层函数的变量始终保持在内存中,即闭包可以使得它诞生的环境一直存在。

    来看一个具体实例就明白了:即通过闭包,n的状态被保留了。具体来说就是:闭包f2用到了外层变量n,导致外层函数f1不能从内存中释放,则f1的内部变量n就始终保存着当前值,供闭包读取,每一次调用都是在上一次调用的基础上进行计算。

    function f1() {
      var n = 999;
      return function f2() {
        return n++;
      }
    }
    
    var result = f1();
    
    result() // 999
    result() // 1000
    result() // 1001
    
  3. 我们可以利用闭包来封装对象的私有属性和私有方法

    如下代码所示:函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

    function Person(name) {
        var _age;
        function setAge(n) {
            _age = n;
        }
        function getAge() {
            return _age;
        }
        
        return {
            name:name,
            getAge:getAge,
            setAge:setAge
        };
    }
    
    var p1=Person('张三');
    p1.setAge(25);
    p1.getAge()  //25
    

    但由于外层函数的每次执行都会生成一个新的闭包,进而保留外层函数的的内部变量,这使得内存消耗很大,因此不能滥用闭包。

4.3 立即调用的函数表达式

我们将圆括号()跟在函数名之后,表示调用该函数。但在利用function命令定义函数后立即调用,则会产生语法错误。当用函数表达式创建函数时,则可以在定义后直接加圆括号调用:

var f = function f(){ return 1}();
f // 1

或者让解释器以表达式来处理函数定义的方法:

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();//~为位运算符,表示反转所有位
-function () { /* code */ }();
+function () { /* code */ }();

但最简单的处理方法是:将函数放在圆括号里面

(function(){ /* code */  });
//或者写为
(function(){ /* code */  })();

上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表达式,而不是函数定义语句,所以就避免了错误。这就叫做立即调用的函数表达式(Immediately-Invoked Function Expression),简称 IIFE

使用立即调用立即执行的函数表达式目的一般有两个:

  1. 不必为函数命名,避免了污染全局变量;
  2. IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

举例如下:写法2要更好,它完全避免了污染全局变量。

//写法1
var tmp=newData;
processData(tmp);
storeData(tmp);

//写法2
(function(){
    var tmp=newData;
    processData(tmp);
    storeData(tmp);
}());

5 参数

function square(x) {return x * x;}中,x就是square函数的参数,每次调用时,都需要提供这个值。

5.1 省略参数

若参数不是必须的,则允许省略:如下参数a时必须的,则加上第3个参数和省略第2个参数都是可以的,但都省略则会出错。

function f(a, b) {
  return a;
}

f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f(,1) //省略第一个参数会报错

f.length // 2

需要注意的是:函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数

5.2 参数传递方式

参数传递方式分为传值传递(passes by value)和传址传递(pass by reference)两种。

  1. 若函数参数为原始类型的值(如数值、字符串、布尔值),传递方式是传值传递;

    var p = 2;
    
    function f(p) {
      p = 3;
    }
    f(p);
    
    p // 2
    

    如上代码所示:变量p是一个原始类型的值,传入函数f的方式是传值传递。因此,在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。

  2. 若函数参数为符合类型的值(如数组、对象、其他函数),传递方式则是传址传递。也就是说,传入函数的原始值的地址,若在函数内部修改对象的属性,将会影响到原始值。

  3. 倘若在函数内部修改的不是对象的某个属性,而是替换掉整个参数对象,则只是将对象指向新的地址,保存在原地址上的值自然不受影响。

    测试如下:

    var obj = [1, 2, 3];
    
    function f(o) {
      o = [2, 3, 4];
    }
    f(obj);
    
    obj // [1, 2, 3]
    

5.3 同名参数

如果有同名的参数,则取最后出现的那个值。测试如下:

function f(a, a) {
  console.log(a);
}

f(1, 2) // 2

若要获得第一个a的值,则可以借助arguments对象:

function f(a, a) {
  console.log(arguments[0]);
}

f(1,2) // 1

5.4 arguments对象

由于JavaScript允许函数有不定数目的参数,因此需要有一种机制,可以在函数体内部读取所有参数,这时就需要arguments对象了。

arguments对象包含了函数运行时的所有参数,像arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推,并且arguments对象仅在函数体内部可以使用。

5.4.1 arguments对象的使用模式

arguments对象的使用分两种模式:

  1. 正常模式下:arguments对象可以在运行时修改参数:

    var f = function(a, b) {
      arguments[0] = 3;
      arguments[1] = 2;
      return a + b;
    }
    
    f(1, 1) // 5
    
  2. 严格模式下:修改arguments对象不会影响到实际的函数参数:

    var f = function(a, b) {
      'use strict'; // 开启严格模式
      arguments[0] = 3;
      arguments[1] = 2;
      return a + b;
    }
    
    f(1, 1) // 2
    

    通过arguments对象的length属性,可以判断函数调用时带的参数个数。

    function f() {
      return arguments.length;
    }
    
    f(1, 2, 3) // 3
    f(1) // 1
    f() // 0
    

    这里再顺便对比下函数的length属性:函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数

5.4.2 将arguments对象转为数组

虽然arguments很像数组,但其实并非数组,要想让其使用数组的一些专有方法,则需要将其先转换为数组。以下为2种常用的转换方法:

  1. 使用slice方法:

    var args=Array.prototype.slice.call(arguments);
    
  2. 逐一填入新数组:

    var args=[];
    for (var i=0;i<arguments.length;i++){
        args.push(arguments[i]);
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值