六 JavaScript函数的概念

本文详细介绍了JavaScript中的函数概念、定义方式(声明、表达式、箭头函数)、参数处理(形参、实参、默认值、对象传参)、作用域(全局、块级、函数作用域)、window对象、变量提升、this指向、严格模式以及箭头函数的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

六 函数

6.1 函数的概念

函数也是一个对象,它具有其他对象所有的功能,函数中可以存储代码,且可以在需要时调用这些代码

语法:

function 函数名(){
    语句...
}

调用函数

调用函数就是执行函数中存储的代码;语法:函数对象()

使用typeof检查函数对象时会返回function

// 创建一个函数对象
function fn() {
    console.log("函数体内容");
}
fn()
console.log(typeof fn);
// 执行结果:
// 函数体内容
// function

6.2 函数的定义方式

6.2.1 函数声明

function 函数名(){
    语句...
}

eg:

function fn1(){
    console.log("函数声明定义函数");
}

6.2.2 函数表达式

const 变量 = function(){
    语句...
}

eg:

const fn2 = function(){
    console.log("函数表达式定义的函数");
}

6.2.3 箭头函数

([参数]) => 返回值

例子:

无参箭头函数:() => 返回值

一个参数的:a => 返回值

多个参数的:(a, b) => 返回值

只有一个语句的函数:() => 返回值

只返回一个对象的函数:() => ({...})

有多行语句的函数:

() => {
    ....   
    return 返回值
}
()=>{
    语句...
}

eg:

const fn3 = () =>{
    console.log("箭头函数");
}
fn1()
fn2()
fn3()

/**
执行结果:
    函数声明定义函数
    函数表达式定义的函数  
    箭头函数
*/

6.3 参数

6.3.1 形参与实参

形式参数:在定义函数时,可以在函数中指定数量不等的形式参数(形参),在函数中定义形参,就相当于在函数内部声明了对应的变量,但是没有赋值。

function fn(a,b){}中a与b就是形式参数

实际参数:在调用函数时,可以在函数的()传递数量不等的实参,实参会赋值给其对应的形参。

函数调用时,let name = "Joe";fn(name) 中参数name就是实际参数,即实参。

  • 如果实参和形参数量相同,则对应的实参赋值给对应的形参

  • 如果实参多余形参,则多余的实参不会使用

  • 如果形参多余实参,则多余的形参为undefined

  • JS中不会检查参数的类型,可以传递任何类型的值作为参数

普通函数接收参数:

function fn(a,b,c) {
    console.log("获取到的参数:",a,b,c);
}
fn(1,2) // 获取到的参数: 1 2 undefined

箭头函数接收参数:

const fn = (a,b) =>{
    console.log("获取到的参数:",a,b);
}
fn(1,2) // 获取到的参数: 1 2 undefined

当箭头函数中只有一个参数时,可以省略()

const fn2 = a=>{
    console.log(a);
}
fn2(1) // 1

6.2.2 参数默认值

function fn(a=1,b=2,c=3){
    console.log(a,b,c);
}
fn() // 1 2 3

6.2.3 对象作为参数

修改对象时,如果有其他变量指向该对象则所有指向该对象的变量都会受到影响;

传递参数时

function fn(a){
    console.log("函数内获取的初始值:",a.animal);
    a.animal = "狗"
    console.log("函数内修改后的值:"+a.animal);
}
let a = {animal:"猫"}
fn(a)
console.log("函数外的值:"+a.animal);
/**
	执行结果:
		函数内获取的初始值: 猫
        函数内修改后的值:狗
        函数外的值:狗
*/

6.2.4 函数传参原理详解

JavaScript函数传参原理详解——值传递还是引用传递_js函数是值传递还是引用传递-优快云博客

事实上,在js中,不管对于值类型还是引用类类型,都是按值传递的,区别在于,对于值类型,传参发生时,复制的是类型本身的值,而对于引用类型,复制的是类型的地址。

6.2.5 函数作为参数

在JS中,函数也是一个对象(一等函数)。 别的对象能做的事情,函数也可以。

function fn1(a){
    console.log("a:",a);
    a()
}
function fn2(){
    console.log("我是fn2的函数体");
}
fn1(fn2)

6.2.6 函数的返回值

在函数中,可以通过return关键字来指定函数的返回值,返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回;

任何值都可以作为返回值使用(包括对象和函数之类),

  • 如果return后不跟任何值,则相当于返回undefined

  • 如果不写return,那么函数的返回值依然是undefined

return一执行函数立即结束

function sum(a,b){
    return a+b
}
console.log(sum(5,6)); // 11

箭头函数的返回值:箭头函数的返回值可以直接写在箭头后(简写)

