个人自学前端16-JS9-递归和引用类型,eval

递归和引用类型

一 递归

1.1 什么是递归?

简单理解:函数自己触发自己。
表现形式 => 一个函数声明内部有自己的调用。
递归都需要一个条件来终止递归.(跟循环一样)。

1.2 递归的作用

递归的作用类似于循环,所有的循环都可以通过递归来改写,但是不是所有的递归都可以通过循环来代替。

1.3 使用场景

何时用循环,何时用递归?

明确知道重复次数的,用循环,不知道重复次数的,用递归。

两个生活场景:

1:给你一摞扑克牌,让你数有多少张,这里应该是循环的逻辑。因为不管数多少遍,次数是固定的。

2:给你一个骰子,让你扔出6,扔不出继续扔,这里应该是递归的逻辑,因为扔几次出6,是不固定的。

递归用于封装函数上,有些时候我们并不知道需要函数执行多少次才可以得到结果,这种情况都可以使用递归。

// getNum 是一个求阶乘的函数。参数6意即求1-6的阶乘。
// 因为实参的数字是不确定的,我们不知道需要函数执行多少次才可以得到最终的结果,因此可以使用递归
let num = getNum(6);

function getNum(n){
    return n==1 ? 1 : getNum(n-1)
}

// 对象套对象,如果需要访问所有的对象属性.需要递归
let obj = {
      name: '幂幂',
      obj: {
        name: '超越',
        obj: {
          name: '热巴'
        }
      }
    }

function fn (obj) {
      console.log(obj.name)
    }
let obj = {
      name: '幂幂',
      obj: {
        name: '超越',
        obj: {
          name: '热巴',
          obj: {
            name: '德刚',
            obj: {
              name: '刘谦'
            }
          }
        }
      }
    }

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);

1.4 递归注意事项

循环需要判断条件以结束循环,递归也需要条件终止循环。

死循环不会报错,但是死递归会报错。

getNum();

// 这就是一个死递归
function getNum(){
	getNum();
}

常规递归的性能特别差,因此能不用递归就尽量不用递归。有些公司的规范中明确要求不能使用递归。

为何递归的性能特别差?

因为递归的作用域是嵌套的,递归10次的作用域就嵌套10次,只有最后一次递归结束释放作用域后,才逐层往外释放父级作用域。第一次调用的作用域在最后才能被释放。

ES6提供了尾递归优化,递归不再有性能问题,但是写法变得更不直观。感兴趣有能力的同学可以研究一二。

1.5 递归报错

如果一个递归停不下来,会报下面的错误.
Uncaught RangeError: Maximum call stack size exceeded

二:引用类型

js数据类型按存储方式不同又可以分为:基本类型和引用类型。

1.1 基本类型

js 数据类型中的基本类型包括6种:

数字(number),字符串(string),布尔值(boolean),undefined,null,symbol。

1.2 引用类型

js数据类型种只有 object 类型属于引用类型。

object类型又可以细分为:

数组(array),函数(function),纯对象(plain object),标签对象(nodeList),日期对象,arguments等

1.3 基本类型和引用类型存储值的区别

基本类型的值直接存储于变量中。

引用类型的值不能直接存储于变量中。(变量中存储的不是引用类型的值本身)(变量返回的不是引用类型的值)

引用类型有两个概念:

1:引用

2:值(对象本身)

对象的引用和对象本身的关系:对象只能够通过对象的引用来访问和操作

举个例子:杨幂(对象本身),杨幂的名片(对象的引用)。我们可以通过杨幂的名片找到杨幂这个人。

引用类型的变量,存储的是对象的引用,而不是对象本身!

取引用类型变量的值:通过变量找到引用,再通过引用找到对象本身。变量=>引用=>对象本身

取基本类型变量的值:直接通过变量取值。变量 => 值。

1.4 基本类型和引用类型赋值上的区别

js 的赋值,再本质上就是复制!

// 以下赋值,其实就是把x中的10多复制出一份,把复制出来的这个10再赋值给变量y,导致最后有两个10.
let x = 10;
let y = x;
console.log(x);//10
console.log(y);//10

因为基本类型变量中存储的就是值本身,因此赋值时复制的就是值本身.

let x = 10;
let y = x;
console.log(x);//10
console.log(y);//10
y = 20;
console.log(x);// 10
console.log(y);// 20

而引用类型变量中存储的是对象的引用,因此赋值时复制的是对象的引用而不是对象本身!

// oYm是引用类型,他的值是对象的引用,把它的值赋值给oCy,是把对象的引用复制一份,再赋值给变量oCy.
// 这会导致oYm和oCy指向同一个对象.
let oYm = {name:"幂幂"};
let oCy = oYm;

// 引用赋值会导致一个对象拥有多个引用.(一个对象可以通过多个对象来操作);
// 通过对象的引用oCy来操作对象,可以通过另一个引用oYm看到变化.
oCy.name = '超越';
console.log(oYm.name);// 超越

1.5:难点

很多时候,引用类型难,是因为分不清到底修改的是变量还是修改的是对象!

let oYm = {name:"幂幂"};
let oCy = oYm;
oCy.name = '超越';
console.log(oYm.name);// 超越

// 这里,把oCy赋值一个新的对象,是让oCy存储另一个对象的引用,不会修改原来的oYm对象。
oCy = {name:'坤坤'};
console.log(oYm.name);// 超越

注意:

1:变量的赋值操作,是在修改变量的值!如果是引用类型,这个操作不会影响引用背后的对象!

