函数

函数

1.函数介绍

函数允许我们封装一系列代码来完成特定的任务。当想要完成某一任务时,只需要调用相应的代码即可。

函数的作用:

​ 功能的封装,直接调用,代码复用率提高

​ 构造对象的模板(构造函数)

函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数都是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定

2.函数声明

自定义函数:

1.由function关键字声明 函数声明

function 函数名(形参裂变){
    //函数体
}

2.将一个匿名函数(没有函数名的函数)赋值给一个函数变量 函数表达式

在调用之前必须先给它赋值

var 函数名=function(形参列表){
	//函数体
}

函数声明与var变量声明类似,会进行提升

// function foo(){}
//函数声明提升到代码的最前边,可以直接调用函数
foo();
function foo(){
    console.log("我是一个函数");
}
foo();
//变量声明提升  变量声明提升到代码的前边,函数声明之后正常代码之前
console.log(a);   //undefined  这里不报错,因为后边有var a的声明。变量的声明进行提升到前边
var a = 'hello';  
console.log(a);   //'hello'
3.函数内部属性

只有在函数内部才能访问的属性,this也可以在函数外部进行使用

arguments

ECMAScript函数不介意传递参数的个数以及参数类型, arguments就是一个类数组对象,包含着传入函数中的所有参数。

// arguments 主要用途是保存函数参数
function foo(a,b) {
    console.log(a,b);//1 2
    // arguments
    console.log(arguments,typeof arguments);//[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 } object
    console.log(arguments[2]);//3
    console.log(arguments.length);//5
    console.log(foo.length);//2
}
foo(1,2,3,4,5)

callee是arguments对象的一个属性,是一个指针,指向拥有这个arguments对象的函数,callee属性的初始值就是正在被执行的Function对象

// 通过arguments中的callee属性去实现递归
var sum = function(n){
    if(n==1)
    {
        return 1;
    }

    return n+arguments.callee(n-1);
    //等同于 return n+sum(n-1);
}
console.log(sum(8));//36

this

面向对象语言中this表示当前对象的一个引用,在JavaScript中this不是固定不变的,它会随着执行环境的改变而改变

//实例
var person = {
  firstName: "LeBron",
  lastName : "James",
  id       : 8888,
  fullName : function() {
    return this.firstName + " " + this.lastName;
  }
};
  • 在方法中,this表示该方法所属的对象

this指向上面实例中的person对象,fullName方法所属的对象就是person

 fullName : function() {
    return this.firstName + " " + this.lastName;
  }
  • 单独使用this

    如果单独使用,this表示全局对象

    浏览器中指向window的全局对象为[object Window];

    在node中指向一个{}

    var x=this;
    console.log(x);
    
  • 在函数中使用this(默认)

函数的所属者默认绑定到this上

在浏览器中指向window的全局对象[object Window]

在 node中指向global对象

function foo(){    return this;}console.log(foo());
  • 在事件的this

    表示接收事件的元素

<button onclick="this.style.display='none'"> 点我后我就消失了 </button>
  • 显示函数绑定

在JavaScript中函数也是对象,对象则有方法,apply和call就是函数对象的方法。

apply和call两种方法允许切换函数执行的上下文环境(context),即this绑定的对象。

var person1 = {    fullName: function () {      return this.firstName + " " + this.lastName;    }  }  var person2 = {    firstName: "Zach",    lastName: "Muyi",  }  //call将函数执行环境切换到person1  var name = person1.fullName.call(person2);  // 返回 "Zach Muyi"  console.log(name);
4.IIFE 立即函数

立即调用的函数表达式,声明函数的同时立即调用这个函数。

**作用:**页面加载完成后只执行一次的设置函数

​ 将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量

函数的声明和IIFE立即执行函数的区别:

//函数的声明function foo(){  var a = 10;  console.log(a);}foo();//立即执行函数 IIFE(function foo(){  var a = 10;  console.log(a);})();

​ 函数的声明是以function关键字开头的,立即执行函数是使用一对括号将函数的声明括起来,两者达到的目的是相同的,都是声明另一个函数foo并且随后调用函数foo。

注意:如果立即执行函数前出现小括号 记得用; 分隔开

可以不命名,匿名立即执行函数

IIFE立即执行函数的多种写法:

1.对返回结果不进行处理

(function(形参){	函数体内容})(实参);

2.对返回结果不进行处理

(function(形参){	函数体内容}(实参));

3.返回的是一个布尔值 ,然后进行取反

var bools=!function(){	return true}()console.log(bools);//false

4.对于数字返回的是原来的结果,非数字返回NaN

+function(形参){	函数体内容}(实参)

5.对于数字返回的是正负符号相反,非数字返回NaN

-function(形参){	函数体内容}(实参)

6.对于数字返回的是正负符号相反再减1,非数字返回NaN

~function(形参){	函数体内容}(实参)

7.返回的结果是undefined

var a=void function(){	return 123}()console.log(a);//undefined

IIFE的基本使用

