Giter Site home page Giter Site logo

blog's People

Contributors

lindysen avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

lct989

blog's Issues

webpack 面试了解

1.webpack 构建离线应用

==优化==
2. 减少文件搜索范围

resolve.modules 的默认值是 ['node_modules'],含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。


resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。


resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')]
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['main'],
    // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,
    // 减少耗时的递归解析操作
    alias: {
      'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'), // react15
      // 'react': path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'), // react16
    }
 
    // 尽可能的减少后缀尝试的可能性
    //在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在
    extensions: ['js'],
    // 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
    //让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。
    noParse: [/react\.min\.js$/],
  },

3 动态链接库

在一个动态链接库中包含给其他模块调用的函数和数据

1.把网页依赖的基础模块抽离出来,打包进一个个动态链接库,一个动态链接库中包含多个模块

2.当需要导入的模块在动态链接中,这个模块不能在被重新打包 而是直接去动态链接库中

3.页面依赖的所有动态链接库需要被加载

 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。  由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。

4 cdn 加速 内容分发网络

压缩代码减少网络传输大小

Webpack 接入cdn

1. 静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL。
2.静态资源的文件名称需要带上有文件内容算出来的 Hash 值,以防止被缓存。
3.不同类型的资源放到不同域名的 CDN(同一时刻针对同一个域名的资源并行请求是有限制的话(具体数字大概4个左右,不同浏览器可能不同)) 服务上去,以防止资源的并行加载被阻塞。这样会增加域名解析的时间 可以通过 head 标签<link rel="dns-perfetch" href="//sxx.com"/>预解析域名 减少域名解析的时间

  1. 压缩代码
1. 混淆源码
2. 减少网络传输流量
3. 提升网页加速

5 背后的运行机制

1. plugin: 扩展插件,在webpack构建过程中的特定时机会广播事件,插件监听特定事件,在特定时机作一些操作

2. loader: 模块转换器,用于把模块原内容转换成新的内容

webpack 的构建过程是串行化的从启动到结束 经过这些过程
1. 确定配置参数: 配置文件中的参数和shell命令行中的参数合并成最终的参数

2. 初始化compiler对象: 使用得出最终的参数初始化compiler对象,加载所有插件配置,执行compiler对象的run方法开始构建

3.确定入口:根据entry参数确定入口文件

4. 编译模块:从入口文件开始调用配置的loader对文件进行转换,在找出入口文件依赖的模块,逐个对他们进行转换,入口文件依赖的模块都被转换过了

5. 完成编译: 在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

6. 输出资源:根据入口与模块之间的依赖关系,产出一个个包含多个模块的chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这里是改变输出内容的最后机会

7. 输出完成:在确定好输出资源后,根据配置的路径和文件名,把文件内容写进到文件系统

Compiler 全局只有一个,包含完整的webpack的配置, 负责文件监听和启动编译

当 Webpack 以开发模式运行时,每当监测到文件变化,就会生成一个新的Compilation对象,Compilation对象包含了当前模块的资源,编译生成资源,变化的文件等,Compilation对象也提供了很多事件回调供插件使用


。。。。。。。。。。。
初始化阶段:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
1.初始化参数
2.实例化compiler
3.加载插件,依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。
4. environment: 应用node.js的文件系统到compiler对象
5. entry-option 根据配置的entry信息,给每个entry生成一个对应的entryPlugin,为入口的解析,递归解析依赖作准备

6. after-plugins:当所有插件apply方法调用完成
7 .after-resolvers

编译阶段:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
1. run 开始一次新的编译
2. complier: 通知插件将开始一次新的编译
3. complilation: 生成新的complilation对象
4. make 一个新的 Compilation 创建完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。
5.after-compile 一次 Compilation 执行完成。
6.invalid 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致 Webpack 退出。


输出阶段:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

1.emit: 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
2.after-emit文件输出完毕
3.done:成功完成一次完成的编译和输出流程。
4.failed 如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。
在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个 Chunk。

6.输出文件分析

原来一个个单独的模块文件被合并到一个文件中
bundle.js

为什么bunlde.js能直接在浏览器中运行呢

bundle.js中能直接运行在浏览器中原因是在输出文件中通过_webpack_require_函数定义了一个在浏览器中执行的加载函数来模拟node.js中的require语句

做不到和node一样在本地加载文件,当发现需要加载新的文件时需要通过网络,但是模块多了的化,时间就很长,所有就放在数组里,一次加载完

_webpack_require_函数还有缓存处理,已经加载过的模块不会进行二次加载,会去缓存中获取

7 抽取页面公共代码

改善什么现象
1. 相同的资源被重复加载,浪费用户的流量 服务器的成本
2. 包体也大

8 按需加载

最关键的一行是 chunkFilename: '[name].js',,它专门指定动态生成的 Chunk 在输出时的文件名称。 如果没有这行,分割出的代码的文件名称将会是 [id].js。
import('/*webpackChunkName:"show"*/''./show.js').then((show)=> {
    show('Webpack');
})

9 plugin

webpack 就像一个生产线,通过一系列流程才能将源文件转换成输出结果,生产线上每个处理流程的职责是单一的,多个流程存在依赖关系
只有完成当前处理后才能交给下一个流程去处理
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。

10 loader 链式调用 翻译员

Loader 运行在 Node.js 中,你可以调用任何 Node.js 自带的 API

loader 将源代码经过转换后输出新的结果支持链式调用

在你开发一个 Loader 时,请保持其职责的单一性,你只需关心输入和输出

module.exports = function(source){
    return source;
}
loader 有同步和异步之分
Webpack 会默认缓存所有 Loader 的处理结果, 

加载本地loader
1. npm-link

11 Prepack

Prepack 在编译阶段就预先执行源码得到结果,再把结果输出来以提高性能
而不是在运行时再去求结果
还不够成熟

不能够识别Dom APi 部分Node.js API
存在优化后代码性能更低的情况
存在优化后代码体积更大的情况

理解 Vue 数据响应原理

Vue 的数据响应是通过数据劫持结合发布者和订阅者实现的。其主要是通过Object.defineProperty()来实现数据劫持的。
本文的例子实现数据更新驱动视图更新是直接通过操作DOM,且是通过直接分析DOM来定位依赖的。其实Vue 内部机制不完全是这样。
下面我补充一些Vue 实现数据响应的一些大体细节。这块东西真的很多,我仔细说个大概,具体细节还需要大家自己去了解。

1 数据观察

实例化 Vue 实例的时候,Vue.prototype._init 方法被第一个执行,在 initState 函数内部使用 initData 函数初始化 data 选项。这是数据响应的开始。initData 函数的主要作用是保证 options 中的 data 选项是个函数且返回的对象。同时在 Vue 实例对象上添加代理访问数据对象的同名属性。最后 通过调用了 observe 函数观测数据,将 data 变成响应式的(data数据类型 是对象或数组时处理的 方式是不同的)。实现对象类型的 data 的观测主要是通过 defineReactive 函数

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  // 省略...

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) { //  watcher
        // 这里闭包引用了上面的 dep 常量
        dep.depend() // 收集依赖 Watcher
        // 省略...
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 省略...
      if (setter) { // 如果属性原本存在set函数则调用
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      // 这里闭包引用了上面的 dep 常量
      dep.notify() // 触发依赖
    }
  })
}

在访问器属性的 getter/setter 中,通过闭包引用了前面定义的“筐”,即 dep 常量。每一个数据字段都通过闭包引用着属于自己的 dep 常量。
那么为数组类型该如何处理???
处理数组的方式与纯对象不同,数组是一个特殊的数据结构,它有很多实例方法,并且有些方法会改变数组自身的值,这些方法有:push、pop、shift、unshift、splice、sort 以及 reverse 等。其通过拦截数组变异方法的方式得知用户调用这些变异方法,从而触发依赖。数组本身也是一个对象,所以它实例的 proto 属性指向的就是数组构造函数的原型,即 arr.proto === Array.prototype 为真。其是通过设置 proto 属性的值为一个新的对象,且该新对象的原型是数组构造函数原来的原型对象。在新对象的变异方法里收集依赖

2 收集依赖

正是因为 watcher 对表达式的求值,触发了数据属性的拦截器函数,从而收集到了依赖,当数据变化时能够触发响应。

new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true /* isRenderWatcher */)
 updateComponent = () => {// 可以简单的认为把渲染函数生成的虚拟DOM渲染成真正的DOM
    // vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)
    // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM
    vm._update(vm._render(), hydrating)
  }

在上面的代码中 Watcher 观察者实例将对 updateComponent() 函数求值, updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变化时将重新执行 updateComponent 函数,这就完成了重新渲染。
Watcher constructor 的最后

if (this.computed) {
  this.value = undefined
  this.dep = new Dep()
} else {
  this.value = this.get() // 此时调用 Watcher 的实例方法 get
}

get () {  // get 为 Watcher 的实例方法
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm) // 此时 this.getter 指的就是 updateComponent
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}

3 触发依赖

修改属性值时会触发属性的 set 拦截器函数,这样就会调用 Dep 实例对象的 noitfy 方法,

set: function reactiveSetter (newVal) {
  // 省略...
  dep.notify()
}
export default class Dep {
  // 省略...

  constructor () {
    this.id = uid++
    this.subs = []
  }

