目录
递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。
// 递归的函数存在堆栈中先进后出
- function fn(n) {
- console.log(n);
- n -= 2
- if (n > 0) {
- fn(n)
- }
- console.log(n);
- }
- fn(10)//
详细图解如下:
// 🏆斐波那契额数列
/*已知第一项和第二项的数字是第一项 第二项 第三项 第四项第五项 第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 值为 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. 使用正确的话,它会在很多地方带来方便,
// 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 使用,能够设置更详细的错误信息
- function fn(x, y) {
- if (!x || !y) {
- // throw 阻止代码往下执行
- // throw‘参数不能为空!‘;
- throw new Error('参数不能为空! 人才')
- }
- return x + y
- }
- fn()
// 我们可以通过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()
debugger 语句调用任何可用的调试功能,例如设置断点。 如果没有调试功能可用,则此语句不起作用。
当 debugger 被调用时,执行暂停在 debugger 语句的位置。就像在脚本源代码中的断点一样。