four
一:函数的概念
个人理解:
在JavaScript中,函数是可以被重复使用的代码块,用来执行特定的任务或计算。
MDN解释:
一般来说,一个函数是可以通过外部代码调用的一个“子程序”(或在递归的情况下由内部函数调用)。 像程序本身一样,一个函数由称为函数体的一系列语句组成。值可以传递给一个函数,函数将返回一个值。 在 JavaScript 中,函数是头等 (first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。 函数与其他对象的区别在于,函数可以被调用。简而言之,它们是
Function对象。
函数的作用:
JavaScript 中的函数有以下作用:
- 简化代码:函数可以将复杂的代码块封装起来,使其更易于理解和维护。
- 提高代码的可重用性:函数可以被多次调用,从而提高代码的效率。
- 将代码分解为更小的模块:函数可以将代码分解为更小的模块,使其更易于理解和维护。
- 实现代码的封装:函数可以将代码封装起来,使其不被外部代码所访问。
- 实现代码的多态性:函数可以使用参数来实现代码的多态性,从而提高代码的灵活性。
高内聚,低耦合:
编程原则:高内聚,低耦合。
耦合:程序中,各个代码块中重复性代码太高;
以下来自---Google bard AI 解释 在计算机编程中,高内聚和低耦合是两个重要的概念。
高内聚是指一个模块内部的元素之间紧密联系,彼此之间具有很强的相关性。一个模块的高内聚意味着该模块只负责完成一个具体的功能, 不需要依赖其他模块。
低耦合是指一个模块与其他模块之间的联系越少越好。一个模块的低耦合意味着该模块与其他模块之间没有太多的依赖关系,可以独立地开发和维护。
高内聚和低耦合是相辅相成的,高内聚可以提高模块的独立性和可重用性,而低耦合可以提高系统的灵活性和可扩展性。
具体来说,高内聚和低耦合可以带来以下好处:
提高模块的独立性和可重用性:高内聚的模块只负责完成一个具体的功能,因此可以独立地开发和维护。低耦合的模块与其他模块之间没有太多的依赖关系,因此可以更容易地被其他模块调用。 提高系统的灵活性和可扩展性:低耦合的模块可以独立地开发和维护,因此可以更容易地进行扩展和修改。 提高系统的健壮性和可靠性:高内聚的模块可以减少错误的发生,因为每个模块只负责完成一个具体的功能。低耦合的模块可以减少相互影响,因此可以提高系统的健壮性和可靠性。 如何实现高内聚和低耦合?
将功能划分为多个独立的模块,每个模块只负责完成一个具体的功能。 减少模块之间的依赖关系,尽量使用接口来实现模块之间的通信。 使用设计模式来提高模块的复用性和可扩展性。 在实际的编程中,我们应该尽量实现高内聚和低耦合,以提高软件的质量和可维护性。
二:函数的语法结构
函数声明
使用 函数声明 创建函数
伪代码演示
function testFn(parameter1, parameter2, ..., parameterN){
function body;
}
testFn();
-----------------------------------------------------------------------------------------------------------------------------
Explanation:
function ---> function ---> 关键字;
testFn ---> name ---> 函数名;
parameter1 ---> a, b, c ---> 传递给函数的参数名称,可选填;
function body ---> console.log(i); ---> 函数体,
testFn(); ---> testFn(); ---> 函数调用,也叫函数执行。
代码演示:函数声明范例
function testFn(){
console.log('我是一个函数声明!');
}
testFn();
//Print result:我是一个函数声明!
函数表达式
把函数返回值赋值给一个变量,函数表达式也叫字面量。
伪代码演示
var createTest = function testFn(parameter1, parameter2, ..., parameterN){
function body;
}
createTest();
-----------------------------------------------------------------------------------------------------------------------------
Explanation:
function ---> function ---> 关键字;
testFn ---> name ---> 函数名;
parameter1 ---> a, b, c ---> 传递给函数的参数名称,可选填;
function body ---> console.log(i); ---> 函数体,
createTest(); ---> testFn(); ---> 函数调用,也叫函数执行。
代码演示:函数表达式范例
var createTest = function testFn(){
console.log('我是一个函数表达式!');
}
createTest();
//Print result://Print result:我是一个函数声明!
匿名函数
函数表达式中,省略函数名;
伪代码演示:
var createTest = function (parameter1, parameter2, ..., parameterN){
function body;
}
createTest();
-----------------------------------------------------------------------------------------------------------------------------
Explanation:
function ---> function ---> 关键字;
---> name ---> 函数名,省略不写;
parameter1 ---> a, b, c ---> 传递给函数的参数名称,可选填;
function body ---> console.log(i); ---> 函数体,
createTest(); ---> testFn(); ---> 函数调用,也叫函数执行。
代码演示:匿名函数范例
var createTest = function (){
console.log('我是一个匿名函数!');
}
createTest();0
//Print result://Print result:我是一个匿名函数!
三:函数的应用
代码演示:解耦合简单范例
//使用函数来解决以下代码的耦合,解耦合
//人话就是代码的重复利用
//判断 3大于0、2大于0、1大于0时,控制台循环打印0-9,直到变量i小于10停止打印
if(3 > 0){
for(var i = 0; i < 10 ; i++){
console.log(i);
}
}
if(2 > 0){
for(var i = 0; i < 10 ; i++){
console.log(i);
}
}
if(1 > 0){
for(var i = 0; i < 10 ; i++){
console.log(i);
}
}
-----------------------------------------------------------------------------------------------------------------------------
//使用函数来解决
//提取重复性代码,简化代码量(解耦合)
function method (){
for(var i = 0; i < 10 ; i++){
console.log(i);
}
}
//测试函数调用
method();
-----------------------------------------------------------------------------------------------------------------------------
//具体实现代码
//一:函数声明
function method (){
for(var i = 0; i < 10 ; i++){
console.log(i);
}
}
//二:函数调用
var num1 = Number(window.prompt('输入一个数字'));
if(num1 >0){
method();
}
代码演示:函数只声明,但未被调用(执行)
//函数声明
function test(){
var a = 1,
b = 2;
console.log(a,b);
}
//此时,函数只是被声明而已,但未执行。
代码演示:函数执行
//函数声明
function test(){
var a = 1,
b = 2;
console.log(a,b);
}
test();//Print result:1 2 第一次 函数调用则执行
test();//Print result:1 2 第二次 函数调用则执行
test();//Print result:1 2 第三次 函数调用则执行
//由此可见,函数可以重复多次被调用执行
//记录函数调用次数
var i = 0;
function testFn(){
i++;
console.log(i);
}
testFn();//Print result:1 第一次调用函数 后置型自增 i++
testFn();//Print result:2 第二次调用函数 后置型自增 i++
testFn();//Print result:3 第三次调用函数 后置型自增 i++
函数名命名规范
函数就是行为(action)。 所以它们的名字通常是动词。它应该简短且尽可能准确地描述函数的作用。这样读代码的人就能清楚地知道这个函数的功能。
一种普遍的做法是用动词前缀来开始一个函数,这个前缀模糊地描述了这个行为。团队内部必须就前缀的含义达成一致。
例如,以
"show"开头的函数通常会显示某些内容。函数以 XX 开始……
"get…"—— 返回一个值,"calc…"—— 计算某些内容,"create…"—— 创建某些内容,"check…"—— 检查某些内容并返回 boolean 值,等。这类名字的示例:
showMessage(..) // 显示信息 getAge(..) // 返回 age(gets it somehow) calcSum(..) // 计算求和并返回结果 createForm(..) // 创建表单(通常会返回它) checkPermission(..) // 检查权限并返回 true/false有了前缀,只需瞥一眼函数名,就可以了解它的功能是什么,返回什么样的值。
代码演示:初识全局变量与局部变量
全局变量:声明在函数外部,它的作用域是整个的JavaScript文件。全局变量可以被任何函数或代码块内部访问。
局部变量:声明在函数内部,它的作用域是函数内部。局部变量只能在函数内部访问,外部不可访问。
//局部变量
function testFn(){
var a = '我是局部变量!';
console.log(a);
}
testFn();// Print result:我是局部变量
console.log(a);//Print result:报错(a is not defined)
Explanation:报错的提示上看,就能很明显的看到,是一个未被定义的变量,但是我在函数内部已经声明了,为什么还会报这种错误,就是因为,这个变量是局部变量,不能在外部访问。只能在函数内访问,所以,才会在函数执行的那一刻,被访问到,执行完后,就会被立即释放。
-----------------------------------------------------------------------------------------------------------------------------
//全局变量
var globlVar = '我是全局变量!';
function testFn(){
var a = '我是局部变量!';
console.log(a + '\n' + globlVar);
}
testFn();
/**Print result:
*我是局部变量!
*我是全局变量!
*/
Explanation:从函数执行后的结果,就能得出,全局与局部变量的差异,也就验证了,全局变量是可以在函数内部访问,它的作用域是在整个的JavaScript文件。
代码演示:函数内的全局变量写法(不推荐,但需要知道)
function test(){
var a = b = 1; //尽量避免这种赋值方式
/**
* 赋值顺序:
* 1、先是数值1赋值给变量b;
* 2、通过关键字var 声明一个变量a;
* 3、变量b的值赋值给变量a。
* 声明赋值知识回顾:先声明,后赋值!
*/
console.log(a,b);
}
test();
//Print result:1 1
console.log(a);// (报错)b is not defined
console.log(b);// 1
Explanation:
上面注释块中,已经写出了一部分。
在补充下:
1、为什么打印a的时候会报错,打印b不会报错;
2、首先a是通过var关键字来声明,所以a是局部变量,又因为是局部变量特性,所以在外部访问,肯定会报错;
3、但是b呢?它实际上是没有通过var关键字来声明变量的,此时,b实际上是挂载到window这个对象上,而window对象代表了浏览器窗口;
4、所以b就理所应当的是全局变量。
进一步了解函数表达式
函数表达式中: 1、在执行函数表达式调用时,函数名会被自动忽略,由此函数名在外部不可访问和执行; 2、变量同时会继承函数的大部分特性,如:
.name等; 3、函数名只能在内部,调用并执行,由此产生的无限循环(死循环)、递归、闭包等概念。匿名函数:目前只知道,函数表达式中,函数名省略后,就是匿名函数。
代码演示:函数表达式
//正常调用执行的函数表达式
var testFnExp =function testFn(){
var a = 1,
b = 2;
console.log(a,b);
}
testFnExp();// 1 2
--------------------------------------------------------------------------------------------------------------------------
//进一步剖析函数表达式
var testFnExp =function testFn(){
var a = 1,
b = 2;
console.log(a,b);
console.log(testFnExp.name); //函数内部打印当前的函数名
//console.log(testFnExp.arguments);
//console.log(testFnExp.arguments.length);
//console.log(testFnExp.arguments.callee);
//console.log(testFnExp.prototype);
//console.log(testFnExp.caller);
}
--------------------------------------------------------------------------------------------------------------------------
/*执行函数表达式*/
testFnExp(); //执行函数表达式
//Print result:1 2 testFn
Explanation:
1、函数内部,15-19行,为测试变量继承函数功能,全部测试通过,目前还没有具体学习这些知识点;
2、函数表达式执行,testFnExp()调用成功,验证在函数内部可以使用全局变量打印函数名,进一步验证,全局变量的作用域是整个JavaScript文件;
--------------------------------------------------------------------------------------------------------------------------
/*调用当前函数名并执行*/
//注释上面代码,只使用本段代码
testFn(); //调用函数名执行 (报错)testFn is not defined
Explanation:
3、不使函数表达式的变量名来执行当前函数,结果为(报错)testFn is not defined,带着这个问题,进入下一段代码;
--------------------------------------------------------------------------------------------------------------------------
/*打印当前的函数名*/
//注释上面代码,只使用本段代码
console.log(testFn.name) //在外部打印函数名
//Print result:(报错) testFn is not defined
Explanation:
4、同样是报错,testFn未被定义;
5、为什么上面的两段代码都报错,那是因为在函数表达式在创建的时候,就会自动忽略function关键字后面的函数名,所以就会报错;
6、在深究一下,感觉应该是预编译阶段的运行机制吧,有待探索。
--------------------------------------------------------------------------------------------------------------------------
//函数表达式死循环
var testFnExp =function testFn(){
var a = 1,
b = 2;
console.log(a,b);
testFn();
}
testFnExp(); //死循环
Explanation:观察到在node中,会提示'Maximum call stack size exceeded',并结束。在chrome等浏览器中,会造成浏览器假死。
代码演示:函数之return
return,字面意思理解,就是返回的意思。
在JavaScript中,
return的作用就是返回函数内计算的值并赋值给其它(这里的其它,目前认知,是赋值给变量,后面学到其它知识在补充)。当然,在函数内部,可以省略return,但是JavaScript虚拟机会自动补全。
//函数之return 自动补全
function sum(){
var a = 1,
b = 2;
console.log(a + b);
//return; 此时,如果这里没有写return,则javascript虚拟机会自动在最后补上return
}
sum();//3
//return返回函数内部计算的值,并赋值给变量
function sum(){
var a = 1,
b = 2;
return a + b;
}
var c = sum(); //函数内计算的值,赋值给变量c;
console.log(c); //3
//return 逻辑 组合应用
function checks(name){
return name || '请输入name!';
//或运算,遇到假一直走下去,遇到真立即返回,如果一直假就走到最后并返回假!
}
checks();//请输入name!
checks('二狗子!');//二狗子
四:函数之形参与实参
形参:
function testFn(){a, b, c}testFn函数小括号内()填写的a b c,这个就是形参,形参是可选,以英文半角逗号,隔开。实参:
testFn(1,2,3)调用执行函数testFn()时,小括号内的值,1 2 3,就是实参,实参是可选,以英文半角逗号,隔开。 上面的解释精练一点的话就是:传入的参数,就是实参。通俗的理解就是:形参就是变量,实参就是给形参(变量)赋值。
代码演示:形参与实参范例
/**
*
* 形参与实参
* 要求:此例中,需要在外部输入两个值,如何来自定义的相加呢?
*
*/
function sum(){
var a = 1,
b = 2;
console.log(a + b);
}
sum(); //3
var aa = Number(window.prompt('a'));
var bb = Number(window.prompt('b'));
function sum(a,b){ //a,b 形式上占位,形式参数,形参
console.log(a + b);
}
sum(aa,bb);//假设aa输入5,bb输入9。此时的 5 , 9 就是实际参数,也叫实参
//14
代码演示:形参与实参在函数运行时的对应关系
//形参与实参在函数运行时的对应关系
//假设aa输入9,bb输入5。
var aa = Number(window.prompt('a'));
var bb = Number(window.prompt('b'));
function sum(a,b){
console.log(b - a);
//console.log(a + b);
}
sum(aa,bb);//结果为-4
Explanation:由此可见,形参与函数体内的变量是一一对应,并当函数调用时,实参也会与形参位置一一对应!
---------------------------------------------------------------------------------------------------------------------------
//形参与实参的个数
function show(a,b,c){
console.log(a,b,c);
}
show(1,2);//1 2 undefined
Explanation:由此可见,形参有三个参数,而实参并没有按照形参个数去填充,且函数内的语句有对应的三个形参,则调用并执行函数时,第三个形参显示undefined。再次验证上一例中,形参与实参是一一对应关系!
---------------------------------------------------------------------------------------------------------------------------
//形参与实参1
function show(a,b){
console.log(a,b);
}
show(1,2,3); //1 2
//形参与实参2
function show(a,b,c){
console.log(a,b);
}
show(1,2); //1 2
Explanation:由此两例中,可以发现,实参与形参个数上并不一定需要相等。
代码演示:实参之数据类型
//函数调用执行时传入的实参,不在乎数据类型
function show(a,b){
console.log(a , b);
}
show(undefined,null); //undefined null
show(false,'true'); //false 'true'
show(NaN,0); //NaN 0
Explanation:由此可验证,函数中的实参,不在乎数据类型
函数之 形参与实参 在深入
形参:函数名 +
.length,获取函数形参长度。实参:arguments,在JavaScript中属于类数组对象,这里的类就是类似的意思。
arguments.length,获取函数实参长度;arguments[0],以索引方式获取函数的实参值,索引从0开始;arguments[0] = value;实参在函数内部可以重新被赋值或修改;typeof(arguments[0])检查实参数据类型。
代码演示:函数内获取形参长度量
//函数名 + `.length` ,获取函数形参长度
function testFn(a, b, c, d, e, f, g){
console.log(testFn.length);
}
testFn();// 7
代码演示:函数内获取实参数量
//`arguments.length` ,获取函数实参长度
function testFn(a, b, c, d, e, f, g){
console.log(arguments.length);
}
testFn(1, 2, 3, 4);// 4
代码演示:在函数内获取实参值
//在函数内获取实参值
function testFn(a, b, c, d, e, f, g){
console.log(arguments[0]);
}
testFn(1, 2, 3, 4);// 1
代码演示:函数内实参值的修改及数据类型检查
//函数内实参值的修改及数据类型检查
function testFn(a, b, c, d, e, f, g){
arguments[0] = '索引0位的值修改!';
console.log('修改后的值:' + arguments[0] + '\n' + '索引0位的数据类型:' + typeof(arguments[0]));
}
testFn(1, 2, 3, 4);// 1
代码演示:函数内遍历实参值及累加
//函数内遍历实参值及累加
function testFn(a, b, c, d, e, f, g){
var sum = 0;
for(var i = 0; i < arguments.length; i++){
console.log(arguments[i]);//遍历实参值
sum +=arguments[i] //累加计算
}
console.log('累加值:' + sum); //打印累加值
}
testFn(1 , 2, 3, 4); //1 2 3 4 10
堆栈之函数的形参与实参映射关系
知识回顾: 1、堆内存,存储引用类型的值(数组、对象、函数等); 2、栈内存,存储原始类型的值(变量); 3、同时堆与栈内又有互相交集关系。
形参与实参的映射关系
当函数被调用时,实参的值将与形参一一映射。形参的数量须与实参的数量相匹配。
如形参与实参数量上不匹配时:
1、如果形参的数量大于实参的数量,则多余的形参将被赋值为 undefined;
2、如果形参的数量小于实参的数量,则多余的实参将会被忽略。
等把预编译学会,在来补充这块的笔记。
//实参与形参,函数体内的执行细节1
//解释:为什么arguments[0],为什么不是1,而是3?
//形参a,在函数内部已经被赋值为3?
//形参a与函数内部的变量a有什么关系?
function testFn(a,b){
a = 3;
console.log(arguments[0]);
console.log(a);
}
testFn(1,2);
//实参与形参,函数体内的执行细节2
function testing(a,b){
b = 3;
console.log(arguments[1]);
console.log(b);
}
testing(1);
//undefined 3
五:函数之变量的全局与局部
代码演示:函数内的全局与局部变量的差异
Global variable
//函数内部可以改变和访问全局变量的值
var a = 1;
function testing(){
a = 5; //重新赋值
console.log(a) //函数内部访问全局变量
}
testing();
//5
local variable
//函数体内部可以改变和访问全局变量的值以及局部变量
var a = 1;
function globalAndLocal(){
var b = 2;
console.log(a);
console.log(b);
console.log('---我是函数globalAndLocal的分割线---');
//第一层函数仅可以访问自身变量以及函数外部的全局变量,同时也可以更改,这里就不作演示!
function globalAndLocal1(){
var c = 3;
console.log(a);
console.log(b);
console.log(c);
console.log('---我是函数globalAndLocal1的分割线---');
//第二层函数仅可以访问自身和一层的局部变量以及函数外部的全局变量,同时也可以更改,这里就不作演示!
function globalAndLocal2(){
var d = 4;
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log('---我是函数globalAndLocal2的分割线---');
//第三层函数仅可以访问自身和二层,一层的局部变量以及函数外部的全局变量,同时也可以更改,这里就不作演示!
}
globalAndLocal2();
}
globalAndLocal1();
}
globalAndLocal();
六:函数形参默认值
ES6支持形参默认值写法,如:
function fuDefault(a = 1, b = 2);ES5不支持形参默认值写法,如上,但有其它方式来解决。
代码演示:验证上述内容
function fuDefault(a,b){
console.log(a);
console.log(b);
}
fuDefault(1);
//1,undefined
//函数调用时,实参没有传入全部的形参数量,则漏缺的形参的默认值为undefined
//初始化形参 数值1赋值给a,数值2赋值给b
//es6支持此写法,es5不支持这种写法
function fuDefault(a = 1, b = 2){
console.log(a);
console.log(b);
}
fuDefault();//1 2
//初始化形参 数值1赋值给a,b不赋值
function fuDefault(a = 1, b ){
console.log(a);
console.log(b);
}
fuDefault();//1 undefined
//初始化形参 1赋值a,b不赋值,但实参里面需要给b进行赋值,并保留形参a的默认值
function fuDefault(a = 1, b ){
console.log(a);
console.log(b);
}
fuDefault(undefined,2);//1 2
Explanation:
1、如果形参的默认值为非undefined的值,实参的值为undefined的值,则取形参的默认值;
2、如果实参的值为非undefined的值,形参的值为undefined的值,则取实参的值;
3、如果都为undefined的值,则取undefined值。
//可以用另一种方法来写,arguments和或运算来做,同样可以达到上一例的效果
function fuDefault(a,b){
var a = arguments[0] || 1; //逻辑或运算,遇假就往后走,遇真就停,如一直假,走到最后就停(即取最后一个假的值);
var b = arguments[1] || 2; //逻辑或运算,遇假就往后走,遇真就停,如一直假,走到最后就停(即取最后一个假的值);
console.log(a);
console.log(b);
}
fuDefault();//1 2
//还可以用另一种方法来写,三元运算(三目运算)、typeof()、arguments、或运算来做,同样可以达到上一例的效果
function fuDefault(a,b){
//var a = arguments[0] || 1;
//var b = arguments[1] || 2;
var a = typeof(arguments[0]) !== 'undefined' ? arguments[0] : 1;
//1:typeof()返回的值,都是字符串。同时arguments[0]里面的值如果实参没有传参,则为undefined,反之为传参的值;
//2:进行比较运算,如果arguments[0]有实参传入的值,则不相等成立,为true。反之为false;
//3:为true,则将arguments[0]的值赋值给a;
//4:为false,则将1赋值给a。
var b = typeof(arguments[1]) !== 'undefined' ? arguments[1] : 2;
//分析同上。
console.log(a);
console.log(b);
}
fuDefault();// 1 2