JS高级-函数this三种指向,函数上下文调用,闭包,递归 深浅拷贝

函数this的三种指向

01函数this的三种指向

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

            this相当于中文中的'我'

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

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

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

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

             一句话总结,this指向三选一   没点没new是window, 有new是实例,有点是点左边的对象

 <script>
        /* 
        1.环境对象 this : 谁调用我,this指向谁
            this相当于中文中的'我'
        2.this指向取决于函数的调用,函数调用有三种方式    
            普通函数;  函数名()  this-window
            对象方法:    对象名.方法名  this-对象
            构造函数;  new 函数名() this-new创建的实例对象
             一句话总结,this指向三选一  
        */
       function fn(){
          console.log(this);
       }
// (1)普通函数
fn() //this -window

// 
// (2)构造函数
 new fn() //this -new创建的实例对象
let obj={
    name:'张三',
    eat:fn
}
 // (3)对象方法
 obj.eat() //this-对象 
    </script>

02this指向测试

 <script>
        /* 
        环境对象 this : 
            普通函数;  
            对象方法:   
            构造函数; 
               
        */

      //作用域链
      let obj = {
        name: "张三",
        eat: function() {
          console.log(this)
          function fn() {
            console.log(this) 
          }
          fn()
        }
      }

      let eat = obj.eat
      obj.eat()
    </script>


02函数的上下文调用

01-call()调用函数

上下文调用 : 修改函数内部的this

1 函数名.call ({修改后的this指向},形参1,形参2…………)

   <script>
        /* 
        1.环境对象 this : 谁调用指向谁
            普通函数;函数名()  this-window
            对象方法:  对象名.方法名() this-对象
            构造函数;  new 函数名() this-new构造的实例对象
   *** 默认情况下,函数内的this是固定的,无法被修改
   *** 如果想要动态修改函数内部的this指向,啧需要使用上下文调用方法
     * 上下文调用 :函数作用域 上下文指向 修改函数作用域内部this指向

        2.上下文调用 : 
            2.1 函数名.call() call(修改的this,参数1,参数2)
            2.2 函数名.apply()
            2.3 函数名.bind()

        3. 面试必问:  call 和 apply 和 bind三者区别
        */
 function fn(a,b){
      console.log(this);
      console.log(a+b);
 }
    //  函数名.call(修改的this,参数1,参数2)
    fn.call({name:'张三'},10,20)  

    </script>

 call场景-伪数组转真数组

  //值类型

        // 字符串 数字 布尔 undefined null

  //引用类型      

        // 数组  函数  对象

        函数名.call() 应用:万能数据类型检测 
        
        1.typeof 数据:检测数据类型,但是有两种数据类型无法检测
         *typeof无法检测 数组和null两种数据类型得到的都是 object
        2.万能数据类型检测 :Object.prototype.toString.call(数据)
        加call的原因是 Object.prototype.toString 检测的是它自身的数据类型 

   <script>
        /* 函数名.call() 应用:万能数据类型检测 */
        
        /*  
        1.typeof 数据:检测数据类型,但是有两种数据类型无法检测
         *typeof无法检测 数组和null两种数据类型得到的都是 object
        2.万能数据类型检测 :Object.prototype.toString.call(数据)
        加call的原因是 Object.prototype.toString 检测的是它自身的数据类型
        */
  //值类型
        // 字符串 数字 布尔 undefined null
        let str='abc'
        let num=123
        let bol=true
        let und=undefined
        let nul=null

  //引用类型      
        // 数组  函数  对象 
        let arr=[1,2,3]
        let fn=function(){}
        let boj={name:'张三'}

        // 万能检测:
        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(unl));  // [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]
         // (1)Object.prototype.toString()内部会返回this的固定类型,得到固定格式字符串‘[object  数据类型 ]’
        // (2)使用object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象
        </script>

 apply()调用函数

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

apply会自动帮你遍历数组,然后按照顺序逐一传参

 <script>
      
 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])
    </script>

 apply场景 :伪数组转数组

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

        伪数组: 有 数组三要素(下标,元素 ,长度),不能使用数组的方法

         *伪数组的本质是 对象

