Javascript(二)——函数(重载、回调)与作用域(附图解)

一、函数

函数分为系统函数自定义函数

函数是一个功能体,需要提供若干个数据,返回处理的结果;用于封装重复执行的代码。

调用函数:
函数名称() //执行函数体重封装的代码

1.1函数的本质

1). 函数也是一个对象,对象中保存着函数的函数体代码
2). 函数名只是一个普通的变量,函数名通过函数对象地址,引用着函数对象
3). function在底层等效于new Function()
function 函数名(){ ... }var 函数名=function(){}在底层都会被翻译为
var 函数名=new Function(...)

只不过function 函数名(){}是先提前,再翻译
var 函数名=function(){}是不提前,原地翻译

1.2创建函数

学习声明方式和赋值方式创建函数之前,最好先学习:声明提前~

1) 声明方式 function

缺陷:声明提前,打乱执行顺序

(1)创建普通函数

function 函数名() {
	函数体
}

(2)创建带有参数的函数

  • 形参:创建函数时的参数
  • 实参:调用函数时的参数,实参赋给形参
    实参个数可以和形参数量不匹配,若形参未被赋值为undefined
function 函数名(参数列表) {
	函数体
}
1.获取形参个数:函数名.length
2.形参和实参个数不同
  • 形参个数 > 实参个数:多出的形参undefined
(function(a,b) {
    alert(typeof a);//number
    alert(typeof b);//undefined
})(1);
  • 形参个数 < 实参个数:多出的实参忽略
(function(a,b) {
    alert(a);//1
    alert(b);//2
})(1,2,3,4);

(3)创建带有返回值的函数

function 函数名(参数列表) {
	函数体
	return;
}

return 用于返回函数调用后的结果。
如果函数中没有return或者return后无值,返回undefined,一旦return执行就会跳出函数,结束函数执行。

2) 赋值方式

var 函数名 = function(参数列表) { 函数体 };
优点:不会声明提前,保持程序原有顺序

今后只要定义函数,优先选择赋值方式创建函数

var f = function(a,b) {
    return a+b;
}
alert(f(1,2));

3)new创建——几乎不用

var 函数名 = new Function("形参1","形参2",……"函数体")
Function的参数都是字符串,body语句之间分号进行分隔

//示例:
var f = Function("a", "b", "a=a+b;return a");

揭示原理:
a.函数也是一个引用类型的对象
b.函数名是一个普通的变量
c.函数名变量通过对象的地址值,引用着函数对象

所有function底层都是new Function(){…}
new出来的都是复杂类型,复杂类型都无权进入变量本地
在这里插入图片描述

4)ES5-create()方法

Object.create(prototype,descriptors)

  • prototype:必需参数,用作原型的对象,可以为null
  • descriptors:可选参数,包含一个或多个属性描述符的js对象
//示例:
var father = {
    money:1000000000000,
    car:"infiniti"
}
var erya=Object.create(father,{
//defineProperties()添加自有属性
    phone:{
        value:"iPhone 13 Max",
        writable:true,
        enumerable:true,
        configurable:false
    },
    bao:{
        value:"LV",
        writable:true,
        enumerable:true,
        configurable:false
    }
});
console.log(erya);
console.log(erya.money, erya.car);

1.2 递归

在一个函数的内部调用了自身这个函数

递归由两部分组成:递归调用,递归终止条件
一般结合if语句来控制。

1、问题的定义是递归的,如数学中的结阶乘函数、幂函数和斐波那契数列
2、问题所涉及的数据结构是递归的,如文档树
3、问题的解法满足递归的性质,如汉诺塔

//使用递归计算任意数字~1之间所有整数的乘积
function getCj(n){
  //边界条件:当n为1的时候,返回1
  if(n === 1){
    return 1;
  }
  //规律:n~1之间所有整数的乘积 = n * n-1的乘积
  return n*getCj(n-1);
}
console.log( getCj(5) );


//阶乘递归,使用回调函数
var f = function(x) {
    if (x < 2) {
        return 1;   //递归终止条件
    } else {
        return x * arguments.callee(x-1);   //递归调用过程
    }
}
alert( f(20) );

1.3 重载函数

1.3.1 arguments

表示参数集合
i. 每个函数自带的
ii. 专门接收所有传入函数的实参值
iii. 类数组对象

