Vue2初始化做了些算了

Vue._init()

首先会处理组件配置挂载到vm.$options下(性能优化,打平配置对象上属性减少运行时原型链的查找,提高执行效率),若是根组件则会将全局配置合并到根组件的局部配置上。(PS:组件选项合并发生在3处)

  • 1.Vue.component(CompName, Comp) ,合并Vue内置全局组件和用户自己注册的全局组件,最终会到全局的components里
  • 2.{ components: { xxx } } ,局部注册,执行render时会做配置合并,最终体现在组件的局部配置上
  • 3.即这里的根组件情况,即Vue_init()里

重点

1
2
3
4
5
6
7
8
9
10
11
12
13
initLifecycle(vm) //组件关系属性初始化
initEvents(vm) //初始化自定义事件
initRender(vm) //初始化插槽,获取`this.$slots`,定义`this._c`即`createElement`方法,平时的`h函数`
callHook(vm, 'beforeCreate') // 执行生命周期 beforeCreate
initInjections(vm) //解析`inject`选项,得到 { key: val } 的对象,并做响应式处理
initState(vm) //响应式原理核心, 处理 `props`、`methods`、`computed`、`data`、`watch`等
initProvide(vm) //处理`provide`选项,after data/props
callHook(vm, 'created') // 执行生命周期 created

// 如果存在el选项,则自动执行$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
  • initLifecycle 组件关系属性初始化
  • initEvents 初始化自定义事件 (PS:考点<comp @click="xxx" /> 这边的监听其实是子组件自己监听,然后再通过父组件updateComponentListeners去触发,用的是$emit$on)
  • initRender 初始化插槽,获取this.$slots,定义this._ccreateElement方法,平时的h函数
  • ==callHook(vm, 'beforeCreate') 执行生命周期==
  • initInjections 解析inject选项,得到 { key: val } 的对象,并做响应式处理
  • initState 响应式原理核心, 处理 propsmethodscomputeddatawatch
  • initProvide 处理provide选项,after data/props
  • ==callHook(vm, 'created') 执行生命周期==

问:可以在beforeCreate内访问数据吗?
答:不行,数据是在initState处理的,最早拿到需要在created

Vuex的单向数据流

Vuex是通过单项数据流来实现双向数据绑定
单项流程:
1.state会显示到view中
2.view中触发action执行操作(如果是同步操作则可能会在action中触发mutaction)
3.mutaction同步修改state
4.state修改完后,会触发依赖从而修改render

DOM事件级别

DOM事件分为

  • DOM0级
  • DOM2级
  • DOM3级

==DOM0级==
DOM0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的

1
<div id="btn" onclick="fn()">test</div>

问:此处fn应该不应该加()
答:若在js中使用事件赋值,则不加(),如

1
document.getElementById("btn").onclick=fn

若在html中onclick赋值,则加(),如

1
<div id="btn" onclick="fn()">test</div>

==DOM2级==
DOM2级事件在DOM0级事件的基础上弥补了一个处理程序无法同时绑定多个处理函数的缺点,允许给一个处理程序添加多个处理函数

1
2
3
4
5
6
7
8
9
<div id="btn" onclick="fn()">test</div>

<script>
var btn = document.getElementById('btn');
function fn() {
alert('Hello World');
}
btn.addEventListener('click', fn, false);
</script>

问:addEventListener的第三个参数作用?
答:true表示在捕获阶段触发,false表示在冒泡阶段触发

==DOM3级==
DOM3级事件在DOM2级事件的基础上添加了更多的事件类型

  1. UI事件,当用户与页面上的元素交互时触发,如:load、scroll
  2. 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
  3. 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
  4. 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
  5. 文本事件,当在文档中输入文本时触发,如:textInput
  6. 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
  7. 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
  8. 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

事件流

DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;

(2)目标阶段:真正的目标节点正在处理事件的阶段;

