个人自学前端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 修改对象和数组
-
修改对象:
修改纯对象:
对象.属性名 = xxxxxxx
对象[属性名] = xxxxxxx -
修改数组:
数组[下标] = xxxxx;
数组.属性 = xxxxx;
push,pop,shift,unshift,splice,reverse,sort -
修改引用(变量重新赋新值):
对象 = 新的值.
数组 = [];
三 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); // (不可取的).
本文来自博客园,作者:暗鸦08,转载请注明原文链接:https://www.cnblogs.com/DarkCrow/p/15031020.html

浙公网安备 33010602011771号