JavaScript函数

JavaScript函数

函数就是一些功能或语句的封装。在需要的时候,通过调用的形式,执行这些语句。函数也是一个对象

函数定义

我们使用function关键字定义函数,中文含义是“函数”、“功能”。可以使用如下方式进行定义。

函数声明

使用函数声明来创建一个函数。语法:

function 函数名([形参1,形参2...形参N]){  // 备注:语法中的中括号,表示“可选”
	// 函数体语句
}
  • 函数名:命名规定和变量的命名规定一样,必须符合JS标识符的命名规则。只能是字母、数字、下划线、美元符号,不能以数字开头。
  • 圆括号里,是形参列表,可选。即使没有形参,也必须书写圆括号。
  • 大括号里,是函数体语句。

函数表达式

使用函数表达式来创建一个函数。语法:

const fun2  = function([形参1,形参2...形参N]){
	语句....
}
  • 上面的 fun2 是变量名,不是函数名。
  • 函数表达式的声明方式跟声明变量类似,只不过变量里存的是值,而函数表达式里存的是函数。
  • 函数表达式也可以传递参数。

从方式二的举例中可以看出:所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。因为,一个匿名函数终究还是要给它一个接收对象,进而方便地调用这个函数。

使用构造函数

使用构造函数new Function()来创建一个对象。这种方式,用的少。语法:

const 变量名/函数名  = new Function('形参1', '形参2', '函数体');

注意,Function 里面的参数都必须是字符串格式。也就是说,形参也必须放在字符串里;函数体也是放在字符串里包裹起来,放在 Function 的最后一个参数的位置。

所有的函数,都是 Fuction 的“实例”(或者说是“实例对象”)。函数本质上都是通过 new Function 得到的。

2、函数既然是实例对象,那么,函数也属于“对象”。还可以通过如下特征,来佐证函数属于对象:

(1)我们直接打印某一个函数,比如 console.log(fun2),发现它的里面有__proto__。(这个是属于原型的知识,后续再讲)

(2)我们还可以打印 console.log(fun2 instanceof Object),发现打印结果为 true。这说明 fun2 函数就是属于 Object。

函数调用

调用函数即:执行函数体中的语句。函数必须要等到被调用时才执行。

普通调用

// 写法1(最常用)
函数名();

// 写法2
函数名.call();

对象方式调用

var obj = {
	a: 'qianguyihao',
	fn2: function() {
		console.log('千古壹号,永不止步!');
	},
};

obj.fn2(); // 调用函数

立即执行

(function() {
	console.log('我是立即执行函数');
})();

通过构造函数调用

function Fun3() {
	console.log('hello~');
}

new Fun3();

绑定事件函数

<!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>
        <div id="btn">我是按钮,请点击我</div>

        <script>
            var btn = document.getElementById('btn');
            //2.绑定事件
            btn.onclick = function() {
                console.log('点击按钮后,要做的事情');
            };
        </script>
    </body>
</html>

定时器函数

    let num = 1;
   setInterval(function () {
       num ++;
       console.log(num);
   }, 1000);

函数参数

函数的参数包括形参和实参。形参是函数内的一些待定值。在调用函数时,需传入这些参数的具体值(即实参)。

可以在函数的()中指定一个或多个参数,也可以不指定参数。多个参数之间用英文逗号隔开。

形参

  • 概念:形式上的参数。定义函数时传递的待定值(此时并不知道是什么值)。
  • 声明形参相当于在函数内部声明了变量,但并不赋值。也可以说,形参的默认值是 undefined

实参

  • 概念:实际上的参数。调用函数时传递的具体值。实参将传递给函数中对应的形参。
// a, b 是形参,表示待定值
function add(a, b) {
const sum = a + b;
console.log(sum);
}

// 1, 2 是实参,表示传入的具体值。调用函数时,传入实参
add(1, 2);

实际参数和形式参数的个数,可以不同。调用函数时,解析器不会检查实参的数量。

  • 如果实参个数 > 形参个数,则末尾的实参是多余的,不会被赋值,因为没有形参能接收它。
  • 如果实参个数 < 形参个数,则末尾的形参是多余的,值是 undefined,因为它没有接收到实参。(undefined参与运算时,表达式的运算结果为NaN)
	function sum(a, b) {
		console.log(a + b);
	}

	sum(1, 2);  // 3
	sum(1, 2, 3); // 3
	sum(1); //Nan

函数的实参可以是任意的数据类型。调用函数时,解析器不会检查实参类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型检查。

函数返回值

函数体内可以没有返回值,也可以根据需要加返回值。语法格式:return 函数的返回值

return关键字的作用既可以是终止函数,也可以给函数添加返回值。

