第八章 探索JS中的函数秘密

本文深入讲解JavaScript函数的各种定义方式,包括全局函数、匿名函数、箭头函数等,探讨函数参数、this指针、回调及继承等核心概念。通过实例演示递归算法、this绑定、随机色效果等实用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.函数声明的多种方式

对象的方式定义函数

 let func = new Function("title","console.log(title)")
 func("weizhuren") // weizhuren

自变量方式定义函数(常用)

  function getName(name){
    console.log(name)
  }
  getName("weizhuren")

在对象中声明一类函数

  let user = {
    name:"",
    setUserName(name){
      this.name = name
    },
    getUserName(){
      console.log(this.name)
    }
  }
  user.setUserName("weizhuren")
  user.getUserName()//weizhuren

2.全局函数定义特点

当我们定义一个全局函数时,这个函数会被添加到window属性下,这就可能会造成我们定义的函数覆盖window下的属性,所以我们应该模块化定义方法(后面介绍)。

  function getInfo(){
    console.log("weizhuren")
  }
  window.getInfo()

我们也可以使用let来声明函数,这样声明的函数就不会成为window下的属性了

  let getInfo = function(){
    console.log("weizhuren")
  }
  window.getInfo()//报错

3.匿名函数与函数提升

不取名的函数,即为匿名函数,反之则为具名函数。

匿名函数不会存在函数提升,我们在函数体之前调用函数赋值的变量,不会调用函数,即使使用var(而不是let)也不可以;但是具名函数存在函数提升,可以在声明函数之前就调用函数。

 // 匿名函数 
 let getInfo = function(){
    console.log("weizhuren")
 }
 // 具名函数
 function getInfo(){
    console.log("weizhuren")
 }

4.立即执行函数与块作用域解决冲突

立即执行函数是指不需要调用就可以执行的函数

(function(){
    console.log("weizhuren") // weizhuren
})()

为了避免各个js库(引入文件或自己定义的js方法)冲突的问题,我们可以使用块作用域的方式解决。我们声明立即执行函数,这个作用域内的相关函数放到一个属性中,这样就可以避免各个js库产生冲突导致的函数调用不成功的情况了。

  (function(){
    function userName(){
      console.log("weizhuren")
    }
    function userInfo(){
      console.log("erbihouke")
    }
    window.user = {userName,userInfo}
  })(window)
  window.user.userName()// weizhuren
  window.user.userInfo()// erbihouke

除此之外,还可以用let定义函数,利用let块作用域的特点,避免冲突

{
    let userName = function(){
      console.log("weizhuren")
    }
    let userInfo = function(){
      console.log("erbihouke")
    }
    window.user = {userName,userInfo}
  }
  window.user.userName()// weizhuren
  window.user.userInfo()// erbihouke

5.形参与实参

形参表示不参与实际运算(函数处理)的参数;而实参表示参与实际运算(函数处理)的参数。在一个函数中,形参应该对应实参个数,如果形参数量大于实参数量,溢出的形参将为undefined值;如果实参数量大于形参数量,溢出的实参将会被忽略/

  function sum (a,b){//a,b为形参
    return a + b
  }
  console.log(sum(1,2))//3 这里的1,2为实参

6.默认参数和使用技巧

声明函数时,可以对函数的形参赋值默认参数,这样做的好处是当我们不传递这个参数的时候,也会存在默认的参数供我们使用。

  function avg(total,year = 1){
    return Math.round(total / year)
  }
  console.log(avg(2000,3))// 667
  console.log(avg(2000))// 2000

默认参数使用在数组排列上

  function sortArr(array,type="asc"){
    return array.sort(function(a,b){
      return type == "asc" ? a - b : b - a
    })
  }
  console.log(sortArr([3,1,4,2]))// [1, 2, 3, 4]
  console.log(sortArr([3,1,4,2],"desc"))// [4, 3, 2, 1]

PS:由于传参时参数由前向后依次对应,所以我们设置默认参数时,将这些参数放在形参最后面。