<script>
        /* 
        apply()场景 :伪数组转真数组
        伪数组: 有 数组三要素(下标,元素 ,长度),不能使用数组的方法
         *伪数组的本质是 对象
        */
       let obj={
           0:10,
           1:20,
           2:30
       }
       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 ,而是借助apply的传参特点: 自动遍历伪数组/数组传参 所以第一个参数应该写arr(保持this不变)
    arr.push.apply(arr,obj)
    console.log(arr);

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

ES6 语法

伪数组转真数组,固定静态方法 Array.from(伪数组)

 let newArr=Array.from(obj)

    console.log( newArr);

apply场景02求数组最大值

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

 <script>
        /*
        apply()场景 :求数组最大值
        */
       let arr=[4,845,65,8,99,24,3]
    //    (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.apply(Math,arr)
console.log(max1);
// ES6 :Math.max(...arr)
// 功能类似于apply,也会自动的把数组给遍历
let max2=Math.max(...arr)
console.log(max2);
    </script>

 bind()调用函数

 1.环境对象 this : 谁调用指向谁

            普通函数;函数名()  this-window

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

            构造函数;  new 函数名() this-new构造的实例对象

   *** 默认情况下,函数内的this是固定的,无法被修改

   *** 如果想要动态修改函数内部的this指向,则需要使用上下文调用方法

     * 上下文调用 :函数作用域 上下文指向 修改函数作用域内部this指向

        2.上下文调用 :

            2.1 函数名.call() call(修改的this,参数1,参数2)

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

            2.3 函数名.bind() 函数名.bind(修改的this)

             *bind 不会立即执行函数,而是得到修改的this的新函数

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

<script>
     
 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])
   //(3)函数名.bind(修改的this)
    //bind 不会立即执行函数,而是得到一个修改的this之后的新函数
    let newFn=fn.bind({name:'王五'})
    newFn(22,33)
    </script>

 bind场景-修改定时器this

/* bind()场景: 修改定时器的this */

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

 <script>
        /* bind()场景: 修改定时器的this */
        // 定时器中的this默认指向window,如果要修改定时器的this,就需要使用this
        let fn= function (){
         console.log(this);
        }
        let newFn=fn.bind({name:'1111'})
        setTimeout(newFn,2000)
      // 上面代码可以简写一行
        // setTimeout(function(){}.bind(),2000)
        setTimeout(function(){
          console.log(this);
        }.bind({name:'李四'}),2000)
        /* 
        变量:是内存空间 只有存储功能,没有运算功能
        变量只有两种语法 :存 ,取
        字面量 :是数据 .只有运算功能,没有存储功能
        */
      //  let arr=[10,20,30]
      //  [10,20,30][0]

    </script>

 经典面试题 call apply bind区别

3. 面试必问:  call 和 apply 和 bind三者区别

   相同点 都可以修改this指向

   不同点

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

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

03闭包

 1.闭包closure是什么 :  两个条件 a:函数 b:函数内部还要访问 其他函数的变量

        其他函数的变量不能是全局变量,也不能是自己的变量,必须是其他函数的

            (1)闭包是一个 访问其他函数内部变量 的函数

            (2) 闭包 =函数 + 上下文引用

           

        2.闭包作用 : 解决变量污染

 <script>
//num +fn1()组成了闭包
let age=20
function fn(){
let num=20
function fn1(){
  console.log(num);
  console.log(age);
}
fn1()
}
  fn()    
    </script>

闭包案例

<script>

 //  打事件断点 从函数体开始 点击事件类型
        // 点击
        document.querySelector('.btn').addEventListener('click',function(){
            // (1)获取输入框文本
            let text=document.querySelector('input').value
            // (2)模拟网络请求
            setTimeout(function(){
                alert(`${text}的搜索结果为123456`)
            },1000)
        })
</script>

04递归

1.递归函数: 一个函数 在内部 调用自己

* 递归作用和循环类似的,也需要有结束条件

 1.递归函数:  在函数中调用自己

        *递归类似于循环,也要有结束条件

 <script>
        /* 
        1.递归函数:  在函数中调用自己
        *递归类似于循环,也要有结束条件
            

        2.递归应用:
        
        */    
function fn(){
    console.log('hh');
    // 递归调用
    fn()
}
    // 双函数递归
    function fn1(){
        console.log('嘿嘿');
        fn2()

    }
    function fn2(){
        console.log('呵呵');
        fn1()
    }
    </script>

 02浅拷贝与深拷贝json实现

 1. 浅拷贝与深拷贝 有两种实现方式  

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

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

        2.深拷贝两种方式

        (1)  json方式  let newObj=JSON.parse(JSON.stringify(js对象))

        */  