// 就像其它任何函数一样,一个立即执行函数也能返回值并且可以赋值给其它变量。var sum = (function (a,b) {  return a + b;}(1,2))console.log(sum);
案列-经典面试题-IIFE
for (var i = 0; i < 6; i++) {  function output() {    console.log(i); // 为什么输出的是6,而不是0,1,2,3,4,5    // 因为输出的 i 是全局作用域的,当循环结束后 i 的值是 6,所以输出的 i 就是6。  }}output()for (var i = 0; i < 6; i++) {  (function (j) {    console.log(j); //0,1,2,3,4,5  })(i)  // 因为 JS 中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数 i 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以每个输出访问的都是不同作用域的 i 的值 。}

IIFE所带来的好处:

弥补了js(es5)在作用域方面的缺陷:js只有全局作用域,函数作用域,从es6开始才有块级作用域

对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

5.作用域

ES5:

​ 函数作用域:在JavaScrip函数中声明的变量,会成为函数的局部变量

​ 函数内部声明的变量,在函数外部不能访问

​ 全局作用域:函数之外声明的变量,会成为全局变量

​ 函数外部声明的变量,在函数内部可以访问

​ 当函数嵌套,在这个时候内部函数与外部函数的这个变量就组成了闭包

// 块级作用域{     var  a =10; }console.log(a);// 全局作用域 在函数外部定义的变量  函数内部是可以访问到的//var v1=3;//v2=2;// 函数作用域function foo() {    var v1=123    var a=123;    console.log(a);    console.log(v1);    console.log(v2);    }foo();// 定义在函数后面 不能使用var v1=3;v2=2;// console.log(a);// is not defined 函数定义的变量在外部是访问不到的

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

作用域链

  • 什么是自由变量

当前作用域没有定义的变量,这就成为自由变量。

获取自由变量的值需要到创建这个函数的那个父级作用域找,如果没有就一直向上级祖先元素寻找(所谓的静态作用域)

var a = 100function fn() {    var b = 200    console.log(a) // 这里的a在这里就是一个自由变量  // 100    console.log(b)}fn()
  • 什么是作用域链

从父级、一层层上级祖先元素、再到全局作用域,这样一层一层的关系就是作用域链

var a = 100function F1() {  var b = 200  function F2() {    var c = 300    console.log(a) // 自由变量,顺作用域链向父作用域找 //100    console.log(b) // 自由变量,顺作用域链向父作用域找 //200    console.log(c) // 本作用域的变量  //300  }  F2()}F1()
6.函数调用

函数声明好之后并不会直接运行,需要进行调用才能运行

调用函数的方式不仅限于()执行:

  • 函数名(实参列表)
  • 函数名.call(执行环境对象,实参列表数组)
  • 函数名.apply(执行环境对象,实参列表数组)
  • 函数名.bind(执行环境对象,实参列表)

当我们不得不把一个对象保存在一个变量中,且需要调用相应的函数的属性时,this该如何指向函数

改变函数内部this指针的指向方法

  1. call(执行环境对象,实参列表)

​ 调用call方法,第一个参数就是要把b添加到那个环境中,也就是this指向那个对象

  1. apply(执行环境对象,实参列表数组)

​ apply也可以有多个参数,但是第二参数必须是一个数组

  1. bind(执行环境对象,实参列表)

​ bind一样可以有多个参数,并且参数可以执行的时候再次添加,参数是按照形参的顺序进行的

注意:

  • 如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象

  • call 和apply都是改变上下文中的this并立即执行这个函数;bind方法通过赋值给新的变量,让对应的函数想什么时候调用就什么时候调用,并且可以将参数在执行的时候添加

7.函数的应用
  • 回调函数

主函数的事先做完再回头调用传进来的那个函数

案例:

//定义主函数,回调函数作为参数function A(callback) {  callback();  console.log('我是主函数');}//定义回调函数function B() {  // 模仿延时操作  setTimeout(() => {    console.log('我是回调函数');  }, 3000);}//调用主函数,将函数B传进去A(B);

作用在于:回调函数一般都用在操作上面,因为主函数不用等待回调函数执行完,可以直接先执行自己的代码。例如ajax请求,处理文件等

  • 作为返回值

在作用域链中,就使用到函数作为返回值得用法:

x var a = 10function fn() {      var b = 20      function bar() {            console.log(a + b) //30      }      return bar}                                                     var x = fn(), // 执行fn() 返回的是barb = 200x() //执行x,就是执行bar函数
8.闭包 是一种特殊的对象
  • 什么是闭包:闭包是指有权访问另一个函数作用域中变量得函数,闭包是一个不会销毁的函数空间

闭包生成的条件

  • 函数嵌套函数
  • 内部函数引用了外部函数中的数据(属性、函数)
  • 参数和变量不会被回收

创建闭包就是一个函数里包含另一个函数,最后在函数返回另一个函数。以下closure就是一个闭包

// 简单创建一个闭包// 函数嵌套函数function foo() {    var a=1,b=2;    function closure() {        //内部可以调用外部作用域的属性        console.log(a,b);        console.log(this.a);//内部函数没声明a变量    }    // 把内部函数当作一个返回值进行返回    return closure;}var result=foo();result();//1 2		 //undefined

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域

  • 闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function f1() {  var n = 999;  nAdd = function () { n += 1 }  function f2() {    console.log(n);  }  return f2;}var result = f1();result(); // 999nAdd();result(); // 1000

函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

在"nAdd=function(){n+=1}"这一行要注意,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包需要注意的点:

  1. 因为闭包会让函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包。解决方法是在退出函数之前,将不使用的局部变量全部删除
  2. 闭包会在父函数外部,改变父函数内部变量的值。当把父函数当作对象使用,闭包当作公用方法,把内部变量当作私有属性,这是不能随便改变父函数内部变量的值。因为多个子函数的scope都是同时指向父级,是完全共享的,当父级的变量对象被修改时,所有子函数都受影响。

闭包this指向的问题

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var name = "window-closure";
        var obj = {
            name: "obj-closure",
            say: function () {
                console.log(this.name);//obj-clowindow
                var that = this;
                return function () {
                    console.log(that.name);//obj-closure
                    console.log(this.name);//window-closure
                    console.log(this);//window对象
                }
            }

        }
        var x = obj.say();
        x();
        
    </script>
</body>

</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值