0
点赞
收藏
分享

微信扫一扫

linux查询进程的启动时间

文风起武 2024-12-03 阅读 8

使用简单的代码逻辑,理一理实现逻辑
为了方便理解,案例中,没有使用虚拟dom和抽象语法树,是通过直接操作dom来实现的

1.模板语法

先看一个简单的实现:

this.compile( this.$el );

大概就是以此来实现modal中的数据,渲染到View中

html:

<body>
<div id='app'>
	<h1> {{   str  }} </h1>
	{{   str  }}
	<p>{{b}}</p>
</div>
<script type="text/javascript" src='vue.js'></script>
<script type="text/javascript">
new Vue({
	el:'#app',
	data : {
		str:'你好',
		b:'这也是data的数据'
	}
})
</script>
</body>

vue.js

class Vue{
	constructor( options ){
	    // 获取到#app
		this.$el = document.querySelector(options.el);
		// 获取到data数据
		this.$data = options.data;
		// 执行模板解析
		this.compile(  this.$el );
	}

	compile( node ){
		node.childNodes.forEach((item,index)=>{
			// 如果是元素节点 说明还有子,继续递归
			if( item.nodeType == 1 ){
				this.compile(  item );
			}
			// 如果是文本节点,如果有{{}}就替换成数据
			if( item.nodeType == 3 ){
				//正则匹配{{}}
				let reg = /\{\{(.*?)\}\}/g;
				let text = item.textContent;
				//给节点赋值
				item.textContent = text.replace(reg,(match,vmKey)=>{
					// 排除空格 拿到属性名称
					vmKey = vmKey.trim();
					//属性名称 在$data中取值
					return this.$data[vmKey];
				})
			}
		})
	} 
}

2.生命周期执行顺序:

在编写代码时,不管外面怎么写顺序,内部生命周期执行顺序是固定的,不受顺序影响

class Vue{
	constructor( options ){
	
		// 1.执行beforeCreate  并绑定this
		if(  typeof options.beforeCreate == 'function' ){
			options.beforeCreate.bind(this)();
		}
		// 挂载data 从这里之后可以获取数据 this.$data值
		this.$data = options.data;
		
		// 2.执行created 并绑定this
		if(  typeof options.created == 'function' ){
			options.created.bind(this)();
		}
		
		// 3.执行beforeMount并绑定this
		if(  typeof options.beforeMount == 'function' ){
			options.beforeMount.bind(this)();
		}
		
		//挂载 节点  从这里之后可以获取dom this.$el值
		this.$el = document.querySelector(options.el);
		
		// 4.执行mounted 并绑定this
		if(  typeof options.mounted == 'function' ){
			options.mounted.bind(this)();
		}
		// 这里之后可与获取  this.$data值 和 this.$el值
	}
}

3.添加事件

模板编译过程中,判断元素节点是否有@click,@change…事件属性,有则addEventListener添加对应事件,当触发addEventListener的时候,执行绑定方法,一般方法在methods中会定义。

class Vue{
	constructor( options ){
		this.$options = options;
		if(  typeof options.beforeCreate == 'function' ){
			options.beforeCreate.bind(this)();
		}
		this.$data = options.data;
		if(  typeof options.created == 'function' ){
			options.created.bind(this)();
		}
		if(  typeof options.beforeMount == 'function' ){
			options.beforeMount.bind(this)();
		}
		this.$el = document.querySelector(options.el);
		
		//模版解析
		this.compile(  this.$el );
		
		if(  typeof options.mounted == 'function' ){
			options.mounted.bind(this)();
		}
	}

	compile( node ){
		node.childNodes.forEach((item,index)=>{
			//元素节点
			if( item.nodeType == 1 ){
				// 判断元素节点是否绑定了@click
				if( item.hasAttribute('@click')  ){
					// @click后绑定的属性名称
					let vmKey = item.getAttribute('@click').trim();
					item.addEventListener('click',( event )=>{
					    // 查找method里面的方法  并挂载事件
						this.eventFn = this.$options.methods[vmKey].bind(this);
						// 点击后 执行方法
						this.eventFn(event);
					})
				}
				if( item.childNodes.length > 0  ){
					this.compile(  item );
				}
			}
			//这是文本节点,如果有{{}}就替换成数据
			if( item.nodeType == 3 ){
				//正则匹配{{}}
				let reg = /\{\{(.*?)\}\}/g;
				let text = item.textContent;
				//给节点赋值
				item.textContent = text.replace(reg,(match,vmKey)=>{
					vmKey = vmKey.trim();
					return this.$data[vmKey];
				})
			}
		})
	} 
}

