事件传参、递归、浅拷贝、深拷贝、JSON对象(进阶)

本文介绍了JavaScript中的静态作用域规则,展示了如何在函数间传递数据,包括事件处理函数的参数传递方法。同时,探讨了递归的概念,包括递归的执行顺序和带return的递归情况。此外,还讨论了引用类型的区别以及浅拷贝和深拷贝的问题,以及JSON对象在对象转换中的作用。
摘要由CSDN通过智能技术生成

 一、静态作用域

如何确定作用域链

  •      js的作用域链如何确定,取决于函数声明写在哪里,而不是函数调用写在哪里
  •      js的作用域链和函数调用写在哪里没有任何关系 => 静态作用域,词法作用域
  •      js的作用域属于静态作用域(词法作用域).
    let x = 20;

    function fn() {
      let x = 10;
      show(); // fn => 全局
    }

    function show() {
      console.log(x);// show => 全局 // 20
    }

    fn();
    let x = 20; (全局x)
    fn();
     let x = 10; (fn内的x)
     show();
       console.log(x);

二、函数间传递数据 

  • 如何让一个函数A去获取另一个函数B的局部变量的值?
  •     在B函数中调用函数A,并传入B函数的局部变量给A函数.
  •     这个做法是很多前端数据通信解决方案的核心逻辑。
  •     例如:jsonp,框架里的组件通信.状态管理.s 
    function A(x) {
      console.log(x);
    }

    function B(){
      let x = 100;
      A(x);
    }

    B();

三、事件传参  

<body>
  
  <button id='btn1'>按钮1</button>
  <div></div>
  <p></p>

  <button id='btn2'>按钮2</button>
  <h3></h3>
  <span></span>
</body>

3.0:方式一 

<script>

    // 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
    // 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span

    let aBtn = document.querySelectorAll('button');
    let oDiv = document.querySelectorAll('div')[0];
    let oP = document.querySelectorAll('p')[0];
    let oH3 = document.querySelectorAll('h3')[0];
    let oSpan = document.querySelectorAll('span')[0];

    aBtn[0].onclick = function() {
      oDiv.innerText = '这是div';
      oP.innerText = '这是一个p标签';
    }

    aBtn[1].onclick = function() {
      oH3.innerText = '这是一个h3';
      oSpan.innerText = '这是一个span';
    }
</script>

 方式二

<script>

    // 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
    // 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span

    let aBtn = document.querySelectorAll('button');
    let oDiv = document.querySelectorAll('div')[0];
    let oP = document.querySelectorAll('p')[0];
    let oH3 = document.querySelectorAll('h3')[0];
    let oSpan = document.querySelectorAll('span')[0];

    // 看代码 => 猜谜语 => 考虑返回值 => 你看见的是谜面 => 你得把谜底猜出来 
    // 不能这样写,这样写会把undefined赋值给点击事件,这是没有任何用处的.
    
    aBtn[0].onclick = setInnerText (oDiv, oP, '这是div', '这是一个p标签');

    aBtn[1].onclick = setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');

    function setInnerText (el1, el2, text1, text2) {
      el1.innerText = text1;
      el2.innerText = text2;
    }

  </script>

3.1

<script>

    // 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
    // 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span

    let aBtn = document.querySelectorAll('button');
    let oDiv = document.querySelectorAll('div')[0];
    let oP = document.querySelectorAll('p')[0];
    let oH3 = document.querySelectorAll('h3')[0];
    let oSpan = document.querySelectorAll('span')[0];
    
    // 事件传参的第一种解决方案:
    // 在匿名函数内调用函数并传参.

    aBtn[0].onclick = function () {
      setInnerText (oDiv, oP, '这是div', '这是一个p标签');
    };

    aBtn[1].onclick = function () {
      setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');
    };

    function setInnerText (el1, el2, text1, text2) {
      el1.innerText = text1;
      el2.innerText = text2;
    }

  </script>

3.2

<script>

    // 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
    // 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span

    let aBtn = document.querySelectorAll('button');
    let oDiv = document.querySelectorAll('div')[0];
    let oP = document.querySelectorAll('p')[0];
    let oH3 = document.querySelectorAll('h3')[0];
    let oSpan = document.querySelectorAll('span')[0];
    
    // 事件传参的第一种解决方案:
    // 在匿名函数内调用函数并传参.

    aBtn[0].onclick = setInnerText (oDiv, oP, '这是div', '这是一个p标签');
    
    aBtn[1].onclick = setInnerText (oH3, oSpan, '这是一个h3', '这是一个span');

    // 闭包传参.
    // 点击按钮,触发的是setInnerText函数,还是setInnerText的子函数?
    // 点击按钮,触发的是setInnerText的子函数
    function setInnerText (el1, el2, text1, text2) {  
      return function() {
        el1.innerText = text1;
        el2.innerText = text2;
      }
    }
  </script>

