相关基础知识点

// 可以让 任意函数/方法 成功临时指定成对象的方法进行调用 - call/apply

// 1. 根据伪数组生成 真数组

const lis = document.getElementsByTagName("li");

const arr = [].slice.call(lis);

const relArr = Array.from(lis);        // ES6语法

// 2. node.nodeType        // 得到节点类型

// 3. 给对象添加属性,指定 属性描述符:存取描述符 get(),set() - 数据描述符

Object.defineProperty(ojb, propertyName, {

get(){return this.firstNAme+"-"+this.lastNAme},

set(newVAlue){const names=newValue.split("-");this.firstNAme=names[0];this.lastNAme=names[1]},

})

// 4. Object.keys(obj);        // 得到对象自身可枚举属性组成的数组

// 5. obj.hasOwnProperty("name")       // 判断 obj 对象自身属性是否包含 name

// 6. DocumentFragment 文档碎片(高效更新多个节点)

内存中用来存储多个节点对象 的容器,不与任何 页面/页面节点 对应

思路: 先将节点复制到内存中,在内存中修改完后,一次性添加到页面中

// 1. 创建一个内存中 节点容器

const fragment = document.createDocumentFragment();

// 2. 取出 div 中所有节点,转移到 fragment 中

const div = document.getElementById("demo");

let child;        // newPos = appendChild(child) 会将 child 从原来位置取下来,放到 newPos 的最后位置

while(child=div.firstChild){fragment.appendChild(child)}

// 3. 在内存中遍历修改

const nodes = fragment.children[0].childNodes;

Array.prototype.slice.call(nodes).forEach(node=>{

if(node.nodeType === 1){

node.textContent = "丘魔的兵"

}

})

// 4. 一次性添加到页面

div.appendChild(fragment);

在 git 上,有个开源项目,剖析了 vue 原理

  • 数据代理

为达到简化编码的目的,

内部实现的关键点: 通过 Object.defineProperty(vm, key, {}) 实现

对于 data 中状态的数据,通过 ViewModel 实例 来进行 代理读/代理写 操作 

---------------------------------------------------------------

function Vue(configObj){

var me = this;

// 1. 保存 配置对象 到 ViewModel

this.$configObj = configObj;

// 2. 保存 data 对象到 ViewModel 和 变量 data 上

var data = this._data = this.$configObj.data;

// 3. 遍历 data 中所有属性, 实现数据代理

Object.keys(data).forEach(function (key) {

me._proxy(key);

});

// 实现数据绑定

observe(data, this);

// 实现模版解析

this.$compile = new Compile(options.el || document.body, this)

}

Vue.prototype = {

//

_proxy: function(key){

var vm = this;

// 给 vm 添加指定属性,使用 属性描述符 (设置 getter/setter)

Object.defineProperty(vm, key, {

configurable:  false,

enumerable: true,

get: function proxyGetter(){    // 代理读

return vm._data[key]

},

set: function proxySetter(newValue){    // 代理写

vm._data[key] = newValue

}

})

},

$watch: function (key, cb, options) {

new Watcher(this, key, cb);

}

}

  • 模板解析
  • 模板表达式

  • 指令
  • 事件指令

  • 一般指令

