vue自定义实现

# 简介

手写vue2核心原理:

  • Vue2响应式原理,模板编译原理,虚拟DOM原理,vue初渲染流程
  • Vue2中生命周期原理,mixin原理,依赖收集WatcherDep原理。
  • 手写computedwatch原理。异步更新原理
  • 手写Vue2中组件渲染原理 Vue.extend原理、Vue2diff算法

# 搭建开发环境

# 1.什么是Rollup?

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码, rollup.js更专注于Javascript类库打包 (开发应用时使用Wwebpack,开发库时使用Rollup

# 2.环境搭建

安装rollup环境

npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D
1

rollup.config.js文件编写

import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
    input: './src/index.js',
    output: {
        format: 'umd', // 模块化类型
        file: 'dist/umd/vue.js', 
        name: 'Vue', // 打包后的全局变量的名字
        sourcemap: true
    },
    plugins: [
        babel({
            exclude: 'node_modules/**'
        }),
        process.env.ENV === 'development'?serve({
            open: true,
            openPage: '/public/index.html',
            port: 3000,
            contentBase: ''
        }):null
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

配置.babelrc文件

{
    "presets": [
        "@babel/preset-env"
    ]
}
1
2
3
4
5

执行脚本配置

  "scripts": {
    "dev": "rollup -c -w",
    "build": "rollup -c",
    "start": "cross-env ENV=development rollup -c -w"
  },
1
2
3
4
5

# 响应式原理

MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

导出vue构造函数

import {initMixin} from './init';

function Vue(options) {
    this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;
1
2
3
4
5
6
7

init方法中初始化vue状态

import {initState} from './state';
export function initMixin(Vue){
    Vue.prototype._init = function (options) {
        const vm  = this;
        vm.$options = options
        initState(vm)// 初始化状态
    }
}
1
2
3
4
5
6
7
8

根据不同属性进行初始化操作

export function initState(vm){
    const opts = vm.$options;
    if(opts.props){
        initProps(vm);
    }
    if(opts.data){
        initData(vm);// 初始化data
    }
    if(opts.computed){
        initComputed(vm);
    }
    if(opts.watch){
        initWatch(vm);
    }
    if(opts.methods){
        initMethod(vm);
    }
}
function initProps(){}
function initData(){}
function initComputed(){}
function initWatch(){}
function initMethod(){}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 1.初始化数据

import {observe} from './observer/index.js'
function initData(vm){
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function' ? data.call(vm) : data;
    observe(data);
}
1
2
3
4
5
6

# 2.递归属性劫持

class Observer { // 观测值
    constructor(value){
        this.walk(value);
    }
    walk(data){ // 让对象上的所有属性依次进行观测
        let keys = Object.keys(data);
        for(let i = 0; i < keys.length; i++){
            let key = keys[i];
            let value = data[key];
            defineReactive(data,key,value);
        }
    }
}
function defineReactive(data,key,value){
    observe(value);
    Object.defineProperty(data,key,{
        get(){
            return value
        },
        set(newValue){
            if(newValue == value) return;
            observe(newValue);
            value = newValue
        }
    })
}
export function observe(data) {
    if(typeof data !== 'object' || data == null){
        return;
    }
    return new Observer(data);
}
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

# 3.数组方法的劫持

import {arrayMethods} from './array';
class Observer { // 观测值
    constructor(value){
        if(Array.isArray(value)){
            value.__proto__ = arrayMethods; // 重写数组原型方法
            this.observeArray(value);
        }else{
            this.walk(value);
        }
    }
    observeArray(value){
        for(let i = 0 ; i < value.length ;i ++){
            observe(value[i]);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

重写数组原型方法

let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = [
    'push',
    'pop',
    'shift',
    'unshift',
    'reverse',
    'sort',
    'splice'
];
methods.forEach(method => {
    arrayMethods[method] = function (...args) {
        const result = oldArrayProtoMethods[method].apply(this, args);
        const ob = this.__ob__;
        let inserted;
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                inserted = args.slice(2) 
            default:
                break;
        }
        if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
        return result
    }
})
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

增加__ob__属性

判断一个对象是否被观测过看他有没有 __ob__这个属性; 及再array重写中设置调用方法;

class Observer { 
    constructor(value){
        Object.defineProperty(value,'__ob__',{
            enumerable:false,
            configurable:false,
            value:this
        });
        // ...
    }
 }
1
2
3
4
5
6
7
8
9
10

给所有响应式数据增加标识,并且可以在响应式上获取Observer实例上的方法

# 4.数据代理

function proxy(vm,source,key){
    Object.defineProperty(vm,key,{
        get(){
            return vm[source][key];
        },
        set(newValue){
            vm[source][key] = newValue;
        }
    });
}
function initData(vm){
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function' ? data.call(vm) : data;
    for(let key in data){ // 将_data上的属性全部代理给vm实例
        proxy(vm,'_data',key)
    }
    observe(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 模板编译

Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = options;
    // 初始化状态
    initState(vm);
    // 页面挂载
    if (vm.$options.el) {
    	vm.$mount(vm.$options.el);
    }
}
Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // 如果没有render方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有el
        if (!template && el) {
        	template = el.outerHTML;
        }
        const render= compileToFunctions(template);
        options.render = render;
    }
}
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

template编译成render函数

# 1.解析标签和内容

const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;  
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
function start(tagName,attrs){
    console.log(tagName,attrs)
}
function end(tagName){
    console.log(tagName)
}
function chars(text){
    console.log(text);
}
function parseHTML(html){
    while(html){
        let textEnd = html.indexOf('<');
        if(textEnd == 0){
            const startTagMatch = parseStartTag();
            if(startTagMatch){
                start(startTagMatch.tagName,startTagMatch.attrs);
                continue;
            }
            const endTagMatch = html.match(endTag);
            if(endTagMatch){
                advance(endTagMatch[0].length);
                end(endTagMatch[1]);
                continue;
            }
        }
        let text;
        if(textEnd >= 0){
            text = html.substring(0,textEnd);
        }
        if(text){
            advance(text.length);
            chars(text);
        }
    }
    function advance(n){
        html = html.substring(n);
    }
    function parseStartTag(){
        const start = html.match(startTagOpen);
        if(start){
            const match = {
                tagName:start[1],
                attrs:[]
            }
            advance(start[0].length);
            let attr,end;
            while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
                advance(attr[0].length);
                match.attrs.push({name:attr[1],value:attr[3]});
            }
            if(end){
                advance(end[0].length);
                return match
            }
        }
    }
}
export function compileToFunctions(template){
    parseHTML(template);
    return function(){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# 2.生成ast语法树

语法树就是用对象描述js语法

{
    tag:'div',
    type:1,
    children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
    attrs:[{name:'zf',age:10}],
    parent:null
}
1
2
3
4
5
6
7
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;

function createASTElement(tagName,attrs){
    return {
        tag:tagName,
        type:ELEMENT_TYPE,
        children:[],
        attrs,
        parent:null
    }
}
function start(tagName, attrs) {
    let element = createASTElement(tagName,attrs);
    if(!root){
        root = element;
    }
    currentParent = element;
    stack.push(element);
}
function end(tagName) {
    let element = stack.pop();
    currentParent = stack[stack.length-1];
    if(currentParent){
        element.parent = currentParent;
        currentParent.children.push(element);
    }
}
function chars(text) {
    text = text.replace(/\s/g,'');
    if(text){
        currentParent.children.push({
            type:TEXT_TYPE,
            text
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 3.生成代码

template转化成render函数的结果

<div style="color:red">hello {{name}} <span></span></div>
render(){
   return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))
}
1
2
3
4

实现代码生成

function gen(node) {
    if (node.type == 1) {
        return generate(node);
    } else {
        let text = node.text
        if(!defaultTagRE.test(text)){
            return `_v(${JSON.stringify(text)})`
        }
        let lastIndex = defaultTagRE.lastIndex = 0
        let tokens = [];
        let match,index;
        
        while (match = defaultTagRE.exec(text)) {
            index = match.index;
            if(index > lastIndex){
                tokens.push(JSON.stringify(text.slice(lastIndex,index)));
            }
            tokens.push(`_s(${match[1].trim()})`)
            lastIndex = index + match[0].length;
        }
        if(lastIndex < text.length){
            tokens.push(JSON.stringify(text.slice(lastIndex)))
        }
        return `_v(${tokens.join('+')})`;
    }
}
function getChildren(el) { // 生成儿子节点
    const children = el.children;
    if (children) {
        return `${children.map(c=>gen(c)).join(',')}`
    } else {
        return false;
    }
}
function genProps(attrs){ // 生成属性
    let str = '';
    for(let i = 0; i<attrs.length; i++){
        let attr = attrs[i];
        if(attr.name === 'style'){
            let obj = {}
            attr.value.split(';').forEach(item=>{
                let [key,value] = item.split(':');
                obj[key] = value;
            })
            attr.value = obj;
        }
        str += `${attr.name}:${JSON.stringify(attr.value)},`;
    }
    return `{${str.slice(0,-1)}}`;
}
function generate(el) {
    let children = getChildren(el);
    let code = `_c('${el.tag}',${
        el.attrs.length?`${genProps(el.attrs)}`:'undefined'
    }${
        children? `,${children}`:''
    })`;
    return code;
}
let code = generate(root);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 4.生成render函数

export function compileToFunctions(template) {
    parseHTML(template);
    let code = generate(root);
    let render = `with(this){return ${code}}`;
    let renderFn = new Function(render);
    return renderFn
}
1
2
3
4
5
6
7

# 创建渲染watcher

# 1.初始化渲染Watcher

import {mountComponent} from './lifecycle'
Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // 如果没有render方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有el
        if (!template && el) {
            template = el.outerHTML;
        }

        const render= compileToFunctions(template);
        options.render = render;
    }
    mountComponent(vm,el);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

lifecycle.js

先调用_render方法生成虚拟dom,通过_update方法将虚拟dom创建成真实的dom

export function lifecycleMixin() {
    Vue.prototype._update = function (vnode) {}
}
export function mountComponent(vm, el) {
    vm.$el = el;
    let updateComponent = () => {
        // 将虚拟节点 渲染到页面上
        vm._update(vm._render());
    }
    new Watcher(vm, updateComponent, () => {}, true);
}
1
2
3
4
5
6
7
8
9
10
11

render.js

export function renderMixin(Vue){
    Vue.prototype._render = function () {}
}
1
2
3

watcher.js

let id = 0;
class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm;
        this.exprOrFn = exprOrFn;
        if (typeof exprOrFn == 'function') {
            this.getter = exprOrFn;
        }
        this.cb = cb;
        this.options = options;
        this.id = id++;
        this.get();
    }
    get() {
        this.getter();
    }
}

export default Watcher;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2.生成虚拟dom

import {createTextNode,createElement} from './vdom/create-element'
export function renderMixin(Vue){
    Vue.prototype._v = function (text) { // 创建文本
        return createTextNode(text);
    }
    Vue.prototype._c = function () { // 创建元素
        return createElement(...arguments);
    }
    Vue.prototype._s = function (val) {
        return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);
    }
    Vue.prototype._render = function () {
        const vm = this;
        const {render} = vm.$options;
        let vnode = render.call(vm);
        return vnode;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

创建虚拟节点

export function createTextNode(text) {
    return vnode(undefined,undefined,undefined,undefined,text)
}
export function createElement(tag,data={},...children){
    let key = data.key;
    if(key){
        delete data.key;
    }
    return vnode(tag,data,key,children);
}
function vnode(tag,data,key,children,text){
    return {
        tag,
        data,
        key,
        children,
        text
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3.生成真实DOM元素

将虚拟节点渲染成真实节点

import {patch} './observer/patch'
export function lifecycleMixin(Vue){
    Vue.prototype._update = function (vnode) {
        const vm = this;
        vm.$el = patch(vm.$el,vnode);
    }
}
1
2
3
4
5
6
7
export function patch(oldVnode,vnode){
    const isRealElement = oldVnode.nodeType;
    if(isRealElement){
        const oldElm = oldVnode;
        const parentElm = oldElm.parentNode;
        
        let el = createElm(vnode);
        parentElm.insertBefore(el,oldElm.nextSibling);
        parentElm.removeChild(oldVnode)
   		return el;
    } 
}
function createElm(vnode){
    let {tag,children,key,data,text} = vnode;
    if(typeof tag === 'string'){
        vnode.el = document.createElement(tag);
        updateProperties(vnode);
        children.forEach(child => { 
            return vnode.el.appendChild(createElm(child));
        });
    }else{
        vnode.el = document.createTextNode(text);
    }
    return vnode.el
}
function updateProperties(vnode){
    let newProps = vnode.data || {}; // 获取当前老节点中的属性 
    let el = vnode.el; // 当前的真实节点
    for(let key in newProps){
        if(key === 'style'){ 
            for(let styleName in newProps.style){
                el.style[styleName] = newProps.style[styleName]
            }
        }else if(key === 'class'){
            el.className= newProps.class
        }else{ // 给这个元素添加属性 值就是对应的值
            el.setAttribute(key,newProps[key]);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 生命周期的合并

# 1.Mixin原理

import {mergeOptions} from '../util/index.js'
export function initGlobalAPI(Vue){
    Vue.options = {};
    Vue.mixin = function (mixin) {
        this.options = mergeOptions(this.options,mixin);// 将属性合并到Vue.options上
        return this;
    }
}
1
2
3
4
5
6
7
8

# 2.合并生命周期

export const LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
]
const strats = {};
function mergeHook(parentVal, childValue) {
    if (childValue) {
        if (parentVal) {
            return parentVal.concat(childValue);
        } else {
            return [childValue]
        }
    } else {
        return parentVal;
    }
}
LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook
})
export function mergeOptions(parent, child) {
    const options = {}
    for (let key in parent) {
        mergeField(key)
    }
    for (let key in child) {
        if (!parent.hasOwnProperty(key)) {
            mergeField(key);
        }
    }
    function mergeField(key) {
        if (strats[key]) {
            options[key] = strats[key](parent[key], child[key]);
        } else {
            if (typeof parent[key] == 'object' && typeof child[key] == 'object') {
                options[key] = {
                    ...parent[key],
                    ...child[key]
                }
            }else{
                options[key] = child[key];
            }
        }
    }
    return options
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4.生命周期发布订阅

export function callHook(vm, hook) {
    const handlers = vm.$options[hook];
    if (handlers) {
        for (let i = 0; i < handlers.length; i++) {
            handlers[i].call(vm);
        }
    }
}
1
2
3
4
5
6
7
8

# 5.调用生命周期

Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = mergeOptions(vm.constructor.options,options);
    // 初始化状态
    callHook(vm,'beforeCreate');
    initState(vm);
    callHook(vm,'created');
    if (vm.$options.el) {
    	vm.$mount(vm.$options.el);
    }
}

export function mountComponent(vm, el) {
  callHook(vm, 'beforeMount');
  // 在初始化时,下面两方法已经提取挂载到属性上;调用render方法去渲染 el属性;
  vm._update(vm._render()); // 先调用render方法创建虚拟节点,在将虚拟节点渲染到页面上; 虚拟Dom ====> 真Dom
  callHook(vm, 'mounted');
  //优化后:
  // 默认vue是通过watcher来进行渲染  = 渲染watcher (每一个组件都有一个渲染watcher)
  // let updateComponent = () => {
  //   vm._update(vm._render()); // 虚拟节点
  // }
  // new Watcher(vm, updateComponent, () => { }, true);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 依赖收集

每个属性都要有一个dep,每个dep中存放着watcher,同一个watcher会被多个dep所记录。

# 1.在渲染时存储watcher

class Watcher{
    // ...
    get(){
        pushTarget(this);
        this.getter();
        popTarget();
    }
}
1
2
3
4
5
6
7
8
let id = 0;
class Dep{
    constructor(){
        this.id = id++;
    }
}
let stack = [];
export function pushTarget(watcher){
    Dep.target = watcher;
    stack.push(watcher);
}
export function popTarget(){
    stack.pop();
    Dep.target = stack[stack.length-1];
}
export default Dep;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.对象依赖收集

let dep = new Dep();
Object.defineProperty(data, key, {
    get() {
        if(Dep.target){ // 如果取值时有watcher
            dep.depend(); // 让watcher保存dep,并且让dep 保存watcher
        }
        return value
    },
    set(newValue) {
        if (newValue == value) return;
        observe(newValue);
        value = newValue;
        dep.notify(); // 通知渲染watcher去更新
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Dep实现

class Dep{
    constructor(){
        this.id = id++;
        this.subs = [];
    }
    depend(){
        if(Dep.target){
            Dep.target.addDep(this);// 让watcher,去存放dep
        }
    }
    notify(){
        this.subs.forEach(watcher=>watcher.update());
    }
    addSub(watcher){
        this.subs.push(watcher);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

watcher

constructor(){
	this.deps = [];
	this.depsId = new Set();
}
addDep(dep){
    let id = dep.id;
    if(!this.depsId.has(id)){
        this.depsId.add(id);
        this.deps.push(dep);
        dep.addSub(this);
    }
}
update(){
    this.get();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3.数组的依赖收集

this.dep = new Dep(); // 专门为数组设计的
if (Array.isArray(value)) {
	value.__proto__ = arrayMethods;
	this.observeArray(value);
} else {
	this.walk(value);
}	

function defineReactive(data, key, value) {
    let childOb = observe(value);
    let dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            if(Dep.target){
                dep.depend();
                if(childOb){ 
                    childOb.dep.depend(); // 收集数组依赖
                }
            }
            return value
        },
        set(newValue) {
            if (newValue == value) return;
            observe(newValue);
            value = newValue;
            dep.notify();
        }
    })
}


arrayMethods[method] = function (...args) {
    	// ...
        ob.dep.notify()
        return result;
}
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

递归收集数组依赖

if(Dep.target){
    dep.depend();
    if(childOb){
        childOb.dep.depend(); // 收集数组依赖
        if(Array.isArray(value)){ // 如果内部还是数组
            dependArray(value);// 不停的进行依赖收集
        }
    }
}
function dependArray(value) {
    for (let i = 0; i < value.length; i++) {
        let current = value[i];
        current.__ob__ && current.__ob__.dep.depend();
        if (Array.isArray(current)) {
            dependArray(current)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现Vue异步更新之nextTick

# 1.实现队列机制

update(){
    queueWatcher(this);
}
1
2
3

scheduler

import {
    nextTick
} from '../util/next-tick'
let has = {};
let queue = [];

function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        let watcher = queue[i];
        watcher.run()
    }
    queue = [];
    has = {}
}
let pending = false
export function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);
        if(!pending){
            nextTick(flushSchedulerQueue)
            pending = true;
        }
    }
}
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

# 2.nextTick实现原理

util/next-tick.js

let callbacks = [];
function flushCallbacks() {
    callbacks.forEach(cb => cb());
}
let timerFunc;
if (Promise) { // then方法是异步的
    timerFunc = () => {
        Promise.resolve().then(flushCallbacks)
    }
}else if (MutationObserver) { // MutationObserver 也是一个异步方法
    let observe = new MutationObserver(flushCallbacks); // H5的api
    let textNode = document.createTextNode(1);
    observe.observe(textNode, {
        characterData: true
    });
    timerFunc = () => {
        textNode.textContent = 2;
    }
}else if (setImmediate) {
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
}else{
    timerFunc = () => {
        setTimeout(flushCallbacks, 0);
    }
}
export function nextTick(cb) {
    callbacks.push(cb);
    timerFunc();
}
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

# watch实现原理

watch用于监控用户的data变化,数据变化后会触发对应的watch的回调方法

# 几种监听方式

  • 1.直接key value;
  • 2.写成key 和数组的方式;
  • 3.监控当前实例上的方法;
  • 4.handler的写法;
let vm = new Vue({
  el: '#app',
  data() {
    return {
      name: 'samy',//普通的watch
      bet: { name: 'samy' }, //对象的watch
      a: { a: { a: 1 } }, //对象的watch2
      winChips: new Array(11).fill(0)
    }
  },
  computed: {
    // 只要bet中的属性发生变化(可被监测到的),便会执行handler函数;如果想监测具体的属性变化,如samy变化时,才执行handler函数,则可以利用计算属性computed做中间层。
    pokerSamy() {
      return this.bet.samy
    }
  },
  watch: {
    name(newValue, oldValue) {
      console.log(newValue, oldValue);
    },
    bet: {
      handler(newValue, oldValue) {
        console.log(newValue)
      },
      deep: true
    },
    //对象具体属性的watch[活用computed]
    pokerSamy(newValue, oldValue) {
    console.log(newValue)
   },
    'a.a.a': {
      handler(newValue, oldValue) {
        console.log(newValue, oldValue, '内部watch')
      }
    },
    winChips: {
      handler(newValue, oldValue) {
        for (let i = 0; i < newValue.length; i++) {
          if (oldValue[i] != newValue[i]) {
            console.log(newValue)
          }
        }
      },
      deep: true
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 初始化

选项中如果有watch则对watch进行初始化

if (opts.watch) {
	initWatch(vm,opts.watch);
}
function initWatch(vm, watch) {
    for (const key in watch) {
        const handler = watch[key];
        // 如果结果值是数组循环创建watcher
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm,key,handler[i]);
            }
        }else{
            createWatcher(vm,key,handler)
        }
    }
}
function createWatcher(vm,exprOrFn,handler,options){
    // 如果是对象则提取函数 和配置
    if(isObject(handler)){
        options = handler;
        handler = handler.handler;
    }
    // 如果是字符串就是实例上的函数
    if(typeof handler == 'string'){
        handler = vm[handler];
    }
    return vm.$watch(exprOrFn,handler,options);
}
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

这里涉及了watch的三种写法,1.值是对象、2.值是数组、3.值是字符串 (如果是对象可以传入一些watch参数),最终会调用vm.$watch来实现

# 扩展原型$watch

扩展Vue原型上的方法,都通过mixin的方式来进行添加的。

stateMixin(Vue);
export function stateMixin(Vue) {
    Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
        options.user = true; // 标记为用户watcher
        // 核心就是创建个watcher
        const watcher = new Watcher(this, exprOrFn, cb, options);
        if(options.immediate){
            cb.call(vm,watcher.value)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 构造Watcher

class Watcher {
    constructor(vm, exprOrFn, callback, options) {
        // ...
        this.user = !! options.user
        if(typeof exprOrFn === 'function'){
            this.getter = exprOrFn; 
        }else{
            this.getter = function (){ // 将表达式转换成函数
                let path = exprOrFn.split('.');
                let obj = vm;
                for(let i = 0; i < path.length;i++){
                    obj = obj[path[i]];
                }
                return obj;
            }
        }
        this.value = this.get(); // 将初始值记录到value属性上
    }
    get() {
        pushTarget(this); // 把用户定义的watcher存起来  
        const value = this.getter.call(this.vm); // 执行函数 (依赖收集)
        popTarget(); // 移除watcher
        return value;
    }
    run(){
        let value = this.get();    // 获取新值
        let oldValue = this.value; // 获取老值
        this.value = value;
        if(this.user){ // 如果是用户watcher 则调用用户传入的callback
            this.callback.call(this.vm,value,oldValue)
        }
    }
}
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

还是借助vue响应式原理,默认在取值时将watcher存放到对应属性的dep中,当数据发生变化时通知对应的watcher重新执行

# computed实现原理

# 初始化

if (opts.computed) {
    initComputed(vm,opts.computed);
}
1
2
3
function initComputed(vm, computed) {
    // 存放计算属性的watcher
    const watchers = vm._computedWatchers = {};
    for (const key in computed) {
        const userDef = computed[key];
        // 获取get方法
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        // 创建计算属性watcher
        watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
        defineComputed(vm, key, userDef)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# dirty/lazy

每个计算属性也都是一个watcher,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法

class Watcher {
    constructor(vm, exprOrFn, callback, options) {
        this.vm = vm;
        this.dirty = this.lazy
        // ...
        this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
    }
}    
1
2
3
4
5
6
7
8

默认计算属性需要保存一个dirty属性,用来实现缓存功能

function defineComputed(target, key, userDef) {
    if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = createComputedGetter(key)
    } else {
        sharedPropertyDefinition.get = createComputedGetter(userDef.get);
        sharedPropertyDefinition.set = userDef.set;
    }
    // 使用defineProperty定义
    Object.defineProperty(target, key, sharedPropertyDefinition)
}
1
2
3
4
5
6
7
8
9
10

# 创建缓存getter

function createComputedGetter(key) {
    return function computedGetter() {
        const watcher = this._computedWatchers[key];
        if (watcher) {
            if (watcher.dirty) { // 如果dirty为true
              watcher.evaluate();// 计算出新值,并将dirty 更新为false
            }
            // 如果依赖的值不发生变化,则返回上次计算的结果
            return watcher.value
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

watcher.evaluate

evaluate() {
    this.value = this.get()
    this.dirty = false
}
1
2
3
4
update() {
    if (this.lazy) {
        this.dirty = true;
    } else {
        queueWatcher(this);
    }
}
1
2
3
4
5
6
7

当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。

# 监听watcher

if (watcher) {
    if (watcher.dirty) {
        watcher.evaluate();
    }
    if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
        watcher.depend()
    }
	return watcher.value
}
1
2
3
4
5
6
7
8
9
depend() {
	let i = this.deps.length
	while (i--) {
		this.deps[i].depend()
	}
}
1
2
3
4
5
6

如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher,这样依赖的属性发生变化也可以让视图进行刷新

# diff算法实现原理

# 使用及步骤流程

想掌握vue中的diff算法就先构建出两个虚拟dom 之后做patch

import {compileToFunction} from './compiler/index.js';
import { patch,createElm } from './vdom/patch';
// 1.创建第一个虚拟节点
let vm1 = new Vue({data:{name:'zf'}});
let render1 = compileToFunction('<div>{{name}}</div>')
let oldVnode = render1.call(vm1)
// 2.创建第二个虚拟节点
let vm2 = new Vue({data:{name:'jw'}});
let render2 = compileToFunction('<p>{{name}}</p>');
let newVnode = render2.call(vm2);
// 3.通过第一个虚拟节点做首次渲染
let el = createElm(oldVnode)
document.body.appendChild(el);
// 4.调用patch方法进行对比操作
patch(oldVnode,newVnode);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

image-20210822002451946

# Diff 算法复杂度

  • diff 算法是 vdom 中最核心、最关键的部分
  • diff 算法能在日常使用 Vue React 中体现出来(循环的 key)

# 优化前 树 diff 的时间复杂度 (n^3)

  1. 遍历Tree1,遍历Tree2
  2. 排序
  3. 假设有1000个节点,就要计算1亿次,算法不可用

# 优化后时间复杂度 (n^1)

  1. 只比较同一层级,不跨级比较
  2. tag 不相同,则直接删掉重建,不再深度比较
  3. tag 和 key,两者都相同,则认为是相同节点,不再深度比较

# 基本Diff算法

# 1.比对标签

// 如果标签不一致说明是两个不同元素
if(oldVnode.tag !== vnode.tag){
  oldVnode.el.parentNode.replaceChild(createElm(vnode),oldVnode.el)
}
1
2
3
4

在diff过程中会先比较标签是否一致,如果标签不一致用新的标签替换掉老的标签

// 如果标签一致但是不存在则是文本节点
if(!oldVnode.tag){
    if(oldVnode.text !== vnode.text){
    	oldVnode.el.textContent = vnode.text;
    }
}
1
2
3
4
5
6

如果标签一致,有可能都是文本节点,那就比较文本的内容即可;

标签一样 并且需要开始比对标签的属性和儿子;

# 2.比对属性和样式

当标签相同时,我们可以复用老的标签元素,并且进行属性的比对。

  • 属性处理:老的有,新的没有 需要删除属性;
  • 样式处理:老的样式中有 新的没有 删除老的样式;
  • 老新的有:那就直接用新的去做更新即可;
// 复用标签,并且更新属性
let el = vnode.el = oldVnode.el;
updateProperties(vnode,oldVnode.data);
function updateProperties(vnode,oldProps={}) {
    let newProps = vnode.data || {};
    let el = vnode.el;
    // 比对样式
    let newStyle = newProps.style || {};
    let oldStyle = oldProps.style || {};
    for(let key in oldStyle){
        if(!newStyle[key]){
            el.style[key] = ''
        }
    }
    // 删除多余属性
    for(let key in oldProps){
        if(!newProps[key]){
            el.removeAttribute(key);
        }
    }
    for (let key in newProps) {
        if (key === 'style') {
            for (let styleName in newProps.style) {
                el.style[styleName] = newProps.style[styleName];
            }
        } else if (key === 'class') {
            el.className = newProps.class;
        } else {
            el.setAttribute(key, newProps[key]);
        }
    }
}
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

# 3.比对子元素

​ 儿子比较分为以下几种情况

  • 老的有儿子 新的没儿子 ;
  • 老的没儿子 新的有儿子;
  • 老新都有儿子,用diff算法,头尾双指针处理;
    • 先头头,尾尾对比;
    • 再头尾对比
// 比较孩子节点
let oldChildren = oldVnode.children || [];
let newChildren = vnode.children || [];
// 新老都有需要比对儿子
if(oldChildren.length > 0 && newChildren.length > 0){
	
// 老的有儿子新的没有清空即可
}else if(oldChildren.length > 0 ){
	el.innerHTML = '';
// 新的有儿子
}else if(newChildren.length > 0){
	for(let i = 0 ; i < newChildren.length ;i++){
		let child = newChildren[i];
		el.appendChild(createElm(child));
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这里要判断新老节点儿子的状况

if (oldChildren.length > 0 && newChildren.length > 0) {
	updateChildren(el, oldChildren, newChildren)
	// 老的有儿子新的没有清空即可
}
1
2
3
4

# Diff中优化策略

# 1.在开头和结尾新增元素

function isSameVnode(oldVnode,newVnode){
    // 如果两个人的标签和key 一样我认为是同一个节点 虚拟节点一样我就可以复用真实节点了
    return (oldVnode.tag === newVnode.tag) && (oldVnode.key === newVnode.key)
}
function updateChildren(parent, oldChildren, newChildren) {
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[0];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];

    let newStartIndex = 0;
    let newStartVnode = newChildren[0];
    let newEndIndex = newChildren.length - 1;
    let newEndVnode = newChildren[newEndIndex];

    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        // 优化向后追加逻辑
        if(isSameVnode(oldStartVnode,newStartVnode)){
            patch(oldStartVnode,newStartVnode);
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = newChildren[++newStartIndex];
        // 优化向前追加逻辑
        }else if(isSameVnode(oldEndVnode,newEndVnode)){ 
            patch(oldEndVnode,newEndVnode); // 比较孩子 
            oldEndVnode = oldChildren[--oldEndIndex];
            newEndVnode = newChildren[--newEndIndex];
        }
    }
    if(newStartIndex <= newEndIndex){
        for(let i = newStartIndex ; i<=newEndIndex ;i++){
            let ele = newChildren[newEndIndex+1] == null? null:newChildren[newEndIndex+1].el;
            parent.insertBefore(createElm(newChildren[i]),ele);
        }
    }
}
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

# 2.头移尾、尾移头

// 头移动到尾部
else if(isSameVnode(oldStartVnode,newEndVnode)){
    patch(oldStartVnode,newEndVnode);
    parent.insertBefore(oldStartVnode.el,oldEndVnode.el.nextSibling);
    oldStartVnode = oldChildren[++oldStartIndex];
    newEndVnode = newChildren[--newEndIndex]
// 尾部移动到头部
}else if(isSameVnode(oldEndVnode,newStartVnode)){
    patch(oldEndVnode,newStartVnode);
    parent.insertBefore(oldEndVnode.el,oldStartVnode.el);
    oldEndVnode = oldChildren[--oldEndIndex];
    newStartVnode = newChildren[++newStartIndex]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

以上四个条件对常见的dom操作进行了优化。

# 3.暴力比对

function makeIndexByKey(children) {
    let map = {};
    children.forEach((item, index) => {
    	map[item.key] = index
    });
    return map; 
}
let map = makeIndexByKey(oldChildren);
1
2
3
4
5
6
7
8

对所有的孩子元素进行编号

let moveIndex = map[newStartVnode.key];
if (moveIndex == undefined) { // 老的中没有将新元素插入
    parent.insertBefore(createElm(newStartVnode), oldStartVnode.el);
} else { // 有的话做移动操作
    let moveVnode = oldChildren[moveIndex]; 
    oldChildren[moveIndex] = undefined;
    parent.insertBefore(moveVnode.el, oldStartVnode.el);
    patch(moveVnode, newStartVnode);
}
newStartVnode = newChildren[++newStartIndex]
1
2
3
4
5
6
7
8
9
10

用新的元素去老的中进行查找,如果找到则移动,找不到则直接插入

if(oldStartIndex <= oldEndIndex){
    for(let i = oldStartIndex; i<=oldEndIndex;i++){
        let child = oldChildren[i];
        if(child != undefined){
            parent.removeChild(child.el)
        }
    }
}
1
2
3
4
5
6
7
8

如果有剩余则直接删除

if(!oldStartVnode){
    oldStartVnode = oldChildren[++oldStartIndex];
}else if(!oldEndVnode){
    oldEndVnode = oldChildren[--oldEndIndex]
}
1
2
3
4
5

在比对过程中,可能出现空值情况则直接跳过

# 更新操作

prevVnode做缓存处理;没有创建过则创建虚拟节点,已经有了的话则通过diff算法对比;

Vue.prototype._update = function (vnode) {
    const vm  = this;
    const prevVnode = vm._vnode; // 保留上一次的vnode
    vm._vnode = vnode;
    if(!prevVnode){
        vm.$el = patch(vm.$el,vnode); // 需要用虚拟节点创建出真实节点 替换掉 真实的$el
        // 我要通过虚拟节点 渲染出真实的dom     
    }else{
        vm.$el = patch(prevVnode,vnode); // 更新时做diff操作
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 组件实现原理

# 全局组件的解析

<div id="app">
    <my-component></my-component>
    <my-component></my-component>
</div>
<script>
Vue.component('my-component',{
	template:'<button>点我</button>',
});
let vm = new Vue({
	el:'#app'
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12

我们可以通过Vue.component注册全局组件,之后可以在模板中进行使用

/**
 * 静态方法部分
 * Vue.component Vue.directive Vue.extend Vue.mixin ...
 */
initGlobalAPI(Vue); // 混合全局的api
export default Vue;

export function initGlobalAPI(Vue){
    // 整合了所有的全局相关的内容
    Vue.options ={}
    initMixin(Vue);

    // _base 就是Vue的构造函数
    Vue.options._base = Vue;
    Vue.options.components = {}

    // 注册API方法
    initAssetRegisters(Vue);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 1.Vue.component方法

export default function initAssetRegisters(Vue) {
    Vue.component = function (id, definition) {
        definition.name = definition.name || id;
        definition = this.options._base.extend(definition);
        this.options['components'][id] = definition;
    }
}
1
2
3
4
5
6
7

Vue.component内部会调用Vue.extend方法,将定义挂载到Vue.options.components上。这也说明所有的全局组件最终都会挂载到这个变量上

export function initGlobalAPI(Vue){
    // 整合了所有的全局相关的内容
    Vue.options ={}
    initMixin(Vue);
    // _base 就是Vue的构造函数
    Vue.options._base = Vue;
    Vue.options.components = {}

    // initExtend
+   initExtend(Vue);

    // 注册API方法
    initAssetRegisters(Vue);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.Vue.extend方法

继承的实现:

Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub;

import {mergeOptions} from '../util/index'
export default function initExtend(Vue) {
    let cid = 0;
    Vue.extend = function (extendOptions) {
        const Super = this;
        const Sub = function VueComponent(options) {
            this._init(options)
        }
        Sub.cid = cid++;
        Sub.prototype = Object.create(Super.prototype);
        Sub.prototype.constructor = Sub;
        Sub.options = mergeOptions(
            Super.options,
            extendOptions
        );
        return Sub
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

extend方法就是创建出一个子类,继承于Vue,并返回这个类

# 3.属性合并

function mergeAssets(parentVal,childVal){
    const res = Object.create(parentVal);
    if(childVal){
        for(let key in childVal){
            res[key] = childVal[key];
        }
    }
    return res;
}
strats.components = mergeAssets;
1
2
3
4
5
6
7
8
9
10

# 4.初始化合并

vm.$options = mergeOptions(vm.constructor.options,options);
1

# 组件的渲染

function makeMap(str) {
    const map = {};
    const list = str.split(',');
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true;
    }
    return (key)=>map[key];
}

export const isReservedTag = makeMap(
    'a,div,img,image,text,span,input,p,button'
)
1
2
3
4
5
6
7
8
9
10
11
12

在创建虚拟节点时我们要判断当前这个标签是否是组件,普通标签的虚拟节点和组件的虚拟节点有所不同

# 1.创建组件虚拟节点

export function createElement(vm,tag, data = {}, ...children) {
    let key = data.key;
    if (key) {
        delete data.key;
    }
    if (typeof tag === 'string') {
        if (isReservedTag(tag)) {
            return vnode(tag, data, key, children, undefined);
        } else {
            // 如果是组件需要拿到组件的定义,通过组件的定义创造虚拟节点
            let Ctor = vm.$options.components[tag];
            return createComponent(vm,tag,data,key,children,Ctor)
        }
    }
}
function createComponent(vm,tag,data,key,children,Ctor){
    // 获取父类构造函数t 
    const baseCtor = vm.$options._base; 
    if(isObject(Ctor)){
        Ctor = baseCtor.extend(Ctor);
    }
    data.hook = { // 组件的生命周期钩子
        init(){}
    }
    return vnode(`vue-component-${Ctor.cid}-${tag}`,data,key,undefined,{Ctor,children});
}
function vnode(tag, data, key, children, text, componentOptions) {
    return {tag, data, key, children, text, componentOptions}
}
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

# 2.创建组件的真实节点

export function patch(oldVnode,vnode){
    // 1.判断是更新还是要渲染
    if(!oldVnode){
        return createElm(vnode);
    }else{
        // ...
    }
}
function createElm(vnode){ // 根据虚拟节点创建真实的节点
    let {tag,children,key,data,text} = vnode;
    // 是标签就创建标签
    if(typeof tag === 'string'){
        // createElm需要返回真实节点
        if(createComponent(vnode)){
            return vnode.componentInstance.$el;
        }
        vnode.el = document.createElement(tag);
        updateProperties(vnode);
        children.forEach(child=>{ // 递归创建儿子节点,将儿子节点扔到父节点中
            return vnode.el.appendChild(createElm(child))
        })
    }else{
        // 虚拟dom上映射着真实dom  方便后续更新操作
        vnode.el = document.createTextNode(text)
    }
    // 如果不是标签就是文本
    return vnode.el;
}
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
function createComponent(vnode) {
    let i = vnode.data;
    if((i = i.hook) && (i = i.init)){
        i(vnode);
    }
    if(vnode.componentInstance){
        return true;
    }
}
1
2
3
4
5
6
7
8
9

调用init方法,进行组件的初始化

data.hook = {
    init(vnode){
        let child = vnode.componentInstance = new Ctor({});
        child.$mount(); // 组件的挂载
    }
}
1
2
3
4
5
6

# 其他原理相关【要点】

# vm.$on,vm.$off,vm.$once,vm.$emit实现原理; 这四个实现方法是一个很标准的订阅发布模式

# vm.$on

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

Vue.prototype.$on = function (event, fn) {
    const vm = this
    if (Array.isArray(event)) {
        for (let i = 0; i < event.length; i++) {
            vm.$on(event[i], fn)
        }
    } else {
        (vm._events[event] || (vm._events[event] = [])).push[fn]
    }
    return vm
}
1
2
3
4
5
6
7
8
9
10
11

温馨提示:_events是实现初始化的时候定义的,this.events = Object.create(null),所以不要困惑_events哪里来的。

vm.$on('test', function (msg) {
  console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"
1
2
3
4
5

# vm.$off

移除自定义事件监听器。

  • 如果没有提供参数,则移除所有的事件监听器;
  • 如果只提供了事件,则移除该事件所有的监听器;
  • 如果同时提供了事件与回调,则只移除这个回调的监听器。
Vue.prototype.$off = function (event, fn) {
    const vm = this
    if (!arguments.length) {
        vm._events = Object.create(null)
        return vm
    }
    if (Array.isArray(event)) {
        for (let i = 0; i < event.length; i++) {
            vm.$off(event[i], fn)
        }
        return vm
    }
    const cbs = vm._events[event]
    if (!cbs) return vm
    if (arguments.length === 1) {
        vm._events[event] = null
        return vm
    }
    if (fn) {
        let len = cbs.length
        while (len--) {
            let cb = cbs[len]
            if (cb === fn || cb.fn === fn) {
                cbs.splice(len, 1)
                break
            }
        }
    }
    return vm
}
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

# vm.$once

监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

Vue.prototype.$once = function (event,fn){
    const vm = this
    function on(){
        vm.$off(event,on)
        fn.apply(vm,arguments)
    }
    on.fn = fn
    vm.$on(event,on)
    return vm
}
1
2
3
4
5
6
7
8
9
10

# vm.$emit

触发当前实例上的事件。附加参数都会传给监听器回调。

Vue.prototype.$emit = function (event, ...params) {
    const vm = this
    let cbs = vm._events[event]
    if (cbs) {
        for (let i = 0; i < cbs.length; i++) {
            cbs[i].apply(vm, params)
        }
    }
    return vm
}
1
2
3
4
5
6
7
8
9
10

# vm.$delete

1.target如果是数组的话并且key是合法的,那就通过splice去改变数组

2.target如果是vue实例.或者是this.$data,那就直接退出程序

3.target如果不是双向绑定数据,那就直接delete就行不需要,通知watcher

4.以上条件都不满足,那么target就是双向绑定数据,delete之后通知watcher

function del(target, key) {
    if (Array.isArray(target) && key > 0) {
        target.splice(key, 1)
        return
    }
    const ob = target.__ob__
    if ((target._isVue && (ob && ob.vmCount)) || !target.hasOwnProperty(key)) return
    delete target[key]
    if (!ob) return
    ob.dep.notify()
}
1
2
3
4
5
6
7
8
9
10
11

# vm.$nextTick

nextTick主要是通过js eventLoop的执行机制原理,将回调通过(promise)添加到microTask上面,来实现,在下一次DOM周期后执行回调函数。

const callback = []
let pendding = false
function nextTick(cb){
    if(cb){
        callback.push(()=>{cb()})
    }
    if(!pendding){
        pendding = true
        promise.resolve().then(()=>{
            padding =false
            const copies = callback.slice(0)
            callback.length = 0
            for(let i = 0;i < copies.length;i++){
                copies[i]()
            }
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Vue({ // 示例
  methods: {
    example: function () {
      this.message = 'changed'// 修改数据
      this.$nextTick(function () {// DOM 还没有更新
        this.doSomethingElse() // DOM 现在更新了// `this` 绑定到当前实例
      })
    }
  }
})
1
2
3
4
5
6
7
8
9
10

# 指令的执行原理

  • 在模版阶段,会将节点上的指令解析处理并添加到AST的directives属性中。
  • 随后directives数据会传到Vnode中,接着就可以通过vnode.data.directives获取一个节点所绑定的指令。
  • 最后,当VDom进行修补时,会根据节点的对比结果触发一些钩子函数。更新指令的程序会监听create,update,destory钩子函数,并在这三个钩子函数出发时对VNode和oldVNode进行对比,最终根据对比触发指令的钩子函数。

# vue中源码大纲

img

  • instance/index.js 真正的Vue的构造函数,并在Vue的原型上扩展方法
  • core/index.js 增加全局API方法
  • runtime/index.js 扩展$mount方法及平台对应的代码

# 总括

# 几个核心思想

  • 数据驱动
  • 组件化
  • 虚拟dom、diff局部最优更新

# 核心知识点

  • 数据驱动分析
    • Vue 实例化
    • Vue 实例挂载
    • render
    • Virtual DOM
    • createElement
    • update
  • 组件化分析
    • createComponent
    • patch
    • 合并配置
    • 生命周期
    • 组件注册
    • 异步组件
  • 响应式原理分析
    • 响应式对象 / 依赖收集
    • 派发更新 / nextTick
    • 检测变化的注意事项
    • 计算属性 VS 侦听属性
    • 组件更新

# 总体把握

  • template风格、对象配置风格
  • 虚拟dom思想(js对象操作代替dom操作)
  • diff算法思想(同层比较,添加、移动、删除)
  • 组件化思想(组件编译、组件通信)
  • 数据响应式(依赖收集、派发更新,发布订阅)

# 相关链接

http://www.zhufengpeixun.com/jg-vue/vue-analyse/source.html

上次更新: 2022/04/15, 05:41:27
×