四、bind传参 

<body>
  
  <button id='btn1'>按钮1</button>
  <div></div>
  <p></p>

  <button id='btn2'>按钮2</button>
  <h3></h3>
  <span></span>
</body>

 4.0

<script>

    // 点击第一个按钮,给div设置内容,这是div,给p设置内容,这是一个p标签
    // 点击第二个按钮,给h3设置内容,这是一个h3,给span设置内容,这是一个span

    let aBtn = document.querySelectorAll('button');
    let oDiv = document.querySelectorAll('div')[0];
    let oP = document.querySelectorAll('p')[0];
    let oH3 = document.querySelectorAll('h3')[0];
    let oSpan = document.querySelectorAll('span')[0];
    
    // 事件传参的第一种解决方案:
    // 在匿名函数内调用函数并传参.

    // bind返回一个函数。用来给点击按钮触发。
    // bind返回的函数如何给它传参? => 直接给bind传参即可.(第一个参数不是)
    aBtn[0].onclick = setInnerText.bind (null, oDiv, oP, '这是div', '这是一个p标签');
    
    aBtn[1].onclick = setInnerText.bind (null, oH3, oSpan, '这是一个h3', '这是一个span');

    function setInnerText (el1, el2, text1, text2) {
      el1.innerText = text1;
      el2.innerText = text2;
    }

  </script>

 4.1

<body>
  
  <button>按钮</button>
</body>
<script>

    const oBtn = document.querySelectorAll('button')[0];

    // oBtn.onclick = show.bind(null, '你好');

    // 在show后面加.bind
    // bind的第一个参数写null.
    oBtn.onclick = show.bind(null, '你好坏哦,我好喜欢!');

    function show(msg) {
      console.log(msg);
    }

  </script>

五、递归 

    递归 => 函数自己触发自己 (跟循环有点像)

    表现形式 => 一个函数声明内部有自己的调用.

    递归都需要一个条件来终止递归.(跟循环一样);

    如果一个递归停不下来,会报下面的错误.

    Uncaught RangeError: Maximum call stack size exceeded

5.0 

<script>
    let count = 0;

    function show() {
      // 打印
      console.log(900);
      // 计数+1
      count++;
      // 如果不足3次,继续递归打印
      if (count < 3) {
        show();
      }
    }

    // for (let count = 0; count < 3; count++) {
    //   console.log(900);
    // }

    show();

  </script>

 5.1

    let arr = [1, 2, 3, 4, 5];

    // 写一个递归,打印这个数组的所有元素

    let i = 0;

    function fn() {
      console.log(arr[i]);
      i++;
      if (i < arr.length) {
        fn();
      }
    }

    fn();

 5.2

let arr = [1, 2, 3, 4, 5];

    function fn(i) {
      console.log(arr[i]);
      i++;
      if (i < arr.length) {
        fn(i);
      }
    }

    fn(0);

5.3

let arr = [1, 2, 3, 4, 5];

    // 传参写法。
    function fn (i) {
      i--;
      if (arr[i] !== undefined) {
        console.log(arr[i]);
        fn (i);
      }
    }
    fn(5);

六、工作中的递归 

<script>

    // 对象套对象,如果需要访问所有的对象属性.需要递归.

    function fn (obj) {
      console.log(obj.name)
    }

    let obj = {
      name: '小陈',
      obj: {
        name: '小赵',
        obj: {
          name: '小迪'
        }
      }
    }

    // 利用同一个函数,传递不同成参数,实现打印name.
    // fn(obj);
    // fn(obj.obj);
    // fn(obj.obj.obj);


  </script>

七、递归处理多层对象嵌套 

<script>

    // 对象套对象,如果需要访问所有的对象属性.需要递归.

    let obj = {
      name: '小陈',
      obj: {
        name: '小赵',
        obj: {
          name: '小迪',
          obj: {
            name: '小飞',
            obj: {
              name: '小文'
            }
          }
        }
      }
    }

    // 利用同一个函数,传递不同成参数,实现打印name.
    // fn(obj);
    // fn(obj.obj);
    // fn(obj.obj.obj);
    // fn(obj.obj.obj.obj);
    // fn(obj.obj.obj.obj.obj);

    function fn (obj) {
      // 打印实参的name属性
      console.log(obj.name);
      // 如果当前的obj存在obj属性,并且这个obj属性是对象,就递归打印name
      if (obj.obj !== undefined && typeof obj.obj === 'object') {
        fn(obj.obj);
      }
    }

    fn(obj);


  </script>

八、递归执行顺序 

