目录
递归
递归函数
递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。
// 递归的函数存在堆栈中先进后出
function fn(n) {
console.log(n);
n -= 2
if (n > 0) {
fn(n)
}
console.log(n);
}
fn(10)//
详细图解如下:
递归求斐波那契数列的第n个数
// 🏆斐波那契额数列
/*已知第一项和第二项的数字是第一项 第二项 第三项 第四项第五项 第6项
1 1 2 3 5 8 13
我们想要知道 数列的第6项的值是第4项和第五项的和 第4项 是第二项和第3项数字的和
*/
function fn(n) {
if (n === 1 || n === 2) {
return 1
} else {
// return n的前两项相加
return fn(n - 1) + fn(n - 2)
}
}
console.log(fn(7));//13
fn(5)的详细图解如下:
递归实现阶乘
// 🏆递归实现阶乘
// function factorial(x) {
// if (x === 1) return 1
// return x * factorial(x - 1)
// }
// console.log(factorial(3));
递归实现数组扁平化
//使用递归来实现数组扁平化
//扁平化:将多维数组转换为一维数组
const arr = [1, 2, 3, [4, 5, [6], 7,[8,[9,[10]]]]]
const newArr = []
function flat(data) {
data.forEach(item => {
// 判断 该元素是不是 数组
if (item instanceof Array) {
// 如果是数组 继续递归
flat(item)
} else {
// 如果不是数组 则把这个元素放入新的数组中
newArr.push(item)
}
})
}
flat(arr)
console.log(newArr);
深浅拷贝
浅拷贝
// 🏆浅拷贝:拷贝的是地址
// 1.只拷贝第一层,,如果是普通类型变量则拷贝值,引用类型变量拷贝内存地址
// 常见方法:
// 1.拷贝对象:Object.assgin() / 展开运算符 {...obj } 拷贝对象
// 2.拷贝数组:Array.prototype.concat() 或者[...arr]
const obj1 = {
uname: '张三',
age: 18,
gender: "男",
gfs: ['凤姐', "芙蓉姐姐", '黄蓉'],
wife: {
w1: '蔡徐坤',
w2: 'ikun'
}
}
// 浅拷贝 只拷贝对象的 第一层 如果第一层 有引用类型 拷贝的内存地址 如果是简单类型 拷贝的值
// 💎1.使用展开运算符拷贝
// const obj2 = { ...obj1 };
// 💎2.使用Object.assign()拷贝
const obj2={}
Object.assign(obj2,obj1)
obj2.uname = '王雄厚'
obj2['wife']['w1'] = '迪丽热巴'
详细图解如下:
深拷贝
// 深拷贝:拷贝的是对象,不是地址
// 常见方法:
// 1. 通过递归实现深拷贝
// 2. lodash/cloneDeep
// 3. 通过JSON.stringify()实现
// 1. 通过递归实现深拷贝
// 函数递归:
// 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
// 简单理解:函数内部自己调用自己, 这个函数就是递归函数
// 递归函数的作用和循环效果类似
// 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
const obj1 = {
uname: '张三',
age: 18,
gender: "男",
gfs: ['凤姐', "芙蓉姐姐", '黄蓉'],
wife: {
w1: '蔡徐坤',
w2: 'ikun'
}
}
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
const item = oldObj[k]
// 判断是不是数组
if (item instanceof Array) {
newObj[k] = []
// 递归
deepCopy(newObj[k], item)
} else if (item instanceof Object) {
newObj[k] = {}
// 递归
deepCopy(newObj[k], item)
}
else {
// 🏆对象名[新属性名] = 新值
newObj[k] = item
}
}
}
obj2 = {}
deepCopy(obj2, obj1)
obj2.uname = '王雄厚'
obj2['wife']['w1'] = '迪丽热巴'
// 2. js库lodash里面cloneDeep内部实现了深拷贝
<script src="./js/lodash.min.js"></script>
<script>
const obj1 = {
uname: '张三',
age: 18,
gender: "男",
gfs: ['凤姐', "芙蓉姐姐", '黄蓉'],
wife: {
w1: '蔡徐坤',
w2: 'ikun'
}
}
const obj2 = _.cloneDeep(obj1)
console.log(obj2);
// 3. 通过JSON.stringify()实现深拷贝
const obj2 = JSON.parse(JSON.stringify(obj1))
处理this指向
this指向
// 🏆普通函数的this指向
// 普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
// 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined
//🏆 this指向-箭头函数
// 目标: 能说出箭头函数的this指向
// 箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !
// 1. 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
// 2.箭头函数中的this引用的就是最近作用域中的this
// 3.向外层作用域中,一层一层查找this,直到有this的定义
// 注意情况1: 在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window
// 因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
// 注意情况2:
// 同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
// 总结:
// 1. 函数内不存在this,沿用上一级的
// 2.不适用
// 构造函数,原型函数,dom事件函数等等
// 3. 适用
// 需要使用上层this的地方
// 4. 使用正确的话,它会在很多地方带来方便,
改变this指向
// JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
// call()
// apply()
// bind()
// 🏆1. call() 了解
// 使用 call 方法调用函数,同时指定被调用函数中 this 的值 语法:
// fun.call(thisArg, arg1, arg2, ...)
// thisArg:在 fun 函数运行时指定的 this 值 arg1,arg2:传递的其他参数
// 返回值就是函数的返回值,因为它就是调用函数
// 🏆2. apply()-理解
// 使用 apply 方法调用函数,同时指定被调用函数中 this 的值 语法:
// fun.apply(thisArg, [argsArray])
// thisArg:在fun函数运行时指定的 this 值 argsArray:传递的值,必须包含在数组里面
// 返回值就是函数的返回值,因为它就是调用函数
// function f(x, y) {
// console.log(this, x, y);
// }
// const obj = {
// uname: 'zs'
// }
// f.apply(obj, [20, 34])
// 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
//求数组最大值
// const arr = [3, 5, 2, 9]
// console.log(Math.max.apply(null, arr))//9 利用apply
// console.log(Math.max(...arr))//9利用展开运算符
// call和apply的区别
// 都是调用函数,都能改变this指向
// 参数不一样,apply传递的必须是数组
//🏆 3. bind()-重点
// bind() 方法不会调用函数。但是能改变函数内部this 指向
// 语法:
// fun.bind(thisArg, arg1, arg2, ...)
// thisArg:在 fun 函数运行时指定的 this 值 arg1,arg2:传递的其他参数
// 返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
// 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向
function f(x, y, z) {
console.log(this, x, y, z);
}
const obj = {
uname: 'zs',
age: 18
}
const ff = f.bind(obj, 10, 20, 30)//返回一个新的函数
ff()
// 💎相同点:
// 都可以改变函数内部的this指向.
// 💎 区别点:
// 区别:
// 1.bind不能调用函数可以返回一个新的函数cal1、apply可以调用函数
// 2.bind第一个参数是用于改变函数里面的this指向,其他的参数可以是参数列表
// 3.cal1第一个参数是用于改变函数里面的this指向,其他的参数可以是参数列表
// 4.apply只有2个参数第一个参数是用于改变函数里面的this指向第二个参数是数组
// 主要应用场景:
// call 调用函数并且可以传递参数
// apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
// bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
异常处理
// 异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
// 总结:
// 1. throw 抛出异常信息,程序也会终止执行
// 2. throw 后面跟的是错误提示信息
// 3. Error 对象配合 throw 使用,能够设置更详细的错误信息
1.throw 抛异常
function fn(x, y) {
if (!x || !y) {
// throw 阻止代码往下执行
// throw‘参数不能为空!‘;
throw new Error('参数不能为空! 人才')
}
return x + y
}
fn()
2.try..catch 捕获异常
// 我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后
// 总结:
// 1. try...catch 用于捕获错误信息
// 2. 将预估可能发生错误的代码写在 try 代码段中
// 3. 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
// 4. finally 不管是否有错误,都会执行
function foo() {
try {
//查找 DOM 节点
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
//try代码段中执行有错误时,会执行 catch 代码段
//查看错误信息
console.log(error.message)
//终止代码继续执行
return
}
finally {
// 不管成功失败,代码都会执行到这里
alert('执行')
}
console.log("‘如果出现错误,我的语句不会执行’")
}
foo()
3.debugger
debugger 语句调用任何可用的调试功能,例如设置断点。 如果没有调试功能可用,则此语句不起作用。
当 debugger 被调用时,执行暂停在 debugger 语句的位置。就像在脚本源代码中的断点一样。