函数

3300 字
17 分钟
函数

函数(Function)#

  • 函数也是一个对象,也具有普通对象的功能
  • 函数中可以封装一些代码,在需要的时候可以去调用函数来执行这些代码
  • 使用typeof检查一个函数时会返回function

创建函数#

  • 函数声明 function 函数名([形参1,形参2…形参N]){ 语句… }

    function fun() {
    consloe.log("hellow");
    }
  • 函数表达式 var 函数名 = function([形参1,形参2…形参N]){ 语句… };

    var fun = function() {
    consloe.log("hellow");
    }

调用函数#

  • 语法: 函数对象([实参1,实参2…实参N]); fun(); sum(); alert(); Number(); parseInt(); //fun() 直接调用 //obj.fun() 通过对象调用 //new fun() new调用 //fun.call(obj) fun.apply(obj) 临时让fun成为obj的方法进行调用 obj.fun() var obj ={}; funcation fun() { this.name = “123”; } //obj.fun() 错误,obj中没有fun这个方法 fun.call(obj) console.log(obj.name) // 123

  • 当我们调用函数时,函数中封装的代码会按照编写的顺序执行

形参和实参#

  • 形参:形式参数
    • 定义函数时,可以在()中定义一个或多个形参,形参之间使用 , 隔开
    • 定义形参就相当于在函数内声明了对应的变量但是并不赋值,
    • 形参会在调用时才赋值。
  • 实参:实际参数
    • 调用函数时,可以在()传递实参,传递的实参会赋值给对应的形参,
    • 调用函数时JS解析器不会检查实参的类型和个数,可以传递任意数据类型的值。
    • 如果实参的数量大于形参,多余实参将不会赋值,
    • 如果实参的数量小于形参,则没有对应实参的形参将会赋值undefined

返回值 return#

可以使用 return 来设置函数的返回值 语法: return 值;

return a;
  • return后的值将会会作为函数的执行结果返回,可以定义一个变量,来接收该结果
  • 在函数中return后的语句都不会执行
  • 如果return语句后不跟任何值就相当于返回一个undefined, 如果函数中不写return,则也会返回undefined

立即执行函数(IIFE)#

  • 函数定义完,立即被调用,这种函数叫做立即执行函数,匿名函数直接调用

  • 立即执行函数往往只会执行一次

  • 作用:隐藏实现,不会污染外部命名空间,编写js模块 语法:

    (function([形参1,形参2...形参N]){
    语句...
    })([实参1,实参2...实参N]);
    (function(){
    consloe.log("hellow");
    })();
    (function(a,b){
    console.log("a = "+a);
    console.log("b = "+b);
    })(123,456);

函数的声明提前#

- 使用函数声明形式创建的函数 function 函数(){}
- 它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
- 使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用

作用域#

  • 作用域指一个变量的作用的范围

全局作用域#

- 直接编写在script标签中的JS代码,都在全局作用域
- 全局作用域在页面打开时创建,在页面关闭时销毁
- 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用
- 在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
- 全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到

函数作用域#

- 函数作用域是在function字样里的作用域,调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,它们之间互相独立的
- 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果没有,再在离得最近的一级作用域,直到找到全局变量,如果全局作用域中没有找到,就会报错
- 如果想直接在函数作用域中用全局的变量,使用window.a来调用(前提是用var或者直接写来声明的变量)
- 在函数作用域中也有声明提前的特性,使用变量声明关键字声明的变量,会在函数中所有的代码执行之前被声明,这个变量是在函数作用域中被声明的,同理在函数中声明的函数也是一样
- 有参的函数在调用的时候不传入实参,形参的值是undefined,因为在定义形参的时候相当于在函数内部用var声明了一个变量,没有赋值的变量一律为undefined,即使全局变量中有一样的变量也会用内部没有赋值的变量