递归执行时,最后调用的fn先结束调用,最先调用的fn最后结束调用。

    函数执行栈 => 先进后出,后进先出.

    递归的性能不好.递归次数越多,性能越不好.

function fn(i) {
      i++;
      if (i < 4) {
        console.log('递归前' + i);
        fn(i);
        console.log('递归后' + i);
      }
    }

    fn(0);

 递归执行顺序分析:

     A:
    console.log('递归前1');
    console.log('递归前2');
    console.log('递归前3');
    console.log('递归后1');
    console.log('递归后2');
    console.log('递归后3');
    B
    console.log('递归前1');
    console.log('递归后1');
    console.log('递归前2');
    console.log('递归后2');
    console.log('递归前3');
    console.log('递归后3');
    C
    console.log('递归前1');
    console.log('递归前2');
    console.log('递归前3');
    console.log('递归后3');
    console.log('递归后2');
    console.log('递归后1');

九、带return的递归 

<script>

    // 1*2*3*4*........

    // num拿到的是第一次fn调用return的值.
    let num = fn(3);

    // fn调用了3次.
    // 最后一次调用把结果1return给第二次调用,第二次调用把2*1return给第一次调用,第一次调用把3*2*1return给num

    function fn(n) {
      // 如果n是1,就返回1,如果不是1,就递归进行阶乘.
      // return n === 1 ? 1 : n * fn(n-1);

      if (n === 1) {
        return 1
      } else {
        return n * fn(n-1)
      }
    }

    // num1 = 1;
    // num2 = 2 * num1;
    // num3 = 3 * num2;

    // 兔子数列 => 斐波那契数列 => 从第三项开始,每项都是前两项的和。
    // 1,1,2,3,5,8,13,21............

    // let count = feibonaqi(10); => 45位 (算不动了)

  </script>

十、引用类型 

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>

10.0 

    let arr = [1, 2, 3];

    // 下面只操作item,原数组的元素是不会改变的
    arr.forEach((item, i) => {
      item++;
      arr[i]++;
    });
    let arr = [{num: 1}, {num: 2}, {num: 3}];

    // 如果数组的元素是对象,则操作item和arr[i]效果是一样的.
    arr.forEach((item, i) => {
      item.num++;
      arr[i].num++;
    })
    let aLi = document.querySelectorAll('li');

    aLi.forEach((item, i) => {
      aLi[i].style.backgroundColor = 'red';
      item.style.backgroundColor = 'red';
    })

 10.1

 js有6种数据类型:number,string,boolean,undefined,null,object

其中object可以分为:array,function,plain object,Math,arguments,node(节点),Date,....

 如果按照存储的方式不同:

    1:基本类型.(number,string,boolean,undefined,null)(存储在栈中).

    2:引用类型.(object)(存储在堆中).

    // 基本类型的赋值现象:
    // 赋值的本质就是复制.
    let x = 10;
    // 把x的值复制一份,再赋值给y.(1个10变成2个10)
    let y = x;
    // 把第二个10变成20,第一个10没有变化.
    y = 20;
    console.log(x);// 10
    // oYm是引用类型.
     let oYm = {name: '小陈'};
    // 引用赋值.
     let obj = oYm;
    // 修改obj的name,从oYm上也可以看到变化.
     obj.name = '小赵';
     console.log(oYm.name);// 

     基本类型变量内存储的是值本身.

  •     引用类型变量内存储的不是对象本身,而是对象的引用(地址).
  •     堆中的对象是不能直接访问的,需要通过对象的引用(地址)来访问.
  •     小陈这个人(对象).
  •     小陈的相片(对象的引用).看相片就能知道相片背后的是哪个人.
  •     基本类型存值本身 => 钱包装钱.钱就是基本类型.(钱直接装在钱包里).
  •     引用类型存的是对象的引用 => 钱包里放的是相片,而不是小陈这个人.(钱包内装的不是小陈这个人)

 10.2

// 引用赋值之后,两个变量代表同一个对象,所以这个变,那个也变.

    let oYm = {
      name: '小赵'
    }
    // 引用赋值,复制的是对象的引用.一个引用变成两个引用(不是一个对象变成两个对象)
    // 两个引用都指向同一个对象.这样在两个引用上都可以看到相同的变化。
    let obj = oYm;
    obj.name = '小赵';
    console.log(obj.name);// 小赵
    console.log(oYm.name);// 小赵
// 原始对象.
    let oYm = {
      name: '小陈'
    }

    引用赋值
    let obj = oYm;

    // 把obj的引用从存储幂幂的引用变成存储超越的引用.
    // 把obj变成一个新的对象,不会影响oYm.
    obj = {
      name: '小赵',
    }

    console.log(obj.name);// 小赵
    console.log(oYm.name);// 小陈

