js 进阶
内存泄漏的场景 ?
- 全局变量引起
- 闭包引起
- DOM删除而事件未清除
- 没关掉的计时器
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
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来判断
- debounce
防抖
/*
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
2
3
4
5
6
7
私有变量实现
ECMAScript中函数参数的按值方式。
参数如果是基本类型是按值传递,如果是引用类型按共享传递。