js 进阶

内存泄漏的场景 ?

    1. 全局变量引起
    1. 闭包引起
    1. DOM删除而事件未清除
    1. 没关掉的计时器

GC (Garbage Collection, 垃圾回收)机制

  • 引用类型是在没有引用之后, 通过 v8 的 GC 自动回收,
  • 值类型如果是处于闭包的情况下, 要等闭包没有引用才会被 GC 回收, 非闭包的情况下等待 v8 的新生代 (new space) 切换的时候回收 1 2

防止内存泄漏

  • ESLint 检测代码检查非期望的全局变量。
  • 使用闭包的时候,避免写出复杂的闭包。
  • 绑定事件的时候,一定得在恰当的时候清除事件。

实现一个深拷贝 ?

const newObj = JSON.parse(JSON.stringify(oldObj));
1

缺点:

  • 1.无法实现对函数 、RegExp等特殊对象的克隆
  • 2.会抛弃对象的constructor,所有的构造函数会指向Object
  • 3.对象有循环引用,会报错
// 简化版
function deepCopy(o) {
    if (o instanceof Array) {
        var n = [];
        for (var i = 0; i < o.length; ++i) {
            n[i] = deepCopy(o[i]);
        }
        return n;
    } else if (o instanceof Function) {
        var n = new Function("return " + o.toString())();
        return n
    } else if (o instanceof Object) {
        var n = {}
        for (var i in o) {
            n[i] = deepCopy(o[i]);
        }
        return n;
    } else {
        return o;
    }
}
//考虑到了数组、对象、函数三种引用类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

还需考虑

  • 对函数 、RegExp 、 Date 、 Map 、 Set 等特殊对象的处理
  • 循环引用

升级版 参考

号称终极版

代码

参考

防抖与节流 ?

  • 函数防抖(debounce)

    • 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
  • 函数节流(throttle)

    • 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
  • 不同:

    • 防抖是将多次执行变为最后一次执行,节流是将多次执行变为在规定时间内只执行一次.
  • 应用场景:

    • debounce
      • search搜索联想,用户在不断输入值时,用防抖来节约请求资源
      • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
    • throttle
      • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
      • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

防抖

/* 
immediate:

非立即执行:,如果你在一个事件触发的n秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行

立即执行:我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行
*/
function debounce(fn, wait, immediate) {
    let timer;
    let debounced = function () {
        let context = this;
        let args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        let callNow = !timer;
        if (immediate) {
            // 已经执行过,不再执行  切换callNow状态
            timer = setTimeout(function () {
                timer = null;
            }, wait);
            if (callNow) {
                fn.apply(context, args);
            }
        } else {
            timer = setTimeout(function () {
                fn.apply(context, args);
            }, wait);
        }
    };

    // 取消方法
    debounced.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    }

    return debounced;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

节流

// 立即执行版:
function throttle(fn, wait) {
    let context,
        args;
    let previous = 0;  // 第一次 now - previous > wait 肯定true,立即执行
    return function() {
        let now = + new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            fn.apply(context, args);
            previous = now;
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 非立即执行版
function throttle(fn, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;  
        // timeout存在,说明时间还不够,返回
        if (timeout) { return false;  }
        timeout = setTimeout(function () { 
            fn.apply(context, args);
            // 切换timeout状态
            timeout = null;
        }, wait);
    };   
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

参考 1 2

toString 和 valueOf 区别

这两个方法主要用于对象的隐式转换。

当这两个方法同时存在时候,会** 先调用 valueOf ** ,若返回的不是原始类型,那么会调用 toString 方法,如果这时候 toString 方法返回的也不是原始数据类型,那么就会报错。

用 String 的拆箱转换会优先调用 toString。

let o = {
    toString: () => {
        return 'my is o,'
    },
    valueOf: () => {
        return 99
    }
}
console.log(o + 'abc') // 99abc
console.log(o * 10) // 990
1
2
3
4
5
6
7
8
9
10
let o = {
    // 返回引用类型
    toString: () => {
        console.log('into toString')
        return { 'string': 'ssss' }
    },
    valueOf: () => {
        console.log('into valueOf')
        return { 'val': 99 }
    }
}

console.log(o + 'xx')
//into valueOf
//into toString
//TypeError

String(o)
//into toString
//into valueOf
//TypeError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

let o = {
    [Symbol.toPrimitive]: () => {console.log("toPrimitive"); return "hello"}
};

o + "";
// toPrimitive 
// hello
1
2
3
4
5
6
7

1

私有变量实现

  • 约定
  • 闭包
  • WeakMap
  • Symbol
  • Proxy 1 2

ECMAScript中函数参数的按值方式。

参数如果是基本类型是按值传递,如果是引用类型按共享传递。

1

最近更新: 7/25/2022, 10:56:28 PM