  // 省略...

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

notify 方法只做了一件事,就是遍历当前 Dep 实例对象的 subs属性所保存的所有观察者对象,并逐个调 用观察者对象的 update 方法,这就是触发响应的实现机制,那么大家应该也猜到了,重新求值的操作应该是在 update 方法中进行的,那我们就找到观察者对象的update方法真正的更新变化操作都是通过调用 观察者实例对象的 run 方法完成的,run 方法内判断次 Watcher 是否是激活状态,若激活则调用实例方法getAndInvoke。

getAndInvoke (cb: Function) {
// 重新求值其实等价于重新执行渲染函数,最终结果就是重新生成了虚拟DOM并更新真实DOM,这样就完成了重新渲染的过程
  const value = this.get() 
  if (
    value !== this.value ||
    // Deep watchers and watchers on Object/Arrays should fire even
    // when the value is the same, because the value may
    // have mutated.
    isObject(value) ||
    this.deep
  ) {
    // set new value
    const oldValue = this.value
    this.value = value
    this.dirty = false
    if (this.user) {
      try {
        cb.call(this.vm, value, oldValue)
      } catch (e) {
        handleError(e, this.vm, `callback for watcher "${this.expression}"`)
      }
    } else {
      cb.call(this.vm, value, oldValue)
    }
  }
}

好啦,谢谢大噶!!!

async

async函数说明 内部有异步操作,当函数执行的时候,一旦遇到awaite就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

await表示 紧跟在后面的表达式需要等待结果

async函数 返回 Promise对象,可以使用then添加回调函数
async函数内部错误,导致返回的Promise对象为reject状态,会被catch函数处理,如果在async函数中直接返回一个直接量,async函数会用Promise.resolve()封装成Promise对象

只有async函数内部所有的异步函数处理完成,才会执行then指定的回调函数(除非内部出现错误或者return值)

正常情况下,await 后面

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

await 【xxx()】 为awaite表达式, xxx()函数如果返回一个Promise对象,该Promise 对象resolve出来的值为 await表达式的结果
如果 await 后面跟着的表达式不是返回Promise对象,该表达式的结果就是await表达式的结果

数据结构与算法之美——第三讲 复杂度分析

第三讲:
如何衡量编写的算法代码的执行效率呢?时间,空间复杂度分析
事后统计法,有非常大的局限性
1.测试结果非常依赖测试环境
2.测试结果受数据规模的影响很大
我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法
大O复杂度表示法
算法的执行效率,粗略地讲,就是算法代码执行的时间。
所有代码的执行时间T(n)与每行代码的执行次数n成正比。
T(n) = O(f(n))
T(n)表示代码执行的时间,n表示数据规模的大小,f(n)表示每行代码执行的次数总和,因为这是一个公式,所以用f(n)表示,公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比,大O时间复杂度实际上不具体表示代码的真正执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫渐进时间复杂度,简称时间复杂度。
当n很大时,而公式中的低阶,常量,系数三部分并不左右增长趋势,所有都可以忽略,只需要记录一个最大阶的量级就可以。
时间复杂度分析
1.只关注循环执行次数最多的一段代码
这段核心代码的执行次数的n的量级,就是整段要分析代码的时间复杂度
2.加法法则:总复杂度等于量级最大的那段代码的复杂度
T1(n) = O(f(n)),T2 = O(g(n)) 那么T(n) =T1(n)+T2(n) = max( O(f(n)),O(g(n))) = O(max(f(n),g(n)))
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
T(n)=T1(n)T2(n) =O(f(n)) O(g(n))= O(f(n)g(n))
几种常见的时间复杂度实例分析
多项式量级和非多项式量级,其中非多项式量级是O(2
n)(2的n次方),O(n!)
当数据规模n越来越大时,非多项式量级算法的执行时间急剧增加,求解问题的执行时间会无限增长,所以非多项式时间复杂度的算法其实是非常低效的算法。
几种常见的多项式时间复杂度
1.O(1) 只要算法中不存在循环语句,递归语句,即使有成千上万行的代码,其时间复杂度也是O(1)
2.O(logn)O(nlogn) 在采用大O标记复杂度的时候可以忽略掉系数,即O(Cf(n)) = O(f(n))
3.O(m+n) O(m*n)代码的复杂度由两个数据的规模决定
m,n是表示两个数据规模,所以无法事先评估m,n谁的量级大,所以在表示复杂度的时候,就不能简单的利用加法法则,省略掉其中一个
T1(m)+T2(n)=O(f(m)+f(n)) T1(m)*T2(n)=O(f(m)*f(n))

时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系
空间复杂度全程就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系

Vue keep-alive

Vue内部把DOM节点抽象成一个个VNode,keep-alive组件缓存的是VNode不是真实DOM节点,它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

keep-alive

created() {
 /* 缓存对象 */
    this.cache = Object.create(null)
}
destoryed() {//在组件被销毁的时候清除cache缓存中的所有组件实例。
     for (const key in this.cache) {
        pruneCacheEntry(this.cache[key])
        // this.cache[key]是vnode
        // key vnode.key or id+tag
    }
}

获取slot 插槽中的组件

render() {
    /* 得到slot插槽中的第一个组件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)
    const componentOptions =  vnode && vnode.componentOptions;

}

浏览器进程与线程

进程 线程
浏览器多进程
没打开一个tab 就多一个进程

browser进程 浏览器的主进程 只有一个

  1. 负责各个页面的管理,创建和销毁其他进程
  2. 网络资源的管理,下载等
  3. 负责浏览器界面显示,与用户交互。如前进,后退等

GPU进程:最多一个,用于3D绘制等

浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的
[页面的渲染,JS的执行,事件的循环]都在这个进程里
默认每个Tab页面一个进程,互不影响。
主要作用为 页面渲染,脚本执行,事件处理等

多进程的优势

  1. 防止单 page crash 影响整个浏览器
  2. 避免第三方插件crash影响整个浏览器
  3. 多进程充分利用多核优势
  4. 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

渲染进程内部是有多线程的看看哪些线程吧

1.GUI渲染线程

1.负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

2.当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

3.GUI渲染线程与JS引擎线程是互斥的  当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

2.JS引擎线程

1. 也称为JS内核,负责处理Javascript脚本程序

2. JS引擎线程负责解析Javascript脚本,运行代码。

3.GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3.事件触发线程

1.当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中

2.当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理


由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4.定时触发器线程

传说中的setInterval与setTimeout所在线程

为什么要单独的定时器线程?因为JavaScript引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。

5.异步http请求线程

在XMLHttpRequest在连接后是通过浏览器新开一个线程请求

将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

==WebWorker==

  1. 创建worker时,js引擎向浏览器申请一个子线程, 子线程完全由浏览器控制 所以不能操作dom

  2. js引擎线程与worker子线程通过特定的方式通信,需要通过序列化对象来与线程交互特定的数据

  3. JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。

WebWorker与SharedWorker

1.WebWorker只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享

所以Chrome在Render进程中(每一个Tab页就是一个render进程)创建一个新的线程来运行Worker中的JavaScript程序。
  1. SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用

sharedWorker 由单独的进程管理, WebWorker只是属于render进程下的一个线程

css加载是否会阻塞dom树渲染?

css是由单独的下载线程异步下载的。

css加载不会阻塞DOM树解析(异步加载时DOM照常构建)

但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)

普通图层和复合图层

GPU中每个复合图层是单独绘制的  所以不相互影响


普通文档流内可以理解成一个复合图层 这里称为默认复合层,里面不管添加多少元素,其实都是在同一个复合图层中)
absolute fixed 布局 脱离文档流 还在这个复合图层



通过硬件加速,声明一个新的复合图层 他会单独分配资源, 当然也会脱离文档流,这样这个复合图层怎么改变,也不会影响默认复合层的重排重绘



如何生成新的复合图层
硬件加速

最常用的方式:translate3d、translateZ
opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)

<video><iframe><canvas><webgl>等元素
其它,譬如以前的flash插件


absolute和硬件加速的区别

absolute 虽然脱离文档流 但是仍然在默认复合层内,就算absolute中信息改变时不会改变普通文档流中的render树,但是浏览器最终绘制的时候还是会整个复合层绘制,所以absolute中信息的改变,仍然会影响整个复合层的绘制。 

硬件加速是在另一个复合层中,他的信息改变不会引起原来的复合层改变(当然,内部肯定会影响属于自己的复合层)仅仅是引发最后的合成(输出视图)


复合图层的作用

一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能量,但是不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡



Event loop


J S中分为两种任务类型:macrotask和microtask在ECMAScript中,microtask称为jobs,macrotask可称为task

macrotask 中的事件都是放在一个事件队列里面,是由事件触发线程维护
可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

1.每一个task会从头到尾将这个任务执行完毕,不会执行其它
2.浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)


microtask 中所有微任务都是添加到微任务队列中,等待当前macrotask执行完毕后 这个队列由js引擎线程自己维护

1. 在当前task任务后,下一个task之前,在渲染之前
2.所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
3.在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

microtask:Promise,process.nextTick等
macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)


总结一下运行机制

1.执行一个宏任务栈中(没有就从事件队列中获取)
2.执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行
4. 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
5. 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

js 执行上下文栈

JS引擎并非一行行执行,而是一段段执行,当执行一段代码时,会进行准备工作

可执行代码

全局代码 函数代码 eval代码

当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做创建"执行上下文(execution context)"

JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

垃圾回收机制

GC 垃圾回收

  1. 找到内存空间中的垃圾
  2. 回收垃圾,让程序员能再次利用这个空间

别备的接触
常用的GC算法

  1. 引用技术
    2.标记-清除算法
标记阶段: 把所有活动对象做上标记。
清除阶段: 把没有标记(也就是非活动对象)销毁。

标记阶段:根可以理解成我们的全局作用域,GC从全局作用域的变量,沿作用域逐层往里遍历(对,是深度遍历),当遍历到堆中对象时,说明该对象被引用着,则打上一个标记,继续递归遍历(因为肯定存在堆中对象引用另一个堆中对象),直到遍历到最后一个(最深的一层作用域)节点。

清除阶段:  又要遍历,这次是遍历整个堆,回收没有打上标记的对象。
解决 循环引用的问题:因为两个对象从全局对象出发无法获取。因此,他们无法被标记,他们将会被垃圾回收器回收。

空闲链表:空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端

GC方式是一个定时运行的任务,也就是说当程序运行一段时间后,统一GC,

优点: 
1. 解决循环饮用这种现象
2. 只有打和不打两个标记
缺点:
1.造成碎片化(有点类似磁盘的碎片化
2.再分配时遍次数多,如果一直没有找到合适的内存块大小,那么会遍历空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端

  1. 复制
1. 只把某个空间的活动对象复制到其他空间。

Vue 编译器初探

编译器是啥,简单点就是将源代码转换成目标代码的工具,详细点是将便于人编写、阅读、维护的高级计算机语言所写的源代码程序,翻译为计算机解读、运行的低阶机器语言的程序。

Vue的编译器大致分在三个阶段,即词法分析 -> 句法分析-> 代码生成。词法分析阶段大致是把字符串模版解析为一个个token,句法分析在词法分析基础上生成AST,代码生成根据AST生成最终代码。本篇大概分析一下词法分析的过程。

词法分析

在源代码(src/compiler/index.js)中由这么一句代码,包含 parse,ast 关键词,可知 parse 函数就是用来解析模版字符串,生成 AST 的。

const ast = parse(template.trim(), options)

找到 parse 函数,发现其内部调用了 parseHTML,实际上parseHTML函数的作用就是用来做词法分析的。而parse函数在词法分析的基础上最终生成 AST

/**
 * Convert HTML string to AST.
 */
export function parse (
  //parse 函数的作用则是在词法分析的基础上做句法分析从而生成一棵 AST。
  template: string,
  options: CompilerOptions
): ASTElement | void {
 ...
  parseHTML(template, ....){
      //省略...
  }
  ...

那我们就去看看parseHTML是如何读取字符流一步步解析模板字符串 的吧

export function parseHTML (html, options) {
  // 定义一些常量和变量
  const stack = []// 初始化为一个空数组,在while循环中处理html字符流每遇到一个非一元标签,就将该开始标签push到该数组中。
 
  const expectHTML = options.expectHTML
  
  const isUnaryTag = options.isUnaryTag || no// 用来检测一个标签是否是一元标签
 
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no// 用来检测一个标签是否是可以省略闭合标签的非一元标签。
  
  let index = 0 // 标识着字符流的读入位置
 
  let last, lastTag
  // last 变量存储剩余还未parse的html字符串
  // 变量 lastTag 存储着位于stack栈顶的元素。

  // 开启一个 while 循环,循环结束的条件是 html 为空,即 html 被 parse 完毕
  while (html) {
    last = html 
    // 每次开始循环时将html的值赋值给变量last
    
    if (!lastTag || !isPlainTextElement(lastTag)) {
      // isPlainTextElement函数确保即将 parse 的内容不是在纯文本标签里 (script,style,textarea)
      
      let textEnd = html.indexOf('<') // html字符串中左尖括号(<)第一次出现的位置,在对textEnd变量进行一系列的判断

      if (textEnd === 0) {
        // textEnd === 0时说明 html 字符串的第一个字符就是左尖括号(<)
        /**
         1、可能是注释节点:<!-- -->
         2、可能是条件注释节点:<![ ]>
         3、可能是 doctype:<!DOCTYPE >
         4、可能是结束标签:</xxx>
         5、可能是开始标签:<xxx>
         6、可能只是一个单纯的字符串:<abcdefg
       */
      }
    
      let text, rest, next
      if (textEnd >= 0) // textEnd >= 0 的情况 
        // **用来处理那些第一个字符是** < 但没有成功匹配标签,或第一个字符不是 < 的字符串。
      }

      if (textEnd < 0) {
        // textEnd < 0 的情况
        // 整个 html 字符串作为文本处理
      }
    
      if (options.chars && text) {
      // 调用parse函数传入的option重的chars钩子处理文本
        options.chars(text)
      }
    } else {
          // 即将 parse 的内容是在纯文本标签里 (script,style,textarea)
    }
    
    
    // 因为while循环内部会调用advance更新html
    // 如果上面的处理之后两者相等,说明html在经过循环体的代码后没有任何变化,此时的html字符串作为纯文本对待
    // 将整个字符串作为文本对待
    if (html === last) {
      options.chars && options.chars(html)
      if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {
        options.warn(`Mal-formatted tag at end of template: "${html}"`)
      }
      break
    }
  }

  // 调用 parseEndTag 函数
  parseEndTag()

  // advance 函数 将已经 parse 完毕的字符串剔除
  function advance (n) {
     index += n
     html = html.substring(n)
  }

  // parseStartTag 函数用来 parse 开始标签
  function parseStartTag () {
    // ...
  }
  // handleStartTag 函数用来处理 parseStartTag 的结果
  function handleStartTag (match) {
    // ...
  }
  // parseEndTag 函数用来 parse 结束标签
  function parseEndTag (tagName, start, end) {
    // ...
  }
}

通过以上代码可知,在数组为空或 标签纯文本标签(style,script,textarea)情况下,获取<在字符串中第一次出现的位置可分为三种情况,

  1. 在 textEnd === 0(<出现在第一个位置) 的情况下,以注释节点和开始标签为例,简单讲解一下,内部是如何处理 textEnd === 0 的情况的。
  • 注释节点
if (comment.test(html)) {
// comment是一个正则常量  /^<!\--/
  const commentEnd = html.indexOf('-->')
  // 完整的注释节点不仅仅要以 
  // <!-- 开头,还要以 --> 结尾

  if (commentEnd >= 0) { // 说明这确实是一个注释节点
    if (options.shouldKeepComment) {
    //在 Vue 官方文档中可以找到一个叫做 comments 的选项,实际上这里的 options.shouldKeepComment 的值就是 Vue 选项 comments 的值
    options.comment(html.substring(4, commentEnd)) //调用parse函数传入的option参数中的comment钩子
    }
    advance(commentEnd + 3) 
    // 调用advance 函数传入已经 parse 完毕的字符串的结束位置,
    // 剔除已经被处理过的html 更新html变量为剩下未处理的字符串
    // 更新indexd的值为commentEnd + 3(html字符串的读入位置)

    continue
    // 跳出此次循环 开启下一次循环,重新开始 parse 过程。
  }
}
 
  • 开始标签
const startTagMatch = parseStartTag()
// 调用 parseStartTag 函数,并获取其返回值,如果存在返回值则说明开始标签解析成功,的确是一个开始标签
if (startTagMatch) {
  handleStartTag(startTagMatch)
  if (shouldIgnoreFirstNewline(lastTag, html)) {
    advance(1)
  }
  continue
}





function parseStartTag () {
  const start = html.match(startTagOpen)
  // startTagOpen为匹配开始标签的正则表达式
  // 用来匹配开始标签的一部分,这部分包括:< 以及后面的 标签名称,并且拥有一个捕获组,即捕获标签的名称。
  //匹配的结果赋值给 start 常量,如果 start 常量为 null 则说明匹配失败,则 parseStartTag 函数执行完毕,其返回值为 undefined。
  if (start) {

    const match = {
      tagName: start[1],
      attrs: [],
      start: index
    }
    advance(start[0].length) // 这里传入tagName标签的长度 调用advance函数
    
    let end, attr
    // while循环体执行的条件是没有匹配到开始标签的结束部分,并且匹配到了开始标签中的属性
    while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
     // attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
      advance(attr[0].length)
      // 这里传入 attr 的长度 调用 advance 函数
      match.attrs.push(attr)
      // 将此次循环匹配到的结果 push 到前面定义的 match 对象的 attrs 数组
    }
    if (end) {
    //变量 end 存在,即匹配到了开始标签的 结束部分 时,才能说明这是一个完整的开始标签。
      match.unarySlash = end[1] 
      // end[1] 不为 undefined,那么说明该标签是一个一元标签
      advance(end[0].length)
      match.end = index // 前面调用 advance 函数更新了 index,所以 match 的 end 为最新的字符串读入位置
      
      return match
      //只有end存在即一个完整的开始标签才会返回match对象,其他情况下返回 undefined
    }
  }
}
// 处理开始标签的解析结果
function handleStartTag (match) {
    const tagName = match.tagName
    const unarySlash = match.unarySlash
    // 常量 unarySlash 的值为 '/' 或 undefined 

    if (expectHTML) {
      // isNonPhrasingTag 非段落标签 自动闭合<p>
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag)
      }
      if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
        // 当前解析的标签是可闭合的标签且与上一个开始标签相同
        parseEndTag(tagName)
      }
    }

    const unary = isUnaryTag(tagName) || !!unarySlash
    // 判断开始标签是否是一元标签

    const l = match.attrs.length
    const attrs = new Array(l)
    // for 循环的作用是格式化 match.attrs 数组,并将格式化后的数据存储到常量 attrs 中
    for (let i = 0; i < l; i++) {
      const args = match.attrs[i]
      // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
      if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
        if (args[3] === '') { delete args[3] }
        if (args[4] === '') { delete args[4] }
        if (args[5] === '') { delete args[5] }
      }
      const value = args[3] || args[4] || args[5] || ''
      attrs[i] = {
        name: args[1],
        value: decodeAttr(
          value,
          options.shouldDecodeNewlines
        )
      }
      //attrs为数组
    }

    if (!unary) { // 非一元标签 push进stack栈内
      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
      lastTag = tagName// lastTag变量保存栈顶的元素 更新lastTag变量
    }

    if (options.start) {
    // 调用parse函数传入的option参数重的start钩子
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }
  1. 在 textEnd >= 0 的情况下
//这段代码处理那些第一个字符是 < 但没有成功匹配标签,或第一个字符不是 < 的字符串
let text, rest, next
if (textEnd >= 0) {
rest = html.slice(textEnd)
// 现rest为<开头的字符串
// while循环的条件是只有截取后的字符串不能匹配标签,说明<存在于普通文本中
while (
  !endTag.test(rest) &&
  !startTagOpen.test(rest) &&
  !comment.test(rest) &&
  !conditionalComment.test(rest)
) {
  // < in plain text, be forgiving and treat it as text
  next = rest.indexOf('<', 1)// 找到第二个<  位置为2
  if (next < 0) break
  // 如果不存在<就跳出循环执行下面的语句
  textEnd += next // 更新后的 textEnd 的值将是第二个 < 符号的索引
  rest = html.slice(textEnd) // 使用新的 textEnd 对原始字符串 html 进行截取,并将新截取的字符串赋值给 rest开始新一轮的循环
}
text = html.substring(0, textEnd) //此时保证text是纯文本
advance(textEnd)
}

if (options.chars && text) {
    options.chars(text) // 调用parse函数的option参数中的chars钩子
}

  1. 在 textEnd <= 0的情况下,整个 html 字符串作为文本处理。
if (textEnd < 0) {
    text = html
    html = ''
}

上面的分析是针对最近一次标签是非纯文本标签的情况下,那么是如何处理纯文本标签的呢?纯文本标签包括 script 标签、style 标签以及 textarea 标签。