(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。

事件捕获:由外到内
事件冒泡:由内到外

事件代理

  • 优点:减少内存消耗,提高性能

推荐阅读:深入理解DOM事件机制

事件循环EventLoop

Chrome源码中,它会开启一个for(;;)的无限循环,此循环每次从消息队列(渲染主线程)中取出第一个任务执行,而其他线程只需要在将任务加到消息队列末尾。(回调事件其实也就是把回调函数封装成任务再添加到消息队列的末尾)
过去把消息队列分为宏队列和微队列,但是此方案已经无法满足当前复杂环境的情况,开始有了细微变化。
根据W3C的官方解释,一个事件循环有一个或多个任务队列,任务队列是一组任务。每一个事件循环都有一个微任务队列(优先度最高)。每个任务必须指定对应的任务源(即同类型的任务在必须在同一个任务队列)。也可通过任务队列的合并,来合并任务源(即不同类型任务也可在同一任务队列)。具体调用顺序还是和以前的宏任务、微任务相似,只是不同的任务队列都有不同的优先级。

如何理解JS的异步?

这是因为JS运行在浏览器的渲染主线程中,然而渲染主线程只有一个,但是要处理许多工作,例如页面渲染、执行js等。
如果渲染主线程使用同步的方式来实现,那就会造成线程的阻塞,后续的任务无法执行,造成卡死,所以浏览器主线程采用异步的方式来避免这种情况,主线程将任务交给其他线程去处理,其他线程处理完成后再将事先传递的回调函数封装成任务添加到消息队列的末尾,再由主线程执行。这样的异步环境下可以最大化的保证单线程的流畅运行。

浏览器渲染原理

渲染器主线程:

  1. 解析HTML解析CSS,生成DOM TreeCSSOM Tree
  2. 合成Render Tree
  3. 根据Render Tree生成Layout Tree
  4. 浏览器根据Layout Tree划分图层(Layer)
  • 图层根据xxx上下文(z-indextransformopacity等)
  • will-change影响浏览器分层策略,权重最大
  • 分层并不是越多越好!
  1. 绘画Paint,主线程 为每个图层生成 指令集 ,交给 合成线程 去处理
    合成线程:
  2. 将每个图层 分块 处理
  3. 分块 的图层进行 光栅化 ,优先处理靠近视口的块
  • 光栅化 结束后,就是位图信息(包含点位和颜色)
  1. 画,合成线程拿到位图信息,生成指引(quard)交给GPU进程,GPU进程最终再画出来

webpack简易流程

1.生成chunk文件表
  入口文件进入
       |
  判断文件是否在(chunk表中) --若在表中则跳过
       |
  若不在表中,读取文件,解析成AST语法树
       |
  提取AST中的require,收集dependencies依赖
       |
  替换require为__webpack_require__,替换依赖路径,然后录入chunk表
       |
  递归读取依赖表中的内容,若chunk表中已存在则跳过
       |
  最后得到chunk表(key为路径,value为内容)
2.通过eval或者其他方式把chunk文件表中的内容生成文件内容
3.根据chunk表生成对应的assets(具体文件),然后生成chunkhash(根据文件内容生成hash)
4.合并所有的chunk,生成bundle(具体文件),并获取文件hash(总文件hash)

原型和原型链

  • prototype和__proto__的关系

举例1:

1
2
3
4
5
6
7
8
9
10
------------  prototype  --------------
| Function | <--------- |new Function|
------------ (原型) --------------
^ |
| |
| __proto__ |
| (隐式原型) |
------------------ |
| 自定义函数(对象) | <------------
------------------ new(实例化)

举例2:

1
2
3
4
5
6
7
8
9
10
-----------------  prototype  --------------
| 自定义函数的原型 | <--------- | 自定义函数 |
----------------- (原型) --------------
^ |
| |
| __proto__ |
| (隐式原型) |
------------ |
| 自定义对象 | <---------------------
------------ new(实例化)
  • ObjectArrayNumberFunction等函数的__proto__都是Function的原型
  • Function原型__proto__Object原型
  • Object原型__proto__null

new 的实现

1
2
3
4
5
6
function _new(f, ...args) {
const obj = {}
obj.__proto__ = f.prototype
const rst = f.apply(obj, args)
return typeof rst === 'object' || typeof rst === 'function' ? rst : obj
}
1.新建一个空对象 `{}`
2.将`f(构造函数)`的原型赋给空对象的`隐式原型__proto__`
3.修改`f(构造函数)`的`this`指向,并执行`f函数`
4.判断结果rst,若是对象或者函数,则返回rst,否则返回obj

发布订阅模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 发布订阅模式
class EventEmitter {
constructor() {
// 事件对象,存放订阅的名字和事件 如: { click: [ handle1, handle2 ] }
this.events = {}
}
// 订阅事件的方法
on(eventName, callback) {
if (!this.events[eventName]) {
// 一个名字可以订阅多个事件函数
this.events[eventName] = [callback]
} else {
// 存在则push到指定数组的尾部保存
this.events[eventName].push(callback)
}
}
// 触发事件的方法
emit(eventName, ...rest) {
// 遍历执行所有订阅的事件
this.events[eventName] &&
this.events[eventName].forEach(f => f.apply(this, rest))
}
// 移除订阅事件
remove(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(f => f != callback)
}
}
// 只执行一次订阅的事件,然后移除
once(eventName, callback) {
// 绑定的时fn, 执行的时候会触发fn函数
const fn = () => {
callback() // fn函数中调用原有的callback
this.remove(eventName, +++===) // 删除fn, 再次执行的时候之后执行一次
}
this.on(eventName, fn)
}
}

script中defer和async的区别

  • 对于defer,我们可以认为是将外链的js放在了页面底部。js的加载不会阻塞页面的渲染和资源的加载。不过defer会按照原本的js的顺序执行,所以如果前后有依赖关系的js可以放心使用。

  • 对于async,这个是html5中新增的属性,它的作用是能够异步的加载和执行脚本,不因为加载脚本而阻塞页面的加载。一旦加载到就会立刻执行在有async的情况下,js一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果js前后有依赖性,用async,就很有可能出错

推荐阅读:script中defer和async的区别