前端程序员需要了解的JavaScript,关于函数的学习见解

前言:在整个计算机编程中,函数的地位可谓十分重要,处理逻辑我们离不开写函数,这一章我们系统的学习下函数的相关知识。

目录

函数的声明:

函数提升:

函数的参数:

默认参数:

参数整合展开:

函数可以作为参数传入:

 箭头函数:

函数的递归: 

函数的回调: 

 闭包:

 this:

this的指向问题:

解决this指向问题:

 改变this的指向:

普通函数和构造函数的区别:

 作用域:

函数作用域:

块级作用域:


函数的声明:

 // function a() {
  //   console.log('这是一个函数');
  // }

建议使用这种定义匿名函数,这样windows就调用不到这个函数
  let a = function () {
    console.log('这是一个函数');
  }
  console.log(a());

在对象里面定义函数:函数可以属性简写

 let obj = {
    name: undefined,
    //简写形式
    setName(name) {
      this.name = name
    },
    getName() {
      return this.name
    }
  }
  console.log(obj.setName('xiaoyu'), obj.getName());

函数提升:

一般来说,具名函数存在作用域提升,而匿名函数不存在函数提升,函数其实是对象类型,所以存在引用类型赋值。

函数的参数:

默认参数:

函数的形参可以赋值默认的值,如果没有传实参,则使用默认的值,可以替代 ||或语句

function a(b, c = 3) {
    return b + c
  }
  console.log(a(1));

参数整合展开:

当传很多实参时,接受的形参不想写那么多,可以用...args 代表传过来的整个实参列表,即为收集再释放

 function a(...args) {
    //将所有的实参都赋值给args数组,然后展开
    console.log(args); //[1, 2, 3, 45, 6]
    console.log(...args); //1 2 3 45 6
  }
  console.log(a(1, 2, 3, 45, 6));

函数可以作为参数传入:

 箭头函数:

箭头函数是函数声明的简写形式,在使用递归调用、构造函数、事件处理器不建议使用箭头函数。

  • 无参数时,()=> { }
  • 有一个参数时,省略括号
  • 当只有一个表达式时,取消花括号和return

函数的递归: 

递归的意思是:函数内部调用自己的方式

  • 主要用于数量不确定的循环操作
  • 要有退出时机否则会陷入死循环

递归的使用总结:一定要有结束条件,return出来,每次调用自己的时候,传过去的参数都是做逻辑操作 (意思是传的参数都不一样)

简单的递归实例:


 function factorial(num = 3) {
    return num == 1 ? num : num * factorial(--num);
  }
  console.log(factorial());

累加:

function sum(...num) {
	return num.length == 0 ? 0 : num.pop() + sum(...num);
}
console.log(sum(1, 2, 3, 4, 5, 7, 9)); //31


操作数组对象的属性
 let lessons = [{
      title: "媒体查询响应式布局",
      click: 89
    },
    {
      title: "FLEX 弹性盒模型",
      click: 45
    },
    {
      title: "GRID 栅格系统",
      click: 19
    },
    {
      title: "盒子模型详解",
      click: 29
    }
  ];

  function change(lessons, num, i = 0) {
    if (i == lessons.length) {
      return lessons;
    }
    lessons[i].click += num;
    return change(lessons, num, ++i);
  }
  console.table(change(lessons, 100));

函数的回调: 

别人使用的函数叫回调函数

 闭包:声明在一个函数体内的函数为闭包,声明位置在体内

为什么要闭包:

1:js有垃圾回收机制,每次调用完一个函数后,函数的声明周期就会结束,里面的东西都会销毁,为了延长函数的声明周期,以及避免回收机制,使得父级函数的变量得以缓存起来。

 function a() {
    let value = 2
    return function () {
      return value += 1
    }
  }

  let c = a()
  c() //3 
  c() //4
  console.log(c()) //5

2:为了让外部访问到内部函数的变量,避免全局变量的污染

函数体内return 出另一个函数,返回出的这个函数可以读取外层函数作用域里面的变量。

1:函数里面嵌套函数,能读到上层函数的变量值

2:这样一直拿到父辈的值会造成内存泄漏,解决办法是 执行完后将父辈变量置为null

let arr = [3, 2, 4, 1, 5, 6];
function between(a, b) {
  return function(v) {
    return v >= a && v <= b;
  };
}
console.log(arr.filter(between(3, 5)));

闭包的问题

1:内存泄露——解决将上级作用域变量制空

2:this的指向问题——使用箭头函数

 this:

this的指向问题:

  1. 在对象的属性方法上,this指向该对象
  2. 在箭头函数身上,this指向函数上下文,会去找父级的this指向
  3. 在普通函数(具名函数和赋值匿名函数)身上,this指向windows对象
  4. 点击oclick等监听事件方法指向dom节点本身,也可以通过event拿到节点本身