arguments是类数组对象,和数组相比::
a. 相同点: 也有下标,length属性,也可for循环遍历
b. 不同点: 不是数组类型,无法使用数组的函数

function f() {
    arguments.length = 2;//修改length值=改变函数实参个数
    for (var i = 0; i < arguments.length; i++) {
        //arguments[i] = i;   修改实参值
        alert(arguments[i]);//显示指定下标的实参值
    }
}
f(3,3,6);//只有3 3

1.3.2 arguments实现重载

通过判断arguments中实际参数的个数和类型来执行不同的逻辑

function sayHello() {
    switch(arguments.length) {
        case 0:
            console.log("Hello");
        case 1:
            return "Hello,"+ arguments[0];
        case 2:
            return (arguments[1] == "cn" ? "你好," : "Hello,")+ arguments[0];
    };
}
sayHello();//Hello
sayHello("Alex");//Hello,Alex
sayHello("Alex","cn");//你好,Alex

1.4 回调函数

1.定义

将函数以实参的形式传递,这个传递的函数称为回调函数

理解: 自己定义的函数,但是自己不调用,而是交给别的函数自动调用

绝大部分回调函数使用匿名函数:
节约内存

(匿名函数可被垃圾回收,若为有名函数在window中被引用,回收不了)
在这里插入图片描述

举例:

 arr.sort(function(a,b){return a-b})

()中的function,虽然是我们自己定义,但是不是我们自己调用。我们不知道何时调用,调用了几次,每次传的什么参数。

()中的function,在sort函数内部,根据sort函数的需要自动调用,自动传参。.

2.对比函数名称()和函数名称

函数名称(),调用函数,得到函数的返回结果。
函数名称,本质上是一个变量,保存了一个函数。


//argumentts.callee获取匿名函数
function f(x,y,z) {
    var a = arguments.length;//函数实参个数
    var b = arguments.callee.length;//Function对象的length返回函数形参个数
    if (a != b) {
        throw new Error("传递的参数不匹配");
    } else {
        return x+y+z;
    }
}

function tao(madai) {
    console.log('涛哥开始跑第1棒');
    console.log('涛哥到达第1棒终点');
    //调用dong
    //madai=dong
    //madai=function(){}
    //调用传递函数,通过形参madai
    madai(); //dong()  (function(){  })()
  }
  function dong(){
    console.log('东哥开始跑第2棒');
    console.log('东哥达到终点');
  }
  //dong就是回调函数
  tao(dong);
  //匿名函数也是回调函数
  tao( function(){
    console.log('陌生人开始跑...');
  } );
  

1.5 系统函数

  • isNaN()
    检测一个值是否为NaN,常用于检测用户输入的值是否含有非数字。会将检测的值隐式转换为数值,然后查看是否为NaN,是->true 不是->false
  • isFinite()
    检测一个值是否为有限值,只有Infinity是无限值,其它所有的值都是有限值, 是有限值 -> true 不是有限值 -> false
  • eval()
    执行字符串表达式

二、作用域

(1)什么是作用域
a.一个变量的可用范围
b.专门保存变量的对象

(2)为什么
避免函数内外的变量之间互相干扰

(3)块级作用域
程序中代码块(一堆程序结构{}内)范围内的程序

if {代码块1} else {代码块2}

JS中没有块级作用域:

for (var i = 0; i < a.length(); i++){
	sum += i;
}
console.log(i);//不报错,在别的语言是报错的

2.1 作用域

1). 全局作用域:window,保存全局变量

优: 可重用
缺: 随处可用, 极易被污染

2). 函数作用域:保存局部变量
局部变量包括: 函数中var出的变量和形参变量

优: 仅函数内可用,不会被污染
缺: 不可重用

2.1.1 原理

i. 函数定义时都自带好友列表,好友列表里2个格子,一个是空(为之后重要的对象预留),一个引用window

ii. 调用函数时临时创建函数作用域对象保存函数局部变量。并将函数作用域对象的地址保存到函数好友列表中离自己近的格子里

iii. 函数执行过程中就近原则(先局部,后全局) 先在自己的函数作用域对象中找局部变量使用。如果找不到,才被迫去全局window中找变量使用.

iv. 函数调用后,好友列表中离自己近的格子清空,导致函数作用域对象以及内部的局部变量被释放!——所以局部变量不可重用!

2.1.2 示例讲解

示例代码:

var a1=10;
function fun1(){
  var a1=100;
  a1++;
  console.log(a1);//101
}
fun1();
console.log(a1);//10

tip:下图中的好友列表,学名:作用域链

①定义函数时:window对象中保存全局变量a1和全局函数fun1的地址0x1234
知识点:
a.function底层为new Function,所以引用的是函数的地址
b.每个函数定义时都自带好友列表,好友列表里2个格子,一个是空,一个引用window

在这里插入图片描述
②调入函数时:临时创建函数作用域对象,地址0x9091,并在好友列表中预留的空格子中填入此地址
tip:函数作用域对象保存的是函数内的局部变量,这段程序中为a1:100
在这里插入图片描述

③函数执行过程中
先执行a1++,就近原则先在自己的函数作用域对象中找a1,存在=>函数作用域的a1++,
执行console.log(a1),就近原则输出函数作用域的a1=101
在这里插入图片描述
函数调用后:执行console.log(a1),清空第一个格子后,只能在window中寻找a1,故结果为10
知识点:函数调用后,好友列表中离自己近的格子清空,导致函数作用域对象以及内部的局部变量被释放
在这里插入图片描述

2.2. 作用域链:

保存一个函数所有可用的作用域对象的链式结构,就是上图中的“好友列表

1). 作用域链保存着一个函数可用的所有变量

2). 作用域链控制着变量的使用顺序先局部后全局

2.3 全局/局部

  • 局部变量:在 函数作用域 下声明的变量,只能在 当前的作用域 下访问到
    函数中var出来的变量,或形参
  • 全局变量:在 全局作用域 下声明的变量,可以在 任意的作用域 下访问到
    项目中禁止使用全局变量!
  1. 函数内不加var声明的变量是全局变量
var m = 5;  //函数外声明变量,标准全局变量
function fn() {
    //函数内不加var声明的变量是全局变量
    n = 5;
}
  1. 在全局作用域下,所有变量和函数的调用对象都是window
fn();//直接调用函数fn(),函数的执行作用域为全局域,故为window对象
console.log(n);

window.w = null;//window对象,全局变量

全局函数:在全局作用域下创建的函数,可以在任意作用域下调用
局部函数:在函数作用域下创建的函数,只能在当前作用域下调用

2.4全局污染

全局污染:全局变量的出现产生的影响
项目中禁止使用全局变量!

解决方案:匿名函数自调用:

//创建一个临时函数
(function(){
  函数作用域下,变量是局部变量,可以防止污染全局
  //功能原代码
})();//立刻调用

好处:
①功能中用到的个别变量,会被圈在匿名函数中,不会成为全局变量
②功能原代码不需要改动


例:
两个倒计时放入匿名函数自调用中,将不会相互影响

 //倒计时1——你写的代码
    (function(){ 
      var i=5;
                //启动周期性定时器,每隔一秒执行一次
      //保存定时器序号,用于将来停止定时器之用
      var timer1=setInterval(function(){
        //如果倒计时还没到0
        if(i!=0){
          //就继续秒杀
          console.log(`1号商品,秒杀进行中...${i}`);
          //倒计时-1
          i--;
        }else{//否则,如果倒计时已经是0
          //就秒杀结束,并停止定时器
          console.log("1号商品秒杀结束!!!");
          clearInterval(timer1);
        }
      },1000);//每隔1秒
    })();//必须加;

    //倒计时2——别人写的功能中
    (function(){
      var i=5;
                //启动周期性定时器,每隔一秒执行一次
      //保存定时器序号,用于将来停止定时器之用
      var timer2=setInterval(function(){
        //如果倒计时还没到0
        if(i!=0){
          //就继续秒杀
          console.log(`2号商品,秒杀进行中...${i}`);
          //倒计时-1
          i--;
        }else{//否则,如果倒计时已经是0
          //就秒杀结束,并停止定时器
          console.log("2号商品秒杀结束!!!");
          clearInterval(timer2);
        }
      },1000);//每隔1秒
    })()

在这里插入图片描述

2.5 声明提升

声明提升是JavaScript中的一个缺陷,会造成输出结果与所构想的结果不一致。

var 程序执行前会将函数/变量提升到所在作用域的最前边

let 关键字定义的变量则不存在提升,有关let详情请看《JavaScript(六)——ES6

点击这里:Javascript——声明提升

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值