解释:

(1)return 后的返回值将会作为函数的执行结果返回,可以定义一个变量,来接收该返回值。

(2)在函数中,return后的语句都不会执行。也就是说,函数在执行完 return 语句之后,会立即退出函数。

(3)如果return语句后不跟任何值,就相当于返回一个undefined

(4)如果函数中不写return,则也会返回undefined

(5)返回值可以是任意的数据类型,可以是对象,也可以是函数。

(6)return 只能返回一个值。如果用逗号隔开多个值,则以最后一个为准。

  • break :结束当前的循环体(如 for、while)
  • continue :跳出本次循环,继续执行下次循环(如 for、while)
  • return :1、退出循环。2、返回 return 语句中的值,同时结束当前的函数体内的代码,退出当前函数。

函数隐藏参数

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  • 1.函数的上下文对象 this
  • 2.封装实参的对象 arguments
function foo() {
    console.log(arguments);
    console.log(typeof arguments);
}

foo('a', 'b');

函数内的 arguments 是一个类数组对象,里面存储的是它接收到的实参列表。所有函数都内置了一个 arguments 对象,有个讲究的地方是:只有函数才有arguments。

具体来说,在调用函数时,我们所传递的实参都会在 arguments 中保存。arguments 代表的是所有实参

arguments 的展示形式是一个伪数组。意思是,它和数组有点像,但它并不是数组。它具有以下特点:

  • 可以进行遍历;具有数组的 length 属性,可以获取长度。
  • 可以通过索引(从0开始计数)存储数据、获取和操作数据。比如,我们可以通过索引访问某个实参。
  • 不能调用数组的方法。比如push()、pop() 等方法都没有。

this

this 指向的是一个对象,这个对象我们称为函数执行的 上下文对象。

在ES5语法中,根据函数的调用方式的不同,this 会指向不同的对象:

1、以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如fun();相当于window.fun();

2、以方法的形式调用时,this 指向调用方法的那个对象

3、以构造函数的形式调用时,this 指向实例对象

4、以事件绑定函数的形式调用时,this 指向绑定事件的对象

5、使用 call 和 apply 调用时,this 指向指定的那个对象

作用域

用域是一个变量或函数的作用范围。作用域在函数定义时,就已经确定了,在 JS 中,一共有两种作用域:全局作用域和局部作用域

全局作用域

直接编写在 script 标签中的 JS 代码,都在全局作用域。全局作用域在页面打开时创建,在页面关闭时销毁。

在全局作用域中有一个全局对象 window,它代表的是浏览器的窗口,由浏览器创建,我们可以直接使用。相关知识点如下:

  • 创建的变量都会作为 window 对象的属性保存。比如在全局作用域内写 const a = 100,这里的 a 等价于 window.a
  • 创建的函数都会作为 window 对象的方法保存。

在内部作用域中可以访问外部作用域的变量,在外部作用域中无法访问到内部作用域的变量。

const a = 'aaa';
function foo() {
    const b = 'bbb';
    console.log(a); // 打印结果:aaa。说明 内层作用域 可以访问 外层作用域 里的变量
}

foo();
console.log(b); // 报错:Uncaught ReferenceError: b is not defined。说明 外层作用域 无法访问 内层作用域 里的变量

局部作用域

(局部作用域):作用于函数内的代码环境。

根据作用域的不同我们可以把变量分为两类:全局变量、局部变量。

全局变量

  • 在全局作用域下声明的变量,叫「全局变量」。在全局作用域的任何一地方,都可以访问这个变量。
  • 在全局作用域下声明的变量是全局变量。

局部变量

  • 定义在函数作用域的变量,叫「局部变量」。仅限函数内部访问这个变量。
  • 函数的形参也是属于局部变量。

从执行效率来看全局变量和局部变量:

  • 全局变量:只有浏览器关闭时才会被销毁,比较占内存。
  • 局部变量:当其所在的代码块运行结束后,就会被销毁,比较节约内存。