7.函数参数与arguments

函数参数可以是定义好的具名函数也可以是在参数中直接定义的匿名函数。

  let arr = [1,2,3,4,5,6,7].filter(function(a){
    return a <= 3
  })
  console.log(arr)// [1, 2, 3]

  function fl(a){
    return a <= 3
  }
  let arr = [1,2,3,4,5,6,7].filter(fl)
  console.log(arr)// [1, 2, 3]

如果我们的实参数量不确定,那么形参就不能确定,这时候使用arguments就可以解决这个问题,不过现在我们也可以使用展开语法更好的解决这个问题

// 使用arguments
function sum1 (){
  let total = 0
  for(let i = 0;i < arguments.length;i++){
    total += arguments[i]
  }
  return total
}
console.log(sum1(1,2,3,4,5,6))// 21

// 使用展开语法
function sum2 (...args){
  return args.reduce((a,b) => a + b)
}
console.log(sum2(1,2,3,4,5,6))// 21

8.箭头函数使用语法

 箭头函数是函数的一种简写形式,就是将“function(){}”简写为“()=>{}”的形式

  // 箭头函数
  let sum1 = (a,b) => {
    return a+b
  }
  console.log(sum1(1,2))// 3
  // 普通函数 
  let sum2 = function(a,b){
    return a+b
  }
  console.log(sum2(1,2))// 3

PS:

1.箭头函数只有一个参数的时候,不需要添加圆括号包裹参数;

2.如果箭头函数只有一行表达式,表达式不需要使用花括号包裹,并且会将计算结果自动return出来;

9.使用函数完成递归算法

  function factorial(num){
    if(num === 1){
      return 1
    }
    return num * factorial(num - 1)
  }
  console.log(factorial(5)) // 120

10.递归求和与点语法注意事项

使用递归求和

  function sum(...args){
    if(args.length === 0){
      return 0
    }
    return args.pop() + sum(...args)
  }
  console.log(sum(1,2,3,4,5,6,7,8))// 36

11.递归实现倒三角

function star(num){
  if(num === 0){
    return
  }
  document.write("*".repeat(num) + "<br/>")
  star(--num)
}
star(5)

12.递归附加参数使用技巧

使用递归实现点击数量的增加,调用函数后,所有点击数增加num个

  let lessons = [
    {
      title:'CSS',
      click:19
    },
    {
      title:'JavaScript',
      click:12
    },
    {
      title:'HTML',
      click:32
    },
    {
      title:'vue',
      click:41
    },
    {
      title:'node',
      click:11
    },
  ]
  // lessons课程,num增加点击数,i用于判断课程数量是否为零
  function change(lessons,num = 100,i = 0){
    if(i === lessons.length){
      return lessons
    }
    lessons[i].click += num
    return change(lessons,num,++i)
  }
  console.log(change(lessons))

13.什么是函数回调

回调函数可以理解为在其他函数中调用的函数。

这里的添加给button事件的函数,就是回调函数

<body>
  <button id="name">weizhuren</button>
</body>
<script>
  document.getElementById("name").addEventListener("click",function(){
    alert(this.innerHTML)
  })
</script>

14.展开语法(点语法)正确使用方式 

展开语法与带默认值的参数同时出现的话,将展开语法放到后面,因为展开语法不能确定参数的个数

  function sum(discount = 0,...price){
    let total = price.reduce((a,b) => a + b)
    return total * (1 - discount)
  }
  console.log(sum(0.5,199,2900,877))// 1988

15.函数与方法中this的不同

当一个函数作为一个对象的属性的时候,我们称其为方法。

对象中的this是指向对象本身的,函数中的this是指向全局的,如果想让函数的this不再指向全局,可以作为对象的属性生命出来,也就是方法了,此时的this是指向对象本身的。

 let obj = {
    site:'weizhuren',
    show(){
      // 对象中的this就是对象本身
      console.log(this.site)
       // 函数中的this就是全局的对象window
      function test(){
        console.log(this)
      }
      test()
    }
  }
  obj.show()
  // window与this
  // 在全局中,this就是window
  console.log(this === window)// true

