函数
每个函数都有一个name属性,其值就是函数名
function fn(){}
console.log(fn.name) // fn1.函数基础
- 明确:代码中的函数跟上学接触的三角函数、线性函数等不是一个概念。
- 循环:当一段代码需要重复执行 用 循环 (减少工作量,提高效率)
- 函数:当一段代码需要多个地方使用函数(减少代码冗余,方便维护开发 )
2.函数概念
- 函数是一段有名字的代码块,完成特定功能的代码块,可以被调用执行多次,实现复用。
- 一次定义,多次使用function
3.函数语法
普通函数:通过function关键词
函数在定义时拥有固定的名称,命名规则同变量命令规则。
// 声明普通函数
function fn(){ // fn 为函数名
console.log('hello Function!');
} // 调用有名函数
fn(); // 这里的圆括号运算符表示对函数的调用操作匿名函数:将函数内容赋值给变量
匿名函数这种做法其实就是将函数作为一个值赋给一个变量,这就是一个赋值表达式
// 定义赋值匿名函数
var fn = function(){
console.log('hello Function!');
}
// 调用函数
fn();函数的四要素
JS语言中函数拥有四个非常重要的特性,它们分别是:
函数名(语法要求)
函数体(函数体缺失那么该函数就是无意义的)
参数
形参
实参
返回值(语法要求)
4.函数的参数
形参
形式参数,是指在函数定义时指定的参数,写在小括号内部,占位置。
function getSum(num1,num2){
console.log(num1+num2);
}上面代码中圆括号内部的num1和num2就是函数的形参。注意这时候的形参是没有值,它需要别人给它们传值,其实我们也可以把形参理解为在函数定义时声明的变量。
function getSum(num1,num2){// 相当于: var num1; var num2;
console.log(num1+num2);
}这样看来是不是好理解多了?而且我们可以看见num1和num2都是只声明没有赋值的,所以它们当前都是undefined。
实参
实际参数,函数调用时传递的参数,写在执行函数的圆括号运算符内部,
有实际的值。 上面的形参仅仅只是被声明了还没有赋值,实参的作用就是为形参传值的,所以它们之间的关系就相当于是在做普通的赋值操作,只不过是语法上发生了改变。
function getSum(num1,num2){// 相当于: var num1 = 10; var num2 = 20;
console.log(num1+num2);
}
getSum(10,20);参数的顺序和个数(细节)
函数的形参个数和实参个数,必须一样
实参的个数小于形参的个数,没有接收到实参的形参值为 undefined
实参的个数大于形参的个数,多出来的实参没有形参接收,但是对于程序也没有影响
正常情况下都是实参和形参的个数是对等的(当然有非正常情况)
函数的形参顺序和实参顺序,必须一致,否则最终结果有问题
5.函数的返回值 return
函数执行完之后必须有一个结果,这个结果叫做:返回值。
使用return关键字来执行函数的返回值。 return关键字后面跟的什么那么返回值就是什么。
function getSum(num1,num2){
return num1+num2;
}
// 函数的返回值直接赋值给一个变量
var result = getSum(10,20);
// 函数返回值直接参与运算
console.log( getSum(10,20)/5 );前面的代码就是函数返回值的简单使用,但是我们需要注意的是,return关键字有一些小的技巧:
return语句会终止当前函数的执行
当函数中的return执行之后就会终止当前函数,所以return后面的语句都不会被执行,所以我们经常会让return语句配合判断结构体一起使用。
- 需求:写一个函数,两个参数(正整数),计算出两个参数之间的整数之和,当和大于100时就返回计算结果。
function getResult(num1,num2){
var big = num1>num2?num1:num2;
var small = num1<num2?num1:num2;
var sum = 0;
for(var i=small;i<=big;i++){
sum+=i;
if(sum>100){
return sum
}
}
}
getResult(10,50);//108上面的代码如果执行完整的话得到的和应该是5050,但是程序得到和为108的时候就已经跳出当前函数了,这种小技巧后面会经常用到,暂时先不做展开讨论。
如果return后没有值或者函数中没有return
函数中没有写return或者return后没有任何东西是我们经常会遇到的情况。这两种情况函数都会默认返回undefined。
// return后没有任何东西
function getSum(num1,num2){
var result = num1+num2;
return;// 这里的return执行的是终止函数的作用,函数会默认返回undfined
console.log(result);// 这句话不会执行
}
// 函数中没有return
function getSum(num1,num2){
var result = num1+num2;
console.log(result);// 这句话会执行
// return; JS会自动补上return
}6.变量和函数的提升
变量提升是JS语言的一个非常有意思的特点,也是将来的学习和工作中经常遇到的问题,所以我们在将来的面试中可以肯定就是必考知识点。
变量提升是指JS语言对变量在声明之前的提升操作,这种提升操作会使变量在声明之前就已经可以被访问。变量提升的本意其实是为了JS语言更少的报错但是这种设计思路对于新手来说非常不友好,现在更多的论调是:变量提升是JS语言的一大缺陷。
请看下面一段代码:
console.log(num);// undefined var num = 10;上面的打印结果undefined,结果不是很好理解。
想要搞明白上面的undefined是如何出现的就必须要搞明白JS语言的运行机制。
JS运行的代码分为三个阶段
预编译:浏览器帮我们做,所做的事情就是基本的语法检查,除了给我们报错时,看不见摸不着
预解析:也是浏览器做,虽然已经能够看得见了,对我们程序中使用var和function定义的变量和函数进行预解析工作。
执行代码:从上往下执行,逐行
当执行函数时才会进入函数内部
再次逐行执行代码
导致变量num值为undefined的原因就是第二步预解析问题。 JS语言中所有的变量名和函数名都会被执行提升操作。
对变量的提升
JS语言在运行代码之前会对该作用域下的所有被var关键字描述的变量名称进行提升操作,将这些变量提升到该作用域下的最顶部但是却不会将他们的值一并提升,所以JS语言为了不让程序报错自作主张为变量赋值为:uundefined。
// 在浏览器逐行阅读代码之前num就已经处于最顶部,并且值为:undefined // num=undefined
console.log(num);// undefined
var num = 10;// 赋值成功
console.log(num);// 10对函数的提升
JS语言对函数也一样会执行提升操作,但是提升的思路并不像变量那么简单粗暴。对函数的提升就是将整个函数都赋值给函数名,至于函数内部有什么东西则完全不管。
// JS对于函数的提升相当于是有名函数赋值表达式
// fn = function fn(){ alert(1) }
fn();// 可以执行
function fn(){
alert(1)
}当变量名和函数名重名
当变量名和函数名重名时,JS对于提升阶段和执行阶段的态度是不一样的,请看下面代码。
console.log(a);// ƒ a(){ alert(20); }
var a = 10;
function a(){
alert(20);
}
console.log(a);// 10在提升阶段变量名和函数名重名,函数优先。
在执行阶段变量名和函数名重名,变量优先。
7.变量的作用域
所谓的变量的作用域就是指变量能够起作用的范围。 变量的作用域决定了变量将来可以被访问的区域,而变量被限制的区域也决定了该变量是全局变量还是局部变量。
全局变量:定义在函数外部,在页面的任何位置都能正常访问的变量。
局部变量:定义在函数内部,只能在该函数内部访问。
var num1 = 10;
function f1(){
var num2 = 20;
console.log(num1);// 10
console.log(num2);// 20
}
f1();
console.log(num1);// 10
console.log(num2);// error上面的代码执行结果很显而易见,变量num2定义在函数内部,但是不可以在函数外部访问,如果访问就会报引用错误。
全局变量和局部变量产生的原因
虽然我们知道全局变量和局部变量他们关键点在于声明时所处的位置,因为声明时所处的位置不同决定了将来该变量能够成为全局或者局部。
全局变量
使用浏览器打开HTML文档,在HTML文档中所有的标签都是一直存在的(只要页面不关闭),而标签中的内容也是一直存在的,所以全局变量对的产生很好理解。
因为script标签一直存在并工作,所以全局变量也是一直存在并工作。
变量的本质就是在内存中开辟一块空间,变量名就是为这块空间取一个你认为合适的名字,而变量的值就是往这块空间内放入一些东西方便将来的使用。
因为全局变量一直存在所以该内存空间也一直保留,故而我们在页面的任何位置都是可以使用全局变量的。
局部变量
局部变量是处于函数内部的,函数在执行之前函数中所有的内容浏览器都不会取尝试解析或者执行,所以在函数中声明的变量是不存在的。
函数执行时程序才会找内存要一块空间用于存放变量的值,此时该局部变量是可以被访问的。
当函数执行完毕后也就意味着一个动作的结束,而这个函数的函数体也就没用了,所以JS语言为了让内存得到最大程度的利用就会把函数体中的所有的东西都清理出内存;这样一来原本在函数中声明的变量也就从内存中清理出去,所以我们在函数外部访问局部变量的时候只会得到报错的结果。
为什么需要局部变量
其实我们之前学习中使用的都是全局变量,程序也能正常运行并且使用难度上比局部变量小不少,不需要我们去关心什么时候使用会报错。我们学习局部变量的根本原因就在于全局变量并不像看起来那么好。
全局变量的优点
- 省心省力
- 有效的减少变量的个数(当多个函数用到同一个值一个全局变量就能搞定)
全局变量的缺点
关于全局的缺点非常多,这里我们只给大家总结几个常见的:
- 变量污染:变量名之间的相互覆盖
- 增加函数之间的耦合性:函数之间相互依赖
- 内存压力:因为一直保存在内存中,即便是不需要的时候也会一直存在,不利于内存空间的合理利用
var num1=10; //全局变量
function fn1(){
var num2=20; //局部变量
console.log(num1); // 10
console.log(num2); // 20
} fn1(); function
fn2(){
console.log(num1); // 10
console.log(num2); // error
}
fn2();
console.log(num1); // 10
console.log(num2); // error全局变量和局部变量重名
一个程序的体量变大就会不可避免遇到全局变量和局部变量重名的情况,这时候我们需要注意它们之间的优先级关系。
var num1 = 10;
function fn1(){
var num1 = 20;
console.log(num1); // 20
}
fn();var num = 10;
function fn2(num){ // var num = 20;
console.log(num); //20
}
ff(20);var num = 10;
function fn2(num){
console.log(num); // 20 var num = 30;
console.log(num); // 30
}
ff(20);注意:当全局变量与局部变量重名的时候优先使用局部变量。
如果我在它们重名的情况下还想在函数中访问到全局变量该怎么办?
- 在变量名前加上window前缀
- 如果该函数是全局函数也可以在变量名前加上this前缀
var num = 10;
function fn(){
var num = 20;
console.log(num);// 20
console.log(window\.num);// 10
console.log(this.num);// 10
}8.声明变量不加var
如果声明变量没有加var,则声明的变量是一个全局变量全局有这个名称的变量就修改,如果没有就是创建
console.log(num1); //undefined
console.log(num2); //error 报错 没有使用var 声明的变量 在赋值之前访问不到
var num1 = 10;
num2 = 20;
console.log(num1); //10
console.log(num2); //20var num = 111; // 全局变量
function fn1(){
var num2 = 222; // 局部变量
}
fn1()
console.log(num); // 结果:111
console.log(num2); // 结果:报错
var num = 111; // 函数体外面,全局变量
function fn2(){
var num2 = 222; // 函数体内,局部变量
console.log(num) // 打印111
console.log(num2) // 打印222
}
fn2();
console.log(num) // 打印全局 能 111
console.log(num2) // 打印局部 不能 error
var num = 111; // 全局变量
function fn3() {
num = 222; // 全局变量(全局有这个变量,则修改已有的值)
console.log(num) // 222
}
fn3();
console.log(num) // 2229.变量的作用域链
什么是作用作用域链,就是多个作用域的集合
如下:
var name = "张建军";
function fn1(){
var name = "帅哥";
console.log("name"); //
function fn2(){
var name = "男神";
console.log("name"); //?
function fn3(){
name = "地表最帅男神";
console.log("name"); //?
}
fn3();
}
fn2();
}
fn1();在作用域链情况下:
- 当变量没加var时,会去在上一个作用域中找【同名&带var】
- 找到了 - 直接修改
- 找不到 - 在作用域链上找
- 一直找到顶级作用域
- 找到了 - 直接修改
- 找不到 - 自己创建修改
10.递归函数
递归本质上是一种算法,是一种程序调用自身的编程技巧。
递归在JS语言中的具体表现为:函数调用自身
递归的特点:重复计算执行,和循环非常类似。所以递归也和循环一样,需要一个出口(停止条件)。
- 需求:从1打印到100
// 循环的方法
for( var i=1;i<=100;i++ ){
console.log(i);
}
// 递归的方法
function showNum(num){
console.log(num);
if(num == 100){
return;
}
num++;
return showNum(num);// 函数内部调用自身
}
showNum(1);上面的代码完成的功能是一样的,从逻辑上来说也是非常类似的,最大的不同点在于我们函数的内部调用了自身。
递归不能代替循环
虽然递归和循环很像,但是递归不可以代替循环。这就要从递归的缺点来说了。
- 性能不如循环
- 内存占用过高(非常致命)
- 递归次数必须预期收敛
递归的最大缺点就是内存的使用率,因为是在函数内部调用自身,一层层的嵌套执行所以会导致内存一直释放不了。 我们将上面的代码循环次数增加,记录它们的计算时间并查看它们对内存的消耗。
// 循环的方法
// 记录开始时间
var startTime = new Date();
for( var i=1;i<=10000;i++ ){
console.log(i);
}
// 记录结束时间
var stopTime = new Date();
// 得出消耗的时间
console.log( stopTime-startTime );
// 递归的方法
// 记录开始时间
var startTime = new Date();
function showNum(num){
console.log(num);
if(num === 10000){
return
}
num++;
showNum(num);// 函数内部调用自身
}
showNum(1);
// 记录结束时间
var stopTime = new Date();
// 得出消耗的时间
console.log( stopTime-startTime );因为每一台电脑的性能不同所以测出的结果也会有差距,甚至有的电脑性能较低很可能会在测试递归的时候出现网页"假死"的情况。
何时使用递归
递归操作拥有自己非常明显的缺点,但是优点也是非常突出的。
- 操作简洁
- 当处理子问题与原始问题相同时,操作简单