4. 数据劫持

Object.defineProperty是 JavaScript 中的一个方法,用于在一个对象上定义一个新属性,或者修改一个现有属性的配置。
它接受三个主要参数:要定义属性的对象、属性名称(作为字符串)和一个包含属性描述符的对象。

let obj = {};
Object.defineProperty(obj, 'name', {
    value: 'John',
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(obj.name); // 输出: John

先看一个简单的实现:

class Vue{
	constructor( options ){
		this.$options = options;
		
		if(  typeof options.beforeCreate == 'function' ){
			options.beforeCreate.bind(this)();
		}
		// 这是data
		this.$data = options.data;
		// 处理数据
		this.proxyData();
		
		if(  typeof options.created == 'function' ){
			options.created.bind(this)();
		}
		if(  typeof options.beforeMount == 'function' ){
			options.beforeMount.bind(this)();
		}
		this.$el = document.querySelector(options.el);
		if(  typeof options.mounted == 'function' ){
			options.mounted.bind(this)();
		}
		
	}
	//1、给Vue大对象赋属性,来自于data中
	//2、data中的属性值和Vue大对象的属性保持双向(劫持)
	proxyData(){
		for( let key in this.$data ){
			Object.defineProperty(this,key,{
				get(){
				   // 取值劫持
					return this.$data[key];
				},
				set( val ){
					// 设置值劫持
					this.$data[key] = val;
				}
			})
		}
	}
}

5. 依赖收集

Dep:依赖收集器类的简单结构示例,用于依赖收集和通知更新

class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(watcher) {
        this.subs.push(watcher);
    }
    notify() {
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}
function defineReactive (obj, key, val) {
    var dep = new Dep();
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            if (Dep.target) {
            // 依赖收集
                dep.addSub(Dep.target);
            }
            return val;
        },
        set: function (newVal) {
            if (newVal === val) return;
            val = newVal;
            dep.notify();
        }
    });
}

上溯代码中, if (Dep.target) 为ture ,才会依赖收集,那什么时候 if (Dep.target) 为ture?

只有当 模板渲染场景 计算属性场景computed 监听器场景watch 情况下才会创建一个watcher ,调用watcher.get获取数据,把watcher实例赋值给Dep.target,触发依赖收集。

模板渲染场景:
 1.插值表达式  {{}}
 2.指令绑定条件 v-bind:class="activeClass"
 3.循环指令 v-if  v-for

例如:下面是一个模板渲染场景的插值表达式情况,生成watcher,第三个参数是是监听到更改值的时候,调用的函数。

function generateRenderFunction(ast) {
    // 遍历节点
    ast.nodes.forEach(node => {
        if (node.type === 'Interpolation') {
            let propertyName = node.content;
            let watcher = new Watcher(vm, propertyName, () => {
                // 当数据变化时更新节点内容
                updateNode(node, vm[propertyName]);
            });
        }
    });
}
Watcher.prototype.get = function () {
	// Dep.target 表是当前是有监听的
    Dep.target = this;
    
    // 然后去取值 走到defineProperty中的get方法中,判断 Dep.target不为空,依赖收集
    var value = this.getter.call(this.vm);
    
    // 依赖收集后,清空 Dep.target
    Dep.target = null;
    
    // 返回value值
    return value;
};

6. 视图更新

1.模板编译基础
在 Vue 2 中,模板编译主要分为三个阶段: 解析(parse)、优化(optimize)和代码生成(codegen)。
在解析阶段,会将模板字符串转换为抽象语法树(AST),这个 AST 包含了模板中的各种元素、指令和插值等信息。

2.解析阶段添加Watcher的线索
当解析到模板中的插值表达式(如{{ message }})或指令(如v - bind、v - model等)时,编译器会识别出对数据属性的使用。
编译器会为插值表达式创建一个对应的 AST 节点,并且在这个节点中记录下需要获取的数据属性。

例如

{
    type: 'Interpolation',
    content: 'message'
}

3.从 AST 到Watcher的创建
在代码生成阶段,编译器会根据 AST 生成渲染函数(render函数),
在这个过程中,对于每个与数据属性相关的 AST 节点,会创建一个Watcher实例来监听对应的数据变化。

