函数及this指向

175 阅读6分钟

函数?

函数概念:

function执行特殊任务的代码块

函数的作用:

函数可以实现代码复用,提高开发效率

函数命名规范 :

1.不能用关键字,有特殊含义的字符,JavaScript 内置的一些英语词汇.例如: let class等

2.只能用下划线、字母、数字、$组成,且数字不能开头

3.字母严格区分大小写,如 Age 和 age 是不同的变量

4.尽量使用小驼峰式命名法,例:getName

5.前缀为动词:

动词含义
can判断是否可执行某个动作
is判断是否为某个值
get判获取某个值
set设置某个值
load加载某些数据

函数返回值:

当调用某个函数,这个函数会返回一个结果出来,这就是有返回值的函数.

当函数需要返回数据出去时,用return关键字

语法: return 数据

例如 : function getSum(x,y) { return x + y}

return 注意点:

1.在函数体中使用 return 关键字能将内部的执行结果交给函数外部使用

2.return 后面代码不会再被执行,会立即结束当前函数,所以 return 后面的数据不要换行写

3.return函数可以没有 return,这种情况函数默认返回值为 undefined

函数细节补充

1.两个相同的函数后面的会覆盖前面的函数

2.在Javascript中 实参的个数和形参的个数可以不一致

如果形参过多 会自动填上undefined (了解即可)

如果实参过多 那么多余的实参会被忽略 (函数内部有一个arguments,里面装着所有的实参)

3.函数一旦碰到return就不会在往下执行了 函数的结束用return

具名函数

具名函数的声明:

function 函数名(形参) { 函数体 }

具名函数调用:

函数名(实参)

匿名函数

匿名函数 : function () {函数体}

函数表达式:将匿名函数赋值给一个变量,并且通过变量名称进行调用 我们将这个称为函数表达式

let fn = function () {}

调用: fn ()

立即执行函数

无需调用,立即执行,其实本质已经调用了

作用: 避免全局变量之间的污染

两种书写方式 :

方式1 : ( function () {函数体} ) () ;

方式2 : ( function () {函数体} () ) ;

(1)当立即执行函数前面还有别的代码要执行,则要在前面加上分号

(2)多个立即执行函数之间也用分号隔开 不然会报错

回调函数

回调函数:被当做参数的函数,称为回调函数

回调函数通常出现在 : 定时器、事件处理函数、ajax

定时器

setInterval(function () {函数体},1000)

setTimeout(function () {函数体} ,1000 )

事件处理函数 : 元素.addEventListener ('事件类型',function () {})

ajax :

axios({
      url:'请求路径',
      method:'get',
      data: { post请求参数},
      params: { get请求参数}
    }).then(res=>{
      //成功回调
      console.log(res)
    })
   function fn (n) {

     }
// 被当做参数的函数,称为回调函数
// 此时的匿名函数为 回调函数,fn 为 高阶函数
     fn (function () {})

间歇函数--- setInterval

  <script>
   // 创建(开启) 计时器
   // 语法 : setInterval (函数,时间)
     // 1.时间是以毫秒为单位  2.每隔多长的时间调用一次函数 3. 若函数为具名函数,则只写函数名 不写括号()
 
     function fn () {
       console.log('打印');
     }
   let timer1 = setInterval(fn, 1000)

   let timer2 = setInterval(function() {
     console.log('打印1');
   },1000)
 // 关闭定时器 
 // 定时器标识:创建定时器时  会自动返回定时器标识(数字)
 // clearInterval(定时器标识)

 // clearInterval(timer1)

 clearInterval(timer2)
 </script>

延时函数---setTimeout

延时函数: 延迟定时器,只出现一次