2:如果是通过变量操作对象的属性,则会影响对象本身!

几个注意的细节:

非数组对象的修改:

方式1:oYm.属性 = 新的值

方式2:oYm[属性] = 新的值

数组的修改:

方式1:arr[下标] = 新的元素值

方式2:通过可修改数组的方法修改数组。例如:push,splice等。

1.6:深拷贝和浅拷贝

由于引用类型赋值其实赋值的是对象的引用,因此在引用类型的赋值的过程中有两种需求:

1:赋值前后指向同一个对象。(浅拷贝)

2:赋值前后不指向同一个对象。(指向新的对象)(深拷贝)

// 浅拷贝
let oYm = {name:"幂幂"};
let oCy = oYm;
// 浅拷贝
let oYm = {name:'幂幂'};
let oCy = Object.assign(oYm); (对象合并)

// 深拷贝
let oYm = {name:"幂幂"};
let oCy = {};
oCy.name = oYm.name
// 深拷贝
let oYm = {name:"幂幂"};
let oCy = {...oYm};

以上是简单示范,但是因为对象的属性本身也可能是引用类型,因此实际工作的深拷贝要复杂一些。

// 以下的例子中,oYm和oCy不是同一个对象,但是oYm.person和oCy.person却指向同一个对象,是浅拷贝.
let oYm = {person:{name:'幂幂'}};
let oCy = {};
oCy.person = oYm.person;

对于对象的深拷贝,有两种做法:

1:简单利用JSON.stringify和JSON.parse来进行深拷贝。

// 这样做,oCy和oYm的任何属性都不会指向同一个对象。
let oYm = {person:{name:'幂幂'}};
let oCy = JSON.parse(JSON.stringify(oYm));

2:通过循环和递归来实现深拷贝。

对象:

  • 1.浅拷贝
let oYm = {
      name: '幂幂',
      age: 32,
      sex: '女'
}
// 最简单的拷贝
let obj = {...oYm};

// 循环对象的写法.对象有多少个属性,就会循环多少次.
// prop就是每次循环到的属性名.
// 语法 => for(let 属性名 in 对象名)
for (let prop in oYm) {
      obj[prop] = oYm[prop];
    }
  • 2.深拷贝
    let oYm = {
      name: '幂幂',
      exhusband: {
        name: '凯威'
      }
    }

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

1.7 数组的缓存

数组是引用类型,因此缓存数组时,不能简单的进行引用赋值。

// 这会导致newArr和arr是同一个数组,达不到缓存的目的。
let arr = [1,2,3];
let newArr = arr;

自然可以通过以上1.6中的深拷贝方法对数组进行深拷贝缓存,但是比较麻烦。

如果数组的元素不是引用类型,还可以通过以下方法来进行数组缓存。

let arr = [1,2,3];
// 以下方法都可以进行数组缓存。
let newArr = [...arr];
let newArr = arr.concat([]);
let newArr = arr.map(item=>item);
let newArr = arr.slice(); (最佳)

对于数组的元素又是引用类型的情形,请参考1.6中的深拷贝方法。

1.8 修改对象和数组

  1. 修改对象:

    修改纯对象:
    对象.属性名 = xxxxxxx
    对象[属性名] = xxxxxxx

  2. 修改数组:
    数组[下标] = xxxxx;
    数组.属性 = xxxxx;
    push,pop,shift,unshift,splice,reverse,sort

  3. 修改引用(变量重新赋新值):
    对象 = 新的值.
    数组 = [];

三 JSON对象

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

json字符串有什么格式规定吗?
1:第一个字符串只能是{或者[.最后一个字符,只能是}或者].
2:属性名必须都是双引号.属性值如果是字符串也必须是双引号。
3:最后一个属性不能加,
4:不能有任何的注释.
5:参数只能是json字符串.
    JSON.parse() => 把json字符串转换为json对象
    JSON.stringify() => 把json对象转换为json字符串

四 递归有用题

        let obj = {
            num:1,
            a:{
                num:2,
                a:{
                    num:3,
                    a:{
                        num:4,
                        a:{
                            num:5
                        }
                    }
                }
            }
        }

        // 封装fn,实现将obj中所有的num都相加起来.
        // 思路:把num属性相加,如果有a属性,就递归相加a的num属性。

        function fn (o) {
            if (o.a !== undefined) {
                return o.num + fn(o.a)
            } else {
                return o.num
            }
        }

        let num = fn(obj);
        console.log(num); //15
    const obj = {
      arr: [1, 2, 3],
      obj: {
        arr: [4, 5, 6],
        obj: {
          arr: [7, 8, 9]
        }
      }
    }

    // 利用递归,将obj内所有的arr拼接起来合成一个数组.

    function fn(obj) {
      if (obj.obj !== undefined) {
        return obj.arr.concat(fn(obj.obj))
      } else {
        return obj.arr
      }
    }

    const arr = fn(obj);
    console.log(arr); // [1,2,3,4,5,6,7,8,9];

eval

eval => 把字符串当成js代码来运行
eval返回的是它的参数的返回值

eval可以转换不符合json格式的字符串为对象
let obj = eval('({name: "幂幂"})');

eval总数会把字符串当前js代码来运行,因此它不安全.
如果这个字符串内包含了恶意代码,eval也会执行这些恶意代码。(xss攻击,脚本注入攻击)
因此必须保证eval的内容是可信的.
eval的参数不应该由用户输入提供.
eval(oText.value); // (不可取的).

posted @ 2021-07-19 16:59  暗鸦08  阅读(223)  评论(0)    收藏  举报