注意

  1. 无论是在函数外还是函数内,变量如果未经声明就赋值(意思是,如果不加var/let/const),这个变量是全局变量
  2. 如果局部变量和全局变量重名,则在函数内部,变量是以局部变量为准。
  3. 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError。
  4. 在函数中要访问全局变量可以使用 window 对象。(比如说,全局作用域和函数作用域都定义了变量 a,如果想访问全局变量,可以使用window.a

作用域预处理

变量提升

使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被声明(但是不会赋值)。但是如果声明变量时不是用 var 关键字(比如直接写a = 1),则变量不会被声明提前。

console.log(a);  // undefined
var a = 123;

console.log(b); //Uncaught ReferenceError: a is not defined。
b = 123; //此时a相当于window.a
foo();

function foo() {
    if (false) {
        var i = 123;
    }
    console.log(i); // undefined
} // 这个例子,再次说明了:变量 i 在函数执行前,就被提前声明了,只是尚未被赋值。

函数提升

使用函数声明的形式创建的函数function foo(){}会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成。所以,在代码顺序上,我们可以先调用函数,再定义函数。

fn1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前声明了, 所以此处可以调用函数

function fn1() {
    console.log('我是函数 fn1');
}

使用函数表达式创建的函数const foo = function(){}不会被声明提前,所以不能在声明前调用。

很好理解,因为此时只是变量 foo 被提升了,且值为 undefined,并没有把 function(){} 赋值给 foo。

// 不会报错,可以正常执行函数,正常打印结果
fun1();

// 此时 fun2 相当于 undefined。执行时会报错:Uncaught ReferenceError: Cannot access 'fun2' before initialization
fun2();

// 函数声明,会被提前声明
function fun1() {
  console.log('我是 fun1 函数');
}

// 函数表达式,不会被提前声明
const fun2 = function () {
  console.log('我是 fun12 函数');
};

函数提升优先于变量提升

在JS的规则中,函数提升优先于变量提升。来看看下面这段代码,你认为打印结果应该如何:(这是一道经典面试题)

fun(); // 打印 B

// 变量提升
var fun = function () {
  console.log('A');
};

// 函数提升
function fun() {
  console.log('B');
}

fun(); // 打印 A

预编译

在讲预编译前,我们先来普及下面两个规律

规律1:任何变量,如果未经声明就赋值,此变量是属于 window 的属性,而且不会做变量提升。(注意,无论在哪个作用域内赋值)

比如说,如果我们直接在代码里写 console.log(a),这肯定会报错的,提示找不到 a。但如果我直接写 a = 100,这就不会报错,此时,这个 a 就是 window.a

规律2:一切声明的全局变量,全是window的属性。(注意,这里说的是在全局作用域内声明的全局变量,不是说局部变量)

比如,当定义 var a = 200 时,这个 a 就是 window.a

由此,我们可以看出:window 代表了全局作用域(是说「代表」,没说「等于」)

function foo() {
    var a = b = 100; // 连续赋值
}

foo();

console.log(window.b); // 在全局范围内访问 100 
console.log(b); // 在全局范围内访问 b,但是前面没有加 window 这个关键字  100

console.log(window.a); // 在全局范围内访问 a  undefined
console.log(a); // 在全局范围内访问 a,但是前面没有加 window 这个关键字 报错

闭包

我们知道,变量根据作用域的不同分为两种:全局变量和局部变量。

  • 函数内部可以访问全局变量和局部变量。
  • 函数外部只能访问全局变量,不能访问局部变量。
  • 当函数执行完毕,本作用域内的局部变量会销毁。

在有些场景下,我们就是想要在函数外部访问函数内部作用域的局部变量,那要怎么办呢?这就引入了闭包的概念。

闭包(closure)的概念

闭包:如果外部作用域有权访问另外一个函数内部局部变量时,那就产生了闭包。这个内部函数称之为闭包函数。注意,这里强调的是访问局部变量

function fun1() {
    var a = 10;
    function fun2() {
        console.log(a)
    }
    return fun2();
    
}

fun1()  // 10

上方代码中,外部作用域(即全局作用域) 访问了函数 fun1 中的局部变量,那么,在 fun1 中就产生了闭包,函数 fun1是闭包函数。

全局作用域中,并没有定义变量a。正常情况下作为函数内的局部变量 a,无法被外部访问到。但是通过闭包,我们最后还是可以在全局作用域中拿到局部变量 a 的值。

注意,闭包函数是fun1,不是fun2。fun2在这里的作用是让全局作用域访问到变量a,fun2只是一个桥梁。

闭包的生命周期

  1. 产生:内部函数fn1被声明时(即被创建时,不是被调用时)就产生了。
  2. 死亡:嵌套的内部函数成为垃圾对象时。(比如fun1 = null,就可以让 fun1 成为垃圾对象)

闭包的表现形式

将一个函数作为另一个函数的返回值
    function fn1() {
      var a = 2

      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }

    var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2
    f() // 3       //执行fn2
    f() // 4       //再次执行fn2
将函数作为实参传递给另一个函数调用

在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

    function showDelay(msg, time) {
      setTimeout(function() {  //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg
        alert(msg)
      }, time)
    }
    showDelay('qianguyihao', 2000)

上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

闭包的作用

  • 作用1:延长局部变量的生命周期。
  • 作用2:让函数外部可以操作(读写)函数内部的数据(变量/函数)。
  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值