let endTagLength = 0
//
//用来保存纯文本标签闭合标签的字符长度
const stackedTag = lastTag.toLowerCase()
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
// reStackedTag 的作用是用来匹配纯文本标签的内容以及结束标签的
// 代码使用正则 reStackedTag 匹配字符串 html 并将其替换为空字符串,常量 rest 将保存剩余的字符
const rest = html.replace(reStackedTag, function (all, text, endTag) {
// all 保存着完整的字符串
// text 纯文本内容保存着结束标签
// endTag
endTagLength = endTag.length
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
  text = text
    .replace(/<!--([\s\S]*?)-->/g, '$1')
    .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')
}
if (shouldIgnoreFirstNewline(stackedTag, text)) {
// 忽略pre textarea 标签的第一个换行符
  text = text.slice(1)
}
if (options.chars) {
// 调用parse函数传入的option重的chars钩子
  options.chars(text)
}
return '' 
 // 替换掉正则匹配到的内容为''
})
index += html.length - rest.length; 
// 结束标签位置为html.length - 剩余字符串长度

html = rest // 更新html 开始新的while循环
parseEndTag(stackedTag, index - endTagLength, index)

看完上面几段代码块,发现 parseEndTag 函数还没有分析,根据名字应该是处理结束标签的吧。

// parseEndTag有三种调用方式
//  parseEndTag() 处理 stack 栈剩余未处理的标签。
// parseEndTag(tagName)
// parseEndTag (tagName, start, end) 正常处理结束标签
function parseEndTag (tagName, start, end) {
    let pos, lowerCasedTagName
    if (start == null) start = index
    if (end == null) end = index
    
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase()
    }
    
    // Find the closest opened tag of the same type
    // stack倒叙查找到与结束标签相对应的开始标签
    if (tagName) {
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break
        }
      }
    } else {
      // If no tag name is provided, clean shop
      pos = 0
    }
    
    if (pos >= 0) {
      // Close all the open elements, up the stack
      for (let i = stack.length - 1; i >= pos; i--) {
        if (process.env.NODE_ENV !== 'production' &&
          (i > pos || !tagName) &&
          options.warn
        ) {
        //在非生产环境下,当不传入tagName或在数组下标大于pos时(因为stack存入的是非一元的起始标签,说明这些起始标签缺少结束标签)
          options.warn(
            `tag <${stack[i].tag}> has no matching end tag.`
          )
        }
        if (options.end) {
        // 调用parse传入的option参数重的end钩子
        // 大概是更新stack
          options.end(stack[i].tag, start, end)
        }
      }
      // Remove the open elements from the stack
      stack.length = pos
      // 当传入tagName时删除pos后的元素
      // 未传入tagName时 pos为0  相当于清空stack
      lastTag = pos && stack[pos - 1].tag 
      // 更新栈顶元素
    } else if (lowerCasedTagName === 'br') { 
    // pso < 0 只写了结束标签没有写开始标签
    // 遇到</br>替换为<br>
      if (options.start) {
        options.start(tagName, [], true, start, end)
      }
    } else if (lowerCasedTagName === 'p') {
    // pso < 0 只写了开始标签没有写结束标签
    // 遇到</p> 补全为<p></p>
      if (options.start) {
      // // 调用parse传入的option参数重的start钩子
        options.start(tagName, [], false, start, end)
      }
      if (options.end) {
      // 调用parse传入的option参数重的end钩子
        options.end(tagName, start, end)
      }
    }
    // pos< 0情况下遇到的其他缺少起始标签的结束标签忽略
}
  

在词法分析的过程中,可以其实现方式就是通过读取字符流配合正则一点一点的解析字符串,直到整个字符串都被解析完毕为止。并且每当遇到一个特定的 token 时都会调用相应的钩子函数,同时将有用的参数传递过去。再parse函数根据这些参数生成AST

Vuex 浅析

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

在 Vue 项目中引入 Vuex,需要采用插件引入方式。
Vue.use(Vuex)

安装 Vuex 插件,会调用插件中提供的 install 方法

// src/stiore.js
// store.js 的顶部定义了一个 Vue 变量,防止注册多次

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
  //这里调用 了applyMixin 看名字应该是个混入,混入啥呢???
}

applyMixin 方法的主要功能将初始化 Vue 实例时传入的 store 设置到 this 对象的 $store 属性上, 子组件则从其父组件引用$store 属性, 层层嵌套进行设置. 这样, 任何一个组件都能通过 this.$store 的方式访问 store 对象了.

export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
  //// 2.x 通过 hook 的方式注入
  // Vue全局混入一个混入对象,在之后每个创建的 Vue 实例的 beforeCreate 生命周期中添加 vuexInit 函数
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 使用自定义的 _init 方法并替换 Vue 对象原型的_init 方法,实现注入
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }
  
  function vuexInit () {
    const options = this.$options
    // 创建 Vue 根实例的 option
    // store injection store 注入
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      // 子组件从其父组件引用 $store 属性
      this.$store = options.parent.$store
    }
      }
    }

在 Vue 项目中,new 一个 Store 实例,并在创建Vue根实例时传入 store 实例


const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
      ...
  },
});

new Vue({
    store
    ...
})

接下来 Store 的构造函数

  1. vuex 先对构造 store 需要的一些环境变量进行断言:
 
if (!Vue && typeof window !== 'undefined' && window.Vue) {
  // Vue 是全局变量时,自动 install
  install(window.Vue)
}

if (process.env.NODE_ENV !== 'production') {
 //  在非生产环境下,进行一些断言 
 //  当不满足参数1为 false 时抛出错误
  assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  assert(this instanceof Store, `store must be called with the new operator.`)
}

2.初始化变量

// store internal state
// 是否在进行提交状态标识
this._committing = false
this._actions = Object.create(null)
// 监听所有的action
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
// 监听所有的mutation
this._subscribers = []
//创建一个 Vue 实例, 利用 $watch 监测 store 数据的变化
this._watcherVM = new Vue()
    

重点看下this._modules = new ModuleCollection(options) 收集 modules 会调用 ModuleCollection,optinons 为 Store 构造函数传入的参数。从函数命名可以看出是模块收集注册的。

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    //注册根module
    this.register([], rawRootModule, false)
  }

  ...

  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      // 检查模块内的getters/ mutations/ actions 可迭代且是函数 或者 actions 的存在 handler 属性且属性值为函数
      assertRawModule(path, rawModule)
    }
    // 创建 module 对象
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
    // root 保存着根 module
      this.root = newModule
    } else {
     // 简单说 确定模块的父子关系
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
      // parent 为父module,在parent._children[path] = newModule
    }

    // register nested modules
    // 递归创建子 module
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
 ... 
}
forEachValue 是一个工具函数,对对象中的每个键值对调用函数
/**
 * forEach for object
 */
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

ModuleCollection 主要将传入的 options 对象整个构造为一个 module 对象, 并循环调用 register 为其中的 modules 属性进行模块注册, 使其都成为 module 对象, 最后 options 对象被构造成一个完整的组件树 。ModuleCollectionnew Module(rawModule, runtime)来创建具体的 module。

export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule
    const rawState = rawModule.state
    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  update (rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }
}

  1. 把 Store 类的 dispatch 和 commit 的方法的 this 指针指向当前 store 的实例上. 这样做的目的可以保证当我们在组件中通过 this.$store 直接调用 dispatch/commit 方法时, 能够使 dispatch/commit 方法中的 this 指向当前的 store 对象而不是当前组件的 this.
// 绑定 this 到 store
const store = this
const { dispatch, commit } = this

// 确保 dispatch/commit 方法中的 this 对象正确指向 store
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}

dispatch 的功能是触发并传递一些参数(payload)给与 type 对应的 action
commit 的功能是触发并传递一些参数(payload)给与 type 对应的 mutation
  1. store其他重要属性的配置
// 确保 dispatch/commit 方法中的 this 对象正确指向 store

this.strict = strict

// 根 module 的 state

const state = this._modules.root.state

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

// apply plugins
plugins.forEach(plugin => plugin(this))

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
  devtoolPlugin(this)
}




// installModule 方法则会将处理过的 modules 进行注册和安装,
/// installModule 接收5个参数: store、rootState、path、module、hot.
// store 表示当前 Store 实例, rootState 表示根 state, path 表示当前嵌套模块的路径数组
// module 表示当前安装的模块,
// hot 当动态改变 modules 或者热更新的时候为 true
function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 根据 path 或者路径上各层模块的 namespace
  const namespace = store._modules.getNamespace(path)
  // 根据 path 数组 寻找路径上的模块,把各模块的 namespaced 拼接起来 

  // register in namespace map
 // 在 store 上的 _modulesNamespaceMap 注册有 namesoaced 的模块
 // 方便后面根据 namespace 属性 直接获取模块
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  // 当 !isRoot为 true 说明 path.length > 0
  // 当 !hot 为 true,说明 hot 为 false
  if (!isRoot && !hot) {
    // 根据path 找到父级模块
    // 可知,在父 module 的 state 中通过 path 路径名注册子 state
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  // 给模块绑定上下文在 mapActions 等辅助函数时有用
  // 该方法其实是在重写 dispatch 和 commit 函数
  // 你是否有疑问模块中的 dispatch 和 commit
  // 是如何找到对应模块中的函数的
  // 假如模块 A 中有一个名为 add 的 mutation
  // 通过 makeLocalContext 函数,会将 add 变成
  // a/add,这样就可以找到模块 A 中对应函数了

  const local = module.context = makeLocalContext(store, namespace, path)
 
  //给模块上下文绑定处理过 type 的 dispatch,mutation,getters,
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    //向 store._mutations[type] 数组添加处理函数
    // local 传递,保证 mutation 的第一个参数是模块内部的state === local.state
    registerMutation(store, namespacedType, mutation, local) // store._mutations[type] type为namespacedType
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    // local 传递 local.dispatch,local.commit,local.getters,local.state
    // type 作用只为找到 store._actions[type] 数组 添加处理函数
    registerAction(store, type, handler, local) 
    // store._actions[type] type为namespacedType
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local) 
    // 在 store._wrappedGetters[type] (type为namespacedType)
    // 向 module 内部的 getter 传递 local.state,local.getters, root state, root getters,
  })
  // 循环注册子模块
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

简单提下如何模块的context是怎么处理的吧

function makeLocalContext (store, namespace, path) {
  // 是否存在命名空间
  const noNamespace = namespace === ''

  const local = {
   // 根 module 与子 module 都是调用 store.dispatch
   // 子 module 的 type 会经过命名空间处理
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        // 非生产环境且 actions[type] 不存在
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },
    // 根 module 与子 module 都是调用store.commit
   // 子module的type会经过命名空间处理
    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace) //返回一个新的对象,属性是 localType,value 是 store.getters[namespacedType]
    },
    state: {
      get: () => getNestedState(store.state, path) 
      // 根据 path 数据返回模块内部的 state
      // 在根 state 上一层层取值获取
    }
  })

  return local
}
  1. 上面可以看到有个函数resetStoreVM(this, state),原注释是初始化store vm,利用Vue的数据响应系统来监听内部变化,同时让store.__wrappedGetters变成绑定成vm的计算属性,响应变化。
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  //定义 getters 属性
  store.getters = {}
  // 获取处理的 getters 函数集合
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  // 循环所有处理过的 getters,
  // 并新建 computed 对象进行存储 getter 函数执行的结果,
  // 然后通过 Object.defineProperty 方法为 getters 对象建立属性
  // 使得我们通过 this.$store.getters.xxxgetter 能够访问到 store._vm[xxxgetters]
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure enviroment.
    computed[key] = partial(fn, store)
   // 等价于 computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent // 先暂存用户配置
  Vue.config.silent = true // 取消Vue的所有日志与警告
  // 设置新的 vm, 传入 state
  // 把 computed 对象作为 _vm 的 computed 属性, 这样就完成了 getters 的注册
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent // 恢复用户配置

  // enable strict mode for new vm
  if (store.strict) {
    // 严格模式下, 在mutation之外的地方修改 state 会报错
    enableStrictMode(store)
  }
  
 // 销毁旧的 vm 实例
  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

上文提到严格模式,是如何控制严格模式的呢

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
  // 利用 store 上的 _vm 属性指向的 vue 实例的 watch 时刻观察 store.state 变化。 
  // 回调函数为断言 store._committing 是否为 true,为 true 说明是 mutation
}
  1. 了解vuex的文档,可知vuex提供订阅mutation和action的功能,这又是如何实现的?可以自主想想,是不是可以在每次调用mutiaon,action时 回调你的handler呢?来看下commit和dispathc里面的处理吧
在 store 的属性配置里初始化了
 // 监听所有的 action
this._actionSubscribers = []

// 监听所有的 mutation
    this._subscribers = []


// 订阅 mutation
subscribe (fn) {
    return genericSubscribe(fn, this._subscribers)
}
//订阅 store 的 action。
  // handler 会在每个 action 分发的时候调用并接收
  //  action 描述和当前的 store 的 state 这两个参数:
subscribeAction (fn) {
// 默认是 before
const subs = typeof fn === 'function' ? { before: fn } : fn
return genericSubscribe(subs, this._actionSubscribers)
}

// 没有就添加 有就删除
function genericSubscribe (fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

// 目前可知 mutation 订阅时往 this._subscribers 数组中 push 处理函数,action 订阅时往 this._actionSubscribers 数组中 push 处理函数

// 接下来简单看下 commit 时处理触发订阅呢
 commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 是这里呢
     this._subscribers.forEach(sub => sub(mutation, this.state))
    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
  
  
   dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    // 调用 before 时机的处理函数
    // 在 before 和 after 不存在的情况默认是 before
    try {
      this._actionSubscribers
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
    // handler 返回的肯定是一个 Promise
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
    // 调用 after 时机的处理函数
    return result.then(res => {
      try {
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      return res
    })
  }

其实还有很多没提,比如 mapXXX 等辅助函数是如何实现,如果动态添加插件,这些大家有兴趣可以去探索呢

Vue 事件机制

由Vue的API可知

vm.$on监听当前实例上的自定义事件

vm.$once监听当前实例上的事件,但是只触发一次,在第一次触发后就移除监听器

vm.$off移除自定义事件监听器

  1. 不传任何参数则移除当前实例上所有的事件监听器
  2. 如果只提供了事件,则移除该事件所有的监听器
  3. 如果同时提供了事件与回调,则只移除这个回调的监听器

vm.$emit触发当前实例上的事件

那么我们来看看源码吧

 vm._events = Object.create(null) // vm._events初始化为一个对象
 vm._hasHookEvent = false 
 // 标志位 是否监听了生命周期钩子事件 beforeCreate等 
 
  
  
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  监听事件
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
    // 在vm._events对象上记录该事件并存下事件处理函数
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
  
  // 只监听一次
  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  //移除监听
  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
    // 移除所有事件监听
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) { // 依次处理移除
      for (let i = 0, l = event.length; i < l; i++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (arguments.length === 1) {
      vm._events[event] = null // 移除特定事件监听
      return vm
    }
    if (fn) { // 移除特定事件的特定处理函数
      // specific handler
      let cb
      let i = cbs.length
      while (i--) {
        cb = cbs[i]
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1)
          break
        }
      }
    }
    return vm
  }
 // 触发事件
  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
    // toArray函数的作用时转换一个类数组对象为真正的数组
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)//去掉第一个参数(事件名)
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)// 执行事件处理函数
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }
}

