目录

  • 为什么使用 Vue?
  • 你会学到什么?
  • 带着问题去学习
  • Vue2.0整体概括
    • 1、传入实例参数
    • 2、设置数据劫持(Object.defineProperty)
    • 3、模板编译(Compile)
    • 4、虚拟DOM(Virtual DOM)
    • 5、对比新老虚拟DOM(Patch)
    • 6、更新视图(Update View)
  • 实现一个input双向绑定(v-model)
    • 一、响应式原理
      • 1、初始化
      • 2、数据劫持
      • 3、监听对象
      • 4、监听数组
      • 5、computed实现
      • 6、methods实现
      • 7、vm.$data代理
    • 二、依赖收集
      • 1、为什么进行依赖收集
      • 2、观察者 Watcher
      • 3、订阅者 Dep
      • 4、依赖收集
    • 三、编译模板
      • 1、将 DOM 拿到内存
      • 2、数据替换
      • 3、塞回页面

为什么使用 Vue?

从前端这么些年的发展史来看,从网页设计年代到了现在大前端时代的来临,各种各样的技术层出不穷。尤其是在前端性能优化方面,为了避免页面的回流和重绘,前辈们总结出了各种解决优化方案,基本都是尽量的减少 DOM 操作。

Vue 的诞生,是一个很大的优化方案,直接用虚拟 DOM 映射真实 DOM,来进行更新,避免了直接操作真实 DOM 带来的性能缺陷。

为了好理解呢,我们换个通俗一点的说法,当页面涉及到操作 DOM 的时候,我们不直接进行操作,因为这样降低了前端页面的性能。而是将 DOM 拿到内存中去,在内存中更改页面的 DOM ,这时候我们操作 DOM 不会导致每次操作 DOM 就会造成不必要的回流和重绘。更新完所有 DOM 之后,我们将更新完的 DOM 再插入到页面中,这样大大提高了页面的性能。

虽然这样讲有些欠妥或者不标准,其实 Vue 的虚拟 DOM 的作用可以这样去理解,也是为了照顾到一些刚刚接触到 Vue 的初学者。本篇写作的目的不是去写一高大上的术语,而是能将分享到的内容让大部分看明白,就已经足够了。

你会学到什么?

本篇主要仅供个人 Vue 源码学习记录,主要以 Vue2.0 为主(如果你是个 Vue 大神,就不必浪费时间看本人的学习笔记了)

主要分享整个 Vue2.0 源码的核心功能,会将一下几个功能通过删减,通过代码对核心原理部分展开分享,一些用到的变量和函数方法可能与源码中不相同,由于时间和精力有限,只分享核心内容部分。主要包括以下几个核心部分:

  • 响应式原理(MVVM)
  • 模板编译 (Compile)
  • 依赖追踪
  • 虚拟 DOM (VDOM)
  • patch
  • diff 算法

带着问题去学习

有问题才有学习的动力和激情,如果毫无目的的只扒源码,显然是非常枯燥的,前期在挖源码的时候,小鹿是带着一下几个疑问去探索原理的,这样更具有目的性。

1、双向绑定是怎么实现的?

2、vue 标签中的指令内部又是如何解析的?

3、什么是虚拟 DOM,它比传统的真实 DOM 有什么优势?

4、当数据更新时,虚拟 DOM 如果对比新老节点更新真实 DOM 的?

5、页面多个地方操作 DOM,内部如何实现优化的?

以上几个个问题,前期给我带来了探索源码的动力。当看了源码一个月过去之后,这个期间通过动手实践和总结,发现这些东西都是在最原本的事物基础上进行改进和优化,尤其是对基本功(JS、数据结构与算法)的重要性,越是简单的东西,越是新事物的组成部分。简单,简而不单,单而不简。能让你创新出新的事物,万物皆如此。

正文

距离上次更新,已经有三个月过去了,这三个月身边发生了很多事情,真是计划不如变化。三个月时间,对之前更新的内容又做了很多的补充,2020年 5 月25日晚,发布了第一版《大前端面试小册》电子书共 74 页。

60 天呕心沥血,我写完这本电子书

电子版主要增加了一下加点:

知识点内容上,都是从知其然,知其所以然开始写起的。后续为了将知识体系化,增加了知识线,
每个知识点,增加了大厂面试笔试题解析。

增加了不少图解。

《大前端面试小册》电子书获取方式,公众号:「小鹿动画学编程」后台回复「前端PDF」获取第一>版本。

Vue2.0 整体概括

在分享各个 Vue 源码中几个核心内容之前,小鹿先把整体的核心原理实现概括一遍,也就是大体说说 Vue 是如何工作的,这为了在分享前更好的建立起一个知识体系。把握整体,各个击破!

Vue 核心原理

初始化 Vue 实例 ==> 挂载实例(mount) ==> 设置数据劫持(Object.defineProperty) ==> 模板编译(compile) ==> 渲染(render function) ==> 转化为虚拟 DOMObject> 对比新老虚拟DOMpatch、diff> 更新视图

1、传入实例参数

