03-JS高级[递归、快排、拷贝]

执行上下文【代码的执行环境】

  1. 全局执行上下文

  2. 函数执行上下文

  3. eval执行上下文[很少使用,了解即可]

    var i="3*3+5"
    document.write(eval(i))//14

执行上下文调用栈【代码的执行流程】【先进后出】

  1. 全局上下文入栈

  2. 函数1上下文入栈

  3. 函数2上下文入栈

  4. 函数N上下文入栈

  5. 函数N上下文出栈

  6. 函数2上下文出栈

  7. 函数1上下文出栈

  8. 全局上下文出栈

递归

含义:

  • 函数内部调用自身的过程

特点:

  1. 函数调用函数,执行传递的动作

  2. 确定最后一次的终止条件,执行回归的动作

1、递归面试题1[用递归求阶乘]

function jie(num){
    //确认边界,回归
    if(num==1){return 1};
    //传递
    var j=jie(num-1)
    return n*j;//jie(num-1)*j
​
}

剖析图

2、递归面试题二【快速排序】

思路分析:

  1. 在数据集之中,选择一个元素作为”基准”

  2. 所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。

  3. 对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 案例模拟: 4,7,3,20,11,2 基准:[3,2],4,[7,20,11] [3,2] 基准:2,[3] [3] [7,20,11] 基准:7,[20,11] [20,11] 基准:11,20 代码实现:

    let arr=[4,7,3,20,11,2];
    function quickSort(list){
    if(list.length==0){
        return list;
    }
    //1.找基准值,就是第一个值
    let basic = list[0];
    let left_arr=[];
    let right_arr=[];
    ​
    if(list.length==1){
        return basic;
    }
    ​
    //2.遍历数组,从基准值的下一个开始
    for(let i=1;i<list.length;i++){
        if(basic<list[i]){
            //3.放右边
            right_arr.push(list[i])
        }
        if(basic>list[i]){
            //4.放左边
            left_arr.push(list[i])
        }
    }
    return [].concat(quickSort(left_arr),basic,quickSort(right_arr));
    }
    console.log(quickSort(arr));

(3)、补充:二分查找法(查找数组指定数字的下标)

前提:有序数组

思路:

  • 利用循环

  • 计算出中间值,然后和要查找的数字比较是在中间值的左侧还是左侧

  • 缩短查找范围,再重新计算中间值,继续判断

  • 直到要查到的数字就是中间值为止

代码

<script>
        //利用二分法,查找指定数字在数组中的下标
        function binarySearch(arr, num) {
            //1、找出中间值
            var start = 0;
            var stop = arr.length - 1;
            var middle = parseInt((start + stop) / 2);
            //2、比较中间值和要查找的数字
            while (arr[middle] != num && stop > start){
                if (arr[middle] > num) {
                    //说明我要找的值在左边
                    stop = middle - 1;
                } else {
                    //说明我要找的值在右边
                    start = middle + 1;
                }
                //3、每次循环都要重新计算中间值
                middle = parseInt((stop + start) / 2)
            }
​
            //如果搜不到这个值的话,返回-1.否则返回索引号(下标)
            return arr[middle] != num ? -1 : middle;
        }
        var m = binarySearch([1, 2, 3, 4, 5, 6], 4)
        console.log(m);
 </script>

对象浅拷贝

概念:复制出来的对象做修改时对原对象造成了影响 写法:

  1. 引用赋值:var 对象B=对象A;

  2. 扩展运算符:var 对象B={…对象A}

    • 展开符只能展开一层,所以里面的复杂数据类型还是浅拷贝

  3. Assign :Object.assign(目标对象,源对象);

    • 里面的复杂数据类形是浅拷贝

  • 复制了引用地址而已[浅拷贝]

<script>
        //浅拷贝:复制出来的对象做修改时对原对象造成了影响
        var a ={
            name:'jack',age:30,work:['写代码','开滴滴','跑摩的']
        }
        var b = a; //复制了引用的地址而已【浅拷贝】
        b.name='rose';
        console.log(a.name);//rose
​
        // 虽然c是新创建的一个对象,c不等于a,
        //但是:展开符只能展开一层,但是a里面还有一个对象,就是work数组
        var c = {...a};
        c.name='peter'
        //c和a是不同的两个对象,但是c和a他们俩共用同一个work
        c.work[0]='项目经理'
        console.log(a.work);//['项目经理','开滴滴','跑摩的']
​
        var x ={};
        var y={code:'XA003',list:[1,2,3]}
        //将y的全部内容复制到x里【浅拷贝】
        var result = Object.assign(x,y);
        console.log(x);
        x.list.push(4,5,6);
        console.log(y);
    </script>

