JavaScript 高阶技巧

发布于:2023-01-09 ⋅ 阅读:(394) ⋅ 点赞:(0)

一、深浅拷贝

  1. 首先浅拷贝和深拷贝只针对想Object,Array这样的复杂对象,简单来说,浅拷贝只复制一层对象的属性,二深拷贝则复制了所有的层级。

  2. 对于字符串类型,浅复制是对值的复制,对于对象来说,浅复制是对对象地址的复制,并没 有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会 改变,而深复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

浅拷贝

如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
    <script>
        const obj = {
            uname: '张三',
            age: 18,
            gender: '男',
            gfs: ['凤姐', '芙蓉姐姐', '黄蓉'],
            wife: {
                w1: '蔡徐坤',
                w2: 'ikun'
            }
        }
        //第一种
        const arr = { ...obj }
        //使用展开运算符
        arr.uname = '解决'
        arr['wife']['w1'] = '开辟'
        console.log(arr)
        console.log(obj)
        //第二种
        const arr = {}
        Object.assign(arr,obj)
        arr.uname = '解决'
        arr['wife']['w1'] = '开辟'
        console.log(arr)
        console.log(obj)
    </script>

 深拷贝

首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
1. 通过递归实现深拷贝
2. lodash/cloneDeep
3. 通过JSON.stringify()实现

 

    <script>
        //递归:在函数里面 调用自己 
        //在内存里面开辟大量的空间,能少写就少写
        //入栈:先进后出,后进先出
        function f(n) {
            console.log(n)
            n = n - 2
            if (n > 0) {
                f(n)
            }
            console.log(n)
        }
        f(10)
    </script>

    <script>
        //斐波拉契
        //1,1,2,3,5,8,13.....
        function f(n) {
            if (n === 1 || n === 2) {
                return 1
            } else {
                return f(n - 1) + f(n - 2)
            }
        }
        const res = f(4)
        console.log(res)

        //阶层
        //!5 = 5*4*3*2*1
        function a(n) {
            if (n === 1) {
                return 1
            } else {
                return a(n - 1) * n
            }
        }
        const to = a(4)
        console.log(to)

        //猴子吃桃子
        function b(n) {
            if (n === 0) {
                return 1
            } else {
                return (b(n-1) + 1) * 2
            } 
        }
        const num = b(10)
        console.log(num)

    </script>

用递归将数组扁平化

    <script>
        const arr = [1, 2, 3, 4, [5, [7, 8, [9, 10]]]]
        const newArr = []
        function get(a) {
            a.forEach(function (item) {
                //判断是否是数组
                if (item instanceof Array) {
                    get(item)
                } else {
                    newArr.push(item)
                }
            })
        }
        get(arr)
        console.log(newArr)
    </script>
通过递归函数实现深拷贝
    <script>
        const obj = {
            uname: '张三',
            age: 18,
            gender: '男',
            gfs: ['凤姐', '芙蓉姐姐', '黄蓉'],
            wife: {
                w1: '蔡徐坤',
                w2: 'ikun'
            }
        }
        const arr = {}
        //将就对象 里面的属性 copy到新对象里面
        function deepCopy(newObj, oldObj) {
            for (let k in oldObj) {
                //获取旧对象 里面的属性值 赋值为item变量
                const item = oldObj[k]
                //判断item是不是数组
                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
                }
            }
        }
        deepCopy(arr, obj)
        console.log(arr)
    </script>

2. js库lodash里面cloneDeep内部实现了深拷贝

    <script src="./js/lodash.min.js"></script>
    <script>
         const obj = {
            uname: '张三',
            age: 18,
            gender: '男',
            gfs: ['凤姐', '芙蓉姐姐', '黄蓉'],
            wife: {
                w1: '蔡徐坤',
                w2: 'ikun'
            }
        }
        const arr = _.cloneDeep(obj)
        arr['gfs'][1] = '订狄'
        console.log(arr)
        console.log(obj)
    </script>

3. 通过JSON.stringify()实现

    <script>
        const obj = {
            uname: '张三',
            age: 18,
            gender: '男',
            gfs: ['凤姐', '芙蓉姐姐', '黄蓉'],
            wife: {
                w1: '蔡徐坤',
                w2: 'ikun'
            }
        }
        const str = JSON.stringify(obj)
        const arr = JSON.parse(str)
        arr['gfs'][1] = '订狄'
        console.log(arr)
        console.log(obj) 
    </script>

二、异常处理

throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。

    <script>
        function f(x,y) {
            if(!x || !y) {
                throw new Error('没有传递参数')
                //阻止代码往下执行
                //Error 对象配合 throw 使用
            }
            return x + y
        }
        const ret = f()
        console.log(ret)
    </script>