当我们开始写 Vue 项目时,首先初始化一个 Vue 实例,传入一个对象参数,参数中包括一下几个重要属性:

{el: '#app',data: {student: {name: '公众号:小鹿动画学编程',age: 20,}}computed:{...}...
}
  • el:将渲染好的 DOM 挂载到页面中(可以传入一个 id,也可以传入一个 dom 节点)。
  • data:页面所需要的数据(对象类型,至于为什么,会在数据劫持内容说明)。
  • computed:计算属性,随着 data 中的数据变化,来更新页面关联的计算属性。
  • methods:实例所用到的方法集合。

除此之外,还有一些生命周期钩子函数等其他内容。

2、设置数据劫持(Object.defineProperty)

所谓的数据劫持,当 Vue 实例上的 data 中的数据改变时,对应的视图所用到的 data 中数据也会在页面改变。所以我们需要给 data 中的所有数据设置一个监听器,监听 data 的改变和获取,一旦数据改变,监听器会触发,通知页面,要改变数据了。

 Object.defineProperty(obj, key, {get() {return value;},set: newValue => {console.log(---------------更新视图--------------------)}}

数据劫持的实现就是给每一个 data绑定 Object.defineProperty()。对于 Object.defineProperty()的用法,自己详细看 MDN (Object.defineProperty()),这也是 MVVM的核心实现 API,下遍很多东西都是围绕着它转。

3、模板编译(compile)

1、拿到传入 dom 对象和 data 数据了,如果将这些 data 渲染到 HTML 所对应的 {{student.age}}v-model="student.name" 等标签中,这个过程就是模板编译的过程之一,主要解析模板中的指令、class、style等等数据。

2、标记静态结点。为了在更新视图时,也就是下一篇文章要分享到的 patch 过程,跳过这些静态结点,也就是这些没有变化的结点,这样更新起来性能更佳。

3、最后,将其转化为 render 字符串。

// 把当前节点放到内存中去(因为频繁渲染造成回流和重绘)
let fragment = this.nodefragment(this.el);// 把节点在内存中替换(编译模板,数据编译)
this.compile(fragment);// 把内容塞回页面
this.el.appendChild(fragment);

我们通过 el 拿到 dom 对象,然后将这个当前的 dom 节点拿到内存中去,然后将数据和 dom 节点进行替换合并,然后再把结果塞会到页面中。下面会根据代码实现,具体展开分享。

4、虚拟 DOM(Virtual DOM)

所谓虚拟 DOM,其实就是一个 javascript对象,说白了就是对真实 DOM 的一个描述对象,和真实 dom做一个映射。

// 真实 DOM
<div><span>HelloWord</span>
</div>// 虚拟 DOM —— 以上的真实 DOM 被虚拟 DOM 表示如下:
{children:(1) [{}]  // 子元素domElement: div		// 对应的真实 dom	key: undefined      // key 值props: {}           // 标签对应的属性text: undefined     // 文本内容type: "div"         // 节点类型...
}

一旦页面数据有变化,我们不直接操作更新真实 DOM,而是更新虚拟 DOM,又因为虚拟 DOM和真实 DOM有映射关系,所有真实 DOM也被更新,避免了回流和重绘造成性能上的损失。

对于虚拟 DOM,主要核心涉及到 diff算法,新老虚拟结点如何检查差异的,然后又是如何进行更新的,后边会展开一点点讲。

5、对比新老虚拟 DOM(patch)

patch 主要是对更新后的新节点和更新前的节点进行比对,比对的核心算法就是 diff 算法,比如新节点的属性值不同,新节点又增加了一个子元素等变化,都需要通过这个过程,将最后新的虚拟 DOM 更新到视图上,呈现最新的变化,这个过程是一个核心部分,面试也是经常问到的。

6、更新视图(update view)

当第一次加载 Vue 实例的时候,我们将渲染好的数据挂载到页面中。当我们已经将实例挂载到了真实 dom 上,我们更新数据时,新老节点对比完成,拿到对比的最新数据状态,然后更新到视图上去。

注意:以下代码并非原封不动的源代码,为了能够清晰易懂,只是将一些核心原理进行抽离,通过自己实现的代码来展开分享,为了避免不必要的争议,请自行翻看源代码。

实现一个 input 双向绑定(v-model)

一、响应式原理

我们都用过 Vue 中的 v-model 实现输入框和数据的双向绑定,其实就是 MVVM框架的核心原理实现。

如果刚接触 MVVM,可以看小鹿之前在公众号分享的一篇文章:动画:浅谈后台 MVC 模型与 MVVM 双向绑定模型

下面我们动手来实现一个 MVVM 双向绑定。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head><body><div id="app"><input type="text" v-model="student.name">{{student.age}}</div><script src="./node_modules/vue/dist/vue.min.js"></script> <script>let vm = new Vue({el: '#app',data: {student: {name: '公众号:小鹿动画学编程',age: 20,}}})</script>
</body></html>

1、初始化

初始化 Vue 实例,这个过程会做很多事情,比如初始化生命周期、datacomputedMethod 等。我们将实例中传入的数据,进行在构造函数中接收。

class Vue {// 传参接收constructor(options) {this.$el = options.el;this.$data = options.data;let computed = options.computed;let methods = options.methods;// 判断 $el 根元素是否存在if (this.$el) {// 1、数据劫持new Observer(this.$data);// 2、computed 实现this.relatedComputed(computed);// 3、methods 实现this.relatedMethods(methods);// 4、编译模板new Compile(this.$el, this);// ....}}
}

以上代码中,判断当前 $el 是否存在,如果存在,就开始初始化响应式系统以及 computedmethods的实现,最后编译模板,显示在视图上。

2、数据劫持

响应式的原理就是通过 Object.defineProperty 数据劫持来实现的,也就上述代码中的 new Observer(this.$data)过程,这个过程发生了什么?以及如何对 data 中各种类型数据进行监听的,下面直接看核心实现原理部分。

先看整体的实现代码,然后分别进行拆分讲解:

class Observer {constructor(data) {this.observer(data);}// 观察者(监听对象的响应式)observer(obj) {// 判断是否为对象if (typeof obj !== "object" || obj == null) return obj;// 实时响应数组中对象的变化if (Array.isArray(obj)) {Object.setPrototypeOf(obj, proto);this.observerArray(obj);} else {// 遍历对象 key value 监听值的变化for (let key in obj) {this.defineReactive(obj, key, obj[key]);}}}defineReactive(obj, key, value) {// value 可能是对象,需要进行递归this.observer(value);Object.defineProperty(obj, key, {get() {return value;},set: newValue => {if (newValue !== value) {// 传入的可能也是对象,需要递归this.observer(value);value = newValue;console.log('-------------------------视图更新-----------------------------')}}});}
  • 首先,声明一个 Observer 类,接收传入 data 中要给页面渲染的数据。
class Observer {constructor(data) {this.observer(data);} 
}

调用 this.observer(data) 方法,遍历 data 中的每个数据进,都通过 Object.defineProperty() 方法设置上监听。

3、监听对象

  • observer() 方法实现主要用于实时响应数组中对象的变化。
observer(obj) {// 判断是否为对象if (typeof obj !== "object" || obj == null) return obj;// 遍历对象 key value 监听值的变化for (let key in obj) {this.defineReactive(obj, key, obj[key]);}
}defineReactive(obj, key, value) {// 递归创建 响应式数据,性能不好this.observer(value);  // 递归Object.defineProperty(obj, key, {get() {return value;},set: newValue => {if (newValue !== value) {// 设置某个 key 的时候,可能是一个对象this.observer(value);   // 递归value = newValue;console.log('-------------------------视图更新-----------------------------')}}});

data 是一个对象,我们对 data 数据对象进行遍历,通过调用 defineReactive 方法,给每个属性分别设置监听(setget 方法)。

我们对属性设置的监听,只是第一层设置了监听,如果属性值是个对象,我们也要进行监听。或者我们在给 Vue 实例 vmdata 赋值的时候,也可能是个对象,如下情况:

data: {student: {name: '小鹿',age: 20,address:{   // address 也是一个对象类型的值,需要对 address 中的属性值进行监听country:'china'province:'shandong',}}
},

所以我们要进行递归,也给其设置响应式。

...defineReactive(obj, key, value) {// 递归创建 响应式数据,性能不好this.observer(value);  // 递归...
}
......
set: newValue => {if (newValue !== value) {// 设置某个 key 的时候,可能是一个对象this.observer(value);   // 递归value = newValue;console.log('-------------------------视图更新-----------------------------')}}
。。。

设置好之后,当我们运行程序,给 vm 设置某一值的时候,会触发视图的更新。

4、监听数组

上述我们只对对象的属性进行监听,但是我们希望监听的是个数组,对于数组,用Object.defineProperty() 来设置是不起作用的(具体原因见 MDN),所以不能用此方法。

如果数组中存放的是对象,我们也应该监听属性的变化,比如监听数组中 name 的变化。

{d: [1, 2, 3, { name: "小鹿" }]
};

首先,我们判断当前传入的如果是数组类型,我们就调用 observerArray 方法。

// 判断传入的参数如果是数组,则执行 observerArray 方法
if (Array.isArray(obj)) {this.observerArray(obj);
}

observerArray 方法的具体实现如下:

// 遍历数组中的对象,并设置监听
observerArray(obj) {for (let i = 0; i < obj.length; i++) {let item = obj[i];this.observer(item);    // 如果数组中是对象会被 defineReactive 监听}
}

当我们进行下方更改值时,视图被触发更新。

// 初始化 data 中的值
{d: [1, 2, 3, { name: "小鹿" }]
}// 更改数组中的对象属性的值
vm.$data.d[3].name = "11";  // 此时视图会更新

还有一点就是,当我们给当前的数组添加元素时,也要触发视图进行更新,比如通过下方的方式更改数组。

// 通过 push 向 data 中的数组中添加一个值
vm.$data.d.push({ age: "15" });

除此之外,数组中添加数据的 APIpushunshiftsplice ,我们可以通过重写这三个原生方法,对其调用时,进行触发视图更新。

let arrProto = Array.prototype; // 数组原型上的方法
let proto = Object.create(arrProto); // 复制原型上的方法// 重写数组的三个方法
[`push`, `unshift`, `splice`].forEach(method => {proto[method] = function(...args) {// 这个数组传入的对象也应该进行监控let inserted; // 默认没有插入新的数据switch (method) {case `push`:case `unshift`:inserted = args;break;case `splice`:inserted = args.slice(2); // 截出传入的数据break;default:break;}console.log("---------------视图更新-----------------");observerArray(inserted); // 如果数组增加的值是对象类型,需要对其设置监听arrProto[method].call(this, ...args);};
});// 实时响应数组中对象的变化
if (Array.isArray(obj)) {Object.setPrototypeOf(obj, proto);  // 如果是数组,就设置重写的数组原型对象this.observerArray(obj);
} else {// 遍历对象 key value 监听值的变化for (let key in obj) {this.defineReactive(obj, key, obj[key]);}
}

好了,我们来测试一下,数组被监听到,视图已更新。

5、computed 原理实现

computed主要是计算属性,每当我们计算属性所依赖的 data 属性发生变化时,通过计算,也要更新视图上的数据。如下实例,如果我们动态改变 this.student.name 属性值,页面中的 getNewName 也会发生改变。

let vm = new Vue({el: '#app',data: {student: {name: '小鹿',age: 20,},},computed: {getNewName() {return this.student.name + ‘公众号:小鹿动画学编程’;}},
})

其实内部的原理做法就是让 computed 内的计算属性也依赖于 data 数据,data 变,computed 依赖的数据也变。

relatedComputed(computed) {for (let key in computed) {Object.defineProperty(this.$data, key, {get: () => {return computed[key].call(this);}});}
}

6、methods 原理实现

我们通常调用方法是通过 vm 实例来调用方法的,所以我们要把 methods 挂载到 vm 实例上。

// methods
relatedMethods(methods) {for (let key in methods) {Object.defineProperty(this, key, {get: () => {return methods[key];}})}
}

7、vm.$data 代理到 vm 实例上

我们一般可以通过 vm.$data.student.name = ‘小鹿’ ,但是还可以使用 vm.student.name = ‘小鹿’。我们可以通过代理,将 vm.$data 代理到 vm 上。

// 代理 vm.$data
proxyVm(data) {for (let key in data) {// 绑定到 vm 上Object.defineProperty(this, key, {get() {return data[key];},set(newValue) {data[key] = newValue;}});}
}

二、依赖收集

1、为什么进行依赖收集

我们 data 中的数据,有时候我们在页面不同地方需要使用,所以当我们动态改变 data 数据的时候,如下:

<div>{{student.name}}</div>
<ul><li>1</li><li>{{student.name}}</li>
</ul>vm.$data.student.name = 'xiaolu '

我们对视图中,所有依赖 data 属性中的值进行更新,那么我们需要对依赖的数据的视图进行数据依赖收集,当数据变化的时候,就对所依赖数据的视图更新。对于依赖收集,需要使用观察者-订阅者模式。

2、观察者 Watcher

观察中的 get() 主要用于获取当前表达式(如:student.name)的 未更新之前的值,当数据更新时,我们就调用 update 方法,就拿出新值和老值对比,如果有变化,我们就更新相对应的视图。

// 观察者
class Watcher {/*** @param {*} vm 当前实例* @param {*} expr 观察的值表达式* @param {*} cb 回调函数*/constructor(vm, expr, cb) {this.vm = vm;this.expr = expr;this.cb = cb;// 默认存放一个老值(取出当前表达式的值)this.oldValue = this.get();}get() {Dep.target = this;let value = CompileUtil.getValue(this.vm, this.expr);// 根据视图中的表达式,取 data 中的值Dep.target = null; // 不取消任何值取值 都会添加 waterreturn value;}// -> 数据变化后,会调用观察者的 update 方法update() {let newValue = CompileUtil.getValue(this.vm, this.expr);// 根据视图中的表达式,取 data 中的值if (newValue !== this.oldValue) {this.cb(newValue);}}
}

3、订阅者

订阅者中主要通过 addSub 方法增加观察者,通过 notify 通知观察者,调用观察者的 update 进行更新相应的视图。

// 订阅者
class Dep {constructor() {this.subs = []; // 存放所有的 watcher}// 订阅addSub(watcher) {this.subs.push(watcher);}// 通知notify() {this.subs.forEach(watcher => watcher.update());}
}

4、依赖收集

在我们更新视图的时候进行依赖收集,给每个属性创建一个发布订阅的功能,当我们的值在 set 中改变时,我们就触发订阅者的通知,让各个依赖该数据的视图进行更新。

defineReactive(obj, key, value) {// 递归创建 响应式数据,性能不好this.observer(value);let dep = new Dep(); // 给每一个属性都加上一个具有发布订阅的功能Object.defineProperty(obj, key, {get() {// 创建 watcher 时,会取到响应内容,并且把 watcher 放到了全局上Dep.target && dep.addSub(Dep.target);  // 增加观察者return value;},set: newValue => {if (newValue !== value) {// 设置某个 key 的时候,可能是一个对象this.observer(value);value = newValue;console.log('-------------------------视图更新-----------------------------')dep.notify(); // 通知}}});

剩下的就是我们调用 new Watcher 地方了,这个过程在编译模板里边。

三、编译模板

对于模板的编译,我们首先需要判断传入的 el 类型,然后拿到页面的结点到内存中去,把节点上有数据编译的地方,比如:v-modelv-on{{student.name}} 进行数据的替换,然后再塞回页面,就完成的页面的显示。

// 编译类
class Compile {constructor(el, vm) {// 判断 el 传入的类型this.el = this.isElementNode(el) ? el : document.querySelector(el);this.vm = vm;// 把当前节点放到内存中去 —— 之所以塞到内存中,是因为频繁渲染造成回流和重绘let fragment = this.nodefragment(this.el);// 把节点在内存中将表达式和命令等进行数据替换this.compile(fragment);// 把内容塞回页面this.el.appendChild(fragment);}
}

1、将 DOM 拿到内存

首先我们之前已经声明好 data 了,如下:

 let vm = new Vue({el: '#app',data: {student: {name: '小鹿',age: 20,},}})

然后我们需要拿到页面的模板,将页面中的一些指令(v-model="student.name")或者表达{{student.name}} 的结点替换成我们对应的属性值。

我们需要通过传入的 el 属性值先拿到页面的 dom 到内存中。

/*** 将 DOM 拿到内存中* @param {*} node DOM*/
nodefragment(node) {let fragment = document.createDocumentFragment();let firstChild;while ((firstChild = node.firstChild)) {fragment.appendChild(firstChild);}return fragment;
}

2、数据替换

我们下一步需要将页面中的这些表达式,替换成相对应的 data 中的属性值,那么页面就将完成的呈现出带有数据的视图来。

<div id="app"><input type="text" v-model="student.name">{{student.age}}
</div>

通过上边的方法,已经将所有的页面结点循环遍历拿到。下一步开始进行一层层的遍历,将数据在内存中进行替换。

/*** 核心编译方法* 编译内存中的 DOM 节点* @param {*} node*/
compile(node) {let childNodes = node.childNodes;[...childNodes].forEach(child => {// 判断当前的是元素还是文本节点if (this.isElementNode(child)) {this.compileElement(child);// 如果是元素的话,需要把自己传进去,再去遍历子节点this.compile(child);} else {this.compileText(child); // 文本节点有 {{student.age}}}});
}/*** 判断当前传入的节点是不是元素节点* @param {*} node 节点*/
isElementNode(node) {return node.nodeType == 1; // 1 代表元素节点
}

2.1 this.isElementNode(child)

页面是由很多的 node 结点构成,在上边的页面中,v-model="student.name" 主要存在与元素节点中,{{student.age}} 表达式的值存在于文本节点中,所以我们需要通过 this.isElementNode(child) 进行判断当前是否为元素节点,然后对当前节点进行不同的处理。

对于元素节点,我们调用 compileElement(child)方法,当然,元素节点中可能存在子节点的情况,所以我们需要递归判断元素节点里是否还有子节点,再次调用 this.compile(child); 方法。

我们以解析 v-model 指令为例,开始对节点进行解析判断赋值。

<input type="text" v-model="student.name">
/*** 编译元素节点 —— 判断是否存在 v- 指令* @param {*} node*/
compileElement(node) {let attributes = node.attributes; [...attributes].forEach(attr => {// type = "text" v-model="student.name"let { name, value: expr } = attr; // name:v-model  expr:"student.name"// 判断当前是否存在属性为 v- 的指令if (this.isDirective(name)) {// v-html  v-bind  v-modellet [, directive] = name.split("-");let [directiveName, eventName] = directive.split(":"); // v-on:click// 调用不同的指令来处理CompileUtil[directiveName](node, expr, this.vm, eventName);}});
}/*** 判断是够是 v- 开头的指令* @param {*} attrName*/
isDirective(attrName) {return attrName.startsWith("v-");
}

同时我们还有一个工具类 CompileUtil,主要用于把对应的 data 数据插入到对应节点中。

上一步中,我们通过 let [directiveName, eventName] = directive.split(":") 解析出了 directiveName= v-modeleventName = student.name

然后我们将两个参数 directiveNameeventName 传入工具类对象中。

// node: 当前节点  expr:当前表达式(student.name) vm:当前 vue 实例
CompileUtil[directiveName](node, expr, this.vm, eventName);

通过调用不同的指令进行不同的处理

/*** 工具类(把数据插入到 DOM 中)* expr: 指令的值(v-model="student.name" 中的 student.name)*/
let CompileUtil = {// ---------------------- 匹配指令或者表达式的函数 ----------------------// 匹配 v-modelmodel(node, expr, vm) {let fn = this.updater["modelUpdater"];new Watcher(vm, expr, newValue => {// 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 inputfn(node, newValue);});// 给 input 绑定事件node.addEventListener("input", e => {let value = e.target.value; // 获取用户输入的内容this.setValue(vm, expr, value);});let value = this.getValue(vm, expr);fn(node, value);},// ---------------- 其他用到的工具函数 -------------------// $data取值 [student, name]getValue(vm, expr) {return expr.split(".").reduce((data, current) => {return data[current];}, vm.$data);},// 给 vm.$data 中数据赋值setValue(vm, expr, value) {expr.split(".").reduce((data, current, index, arr) => {// 如果遍历取到最后一个,我就给赋值if (index == arr.length - 1) {return (data[current] = value);}return data[current];}, vm.$data);},// -------------- 给对应的 dom 进行赋值 -------------------updater: {modelUpdater(node, value) {// 处理指令结点 v-modelnode.value = value;}}
};

以上就会触发这个函数:

// 匹配 v-model
model(node, expr, vm) {let fn = this.updater["modelUpdater"];new Watcher(vm, expr, newValue => {// 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 inputfn(node, newValue);});// 给 input 绑定事件node.addEventListener("input", e => {let value = e.target.value; // 获取用户输入的内容this.setValue(vm, expr, value);});let value = this.getValue(vm, expr);fn(node, value);
},

同时我们看到了 new Watch 对该属性创建一个观察者,用于以后数据更新时,通知视图进行相应的更新的。

new Watcher(vm, expr, newValue => {// 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 inputfn(node, newValue);
});

同时又给 input 绑定了一个事件,用于实现对 input 框的监听,相对应的 data 也要更新,这就实现了v-model输入框的双向绑定功能。

// 给 input 绑定事件
node.addEventListener("input", e => {let value = e.target.value; // 获取用户输入的内容this.setValue(vm, expr, value);
});

每当 data 数据被改变,我们就触发 this.updater 中的视图更新函数。

let fn = this.updater["textUpdater"];
fn(node, value);
// 给 dom 文本结点赋值数据
updater: {modelUpdater(node, value) {// 处理指令结点 v-modelnode.value = value;}
}

对于文本节点,调用 this.compileText(child) 方法和以上同样的实现方法。这一部分的整体实现代码如下:

*** 工具类(把数据插入到 DOM)* expr: 指令的值(v-model="school.name" 中的 school.name)*/
let CompileUtil = {// $data取值 [school, name]getValue(vm, expr) {return expr.split(".").reduce((data, current) => {return data[current];}, vm.$data);},// 给 vm.$data 中数据赋值setValue(vm, expr, value) {expr.split(".").reduce((data, current, index, arr) => {// 如果遍历取到最后一个,我就给赋值if (index == arr.length - 1) {return (data[current] = value);}return data[current];}, vm.$data);},// 匹配 v-modelmodel(node, expr, vm) {let fn = this.updater["modelUpdater"];new Watcher(vm, expr, newValue => {// 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 inputfn(node, newValue);});// 给 input 绑定事件node.addEventListener("input", e => {let value = e.target.value; // 获取用户输入的内容this.setValue(vm, expr, value);});let value = this.getValue(vm, expr);fn(node, value);},html(node, expr, vm) {//xsslet fn = this.updater["htmlUpdater"];new Watcher(vm, expr, newValue => {console.log(newValue);fn(node, newValue);});let value = this.getValue(vm, expr);fn(node, value);},// 获取 {{a}} 中的值getContentValue(vm, expr) {// 遍历表达式 将内容 重新特换成一个完整的内容 返还出去return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {return this.getValue(vm, args[1]);});},// v-on:click="change"on(node, expr, vm, eventName) {node.addEventListener(eventName, e => {vm[expr].call(vm, e);});},// 可能存在 {{a}} {{b}} 多个样式text(node, expr, vm) {let fn = this.updater["textUpdater"];let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {// 给表达式 {{}} 中的值添加一个观察者,如果数据更新了,会触发此方法new Watcher(vm, args[1], () => {fn(node, this.getContentValue(vm, expr)); // 返回一个全新的字符串});return this.getValue(vm, args[1]);});fn(node, content);},// 给 dom 文本结点赋值数据
updater: {modelUpdater(node, value) {// 处理指令结点 v-modelnode.value = value;},textUpdater(node, value) {// 处理文本结点 {{}}node.textContent = value;},htmlUpdater(node, value) {// 处理指令结点 v-htmlnode.innerHTML = value;}
}
};

3、塞回页面

此时,我们将渲染好的 fragment 塞回到真实 DOM中就可以正常显示了。

this.el.appendChild(fragment);

当我们在输入框中输入数据时,相对应的视图上 {{student.name}} 的地方进行实时的更新;
当我们通过 vm.$data.student.name 改变数据时,输入框内的数据也会发生改变。

效果图如下:

从头到尾我们实现了一个双向绑定。

小结

上述代码中,大多数都是通过动手实践模拟原理的过程,而非单纯枯燥的知识点。通过手动去写一写原理的实现,发现并不是很简单,但是这些原理都是建立在基础知识之上的东西。

但是,当我们翻看源码的时候,就很难找到头绪,源码中,有各种参数判断、条件判断。此篇文章当做自己的随手笔记,里边的内容并非全都准确无误,如有理解不当,错误的地方,欢迎指出!

后续

后续会将《手写 Vue 2.0 核心原理(下)》整理出来,而且这部分会进行部分的优化和更改,将这部分全部写入《大前端面试小册》中,这个过程就是一个不断发现错误,反馈错误,不断优化的过程!

最后,我是小鹿,一个互联网业余写作分享爱好者,只有变秃,才能变强,欢迎来 Get !

最后

原创不易,拒绝白嫖不良习惯,点赞就完事了!

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 智慧树巴蜀文化答案

    知到app巴蜀文化答案课后单元测试答案第一章1【多选题】(10分)( ACD)是传说中蜀地的三个王朝。A.柏灌氏B.蜀山氏C.开明氏D.杜宇2【多选题】(10分)( BD)及( ),开国何茫然,尔来四万八千岁,不与秦塞通人烟。A.柏灌B.蚕丛C.高阳氏D.鱼凫3【多选题】(10分)历史上巴蜀文化的…...

    2024/5/1 9:55:15
  2. 一键解决 go get golang.org/x 包失败

    一键解决 go get golang.org/x 包失败 问题描述 当我们使用 go get、go install、go mod 等命令时,会自动下载相应的包或依赖包。但由于众所周知的原因,类似于 golang.org/x/… 的包会出现下载失败的情况。如下所示: 1 2 3 $ go get -u golang.org/x/sys go get golang.org/…...

    2024/4/16 23:02:28
  3. SRT互联网传输设备技术分享

    原文链接:https://blog.csdn.net/weixin_42228920/article/details/90946259 高手先驱太多。SRT互联网传输设备技术分享 前 言 序 言 Chapter 1. 什么是SRT? 1.1. SRT 联盟 1.2. SRT传输技术 1.3. SRT的典型应用模式 1.3.1. 点对点单向传输和视频互动 1.3.2. 点对多点传输 1.…...

    2024/4/22 15:50:02
  4. springboot扫盲写给自己看的

    springboot扫盲写给自己看的启动流程 启动流程构造springApplication类 构造函数里会去做 a ApplicationInlitializer 初始化器 对象 b.初始化ApplicationListener 即加载监听器的类 ,也可以自己定制 run方法 1.Environment配置 2.ApplicationContext容器的创建,及属性的设置 e…...

    2024/5/1 9:28:02
  5. CSS:09-CSS高级技巧

    CSS高级技巧 目标理解能说出元素显示隐藏最常见的写法 能说出精灵图产生的目的 能说出去除图片底侧空白缝隙的方法应用能写出最常见的鼠标样式 能使用精灵图技术 能用滑动门做导航栏案例1. 元素的显示与隐藏目的 让一个元素在页面中消失或者显示出来场景 类似网站广告,当我们点…...

    2024/5/1 6:18:18
  6. /dev/mapper/centos-root空间不足,新增磁盘并对root扩容。

    虚拟机/dev/mapper/centos-root满了,剩余空间20K……需要扩容。从网上帖子看大部分是将/dev/mapper/home的空间分给/root,结果df -lh直接傻眼了,可能是之前手动配置的。并没有/dev/mapper/home。 VMware直接修改磁盘空间,失败,不能做到。 故尝试加磁盘,然后将新加的空间分…...

    2024/5/1 7:48:15
  7. Centos7虚拟机yum安装mysql实现主从复制案例教程

    Centos7虚拟机yum安装mysql实现主从复制案例教程mysql安装navicat 链接mysql配置主从复制拓展 mysql安装首先,centos 默认安装有MariaDB, 属于mysql 分支, 安装mysql 时会覆盖掉MariaDB使用wget下载mysql 官方Repositorywget -i -c http://dev.mysql.com/get/mysql57-commun…...

    2024/5/1 11:16:55
  8. Python之Selenium如何正确运用?案例详解

    Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些JavaScript动态渲染的页面来说,此种抓取方式非常有效。本节中,就让我们来感受一下它的强大之处吧。1. 准备工…...

    2024/4/24 8:45:31
  9. Java中Lambda表达式的学习

    Lambda表达式 1.函数式编程思想概述 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。 2.Lam…...

    2024/4/24 8:45:30
  10. c++字符串操作

    从键盘输入任意的一个字符串 A,其长度 L 不小于 6,同时再输入一个整数 N(其中:1 < N <L + 1)作为插入点,以及任意的一个字符串 B,其长度为 L1(其长度为L1<L)现要求完成下列功能:如果字符串 A 的长度 L大于 100输出 100,否则输出 L; 在 N点处,插入字符串 B…...

    2024/4/24 8:45:29
  11. Android.mk 文件详解-1

    前言 最近在使用mk 进行编译系统。中间碰见了很多的问题,而最关键的就是在于针对mk文件的相关配置和属性参数不了解,才造成的错误,下面主要介绍下mk相关的配置指令的意义。 1.模板介绍 编译成so库 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)# 库的名称值 LOCAL_MO…...

    2024/4/24 8:45:28
  12. dubbo springboot nacos 组合测试

    dubbo介绍 优秀的 RPC 服务治理框架,直接查看 官网. nacos 介绍 作为 注册中心 和 参数配置中心使用, 本次作为dubbo 服务注册发现中心,介绍查看官网. spring boot 注解方式配置 本次测试环境 springboot 2.2.6 dubbo 2.7.7 通过反复查看官网和官方demo, 其实基本可以不用 …...

    2024/4/23 13:38:43
  13. 2-2单元 -主机基本安全防护措施 —— SELinux 管理原则

    主机基本安全防护措施 —— SELinux 管理原则< 一 > SELinux 简介:SELinux使用委任式访问控制(Mandatory Access Control , MAC),它可以针对特定的程序与特定的文件资源来进行权限管理。工作模式:主体 (Subject) —— SELinux主要管理的就是程序 ;目标 (Object)…...

    2024/4/24 8:45:27
  14. 歌声合成full labels 解析(python版本)

    解析代码 # -*- coding:utf-8 -*- # /usr/bin/pythonimport re import numpy as np import pandas as pd from keras.models import * from keras.layers import * from keras.layers import * from keras import optimizers from keras.utils import np_utilsimport os.path…...

    2024/4/24 8:45:25
  15. 第七周、软件包管理与磁盘存储

    1、自建yum仓库,分别为网络源和本地源 1.1 基于光盘的yum源 [root@localhost ~]#cat /etc/yum.repos.d/cdrom.repo [cdrom] name=cdrom base baseurl=file:///misc/cd/ gpgcheck=1 gpgkey=file:///misc/cd/RPM-GPG-KEY-CentOS-$releasever[eple] name=aliyun epel baseurl=ht…...

    2024/4/24 8:45:24
  16. Java里面的next()和nextLine()的区别

    Java的next()和nextLine()@[YXG] next() 用Scanner方法记录键盘打印的String类型,当next()遇见空格时候,将会停止。 代码:System.out.println("请输入您的用户名: ");String name=input.next();System.out.println(name);结果:nextLine() 用Scanner方法记录键盘打…...

    2024/4/24 8:45:31
  17. js删除数组中指定值的元素/splice函数实例

    效果:输入一个数组和一个值,返回该值在数组的索引;如果不在,则返回-1 自己写一个返回当前值索引下标的函数: function returnindex(ids,id,length) {for(var x=0;x<length;x++){ //有效数组长度if (id==ids[x]){return x;}}return -1;}length可以直接使用ids.length,但…...

    2024/4/24 8:45:22
  18. 关于SQL查询时间问题

    1. 查询N条数据,取最高,最低,计算等。。。。select max(datavalue) as maxDataValue,min(datavalue) as minDataValue,count(1) as dataSize,sum(dataValue) as sumDataValue from RT_realDataDoubleValue查询所需要时间 0.482sselect datavalue from RT_realDataDoubleValu…...

    2024/4/24 8:45:21
  19. OpenCV:无法启动此程序,因为计算机中丢失opencv_core2410.dll

    OpenCV:无法启动此程序,因为计算机中丢失opencv_core2410.dll 解决办法: 将bin目录(我的是 G:\OpenCV\build\x64\vc10\bin)中的3个dll文件![(opencv_core2410.dll;opencv_core2410d.dll )(https://img-复制在(C:\Windows\System32)中。...

    2024/4/24 8:45:20
  20. 踩坑Hystrix:不显示详情,一直报Loading

    今天配置Hystrix,解决了Unable to connect to Command Metric Stream这个错误,进去之后还是显示不来详情,截图如下: 解决方法:给被监控的微服务随便发一个请求(加了@HystrixCommand的任意请求),就解决了!...

    2024/4/24 8:45:22

最新文章

  1. 关于阿里云DataWorks的20道面试题

    1. 请简要介绍阿里云DataWorks的基本概念和主要功能。 阿里云DataWorks是一个全链路的大数据开发治理平台&#xff0c;其主要功能包括数据集成、数据建模与开发、数据地图、数据质量和数据服务等。 DataWorks的基本概念围绕其作为一个大数据开发和治理的平台&#xff0c;它整…...

    2024/5/1 11:20:40
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 《c++》多态案例一.电脑组装

    一.代码展示 #include <iostream> using namespace std; class CPU { public://抽象计算函数virtual void calculate() 0;};class CVideoCard { public://抽象显示函数virtual void display() 0;}; class Memory { public://抽象存储函数virtual void storage() 0;};…...

    2024/4/30 4:16:58
  4. c# wpf XmlDataProvider 简单试验

    1.概要 2.代码 <Window x:Class"WpfApp2.Window12"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend…...

    2024/5/1 6:51:36
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/29 23:16:47
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57