对象深拷贝

概念:复制出来的对象和源对象无任何关系,不会再互相影响,在堆内存中独立存储 写法: 方式一: JSON.stringify(源对象)、JSON.parse(源对象JSON字符串) 缺点:undefined的值、函数无法拷贝

<script>
        var phone={
            brand:'MI',
            title:'K30S',
            price:2999,
            date:null,
            power:{
                GPS:'查看定位信息',
                call(){
                    alert('打电话')
                },
                play(){
                    alert('玩游戏')
                }
            }
        }
​
        var phone2 = {};
​
        //深拷贝(函数无法拷贝、undefined的值无法拷贝)
        var str = JSON.stringify(phone);
        phone2 = JSON.parse(str)
        phone2.title='MIX'
        phone2.power.GPS='北斗定位';
        console.log(phone2);
    </script>

方式二: 递归深拷贝

 
function deepCopy(target,source){
        for(let i in source){
            if(typeof source[i]=='object'){
                let p=null;
                if(source[i].length){
                    //数组
                    p=[];
                }else{
                    //对象
                    p={};
                }
                 //1、拷贝方法,对象
                target[i] = deepCopy(p,source[i])
            }else{
                //2、直接拷贝属性
                target[i]=source[i];
            }
        }
        return target;
    }
<script>
        //深拷贝的终极方案(递归深拷贝)
        function deepCopy(target,source){
            for(let i in source){
                if(typeof source[i]=='object'){
                    let p=null;
                    if(source[i].length){
                        //数组
                        p=[];
                    }else{
                        //对象
                        p={};
                    }
                    target[i] = deepCopy(p,source[i])
                }else{
                    target[i]=source[i];
                }
            }
            return target;
        }
        var phone={
            brand:'MI',
            title:'K30S',
            price:2999,
            date:undefined,
            memory:['8','256'],
            power:{
                GPS:'查看定位信息',
                call(){
                    alert('打电话')
                },
                play(){
                    alert('玩游戏')
                }
            }
        }
        
        var phone2={};
        
        deepCopy(phone2,phone);
​
        phone2.memory.push(1024);
​
        console.log(phone);
   </script>

性能优化

一、函数防抖(debounce)

思路:

  • 在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms

  • 如果在200ms内没有再次滚动事件触发,那么就执行函数

  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:

  • 如果短时间内大量触发同一件事,只会执行一次函数

实现:

  • 借助定时器完成

<body>
    <div>
        …………
    </div>
     
    <script>
        //滚动时,调用的其实是debound里面返回的匿名函数
        window.onscroll = debounce();

        function debounce() {
            //每次滚动的时候,不断的清楚上一次还未执行的定时器,然后再启动新的定时器
            var timer = null;
            //高频次的滚动,不断的清楚定时器,减少函数的执行次数
            return function() {
                clearTimeout(timer);
                timer = setTimeout(function() {
                    var top = document.body.scrollTop || document.documentElement.scrollTop;
                    console.log(top);
                }, 100)
            }
        }
    </script>


</body>

二、函数节流(throttle)

问题:

  • 希望即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈

思路:

  • 连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率,类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活

<script>
        //节流
        //滚动时,调用的其实是throttle里面返回的匿名函数
        window.onscroll = throttle(500)

        function throttle(delay) {
            var start = 0; //初始化第一次执行的时间点
            return function() {
                //每次滚动时调用内部函数
                var now = Date.now();
                //用当前时间-上一次执行的时间
                var sub = now - start;
                //计算是否超过了500毫秒
                if (sub > delay) {
                    //如果超过了500毫秒,去执行一次代码
                    var top = document.body.scrollTop || document.documentElement.scrollTop;
                    console.log(top);
                    start = now
                }
            }
        }
    </script>

键盘输入事件,时隔多少秒获取一次

<input type="text">

<script>
        var input = document.querySelector('input');

        // 键盘输入节流
        input.onkeyup = delay(500);

        function delay(time) {
            var start = 0;
            return function() {
                var now = Date.now()
                var sub = now - start;
                if (sub > time) {
                    console.log(input.value);
                    start = now
                }
            }
        }

        //键盘输入防抖
        input.onkeyup = play()

        function play() {
            var timer = null;
            return function() {
                clearTimeout(timer)
                var timer = setTimeout(function() {
                    console.log(input.value);
                }, 200)
            }
        }
    </script>

防抖和节流的区别

防抖

  • 事件结束后多长时间后才会执行

  • 如果一直在滚动,就一直不会执行,只有滚动结束后,停止滚动间隔一段时间后才会执行

节流

  • 隔一段时间就执行一次

  • 滚动每间隔多长时间就执行一次

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值