一些重要的前端知识总结(基于笔面试题的扩展),包含原型链、instanceof、深度剖析Vue底层原理
目录
一、原型链
二、instanceof
1. instanceof
2. 用法
三、defineProperty和Proxy
1. vue架构-MVVM
2. render函数
1)render是什么
2)render的工作流程
3. Vue实例
1)Vue实例
2)Vue实例的选项
4.观察者机制(订阅者-发布者模式)
1)订阅者-发布者模式
2)Vue中的Watcher
5.diff算法
6.Vue渲染页面过程
1)初始化:
2)编译模板:
3)虚拟DOM:
4)挂载:
5)数据变化:
6)差异对比(diff):
7)更新真实DOM:
7.Vue的数据双向绑定机制(响应式原理)
1)Vue2的响应式机制:
2)Vue3的响应式机制:
3)双向数据绑定的简化流程:
一、原型链
原型链:每个对象都有与之相关的原型对象,原型对象自身也可以有原型,以此类推,层层嵌套实现原型链。
1. prototype是一个属性,每个构造函数(类函数)都有该属性,该属性是一个对象,用于作为通过构造函数创建的实例的原型,
也就是通过这个构造函数(类函数)创建的实例(new创建)的原型指向构造函数的prototype属性
2. 两个类,如果要让一个类继承另一个类的属性和方法,就需要让这个类的原型指向另一个类的prototype属性,
比如一个类为A,另一个类为B,设置A.prptotype = Object.create(B.prototype) 这个方法利用Object.create()函数
将A的原型设置为一个对象,这个对象的原型是B.prototype
在设置prototype指向后,还需要修正A.prototype的construstor属性,使其指向A函数本身。
例题:
// Animal构造函数
function Animal (name, age) {this.name = name;this.age = age;
}//Animal通过原型链定义方法
Animal.prototype.eat = function() {console.log(`${this.name} is eating.`);
}// Dog构造函数
function Dog(name, age, breed) {Animal.call(this, name, age);this.breed = breed;
}// 通过原型链将Dog的原型指向Animal.prototype,也就是设置Dog的原型为Animal的实例,
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;Dog.prototype.bark = function() {console.log(`${this.name} barks.`);
}const myDog = new Dog('Buddy', 3, 'Labrador');myDog.eat();
myDog.bark();
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
输出结果如图:
二、instanceof
1. instanceof
instanceof 操作符,用于检测构造函数的prototype属性是否存在于某个实例对象的原型链上(判断一个对象是否是其指定构造函数的实例)
比如上面的Animal和Dog例子,myDog是Dog()的实例,也就是myDog的原型指向Dog.prototype,原型链中Dog.prototype又指向Animal.prototype,所以也属于Animal的实例,可以使用Animal中的属性和方法。
2. 用法
FormData是一个Web API提供的接口,用于构建一组键值对,代表表单字段和它们的值,以便可以使用XMLHttpRequest发送或使用fetch API发送数据。
要检查一个变量是否为FormData的实例:
// 创建一个FormData对象
var formData = new FormData(); // 使用instanceof检查formData是否是FormData的实例
if (formData instanceof FormData) { console.log('The variable is a FormData instance.');
} else { console.log('The variable is not a FormData instance.');
}
在实际开发中,接口请求时,如果传送的是file文件,那可以用:
const formData = new FormData();
formData.append('file', file);
然后将formData作为参数发送请求,但是由于传送的是FormData()实例,请求头需要添加'multipart/form-data',所以可以如下设置,添加请求拦截器,利用instanceof判断传递的data是否为FormData的实例,如果是,则添加对应请求头:
// 添加请求拦截器
instance.interceptors.request.use(config => {if (config.data instanceof FormData) {config.headers['Content-Type'] = 'multipart/form-data';}return config;},error => {return Promise.reject(error);}
);
三、defineProperty和Proxy
这个就要了解到vue的响应式原理。
1. vue架构-MVVM
以上是一个简单理解MVVM的原理图
MVVM:
M(Model):指在Vue实例的data、computed、methods等选项中定义的数据对象和方法,这些数据对象和方法是应用程序的业务逻辑和数据模型,可以是简单的变量、对象、数组,也可以是复杂的数据结构和逻辑处理函数。
V(View):Vue组件的模板部分,将数据模型通过render函数生成虚拟DOM树,渲染成用户界面。
M(ViewModel):指Vue实例本身,View实例作为View和Model之间的桥梁,通过数据双向绑定机制实现了View和Model之间的通信和同步。当Model中的数据发生变化时,Vue会利用Watcher观察者机制监听这些变化,并自动更新虚拟DOM树,再利用diff算法更新真实DOM树。
以上又出现了几个问题:
1.render函数?
2.Vue实例?
3.观察者机制?
4.diff算法
5.Vue渲染页面过程?
一一解答:
2. render函数
1)render是什么
renger是vue2.x中新增的一个函数,用来提升节点的性能。它是基于JavaScript计算,使用render函数将Template里面的节点解析成虚拟的DOM树。
当使用模板HTML语法组建页面时,Vue会将其编译成VNode(虚拟节点)的函数。而当使用render函数构建DOM时,Vue就免去了转译的步骤,直接生成VNode。
2)render的工作流程
render(h) {// h 是 createElement 的别名return h('div', /* props */, /* children */)
}
(1)定义:在 Vue 组件中,render 函数是一个选项,它返回一个虚拟节点 (VNode),或者是一个包含多个 VNode 的数组。
(2) createElement函数:render函数通常接收一个createElement函数作为参数,通常简写为h。createElement函数用于创建VNodes。
// 示例:创建一个简单的 VNode
createElement('div', { /* 属性 */ }, [ /* 子节点 */ ])
(3) 创建VNodes:createElement接受三个参数:一个标签名、一个包含属性的对象,以及子节点(可以是字符串、数组或另一个VNode)。
(4) 编译模板:如果在组建中定义了template,Vue的编译器会将模板编译成render函数,这个编译过程是在构建时完成的,不是在运行时。
(5) 更新机制:当组件的状态(数据)发生变化时,Vue会调用render函数生成一个新的VNode树,并与旧的VNode树进行比较(diff算法),计算出需要对真实DOM进行的最小更新。
3. Vue实例
1)Vue实例
Vue中,一个Vue实例是通过构造函数Vue创建的一个对象,它是使用Vue构建应用程序的起点。Vue实例通常包含了应用程序的根组件,并负责管理应用的声明周期、数据状态、事件处理以及与DOM的交互。
(可以将一个.vue页面视为一个Vue实例)
2)Vue实例的选项
- data:包含Vue实例响应式数据的基本对象
- methods:定义可以通过Vue实例访问的方法
- computed:定义计算属性,这些属性是基于它们的依赖进行缓存的(即在第一次得出结果后,如果它依赖的数据没有发生变化,则会利用缓存机制,不会再重新计算计算属性的值)
- watch:提供一个对象,键是数据属性名,值是对应的回调函数,用于响应数据的变化。
- components:注册组件
- filters:定义过滤器,用于模板中的数据处理
- directives:定义自定义指令,用于扩展HTML的行为
- props:当实例用作组件时,props定义了组件可以接收的属性
- template:提供一个字符串模板作为Vue实例的HTML
- render:提供一个函数,用于生成虚拟节点(VNode),替代模板。
- el:提供一个在页面上已存在的DOM元素作为Vue实例的挂载目标。(挂载元素是指Vue实例将要管理的DOM元素,‘el’是‘element’的缩写。当指定一个DOM元素时,Vue会将该元素作为挂载元素然后将该元素的内部内容作为Vue实例的模板进行解析和渲染
比如:
new Vue({
el:'#app',
data:{
message:'Hello World!'
}
})
以上el:'#app',指定了挂载元素为id为app的DOM元素)
4.观察者机制(订阅者-发布者模式)
1)订阅者-发布者模式
一种设计模式,用于定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并被自动更新。在Vue.js中,这种模式被用于实现数据的响应式系统。
发布者(Subject):
- 发布者维护一个观察者列表,并提供方法来移除观察者。
- 当发布者的状态发生变化时,它会通知所有注册的观察者。
订阅者(Observer):
- 订阅者是一个对象,它监视发布者的状态
- 当发布者的状态改变时,订阅者会收到通知,并执行一些操作。
2)Vue中的Watcher
(1)数据绑定:在Vue中如果模板中有多个地方使用了同一个数据属性,那么每个使用该属性的地方都会有一个观察者。当属性值发生变化时,所有依赖于该属性的地方都需要更新。
(2)依赖收集:当组件渲染或计算属性被计算时,Vue会创建一个Watcher实例。这个Watcher实例会在其构造函数中访问数据属性,从而触发数据的getter方法。在getter方法中,Watcher实例会被添加到数据属性的依赖列表中。
(3)触发更新:当属性值通过setter改变时,Vue会通知所有依赖于该属性的Watcher实例(所有注册的观察者),每个Watcher实例都会调用其回调函数,这通常会导致组件的重新渲染或计算属性的重新计算。
补充:
Watcher:Watcher是一个类,它不是一个简单的函数,而是一个构造函数,它创建了一个可以跟踪数据变化并执行毁掉的实例。
5.diff算法
当Vue中的数据发生变化时,Vue会根据新数据生成一颗新的虚拟DOM树,然后使用diff算法来比较两颗虚拟DOM树的差异,找出需要更新的节点,将变化的部分更新到真实DOM中,从而最小化对真实DOM的操作。
Vue的diff算法是基于同级比较的,即只会比较同级节点,不会跨级比较。
在比较过程中,Vue会先比较Tag和Key,如果Tag不同,直接删除旧节点,创建新节点;如果Tag相同,则继续比较Key;如果Key也相同,则认为是同一个节点,否则认为是不同的节点。对于静态节点,Vue会在初始化时就创建一个静态的虚拟DOM树,并在后续的更新过程中直接复用这棵树,从而避免了不必要的比较和更新操作。
6.Vue渲染页面过程
结合上述知识,总结Vue渲染页面的过程:
1)初始化:
- 创建Vue实例时,它会接收一个选项对象,包括数据、模板、方法、计算属性等
- Vue会遍历选项对象中的数据属性,使用Object.defineProperty()或Proxy(Vue3)将它们转换为getter/setter,以便实现响应式。
2)编译模板:
- Vue使用HTML模板字符串或<template>标签中的内容作为输入
- 模板编译器会将模板字符串转换成一个渲染函数,这个渲染函数会返回虚拟DOM树(VNodes)
new Vue({el: '#app',template: `<div><h1>{{ message }}</h1><p>{{ description }}</p></div>`,data: {message: 'Hello Vue!',description: 'This is a Vue instance.'}
});
补充:
- 模板字符串:
HTML模板字符串是指一段用双引号(“”)或反引号(``)包围的字符串,其中包含了HTML代码。
- 模板编译器:
一个将模板字符串转换成渲染函数的工具。
(1)解析:模板编译器首先解析模板字符串,将其转换成抽象语法树(AST)。AST 是源代码的抽象表示,它以树状结构表示代码的结构。
(2)优化:Vue 的编译器会遍历 AST,并找出静态节点和静态根节点。这些信息将被用来优化渲染过程,因为静态节点不需要在每次重新渲染时都重新生成。
(3)代码生成:编译器的最后一步是将优化后的 AST 转换成渲染函数代码。这个渲染函数是一个返回虚拟 DOM 树的 JavaScript 函数。
3)虚拟DOM:
- 虚拟DOM是真实DOM的内存表示。它是一个轻量级的JavaScript对象,描述了应该渲染的DOM结构
- 渲染函数的输出是一个虚拟节点树,Vue将使用这个树来更新浏览器中的真实DOM
4)挂载:
- 在Vue实例的mounted生命周期钩子被调用之前,Vue会将虚拟DOM转换为真实DOM,并将其挂载到指定的DOM元素上(通常是#app)
5)数据变化:
- 当数据变化时,由于数据响应式系统,Vue找到依赖了这些数据的虚拟节点进行更新
- Vue重新调用渲染函数,生成一个新的虚拟DOM树
6)差异对比(diff):
- Vue通过diff算法对比新旧虚拟DOM树,找出差异部分并更新节点
7)更新真实DOM:
- Vue最小化地更新真实DOM,反应数据的变化
7.Vue的数据双向绑定机制(响应式原理)
1)Vue2的响应式机制:
(1)数据劫持:Vue使用Object.definePropert()对每个对象的每个属性进行劫持(数据绑定),为每个属性添加setter和getter,以便在数据读取和修改时执行特定的逻辑。
(2)依赖收集:当组件进行渲染时,会访问模板中用到的数据属性,这时属性的getter会被调用,Vue将会记录这次访问的依赖关系,当数据变化时,setter会被触发,Vue会通知那些依赖这个数据属性的地方进行更新。
(3)发布-订阅模式:Vue2使用了发布-订阅模式,每个属性都有一个或多个观察者(Watcher),当数据变化时,会通知所有订阅了该数据的观察者。
(4)局限:Vue2的响应式系统无法检测到对象属性的添加或删除,也无法检测到数组索引和长度的变化。
2)Vue3的响应式机制:
Vue3引入了一个全新的响应式系统,基于ES6的Proxy特性。
(1)Proxy:Vue3使用Proxy来代理整个对象,而不是像Vue2一样对对象的每个属性进行劫持。Proxy可以拦截对象的任意操作,包括属性的读取、设置、枚举、函数调用等。
(2)优势:
- 代理整个对象,不需要为每个属性都设置setter和getter,减少内存使用,提高性能;
- 支持数组的索引和长度变化,以及对象属性的添加和删除。
- 允许更精细的变更检测,如单独跟踪数组变化,而不是对整个数组进行重新处理。
- 支持可中断更新。
3)双向数据绑定的简化流程:
(1)初始化:Vue 实例在初始化时会遍历 data 对象中的所有属性,并使用 Object.defineProperty()(Vue 2)或 Proxy(Vue 3)将它们转换为 getter/setter,以便于追踪变化。
(2)视图渲染:当视图被渲染时,v-model 指令会告诉 Vue 将指定的数据属性绑定到表单元素的值上。
(3)用户输入:用户在输入框中输入内容时,会触发输入框的 input 事件。
(4)更新模型:v-model 指令监听到 input 事件后,会将输入框的最新值赋给绑定的数据属性。
(5)视图更新:由于数据属性现在是响应式的,一旦它被修改,Vue 将自动更新依赖于该属性的视图部分