从性能讲起
一些读书总结
DNS
TCP
HTTP请求
节流防抖?
webpack和Gzip
- DllPlugin: 库不变更,我绝不打包!
- Happypack: 你电脑是多核的,我的loaders全都要用!
- webpack-bundle-analyzer:让我看看你项目内裤的大小
- require.ensure: 动态加载我最在行
- Gzip 后端:前端小老弟,你想更快吗?
HTTP 返回
- 服务端渲染: 服务端跑完虚拟dom后,把页面返回给浏览器,此时将浏览器的一部分工作交给服务器做,能者多劳,但在 “网民横行” 的现在,浏览器数量远大于服务器数量,服务器亚历山大,这算是服务端渲染的一个弊端。优先考虑其他seo方案,无路可走时,最后一步买服务器做服务端渲染! 「吐槽,加缓存后的node中间层压力有多大?」
css
#myList li {}
我们是从 #myList => li 开始读, 坑爹的浏览器是从 li -> #myList,它会先遍历所有 li 节点!!!
再看看它!
……
* {}
so:
- 别特娘的用 通配符了
- 最好别用元素选择器,用类名!
- css 渲染是阻塞的: 我没渲染呢,你们都等等 (css资源先加载先渲染)
js
- async defer 模式
Dom操作咋就这么慢
- JS 引擎和渲染引擎 之间的“桥连”,竟然要收tmd过桥费!,所以你以后“送货”的时候尽量一次性送完
Event Loop
- macro-task 比如: setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等
- micro-task 比如: process.nextTick、Promise、 MutationObserver 等。
macro-task 出栈是一个个出的,micro-task 是一组一组出的。
上面这句话你一定没听懂哈哈哈哈,看代码
1 | function main() { |
2 3 1 4
以上结果你肯定能根据经验主义猜出来,但你还可以清晰的理解它
1 | function main() { <- 宏任务 0 |
先把这些任务排到队列里
1 | macro<list> = [宏任务 0, 宏任务 1, 宏任务 2] |
大声告诉我以上执行结果!!!
如果你想更快的渲染dom,你会让它在宏任务还是微任务/?
开始做木易杨大神的知识整理
2019/8/31 以前做不完是小狗!! –尼古拉斯.杜
https://github.com/yygmind/blog
Javascript 的执行上下文和执行栈
这篇在半年前看过,过了这么久知识点又模糊了,惭愧呀……
执行上下文: 是代码在解析和执行时所在环境的抽象概念
它分为三种:
- 全局上下文(window/golbal[node]…)
- 函数上下文
- eval (性能问题、安全问题)
执行栈:引擎会在运行的时候,将全局上下文以及函数上下文入栈,等栈顶函数运行完毕,再依次出栈。
执行上下文创建:
- 创建阶段
- 执行阶段
创建阶段:
- this绑定
- 词法环境被创建
- 变量环境被创建
执行阶段:
- 直接执行代码
内存空间
- 堆就是字典,引用类型存在这里
- 基本类型存在栈里
1 | var a = {n: 1}; |
end
内存机制
闭包又一定义:A 函数返回了 B 函数,且 B 函数引用了 A 函数内部的变量
两种内存回收的方式:
- 引用计数 变量被引用就不清除,但如果 a 引用 b b 引用 a,就gg了,造成内存泄漏
- 标记清除 从全局对象出发,能追溯到的变量就保留,追溯不到的就标记清除
1 | // div 引用了 onclick,并且 回调里又引用了 div |
node 监测内存泄漏 console.log(process.memoryUsage());
什么姿势内存会泄漏
- 函数内部未定义的全局变量
- 未关掉的定时器或回调函数(eg:IE里的click事件)
- 闭包
一个变量被标记为 null 表示没有引用,则引擎会将它回收,而undfined 会将值置空,但变量还存在
const 的值,保证了引用地址的不可变性,所以 修改 变量的属性没问题,因为地址没变,而基本类型的地址和值是绑定的,so 不能改变
闭包
闭包特性:
- 可以访问当前函数以外的变量
- 即使外部函数已返回,还能访问外部函数定义的变量
- 可以更新外部变量的值
作用域链
这里经常会和原型链混,从字面意思来区分,作用域!就要联想到执行上下文,当访问一个变量时,解析器会从当前作用域找,找不到就去上级找,直到找到,要是找到了全局(window)还没找到,那就要报错了Uncaught ReferenceError: a is not defined
分析一段闭包代码
首先得搞清楚几个知识点
变量对象
变量对象是一个抽象的概念,在执行上下文 创建阶段,储存着在上下文定义的以下内容:1,函数的形参 2,函数声明 3,变量声明
活动对象
与变量对象对应,当处于 执行阶段 时,变量对象会转为活动对象(全局变量除外)
自己分析去吧
1 | // 创建全局上下文 |
this 绑定
- apply call bind
- class内部用 =
- new Function
- 箭头函数
那,手写一个new函数试试,什么?你不会写?复习去吧~
再写一个硬绑定,函数无论在哪里调用,或者被谁调用,始终绑定初始化对象
浅拷贝和深拷贝
看到这个标题的时候还算淡然,去年毕业的时候不知写过多少次深拷贝,现在写一个应该更简单。
浅拷贝
浅拷贝就只拷贝外面的一层,典型的方法像 Object.assign
或 loadsh.extend
或 ...
扩展符
1 | function assign(obj) { |
浅拷贝随随便便就撸一个1
2
3
4var obj = {a:2,b: {c:3}}
var obj2 = assign(obj)
obj.a = 4
obj.b.c = 3
可以看到,obj 和 obj2 共享 第二层的属性 c,而a作为基本类型,两个对象不共享。
深拷贝
深拷贝则是拷贝所有属性。再撸一个深拷贝?1
2
3
4
5
6
7
8
9
10
11
12
13
14/*
{a: 2, b: { c: 3 } }
*/
function deepClone(obj, clone = {}) {
for( let key in obj) {
// 单独对 null 做了判断,因为 typeof null === 'object'
if(typeof obj[key] !== 'object' || obj[key] === null) {
clone[key] = obj[key]
} else {
clone[key] = deepClone(obj[key])
}
}
return clone
}
看来在dxy工作了一年确实有长进,之前写这种函数要“脑梗”很久,现在花了五六分钟就写好了,还一次跑通。1
2
3
4
5
6var obj = {a:2,c:{b:3}}
var obj1 = deepClone(obj)
var obj2 = deepClone(obj)
obj1.c.b = 444
// obj1.c.b: 444
// obj2.c.b: 3
没问题,所有属性都不共享了。
这就是tmd 深拷贝,当然项目中经常看到同事省时间这么写: JSON.parse(JSON.stringify(obj))
大多数情况下,客户端代码这么写真心可以,因为数据量小,感受不到性能的差距。在数据量大的情况下,还是寻找一些
开源库去做深拷贝,像:lodash/immutable/jQuery.extend。
我的观点正确吗
以上两节是我“想当然”的写出的,当我看了木易杨的博客之后,发现有很多错误点。分为两点展开。
- deepClone 完美吗?
JSON.parse(JSON.stringify(obj))
真的可以用在项目里做深拷贝吗?JSON 方法
用JSON.parse(JSON.stringify(obj))
有什么缺点?
首先我们分析它是怎么实现深拷贝的。
对象 -> json字符串 -> 对象
既然是 json 字符串,那肯定有它的标准。一些特殊的对象不能在它里面正确的出现。
比如:Symbol Function undefined Date
可以试试1
2
3
4
5
6
7
8var obj = {
sym: Symbol('duhao'),
date: new Date(),
b: undefined,
fn: () => {}
}
var obj2 = JSON.parse(JSON.stringify(obj))
// { date: "2019-08-19T15:57:06.148Z" }
只留下了 date 类型,并且格式和 new Date()
不同,貌似被转换成了 ISO 标准。
所以当你的数据结构 当前或者之后 可能有以上类型时,不推荐使用该方法。
还有一个特殊的表达式:正则表达式。1
2
3var obj = {c: /fsf/}
JSON.stringify(obj)
// "{"c":{}}"
可以看到,JSON直接丢掉了我们的正则表达式,取而代之的是一个空对象。
json 结构还有一个缺点,是不能表示循环引用,如果一个对象内部存在循环引用,JSON 会怎么做呢?1
2
3
4
5var a = {}
var b = {}
a.b = a
JSON.stringify(a)
// 直接报错 Converting circular structure to JSON 提示循环引用。
既然有这么多雷区,以后还是少用这个方法为妙,说不准哪天就踩上了。
自己写的 deepClone 有什么问题
让我再把这个方法贴过来。1
2
3
4
5
6
7
8
9
10
11
12
13
14/*
{a: 2, b: { c: 3 } }
*/
function deepClone(obj, clone = {}) {
for( let key in obj) {
// 单独对 null 做了判断,因为 typeof null === 'object'
if(typeof obj[key] !== 'object' || obj[key] === null) {
clone[key] = obj[key]
} else {
clone[key] = deepClone(obj[key])
}
}
return clone
}
我还很“聪明”的对 null 做了一个判断,那用这个方法,再对比下 JSON
做深拷贝,看看能不能通过用例。
1 | var obj = { |
可以看到,deepClone
比 json 好了很多,但 Date 类型还是有问题。
再试试循环引用:1
2
3
4
5var a = {}
var b = {}
a.b = a
deepClone(a)
// Maximum call stack size exceeded 直接爆栈!因为我们的递归函数一直运行下去了。
正则呢?1
2
3deepClone({a: /sfsf/})
// 和JSON表现一致
// {a: {}}
总结一下,存在以下几个问题:
- Array 对象
- 函数传参 null
- 正则表达式
- 循环引用
- 数据格式过复杂还是会爆栈(因为是递归)
- 引用丢失问题
- Symbol 为key 值如何拷贝?
我们一个个解决不就可以了。完美的
deepClone
兼容
Array
和null
1 | /* |
经过以上代码,我们还有两个问题未解决
- 数据格式过复杂还是会爆栈(因为是递归)
- Symbol 对象作为key值
Reflect
这个新方法可以用来得到 对象的所有属性,包括 Symbol 对象1
2
3
4
5let symbol = Symbol('duhao')
let obj = {'name': 'duhao'}
obj[symbol] = 2
Reflect.ownKeys(obj)
// [Symbol(duhao), 'name']
可以看到,这个方法既可以遍历普通key,也可以遍历Symbol key,我们可以用它来替代 for in,实现我们的深拷贝。
1 | /* |
爆栈问题
最终这个问题是比较难遇到的,如果需要拷贝的对象达到了浏览器栈的最大长度,那递归函数就会爆栈。
我们可以通过宏任务 setTimeout 或者 微任务 Promise 来包装上文封装的函数。
也可以将递归改为循环,因为这个情况比较难遇到,就不在此研究了。
???
sfd