const sum = (a,b)=>a+b
console.log(sum(5,3));

如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来

错误写法:

const sum = (a,b)=>{animal:"猫"}
console.log(sum(5,3)); // undefined

正确写法:

const sum = (a,b)=>({animal:"猫"})
console.log(sum(5,3));

PS:这里有一个小面试题

function fn(){
    console.log(a);
    return
    var a = 666
}
fn()

上述函数的执行结果是undefined而不是 a is not defined

说明函数中return后面var的变量也存在变量提升特性

6.3 作用域(scope)

6.3.1 参数作用域

作用域指的是一个变量的可见区域

作用域有两种:

  1. 全局作用域

    • 全局作用域在网页运行时创建,在网页关闭时消耗
    • 所有直接编写到script标签中的代码都位于全局作用域中
    • 全局作用域中的变量是全局变量,可以在任意位置访问
  2. 局部作用域

    • 块作用域是一种局部作用域
    • 块作用域在代码块执行时创建,代码块执行完毕它就销毁
    • 在块作用域中声明的变量是局部变量,只能在块内部访问,外部无法访问

比如let定义的参数就具有块作用域,而var它当前的执行上下文及其闭包(嵌套函数),或者对于声明在任何函数外的变量来说是全局作用域

var a = 123
{
    console.log(a); // 123
    let b = 0
}
console.log(b); //  b is not defined

/**
	var 定义的变量a的作用域是全局作用域
	let 定义的变量b的作用域是{}包起来的局部作用域

*/

6.3.2 函数作用域

  • 函数作用域也是一种局部作用域
  • 函数作用域在函数调用时产生,调用结束后销毁
  • 函数每次调用都会产生一个全新的函数作用域
  • 在函数中定义的变量是局部变量,只能在函数内部访问,外部无法访问
function fn(){
    let a = "Hello World"
    console.log(a);
}
fn()
console.log(a); 
/**
	执行结果:
		1. Hello World
		2. Uncaught ReferenceError ReferenceError: a is not defined
		
*/

6.3.3 作用域链

当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,

  • 如果找到了则直接使用;

  • 如果没找到,则去上一层作用域中寻找,找到了则使用;

  • 如果没找到,则继续去上一层寻找,以此类推

  • 如果一直到全局作用域都没找到,则报错 xxx is not defined

后代可以使用父辈的变量,父辈不能使用后代定义的变量

{
    let a = 666
    {
        {
            {
                {
                    {
                        console.log("a:",a);
                    }
                }
            }
        }
    }
}

6.4 window对象

在浏览器中,浏览器为我们提供了一个window对象,可以直接访问,window对象代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作;

除此之外window对象还负责存储JS中的内置对象和浏览器的宿主对象

window对象的属性可以通过window对象访问,也可以直接访问

函数就可以认为是window对象的方法

<!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>
        console.log(window);
    </script>
</body>
</html>
image-20240203174751796

在全局中使用var声明的变量都会作为window对象的属性保存

    <script>
        var a = 666
        console.log(window.a);
    </script>

运行结果:
在这里插入图片描述

使用function声明的函数,都会作为window对象的方法保存

    <script>
        function fc(){
            console.log("自定义函数");
        }
        window.fc()
    </script>

执行结果:

在这里插入图片描述

使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)

var虽然没有块作用域,但有函数作用域

function fn(){
    var a = 666
}
console.log(a);
// Uncaught ReferenceError ReferenceError: a is not defined

6.5 提升

6.5.1 变量提升

使用var声明的变量,它会在所有代码执行前被声明,所以我们可以在变量声明前就访问变量。

6.5.2 函数提升

使用函数声明创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数。

console.log(a); // Uncaught ReferenceError ReferenceError: a is not defined

由上述代码可见,如果运行时使用了未定义的变量,会报错:a is not defined

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

由上述代码可见,使用var定义的变量a虽然在使用他之后才定义赋值,但是没有报错not defined所以可得var变量的声明提前了。

let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问

console.log(a); // Uncaught ReferenceError ReferenceError: Cannot access 'a' before initialization
let a = 666 

使用let关键字定义参数a,最后报的错也不是not defined而是Cannot access 'a' before initialization

ps:let有的功能const也一样

6.6 debug

使用debug关键字在代码中打一个断点

在浏览器中(index.html):

在这里插入图片描述

在vscode中(test.js):
在这里插入图片描述

6.7 立即执行函数(IIFE)

见:IIFE(立即调用函数表达式) - MDN Web 文档术语表:Web 相关术语的定义 | MDN (mozilla.org)

立即执行函数是一个匿名的函数,并且他只会调用一次;