10.3

 当前的修改时在修改对象,还是在修改引用?

    修改纯对象:

    对象.属性名 = xxxxxxx

    对象[属性名] = xxxxxxx

    修改数组:

    数组[下标] = xxxxx;

    数组.属性 = xxxxx;

    push,pop,shift,unshift,splice,reverse,sort

    修改引用(变量重新赋新值):

    对象 = 新的值.

    数组 = [];

    function fn(list, num) {
      list.push(100);
      num = 100;
    };

    let arr = [1,2,3];
    let x = 10;

    // 引用赋值
    list = arr;
    // 基本赋值
    num = x;
    fn(arr, x);

    console.log(arr);// [1,2,3,100]
    console.log(x);// 10
let arr1 = 'john'.split('');
    arr2和arr1是同一个数组
    let arr2 = arr1.reverse();
    let arr3 = 'jones'.split('');
    arr2push,也就是arr1push.
    arr2.push(arr3);
    console.log(arr1.length + ' ' + arr1.slice(-1));
    // 5 ['j','o','n','e','s']
    const x = 10;
    // const声明的是常量,值不能修改
    x = 20;

    // obj是引用类型.存储的是引用.
    const obj = {name: 8080};
    // 存储的是引用,不能修改
    obj = 200;
    // 修改引用背后的对象,不会报错.
    obj.name = '8888';

    // const声明的数组,还能push吗?
    // 能,push修改的是数组本身,而不是数组的引用.

    const arr = [1,2,3];
    arr.push(4);
    // 不能直接修改数组引用
    arr = [];

十一、浅拷贝 

<script>

    // 深浅拷贝 => 引用类型的属性拷贝.(把一个对象的属性拷贝给另一个对象).

    let oYm = {
      name: '小陈',
      age: 32,
      sex: '女'
    }

    // 最简单的拷贝
    // let obj = {...oYm};

    let obj = {};

    // 循环对象的写法.对象有多少个属性,就会循环多少次.
    // prop就是每次循环到的属性名.
    // 语法 => for(let 属性名 in 对象名)
    for (let prop in oYm) {
      obj[prop] = oYm[prop];
    }

    // obj.name = oYm.name;
    // obj.age = oYm.age;
    // obj.sex = oYm.sex;

  </script>

十二、浅拷带来的问题 

浅拷贝在拷贝时,如果属性是引用类型,则拷贝前和拷贝后的属性指向同一个对象.
这样会导致修改其中一个,另一个也会跟着变. 

<script>

        let oYm = {
      name: '小陈',
      exhusband: {
        name: '小邓'
      }
    }

    let obj = {};

    // for in 浅拷贝.
    for (let prop in oYm) {
      obj[prop] = oYm[prop];
    }

    // obj.name = oYm.name;
    // 这是引用赋值,会导致obj.exhusband和oYm.exhusband指向同一个对象.
    // obj.exhusband = oYm.exhusband;

    // 修改obj的属性,实际上也是在修改oYm的属性.
    obj.exhusband.name = '小飞';
    // 小飞
    console.log(oYm.exhusband.name);

  </script>

十三、深拷贝 

  • 为了解决浅拷贝带来的问题,我们需要深拷贝
  • 浅拷贝拷贝前后是相同的对象.
  • 深拷贝拷贝前后是不同的对象.
<script>
    let oYm = {
      name: '小陈',
      exhusband: {
        name: '小邓'
      }
    }

    // 最简单的深拷贝.
    // let obj = JSON.parse(JSON.stringify(oYm));

    // 两步完成深拷贝
    let str = JSON.stringify(oYm);
    let obj = JSON.parse(str);

    obj.exhusband.name = '小飞';

    // false
    console.log(oYm.exhusband === obj.exhusband);
    // 小邓
    console.log(oYm.exhusband.name);

  </script>

十四、JSON对象 

JSON => 是一个不标准的对象 => 但是现在大多数浏览器都支持,因此都习惯当成标准的对象来用.

    JSON对象 => 处理纯对象.

    什么json对象 => 就是纯对象.

    for in => 循环对象.对象没有length属性,因此无法通过for来循环.

    JSON.parse => 把json字符串转换为json对象

    JSON.stringify => 把json对象转换为json字符串.

    // 纯对象(json对象)
    // let oYm = {
    //   name: '小陈'
    // }

    // JSON.parse() => 把json字符串转换为json对象
    // JSON.stringify() => 把json对象转换为json字符串

    let oYm = {name: '小陈'}

    // 打印oYm转换为json字符串之后的结果
    let str = JSON.stringify(oYm);
    console.log(str);
    console.log(typeof str);
    
    let obj = JSON.parse(str);
    console.log(obj);
    console.log(typeof obj);
    // 经过JSON.parse转换之后,oYm和obj就不是同一个对象了。
    console.log(obj === oYm);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值