浏览器 进程/线程

进程 线程
浏览器多进程
没打开一个tab 就多一个进程

browser进程 浏览器的主进程 只有一个

  1. 负责各个页面的管理,创建和销毁其他进程
  2. 网络资源的管理,下载等
  3. 负责浏览器界面显示,与用户交互。如前进,后退等

GPU进程:最多一个,用于3D绘制等

浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的
[页面的渲染,JS的执行,事件的循环]都在这个进程里
默认每个Tab页面一个进程,互不影响。
主要作用为 页面渲染,脚本执行,事件处理等

多进程的优势

  1. 防止单 page crash 影响整个浏览器
  2. 避免第三方插件crash影响整个浏览器
  3. 多进程充分利用多核优势
  4. 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

渲染进程内部是有多线程的看看哪些线程吧

1.GUI渲染线程

1.负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

2.当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

3.GUI渲染线程与JS引擎线程是互斥的  当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

2.JS引擎线程

1. 也称为JS内核,负责处理Javascript脚本程序

2. JS引擎线程负责解析Javascript脚本,运行代码。

3.GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3.事件触发线程

1.当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中

2.当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理


由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4.定时触发器线程

传说中的setInterval与setTimeout所在线程

为什么要单独的定时器线程?因为JavaScript引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。

5.异步http请求线程

在XMLHttpRequest在连接后是通过浏览器新开一个线程请求

将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

==WebWorker==

  1. 创建worker时,js引擎向浏览器申请一个子线程, 子线程完全由浏览器控制 所以不能操作dom

  2. js引擎线程与worker子线程通过特定的方式通信,需要通过序列化对象来与线程交互特定的数据

  3. JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。

WebWorker与SharedWorker

1.WebWorker只属于某个页面,不会和其他页面的Render进程(浏览器内核进程)共享

所以Chrome在Render进程中(每一个Tab页就是一个render进程)创建一个新的线程来运行Worker中的JavaScript程序。
  1. SharedWorker是浏览器所有页面共享的,不能采用与Worker同样的方式实现,因为它不隶属于某个Render进程,可以为多个Render进程共享使用

sharedWorker 由单独的进程管理, WebWorker只是属于render进程下的一个线程

css加载是否会阻塞dom树渲染?

css是由单独的下载线程异步下载的。

css加载不会阻塞DOM树解析(异步加载时DOM照常构建)

但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)

普通图层和复合图层

GPU中每个复合图层是单独绘制的  所以不相互影响


普通文档流内可以理解成一个复合图层 这里称为默认复合层,里面不管添加多少元素,其实都是在同一个复合图层中)
absolute fixed 布局 脱离文档流 还在这个复合图层



通过硬件加速,声明一个新的复合图层 他会单独分配资源, 当然也会脱离文档流,这样这个复合图层怎么改变,也不会影响默认复合层的重排重绘



如何生成新的复合图层
硬件加速

最常用的方式:translate3d、translateZ
opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)

<video><iframe><canvas><webgl>等元素
其它,譬如以前的flash插件


absolute和硬件加速的区别

absolute 虽然脱离文档流 但是仍然在默认复合层内,就算absolute中信息改变时不会改变普通文档流中的render树,但是浏览器最终绘制的时候还是会整个复合层绘制,所以absolute中信息的改变,仍然会影响整个复合层的绘制。 

硬件加速是在另一个复合层中,他的信息改变不会引起原来的复合层改变(当然,内部肯定会影响属于自己的复合层)仅仅是引发最后的合成(输出视图)


复合图层的作用

一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能量,但是不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡



Event loop


J S中分为两种任务类型:macrotask和microtask在ECMAScript中,microtask称为jobs,macrotask可称为task

macrotask 中的事件都是放在一个事件队列里面,是由事件触发线程维护
可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)

1.每一个task会从头到尾将这个任务执行完毕,不会执行其它
2.浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)


microtask 中所有微任务都是添加到微任务队列中,等待当前macrotask执行完毕后 这个队列由js引擎线程自己维护

1. 在当前task任务后,下一个task之前,在渲染之前
2.所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
3.在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

microtask:Promise,process.nextTick等
macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)


总结一下运行机制

1.执行一个宏任务栈中(没有就从事件队列中获取)
2.执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行
4. 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
5. 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

你不知道的javaScript(一)

# 第一部分 作用域和闭包

程序中的一段代码经过词法分析-->语法分析-->代码生成三个阶段。

词法分析,把由字符组成的字符串拆分成词法单元(token)

语法分析,把词法单元流组成元素逐级嵌套的树,抽象语法树

代码生成,把抽象语法树变成可执行代码

对于js代码来说,是在执行前进行编译的。

引擎:从头到尾负责js代码的执行

编译器:负责词法分析和代码生成阶段

作用域:负责维护与查找标识符

作用域嵌套

在当前作用域没找到标识符时,引擎就会逐级向外层作用域进行查找,直到找到为止或抵达最外层作用域为止(全局作用域)

在非严格模式下,若在全局作用域未找到目标变量,全局作用域就会创建一个该名称的变量并返回给引擎。

词法作用域由你写代码时将变量或函数写在哪里决定的,因此词法分析器处理代码时保持作用域不变。换句话说,就是你的函数在哪里调用,如何调用,它的词法作用域只由函数被声明时所处的位置决定。编译的词法分析阶段基本知道全部标识符在哪里以及如何声明的。从而能预测执行过程中如何对它进行查找

词法作用域就是一套关于引擎如何寻找变量以及在何处找出变量的规则,词法作用域最重要的特征是它的定义过程发生在书写阶段。

函数作用域与块作用域

函数作用域
是指属于这个函数的全部变量在整个函数中都可以使用,包括在嵌套作用域中。任意一段代码,用函数声明封装起来,可以将内部的变量函数隐藏起来,外部作用域无法访问。

函数表达式

(function foo(){

})()

函数表达式和函数声明的最重要的区别在于名称标识符绑定在何处。函数声明被绑定在所在作用域中。函数表达式被绑定在函数表达式自身的函数中。

匿名函数表达式

setTimeOut(function() {
    
},100)

匿名函数书写起来便捷但是有几个缺点
1.匿名函数在堆栈中不会显示有意义的函数,不方便调试
2. 匿名函数不方便在函数内部调用自身,只能使用过时的arguments.callee引用
3. 匿名函数省略了对于代码可读性/理解性很重要的函数名

立即执行函数

(function foo() {
    
})()

函数被包含在一对()内部,成为一个表达式,通过末尾添加上()可以立即执行该函数表达式。

块作用域
变量的声明距离使用的地方越近越好,最大限度的的本地化。

  1. try/catch,catch分句会创建一个块作用域,其中声明的变量仅在catch内部中使用。
  2. let关键字可以将变量绑在任意作用域中,通常是({...})中,换句话说,let为其声明的变量隐式的绑定块作用域
  3. const可以创建块作用域但其值是固定的之后尝试修改其值将会报错。
  4. 暂存性死区

变量提升
引擎在解释js代码之前会先进行编译,编译的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
正确的思路是 变量和函数在内的声明都会在代码被执行前先被处理。

var a = 2;js实际上会将其看成2个声明;第一个var a 第二个 a = 2; 第一个声明在编译阶段,第二个赋值声明在执行阶段。

函数和变量声明被提到作用域的最上面的过程称为提升。换句话说先有声明再有赋值。只有声明语句被提升,而赋值等其他运行逻辑会留在原地。
每个作用域都存在提升。函数声明会提升但是函数表达式却不会提升。即使是具名的函数表达式,名称标识符在赋值操作前也不能使用

函数声明和变量声明都会被提升。但是值得注意的一个细节是函数首先被提升然后才是变量。

作用域闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前作用域之外执行。
无论通过什么方式将内部函数传递到所在的词法作用域之外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

模块有两个特征:

  1. 为创建内部作用域而调用一个包装函数
  2. 保障函数的返回值必须至少包括一个对内部函数的引用

# 第二部分 this和对象原型

this 在任何时候都不会指向函数的词法作用域。在javaScript内部,作用域确实和对象一样,可见标识符相当于作用域的属性,但是作用域“对象”无法通过js代码访问,它存在与js引擎内部

this不指向函数本身和词法作用域

this是运行时绑定的并不是编写时绑定。this的值取决于函数调用的各种条件,和函数声明的位置没有关系,只取决于函数调用关系。

当函数被调用时 会创建一个活动记录,记录函数在哪里被调用(调用栈),函数调用方式,函数传入的参数,this只是记录的一个属性,会在函数的执行过程中用到

调用位置(函数被调用的方法):函数在代码中被调用的位置。

调用栈:为了到达当前执行位置所调用的所有函数

在函数的执行过程中调用位置如何决定this的绑定对象。

绑定规则:

  1. 默认绑定
    最常用的函数调用类型:独立函数调用。this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。在严格模式下绑定到undefined,否则绑定到全局对象。

  2. 隐式绑定
    调用位置是否存在上下文对象。当函数引用由上下文对象时,隐式绑定规则函数调用中的 this 绑定到这个上下文对象。

隐式丢失

function foo() { console.log( this.a );
}
var obj = { a: 2,
foo: foo };
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
bar(); //  bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
  1. 显式绑定,使用call(...)和apply(...),它们的第一个参数是一个对象,它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象。

  2. new绑定。在javaScript中,构造函数只是一些使用new操作符时被调用的函数,他们并不属于哪个类也不会实例化哪个类,实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。
    实际上不存在什么构造函数,只有函数的构造调用。

    使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行[[原型]]连接。
    3. 这个新对象会绑定到函数调用的this。
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
  3. 箭头函数,箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。

function foo() {
   // 返回一个箭头函数
    return (a) => {
    //this 继承自 foo()
    console.log( this.a ); };
}
var obj1 = { a:2 };
var obj2 = { a:3 };
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

// foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不 行!)

对象

对象可以通过文字形式和构造形式定义

文字形式可以一次定义多个属性,但是构造形式只能一个个添加

var myObj = { key: value
// ... };

var myObj = new Object(); myObj.key = value;

类型
在Js中有7个主要类型

  • string
  • boolean
  • number
  • null
  • undefind
  • symbol
  • object

js 内置对象

  • String
  • Number
  • Array
  • Function
  • Date
  • Error
  • RegExp
  • Object
    null undefind没有对应的构造形式,他们只有文字形式。

属性描述符
从ES5开始,所有的属性都具备了属性描述符

var myObject = { 
    a:2
};

Object.getOwnPropertyDescription(myObject,'a');
<!--{-->
<!--    value: 2,-->
<!--    configurable:true,-->
<!--    writeable:true,-->
<!--    enumerable:true,-->
<!--}-->

 Object.defineProperty( myObject, "b", {
         value: 2,
         writable: true,  //决定是否可以修改属性值
         configurable: true,//可配置的,只要属性是可配置的就可以通过Object.defineProperty修改特性值,禁止删除这个属性
         enumerable: true// 是否出现在对象属性枚举中
     } );

结合write:false和configurable:false来创建一个真正的常量属性(不可修改、 重定义或者删除):

禁止扩展

  1. 禁止添加新属性
var myObject = {
     a:2
};
Object.preventExtensions( myObject );
  1. 密封,实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。但是可修改属性值
Object.seal()

  1. 冻结,实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false。
Object.freeze()

对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取

当你给一个属性设置getter,setter或者两者都有时,这个属性会被定义为“访问描述符”。

var myObject = {
// 给 a 定义一个 getter 
 get a() {
   return this._a_; 
 },
// 给 a 定义一个 setter 
set a(val) {
  this._a_ = val * 2; 
  }
};
Object.defineProperty( 
    myObject, // 目标对象 
    "b", // 属性名
    {
    // 描述符
    // 给 b 设置一个 getter
    get: function(){ return this.a * 2 },
    // 确保 b 会出现在对象的属性列表中
      enumerable: true
            }
    );

存在性

in操作符会检查属性是否在对象及其[[prototype]]原型链中。hasOwnProperty只会检查属性是否在对象中,不会检查prototype链。

可枚举相当于可以出现在对象的遍历中

var myObject = { };
     Object.defineProperty(
         myObject,
    "a",
    // 让 a 像普通属性一样可以枚举
    { enumerable: true, value: 2 }
);
     Object.defineProperty(
         myObject,
        "b",
        // 让b不可枚举
        { enumerable: false, value: 3 }
);
    myObject.b; // 3
    ("b" in myObject); // true myObject.hasOwnProperty( "b" ); // true
   // .......
    for (var k in myObject) { console.log( k, myObject[k] );
}
// "a" 2

Object.keys(..) 会返回一个数组,包含所有可枚举属性。

for...of首先会向被访问对象请求迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。如果对象本身定义了迭代器的话也可以遍历对象

和数组不同,普通对象没有内置的@@iterator,所以无法完成for...of循环。当然我们可以给任何想遍历对象添加自定义@@iterator

var myObject = {
    a:2,
    b:3,
};
Object.defineProperty(myObject, Symbol.iterator,{
    configurable: true,
    enumerable: false, // 不可枚举
    writable: false,
    value: function() {
        var o = this;
        var idx = 0;
        var ks = Object.keys( o );
        return {
            next:function() {
                return {
                    value: o[ks[idx++]],
                    done: idx< ks.length,
                }
            }
        }
    }
})

原型

JavaScript中的对象都有一个[[Prototype]]内置属性,其实就是对于其他对象的引用。

Object.create() 会把新对象的[[Prototype]]设置为参数对象。

所有普通[[Prototype]]链的尽头都是Object.prototype

属性屏蔽

如果属性名 foo 既出现在 myObject 中也出现在 myObject 的 [[Prototype]] 链上层,那 么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为 myObject.foo 总是会选择原型链中最底层的 foo 属性。

如果 foo 不直接存在于 myObject 中而是存在于原型链上层时 ==myObject.foo = "bar"== 会出现的三种情况。

1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性(参见第3章)并且没 有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新 属性,它是屏蔽属性。
2. 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable:false),那么 无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会 抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
3. 
3. 如果在[[Prototype]]链上层存在foo并且它是一个setter(参见第3章),那就一定会 调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这 个 setter。

解释一下第二条

只读属性会阻止[[prototype]]链下层隐式创建同名属性。
,这个限制只存在于 = 赋值中,使用 Object. defineProperty(..) 并不会受到影响。

javaScript中只有对象

所有的函数默认都有一个prototype共有且不可枚举的属性,指向另一个对象。这个对象被称为Foo的原型

function Foo() {
    
}
Foo.prototype {}

 Foo.prototype.constructor === Foo;
 // true
 // Foo.prototype 默认(在代码中第一行声明时!)有一个公有并且不可枚举(参见第 3 章) 的属性 .constructor,这个属性引用的是对象关联的函数(本例中是 Foo)
 
 // 这是一个很不幸的误解。实际上,.constructor 引用同样被委托给了 Foo.prototype,而 Foo.prototype.constructor 默认指向 Foo。

 
 
var a = new Foo();
 a.constructor === Foo; // true
Object.getPrototypeOf(a) === Foo.protoype // true




在面向类的语言中,类可以被复制(或者说实例化)多次,就像用模具制作东西一样,实例化(或者继承)一个类就意味着“把类的 行为复制到物理对象中”,对于每一个新实例来说都会重复这个过程。