// 创建语法:setTimeout(函数, 延迟的时间);
  const t2 = setTimeout(function (){
      console.log(123)
//清除语法:clearTimeout(定时器标识)   
      clearTimeout(t2)

递归函数

递归函数: 函数内部调用自己 例如 : function fn () { fn() }

递归函数应用

应用1 :延时函数配合递归函数 得到的效果和间歇函数一样

// 延时函数配合递归函数 得到的效果和间歇函数一样
 function show () {
   let n = new Date ()
   document.querySelector('div').innerHTML = n.toLocaleString()
    // 2.延时函数做法:
    setTimeout(show,1000)
 }
show()

/* //  1.直接用间歇函数的做法:
show()  //bug1  防止每次页面刷新时,有一秒的卡顿(出现div原本就有的"时间"两个字)
setInterval(show,1000) */

应用2 :深拷贝/浅拷贝

 let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习']
        }

浅拷贝:拷贝地址,修改拷贝之后的数据对原数据有影响

//  浅拷贝:拷贝地址
        let newObj  = obj
        newObj.name = '李四'
        console.log(obj,newObj);

深拷贝: 拷贝数据,修改拷贝后的数据对原数据没有影响

深拷贝的两种方式:

方式1:json方式

(1)JSON.stringify(js对象):把js对象转换为接送字符串

(2)JSON.parse(json字符创): json字符串转为js对象

方式2:递归函数

// 深拷贝---json

       /*  let json = JSON.stringify(obj)
        let newObj1 = JSON.parse(json) */

        // 简写:
        let newObj1 = JSON.parse(JSON.stringify(obj)) 
        console.log(obj,newObj1);


        // 深拷贝---递归函数
        // 深拷贝函数
        function copy(obj,newObj) {
            // 遍历obj,把obj里面的数据 拷贝给newObj
            for(let key in obj) {
                // 判断obj里面的属性是不是数组
                if (obj[key] instanceof Array) {
                    // 如果是,建立一个空数组
                    newObj[key] = []
                    // 递归函数--遍历数组
                    copy(obj[key],newObj[key] )
                    // 判断obj里面的方法
                } else if (obj[key] instanceof Object) {
                    // 如果是方法,声明一个空对象
                    newObj[key] = {}
                    copy(obj[key],newObj[key] )
                } else {
                    newObj[key] = obj[key]
                }
            }
        }

        // 创建一个空对象
        let newObj2 = {}
        // 调用copy函数
        copy(obj,newObj2)
        console.log(obj,newObj2);

应用3:递归函数的应用----遍历DOM树

//封装一个添加菜单的函数
      function addElement(arr,father){
        for(let i = 0;i<arr.length;i++){
          let div = document.createElement('div')
          div.innerHTML = `<p>${ arr[i].type || arr[i] }</p>`
          father.appendChild(div)
          if( arr[i].data ){
            // 调用函数
            addElement(arr[i].data , div )
          }
        }
      }


      //调用函数
      addElement( arr, document.querySelector('.menu') )

箭头函数

箭头函数 : 相当于函数表达式的简写:

把function改成箭头 => (2)把形参() 写在箭头 左边

    //匿名函数(函数表达式)
    const fun = function() {
      console.log(456);
    }
    fun()
    // 1.箭头函数语法: (先声明再调用)
    const fn = () => {
      console.log(123);
    }
    
    fn()

    // 2.箭头函数只有一个形参时,可以省略小括号
    const fn1 = x => {
      console.log(x);
    }

    fn1(1)

    // 3.箭头函数只有一行代码的时候,可以省略花括号{}

    const fn2 = x => console.log(x);
      
    fn1(2)

    // 4. 箭头函数只有一行代码的时候,可以省略return
    const fn3 = x => x + x
    /* 
    const fn3 = (x) => {
      return x + x
    } 
    
    */

    console.log(fn3(22));

    // 5.箭头函数可以直接返回一个对象,返回对象要加()
    const fn4 = (uname) => ({uname:uname})

    console.log(fn4('剪刀手'));

    // 6.箭头函数里面没有 arguments动态参数  只有剩余参数
    // 箭头函数求和:
    const fn5 = (...arr) => {
      let sum = 0
      for (let i = 0; i < arr.length; i++) {
        sum += arr[i]      
      }
      console.log(sum);
    }

    fn5 (1,2,3,4,5)

    // 7.箭头函数不会 创建自己的this,他只会从自己的作用域的上一层找this 
          // 箭头函数this : 箭头函数没有this
           // * 箭头函数中使用this, 本质是通过作用域链找上一级作用域的this 
    // DOM事件不推荐使用箭头函数

函数内的this指向

  1. 环境对象 this : 谁 ‘调用’ 我,this就指向谁

说人话: this相当于中文中的 '我' , 谁说出来这个字,这个字就代表谁

  1. this指向取决于函数的调用, 函数有三种调用方式

(1)普通函数: 函数名() this->window

(2)构造函数: new 函数名() this->new创建的实例对象

(3)对象方法: 对象名.方法名() this->对象

一句话总结: this指向三选一, 先找new,后找点

补充:

(1)定时器里的this默认指向window

(2) this只在函数中存在,箭头函数内部没有this

3.箭头函数不会 创建自己的this,他只会从自己的作用域的上一层找this 箭头函数this : 箭头函数没有this

  • 箭头函数中使用this, 本质是通过作用域链找上一级作用域的this

4.拓展:箭头函数没有this,对箭头函数影响

(1)箭头函数不能作为构造函数

(2)箭头函数不能修改this

(3)事件处理函数一般不用箭头函数

修改this指向

  • 默认情况下,函数内的this是固定的,无法被修改
  • 如果想要动态修改函数内部的this指向,则需要使用上下文调用方法
  • 上下文 : 函数作用域 上下文指向: 修改函数作用域内部this指向
  • 上下文调用 : 修改函数内部this
  1. 函数名.call()

  2. 函数名.apply()

  3. 函数名.bind()

函数名.call()

//声明函数
        function fn( a , b ) {
            console.log(this)
            console.log(a + b)
        }

        // 函数名.call(修改的this,参数1,参数2………)
        fn.call( { name: '张三' } , 10 , 20 )  // call()

call()使用场景---检测数据类型

函数名.call() 应用 : 万能数据类型检测

  1. typeof 检测数据 : 检测数据类型,但是有两种数据类型无法检测
  • typeof无法检测 数组 与 null , 得到的都是'object'

2.万能数据类型检测: Object.prototype.toString.call( 数据 ) :

万能数据类型检测原理 :

(1)Object.prototype.toString() 内部会返回this的数据类型, 得到固定格式字符串 '[object 数据类型]'

(2)使用Object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象

//值类型
        let str = 'abc'
        let num = 123
        let bol = true
        let und = undefined
        let nul = null

        //引用类型
        let arr = [10,20,30]
        let fn = function(){}
        let obj = {name:'ikun'}

        console.log( typeof str )//'string'
        console.log( typeof num )//'number'
        console.log( typeof bol )//'boolean'
        console.log( typeof und )//'undefined' 未定义
        console.log( typeof nul )//'object'    空值
        console.log( typeof arr )//'object'
        console.log( typeof fn )//'function'
        console.log( typeof obj )//'object'

        /* 万能数据类型检测原理
        (1)Object.prototype.toString() 内部会返回this的数据类型, 得到固定格式字符串 '[object 数据类型]'
        (2)使用Object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象
        */

       console.log(  Object.prototype.toString.call(str) )//'[object String]'
       console.log(  Object.prototype.toString.call(num) )//'[object Number]'
       console.log(  Object.prototype.toString.call(bol) )//'[object Boolean]'
       console.log(  Object.prototype.toString.call(und) )//'[object Undefined]'
       console.log(  Object.prototype.toString.call(nul) )//'[object Null]'
       console.log(  Object.prototype.toString.call(arr) )//'[object Array]'
       console.log(  Object.prototype.toString.call(fn) )//'[object Function]'
       console.log(  Object.prototype.toString.call(obj) )//'[object Object]'
       

函数名.apply()

函数名.apply(修改的this, 数组/伪数组 )

apply会自动遍历数组和伪数组,然后逐一传参

 //声明函数
        function fn(a, b) {
            console.log(this)
            console.log(a + b)
        }

        //(1) 函数名.call(修改的this,参数1,参数2………)
        fn.call({ name: '张三' }, 10, 20)

        //(2) 函数名.apply(修改的this, 数组/伪数组 )
        // apply会自动遍历数组和伪数组,然后逐一传参
        fn.apply({ name: "李四" }, [50, 60] )

apply()应用场景---伪数组转真数组

/* 
        apply()场景:  伪数组转真数组

        1.伪数组 :  有 数组三要素(下标、元素、长度),不能使用数组的方法
            * 伪数组本质是 对象
        */

        let obj = {
            0: 10,
            1: 20,
            2: 30,
            length: 3
        }
        console.log(obj)

        //需求: 有时候伪数组 想要使用 真数组的方法 , 就需要把伪数组转成真数组

        //(1)把伪数组的元素取出来, push到真数组中
        let arr = []
        // arr.push(obj[0], obj[1], obj[2])
        // console.log(arr)

        //(2)手动写循环遍历添加
        // for (let i = 0; i < obj.length; i++) {
        //     arr.push(obj[i])
        // }

        //(3) arr.push.apply( arr,伪数组 )
        //这里使用apply不是为了修改this,而是借助传参特点:自动遍历伪数组/数组传参. 所以第一个参数应该写arr(保持this不变)
        arr.push.apply(arr,obj)
        console.log( arr )

        //ES6 : 伪数组转真数组,固定静态方法   Array.from( 伪数组 )
        let newArr = Array.from( obj )
        console.log( newArr )

apply()使用场景--- 求数组最大值

/* 
        apply()场景 : 求数组最大值
        */


        let arr = [20, 55, 60, 80, 100, 30, 40]

        //(1)js基础 : 擂台思想

        let max = arr[0]
        for (let i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i]
            }
        }
        console.log(max)

        //(2)js高级 : Math.max()

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

        //ES6 : Math.max(...arr)
        // ...功能类似于apply,也会自动的把数组给遍历
        let max2 =  Math.max(...arr)
        console.log(max2)

