问题一:js中如何判断是否是数组
方法一:isArray()数组自带的判断方法
let a =[1,2,3];
Array.isArray(a); //true
方法二:instanceof操作符
let a = [1,2,3];
a instanceof Array; //true
方法三:constructor.toString()方法
let a = [1,2,3]
//把constructor转换成字符串,然后通过indexOf去判断是否包含Array
a.constructor.toString().indexOf("Array") !== -1; //true
const a = [];
console.log(a.constructor);//function Array(){ [native code] }
const a = [];
console.log(a.constructor == Array);//true
方法四:Object.prototype.toString.call(a).indexOf(“Array”)
let a = [1,2,3];
//和上面一个意思,都是转换成字符串,然后判断是否包含Array,
//有就会返回下标位置
/*
Object.prototype.toString会取对象的一个内部属性[[Class]],
大概会返回一个类似于"[object Array]"这样的字符串,注意,
这里这个是内部属性,外部是无法访问的,然后再配合call方法,
改变toString的this指向,也就是指向a数组
*/
问题二:vue watch和computed和methods区别
1、watch(监听),重视过程,当监听的数据(data里面定义的数据比如num)发生改变才可以执行,watch接收两个参数一个时oldValue(旧值),newValue(新值),第一个参数为更新的数据,第二个参数为之前的旧数据,不用返回值。
监听单个变量或一个数组.
例子:watch属性可以用来做页面中的模糊查询使用.
num(newValue,oldValue){
console.info(newValue);
}
2、computed (计算属性),重视结果,响应式依赖发生改变时执行并记录缓存,当响应式值不改变时后续读取数据都是用缓存的,
当响应时值改变时才会重新求值并返回,必须有return。
例子:计算可以使用computed
<script src="https://how2j.cn/study/vue.min.js"></script>
<style>
table tr td{
border:1px solid gray;
padding:10px;
}
table{
border-collapse:collapse;
width:800px;
table-layout:fixed;
}
tr.firstLine{
background-color: lightGray;
}
</style>
<div id="div1">
<table align="center" >
<tr class="firstLine">
<td>人民币</td>
<td>美元</td>
</tr>
<tr>
<td align="center" colspan="2">
汇率: <input type="number" v-model.number="exchange" />
</td>
</tr>
<tr>
<td align="center">
¥: <input type="number" v-model.number = "rmb" />
</td>
<td align="center">
$: {{ dollar }}
</td>
</tr>
</table>
</div>
<script>
new Vue({
el: '#div1',
data: {
exchange:6.4,
rmb:0
},
computed:{
dollar() {
return this.rmb / this.exchange;
}
}
})
</script>
监听复杂数据(深度监听 deep)
不使用 deep 时,当我们改变 obj.a 的值时,watch 不能监听到数据变化,默认情况下,handler 只监听属性引用的变化,也就是只监听了一层,但改对象内部的属性是监听不到的。
immerdiate 属性: 通过声明 immediate 选项为 true,可以立即执行一次 handler。
<template>
<div>
<el-input v-model="obj.text"></el-input>
</div>
</template>
<script>
export default {
data() {
return {
obj:{
text:'hello'
}
};
},
watch:{
// 监听对象obj的变化
obj:{
handler (newVal,oldval) {
console.log(newVal,oldval)
},
deep: true,
immediate: true
}
},
};
</script>
通过使用 deep: true 进行深入观察,我们监听 obj,会把 obj 下面的属性层层遍历,都加上监听事件,这样做性能开销也会变大,只要修改 obj 中任意属性值,都会触发 handler,那么如何优化性能呢?
可以直接对用对象 . 属性的方法拿到属性
<template>
<div>
<el-input v-model="obj.text"></el-input>
</div>
</template>
<script>
export default {
data() {
return {
obj:{
text:'hello',
}
};
},
watch:{
// 监听对象单个属性text
'obj.text':{
handler (newVal,oldval) {
console.log(newVal,oldval)
},
immediate: true, // 该属性会先执行一次handler
}
},
};
</script>
3、methods.没有缓存每次都会求值,是事件绑定用的,逻辑运算,可以不用return
绑定事件专用
4、computed中的函数必须要用return返回,watch中的函数不是必须要用return
computed与watch的使用场景:
computed:是多对一,多个数据影响一个。当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
watch:是一对多,一个数据发生变化,执行相应操作会影响多个数据。当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
问题三:rpx、px、em、rem、%、vh、vw的区别是什么
rpx、px、em、rem、%、vh、vw的区别是什么?
rpx 相当于把屏幕宽度分为750份,1份就是1rpx px 绝对单位,页面按精确像素展示 em 相对单位,相对于它的父节点字体进行计算 rem 相对单位,相对根节点html的字体大小来计算 % 一般来说就是相对于父元素 vh 视窗高度,1vh等于视窗高度的1% vw 视窗宽度,1vw等于视窗宽度的1%
问题四:typeof和instanceof的区别是什么
比较typeof与instanceof
相同点:
JavaScript 中 typeof 和 instanceof 常用来判断一个变量是否为空, 或者是什么类型的。
不同点:
typeof:
1、返回值是一个字符串, 用来说明变量的数据类型。
2、typeof 一般只能返回如下几个结果: number, boolean, string, function, object, undefined。
var a = [34,4,3,54],
b = 34,
c = 'adsfas',
d = function(){console.log('我是函数')},
e = true,
f = null,
g;
console.log(typeof(a));//object
console.log(typeof(b));//number
console.log(typeof(c));//string
console.log(typeof(d));//function
console.log(typeof(e));//boolean
console.log(typeof(f));//object
对于 Array, Null 等特殊对象使用 typeof 一律返回 object, 这正是 typeof 的局限性。
instanceof:
1、返回值为布尔值
2、instanceof 用于判断一个变量是否属于某个对象的实例。
// var a = new Array();
// alert(a instanceof Array); // true
// alert(a instanceof Object) // true
//如上, 会返回 true, 同时 alert(a instanceof Object) 也会返回 true;
// 这是因为 Array 是 object 的子类。
// alert(b instanceof Array) // b is not defined
// function Test() {};
// var a = new test();
// alert(a instanceof test) // true
问题五:vue实现移动端适配方案
回答一:页面初始化时判断当前环境是手机端还是pc端切换不同的项目版本
回答二:vue项目在index.html中加入<meta name="viewport" content="width=device-width,initial-scale=1.0">
回答三:html5中手机端适配使用document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px';
问题六:js闭包
- 保护全局变量安全。函数a中的num只有函数b才能访问,而无法通过其他途径访问到,因此保护了全局变量的安全。
- 在内存中维持一个变量
- 所有的(闭包)自由变量的查找,是在函数定义的地方,向上级作用域查找不是在执行的地方!!!
var num = 6;
function a() {
//上层作用域
var num = 1;
function b() {
var n = 2;
alert(n + num);
}
//执行的地方
return b
}
const c= a();
c()//等于3 没有污染最外层的num
alert(num);//等于6 没有污染最外层的num
扩展:内存占用问题
因为f()被fn一直占用所以在内存中num改变后的值是被记录下来的也就导致每次调用都是+的问题
var f = function() {
var num = 0;
return function() {
return num += 1;
};
}
var fn = f();
fn()
// 1
fn()
// 2
fn()
// 3 最后等于3 num进行了累加
如何释放内存 fn=null 就可以了
内存回收机制
f函数已经执行完毕了,没有其他资源引用了,ta会被立即释放,也就是说,f()执行完后,fn=null,不在占用f()函数了,内存立即就释放了。
var f = function() {
var num = 0;
return function() {
return num += 1;
};
}
var fn = f();
console.info(fn())
// 1
fn =null
var fn = f();
console.info(fn())
// 1
fn =null
var fn = f();
console.info(fn())
// 1
fn =null
实际应用场景:
隐藏数据,做一个简单的cache工具
// 闭包隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
问题七:this、let和var
1、 this的指向是,由它所在函数调用的上下文决定的而不是,由它所在函数定义的上下文决定的
2、 var创建的变量会挂载在window对象上。 而let 、 const创建的变量不会挂载在window对象上
问题八:vue中 的传值问题 父传子和子传父和兄弟组件传值
1、父组件可以使用 props 把数据传给子组件。
2、子组件可以使用
e
m
i
t
触发父组件的绑定的自定义事件。
3
、通过
b
u
s
需要先引入发送
b
u
s
.
emit 触发父组件的绑定的自定义事件。 3、通过bus需要先引入 发送bus.
emit触发父组件的绑定的自定义事件。3、通过bus需要先引入发送bus.emit(‘a’,this.msg)
接收bus.on(‘a’,(data)=>{
console.info(data);
this.msg=data;
})
问题九:vue 响应式原理
Vue的数据双向绑定,响应式原理,其实就是通过Object.defineProperty()结合监听者订阅者模式来实现的
当data改变时set进行拦截通知watcher,在watcher中触发所有订阅者进行更新,在更新过程中会生成一颗新的虚拟dom tree在对比老的虚拟dom tree以最小的代价找出不同后更新到真实的dom中
问题十:vue v-model
v-model只不过是一个语法糖而已,真正的实现靠的还是
v-bind:绑定响应式数据
触发oninput 事件并传递数据
语法糖
<input v-model="searchText">
原理
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value">
问题十一:小程序的双向绑定
在input中绑定value="{{username}}"再添加bindinput事件监听input发生的变化通过e.detail.value获取值用
this.setData({username:e.detail.value})实现双向绑定
wxml:
<label>
用户名:{{username}}
</label>
<input class="input" type="text" placeholder-class="search-placeholder" placeholder="请输入用户名" maxlength="20" value="{{username}}" bindinput="inputstr" />
</view>
js:
inputstr(e){
var name=e.detail.value
this.setData({
username:name
})
}
问题十二:生命周期:
从Vue实例创建、运行、到销毁期间,伴随着的各种事件,这些事件统称为生命周期
生命周期函数分类:
创建期间的生命周期函数:
beforeCreate:实例刚在内存中被创建出来,此时还没有初始化好data和methods属性
created:实例已经在内存中创建出来,此时的data和methods以及创建完成,但是还没有开始编译模板
beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面上
mounted:已经将编译好的模板,挂载到了页面指定的容器中显示
运行期间的生命周期函数:
beforeUpdate:状态更新之前执行此函数,此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
updated:实例更新完毕之后调用此函数,此时data中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
销毁期间的生命周期函数:
注意 !!!vue3,则是beforeunmount和unmount
beforeDestory:实例销毁之前调用,在这一步,实例仍然完全可用
destroyed:Vue实例销毁之后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
问题十三:为什么现代前端框架要基于虚拟 DOM?
一、虚拟 dom 使用 diff 算法,模拟浏览器内核渲染算法,并在此基础上进行了优化;
二、虚拟 dom 只占用浏览器内存,不占用浏览器内核资源。
三、虚拟 dom 渲染时,首先与旧的 dom 结构进行比较,然后将需要渲染的节点渲染到页面,很少需要回流,大多数只进行了重绘。
四、虚拟 dom 将浏览器内核中的 dom 树结构,转化为 dom 对象结构,可以使用操作对象的方法进行同层的比较等。
五、虚拟 dom 可以解决不同浏览器内核渲染时的过程差异,减少开发成本,节省开发时间。
问题十三:Vue中key的作用
1.虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1).就虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变,直接使用之前的真实DOM!
②.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).就虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到页面。
3.用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作;
会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。
2.如果结构中还包含输入类的DOM;
会产生错误DOM更新 ==> 界面有问题。
4.开发中如何选择key?
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆向添加、逆向删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
问题十四:html5中打包app调用相机的方法(还没有研究明白)
git地址
jsbridge实现前端js和java之间架起一座桥梁,服务操作完app后将内容返回给前端
JitPack.io
git内容
我强烈推荐https://jitpack.io
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
在 Java 中使用它
将 com.github.lzyzsd.jsbridge.BridgeWebView 添加到您的布局中,它是从 WebView 继承的。
注册一个Java处理函数,让js可以调用
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
function.onCallBack("submitFromWeb exe, response data from Java");
}
});
Node.js 可以通过以下方式调用这个 Java 处理程序方法“submitFromWeb”:
WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': str1}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
你可以在Java中设置一个默认的handler,这样js就可以在不分配handlerName的情况下向Java发送消息
webView.setDefaultHandler(new DefaultHandler());
window.WebViewJavascriptBridge.send(
data
, function(responseData) {
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
注册一个 JavaScript 处理函数,以便 Java 可以调用
WebViewJavascriptBridge.registerHandler(“functionInJs”, function(data, responseCallback) {
document.getElementById(“show”).innerHTML = ("data from Java: = " + data);
var responseData = “Javascript Says Right back aka!”;
responseCallback(responseData);
});
Java可以通过以下方式调用这个js处理函数“functionInJs”:
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
你也可以定义一个默认的handler使用init方法,这样Java就可以在不分配handlerName的情况下向js发送消息
例如:
bridge.init(function(message, responseCallback) {
console.log('JS got a message', message);
var data = {
'Javascript Responds': 'Wee!'
};
console.log('JS responding with', data);
responseCallback(data);
});
webView.send("hello");
将在 webview 问题中打印 ‘JS got message hello’ ‘JS respond with’。
注意
这个库将向多个打击对象注入一个 WebViewJavascriptBridge。您可以收听WebViewJavascriptBridgeReady事件以确保window.WebViewJavascriptBridge存在,如代码所示:
if (window.WebViewJavascriptBridge) {
//do your work here
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
//do your work here
},
false
);
}
或者定义,如果JBridge函数将所有通话时间window.WVJBCallbacks中,当事件触发window.WebViewJavascriptBridge时,此任务将被发掘。WebViewJavascriptBridgeReady
将 setupViewJavascriptBridge 复制并粘贴到您的 JS 中:
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback);
}
window.WVJBCallbacks = [callback];
}
调用setupWebViewJavascriptBridge然后使用桥来注册处理程序或调用Java处理程序:
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data);
responseCallback(data);
});
bridge.callHandler('ObjC Echo', {'key':'value'}, function(responseData) {
console.log("JS received response:", responseData);
});
});
它与https://github.com/marcuswestin/WebViewJavascriptBridge相同,您可以轻松地在 Android 和 iOS 之间的不同平台上定义相同的行为。同时,编写自己的代码。
问题十五:Vue中的$nextTick有什么作用?
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
理解:nextTick()是将回调函数延迟在下一次dom更新数据后调用,即当数据更新了在DOM中渲染后自动执行该函数。
Vue实现响应式并非数据发生变化后DOM立即变化,而是按照一定的策略进行DOM的更新
原理:
Vue是异步执行DOM更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环(event loop)中观察到的数据变化的watcher推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效去掉重复数据造成不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置vm.somedata = 'new value’时,DOM并不会立马更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。若此时你想要根据更新的DOM状态去做某些事情,就会出现问题。为了在数据变化后等待Vue完成更新DOM,可在数据变化后立即使用Vue.nextTick(callback),这样回调函数在DOM更新完成后就会调用。
例子:
const vm = new Vue({
el: '#app',
data: {
message: '原始的值'
}
})
vm.message = '修改后的值'
// DOM 还没有更新
console.log(vm.$el.textContent) // 原始的值
Vue.nextTick(function () {
// DOM 更新了
console.log(vm.$el.textContent) // 修改后的值
})
组件内使用 vm. n e x t T i c k ( ) 实例方法只需要通过 t h i s . nextTick() 实例方法只需要通过this. nextTick()实例方法只需要通过this.nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '修改后的值'
})
$nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情
this.message = '修改后的值'
console.log(this.$el.textContent) // => '原始的值'
await this.$nextTick()
console.log(this.$el.textContent) // => '修改后的值'
问题十四:懒加载的实现
一、什么是懒加载?
懒加载突出一个“懒”字,懒就是拖延迟的意思,所以“懒加载”说白了就是延迟加载,比如我们加载一个页面,这个页面很长很长,长到我们的浏览器可视区域装不下,那么懒加载就是优先加载可视区域的内容,其他部分等进入了可视区域在加载
二.为什么要懒加载?
减少无用资源的加载
提升用户体验
防止加载过多图片而影响其它资源文件的加载
三.懒加载的实现原理?
图片的加载是由 src 引起的,当对 src 赋值时,浏览器就会请求图片资源。根据这个原理,我们使用 HTML5 的 data-xxx 属性来储存图片的路径,在需要加载图片的时候,将 data-xxx 中图片的路径赋值给 src,这样就实现了图片的按需加载,即懒加载
四.实现步骤
知识点
window.innerHeight 是浏览器可视区的高度
document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离
imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离)
图片加载条件:img.offsetTop < window.innerHeight + document.body.scrollTop;
代码实现
<!DOCTYPE html>
<html lang="en">
<div class="container">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
<img src="loading.gif" data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winHeight= window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winHeight ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
window.onscroll = lozyLoad();
四. 微信小程序实现图片懒加载
十六. js执行顺序问题
setTimeout是宏任务【macrotask】,Promise整体是微任务【microtask】,
Promise 是放入 microtask 队列的, 而 setTimeout 放入 macrotask 队列.
主线程执行完了之后,先处理微任务【microtask】队列, 【微任务】队列每次处理直到队列为空, 接下来处理【宏任务】队列,【宏任务】 每次只处理的队列里的第一个任务, 当任务处理完后, 又会进入到【微任务】队列的处理. 如此反复。
setTimeout(function(){
console.log('D')
},0)
var promise = new Promise(function(resolve, reject){
console.log('A')
resolve()
}).then(function(){
console.log('C')
})
console.log('B');
// A
// B
// C
// D
十七.虚拟dom是什么?
从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。
虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对 DOM的描述。它设计的最初目的,就是更好的跨平台,比如Node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟DOM,因为虚拟DOM本身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实DOM结构,最终渲染到页面。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。
个人理解:
虚拟DOM就是用JS对象模拟了真实渲染好的DOM,所以模拟出来的DOM称为虚拟DOM,浏览器内核中有两个引擎,JS引擎和渲染引擎,他们是彼此分开的,且每一次使用JS去操作DOM时,两个引擎之间会发生“跨界交流”,这个交互的过程是有性能开销的,访问的越多会造成明显的性能卡顿问题,所以我们选择用JS去操作JS模拟出来的虚拟DOM这样JS和JS之间不存在跨界问题就会很快,新生成的虚拟DOM(每次操作节点会创建新的虚拟DOM)会和之前旧的虚拟DOM(初始化时就会创建虚拟DOM)进行diff比较,比较完成后把变化的地方一次性更新到真实的 DOM 树上(新的虚拟DOM会缓存下来和下次生成的虚拟DOM进行比较)。
vue里面
虚拟DOM(Virtual Dom)即虚拟节点。
虚拟DOM在Vue.js里做了两件事:
创建JS对象(虚拟节点),用来模拟真实DOM节点。
该对象包含了真实DOM的结构及其属性
将虚拟节点与旧虚拟节点进行对比,然后更新视图(渲染)
vue的VNode
在vue中存在一个VNode类,使用它可以实例化不同类型的vnode实例,而不同类型的vnode实例各自代表不同类型的DOM元素,vnode的作用主要是描述了怎样去创建DOM节点
vnode的几种类型
1.注释节点
2.文本节点
3.元素节点
4.组件节点
5.函数式组件
6.克隆节点
事实上,只有前三类节点会被创建并插入到DOM中
vnode有tag属性就是元素节点
没有tag属性就可以看isCommet属性,true代表注释节点,false就是文本节点了