但在 JavaScript 中,并没有类似的复制机制。你不能创建一个类的多个实例,只能创建 多个对象,它们 [[Prototype]] 关联的是同一个对象。但是在默认情况下并不会进行复制, 因此这些对象之间并不会完全失去联系,它们是互相关联的。

==调用new Foo()会创建a,其中一步是给a的一个内部的[[prototype]]连接关联到 Foo.prototype 指向的那个对象。==

在javaScript中对于构造函数最准确的解释是,所有带new的函数调用

函数不是构造函数,但是当且仅当使用new时,函数调用就会变成“构造函数调用”

“constructor 并不表示被构造”。

语句 Bar.prototype = Object.create(
Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你 指定的对象(本例中是 Foo.prototype)。

function Foo(name) { 
    this.name = name;
}
Foo.prototype.myName = function() { 
    return this.name;
};
function Bar(name,label) { 
    Foo.call( this, name ); 
    this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype Bar.prototype = Object.create( Foo.prototype );
// 注意!现在没有 Bar.prototype.constructor 了 // 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() { 
    return this.label;
};
var a = new Bar( "a", "obj a" );
 a.myName(); // "a"
 a.myLabel(); // "obj a"
 
。这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能 直接修改已有的默认对象。
ES6 添加了辅助函数 Object.setPrototypeOf(..),可以用标准并且可靠的方法来修 改关联。
// ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype );

a instanceof Foo
instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数。

这个方法只能处理对象(a)和函数(带 .prototype 引用的 Foo)之间的关系

instanceof 回答
的问题是:
==在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?==

==现在我们知道了,[[Prototype]] 机制就是存在于对象中的一个内部链接,它会引用其他
对象。
通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototype]] 关联的对象上进行查找。同理,如果在后者中也没有找到需要的 引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”==。

var foo = {
something: function() {
             console.log( "Tell me something good..." );
         }
};
var bar = Object.create( foo );
bar.something(); // Tell me something good...

Object.create(..) 会创建一个新对象(bar)并把它关联到我们指定的对象(foo),这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。

我们的实现遵循的是委托设计模式(参见第 6 章),通过 [[Prototype]] 委托到 anotherObject.cool()。

玩转Webpack

依赖图的入口Entry
模块
依赖关系
依赖树
单入口
单页应用

多入口
多页应用

Output 如何将编译后的文件输出到指定目录
path参数必须是绝对路径

loader Webpack开箱即用只支持js和json两种文件类型,通过loaders去支持其他文件类型并且把他们转化成有效的模块,并且可以添加到依赖图中
本身是一个函数,接受源文件做为参数,返回转换结果

15 解析es6 react jsx
使用babel-loader .babelrc文件
preset 多个功能的集合多个plugin的集合
plugin 一个plugin对应一个功能

{
    "presets":[
    "@bable/preset-env" //解析es6 es7等语法
    "@bable/preset-react" // 解析react jsx
    ],
    plugins: [
    ],
}

16 解析css
css-loader 用于加载.css文件,并且转换成commonjs对象
style-loader 将样式通过<style>标签插入到head中

18 webpack的文件监听
文件监听是发现源代码发生变化时,自动重新构建出新的输出文件

启动webpack命令时,带上--watch 每次需要手动刷新浏览器

19 webpack的热更新及原理

webpack-dev-server
WDS 不刷新浏览器 存在内存中 不操作磁盘

webpack-dev-server --open 构建完成自动打开浏览器

20.文件指纹策略

文件指纹 打包后输出文件的文件名的后缀 -- 用于版本管理

文件指纹如何生成

Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改

Chunkhash:和webpack 打包的chunk有关,不同的entry会生成不同的chunkhash值

ContentHash :和文件的内容有关(一般css文件)
MiniCssExtraPlugin 把style标签内的css 提取成文件

图片的文件指纹设置
设置file-loader的name使用【hash】这里的hash是根据文件内容的hash,非前文的hash

21 html css js代码压缩

23 postcss 插件

24节 移动端 css px自动转换成rem

css媒体查询实现响应式布局
@media screen and (max-width:1200px){

}

rem 的定义:font-size of the root element

rem 和 px 的对比
rem 是相对单位
px 是绝对单位

25 资源内联(html js css raw-loader实现内联)
意义

代码层面:

1.页面框架的初始化脚本

2.上报相关打点

3.css 内联避免页面闪动

请求层面: 减少http 网络请求数
小图片或者字体内联(url-loader)

html 和 js 内联

raw-loader 内联html

<script >${ require('raw-loader!babel-loader! ./meta.html')}</script>

raw-loader 内联js

<script >${ require('raw-loader!babel-loader!../node_modules/lib-flexble')}</script>

css 内联
方案一 style-loader
方案二 html-inline-css-webpack-plugin

第四讲——最好、最坏、平均、均摊时间复杂度分析

最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度
最坏情况时间复杂度:在最糟糕情况下,执行这段代码的时间复杂度
平均情况时间复杂度:最好最坏情况时间复杂度都是极端情况下的代码复杂度,发生的概率并不大,为了更好的表示平均情况下的复杂度,引入平均情况时间复杂度,简称为平均时间复杂度。
计算平均情况时间复杂度时把每种情况发生的概率进去,这个值就是概率论的加权平均值,也叫做期望值,平均时间复杂度简称加权平均时间复杂度或期望时间复杂度
很多时候我们使用一个复杂度极就行了,只有同一块代码在不同情况下,时间复杂度有量级的差距,我们才会使用这三种复杂度表示法来区分。

均摊时间复杂度
摊还分析法 通过摊还分析法得到的时间复杂度,叫均摊时间复杂度
对一个数据结构进行一组连续操作中,大部分情况时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,此时,可将一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上方,而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于是最好情况时间复杂度。
均摊时间复杂度是一种特殊情况的平均时间复杂度。

前端水印

目的:信息安全,信息泄露可追踪

特点:包含一段标识信息,同时需要覆盖足够的区域

需求:

  • 作为背景图
  • 文字样式可调整

那么怎么生成自定义背景图呢???

Canvas

HTMLCanvasElement.toDataURL该方法返回一个包含图片展示的data URI

    const canvas = document.createElement('canvas');
    canvas.setAttribute('width', width);
    canvas.setAttribute('height', height);
    var ctx = canvas.getContext("2d");
    
    ctx.textAlign = textAlign;
    ctx.textBaseline = textBaseline;
    ctx.font = font;
    ctx.fillStyle = fillStyle;
    ctx.rotate(Math.PI / 180 * rotate);
    ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
    
    var base64Url = canvas.toDataURL();

SVG

SVG:可缩放矢量图形是一种基于可扩展标记语言,用于描述二维矢量图形的图形格式。使用SVG生成图片的方式和Canvas的方式类似,只是base64Url的生成方式换成了SVG

const svgStr = 
`<svg xmlns="namespace" width="${width}" height="${width}">
      <text x="50%" y="50%" dy="12px"
        text-anchor="middle"
        stroke="#000000"
        stroke-width="1"
        stroke-opacity="${opacity}"
        fill="none"
        transform="rotate(-45, 120 120)"
        style="font-size: ${fontSize};">
        ${content}
      </text>
</svg>`;
const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;

了解如何生成背景图之后,生成背景图后跟水印又有什么关系呢?开门见山的说吧,水印是可以通过在Dom节点上添加background-image属性来实现的。那么怎么给目标节点添加background-image属性呢?来看看Vue React项目中如何实现的吧。下文提到的imageURI为上文提到的base64Url哦

Vue

1.自定义指令,在需要打上水印的节点上添加v-watermarked指令

Vue.directive('watermarked', {
  bind(el, binding, vnode) {
    if (binding.value === undefined || !!binding.value) {
      el.style.backgroundImage = imageURI ;
      el.style.backgroundRepeat = 'space repeat';
    }
  },
  update(el, binding, vnode) {
    if (binding.value === undefined || !!binding.value) {
      el.style.backgroundImage = imageURI;
      el.style.backgroundRepeat = 'space repeat';
    }
  },
});

  1. 封装组件,在需要打上水印的节点外层包上
<template>
  <div
    class="cc-watermark"
    :style="{
      backgroundRepeat: 'space repeat',
      backgroundImage:imageURI,
  }">
    <slot />
  </div>
</template>

react

  1. 封装组件
    <watermark>...</watermark>
    
  2. 直接写进组件样式里面
    import styled from 'styled-components';
    const Root = styled.div`...`
    
  3. 高阶组件
    const drawPattern = (waterInfo) => {
     canvas操作同上...
     return new Promise((resolve, reject) => {
        if(window.signature) {
          resolve(window.signature);
        }
        if (process.env.NODE_ENV === 'development') {
        // 开发环境下 canvas图像信息保存在内存中
          canvas.toBlob((blob) => {
            window.signature = URL.createObjectURL(blob);
            resolve(URL.createObjectURL(blob));
          });
        } else {
          window.signature = canvas.toDataURL();
          resolve(canvas.toDataURL());
        }
      });
    }
    
    export default function (WrappedComponent) {
      return class extends Component {
        constructor() {
          super();
          this.state = {
            imageURI: '',
          }
        }
        componentDidMount() {
          drawPattern('chensen 8535').then((blob) => {
            this.setState({
              imageURI: blob,
            });
          });
        }
        render() {
          const ContainerStyle = {
            backgroundImage: `url('${this.state.imageURI}')`,
          };
          return (
          <Root style={ContainerStyle}>
            <WrappedComponent/>
          </Root>
          );
        }
      };
    }
    
    

现在你已经大概了解如何给页面打上水印了,你发现有什么问题了吗?其实这存在一个弊端,用户通过开发者工具动态更改DOM属性或者结构,就可以轻松删除掉水印。那么我们该如何阻止该行为呢?继续吧

MutationObserver

MutationObserver给开发者们提供了能在某个范围内的DOM树发生变化时作出适当反应的能力。

使用MutationObserver构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变动数组(包含一系列变动记录MutationRecord),第二个是观察器实例。MutationObserver 的实例的observe方法用来启动监听,它接受两个参数。第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变动(config)。

MutationObserver只能监测到诸如属性改变,增删子节点等,但需要注意的是对于自己本身被删除,是没有办法的,可以用过监测父节点来达到要求。

拿阔爱的立方写个栗子吧,可以在监听到用户删除style属性操作,及时恢复水印。

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    const config = {
      attributes: true, //观察受监视元素的属性值更改
      attributeOldValue: true, // 记录任何有改动的属性的上一个值
    };
    const callback = (mutationList, observer) => {
    /*mutationList包含一系列变动记录*/
      _.forEach(mutationList, (mutationRecord) => {
        const { type, attributeName } = mutationRecord;
        /*
        type 更改类型
        attributeName 返回被修改属性的属性名
        */
        if (type === 'attributes' && attributeName === 'style') {
          observer.disconnect(); //先停止监听 否则会不断触发
          const { target, oldValue } = mutationRecord;
          /*
          target为受监视元素
          oldValue被更改属性值的旧值
          */
          target.setAttribute('style', oldValue);
          observer.observe(target, config); // 修改完后恢复监听
        }
      });
    };
    Vue.directive('watermarked', {
      bind(el, binding, vnode) {
        if (binding.value === undefined || !!binding.value) {
          el.style.backgroundImage = imageURI;
          el.style.backgroundRepeat = 'space repeat';
          const om = new MutationObserver(callback);
          om.observe(el, config);
        }
      },
    });

第五讲——为什么很多编程语言中数组都从0开始编号

数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同数据类型的数据。
1.线性表就是数据排成像一条线一样的结构,每个线性表上的数据最多只有前后两个方向,其实除了数组,链表,队列,栈等也是线性结构
非线性表 比如二叉树,堆,图等,在非线性表中,数据之间并不只是简单的前后关系
2.连续的内存空间和相同类型的数据,“随机访问”,这个限制让数组的很多操作变得非常低效,比如数组中删除,插入,为了保证连续性,需要大量数据搬移

数组如何实现根据下标随机访问数组元素呢?
计算机给每个内存单元分配一个地址,计算机通过地址访问内存中的数据。当计算机需要随机访问数组中的某个元素时
寻址公式计算出元素存储的内存地址
a[i]_address = base_address+ i* data_type_size (data_type_size表示数组中每个元素的大小)

查找的时间复杂度不是O(1)
数组支持随机访问,根据下标随机访问的时间复杂度时O(1)

低效的插入和删除操作 平均时间复杂度O(n)
插入操作: 如果数组中的数据是有序的,在某个位置插入一个新的元素师,就必须按照刚才的方法搬移k之后的数据,但是如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合,在这种情况下,如果要将某个数据插入到第K个位置,为了避免大规模的数据搬移,还有一个简单的办法,就是直接将第K位的数据搬移到数组的最后把新的元素直接插入到第k的位置。利用这种处理技巧,在特定场景下,在第k个位置插入一个元素的时间复杂度将会降为O(1),这个处理**也会在快排中用到
删除操作:

访问数组的本质就是访问一段连续的内存,只要数组通过偏移计算得到的内存地址是可用的
容器能否完全代替数组。。。
为什么下标从0开始
a[i]_address = base_address+ k* data_type_size (data_type_size表示数组中每个元素的大小)
从1 开始
a[i]_address = base_address+ (k-1)* data_type_size (data_type_size表示数组中每个元素的大小) 对于CPU来说就是多了一次减法指令

Vuex 介绍大纲

==vuex状态管理==

组件层和数据层抽离,组件层单纯展示,把数据层放到全局,形成一个单一的store,
,所有的数据变更都要经过stroe来进行,形成单向数据流,使数据变化变化变得可预测

vuex 将共享的数据抽离到全局以一个单例存放.
所有修改state的操作只能通过Mutation进行,Mutation同时提供了观察者模式,供外部获取state数据的更新,所有异步接口走Action,Action内部还是通过mutation改变state,最后state变化引起页面更新。
Vuex 运行依赖vue实现的数据双向绑定,需要new Vue来实现响应化。
所以vuex 是专门为vue.js实现的状态管理库

严格模式下,不是通过mutation改变state都会报错
Vue通过Vue.use()来安装一个插件,内部会调用插件的insatll方法

安装
首先会检查是否重复安装了 然后根据vue版本实现不同的安装办法
1.0 Vue._init方法中
2.0 Vue的beforeCreate 钩子中,$options.store存在的化 说明是Root节点,若不存在则是子组件,则从父组件的option中拿 ,这样每个组件都可以通过this.$store访问全局store实例


构造函数
判断环境信息,是否支持promise,是否通过new store 等然后开始
初始化变量

_committing 用于在严格模式下是否是用mutation改变state等
_wrappedGetters 存放getter
_action // 存放actions
将dispatch/commit调用的this绑定为store实例,否则在组件内部this.dispatch时的this会指向组件的vm*/


初始化根module,同时递归注册子module,收集所有的module的getter到_wrappedGetters
installMudule(this,state,[],this._module.root)

resetStoreVM(this,state)//通过VM使store变成响应式的
plugins.forEach(plugin => plugin(this)) // 调用插件

installMudule仔细看看吧
判断是否是 根module,