// 可以使用对象的方式定义函数,这样可以让this指向对象而非全局
  function User(name){
    this.name = name
    this.show = function(){
      return this.name
    }
  }
  let lisi = new User("lisi")
  console.log(lisi.show())

16.通过常量改变this指针

解决方法1:将this赋值给一个常量

 let lessons = {
   site:"weizhuren",
   lists:["js","css","html"],
   show(){
     const that = this
     return this.lists.map(function(value){
      //  console.log(this) 即使声明了that,此时this也指向全局,但是that指向对象本身
      return `${that.site}的${value}`
     })
   }
 }
console.log(lessons.show())// ["weizhuren的js", "weizhuren的css", "weizhuren的html"]

解决方法2:利用函数特性,比如map函数中可以传入第二个参数,这个参数的值被设计为这个方法作用域内的this,如果我们将对象的this传入,那么map函数作用域内的this就是指向对象的this了,这个方法可以在提供此参数的方法中使用。

 let lessons = {
   site:"weizhuren",
   lists:["js","css","html"],
   show(){
     return this.lists.map(function(value){
       console.log(this)
      return `${this.site}的${value}`
     },this)
   }
 }
console.log(lessons.show())// ["weizhuren的js", "weizhuren的css", "weizhuren的html"]

PS:在这我我忽然有所顿悟,成为函数的this皆指向全局,成为方法的this则指向本身。

17.箭头函数带来的this变化实例

箭头函数中的this直接指向父级作用域中的this(或者称为上下文),而不是全局。这是因为箭头函数中没有this,所以在箭头函数中调用this时,函数会向上级寻找this。

 let lessons = {
   site:"weizhuren",
   lists:["js","css","html"],
   show(){
     return this.lists.map(value => {
      console.log(this)
      return `${this.site}的${value}`
     })
   }
 }
console.log(lessons.show())

我们在开发中可能会遇到这种问题,this即需要指向函数的父级作用域,也需要指向自身,那么此时我们怎么办呢?

解决方法1:使用event参数

无论在箭头函数,还是在普通函数中,都可以使用event参数,但是这个参数是由限制的,这个参数其实是标签的,所以在标签元素的函数中才可以使用。

<body>
 <button>weizhuren.com</button>
</body>
<script>
 let Dom = {
   site:"weizhuren",
   bind(){
    const button = document.querySelector("button")
    let info = "info"
    button.addEventListener("click",(event)=>{
      console.log(event)//输出dom对象的属性
    })
   }
 }
 Dom.bind()
</script>

PS:

1.箭头函数的this指向上下文,也就是说箭头函数中的this指向父级元素作用域的this。

2.普通写法函数中的this就是全局,我们可以在他的上(上级,也不一定是父机)级通过定义 “let that = this” 的方式,从而获取到上级的this指向。

18.this的构造实现原理

当我们声明一个构造函数的时候,这时候这个函数没有this(或者说this指向window);当我们使用这个构造函数构造一个新的对象的时候,这时的this就会指向函数本身。

function User(name){
  this.name = name
  console.log(this)
}
User()// 指向window
let lisi = new User("lisi")// User {name: "lisi"}

可以使用call()方法调用构造函数,改变this。

这里的第一参数是call的参数,会直接放到构造函数的属性中,接下来的参数是传递给构造函数的了。

到这里其实我还是没有理解call!!!接下来继续学习!!!

function User(name){
  this.name = name
  console.log(this)
}
let weizhuren = {url:'weizhuren.com'}
User.call(weizhuren,"第八章学习")// {url: "weizhuren.com", name: "第八章学习"}

19.call与apply

call与apply都是可以定义this的。他们的第一个参数都是重定义this指针的对象,剩下的参数是构造函数中的参数值。

call与apply的区别是:call方法在传递完this指针的对象后,对于构造函数中的参数逐个传递;而apply方法则是将构造函数中的参数放到数组中传递。

