侧边栏壁纸
博主头像
xuesheng博主等级

分享web知识,学习就是取悦自己!

  • 累计撰写 118 篇文章
  • 累计创建 14 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

Vue2源码分析

xuesheng
2019-03-22 / 0 评论 / 1 点赞 / 231 阅读 / 12,428 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-02-19,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Vue架构概览-src

  • ./compiler目录是编译模板
  • ./core目录是vue的核心(也是后面的重点)
  • ./entries目录是生产打包的入口(在之前的版本中存在)
  • ./platforms目录是针对核心模块的‘平台’模块
    • platforms目录下有:web目录、weex目录
    • web目录下有对应的./comliler、./runtime、./server、./util目录
  • ./server目录是处理服务器渲染
  • ./sfc目录处理单文件vue
  • ./shared目录提供全局用到的工具函数

vue在编译的时候能够编译很多个版本,在不同场景下使用

  • 位置:build/config
  • 编译的过程并不是直接输出*.min.js,而是build出多个模板环境
    • web-full-prod生成全部的生产环境
    • web server renderer-CommonJS专门给服务器端使用的CommonJS环境
  • weex-factory专门提供给weex环境使用
  • 结论:Vue.js的组成是由 core + 对应的 ‘平台’ 补充代码构成(独立构建和运行时构建 只是 platforms 下 web 平台的两种选择)。

core解剖

vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件

  • components:模板编译的代码
  • global-api:最上层的文件接口
  • instance:生命周期->init.js
  • observer:数据收集与订阅
  • util:常用工具方法类
  • vdom:虚拟dom

双向绑定(响应式原理)所涉及到的技术

Object.defineProperty
Observer
Watcher
Dep
Directive

实现数据绑定的做法:

  • 发布者-订阅者模式:backbone.js
  • 脏值检查:angular.js
  • 数据劫持:vue.js

MVC、MVP、MVVM

MVC

M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

  • View传送指令到Controller
  • Controller完成业务逻辑后,要求Model改变状态
  • Model将新的数据发送到View,用户得到反馈

用户可以通过改变View或者改变Controller来达到目的

MVP

