【JS从入门到精通】06-函数
函数
一、函数的简介
函数也是一个对象,可以封装一些功能(代码),在需要时可以执行这些功能(代码),可以保存一些代码在需要的时候调用
使用typeof
检查一个函数对象时,会返回function
// 创建一个函数对象
// 可以将要封装的代码以字符串的形式传递给构造函数
var fun = new Function("console.log('Hello World.');");
// 封装到函数中的代码不会立即执行
// 函数中的代码会在函数调用的时候执行
// 调用函数语法:函数对象()
// 当调用函数时,函数中封装的代码会按照顺序执行
fun(); // Hello World.
1.1 使用函数声明来创建一个函数
function 函数名([形参1, 形参2...形参N]) {
语句...
}
// 调用函数
函数名();
示例
function fun1(){
console.log("Hello world.");
alert("Hello World!");
document.write("Helloworld");
}
fun1();
1.2 使用函数表达式(匿名函数)来创建一个函数
var 函数名 = function([形参1, 形参2...形参N]) {
语句...
};
// 调用函数
函数名();
示例
var fun1 = function(){
console.log("Hello world.");
alert("Hello World!");
document.write("Helloworld");
};
fun1();
-
不能使用关键字以及保留字
-
不能是纯数字或者是数字开头
-
可以出现英文+数字+下划线,一般下划线在中间位置
-
建议使用驼峰命名法,见名知意
-
方法名字与变量名一样都必须是JavaScript合法的标识符。
-
二、函数的参数
定义一个用来求两个数和的函数
可以在函数的()
中来指定一个或多个形参(形式参数)多个形参之间使用,
隔开,声明形参就相当于在函数内部声明了对应的变量
在调用函数时,可以在()
中指定实参(实际参数)
-
调用函数时解析器不会检查实参的类型。所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查
-
调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值。如果实参的数量少于形参的数量,则没有对应实参的形参将是
undefined
// 创建一个函数,用来计算三个数的和
function sum(a, b, c) {
alert(a + b + c);
}
sum(1, 2, 3, 4); // 6
三、函数的返回值
3.1 什么情况下使用函数的返回值
当在函数的外部想要获取到函数内部的运行结果(或者是某一个数据)的时候就必须使用函数的返回值,哪里调用函数,哪里接收返回值
可以使用return
来设置函数的返回值语法:return 值
return
后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果
在函数中return
后的语句都不会执行
如果return
语句后不跟任何值,就相当于返回一个undefined
;如果函数中不写return
,则也会返回undefined
return
后可以跟任意类型的值
2.语法设置:
function 函数名(){
return 值;
}
- 没有return关键字
- return后面代码不执行
- 有return,后面没值
- 有且一个return,多个使用分支语句
- return后面默认返回一个值
// 创建一个函数,用来计算三个数的和
function sum(a, b, c) {
// var result = a + b + c;
// return result;
return a + b + c;
}
// 调用函数
// 变量result的值就是函数的执行结果
// 函数返回什么result的值就是什么
var result = sum(1, 2, 3);
console.log("result = " + result);
实参可以是任意的数据类型,也可以是一个对象。当我们的参数过多时,可以将参数封装到一个对象
function sayHello(o){
console.log("我是" + o.name
+ ",今年我" + o.age
+ "岁了,我是一个" + o.gender
+ "人,我住在" + o.address);
}
var obj = {
name: "孙悟空",
age: 1000,
gender: "男",
address: "花果山"
};
sayHello(obj); // 我是孙悟空,今年我1000岁了,我是一个男人,我住在花果山
实参可以是一个对象,也可以是一个函数
function calCirc(radius) {
return Math.PI * Math.pow(radius, 2);
}
function fun(a){
console.log("a = " + a);
}
fun(calCirc);
// a = function calCirc(radius) {
// return Math.PI * Math.pow(radius, 2);
// }
fun(calCirc(10)); // a = 314.1592653589793
calCirc(10)
- 调用函数
- 相当于使用的函数的返回值
calCirc
- 函数对象
- 相当于直接使用函数对象
函数也是一个对象,特殊在其具有功能
break、continue、return对比
break
可以退出当前的循环continue
用于跳过当次循环return
可以结束整个函数
在函数内部再声明一个函数
function fun3(){
function fun4(){
console.log("I'm fun4.");
}
fun4();
}
fun3(); // I'm fun4.
function fun5(){
function fun6(){
console.log("I'm fun6.");
}
return fun6;
}
var a = fun5();
a(); // I'm fun6.
fun5()(); // I'm fun6.
4.1 概念
通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围,
作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
-
作用:无论是变量还是函数在代码中的某一个区域中的有效生命周期范围
-
域:区域,位置
-
作用域: 通俗的说,就是数据起作用的范围(某条数据可以在什么范围内使用)
4.2 分类
-
全局作用域:在script标签的任意位置都可以进行访问变量/函数
通过var或function声明在全局(声明在任意函数之外和代码块之外)中的数据,
在全局的任意地方都可以调用或修改(即全局变量)和在window下的属性
//声明在全局中的变量 var a = 0; console.log(a); //可在全局任意地方使用 function fn(){ console.log(a); //可以在函数内部中使用 a = 10;//可以在任意地方修改全局中的变量 } //函数调用 fn(); console.log(a);
运行:
-
函数作用域(局部作用域):变量仅能在声明的函数内部可见,函数外是不允许访问的
声明在函数内部的某个数据(var,function,参数),只能在函数内部使用(函数的局部作用域)
function fun(){ //局部作用域下的变量:局部变量 var b = 2; //生命周期只能在这个fun函数的内部 console.log(b); // 2 //局部作用域下的函数:局部函数 function fun1(){ console.log(b); //2 b默认和fun1都是同作用域,可以之间相互访问 } fun1(); //局部函数的自身调用 } fun();
运行:
4.3 作用域变量
-
局部变量:在函数内部定义的变量是局部变量
-
全局变量:在函数之外定义的变量是全局变量
特别需要注意:如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
var b = 100; //全局作用域下声明的变量 周期范围是在整个script标签的任意位置 //先在全局变量声明了a变量 但是没有值 //var a; function fun(){ var c = 80; //局部作用域下声明的变量 周期范围只能在当前fun函数的内部的任意位置 a = 100; //全局变量 //在函数内部访问 console.log(a); //100 } fun(); //在函数外部访问 console.log(a);
运行:
但是如果该变量出现在形式参数中,则为局部变量。
function fun(a){ //在调用函数进来解析这个函数体的时候,发现了形式参数 //然后再函数体内部创建了一个与形参同名的局部变量 //var a = 10; //作用域:属于fun函数的局部 console.log(a); //此时的a为局部变量 10 } fun(10); //在函数外部无法直接访问,报错 console.log(a); // 报错
运行:
总结以下函数内部写变量中不带var的情况:
-
首先找形参
-
如果形参有,当做是局部变量对待。
-
如果形参没有,就去找外部全局看有没有,有就是函数内部在操作全局变量
-
如果全局变量中也没有,那么此时这个变量相当于在全局定义了一个变量;
6.1 概念
其实就是JS中数据的查找规则
作用域链决定了哪些数据能被函数访问。
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
6.2 查找规则
在JS中我们查找一个变量时,会先在当前作用域进行查找,如果能找到,就直接使用这个变量,如果找不到,就从向上找父作用域的数据,还找不到就接着向上,一直找到全局作用域(window对象),window都找不到就报错。
var a = 2;
function test(){
var a = 1;
console.log('结果:',a); //1
}
test();
运行:
//调用fn()时,在其子函数fn2被调用时,首先会在fn2()自己的作用域内查找变量a
//找不到就在其父级作用域即fn()作用域中查找,即a=10,然后打印a=10
function fn(){
var a = 10;
function fn2(){
console.log('结果:',a); //10
}
fn2()
}
fn();
JS在读取到一个script标签(或者一个函数作用域)时,会先进行一个预解析的过程,在这个过程中,会把var声明的变量和function声明的函数体,提升到整个script标签(或者一个函数作用域)最前边去。在预解析完之后,JS才会从上到下一行一行解析代码并执行。
7.1 分类
-
变量预解析(变量提升)
-
函数预解析(函数提升)
7.2 解析过程
-
先去解析声明定义的函数,再去解析所有的带var变量
-
提取重点:- 函数提升一定在变量提升之前的- 变量只有带有var标识的才有提升
-
如果函数与函数重名,会覆盖(后面的函数会把前面的函数覆盖掉)
-
如果变量与函数重名,会忽略变量,保函数舍变量
-
注意:- 变量如果不带var,变量是不会进行预解析的- 表达式定义的函数也是当做变量去解析。
console.log(a);
var a = 10;
function a(){
console.log('函数',a);
}
/*
function a(){
console.log('函数',a);
}
console.log(a); //function(){}
var a = 10;
console.log(a); //在当前这行a就是前面的function(){} 所以利用10覆盖function(){} 10
*/
运行:
7.3 特点
7.3.1 变量解析时
变量在预解析时,会把声明语句提升到最前边(赋值前打印返回undefined)。
注意:
变量如果不带var,变量是不会进行预解析的;
只有带var的变量才会进行预解析,只提升声明,不会把赋值过程进行提升
表达式定义的函数也是当做变量去解析。
7.3.2 函数解析时
解析函数的时候,函数定义方式不同,解析过程也不大一样:
-
函数式声明
function的函数体在预解析时,会把整个函数体提升至最前边,整个函数都要被提升:
func();
function func(){};
func();
-
表达式函数
只会提升函数表达式的声明,不会执行(真正执行函数表达式前调用会返回undefined),只会提升变量
var func = function(){};
func();
解析过程中:
如果函数重名,会覆盖(后面的函数会把前面的覆盖掉)
如果变量重名,会忽略;
function a(){
console.log(a);
}
a();
//var变量的预解析
//var a;
console.log('var变量的预解析',a); //undefined
a = 10;
//函数体的预解析
/*
* function fn(){
console.log('函数');
}
*/
console.log('函数体的预解析',fn);
function fn(){
console.log('函数');
}
//函数表达式的预解析
//var fn1
console.log('函数表达式的预解析',fn1);
fn1 = function(){
console.log('函数表达式');
}
八、
function fn(){
console.log("I am 普通函数");
}
8.2 匿名函数
顾名思义,就没有名字的函数,在实际开发中使用的频率非常高!也是学好JS的重点。
var obj = {
a:1,
b:function(){}
}
var sum = function(){
console.log('I am 匿名函数');
}
sum();
8.3 立即执行函数 IIFE
函数定义完,立即被调用,这种函数叫做立即执行函数
立即执行函数往往只会执行一次
立即执行函数也叫做匿名函数自调用
function (){
console.log("i am IIFE");
}
发现报错啦!!!
-
解决方法只需要给匿名函数包裹一个括号即可:
(function (){
//由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
console.log("匿名函数不会报错了");
})
-
匿名函数后面加上一个括号即可立即执行!
(function (){
console.log("运行匿名函数");
})()
-
在函数前添加 !~ + - 一元运算符
!function (){
alert("heng");
}()
-
倘若需要传值,直接将参数写到括号内即可:
(function (str){
console.log("Hello "+str);
return str;
})("World")
-
-
匿名函数自调用只能执行一次
-
如果需要一个函数可以执行多次,这个函数必须是有名函数
-
如果函数没有名字要想执行必须是自调用,但是只能执行一次
-
-
匿名函数自调用,函数整体不会发生预解析,但是函数内部执行代码是要去预解析
(function(){
var a = 1;
function fun(){
console.log(a);
}
fun();
/*
function fun(){
console.log(a); // 1
}
var a = 1;
fun();*/
})()
8.3.2 IIFE作用(背)
-
防止外部命名空间污染
当如果在一个script标签中出现两个或两个以上的立即执行函数,那么必须在每一个立即里执行函数的后面添加一个分号;
(function(){ var name = '张三'; function f1(){ console.log(name); //张三 } f1(); })(); (function(){ var name = '李四'; function f1(){ console.log(name); //李四 } f1(); })();
-
隐藏内部代码暴露接口
后期开发都使用的是一个外部的js链接,但是实际的代码看不到
-
对项目的初始化,只执行一次
8.3.3 全局污染
全局变量污染:
大家都在全局中写代码,很容易造成命名冲突,导致代码冲突。
ES6中代码冲突会直接报错。所以要养成好的习惯不要在全局去声明变量。
var age = 12;
var age = 21;
function fun2() {
var name = 'zhangsan';
var name = 'lisi';
console.log('姓名:',name);
}
console.log('年龄:',age);
fun2();
运行:
解决策略:不要声明全局变量
(function(){
var name = 'wangwu';
console.log(name);
})();
(function(){
var name = 'zhaoliu';
console.log(name);
})();
立即执行函数本身就是为了避免在全局写代码,避免冲突的。
立即执行函数自执行也叫开启一个新的命名空间。即开启新的作用域,此作用域和其他的不会冲突。
9.1 获取函数参数个数
-
使用arguments对象的length属性可以获取函数的实参个数。
-
arguments对象只能在函数内可见,因此arguments.length也只能在函数体内使用。
-
使用函数对象的length属性可以获取函数的形参个数,
该属性为只读属性,在函数体内和函数体外都可以使用
function add(a,b,c) {
console.log(add.length)//函数形参的个数
console.log(arguments.length)//获取实参的个数
//arguments:实参集合列表对象 也是伪数组 但是可以按照数组的简单操作(for、[下标]、length) object类型
return a+b+c;
}
console.log(add.length)//函数形参的个数
console.log(add(1, 2));
9.2 使用arguments对象
-
arguments对象表示函数的实参集合,仅能够在函数体内可见,并可以直接访间。
-
参数对象是一个伪类数组,不能够继承Array的原型方法。
可以使用数组下标的形式访问每个实参
例如参数[0]表示第一个实参
-
通过修改length属性值,可以改变函数的实参个数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)