浅拷贝

浅拷贝 :拷贝地址

浅拷贝如果是一层对象,不互相影响,可以直接拷贝值

如果是复杂类型的拷贝 ,出现多层拷贝会相互影响

<script>

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

let newObj=obj
newObj.name='李四'
 console.log(obj.newObj);
</script>

深拷贝

深拷贝拷贝的是对象,不是地址

常见方法

1,通过递归实现深拷贝

2.lodash/cloneDeep

3.通过JSON.stringfiy()实现

js库loadsh里面cloneDeep内实现了深拷贝

 json深拷贝

// json 深拷贝

//   (1)JSON.stringify(js对象):把js对象 ->json字符串(json 底层会自动深拷贝)

<script>
 let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习']
        }
// let json =JSON.stringify(obj)
    //  (2) JSON.parse(json字符串)  :json字符串 ->js对象
        // let newObj=JSON.parse(json)
        // 简写一行
        let newObj=JSON.parse(JSON.stringify(obj))
        newObj.name='李四'
        newObj.hobby[0]='游戏'
        console.log(obj,newObj);

</script>

 03浅拷贝与深拷贝  递归实现

        2.递归应用:

            浅拷贝与深拷贝 :

            json方式  let newObj=JSON.parse(JSON.stringify(js对象))

               

            遍历dom树

如果是数据,直接进行拷贝 如果是数组或者对象 要 (1)声明一个空数组 (2)递归遍历旧的数组,把旧的数组里的数据追加给新的数组

(两者数组和对象的地址一样,所以修改新的数据的同时修改了旧的数据)

<script>
        /* 
        1.递归函数:  
            

        2.递归应用:
            浅拷贝与深拷贝 : 
            json方式  let newObj=JSON.parse(JSON.stringify(js对象))
                
            遍历dom树
        */  
       
        let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习'],
            student:{
                name:"班长",
                score:90
            }
        }
// 深拷贝函数封装
function kaobei(obj,newObj){
    // 遍历obj ,把里面的数据拷贝给newObj
    for(let key in obj){
        // 判断是不是数组,如果是数组还需要继续遍历拷贝
        if(obj[key] instanceof Array){
            // (1)声明一个空数组
            newObj[key]=[]
            // (2)递归遍历数组
            kaobei(obj[key],newObj[key])
        }else if(obj[key] instanceof Object){
            // (1)声明一个空数组
            newObj[key]=[]
            // (2)递归遍历数组
            kaobei(obj[key],newObj[key])
        }else{
            newObj[key]=obj[key]
        }
    }
}

        
    </script>

递归遍历dom数 (了解)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }

      .menu p {
        width: 100px;
        border: 3px solid;
        margin: 5px;
      }

      .menu > div p {
        margin-left: 10px;
        border-color: red;
      }

      .menu > div > div p {
        margin-left: 20px;
        border-color: green;
      }

      .menu > div > div > div p {
        margin-left: 30px;
        border-color: yellow;
      }
    </style>
  </head>
  <body>
    <div class="menu">
      <!-- <div>
        <p>第一级菜单</p>
        <div>
          <p>第二级菜单</p>
          <div>
            <p>第三级菜单</p>
          </div>
        </div>
      </div> -->
    </div>
    <script>
      //服务器返回一个不确定的数据结构,涉及到多重数组嵌套
      let arr = [
        {
          type: "电子产品",
          data: [
            {
              type: "手机",
              data: ["iPhone手机", "小米手机", "华为手机"]
            },
            {
              type: "平板",
              data: ["iPad", "平板小米", "平板华为"]
            },
            {
              type: "智能手表",
              data: []
            }
          ]
        },
        {
          type: "生活家居",
          data: [
            {
              type: "沙发",
              data: ["真皮沙发", "布沙发"]
            },
            {
              type: "椅子",
              data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
            },
            {
              type: "桌子",
              data: ["办公桌"]
            }
          ]
        },
        {
          type: "零食",
          data: [
            {
              type: "水果",
              data: []
            },
            {
              type: "咖啡",
              data: ["雀巢咖啡"]
            }
          ]
        }
      ]
 //  封装一个添加菜单的函数

 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')) 
    </script>
  </body>
</html>

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值