MVP的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter(MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过Controller

  • 各部分之间的通信,都是双向的
  • View与Model不发生练习,都是通过Presenter传递
  • View非常薄,不部署任何逻辑,称为‘被动视图’,重点是没有主动性

MVVM

MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。

M是逻辑方法加上数据,V就是用户看到的界面,VM就是逻辑方法加上界面渲染的代码,例如HTML。

核心是:提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。

  • 和MVP基本上模式完全一样
  • 唯一的区别就是,MVVM采用双向绑定,view具有主动性

Object.defineProperty()

分为数据属性和访问器属性

  • 作用:修改属性默认的特性,即控制变量的变量
  • 这里用到的数访问器属性操作中的,getter和setter函数
var book = {
    _year:2004,
    edition:1
};
Object.defineProperty(book,"year",{
    get:function(){
        return this._year;
    },
    set:function(newVal){
        if(newVal>2004){
            this._year = newVal;
            this.edition += newVal -2004;
        }
    }
});

console.log(book.year)
// 这里访问Object.defineProperty定义的属性
book.year = 2005;
console.log(book.edition);

兼容性

  • IE8是第一个实现Object.defineProperty方法的浏览器版本
  • 然后,这个版本的实现存在诸多限制:只能在DOM对象上使用这个方法,而且只能创建访问器属性

设计模式

什么是设计模式?

一套可以被复用的,编目(标准和规则)分明的经验总结

作用

让我们写的代码可复用,提高我们的代码的可维护性
代码更容易被他人理解、保证代码可靠性

构成

创建型设计模式:解决对象在创建时产生的一系列问题
结构型设计模式:解决类或对象之间组合时产生的一些列问题
行为型设计模式:解决类或对象之间的交互以及职责分配的一些问题

  • 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式

设计模式、框架、架构名词解释

设计模式

  • 就是可以被复用,众人知晓,编目分明的经验的总结,侧重于解决某个(些)问题的

框架

  • 在某一些软件领域中,将公用的部分提取,抽象出来形成统一的整体,往往是一个半成品,我们需要对他们进行再次加工完成项目的开发,设计了一套思想,引导我们去实现

架构

设计的蓝图,没有具体的实现,框架是一种实现了的架构

设计模式和框架的区别

  • 设计模式是一个单一的思想,存在就是为了解决某一类问题
  • 框架是一套思想的统一,因此可以包含多个设计模式,他们在解决问题的思想上是统一的(一个框架可以包含多个设计模式,是设计模式的结晶)

工具库

  • 只是一些方法的封装,每一个方法之间具有独立性
  • 框架也是一套方法的封装,框架中的方法彼此之间是有联系的,彼此分工合作实现需求

观察者模式

定义

  • 定义对象间的一种一对多的依赖关系。
  • 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

观察者模式流程

  • 订阅者也被称为观察者(Observer),订阅者是最常用的一种观察者模式的实现
  • 观察者观察的对象,即目标(subject)

观察者和订阅者的具体区别

发布 + 订阅 = 观察者模式

  • 在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接受事件并作出响应
  • 在发布者模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接受事件,另一方面想订阅者发布事件;订阅者需要从事件通道订阅事件
// 基于jQuery
(function($){
    var o = $({});
    $.subscribe = function(){
        o.on.apply(o, arguments);
    };
    $.unsubscribe = function(){
        o.off.apply(o, arguments);
    };
    $.publish = function(){
        o.trigger.apply(o, arguments);
    };
}(jQuery));
// 订阅
$.subscribe('/some/topic',function(e,a,b,c){
    console.log(a+b+c);
});
// 发布
$.publish('/some/topic',['a','b','c']);
// 退订
$.unsubscribe('/some/topic');

事件队列:异步macrotask宏队列 和 microtask微队列 简介

事件队列,通过eventLoop实现快速处理
先执行,microtask:promise、observe
最后执行,macrotask:ajax、click、set Timeout

// node运行
setTimeout(function(){
    console.log(5);
},0);
setImmediate(function(){
    console.log(6);
});
new Promise(function(resolve){
    console.log(1);
    resolve();
    console.log(2);
}).then(function(){
    console.log(4);
});
console.log('哈哈哈');
process.nextTick(function(){
    console.log(3);
});
// 依次输出
// 1 2 哈哈哈 3 4 5 6

为什么js是单线程?

  • js之所以采用单线程,原因是一开始设计的时候不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。
  • 比如,假定js同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
  • 在Java中会使用锁来解决这种竞态条件,而js并不想这样来解决。
  • 单线程模型带来的问题:主要是新的任务被加在队列的尾部,只有前面的所有任务运行结束,才会轮到它执行。如果有一个任务特别耗时,后面的任务都会停在那里等待,造成浏览器失去响应,又称“假死”。为了避免“假死”,当某个操作在一定时间后仍无法结束,浏览器就会跳出提示框,询问用户是否要强行停止脚本运行。
  • javascript 是的单线程的,于是就产生了一种任务执行机制叫 eventloop。它维护了一个任务队列,完成一个任务才会开始下一个任务。

setstate

  • React通过this.state来访问state,通过this.setState()方法来更新state。当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UI
  • Vue的DOM更新是异步的,所以如果需要等待DOM更新后执行操作就需要:
this.show = false
// 这里data更新了,但对应的DOM还没更新
Vue.nextTick(() => {
  // 这里跟`show`对应的DOM也被更新了
})

类似React里this.setState(state, callback)中的callback

vue的实际问题

  • 在我们使用vue进行开发的过程中,可能会遇到一种情况:当生成vue实例后,当再次给数据赋值时,有时候并不会自动更新到视图上去;所以一般是把自己用到的值提前的保存在data中,避免这种问题的发生

  • 例子:

var vm = new Vue({
    el:'#vm_box',
    data:{
        btn:'阿牛',
        box:'内容'
    }
})
vm.$data.sex = '男'
console.log(vm.$data);
  • 根据上面的原理图,能够明白,当后添加属性的时候,并没有触发Watcher

  • 所以在Vue3.0把这套原理删除掉,采用es6提出的Proxy和Reflect

es6 proxy代理

扩展(增强)对象一些功能

作用:

  • vue中的拦截
  • 预警、上报、扩展功能、统计、增强对象等等
  • 深入来说,几乎所有的需求都可以用到
  • proxy是设计模式的一种,代理模式

当访问对象没有的属性,抛出错误

const obj = {
    name:'怪岸咖啡'
};
const newObj = new Proxy(obj,{
    get(target,property){
        if(property in target){
            return target[property];
        }else{
            console.warn('^_^')
            throw new ReferenceError(`${property}属性不在此对象上`);
        }
    }
});
console.log(newObj.name);
console.log(newObj.a);

Reflect反射

  • 操作对象而提供的API
  • 方便了我们进行各种之前的操作,让其更加的合理和友好

实例:使用Proxy实现观察者模式

  • 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
// 观察者函数集合
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
// 返回一个原始对象Proxy代理
const observable = obj => new Proxy(obj, {set});
// 数据对象person是观察目标
const person = observable({
  name: '张三',
  age: 20
});

observe(print);
person.name = '李四';

// 函数print是观察者,数据对象发生变化,print就会自动执行
function print() {
  console.log(`${person.name}, ${person.age}`)
}
// 拦截函数set,会自动执行所有观察者
function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

虚拟dom-Virtual DOM

为什么说操作DOM是昂贵的?

  • 直接代码
var dom = document.getElementById('vm_box');
var arr = [];
for(var item in dom){
    arr.push(item);
}
console.log(JSON.stringify(arr));

一个基本的DOM身上绑定了235个属性和方法

  • 什么叫牵一发而动全身
  • 什么叫庞然大物
  • 这只是第一层,还可以多维遍历
["align","title","lang","translate","dir","dataset","hidden","tabIndex","accessKey","draggable","spellcheck","contentEditable","isContentEditable","offsetParent","offsetTop","offsetLeft","offsetWidth","offsetHeight","style","innerText","outerText","onabort","onblur","oncancel","oncanplay","oncanplaythrough","onchange","onclick","onclose","oncontextmenu","oncuechange","ondblclick","ondrag","ondragend","ondragenter","ondragleave","ondragover","ondragstart","ondrop","ondurationchange","onemptied","onended","onerror","onfocus","oninput","oninvalid","onkeydown","onkeypress","onkeyup","onload","onloadeddata","onloadedmetadata","onloadstart","onmousedown","onmouseenter","onmouseleave","onmousemove","onmouseout","onmouseover","onmouseup","onmousewheel","onpause","onplay","onplaying","onprogress","onratechange","onreset","onresize","onscroll","onseeked","onseeking","onselect","onstalled","onsubmit","onsuspend","ontimeupdate","ontoggle","onvolumechange","onwaiting","onwheel","onauxclick","ongotpointercapture","onlostpointercapture","onpointerdown","onpointermove","onpointerup","onpointercancel","onpointerover","onpointerout","onpointerenter","onpointerleave","nonce","click","focus","blur","ontouchcancel","ontouchend","ontouchmove","ontouchstart","namespaceURI","prefix","localName","tagName","id","className","classList","slot","attributes","shadowRoot","assignedSlot","innerHTML","outerHTML","scrollTop","scrollLeft","scrollWidth","scrollHeight","clientTop","clientLeft","clientWidth","clientHeight","onbeforecopy","onbeforecut","onbeforepaste","oncopy","oncut","onpaste","onsearch","onselectstart","previousElementSibling","nextElementSibling","children","firstElementChild","lastElementChild","childElementCount","onwebkitfullscreenchange","onwebkitfullscreenerror","setPointerCapture","releasePointerCapture","hasPointerCapture","hasAttributes","getAttributeNames","getAttribute","getAttributeNS","setAttribute","setAttributeNS","removeAttribute","removeAttributeNS","hasAttribute","hasAttributeNS","getAttributeNode","getAttributeNodeNS","setAttributeNode","setAttributeNodeNS","removeAttributeNode","closest","matches","webkitMatchesSelector","attachShadow","getElementsByTagName","getElementsByTagNameNS","getElementsByClassName","insertAdjacentElement","insertAdjacentText","insertAdjacentHTML","requestPointerLock","getClientRects","getBoundingClientRect","scrollIntoView","scrollIntoViewIfNeeded","animate","before","after","replaceWith","remove","prepend","append","querySelector","querySelectorAll","webkitRequestFullScreen","webkitRequestFullscreen","scroll","scrollTo","scrollBy","createShadowRoot","getDestinationInsertionPoints","ELEMENT_NODE","ATTRIBUTE_NODE","TEXT_NODE","CDATA_SECTION_NODE","ENTITY_REFERENCE_NODE","ENTITY_NODE","PROCESSING_INSTRUCTION_NODE","COMMENT_NODE","DOCUMENT_NODE","DOCUMENT_TYPE_NODE","DOCUMENT_FRAGMENT_NODE","NOTATION_NODE","DOCUMENT_POSITION_DISCONNECTED","DOCUMENT_POSITION_PRECEDING","DOCUMENT_POSITION_FOLLOWING","DOCUMENT_POSITION_CONTAINS","DOCUMENT_POSITION_CONTAINED_BY","DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC","nodeType","nodeName","baseURI","isConnected","ownerDocument","parentNode","parentElement","childNodes","firstChild","lastChild","previousSibling","nextSibling","nodeValue","textContent","hasChildNodes","getRootNode","normalize","cloneNode","isEqualNode","isSameNode","compareDocumentPosition","contains","lookupPrefix","lookupNamespaceURI","isDefaultNamespace","insertBefore","appendChild","replaceChild","removeChild","addEventListener","removeEventListener","dispatchEvent"]

虚拟dom诞生

  • 相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来:
// html
<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>
// js
var element = {
  tagName: 'ul', // 节点标签名
  props: { // DOM的属性,用一个对象存储键值对
    id: 'list'
  },
  children: [ // 该节点的子节点
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
  ]
}

  • 从上面能够明白,虚拟dom是把我们要做的事情,最简单最直接的表现出来
  • 没有任何的附加条件,而用dom操作一个dom第一层就有200多个属性和方法,可想而知多个dom是多么可怕的消耗
  • 思考牵引:类似我们使用display:none/block;来回切换介绍优化性能,是一个道理,就是让我们最直接的做自己想做的事情

document.createDocumentFragment

  • 创建一个虚拟的节点对象或者说,用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的
  • 文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段document fragments 通常会起到优化性能的作用
1

评论区