function User(name){
  this.name = name
  console.log(this)
}
let weizhuren = {url:'weizhuren.com'}
User.call(weizhuren,"第八章学习")// {url: "weizhuren.com", name: "第八章学习"}
User.apply(weizhuren,["第八章学习"])// {url: "weizhuren.com", name: "第八章学习"}

PS:

1.这里我有点懂了,但是对于第一个参数改变this的指针我不太能描述清楚,但是表面上看就是这个参数可以将自己的属性添加到构造函数中,并且可以使构造函数的this指向自身。

2.这里其实也可以不传递这个对象(也不能这么说,是可以随便传递,只要传递了参数,这个指针就会指向函数本身),但是如果一个参数都不传递的话,即使使用call和apply来调用构造函数,this仍然指向全局(window)。

这里忽然有所感悟(今天感悟有点多),一直再说语法啊,意义啊,其实这里一个小例子让我觉得编程最主要的还是思想,这里我们可以利用apply第二个参数为数组的特性,计算数组的最大/小值

因为Math.max(min)方法的参数不能是数组,这里我们可以直接用apply方法的特性将数组展开,当然也可以使用call方法,借助点语法(展开语法)来进行获取。

let arr = [1,2,3,4,5]
console.log(Math.max.apply(Math,arr))// 5
console.log(Math.max.call(Math,...arr))// 5

20.构造函数方法继承

使用call(apply)方法实现函数方法继承(代码复用),模拟一个真实场景,当我们需要访问某个接口时,接口的baseUrl基本上都是相同的,但是可能分布在各个子路径下,此时我们写一个Requset方法,来存放我们的路径,当不同接口调用的时候,利用对应的构造函数通过call(apply)调用Requset方法,并且传入对应参数(自身的this指针),就实现了函数方法的继承。

  function Request(){
    this.get = function(params){
    let str = Object.keys(params).map(k=> `${k}=${params[k]}`).join('&')
    let url = `www.weizhuren.com/${this.url}?${str}`
    console.log(url)
   }
  }
  function Article(){
    this.url = 'article/lists'
    Request.apply(this)
  }
  let a = new Article()
  // www.weizhuren.com/article/lists?id=1&cat=js
  a.get({id:1,cat:'js'})

  function User(){
    this.url = 'user/lists'
    Request.apply(this)
  }
  let b = new User()
  // www.weizhuren.com/user/lists?id=2&role=admin
  b.get({id:2,role:'admin'})

21.bind的使用方法

bind方法和call(apply)方法本之相同(也是逐个元素传递),细微的区别在于bind方法不直接调用函数,需要手动调用。

值得一提的是:

1.bind在两次调用中都可以传参,但是在绑定的时候(第一次调用时)传递的参数后,再在第二次调用时传递参数,第二次传递的参数不会覆盖第一次传递的参数。

2.如果参数个数为N个,第一次调用时传递的参数数量小于N个,第二次传递的参数的第一个参数,就是第一次传递参数d。

  function show(a,b){
    console.log(this.name,a,b)
  }
  show.call({name:"weizhuren"},1,2)// weizhuren 1 2
  show.bind({name:"weizhuren"},1,2)(3,4)// weizhuren 1 2
  show.bind({name:"weizhuren"},1)(3,4)// weizhuren 1 3

22.漂亮的随机色效果

<style>
  body{
    transition: all .5s;
  }
  h1{
    text-align: center;
    padding: 30px;
  }
</style>
<body>
  <h1>weizhuren</h1>
</body>
<script>
 function Color (elem){
   this.elem = elem
   this.colors = ['#1abc9c','#f1c40f','#9b59b6','#f39c12']
   this.run = function(){
    setInterval(function(){
      let i = Math.floor(Math.random() * this.colors.length)
      this.elem.style.background = this.colors[i]
    }.bind(this),1000)
   }
 }
 let obj1 = new Color(document.body)
 let obj2 = new Color(document.querySelector("h1"))
 obj1.run()
 obj2.run()
</script>
</html>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值