如果有namespace则在_modulesNamespaceMap中注册 */
Vue.set(parentState,modulename,state)
///* 将子module设成响应式的 */
遍历注册 action mutation getters 然后递归安装子module

registerActions时,将push进_actions的action进行了一层封装(wrappedActionHandler,)所以我们在进行dispatch的第一个参数中获取state、commit等方法。执行结果res会被进行判断是否是Promise,不是则会进行一层封装,将其转化成Promise对象。
registerGetter时 就是在把module中的getter收集到 store._warppedGetters中,


resetStoreVM--resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每一个getter绑定上get方法,这样我们就可以在组件里访问this.$store.getters.test就等同于访问store._vm.test。

新建Vue对象通过vue内部的响应式实现注册store中的state和computed
对于旧的Vue对象,解除旧vm的state的引用,以及销毁旧的Vue对象 */
resetStoreVM

严格模式

strict参数可以控制Vuex的严格模式上
store._vm.watch(function() {return this._data.$$state },()=> {
    // 断言判断_committing 是否使true 从而判断是否是通过mutation改变的
})

commit
store中的commit方法中执行mutation的语句是这样的。
this._withCommit(() => {

  entry.forEach(function commitIterator (handler) {
    handler(payload)
  })
})
commit方法会根据type找到并调用_mutations中的所有type对应的mutation方法,所以当没有namespace的时候,commit方法会触发所有module中的mutation方法。再执行完所有的mutation之后会执行_subscribers中的所有订阅者。我们来看一下_subscribers是什么。


Store给外部提供了一个subscribe方法,用以注册一个订阅函数,会push到Store实例的_subscribers中

dispatch
 1. actions中取出type对应的action */
 2. 是数组则包装Promise形成一个新的Promise,只有一个则直接返回第0个 */


registerModule
动态注册 module,内部实现实际上也只有installModule与resetStoreVM两个步骤,前面已经讲过,这里不再累述。
 
resetStore,将store中的_actions等进行初始化以后,重新执行installModule与resetStoreVM来初始化module以及用Vue特性使其“响应式化”,这跟构造函数中的是一致的。

css 准备

重绘 重排

水平居中

垂直居中

BFC 如何创建块级格式化上下文 有什么用

1. 根元素
2. 浮动元素 float不为none
3. position absolute fixed
4. overflow不为visible
5. display的取值为 inline-block table-cell flex inline-flex之一的元素

用处
1. 包含浮动元素
2. 阻止父子元素的margin 折叠

对BFC规范的理解


块级格式化上下文是一个独立的渲染区域,让处于BFC中的元素与外部元素隔离,是内外元素的定位不受影响
规则:
1. 属于一个BFC

应用:
1. 阻止margin折叠
2. 可以阻止元素被浮动元素覆盖
3. 计算 BFC 的高度时,浮动子元素也参与计算
4. BFC 的区域不会与 float 的元素区域重叠
5. 文字层不会被浮动层覆盖,环绕于周围
6. 属于同一个 BFC 的两个相邻 Box 垂直排列


link @import的区别

link 是 html 方式 @import 是css 方式
link 最大限度支持并行下载 @import 过多嵌套导致串行下载 造成FOUC
link 实现早于@import @import 在老式浏览器造成样式隐藏
link 通过ref="alternate stylesheet" 指定候选样式
@import必须在样式规则之前,可以在css文件中引用其他文件
link 优于 @import

当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载
@import需要 IE5 以上才能使用

FOUC ( flash of unstyled content 文档样式短暂失效)

在用户定义样式表加载之前,浏览器采用默认样式显示文档,用户自定义样式表加载渲染好再显示新文档,造成页面闪烁

为啥要初始化css样式

因为浏览器兼容问题,不同浏览器对有些标签的默认样式不同,保证浏览器之间页面无差异

清除浮动的几种方式 各自的优缺点

1. 父级div定height
2. 结尾处加空div元素 clear:both
3. 父级div定义伪元素:after  zoom

display有哪些值

block 
inline
inline-block 像行内元素一样显示 但其内容像块状元素一样显示
table 此元素会作为块级表格来显示
inherit 规定应该从父元素继承 display 属性的值
flex
grid

css 盒模型

dom元素所采用的布局模型 通过box-sizing设定
IE W3C

IE content包括padding和border算进去

display:none visibility:hidden

联系:它们都能让元素不可见

display:none 渲染树元素消失 不占空间 不可点击,会引起重排,性能消耗较大

visibility:hidden 不会从渲染树消失 占空间 不可以点击,会引起重绘,性能消耗较小

opacity:0 不会从渲染树消失,占空间,可以点击,引起重绘,性能消耗较小

display:none,opacity :0  是非继承属性,子元素消失是因为父元素消失

visible 是继承属性,自元素消失是因为继承了父元素的visible属性,修改子元素的visible属性可以使子元素显现


css content属性

伪元素中插入内容

伪类与伪元素的区别

:单冒号标示伪类 ::双冒号标示伪元素
伪类表状态
伪元素是真的有元素


CSS中,::before 创建一个伪元素,其将成为匹配选中的元素的第一个子元素。常通过 content 属性来为一个元素添加修饰性的内容。此元素默认为行内元素

CSS伪元素::after用来创建一个伪元素,作为已选中元素的最后一个子元素。通常会配合content属性来为该元素添加装饰内容。这个虚拟元素默认是行内元素。

清除浮动防止父元素高度坍塌

1. 尾部加一个空div clear:both
2. :after或者 br content: '';clear:both;
3. 使父元素成为BFC
4. 设定高度

css 预处理器

类css语言通过webpack编译成浏览器可读的css,在这层编译上便可以赋予css更多更强大的能力

嵌套
变量
循环语句
条件语句
自动前缀
mix复用

==CSS animation 与 CSS transition 有何区别?==

transition 关注的是CSS property的变化,property值和时间的关系是一个三次贝塞尔曲线。

animation 作用于元素本身而不是样式属性,可以使用关键帧的概念,应该说可以实现更自由的动画效果。

Vue nextTick 原理实现

Vue.nextTick 延迟一个任务使其异步执行,在下一个tick时执行。

现在来看看源码
nextTick是一个立即执行函数,返回一个function
这个函数的作用是在task或者microtask中推入一个timerFunc,当前调用栈执行完以后以此执行直到执行到timerFunc,目的是延迟到当前调用栈执行完以后执行

export const nextTick = (function() {
  // 存放异步执行的回调
  const callbacks = [];
  // 一个标志位,如果timerFunc 已经被推送到任务队列中 则不会重复推送
  let pending = false;
  let timerFunc;
  // 下一个tick执行的回调,
  function nextTickHandler() {
    pending = false; // 恢复标志位 以便下次推送到任务队列
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    /*执行所有callback*/
    for (let i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

  // An asynchronous deferring mechanism.
  // In pre 2.4, we used to use microtasks (Promise/MutationObserver)
  // but microtasks actually has too high a priority and fires in between
  // supposedly sequential events (e.g. #4521, #6690) or even between
  // bubbling of the same event (#6566). Technically setImmediate should be
  // the ideal choice, but it's not available everywhere; and the only polyfill
  // that consistently queues the callback after all DOM events triggered in the
  // same loop is by using MessageChannel.
  /* istanbul ignore if */
  // 这里解释一下,一共有Promise、MutationObserver以及setTimeout三种尝试得到timerFunc的方法
  // 优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在microtask中执行,会比setTimeout更早执行,所以优先使用。
  // 如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve();
  timerFunc = () => {
    p.then(flushCallbacks);
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop);
  };
  isUsingMicroTask = true;
} else if (
  !isIE &&
  typeof MutationObserver !== 'undefined' &&
  (isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  // 新建一个textNode的DOM对象,用MutationObserver绑定该DOM并指定回调函数,在DOM变化的时候则会触发回调,该回调会进入主线程(比任务队列优先执行),即textNode.data = String(counter)时便会触发回调*/
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () => {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Techinically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks);
  };
} else {
  // Fallback to setTimeout.
  ///*使用setTimeout将回调推入任务队列尾部*/
  timerFunc = () => {
    setTimeout(flushCallbacks, 0);
  };
}
 
  return function queueNextTick(cb?: Function, ctx?: Object) {
    let _resolve;
    callbacks.push(() => {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
     pending = true;
     timerFunc();
    }
    // $flow-disable-line
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        _resolve = resolve;
      });
    }
  };
})();

数据结构与算法之美——链表

链表(Linked List)
缓存淘汰算法

  1. FIFO算法 先进先出策略
    2.最少使用策略 LFU(Least Frequently Used)
    3.最近最少使用策略 LRU(least Recently Used)
    链表与数组的比较
    1.底层存储结构 数组需要一块连续的内存空间,当内存中没有连续的足够大的存储空间时,即使内存的剩余空间总和满足,仍然会申请失败,而链表相反,他不需要连续的内存空间,它通过指针将一组零散的内存快串联起来。
    链表的结构
    1.单链表
    链表通过指针将一组零散的内存块串联起来,其中我们把内存块称为链表节点,每个结点除了存储数据之外,还需要记录链上下一个结点的位置,把记录下个结点地址的指针称为后继指针next。习惯性叫第一个结点为头节点,最后一个结点称为尾结点。头节点用来记录链表的基地址。尾节点的next指针不是指向下一个节点,而是空地址NUll
    2.双向链表,跟单链表唯一区别是尾结点指针指向链表的头节点,优点是链尾到链头比较方便,当处理的数据具有环形结构特点时,采用循环链表
    3.循环链表,每个节点有后继指针next指向后面的节点,还有一个前驱节点prev指向前面的节点
    链表中删除一个数据
  2. 删除节点中“值等于某个给定值”的节点
    2.删除给定指针指向的节点

数组的大小固定,一经声明就会申请连续的内存空间,扩容需要搬移数据
链表不需要连续的内存空间,天然支持动态扩容 更适合删除 插入操作但是查询的时间复杂度较高

基于链表实现LRU缓存淘汰算法
规则

  1. 有序单链表
  2. 越靠近链表尾部的节点是越早访问的

分析
1.不在单链表中
1.1 缓存已满,删除尾部节点,插入链表的头部、
1.2 缓存未满,插入链表头部
2.在单链表中
删除对应的节点,插入到链表头部

面经

面试回答

1.跨域

jsonp
CORS 需要服务端配合 
简单请求 
HEAD
get
post 
非简单请求
 
会先发送一个预检请求[options],返回204[请求成功但是客户端不需要更新页面] 预检通过后开始发送真正的请求
cookie需要服务端和客户端共同设置才有效,credentials为true
带了cookie之后,且后端设置指定的origin

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

Access-Control-Allow -origin 返回允许访问的域
Allow-methoh 返回允许的请求方法
access-control-allow-headers

iframe postMessage
  1. get 与post请求的区别
get 使用URL或Cookie传参,而post 放在body里面,
get (请求url的长度限制 是浏览器和服务器的限制),post 
get 明文传输,数据在地址栏上可见
在使用上的区别

本质上的区别
GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是
幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

所以get请求不应该处理增删改操作
  1. Vue 是如何做双向绑定的
普通对象
data  Object.defineProperty setter getter
需要遍历data的每个属性 给它们加上访问描述符
如果 有属性值还是对象的话 就递归添加

array 重写变异方法,里面还是调用Array.prototype的方法 只是添加了一些自定义的操作 比如 dep.notify


Proxy
不需要一个个属性添加访问描述符
有13种拦截操作
访问Proxy代理对象就可以了
也可以拦截数组操作 
避免了 使用 Vue.set(['xx'],indexOf,'xx')

  1. Vue React的区别
Vue和React 鼓励组件化应用
React: 
1. JSX JS写在一个组件文件中
2. 展示和逻辑耦合在一起了 js和jsx写在一起了

state对象, 不能直接改变,只能通过setState改变

Vue
1. 单文件组件 模版系统 HTML CSS JS写在一个文件里面
 2. 三者是分离的
2. Vue 中的data 是可以直接改变的

监听数据变化不同 

Vue getter/setter 数据劫持 更加细粒度准确知道数据变化
React 是通过比较引用的方式进行的  像shouleComponentUpdate/PureComponent

通信方式不同 
2. provider/inject
1. Vue支持自定义事件 父组件 props 传递给子组件,子组件通过事件给父组件通信

react 
代码逻辑复用不同 mixins,hoc

React组件内部实现渲染列表 条件展示 都是通过js代码控制的
Vue 组件有内置的模版

Vuex是直接修改数据 每个实例都有this.$store拿到store中的数据

Redux  是每次生成一个新的对象,然后组件要通过 connect 拿到组件的属性
  1. ==Virtural DOM==
Js和真实DOM的缓存
操作js对象比操作真实DOM 更节约性能

用js对象描述dom 结构
  1. react 的diff算法
通过深度优先遍历,当访问到一个新树的节点 就和老树的节点进行比较 ,tag 不同 就把差异(有几种差异类型,replace props reorder,text这样)存放在一个对象上,然后patch阶段就通过差异记录,修改DOM树

1. 只在同层级比较
2.class不同直接替换
3. 组件内 比较tag tag不同就直接替换

作用:通过比较两个虚拟树,创建补丁(描述改变的内容),改变真实DOM

diff 更改类型
Attr
Text
Remove
Replace

patch 通过刚才的补丁对象 对真实dom进行操作

5 ==Vuex的内部机制==

Vue.use(Vuex)
调用Vuex.install方法
通过在Vue根实例上添加$store属性然后非根实例都用$parent上¥store都可以访问到Vuex store中的状态

Vuex 
1. 监测window.Vue判断是否安装Vue ,参数中监测action mutation  是否合法
2. 初始化参数 比如 warpGetter actions mutaitons committing 这些
3. 让dispatch commit 通过bind 使这些方法内部的this指向 store实例
4. 根据调用ModuleCollection方法, options生成根module,options中的moduels生成子module,最终生成一个module树,

5. installModule,使父组件的state有指向子组件state的应用,就让Vuex实例的state 是 一个整个Vuex应用的入口
在Vuex实例上分别注册带有命名空间的actions,mutation,getters
同时子module的dispatch被改写传入的type变成namespacedType这样,变成指向Vuex实例上的namespacedAction, 函数
vuex内部也是通过vm来管理状态,
监测到state变化
严格模式 每次状态变化监测committing 整个标记是不是true 不是 true 就认为非正常操作
  1. PureComponent
1.  props state没有改变 就不会执行render
2.  默认帮我们执行了shouleComponentUpdate 若有这个 则以默认帮我们执行了shouleComponentUpdate为准

适合一些纯函数和简单组件
  1. hooks 和高阶组件
hooks使函数式组件拥有了状态
自定义hooks 实现逻辑复用
减少组件嵌套

一个组件 包括 展示 逻辑 状态

更细粒度的封装逻辑
  1. 为什么会选择 monaco editor
monaco editor
优点
1.就是 vscode 的web版本编辑 使用简单,和vscode的风格交互一致

2. 功能完备集成度高,可以说即插即用,非常适合仅需代码编辑不需要太多扩展和自定义样式的业务场景

3. 原生支持diff,typeScript 

缺点
1. 不支持移动端浏览器
2. 支持的语言种类少
3. 扩展性差

其他的代码编辑器
1. codeMirror 适合扩展性高的定制型编辑器
2.