函数名.bind()

函数名.bind(修改的this)

  • bind()不会立即执行函数,而是得到修改this的新函数。

  • bind()一般修改: 定时器函数 、 事件处理函数

    //声明函数
    function fn(a, b) {
      console.log(this)
      console.log(a + b)
    }
 //(3) 函数名.bind(修改的this)
    // bind不会立即执行函数,而是得到一个修改this之后的新函数
    let newFn = fn.bind({name:'王五'})
    newFn(22,33)

bind()场景---修改定时器的this

//定时器中的this默认指向window,如果修改定时器的this,就需要使用bind

    let fn = function (){
      console.log(this)
    }

    let newFn = fn.bind({name:'111'})

    setTimeout( newFn  , 2000)

    //上面步骤可以简写一行
    setTimeout( function(){
      console.log(this)
    }.bind({name:'李四'}) , 2000 )

    /* 
    变量 : 是内存空间. 只有存储功能,没有运算功能。
      变量只有两种语法 : 存 , 取
    字面量 : 是数据 。 只有运算功能,没有存储功能。
    */

call、apply、bind三者的区别

call 和 apply 和 bind 三者区别

相同点 : 都可以修改this指向

不同点 :

(1)传参方式不同 : call是单个传参, apply是数组/伪数组传参

(2)执行机制不同 : call和apply会立即执行函数, bind不会立即执行而是得到修改this的新函数