总结:

  1. throw 抛出异常信息,程序也会终止执行

  2. throw 后面跟的是错误提示信息

  3. Error 对象配合 throw 使用,能够设置更详细的错误信息

try /catch 捕获异常
<script>
   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()
</script>

 

总结:

  1. try...catch 用于捕获错误信息

  2. 将预估可能发生错误的代码写在 try 代码段中

  3. 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息

debugger

 

    <script>
        const obj = {
            uname: '张三',
            age: 18,
            gender: '男',
            gfs: ['凤姐', '芙蓉姐姐', '黄蓉'],
            wife: {
                w1: '蔡徐坤',
                w2: 'ikun'
            }
        }
        const arr = {}
        function deepCopy(newObj, oldObj) {

            debugger
            //直接在这里进行断点调试

            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
                }
            }
        }
        deepCopy(arr, obj)
        console.log(arr)
    </script>

this

普通函数

普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】

注: 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined

 

<script>
  // 普通函数
  function sayHi() {
    console.log(this)  
  }
  // 函数表达式
  const sayHello = function () {
    console.log(this)
  }
  // 函数的调用方式决定了 this 的值
  sayHi() // window
  window.sayHi()
	

// 普通对象
  const user = {
    name: '小明',
    walk: function () {
      console.log(this)
    }
  }
  // 动态为 user 添加方法
  user.sayHi = sayHi
  uesr.sayHello = sayHello
  // 函数调用方式,决定了 this 的值
  user.sayHi()
  user.sayHello()
</script>

箭头函数

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。

在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window .因此DOM事件回调函数 如果里面需要 DOM对象的this,则不推荐使用箭头函数
1. 函数内不存在this,沿用上一级的,过程:向外层作用域中,一层
一层查找this,直到有this的定义
2. 不适用
构造函数,原型函数,字面量对象中函数,dom事件函数
3. 适用
需要使用上层this的地方

改变this

call

使用 call 方法调用函数,同时指定函数中 this 的值

 

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: '小明',
    age: 18
  }

  let student = {
    name: '小红',
    age: 16
  }

  // 调用函数并指定 this 的值
  sayHi.call(user); // this 值为 user
  sayHi.call(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }

  // 调用 counter 函数,并传入参数
  let result = counter.call(null, 5, 10);
  console.log(result);
</script>

总结:

  1. call 方法能够在调用函数的同时指定 this 的值

  2. 使用 call 方法调用函数时,第1个参数为 this 指定的值

  3. call 方法的其余参数会依次自动传入函数做为函数的参数

apply

使用 apply 方法调用函数,同时指定函数中 this 的值

 

   <script>
        function f(x,y) {
            console.log(this,x,y)
        }
        const obj = {
            name: '订狄'
        }
        //函数名.apply(改变函数里面的对象)
        //相同点:call()和apply() 都可以调用函数,也可以改变函数里面的this指向
        //区别:传递的参数个数不一样,call() 可以传递多个参数, apply()只有两个参数, 第一个参数是,需要改变函数里面的对像,第二个参数必须是数组
        //f.apply(obj)
        f.apply(obj,[1,2,3])

        const a = [1,2,3,4,5,6,7]
        console.log(...a)
    </script>

 最大值案例

    <script>
        const arr = [1,2,3,45,5]
        const res = Math.max.apply(Math,arr)
        //const res = Math.max(...arr)
        console.log(res)
    </script>
call和apply的区别是?
都是调用函数,都能改变this指向
参数不一样,apply传递的必须是数组

总结:

  1. apply 方法能够在调用函数的同时指定 this 的值

  2. 使用 apply 方法调用函数时,第1个参数为 this 指定的值

  3. apply 方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数

bind()
bind 方法并 不会调用函数,而是创建一个指定了 this 值的新函数

 

    <script>
        //语法: 函数名.bind(this指向,参数1,参数2,....)
        //不能调用函数 可以改变函数里面this的指向  可以返回一个新的函数

        function f(x, y, z) {
            console.log(this, x, y, z)
        }

        const obj = {
            name: 'hh',
            age: 12
        }

        const ff = f.bind(obj, 10, 20, 30)
        ff()
    //bind 、call 、apply
    //相同点:都可以改变函数里面 this 指向
    //区别:
    //1. bind不能调用函数 可以返回一个新的函数 call 、apply可以调用函数
    //2. bind 第一个参数是用于改变函数里面的 this 指向 ,其他的参数 可以是 参数列表
    //3. call 第一个参数是用于改变函数里面的 this 指向 ,其他的参数 可以是 参数列表
    //4. apply 只有2个参数 第一个参数是用于改变函数里面的 this 指向 第二个参数是数组
    </script>

注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。

 


网站公告

今日签到

点亮在社区的每一天
去签到