monaco 是用worker方式 异步处理的我们的编辑器
monaco-editor-webpack-plugin
1. 自动注入 getWorkerUrl 全局变量
2. 自动处理worker 配置问题
2. 自动引入语言包和控件
传入一个组件 返回一个新的组件,新的组件里被加上了模拟器组件

10 react dnd

redux 作者写的 dnd是drag and drop 的意思

HTML5 拖放API的问题
1. 不支持移动端
2. 拖动预览问题
3. 无法开箱即用

React dnd

1. 兼容浏览器差异
2. 拖动源和目标解耦
3. 拖动预览
默认提HTML5 api 封装
4  不直接操作DOM
5. 专注拖拽,不提供现成组件,而是采用包裹使用者的组件注入props实现的

Backend是啥嘞
引入了后端的概念,提供 HTML5 拖拽后端 ,可插拔,可自定义touch mouse行为 模拟的后端实现,后端抹平浏览器差异,处理dom事件
,把dom事件转换为redux action

monitor 检测器,拖放是有状态的,monitor及时获取拖放元素的状态

backend 专注dom事件,组件关注拖动状态,connector把benkend和组件联系起来,可以让bankend获取到dom

type 定义了应用程序里支持的拖拽类型,拖放目标只处理type匹配的拖动源

只有 DragSource 注册的类型 和 DropTarget 注册的类型 完全匹配才可以drop

11 React.cloneElement React.createElement

React.createElement(
type, // 标签名字符串 或者 React组件类型
[props], // 属性
[...children] // 子组件
)

使用JSX编写的代码 会被转换成使用React.createElement创建react元素

cloneElement 以 element 为样本克隆并返回新的元素,返回元素的props是由传入的props和原有props 浅层合并后的结果 key和ref保留 新的子元素将取代现有的子元素

  1. 钉钉应用如何在手机端看到请求
1.vconsole
2. 模拟器

10 输入url 浏览器的执行过程

1. 收到输入的URL,分析协议域名端口 path
2. 解析域名 拿到主机ip 浏览器缓存,本机 hosts 路由器 dns
3. 发起请求,(TCP三次握手)
4. 后端返回响应
5. gizp 解码啥的
6. 分析响应内容 开始渲染
7. 解析html 文档生层dom树,
    异步下载css文件 构建cssom树 然后生成渲染树然后GUI线程去渲染页面

async 异步下载 异步执行 不一定保证顺序 不会阻塞dom树构建
defer 是立即下载延迟执行 等dom树构建完成再执行 保证顺序,相当于放在body底部的效果

11 如何理解原型链

每个对象有个原型链最终指向Object.prototype
但是Object.prototype.proto指向null

12 作用域链

先在当前上下文中寻找变量属性,找不到的话 就去父作用域找,最后就到全局作用域找,找不到就返回undefined
闭包 是函数+自由变量
函数的执行上下文(this, 变量对象,)中有个scope属性 里面存着作用域的Ao

13 证书???

ca 发给服务器的签名
ca = 本身信息+服务器信息+加上服务器信息的摘要用私钥加密的秘闻

14.== router底层实现==

  1. forceUpdate 和setState的区别
setState 总是会触发一次渲染,除非shouleComponentUpdate检查为false,

当props,state 深层次数据改变,或者render()依赖外部数据时,

forceUpdate将会跳过 本身组件的 shouleComponentUpdate的调用
但是子级的生命周期 和shouledComponetntUpdate还是会调用
  1. ==webpack 核心流程原理==,怎么实现模块化的,treeshaking 怎么做的?

17 ==Vue compouted 怎么实现==

18 函数柯里化

处理多参数 函数的,
接受函数的部分参数,返回一个接受余下参数的函数

解决的问题 场景
1. 参数复用
2. 延迟调用

生于函数式编程,陷于函数式编程
相对于 bind -> 箭头函数等,性能不算高的

function trueCurrying(fn,...args){
    if(args.length > fn.length) {
      return fn(...args);
    }
    return function(args2) {
      return trueCurrying(fn,...args,...args2);
    }
}

19 函数式和响应式的理解

函数式编程
1.函数是一等公民,认为和其他的变量是一样的,可以被传递,做为返回值等
函数式编程的特性:
1. 纯函数 相同输入必然得到相同输出,没有任何副作用(做了与函数本身无关的事情),不会改变外界变量,http请求,console.long等
2. 只用表达式不用语句,表达式是有返回值的,语句没有返回值

优势:
2. 更便于测试
3. 更接近于自然语言
4. 开发更快

响应式编程
面向数据流和变化传播的编程范式
用异步数据流进行编程,点击事件就是一个异步事件流,可以对他进行侦测然后进行操作

21 客户端渲染和服务端渲染

22 Vue router 和window.location.href

Vue router 不会打开一个新的页面, 不会引起页面刷新
window.location.href 是打开一个新的页面,

23== css 动画 和js动画==

1. transtion  animation 的区别

js 

25 React 16版本中生命周期的变化

unsafe_componentWillMount 
可能会首页白屏,,componentWillMount执行后,第一次渲染已经开始了, 请求还没得到响应 ,没有异步数据,首页还是白屏

服务端渲染的话 在componentWillMount 开启定时器事件监听的话,不会执行 componentWillUnmount 这样会引起内存泄露

可能还会引起发两次相同请求 ,服务端一次 客户端一次

getDerivedStateFromPorps 获取派生state 从props中,函数内禁止调用this.props 通过nextProps 和 prevProps进行比较 来返回组件的state

之前使 根据props 生成新的state, 执行回调放在componentWillReceiveProps一个生命钩子中
unsafe_componentWillReceiveProps

现在分成两个
getDerivedStateFromProps  产生派生状态
componentDidUpdated 执行回调

在组件更新前读取 DOM 元素状态

unsafe_componentWillUpdate,根据props执行一个回调,可能会被执行多次
componentDidUpdate 保证执行一次

有部分现象时在componentWillUpdate获取更新前的dom元素信息,再在 componentDidUpdate利用,
 getSnapShotBeforeUpdate 在render之前调用 保证获取到的信息更准确
 

26 createElement 和 cloneElement 的 区别

createElement是 把 根据jsx 生成 描述dom的js对象
cloneElement 是 改造自组件,添加上一些 额外的prorps

type attrs children
attrs: style value
style: style.cssText = value
value input或者textArea  直接 node.value
其他setAttribute()

28 webpack 怎么实现模块化的 treeshaking 怎么做到的

treeShaking 依赖的es6 的静态分析,
但是还有短版的
1. 不能对entry 进行treeShaking
2. 不会对异步分割出去的代码做 Tree Shaking。
Webpack 只是指出了哪些函数用上了哪些没用上
要剔除用不上的代码还得经过 UglifyJS 去处理一遍

resolve 中的 mainFields 用于配置采用哪个字段作为模块的入口描述。
//  针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
jsnext:main
// 依次降级
mainFields: ['jsnext:main', 'browser', 'main']

29 react 16 新特性,react 17 前瞻,fiber,hooks,suspense,异步渲染


30 协调算法

通过VDom 前后对比,找出变化,然后在真实dom上应用变化,这一过程叫 reconcilation

16版本以前,diff 是中序深度优点遍历整个树,收集change, 若整个树很大diff的过程就会很长占据主线程,,交互 动画等就会阻塞无响应,导致页面卡顿

stack
存在的问题
1. 并不是所有setState更新 都要马上更新真实dom显示出来 ,比如在屏幕之外的部分
2. 并不是所有的更新的优先级一样
3. 理想情况下,高优先级的操作是可以打断低优先级操作的执行的


fiber 就是把这个过程拆分,防止主线程占据时间过长
reconcilation(可中断的)
commit(不可中断的)

fieber节点
1. return 指向父节点
2. children 指向第一个子节点
3. sider指向兄弟节点

fiber 
{
    return{} // 改变的结果合并到改对象
    children{}
    sider{}
}

 
在render 就是渲染完成的生成一个 fiber tree,
同时维护 一个 workInprocess Tree来标记更新

自顶向下处理 fiber tree ,每处理一个fiber 节点都看 是否还能拥有主线程的时间,有的话 就继续处理下一个fiber节点(通过child 和 sider 指针,指向下一个待处理的fiber节点),没改动直接返回标记这个节点处理完成再返回 ,有改动就打个标记

改动添加到 effect list中 并合并到上一层父节点中
// 合并后,回到父节点Item,父节点标记完成。

执行完成后一层层向上 merge effect,所有节点已经标记完成 workInProcess tree 进入 precommiting阶段
commit 阶段 更新真实dom

更新完成后 workInprocess Tree 和真实dom的结构一致,
为了保持 fiber tree 和真实dom 保持一致就 fiber tree 与 workInprocess Tree 互换,释放内存

如果当前处理 react 渲染时间过长,到时后面的渲染卡住,所有fiber reconcilation就有了优先级策略
1. 高优先级任务可以打断低优先级
2. 当低优先级任务重写获得时间片的时候 会从新执行

componentwillrecivePorps 这些生命钩子 可能会执行多次

31 装饰器

不用于函数,是因为函数存在变量提升
装饰器一个函数,只能用于类和类的方法,不能用于函数

function (target,name,descriptor){
     // 为类/ 类的方法
}
//core-decorators.js

Mixin 模式

改造类,使类继承另一个类的方法 是对象继承的另一种方式
// 相当于一个混合类生成器,继承superclass的同时,混入foo 方法
minin = (superclass) => class extends superclass{
    foo() { // 
    console.log('foo from MyMixin');
  }
}
这种写法的一个好处是 可以调用super
避免覆盖父类中的同名写法

32 ==算法== 深度优先 广度优先

33 https 怎么做到数据包被劫持的


34 证书的是什么

CA = 服务器信息+ 证书中心的信息+ 服务器信息的摘要的私钥密文

客户端 根据相同的散列算法算出服务器信息的摘要,在用证书中心的公钥加密 比较密文是否相同

36 闭包 ,闭包的应用

闭包 是函数+ 自由变量

自由变量是指那些不是函数的形参或者函数内定义的局部变量

函数在创建的时候就会把他的上层上下文数据保存起来,哪怕是全局变量也是如此,因为函数中访问全局变量就是在访问自由变量

[[scope]]中保存着父函数的AO

即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)

37 redux 好难啊啊啊啊

38 变量提升 函数提升

块级作用域
 块级作用域存在于
 1. 函数内部
 2. {}大括号里
 
块级作用域用于在块之外无法访问块中声明的变量

在进入执行上下文时,AO 活动对象,会先处理函数声明,其次进行变量声明,如果变量声明时,已经有行参和函数定义了,就不影响这些已经定义的变量

39 异步渲染

40 hook

1. 使函数组件拥有状态
2. 使代码逻辑更加精简 不重复,本来写在componentDidMount/ componentDidUpdate 的逻辑写在一起,comoenentwillUnmount
3. 逻辑复用,减少组件嵌套
组件包括 状态 生命周期 展示 ,但是 hooks 专注与展示 逻辑复用

41 属性访问描述符

数据描述符

enumeable// 是否是可枚举的 for in object.keys JSON.stringify都获取不到
value
configuable //  是否是可删除的 delete 操作,是否是可配置的不能使用 defineProperty
writable // 是否可重写

访问描述符
get
set

45 层叠上下文

层叠等级在同一个层叠上下文中比较才有意义

拥有层叠上下文的元素比普通元素更接近于屏幕

怎么产生层叠上下文呢
1. html元素 根层叠上下文
2. position 不为static 且z-index为具体的值,auto不算具体值

两个层叠上下文,层叠等级相同,后面的覆盖前面
层叠顺序
background/border < ( index<0) < block块级盒子< float盒子< inline-block盒子< z-index 0/auto < z-index>0

46 react 中的key

保证某个元素的key在同级列表中是否唯一,
virtual DOm diff阶段,根据key 来判断这个元素是新近创建还是移动而来

47 CSRF cross-site-request-forgery 跨站请求伪造

同源策略
浏览器,从两个方面去作同源策略: 1 是dom 2请求的接口
攻击者盗用了你的身份 ,以你的名义发送恶意请求,骗取服务器执行某些操作

1. Referer 检查,检查请求来源
// referer是在发起请求前,或者window.location.href的值设为referer
2. 服务端生成 token,请求时带着cookie和token 以便服务器检查/校验

48 xhr 封装库