this.$compile = new Compile(options.el || document.body, this)

  • function Compile(el, vm) {
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { // 整体流程:
    this.$fragment = this.node2Fragment(this.$el); // 1. 将 目标元素节点 拷贝到 DocumentFragment
    this.init(); // 2. 内存中编译 DocumentFragment
    this.$el.appendChild(this.$fragment); // 3. 将 DocumentFragment 追加到目标元素节点
    }
    } Compile.prototype = {
    node2Fragment: function(el) {
    var fragment = document.createDocumentFragment(),
    child; // 将原生节点拷贝到fragment
    while (child = el.firstChild) {
    fragment.appendChild(child);
    } return fragment;
    }, init: function() {
    this.compileElement(this.$fragment); // 传入待处理节点,进行编译处理
    }, compileElement: function(el) {
    var childNodes = el.childNodes,
    me = this; [].slice.call(childNodes).forEach(function(node) {
    var text = node.textContent; // 换行
    var reg = /\{\{(.*)\}\}/; // 匹配 {{模板表达式}} if (me.isElementNode(node)) {
    me.compile(node);
    } else if (me.isTextNode(node) && reg.test(text)) { // 如果是 {{模板表达式}}
    // 则 解析 成node.textContent = 模板表达式,不管匹配成功几个表达式,总是只生效第一个

    me.compileText(node, RegExp.$1);
    }
    if (node.childNodes && node.childNodes.length) {    // 如果还有子节点,则递归编译
    me.compileElement(node);
    }
    });
    }, compile: function(node) {
    var nodeAttrs = node.attributes,
    me = this; [].slice.call(nodeAttrs).forEach(function(attr) {
    var attrName = attr.name;
    if (me.isDirective(attrName)) {
    var exp = attr.value; // 得到表达式 即 回调函数名
    var dir = attrName.substring(2); // 得到指令
    // 事件指令
    if (me.isEventDirective(dir)) {
    compileUtil.eventHandler(node, me.$vm, exp, dir);
    // 普通指令
    } else {
    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
    } node.removeAttribute(attrName);
    }
    });
    }, compileText: function(node, exp) {
    compileUtil.text(node, this.$vm, exp);
    }, isDirective: function(attr) {
    return attr.indexOf('v-') == 0;
    }, isEventDirective: function(dir) {
    return dir.indexOf('on') === 0;
    }, isElementNode: function(node) {
    return node.nodeType == 1;
    }, isTextNode: function(node) {
    return node.nodeType == 3;
    }
    }; // 用于编译的方法 - 指令处理集合
    var compileUtil = {
    text: function(node, vm, exp) { // 编译 v-text 或者 {{模板表达式}}
    this.bind(node, vm, exp, 'text');
    }, html: function(node, vm, exp) { // v-html
    this.bind(node, vm, exp, 'html');
    }, model: function(node, vm, exp) { // v-model
    this.bind(node, vm, exp, 'model'); var me = this,
    val = this._getVMVal(vm, exp);
    node.addEventListener('input', function(e) {
    var newValue = e.target.value;
    if (val === newValue) {
    return;
    } me._setVMVal(vm, exp, newValue);
    val = newValue;
    });
    }, class: function(node, vm, exp) { // v-bind:class
    this.bind(node, vm, exp, 'class');
    }, bind: function(node, vm, exp, dir) {
    var updaterFn = updater[dir + 'Updater']; // 根据指令名 得到一个用于更新节点的更新函数 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 得到指定表达式对应的值

    // 解析每个表达式后 - 为表达式创建对应的 watcher 对象(事件表达式除外)
    new Watcher(vm, exp, function(value, oldValue) {
    updaterFn && updaterFn(node, value, oldValue); // 更新节点
    });
    }, // 事件处理
    eventHandler: function(node, vm, exp, dir) {
    var eventType = dir.split(':')[1],
    fn = vm.$options.methods && vm.$options.methods[exp]; if (eventType && fn) {
    node.addEventListener(eventType, fn.bind(vm), false);
    }
    }, _getVMVal: function(vm, exp) {
    var val = vm._data;
    exp = exp.split('.');
    exp.forEach(function(k) {
    val = val[k];
    });
    return val;
    }, _setVMVal: function(vm, exp, value) {
    var val = vm._data;
    exp = exp.split('.');
    exp.forEach(function(k, i) {
    // 非最后一个key,更新val的值
    if (i < exp.length - 1) {
    val = val[k];
    } else {
    val[k] = value;
    }
    });
    }
    }; // 更新节点的方法集
    var updater = {
    textUpdater: function(node, value) { // v-text 或者 {{模板表达式}}
    node.textContent = typeof value == 'undefined' ? '' : value;
    }, htmlUpdater: function(node, value) { // v-html
    node.innerHTML = typeof value == 'undefined' ? '' : value;
    }, classUpdater: function(node, value, oldValue) { // v-bind:class
    var className = node.className;
    className = className.replace(oldValue, '').replace(/\s$/, ''); var space = className && String(value) ? ' ' : ''; node.className = className + space + value;
    }, modelUpdater: function(node, value, oldValue) { // v-model
    node.value = typeof value == 'undefined' ? '' : value;
    }
    };
  • 数据绑定 -------- 双向数据绑定

使用 数据劫持技术 实现数据绑定

思想: 通过 defineProperty() 来监视 data 中所有状态属性(任意层次)的变化,一旦变化了,界面会实时更新

Observer

用来对 data 中数据进行监视 的构造函数

为每个 data 数据设置 getter  和 setter ,并配置 dep 对象

Compile

用来解析模板页面对象的构造函数

利用 compile 对象解析模板页面

每解析一个表达式都会创建一个 watcher 对象,并建立 watcher 和 dep 的关系

1 个 watcher 对应 n>=1 个 dep -------- 假设表达式是 2 层表达式 mom.son 时,当前 watcher 就对应 2 个 dep

1 个 dep 对应 n>=0 个 watcher -------- 没有一个表达式用到这个属性; 只有一个表达式用到这个属性; 多个表达式用到这个属性

每个 dep 都有唯一的 id

