从性能讲起

一些读书总结

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 是一组一组出的。
上面这句话你一定没听懂哈哈哈哈,看代码

function main() {
  setTimeout(() => {
    console.log(1)
  }, 0)
  new Promise((resolve) => {
    resolve(2)
  }).then(ev => console.log(ev))
  setTimeout(() => {
    console.log(4)
  }, 0)
  new Promise((resolve) => {
    resolve(3)
  }).then(ev => console.log(ev))
}

2 3 1 4
以上结果你肯定能根据经验主义猜出来,但你还可以清晰的理解它

function main() { <- 宏任务 0
  setTimeout(() => { <- 宏任务 1
    console.log(1)
  }, 0)
  new Promise((resolve) => { <- 微任务 1
    resolve(2)
  }).then(ev => console.log(ev))
  setTimeout(() => { <- 宏任务 2
    console.log(4)
  }, 0)
  new Promise((resolve) => { 微任务 2
    resolve(3)
  }).then(ev => console.log(ev))
}

先把这些任务排到队列里

macro<list> = [宏任务 0, 宏任务 1, 宏任务 2]
micro<list> = [微任务 1, 微任务 2]

再读这句话: macro-task 出队列是一个个出的,micro-task 是一组一组出的。
宏任务 0 -> 微任务1 -> 微任务 2 -> 宏任务 1 -> 宏任务 2

别急,再练习一下

function main() {
  setTimeout(() => {
    console.log(1)
  }, 0)
  new Promise((resolve) => {
    resolve(2)
  }).then(ev => console.log(ev))
  setTimeout(() => {
    console.log(4)
  }, 0)
  new Promise((resolve) => {
    resolve(3)
  }).then(ev => console.log(ev))

  setTimeout(() => {
    new Promise((resolve) => {
      resolve(3)
    }).then(ev => console.log(ev))
    setTimeout(() => {
      console.log(6)
    }, 0)
    new Promise((resolve) => {
      resolve(4)
    }).then(ev => console.log(ev))
    setTimeout(() => {
      console.log(5)
    }, 0)
  }, 3000)
}

大声告诉我以上执行结果!!!
如果你想更快的渲染dom,你会让它在宏任务还是微任务/?


开始做木易杨大神的知识整理

2019/8/31 以前做不完是小狗!! –尼古拉斯.杜
https://github.com/yygmind/blog

Javascript 的执行上下文和执行栈

这篇在半年前看过,过了这么久知识点又模糊了,惭愧呀……


执行上下文: 是代码在解析和执行时所在环境的抽象概念
它分为三种:

  • 全局上下文(window/golbal[node]…)
  • 函数上下文
  • eval (性能问题、安全问题)

执行栈:引擎会在运行的时候,将全局上下文以及函数上下文入栈,等栈顶函数运行完毕,再依次出栈。


执行上下文创建:

  • 创建阶段
  • 执行阶段

创建阶段:

  • this绑定
  • 词法环境被创建
  • 变量环境被创建

执行阶段:

  • 直接执行代码

内存空间

  • 堆就是字典,引用类型存在这里
  • 基本类型存在栈里
var a = {n: 1};
var b = a;
a.x = a = {n: 2};

a.x     // 这时 a.x 的值是多少
b.x     // 这时 b.x 的值是多少
// . 操作符优先级大于 =操作符

end


内存机制

闭包又一定义:A 函数返回了 B 函数,且 B 函数引用了 A 函数内部的变量

两种内存回收的方式:

  • 引用计数 变量被引用就不清除,但如果 a 引用 b b 引用 a,就gg了,造成内存泄漏
  • 标记清除 从全局对象出发,能追溯到的变量就保留,追溯不到的就标记清除
// div 引用了 onclick,并且 回调里又引用了 div
var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
    console.log(div)
};

node 监测内存泄漏 console.log(process.memoryUsage());

什么姿势内存会泄漏

  • 函数内部未定义的全局变量
  • 未关掉的定时器或回调函数(eg:IE里的click事件)
  • 闭包

一个变量被标记为 null 表示没有引用,则引擎会将它回收,而undfined 会将值置空,但变量还存在
const 的值,保证了引用地址的不可变性,所以 修改 变量的属性没问题,因为地址没变,而基本类型的地址和值是绑定的,so 不能改变


闭包

闭包特性:

  • 可以访问当前函数以外的变量
  • 即使外部函数已返回,还能访问外部函数定义的变量
  • 可以更新外部变量的值

作用域链

这里经常会和原型链混,从字面意思来区分,作用域!就要联想到执行上下文,当访问一个变量时,解析器会从当前作用域找,找不到就去上级找,直到找到,要是找到了全局(window)还没找到,那就要报错了
Uncaught ReferenceError: a is not defined

分析一段闭包代码

首先得搞清楚几个知识点

变量对象

变量对象是一个抽象的概念,在执行上下文 创建阶段,储存着在上下文定义的以下内容:1,函数的形参 2,函数声明 3,变量声明

活动对象

与变量对象对应,当处于 执行阶段 时,变量对象会转为活动对象(全局变量除外)

自己分析去吧