解决this指向问题:

  1.  在使用回调function时,将之前对象的this存起来赋值给一个变量

  2. 使用箭头函数,就可以往上级去找this

 改变this的指向:

通过 call 和 apply 和 bind 改变

三种方法的区别:

  • 都接受参数,第一个参数均为传进去的对象,即要改变函数体内this指向的对象
  • call 方法  第二个传的值为 参数  ,多个参数用 逗号 分割 ,
  • apply 方法,第二个传的值为 参数 。把 参数 用 方括号 包起来
  • bind 方法, 第二个传的值为 参数 。 他和上面方法有个最大的区别是 他返回的是一个新函数,需要使用()去调用,还有它有两次传参的机会,一次在bind方法里,一次在()调用里 ​​​​,如果都传参,优先传入bind方法里参数值

call 方法
 let obj = {
    a: 1,
    show(...args) {
      console.log(this.a, ...args);
    }
  }

  obj.show.call({
    a: 3
  }, 4, 5)    //结果:  3 4 5


apply 方法
let obj = {
    a: 1,
    show(...args) {
      console.log(this.a, ...args);
    }
  }

  obj.show.apply({
    a: 3
  }, [4, 5])  //结果是  3 4 5  


bind 方法
 let obj = {
    a: 1,
    show(...args) {
      console.log(this.a, ...args);
    }
  }

  obj.show.bind({
    a: 3
  }, 4, 5)() //结果是  3 4 5  

或者
obj.show.bind({
    a: 3
  })(4, 5)  //结果是  3 4 5 

或者
 obj.show.bind({
    a: 3
  }, 7, 8)(4, 5)  //结果 3 7 8 4 5

普通函数和构造函数的区别:

普通函数包含块级作用域,含有自己的变量,在调用函数时,可以涉及到传参,构造函数可以通过new出来一个对象,然后构造函数里面包含该对象的属性和属性方法,通过this去调用。在没有构造新的对象时,this指向空对象

 作用域:

函数作用域:

全局作用域只有一个,每个函数又都有作用域(环境)。

  • 编译器运行时会将变量定义在所在作用域
  • 只有在全局作用域或一个函数作用域下定义的函数,才会去找父辈寻找变量,其余的只能通过传参
  • 使用变量时会从当前作用域开始向上查找变量!
  •  let c = 1
    
      function a() {
        console.log(c);   //c在当前函数作用域内未定义,所以去外层找
      }
      (a())

  • 但是对于对象的属性方法,通过this调用函数时,只能靠传参,传递变量参数,对于属性方法里面定义函数,函数可以找父辈要变量,不过这个情况少见,vue中一般都是属性方法里通过this调用其他属性方法,那样就只涉及到传参,因为调用的函数方法不是在该作用域内定义的。面向对象编程尽可能少使用面向过程。
  •  let obj = {
        a: 1,
        show() {
          let c = 2
          this.get(c)
        },
        get(c) {
          console.log(c);  //2
        }
      }
      obj.show()
    
    
    情况少见
     let obj = {
        a: 1,
        show() {
          let c = 2
          function s() {
            console.log(c);
          }
          s()
        },
    
      }
      obj.show()

  • 对于函数作用域里面定义的函数,如果里面的函数没有该变量,就会选择去父辈作用域寻找
  •  function a() {
        let s = 2
    
        function b() {
          console.log(s);
        }
        b()
      }
      a() //2

  • var是有函数作用域的,但没有块级作用域

函数在执行后,在其作用域里的变量会销毁,函数每次在调用时,重新创建新的作用域 ,在调用完该函数后,该函数销毁,重新调用,则生成新的函数地址空间,

如果想延申函数的生命周期,则使用闭包,返回函数体内的函数,将变量缓存起来

或者构造函数构造新的对象,对象调用属性方法时也会把构造函数的环境变量缓存起来

块级作用域:

在括号内则为一个块级作用域,仅适用于let const  。 var暂不适用,所以对于那种for循环有个方括号的块级作用域,var同样会不支持,他会跳出块级作用域的范围

两个块级作用于互不干扰
  {
    let a = 1
  } 
  {
    let a = 2
  }  

 {
    var a = 1
  } {
    let a = 2
  }
  console.log(a); //打印 a 为 1

如何实现var的伪块级作用域:我们知道var他是支持函数作用域的,所以我们用一个立即执行函数把var定义的变量包起来,将var定义的变量通过实参传给这个函数

 for (var i = 0; i < 3; i++) {
    (function (a) {
      console.log(a);  // 0 1 2
    })(i)
  }


如果不做伪块级作用域,var不支持块级作用域,他会跳出来进入全局作用域,此时的i会是3

for (var i = 0; i < 3; i++) {}
  console.log(i); //3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值