axios 是基于Promise实现的是,
1. 使用promise api
2. 是对XMLHttpRequest的封装,可以监测到请求的进度
3. 可设置请求超时,validateStatus, 
4. 可取消请求
5. 可拦截请求,
6. 转换请求和响应的数据 transformRequest,只能在post put patchde 这几个请求方法中,参数值要求是个函数数组,要求函数返回字符串,arraybuffer,stream
7. transformRequest 在被then和catch 处理前 转换响应数据
8. 配置灵活,创建请求实例,或者设置默认配置
9 自动转换成json格式
10 提供并发请求的接口
axios.all([getUserInfo(),getUserData()]).then(axios.spread(function(info,data){
    
})
11 体积小
12 客户端支持csrf,每次请求都带一个从cookie里拿到一个key,同源策略,假冒的网站是拿不到这个key


fetch
1. 需要自己对请求到的数据进行转换 转成text json
2. 更加底层 可以看成是一个原生的XHR 提供丰富的api
3. 关注点分离,让输入,输出,用事件跟踪状态放在一个对象里

缺点:
1. 当服务器返回400 500的错误码时,并不会reject,只有当网络错误导致请求不能完成 才会reject
2 fetch 不支持abort,不支持超时控制,setTimeout 这些控制不会阻止请求,请求还是会在后台进行,浪费流量
3. 默认跨域不带cookie 需要带上 fetch(url,{cred: 'include'})

49 尾调用优化,只在严格模式下有效

尾调用是函数式编程的一个重要的概念,当函数执行的最后一个是返回另外一个函数的调用。

调用栈:函数执行就会往调用栈中push一条记录,执行结束就弹出

栈桢: 函数调用时产生的空间

调用帧
当前帧

当函数A执行的时候就会往调用栈中push一个记录,此时叫调用帧,内部调用另外一个函数B,就进入那个函数B的上下文,那个函数叫当前帧,因为函数A执行的最后一句是return,所有不需要保留函数A的调用栈,就用B替换掉A,减少调用栈的大小

只要直接用内层函数的调用记录取代外层函数的调用记录


50 typed Array 类型化数组 Int8Array

ArrayBuffer DataView TypedArray 为javaScript操作二进制数据提供了便利

ArrayBuffer 二进制数据缓冲区 一块内存 存储二进制数据的内容 不可以直接读写 

需通过 TypedArray ,DataView读写 将缓冲区数据表现为特定的格式

TypedArray  Int8Array[表示每个元素存储一个字节]  Int32Array 

TypedArray对象 是描述一个底层二进制数据缓冲区的内容的 类似数组视图

TypedArray 数组内成员是同一个类型,且完全连续无空位,本身不存储真实数据 是展示视图 数据存在ArrayBuffer中,适合处理简单二进制数据,复杂的数据还是适合由DataView处理

TypeArray构造器 是所有类型化数组构造器的原型 没有全局的TypeArray和TypeArray属性

只能通过使用类似Object.getPrototypeOf(Int8Array.prototype)的方式进行访问.

所有类型化数组构造器都会继承TypedArray构造器上的通用的属性和方法
所有类型化数组构造器的原型都以TypedArray.prototype为原型

当创建一个TypedArray实例(例如:Int8Array)时,一个数组缓冲区将被创建在内存中,如果ArrayBuffer对象被当作参数传给构造函数将使用传入的ArrayBuffer代替。缓冲区的地址被存储在实例的内部属性中,所有的%TypedArray%.prototype上的方法例如set value和get value等都会操作在数组缓冲区上。

51 MVVM 和 MVC 的区别

MVC

Model 存着数据和数据处理逻辑
View 是视图展示层
controller  处理view的用户交互,调用model层的方法改变数据,数据改变,Model通知view更改
model 通知view 是观察者模式

其实视图层完全有处理用户交互的能力,所有的事件处理逻辑含在controller里面 ,就很臃肿,而且无法复用

MVVM

view 视图层
model 数据层
viewModel是最核心的地方

view和model 相互无感知


52 children.map

React.Children.map(children,function[(thisArg)]) 
children里的每个子节点都调用一遍函数,并将 this 设置为 thisArg

53 TypeScript

是 JavaScript 的超集 给 js 的生态加了类型机制并最终把代码编译为js代码

javaScript是弱类型语言,变量的类型具有动态性,只有执行的时候才能确定变量的类型,
TypeScript的类型机制杜绝了由变量类型的误用问题
静态分析,自动编译工具的帮助,在编写代码时减少错误

增大了开发人员的学习曲线,增加了设定类型的开发时间

增强了代码的维护性和健壮性

interfase test{
     destroy(): void;
}

类型声明文件怎么写的

声明文件 xxx.d.ts 存放当前项目中,建议和其他 *.ts 都存放在 src 目录下(没有生效,可检查 tsconfig.json 中的 file、include、exclude 等配置);

为了便于自己和他人,请将声明文件和 npm 包绑定在一起
npm包 检查 package.json types字段 

npm 包声明文件和其有一定区别。

不使用 declare 声明全局变量,就只是声明一个普通变量(局部变量);
声明文件中使用 export 导出;
使用文件用 import 导入然后使用,这个和 ES6 一样(无学习成本);

decaler module 'dom-util'{
    <!--type text = (test:string) =>  void-->
    <!--export default waterMark;-->
    export function(text:string):void//  类型声明
    export function parseUrl(url?: string): Location
}

54 技术选型

1. 社区 react的社区更大 有更多的工具 包啥的
2. Vue 有官方维护的全家桶 react 没有
3.主要是看 团队成员喜欢擅长哪个把 毕竟 项目的开发速度与质量还是要保证的

react
1.学习曲线
更庞大的社区,大量的工具和包。
3 成熟度 
facebook团队维护 13年发布拥有强大背景的公司支持,更加安全的未来和稳定。
4. React不具有实际的路线图,基于RFC规则

vue
1.状态管理路由管理这些是官方维护的,react 需要去自己折腾
2.更快的上手,FED(前端开发)和BED(后端开发)都会在Vue代码中感觉很自然,速度很快
4.Vue的高级路线图 github上 可以找到 在博客上
可用的插件和工具更少,社区更小。

55 ==react的事件机制==

56 babel-polyfill vs babel-runtime

babel-polyfill
babel只负责语法转换,例如把es6的语法转成es5 ,但是有对象,方法 本身浏览器不支持
比如:
1. 全局对象: weakMap Promise等
2. 全局静态函数等: Array.form等
3. 实例的方法: Array.prototype.includes等
需要引入 abel-polyfill 来模拟实现

引入babel-polyfill会有两个副作用
1. 引入新的全局对象,比如Promise、WeakMap等。
2. 修改现有的全局对象,比如修改Array.prototype


开发库,工具 优先考虑babel-runtime
babel-runtime 将开发者依赖的内置的全局对象,抽取成单独的模块,并通过模块导入的方式引入,避免了对全局作用域的修改(污染)。

babel-plugin-transform-runtime 主要做了三件事
1. core.js : 内置的全局对象 通过从babel-runtime/core.js映射到相应模块中
2. helper: 内联的工具的消除,从babel-runtime/helpers中导入
3. regenerator 遇到async/generator函数 则自动导入bable-runtime/regenerator模块

虽然避免了修改全局对象,但是也引入了不少冗余的导入代码。

babel-register

它的原理是通过改写 node 本身的 require,添加钩子,然后在 require 其他模块的时候,就会触发 babel 编译。
你引入require('babel-register')的文件代码,是不会被编译的

是一个编译器,实时编译,不需要输出文件,执行的时候再去编译。所以它很适用于开发。

57 怎么实现登陆

cookie session
1.当客户端把身份信息传给服务端通过身份认证后,服务端就会生成身份认证相关的session数据保存在内存或者内存数据库。并将对应的 sesssion_id返回给client,client会把保存session_id(可以加密签名下防止篡改)在cookie。此后client的所有请求都会附带该session_id(毕竟默认会把cookie传给server)以确定服务端是否存在对应的session以检验登陆状态
优点
相比JWT,最大的优势就在于可以主动清除session了
缺点
如果是分布式部署,需要做多机共享session机制,实现方法可将session存储到数据库中或者redis中

基于cookie 的机制很容易被 CSRF

jwt json web token

它定义了一种紧凑且独立的方式,可以将各方之间的信息作为JSON对象进行安全传输。该信息可以验证和信任,因为是经过数字签名的。

JWT基本上由.分隔的三部分组成,分别是头部,有效载荷和签名。 
Header.Payload.Signature
Header是固定的 {
    type: 'JWT',
    alg: 采用的hash算法
}
payload 部分是可以被加密的,真实存储我们需要传递的信息的部分

Signature就是会header和payload部分进行签名保证token在传输的过程中不被串改,但是为了加密,除了header和payload字段外 还有一个密钥字段

客户端拿到token后存到localStorage 或者 sessionStorage中
下次请求时放到header的auth字段

无状态JWT,只有等到过期才可销毁

58 cookie localStorage sessionStorage

cookie 大小只有几k 存在过期时间  请求时可以带上

localStorage 大小是5M 存在本地 持久存储 除非手动清楚  仅在客户端中保存,不参与和服务器的通信
在同一个浏览器内,同源文档之间共享 localStorage 数据,可以互相读取、覆盖。

sessionStorage 大小也是5M 会话期间有效 会话结束清除仅在客户端中保存,不参与和服务器的通信
,只有同一浏览器、同一窗口的同源文档才能共享数据。

59 Session

Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。

60 物理像素 逻辑像素

物理像素
设备屏幕实际拥有的像素点,屏幕的基本单元,是有实体的。

逻辑像素
设备独立像素,可以理解为反映在CSS/JS程序里面的像素点,也就是说css像素是逻辑像素的一种。

设备像素比 Device Pixel Ratio
一个设备的物理像素与逻辑像素之比。

使用4个乃至更多物理像素来渲染1个逻辑像素

window.devicePixelRatio

61 发布第三方包有那些考量

jsnext:mian

有es5 版本 和es6 版本 方便使用tree shaking
内建 ts 类型 声明文件 不需要使用者再去写类型声明文件了

想要 发布到 Npm 上去的代码保持和源码的目录结构一致,那么用 Webpack 将不在适合
像 Lodash 这样的工具函数库在项目中可能只用到了其中几个工具函数,如果所有工具函数打包在一个文件中,
Webpack 适合于构建完整不可分割的 Npm 模块。



output.libraryTarget 配置以何种方式导出库。
output.library 配置导出库的名称。
output.libraryExport
配置要导出的模块中哪些子模块需要被导出。。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义。

62 ==Vue keep-alive怎么实现的==

63 单元测试 mocha

1. 测试脚本要与源码脚本同名 xx.test.js
2. describe 测试套件 it 测试用例

var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函数的测试',function() {
it('1+1',function() {
    expect(add(1+1)).to.be.equal(2);
})
})

所谓"断言",就是判断源码的实际执行结果与预期结果是否一致,

mocha add.test.js

Mocha默认运行test子目录里面的测试脚本。Mocha默认只执行test子目录下面第一层的测试用例,不会执行更下层的用例。

mocha --recursive test子目录下面所有的测试用例----不管在哪一层----都会执行。

Mocha允许在test目录下面,放置配置文件mocha.opts

--compilers js:babel-core/register参数指定测试脚本的转码器。

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。
-t或--timeout参数指定超时门槛。

it块执行的时候,传入一个done参数,当测试结束的时候,必须显式调用这个函数,告诉Mocha测试结束了。

Mocha在describe块之中,提供测试用例的四个钩子:before()、after()、beforeEach()和afterEach()。它们会在指定时间执行。

it('异步请求应该返回一个对象', function() {
  return fetch('https://api.github.com')
    .then(function(res) {
      return res.json();
    }).then(function(json) {
      expect(json).to.be.an('object');
    });
});

Mocha内置对Promise的支持,允许直接返回Promise,等到它的状态改变,再执行断言,不必显示done()

64 scope hoisting 作用域提升 webpack

1. 代码体积更小,因为函数声明存在大量的代码
2. 代码运行时因为创建的函数作用域变少 内存开销变小

现象是  函数声明由2个变成1个,b中的代码直接被注入到a中

实现原理: 分析模块之间的依赖关系,尽可能把分散的代码合并,前提是不能造成冗余代码,所有只有那些被引用到一次的模块才会被合并

需要配合一个插件
new ModuleConcatenationPlugin()

Webpack plugin

Webpack 事件流

Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果,插件就像是一个插入到生产线中的一个功能,在特地的时机对生产线上的资源进行处理。Webpack 通过 Tapable 来组织这条生产线。Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到生产线中,去改变生产线的运作。Webpack的核心功能,是抽离成很多个内部插件来实现的

插件

那么怎么样的一个东西可以称之为 webpack 插件呢?一个完整的 webpack 插件需要满足以下几点规则和特征:

  • 一个具名 JavaScript 函数。
  • 是一个独立的模块。
  • 模块对外暴露一个 js 函数。
  • 函数的原型 (prototype) 上定义了一个注入 compiler 对象的 apply 方法。
  • apply 函数中需要有通过 compiler 对象挂载的 webpack 事件钩子,钩子的回调中能拿到当前编译的 compilation 对象,如果是异步编译插件的话可以拿到回调 callback。
    完成自定义子编译流程并处理 complition 对象的内部数据。
    如果异步编译插件的话,数据处理完成后执行 callback 回调。

在开发 Plugin 时最常用的两个对象就是 Complier 和 Compilation,它们是 Plugin 和 Webpack 之间的桥梁

Compiler

Compiler 对象包含了 Webpack 环境所有的配置信息,包含options,loaders,plugins 这些信息,这个对象在Webpack启动时候被实例化,它是全局唯一的,可以简单地把它理解为Webpack实例。

Compilation

Compilation 对象包含了当前的模块资源,编译生成资源,变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 也提供了很多事件回调供插件拓展,通过 Compilation 也能读取到 Compiler 对象。

  • modules 记录了所有解析后的模块
  • chunks 记录了所有chunk
  • assets记录了所有要生成的文件

Compiler 和 Compilation 的区别在于:Compiler 代表了整个Webpack从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。

Tapable

Webpack 中最核心的负责编译的 Compiler 和负责创建bundles 的 Compilation 都是 Tapable 的实例。 Tapable包暴露出很多钩子函数。
核心代码

class SyncHook{
    constructor(){
       this.hooks = [];
     }
    tap(name,fn){
        this.hooks.push(fn);
    }
    call() {
        this.hooks.forEach(hook => hook(...arguments));
    }
}

在Webpack hook 上的所有钩子都是Tapable的示例,所以我们可以通过tap方法监听事件,使用call方法广播事件。

HTTP

HTTP 超文本传输协议

基于请求和响应 无状态传输协议 应用层协议

HTTP1.1

  1. 缓存处理
在http1.0 中主要使用header中的if-modified-since,expires 作为缓存判断的标准

HTTP1.1则引入了更多的缓存控制策略
例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  1. 长链接
Connection:keep-alive;
一个TCP连接上可以传送多个Http请求和响应
减少建立和关闭连接的消耗和延迟

在一定程度上弥补了Http1.0 每次请求建立连接的缺点

  1. 错误通知的管理
新增24个错误状态码
410(Gone)表示服务器上的某个资源被永久性的删除
409

HTTPS

1. 需要到CA申请证书
2. HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容经过加密
3.  HTTP和HTTPS采用的连接方式不一样 端口也不一样 HTTP 80 HTTPS 443

SPDY HTTP1.x的升级

降低延迟 多路复用通过多个请求stream共享一个tcp连接

请求优先级 在连接共享的基础上有可能会导致关键请求的阻塞延迟,在每个request设置优先级,这样重要的请求就会得到优先响应

header 压缩 HTTP1.x的header 采用合适的压缩算法可以减小包的大小和数量

服务端推送

HTTP
SPDY
TLS/SSL
TCP

HTTP2.0 是 SPDY的升级版
HTTP2.0 和 SPDY 区别


1. HTTP2.0 支持明文传输 而SPDY 强制使用HTTPs
2.头部压缩算法不同
HTTP2.0(HPACK)
SPDY(DEFLATE)

HTTP2.0 与HTTP1.x的新特性

1. 新的二进制格式
HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

2. 多路复用
即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
3. header压缩 
对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
4.服务端推送 
同SPDY一样,HTTP2.0也具有server push功能。

==HTTP2.0多路复用==和HTTP1.x的长链接有什么区别

HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;

HTTP/1.1 Pipeling解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;

HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行

附注


1.服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。

2.
为什么需要头部压缩?假定一个页面有100个资源需要加载(这个数量对于今天的Web而言还是挺保守的), 而每一次请求都有1kb的消息头(这同样也并不少见,因为Cookie和引用等东西的存在), 则至少需要多消耗100kb来获取这些消息头。HTTP2.0可以维护一个字典,差量更新HTTP头部,大大降低因头部传输产生的流量

3.HTTP2.0多路复用有多好?HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 ==TCP 慢启动==。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。

HTTP2.0 多路复用

含义

  • HTTP2 的 TCP connection一旦建立,后续请求以stream方式发送
  • stream的组成单位是frame(二进制帧) header frame data frame
  • 请求头HEADERS Frame组成了resquest,和返回头 header frame, data frame组成一个 response ,request和response组成一个stream

HTTP1.x的keep-alive 可以解决TCP复用问题 但是没办法处理请求阻塞的问题

所谓请求阻塞是指一条TCP的connection在同一时间只能允许一个请求经过,后续请求想要复用这个tcp连接只能等前一个请求完成才行
之所以这样,HTTP1.x需要每条请求都是可识别,按顺序发送,否则server就无法判断该相应哪个具体的请求

多路复用是指 在同一域名下一旦TCP 连接建立起来,每个请求以stream方式传输,每个stream都有唯一标识,connection一旦建立,后续的请求都可以复用这个connection并且可以同时发送。服务端根据stream 唯一标示来处理相应的请求

多路复用就不会关闭了么

多路复用使用的同一个TCP的connection会关闭么

关闭时机

1)用户离开这个页面。

2)server主动关闭connection。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.