// 创建全局上下文
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope(); // foo指向函数f
foo();        

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

checkscope()();  

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope(); 
foo();                    

this 绑定

  • apply call bind
  • class内部用 =
  • new Function
  • 箭头函数
    那,手写一个new函数试试,什么?你不会写?复习去吧~
    再写一个硬绑定,函数无论在哪里调用,或者被谁调用,始终绑定初始化对象

    原型链

    prototype 是啥玩意儿

    每个函数,都有一个prototype

    __proto__ 又是啥玩意儿

    很多人看到 __proto__ 就会想到下面这张图,有的时候,知识抽象成图表反而不太明了,必须得知道对应关系,再结合图表来看,就会倍感清晰。
    跟着我看完,如果还不清晰,你来砍我???

    神图

    不要一上来就给别人一张图
    img

    规律

    首先你需要打开谷歌浏览器的控制台,依次执行以下代码,看看其返回值,总结出一些规律。
    function fn() {} 
    var f = new fn() 
    f.__proto__  // 看看这个对象里面有啥  
    /*
    上面那行代码返回一个对象,constructor 指向其构造函数, 
    又有一个 __proto__ 指向 'Object',记住这个Object的结构,待会要考
    {constructor: ƒ}
    constructor: ƒ fn()
    __proto__: Object
    */
    fn.prototype
    /* 
    再看看这行代码,好神奇耶,看起来跟 f.__proto__ 相等,
    所以,我们得出第一个“规律”
    f.__proto__ === fn.prototype  
    你试着验证这条规律,发现它是对的,我们用文字总结:
    规律1,对象的__proto__总是指向它的构造函数的prototype,也就是原型链
    */
    
    那么问题来了,在js的世界里,一切皆对象,上面的 fn ,应该也是对象。再打开你的控制台,输入以下代码
    fn.__proto__  
    // 奇怪,跟上次长得不一样,控制台打印出了看不懂的代码 'ƒ () { [native code] }'  
    // 再想想上个规律,对象的—__proto__总指向其构造函数,那 fn 的构造函数是?
    Function.prototype
    // 机智的你,迫不及待的想 用 === 把两个值进行比较,你没猜错,是 true
    // 于是我们有了规律2,虽然它是规律1 的子集,但还是先总结起来,后面有用  
    // 规律2,函数的__proto__ 总是指向 Function 的 原型链,并且还是一个 
    // ƒ () { [native code] }
    
    emmm,问题又来了,上面的代码片段打印出 ƒ () { [native code] }, 虽然它是原生代码,但它还是隐喻我们,它是一个 funtion,好奇的我们又想知道,它到底是什么玩意儿?
    typeof Function.prototype
    // 'function' 果然,它还是函数!!!,我们的规律2可以用起来了,
    // 函数的 __proto__ 肯定是指向 Function 的原型链  
    Function.prototype.__proto__ === Function.prototype  
    // false  
    // ???
    // ???
    // 你在逗我?
    // 别着急,我们 打印出来看看
    Function.prototype.__proto__  
    // 你会发现,它是一个对象,并且其 constructor 指向了 Object 构造函数。  
    // 它?会不会是 Object.prototype 呢?  
    Function.prototype.__proto__ === Object.prototype
    // 诚不欺我,果然是。可是为啥要推翻我们的规律,
    // 硬生生的将它指给 Object.prototype呢  
    // 再回头看看代码,其实它循环引用的。啥玩意儿是循环引用呢?  
    let a = {}
    let b = {}
    a.b = a  
    // 这就是循环引用,子子孙孙无穷尽也,为了规避这种情况,浏览器特意做了如上操作,于是我们又有了规律 3
    // 规律3: Function 原型链的 __proto__ 指向了 Object 的 原型链,是为了规避循环引用。
    
    我们总结了三种规律,到头了吗,Function 到头了,Object 还没有(这句话有些哲学的味道)
    让我们继续探究
    规律3 是针对 Function 的原型链的,那 Object 的原型链是啥
    typeof Object.prototype
    // 快看,是对象,我们的规律1派上用场了,再回头看看规律1  
    // 既然是对象,那其 __proto__ 肯定指向 Object.prototype 了,机智如我  
    Object.prototype.__proto__ === Object.prototype
    // false
    // ?
    // ???
    // 经过上次 Function.prototype 的洗礼,我们一眼就发现了循环引用的问题
    // 是的,为了避免这种问题,浏览器让 Object.prototype.__proto__ 指向了 null
    // 让我们的原型链,有了“终点”,有了“根”。
    // 我们的规律4也出炉了 Object.prototype 的 __proto__ 指向了 null
    

    __proto__ 四条规律

  • 对象的 __proto__ 总是指向它的构造函数的prototype,也就是原型链
  • 函数的 __proto__ 总是指向 Function 的 原型链,并且还是一个 native code function
  • Function 原型链的 __proto__ 指向了 Object 的 原型链,是为了规避循环引用。
  • Object.prototype 的 __proto__ 指向了 null,也是为了规避循环引用
  • 当你彻底了解这四条规律的时候,尝试去看最开始那张神图,你会发现,so easy~~ *
    sfd

    文章