subs 包含 n 个对应 watcher 的数组 ---- subcribes 的简写

  • function Observer(data) {
    this.data = data;
    this.walk(data);
    } Observer.prototype = {
    walk: function(data) {
    var me = this;
    Object.keys(data).forEach(function(key) {
    me.convert(key, data[key]);
    });
    },
    convert: function(key, val) { // 给每个对象设置 响应式属性
    this.defineReactive(this.data, key, val);
    }, defineReactive: function(data, key, val) { // 对某个属性进行劫持
    var dep = new Dep(); // 在每个属性进行劫持前创建一个对应的 dep 对象
    var childObj = observe(val); // 递归实现属性下所有层次属性的劫持 Object.defineProperty(data, key, {
    enumerable: true, // 可枚举
    configurable: false, // 不能再define
    get: function() {
    if (Dep.target) {
    dep.depend();
    }
    return val;
    },
    set: function(newVal) {
    if (newVal === val) {
    return;
    }
    val = newVal; childObj = observe(newVal); // 新的值是object的话,进行监听 dep.notify();// 通知订阅者
    }
    });
    }
    }; function observe(value, vm) {
    if (!value || typeof value !== 'object') {
    return;
    } return new Observer(value);
    }; var uid = 0; function Dep() {
    this.id = uid++;
    this.subs = [];
    } Dep.prototype = {
    addSub: function(sub) {
    this.subs.push(sub);
    }, depend: function() {
    Dep.target.addDep(this);
    }, removeSub: function(sub) {
    var index = this.subs.indexOf(sub);
    if (index != -1) {
    this.subs.splice(index, 1);
    }
    }, notify: function() {
    this.subs.forEach(function(sub) {
    sub.update();
    });
    }
    }; Dep.target = null;

5

5

5

55

5

5

vue_源码 原理 剖析的更多相关文章

  1. ArrayList源码深度剖析,从最基本的扩容原理,到魔幻的迭代器和fast-fail机制,你想要的这都有!!!

    ArrayList源码深度剖析 本篇文章主要跟大家分析一下ArrayList的源代码.阅读本文你首先得对ArrayList有一些基本的了解,至少使用过它.如果你对ArrayList的一些基本使用还不太 ...

  2. Redis 源码简洁剖析 03 - Dict Hash 基础

    Redis Hash 源码 Redis Hash 数据结构 Redis rehash 原理 为什么要 rehash? Redis dict 数据结构 Redis rehash 过程 什么时候触发 re ...

  3. HashMap源码深度剖析,手把手带你分析每一行代码,包会!!!

    HashMap源码深度剖析,手把手带你分析每一行代码! 在前面的两篇文章哈希表的原理和200行代码带你写自己的HashMap(如果你阅读这篇文章感觉有点困难,可以先阅读这两篇文章)当中我们仔细谈到了哈 ...

  4. ArrayDeque(JDK双端队列)源码深度剖析

    ArrayDeque(JDK双端队列)源码深度剖析 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列 ...

  5. JDK数组阻塞队列源码深入剖析

    JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配版的数组阻塞队列.在 ...

  6. Contiki源码+原理+功能+编程+移植+驱动+网络(转)

    源:Contiki源码+原理+功能+编程+移植+驱动+网络 请链接:http://www.rimelink.com/nd.jsp? id=31&_np=105_315 假设您对于用Contik ...

  7. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  8. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

  9. libevent源码深度剖析十一

    libevent源码深度剖析十一 ——时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

随机推荐

  1. Entity Framework入门教程(9)---离线场景附加实体图集到上下文

    附加离线实体图集到上下文 这节主要内容是通过不同的方法将离线实体附加到上下文中. 在离线场景中,保存一个实体要略微困难一些.当我们保存一个离线的实体图集或一个单独的离线实体时,我们需要做两件事.首先, ...

  2. EF CodeFirst系列(8)--- FluentApi配置单个实体

    我们已经知道了在OnModelCreating()方法中可以通过FluentApi对所有的实体类进行配置,然而当实体类很多时,我们把所有的配置都放在OnModelCreating()方法中很难维护.E ...

  3. golang命令行库cobra使用

    github地址:https://github.com/spf13/cobra Cobra功能 简单子命令cli 如  kubectl verion    kubectl get 自动识别-h,--h ...

  4. [再寄小读者之数学篇](2014-06-26 Logarithmical Sobolev inequality using BMO space)

    $$\bex q>3\ra \sen{\n f}_{L^\infty} \leq C(q)\sez{ 1+\sen{\n f}_{BMO} \ln^\frac{1}{2}\sex{e+\sen{ ...

  5. Django REST Framework API Guide 06

    本节大纲 1.Validators 2.Authentication Validators 在REST框架中处理验证的大多数时间,您将仅仅依赖于缺省字段验证,或在序列化器或字段类上编写显式验证方法.但 ...

  6. django drf 基础学习2

    DRF基本程序调用一 models初步编写  1 编写model.py    from django.db import models 导入    class dbinfo(models.Model) ...

  7. 排序算法以及其java实现

    一.术语了解 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面: 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面: 内排序:所有排序操作都在内存中完成: 外排序:由 ...

  8. Quartz+TopShelf实现定时任务

    转自 https://www.cnblogs.com/frozenzhang/archive/2016/04/29/5443778.html 1.创建控制台程序 2.添加引用 添加TopShelf的引 ...

  9. MySQL基础使用

    数据库 其实我们常常说的数据库,应该叫数据库系统. 表和库 数据表:用来保存数据的表格 数据库:用来统一管理数据表的容器 启动mysql 关闭mysql service mysqld start(启动 ...

  10. 03-django模型(1)

    一.内容回顾 1.路由层 a.简单使用 b.有名分组 c.路由分发 d.反向解析 2.视图层 a.HttpRequest对象 常用的属性 常用方法 b.HttpResponse对象 响应三剑客 3.模 ...