可以利用IIFE来创建一个一次性的函数作用域,避免变量冲突的问题。

function这个关键字,既可以当做语句,也可以当做表达式。

规定:如果function出现在行首,一律解析成语句。行首是function关键字时,这一段都是函数定义,不应该以圆括号结尾,所以会报错。当不让function出现在行首时,这将被理解为一个表达式,最简单的处理方式就是将其放在一个圆括号里。

以圆括号开头,这将被理解为表达式,而不是一个函数定义语句,所以就避免了错误,这就叫“立即执行函数”。

其他的写法:(就是不让function关键字在句首呗?)

(function() {alert('匿名函数')}())	//用括号将整个表达式包起来
(function() {alert('匿名函数')})()	//用括号将函数包起来
!function() {alert('匿名函数')}()	
+function() {alert('匿名函数')}()
-function() {alert('匿名函数')}()
~function() {alert('匿名函数')}()
void function() {alert('匿名函数')}()
new function() {alert('匿名函数')}()

6.7.1 立即执行函数的作用

  1. 避免污染全局命名空间:因为我们的程序可能包括很多来自不同源文件的函数和全局变量,因此限制全局变量的数量非常重要。如果我们有一些不再使用的初始化代码,我们可以使用 IIFE 模式。由于我们不会再次重用代码,因此在这种情况下使用 IIFE 会比使用函数声明或者函数表达式更好。
(() => {
  // 初始化代码
  let firstVariable;
  let secondVariable;
})();

// firstVariable 和 secondVariable 变量在函数执行后会被丢弃
  1. ES6 之前在 For 循环中使用 var

ES6 引入 letconst 声明和块级作用域之前,我们可以在一些旧代码中看到 IIFE 的以下用法。通过 var 声明变量,只有函数作用域和全局作用域。假设我们创建两个按钮,文本按钮 0 和按钮 1,并且当我们点击它们时,想让它们 alert 0 和 1。下面的代码不能起作用:

for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // 2

当点击时,按钮 0 和按钮 1 都会 alert 2,因为 i 是全局的,并且值为 2。在 ES6 之前为了解决这个问题,我们可以使用 IIFE 模式:

for (var i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = (function (copyOfI) {
    return () => {
      console.log(copyOfI);
    };
  })(i);
  document.body.appendChild(button);
}
console.log(i); // 2

当点击时,按钮 0 和按钮 1 会 alert 0 和 1,变量 i 是全局的。更简单的是使用 let 声明变量:

for (let i = 0; i < 2; i++) {
  const button = document.createElement("button");
  button.innerText = `Button ${i}`;
  button.onclick = function () {
    console.log(i);
  };
  document.body.appendChild(button);
}
console.log(i); // Uncaught ReferenceError: i is not defined.

… 以上用来入门就可以了

6.8 this

函数在执行时,JS解析器每次都会传递进一个隐含的参数,这个参数就叫做this

this会指向一个对象

this所指向的对象会根据函数调用方式的不同而不同

  1. 以函数形式调用时,this指向的是window
  2. 以方法的形式调用时,this指向的是调用方法的对象

通过this可以在方法中引用调用方法的对象

箭头函数没有自己的this,它的this有外层作用域决定且箭头函数的this和它的调用方式无关。

function fn(){
    console.log("this:",this);
}

const obj1 = {
    animal:"猫",
    method:function(){
        console.log("this:",this);
    }
}

const obj2 = {
    animal:"猫",
    method:()=>{
        console.log("this:",this)
    }
}
fn() // this: global {global: global, clearInterval: ƒ, clearTimeout: ƒ, setInterval: ƒ, setTimeout: ƒ, …}
obj1.method() // this: {animal: '猫', method: ƒ}
obj2.method() // this: {} 

vscode运行结果:

在这里插入图片描述

在vscode里面,也就是nodejs环境下给我返回了个{}而在浏览器环境下:

在这里插入图片描述

有无懂哥知道为什么是{}

6.9 严格模式

JS运行代码的模式有两种:

  1. 正常模式:默认情况下代码都运行在正常模式中,在正常模式,语法检查并不严格,它的原则是:能不报错的地方尽量不报错,而这种处理方式导致代码的运行性能较差
  2. 严格模式:在严格模式下,语法检查变得严格
    1. 禁止一些语法
    2. 更容易报错
    3. 提升了性能

在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能。

"use strict" // 开启全局的严格模式
a = 100
console.log(a); // Uncaught ReferenceError ReferenceError: a is not defined
// "use strict" // 全局的严格模式
a = 100
console.log(a); // 100 不报错
function fn(){
     "use strict" // 函数的严格的模式
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值