function generateRenderFunction(ast) {
    // 遍历AST节点
    ast.nodes.forEach(node => {
    //发现是{{}} 插值表达式
        if (node.type === 'Interpolation') {
            let propertyName = node.content;
            // 生成watcher
            let watcher = new Watcher(vm, propertyName, () => {
                // 当数据变化时更新节点内容
                updateNode(node, vm[propertyName]);
            });
        }
    });
    // 根据AST和创建的Watcher等生成完整的渲染函数
}
  • 当发现类型为Interpolation的节点(插值表达式)时,会提取出相关的数据属性名(propertyName),然后创建一个Watcher实例。
  • 这个Watcher的getter函数会获取对应的vm[propertyName]的值,并且在数据变化时,会执行一个回调函数来更新对应的节点内容(updateNode函数,这里假设它用于更新节点)。

Watcher 内容:

function Watcher (vm, expOrFn, cb, options) {
    this.vm = vm;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
}
Watcher.prototype.get = function () {
	// Dep.target 表是当前是有监听的
    Dep.target = this;
    var value = this.getter.call(this.vm);
    Dep.target = null;
    return value;
};
Watcher.prototype.update = function () {
    // 处理更新逻辑,可能是异步或同步更新
    if (this.lazy) {
        this.dirty = true;
    } else if (this.sync) {
        this.run();
    } else {
        queueWatcher(this);
    }
};
Watcher.prototype.run = function () {
    var value = this.get();
    var oldValue = this.value;
    this.value = value;
    if (this.cb) {
        this.cb.call(this.vm, value, oldValue);
    }
};

7、虚拟 DOM 和 真实DOM(概念、作用)

1.1 概念

真实 DOM(Document Object Model):是浏览器中用于表示文档结构的树形结构。

<h2>你好</h2>

虚拟DOM:用 JavaScript 对象来模拟真实 DOM 的结构

{
  children: undefined
  data: {}
  elm: h1
  key: undefined
  sel: "h1"
  text: "你好h1"
}

(Vue.js 在早期开发过程中借鉴了 Snabbdom 的设计理念来构建自己的虚拟 DOM 系统)

1.2 作用

性能优化方面

真实DOM

虚拟DOM

8、Diff 算法

(源码地址)

它的主要作用是比较新数据与旧数据虚拟 DOM 树的差异,从而找出需要更新的部分,以便将这些最小化的变更应用到真实 DOM上,减少不必要的 DOM 操作,提高性能。
在这里插入图片描述

  1. 首先sameVNode 比较一下新旧节点是不是同一个节点(同级对比,不跨级)

2.当节点类型相同的时候,Diff 算法会比较节点的属性是否有变化。如果属性有变化,就更新真实 DOM 节点的属性。

3.当节点类型,属性都相同,则比较是否存在子节点,
3.1子节点是文本节点

4.如果新节点和老节点都有子节点,需要进一步比较(双端diff核心updateChildren)

那怎么优化这个算法呢?

开启一个循环,循环的条件就是 oldStart 不能大于oldEnd ,newStart不能大于newEnd,以下是循环的重要判断

  • 跳过undefined **if (isUndef(oldStartVnode))**
  • 快捷对比(https://www.jianshu.com/p/b9916979a740)**4个 else if(sameVnode(xxx))**
  • key值查找(2.快捷对比都不满足的情况下) **}else {**

将旧节点数组剩余的vnode(oldStartIdx到oldEndIdx之间的节点)进行一次遍历,生成由vnode.key作为键,idx索引作为值的对象oldKeyToIdx,然后遍历新节点数组的剩余vnode(newStartIdx 到 newEndIdx 之间的节点),根据新的节点的key在oldKeyToIdx进行查找。

以上是while内部处理,以下是while外部处理

  • 剩余元素处理(不满足循环条件后退出,循环外处理剩余元素)循环外

以上便是vue2的diff的核心流程了,具体案例参考这里

什么是MVVM

1.概念
它主要目的是分离用户界面(View)和业务逻辑(Model),并通过一个中间层(ViewModel)来进行数据绑定和交互。
这种模式能够使代码更加清晰、易于维护和扩展。

  • MVVM 模式的优势在于它能够很好地分离,这使得代码的维护和扩展变得更加容易。
  • 开发人员专注于 Model 的业务逻辑,设计人员专注于 View 的界面设计, ViewModel 则负责两者之间的沟通和协调。

web1.0时代

web2.0时代

MVC、MVVM 前端框架

举报

相关推荐

0 条评论