关键字 this#

  • 解析器在调用函数每次都会向函数内部传递进一个隐含的参数这个隐含的参数就是this
  • this指向的是一个对象,这个对象我们称为函数执行的上下文对象
    • 根据函数的调用方式的不同,this会指向不同的对象
      1. 以函数的形式调用时,this永远都是window var name = “abc”;

        function fun(){
        console.log(this.name);//此时this.name指向全局作用域的window.name
        }
        fun();
      2. 以方法的形式调用时,this就是调用方法的那个对象 function fun(){ console.log(this.name);//此时的this.name指向的是定义的obj对象中的name属性 }

        var obj = {
        name:"abc",
        sayName:fun
        };
        obj.sayname();
      3. 以构造函数的形式调用时,this就是新创建的那个对象 function Person(name , age , gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = function(){ console.log(this.name); //this指向的都是Person类中的属性 }; }

构造函数#

  • 构造函数就是一个普通的函数,创建方式和普通函数没有区别,
    • 不同的是构造函数习惯上首字母大写

    • 构造函数和普通函数的区别就是调用方式的不同 普通函数是直接调用,而构造函数需要使用new关键字来调用

    • 构造函数的执行流程: 1.立刻创建一个新的对象 2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象 3.逐行执行函数中的代码 4.将新建的对象作为返回值返回

    • 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。 我们将通过一个构造函数创建的对象,称为是该类的实例

      function Person(name , age , gender){
      this.name = name;
      this.age = age;
      this.gender = gender;
      this.sayName = function(){
      console.log(this.name);
      };
      }
      var p1 = new Person("张三", 18, "男"); //此时的person是以构造函数的形式赋值给p1变量
      var p2 = Person("李四", 18, "男"); //此时的person是以普通函数的形式赋值给p1变量

闭包#

  • 当函数可以记住并访问所在的词法作用域时, 就产生了闭包, 即使函数是在当前词法作用域之外执行。 通俗来说当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包 (函数嵌套,内部函数引用外部函数的变量) function foo() { var a = 2;

    function bar() {
    console.log(a);
    }
    return bar;
    }
    var baz = foo();
    baz(); //2
    • 在上述代码中,函数 bar() 的词法作用域能够访问 foo() 的内部作用域(foo()内部嵌套了bar()函数)。将 bar() 函数本身当作一个值类型进行传递,使得bar()能够在它的词法作用域外部进行调用
    • 我们都知道在函数执行完毕后就,将由垃圾回收机制释放掉foo()函数所占用的内存,但是 var baz = foo() 这行代码中,bar()函数一直处于执行状态,而foo()函数内部作用域任然在被bar()函数使用(bar()打印变量a的值),拜 bar() 所声明的位置所赐, 它拥有涵盖 foo() 内部作用域的闭包, 使得该作用域能够一直存活, 以供 bar() 在之后任何时间进行引用。
    • bar() 依然持有对该作用域的引用, 而这个引用就叫作闭包。
  • 循环与闭包 正常情况下,我们对这段代码行为的预期是分别输出数字 1~5,每秒一次, 每次一个。但实际上, 这段代码在运行时会以每秒一次的频率输出五次 6。 for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); } 因为延迟回调函数的执行总是在循环执行结束才执行的,此时执行回调函数是 i === 6。尽管循环中5个函数都是分别定义的,但是它们都处在一个共享全局作用域中,因此实际上只有一个 i 。

    for (var i = 1; i <= 5; i++) {
    (function (i) {
    setTimeout(function timer() {
    console.log(i);
    }, j * 1000);
    })(i);
    }

    IIFE 会通过声明并立即执行一个函数来创建作用域。在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域, 使得延迟函数的回调可以将新的作用域封闭在每个迭代内部, 每个迭代中都会含有一个具有正确值的变量供我们访问。

    for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
    console.log(i);
    }, i * 1000);
    }

    还能使用let声明创建块作用域,并且for 循环头部的 let 声明还会有一个特殊的行为。 这个行为指出变量在循环过程中不止被声明一次, 每次迭代都会声明。 随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

  • 模块

    • 我们可以利用闭包创建JS模块

      • 具有特定功能的js文件

      • 将所有的数据和功能都封装在一个函数内部(私有的)

      • 只向外暴露一个包信n个方法的对象或函数

      • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能

        function CoolModule() {
        var something = "cool";
        var another = [1, 2, 3];
        function doSomething() {
        console.log(something);
        }
        function doAnother() {
        console.log(another.join(" ! "));
        }
        return {
        doSomething: doSomething,
        doAnother: doAnother
        };
        }
        //将以上部分通过js文件保存起来,直接引用外部js文件
        var foo = CoolModule();
        foo.doSomething(); // cool
        foo.doAnother(); // 1 ! 2 ! 3
    • 首先, CoolModule() 只是一个函数, 必须要通过调用它来创建一个模块实例。 如果不执行外部函数, 内部作用域和闭包都无法被创建。

    • 其次, CoolModule() 返回一个用对象字面量语法 { key: value, … } 来表示的对象。 这个返回的对象中含有对内部函数而不是内部数据变量的引用。 我们保持内部数据变量是隐藏且私有的状态。 可以将这个对象类型的返回值看作本质上是模块的公共 API。

    • 这个对象类型的返回值最终被赋值给外部的变量 foo, 然后就可以通过它来访问 API 中的属性方法, 比如 foo.doSomething()。

    • doSomething() 和 doAnother() 函数具有涵盖模块实例内部作用域的闭包( 通过调用CoolModule() 实现)。

    • 模块模式需要具备两个必要条件。

      1. 必须有外部的封闭函数, 该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
      2. 封闭函数必须返回至少一个内部函数, 这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
  • 记住闭包产生的条件:当函数可以记住并访问所在的词法作用域, 即使函数是在当前词法作用域之外执行, 这时就产生了闭包。

垃圾回收(GC)#

  • 就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾

  • 当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。

  • 在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作

  • 我们需要做的只是要将不再使用的对象设置null即可 var obj = new Object();

    //对对象进行各种操作。。。。
    obj = null;

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

函数
https://sukksa.github.io/posts/han-shu/
作者
Sukksa
发布于
2021-10-16
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Sukksa
记录前端学习与日常
公告
博客刚刚上线,内容还在慢慢补齐。欢迎随便逛逛~
分类
标签
最新动态
站点统计
文章
69
分类
16
标签
17
总字数
178,517
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录