🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来Vuet篇专栏内容:vue综合指南(二)
目录
21、介绍虚拟DOM
22、vue生命周期的理解
23、vue父组件向子组件传递数据?
24、vue-router 路由实现原理
25、v-if 和 v-show 区别
26、$route和$router的区别
27、对比 jQuery ,Vue 有什么不同
28、Vue 中怎么自定义指令
29、Vue 中怎么自定义过滤器
30、Vue 中 key 的作用
31、Vue 的核心思想是什么
32、Vue 等单页面应用的优缺点
33、Vue-router 使用params与query传参有什么区别
34、子组件像父组件传递事件?
35、Vue中 的作用
36、如何让CSS只在当前组件中起作用?
37、如何获取dom?
38、说出几种vue当中的指令和它的用法?
39、Vue Router的钩子函数有哪些?他们有哪些参数?
40、说明一下封装vue组件的原则和方法
21、介绍虚拟DOM
先介绍浏览器加载一个HTML文件需要做哪些事,帮助我们理解为什么我们需要虚拟DOM。webkit引擎的处理流程,一图胜千言:
所有浏览器的引擎工作流程都差不多,如上图大致分5步:创建DOM tree –> 创建Style Rules -> 构建Render tree -> 布局Layout –> 绘制Painting
第一步,用HTML分析器,分析HTML元素,构建一颗DOM树。
第二步:用CSS分析器,分析CSS文件和元素上的inline样式,生成页面的样式表。
第三步:将上面的DOM树和样式表,关联起来,构建一颗Render树。这一过程又称为Attachment。每个DOM节点都有attach方法,接受样式信息,返回一个render对象(又名renderer)。这些render对象最终会被构建成一颗Render树。
第四步:有了Render树后,浏览器开始布局,会为每个Render树上的节点确定一个在显示屏上出现的精确坐标值。
第五步:Render数有了,节点显示的位置坐标也有了,最后就是调用每个节点的paint方法,让它们显示出来。
当你用传统的源生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。显然例如计算DOM节点的坐标值等都是白白浪费性能,可能这次计算完,紧接着的下一个DOM更新请求,这个节点的坐标值就变了,前面的一次计算是无用功。
即使计算机硬件一直在更新迭代,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验。真实的DOM节点,哪怕一个最简单的div也包含着很多属性,可以打印出来直观感受一下:
虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。
实现虚拟DOM
我们来实现一个虚拟DOM。例如一个真实的DOM节点:代码见仓库里的src/firstStep
<div id="real-container"><p>Real DOM</p><div>cannot update</div><ul><li className="item">Item 1</li><li className="item">Item 2</li><li className="item">Item 3</li></ul>
</div>
用js对象来模拟DOM节点如下:
const tree = Element('div', { id: 'virtual-container' }, [Element('p', {}, ['Virtual DOM']),Element('div', {}, ['before update']),Element('ul', {}, [Element('li', { class: 'item' }, ['Item 1']),Element('li', { class: 'item' }, ['Item 2']),Element('li', { class: 'item' }, ['Item 3']),]),
]);
const root = tree.render();
document.getElementById('virtualDom').appendChild(root);
用js对象模拟DOM节点的好处是,页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了。等更新完后,再将最终的js对象映射成真实的DOM,交由浏览器去绘制。
那具体怎么实现呢?看一下Element方法的具体实现:
function Element(tagName, props, children) {if (!(this instanceof Element)) {return new Element(tagName, props, children);}
this.tagName = tagName;this.props = props || {};this.children = children || [];this.key = props ? props.key : undefined;
let count = 0;this.children.forEach((child) => {if (child instanceof Element) {count += child.count;}count++;});this.count = count;
}
第一个参数是节点名(如div),第二个参数是节点的属性(如class),第三个参数是子节点(如ul的li)。除了这三个参数会被保存在对象上外,还保存了key和count。
有了js对象后,最终还需要将其映射成真实的DOM:
Element.prototype.render = function() {const el = document.createElement(this.tagName);const props = this.props;
for (const propName in props) {setAttr(el, propName, props[propName]);}
this.children.forEach((child) => {const childEl = (child instanceof Element) ? child.render() : document.createTextNode(child);el.appendChild(childEl);});
return el;
};
上面都是自解释代码,根据DOM名调用源生的createElement创建真实DOM,将DOM的属性全都加到这个DOM元素上,如果有子元素继续递归调用创建子元素,并appendChild挂到该DOM元素上。这样就完成了从创建虚拟DOM到将其映射成真实DOM的全部工作。
22、vue生命周期的理解
当面试官问:“谈谈你对vue的生命周期的理解”,听到这句话你是不是心里暗自窃喜:这也太容易了吧,不就是beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed 这几个钩子函数么,创建=>挂载=>更新=>销毁,So easy !!!
非也非也。如果你只是简单罗列出这几个钩子函数的名称,不具体深入阐述的话,你这样的回答很难令面试官满意。如何才能以点带面深入阐述自己对vue的生命周期理解,从而让面试官对你留下好印象呢?
别急,闰土大叔来告诉你,下次再碰到这个问题,你可以直接甩给面试官下面这张Image就OK了~
当然,甩张Image给面试官这句话肯定是开玩笑的(适度幽默,缓解紧张气氛)。不过这张流程图还是有用的,因为它是我从Vue官网上拷贝下来的,只要你能理解了这张图,也就对Vue的生命周期有了一个大致的了解。那么接下来,闰土大叔将手摸手教你如何深入浅出地说出令面试官满意的、有亮点的回答。
在谈到Vue的生命周期的时候,我们首先需要创建一个实例,也就是在 new Vue ( ) 的对象过程当中,首先执行了init(init是vue组件里面默认去执行的),在init的过程当中首先调用了beforeCreate,然后在injections(注射)和reactivity(反应性)的时候,它会再去调用created。所以在init的时候,事件已经调用了,我们在beforeCreate的时候千万不要去修改data里面赋值的数据,最早也要放在created里面去做(添加一些行为)。
当created完成之后,它会去判断instance(实例)里面是否含有“el”option(选项),如果没有的话,它会调用vm.$mount(el)这个方法,然后执行下一步;如果有的话,直接执行下一步。紧接着会判断是否含有“template”这个选项,如果有的话,它会把template解析成一个render function ,这是一个template编译的过程,结果是解析成了render函数:
render (h) {return h('div', {}, this.text)
}
解释一下,render函数里面的传参h就是Vue里面的createElement方法,return返回一个createElement方法,其中要传3个参数,第一个参数就是创建的div标签;第二个参数传了一个对象,对象里面可以是我们组件上面的props,或者是事件之类的东西;第三个参数就是div标签里面的内容,这里我们指向了data里面的text。
使用render函数的结果和我们之前使用template解析出来的结果是一样的。render函数是发生在beforeMount和mounted之间的,这也从侧面说明了,在beforeMount的时候,$el还只是我们在HTML里面写的节点,然后到mounted的时候,它就把渲染出来的内容挂载到了DOM节点上。这中间的过程其实是执行了render function的内容。
在使用.vue文件开发的过程当中,我们在里面写了template模板,在经过了vue-loader的处理之后,就变成了render function,最终放到了vue-loader解析过的文件里面。这样做有什么好处呢?原因是由于在解析template变成render function的过程,是一个非常耗时的过程,vue-loader帮我们处理了这些内容之后,当我们在页面上执行vue代码的时候,效率会变得更高。
beforeMount在有了render function的时候才会执行,当执行完render function之后,就会调用mounted这个钩子,在mounted挂载完毕之后,这个实例就算是走完流程了。
后续的钩子函数执行的过程都是需要外部的触发才会执行。比如说有数据的变化,会调用beforeUpdate,然后经过Virtual DOM,最后updated更新完毕。当组件被销毁的时候,它会调用beforeDestory,以及destoryed。
这就是vue实例从新建到销毁的一个完整流程,以及在这个过程中它会触发哪些生命周期的钩子函数。那说到这儿,可能很多童鞋会问,钩子函数是什么意思?
钩子函数,其实和回调是一个概念,当系统执行到某处时,检查是否有hook,有则回调。说的更直白一点,每个组件都有属性,方法和事件。所有的生命周期都归于事件,在某个时刻自动执行。
其实,当你跟面试官阐述到这儿的时候,面试官基本上已经满意你的回答了,隐约看到了你的技术功底。当然,如果你还想更进一步,让面试官对你刮目相看,达到加分的效果,你还可以这样说:
在这个过程当中,Vue为我们提供了renderError方法,这个方法只有在开发的时候它才会被调用,在正式打包上线的过程当中,它是不会被调用的。它主要是帮助我们调试render里面的一些错误。
renderError (h, err) {return h('div', {}, err.stack)
}
有且只有当render方法里面报错了,才会执行renderError方法。
所以我们主动让render函数报个错:
render (h) {throw new TypeError('render error')
}
如图所示,渲染出来的就是Error信息了。还有一点,renderError只有在本组件的render方法报错的情况下它才会被调用。
23、vue父组件向子组件传递数据?
通过props
24、vue-router 路由实现原理
更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有2
种方式:
1.Hash --- 利用 URL 中的hash("#");
2.利用 History interface 在HTML5中新增的方法。
Vue 中,它是通过 mode 这一参数控制路由的实现模式:
const router=new VueRouter({mode:'history',routes:[...]
})
创建 VueRouter 的实例对象时,mode 以构造参数的形式传入,如下代码:
源码
mode 参数:
1.默认 hash
-
history。如果浏览器不支持 history 新特性,则采用 hash
-
如果不在浏览器环境下,就采用 abstract(Node环境下)
-
mode 区别:
-
mode:"hash" 多了 “#”
http://localhost:8080/#/login
2.mode:"history"
http://localhost:8080/recommend
HashHistory:
hash("#") 的作用是加载 URL 中指示网页中的位置。
本身以及它后面的字符称职位 hash,可通过 window.location.hash 获取
特点:
-
hash 虽然出现在 url 中,但不会被包括在 http 请求中,它是用来指导浏览器动作的,对服务器端完全无用,因此,改变 hash 不会重新加载页面。
可以为 hash 的改变添加监听事件:
window.addEventListener("hashchange",funcRef,false)
-
每一次改变 hash(window.localtion.hash),都会在浏览器访问历史中增加一个记录。
利用 hash 的以上特点,就可以来实现前端路由"更新视图但不重新请求页面"的功能了。
HashHistory 拥有两个方法,一个是 push, 一个是 replace
两个方法:HashHistory.push() 和 HashHistory.replace()
HashHistory.push() 将新路由添加到浏览器访问历史的栈顶
HashHisttory.push()
从设置路由改变到视图更新的流程:
$router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> {app._route = route} --> vm.render()
解析:
1 $router.push() //调用方法
2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
3 History.transitionTo() //监测更新,更新则调用History.updateRoute()
4 History.updateRoute() //更新路由
5 {app._route= route} //替换当前app路由
6 vm.render() //更新视图
transitionTo() 方法是父类中定义的是用来处理路由变化中的基础逻辑的,push() 方法最主要的是对 window 的 hash 进行了直接赋值:
window.location.hash=route.fullPath
hash 的改变会自动添加到浏览器的访问历史记录中。 那么视图的更新是怎么实现的呢,我们来看看父类 History 中的 transitionTo() 方法:
transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {const route = this.router.match(location, this.current)this.confirmTransition(route, () => {this.updateRoute(route)...})
}
updateRoute (route: Route) {this.cb && this.cb(route)}
listen (cb: Function) {this.cb = cb
}
可以看到,当路由变化时,调用了Hitory
中的this.cb
方法,而this.cb
方法是通过History.listen(cb)
进行设置的,回到VueRouter
类定义中,找到了在init()
中对其进行了设置:
init (app: any /* Vue component instance */) {this.apps.push(app)
history.listen(route => {this.apps.forEach((app) => {app._route = route})})
}
HashHistory.replace()
replace()方法与push()方法不同之处在于,它并不是将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由
HashHisttory.replace()
HTML5History
History interface 是浏览器历史记录栈提供的接口,通过back()、forward()、go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。``
从 HTML5开始,History interface 提供了2个新的方法:pushState()、replaceState() 使得我们可以对浏览器历史记录栈进行修改:
window.history.pushState(stateObject,title,url)window.history,replaceState(stateObject,title,url)
stateObject:当浏览器跳转到新的状态时,将触发 Popstate 事件,该事件将携带这个 stateObject 参数的副本
title:所添加记录的标题
url:所添加记录的 url``
这2
个方法有个共同的特点:当调用他们修改浏览器历史栈后,虽然当前url
改变了,但浏览器不会立即发送请求该url
,这就为单页应用前端路由,更新视图但不重新请求页面提供了基础
1.push
与hash模式类似,只是将window.hash改为history.pushState
2.replace
与hash模式类似,只是将window.replace改为history.replaceState
3.监听地址变化
在HTML5History的构造函数中监听popState(window.onpopstate)
25、v-if 和 v-show 区别
1.共同点
v-if 和 v-show 都可以动态地显示DOM元素
2.区别
(1)手段:
v-if 是动态的向DOM树内添加或者删除DOM元素; v-show 是通过设置DOM元素的display样式属性控制显隐;
(2)编译过程:
v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件; v-show只是简单的基于css切换;
(3)编译条件:
v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
(4)性能消耗:
v-if 有更高的切换消耗; v-show 有更高的初始渲染消耗;
(5)使用场景:
v-if 适合运营条件不大可能改变; v-show 适合频繁切换。
26、$route
和$router
的区别
router.push方法,路由跳转,传递参数等。
返回上一个历史history用$router.to(-1)
$route为当前router跳转对象。里面可以获取当前路由的name,path,query,parmas等 可以拿里边的值来做各种判断。
27、对比 jQuery ,Vue 有什么不同
1.jquery介绍:想必大家都用过jquery吧,这个曾经也是现在依然最流行的web前端js库,可是现在无论是国内还是国外他的使用率正在渐渐被其他的js库所代替,随着浏览器厂商对HTML5规范统一遵循以及ECMA6在浏览器端的实现,jquery的使用率将会越来越低
2.vue介绍:vue是一个兴起的前端js库,是一个精简的MVVM。从技术角度讲,Vue.js 专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来,通过对数据的操作就可以完成对页面视图的渲染。当然还有很多其他的mvmm框架如Angular,React都是大同小异,本质上都是基于MVVM的理念。 然而vue以他独特的优势简单,快速,组合,紧凑,强大而迅速崛起
3.vue和jquey对比
jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:$("lable").val();
,它还是依赖DOM元素的值。
Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。
28、Vue 中怎么自定义指令
指令的概念
指令是指可以控制操作 DOM 的一些小命令,通常以 v-
前缀出现的特殊特性。
例如我们经常使用的v-if
、v-show
、v-bind
、v-on
、v-html
等。
使用指令时,你可以传递值,字符串,也可以给指令添加参数,修饰符等等。比如:
1.传递值
<p v-if="isShow">你好,我是六哥</p>
let vm = new Vue({el: "#app",data: {isShow: true}
});
2.字符串
<p v-text="'hello world'"></p>
3.添加参数
// class,style 就是传给指令的参数
<div v-bind:class="classObj"></div>
<div v-bind:style="classObj"></div>
4.修饰符
// prevent 指令的修饰符
<button v-on:submit.prevent="onSubmit"></button>
以上是你经常使用指令的方式,了解这些之后,它可以帮助我们更好的认识自定义指令的 一些参数问题。下面就来看看自定义指令。
自定义指令
指令的注册方式和「过滤器」、「混入」、「组件」注册的方式一样都分为两种:一是全局注册,二是局部注册。
1.全局注册
Vue.directive('name', {})
2.局部注册
directives: {name: {}
}
然后在模版中直接使用即可。
<p v-name>你好,六哥在这</p>
我个人更倾向于使用全局注册的方式,因为既然已经使用了自定义指令,应该是通用,可复用的。
所以提供整个项目使用的指令才更有价值,而不仅仅只限于某个组件内部。如果单一地方使用直接把功能搂出就行了,何必费这力气。
继续来看具体的实现方式。
Vue 提供了自定义指令的几个钩子函数:
-
bind:指令第一次绑定到元素时调用,只执行一次。
-
inserted:被绑定的元素,插入到父节点的 DOM 中时调用。
-
update:组件更新时调用。
-
componentUpdated:组件与子组件更新时调用。
-
unbind:指令与元素解绑时调用,只执行一次。
除update 与 componentUpdated 钩子函数之外,每个钩子函数都含有 el
、binding
、vnode
这三个参数。
oldVnode
只有在 update 与 componentUpdated 钩子中生效。
参数el
就是指令绑定的 DOM 元素,而binding
是一个对象,它包含一下属性:name
、value
、oldValue
、expression
、arg
、modifiers
。
另外值得注意的一点是,除了 el
之外,binding
、vnode
属性都是只读的。
熟悉指令的创建方式与参数之后,我们就用它来创建一个案例。
创建自定义指令
Loading 是项目中最常见的一个小功能,别看它功能小,但是起到的作用却很大,手动创建一个 Loading 指令。
Vue.directive("loading", {bind(el, binding) {if (binding.value) {let div = document.createElement("div");div.className = "loading-parent";div.innerHTML = `<div class="loading-spinner"><i class='el-icon-loading'></i></div>`;el.appendChild(div);}},update(el, binding) {if (binding.value) {let div = document.createElement("div");div.className = "loading-parent";div.innerHTML = `<div class="loading-spinner"><i class='el-icon-loading'></i></div>`;el.appendChild(div);el.load = div;} else {el.load && el.load.parentNode && el.load.parentNode.removeChild(el.load);}}
});
然后我们直接就可以在模版中使用了。
<div v-loading="loading" class="box"></div>
你也看出来了很多代码是重复的,怎么办呢?
Vue 中给我们提供了简写方式。当只有这两个钩子函数时bind
与 update
,我们可以简写如下。
Vue.directive("loading", function(el, binding) {if (binding.value) {let div = document.createElement("div");div.className = "loading-parent";div.innerHTML = `<div class="loading-spinner"><i class='el-icon-loading'></i></div>`;el.appendChild(div);el.load = div;} else {el.load && el.load.parentNode && el.load.parentNode.removeChild(el.load);}
});
我们实现了一个非常简单的指令,但还不够灵活,比如我想添加 loading 的背景色,修改图标的颜色怎么办呢?
这时候就需要给指令传入多个值,改造下实现背景与图标颜色。
Vue.directive("loading", function(el, binding) {if (binding.value) {let div = document.createElement("div");div.className = "loading-parent";div.style.backgroundColor = binding.value.background;div.style.color = binding.value.color;div.innerHTML = `<div class="loading-spinner"><i class='el-icon-loading'></i></div>`;el.appendChild(div);el.load = div;} else {el.load && el.load.parentNode && el.load.parentNode.removeChild(el.load);}
});
直接使用
<div v-loading="{color: 'white', background: '#000'}">我可以拥有更多属性</div>
在文章开始我们介绍指令时,还说到指令的「参数」与「修饰符」这里我就不多介绍了,大家不妨自己去尝试一下吧。体验下自定义指令的魅力。
29、Vue 中怎么自定义过滤器
注意事项:
(1)全局方法 Vue.filter() 注册一个自定义过滤器,必须放在Vue实例化前面
(2) 过滤器函数始终以表达式的值作为第一个参数。带引号的参数视为字符串,而不带引号的参数按表达式计算
(3)可以设置两个过滤器参数,前提是这两个过滤器处理的不冲突
(4)用户从input输入的数据在回传到model之前也可以先处理
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>vue自定义过滤器</title><script src="../js/vue.js" type="text/javascript" charset="utf-8"></script><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /></head><body><div class="test"><p>{{message | sum}}</p><p>{{message | cal 10 20}}</p> <!--过滤器函数始终以表达式的值作为第一个参数。带引号的参数视为字符串,而不带引号的参数按表达式计算。--><p>{{message | sum | currency }}</p> <!--添加两个过滤器,注意不要冲突--><input type="text" v-model="message | change"> <!--用户从input输入的数据在回传到model之前也可以先处理--></div><script type="text/javascript">// -----------------------------------------华丽分割线(从model->view)---------------------------------------Vue.filter("sum", function(value) { //全局方法 Vue.filter() 注册一个自定义过滤器,必须放在Vue实例化前面return value + 4;});Vue.filter('cal', function (value, begin, xing) { //全局方法 Vue.filter() 注册一个自定义过滤器,必须放在Vue实例化前面return value + begin + xing;});
// -----------------------------------------华丽分割线(从view->model)---------------------------------------Vue.filter("change", {read: function (value) { // model -> view 在更新 `<input>` 元素之前格式化值return value;},write: function (newVal,oldVal) { // view -> model 在写回数据之前格式化值console.log("newVal:"+newVal); console.log("oldVal:"+oldVal);return newVal;}});
var myVue = new Vue({el: ".test",data: {message:12}});</script></body>
</html>
二 自定义过滤器(注册在实例化内部)
上面的例子直接注册在Vue全局上面,其他不用这个过滤器的实例也会被迫接受,其实过滤器可以注册在实例内部,仅在使用它的实例里面注册
上面的程序改写为:
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>vue自定义过滤器</title><script src="../js/vue.js" type="text/javascript" charset="utf-8"></script><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /></head><body><div class="test"><p>{{message | sum}}</p><p>{{message | cal 10 20}}</p> <!--过滤器函数始终以表达式的值作为第一个参数。带引号的参数视为字符串,而不带引号的参数按表达式计算。--><p>{{message | sum | currency }}</p> <!--添加两个过滤器,注意不要冲突--><input type="text" v-model="message | change"> <!--用户从input输入的数据在回传到model之前也可以先处理--></div><script type="text/javascript">Vue.filter("change", {read: function (value) { // model -> view 在更新 `<input>` 元素之前格式化值return value;},write: function (newVal,oldVal) { // view -> model 在写回数据之前格式化值console.log("newVal:"+newVal); console.log("oldVal:"+oldVal);return newVal;}});
var myVue = new Vue({el: ".test",data: {message:12},filters: {sum: function (value) {return value + 4;},cal: function (value, begin, xing) {return value + begin + xing;}}});</script></body>
</html>
30、Vue 中 key 的作用
1.v-if中用key管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做,除了使 Vue 变得非常快之外,还有一些有用的好处。例如,如果你允许用户在不同的登录方式之间切换:
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模版使用了相同的元素,<input> 不会被替换掉——仅仅是替换了它的 placeholder。这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来声明“这两个元素是完全独立的——不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
2、v-for中的key
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这个类似 Vue 1.x 的 track-by="$index" 。
这个默认的模式是有效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值)的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有唯一 id。这个特殊的属性相当于 Vue 1.x 的 track-by ,但它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值(在这里使用简写):
31、Vue 的核心思想是什么
Vue核心思想:数据驱动、组件化
一,数据驱动
传统的前端数据交互是用Ajax从服务端获取数据,然后操作DOM来改变视图;或者前端交互要改变数据时,又要再来一次上述步骤,而手动操作DOM是一个繁琐的过程且易出错。 Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。它让开发者省去了操作DOM的过程,只需要改变数据。Vue会通过Dircetives指令,对DOM做一层封装,当数据发生改变会通知指令去修改对应的DOM,数据驱动DOM变化,DOM是数据的一种自然映射。 Vue还会对操作进行监听,当视图发生改变时,vue监听到这些变化,从而改变数据,这样就形成了数据的双向绑定。 Vue是一种MVVM框架。而DOM是数据的一个种自然映射。传统的模式是通过Ajax请求从model请求数据,然后手动的触发DOM传入数据修改页面。Vue中,Directives对view进行了封装,当model里的数据发生变化是,Vue就会通过Directives指令去修改DOM。同时也通过DOM Listener实现对视图view的监听,当DOM改变时,就会被监听到,实现model的改变,实现数据的双向绑定。
二,组件响应原理 数据(model)改变驱动视图(view)自动更新 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。 用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。 每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
三,组件化
扩展HTML元素,封装可重用的代码。每一个组件都对应一个ViewModel。页面上每个独立的可视/可交互区域都可以视为一个组件。每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就进维护。页面是组件的容器,组件可以嵌套自由组合形成完整的页面。
组件化实现了扩展HTML元素,封装可用的代码。页面上每个独立的可视/可交互区域视为一个组件;每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护;页面不过是组件的容器,组件可以嵌套自由组合形成完整的页面。
32、Vue 等单页面应用的优缺点
先来说说什么是单页面应用和多页面应用:
单页面应用(SPA),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。
多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新。
单页面的优点和缺点:
优点:
1、用户体验好,快,内容的改变不需要重新加载整个页面,对服务器压力较小。
2、前后端分离,比如vue项目
3、完全的前端组件化,前端开发不再以页面为单位,更多地采用组件化的思想,代码结构和组织方式更加规范化,便于修改 和调整;
缺点:
1、首次加载页面的时候需要加载大量的静态资源,这个加载时间相对比较长。
2、不利于 SEO优化,单页页面,数据在前端渲染,就意味着没有 SEO。
3、页面导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自 己建立堆栈管理)
33、Vue-router 使用params与query传参有什么区别
首先简单来说明一下$router
和$route
的区别
//$router : 是路由操作对象,只写对象
//$route : 路由信息对象,只读对象
//操作 路由跳转
this.$router.push({name:'hello',params:{name:'word',age:'11'}
})
//读取 路由参数接收
this.name = this.$route.params.name;
this.age = this.$route.params.age;123456789101112131415
1·query传递参数
我看了很多人都说query传参要用path来引入,params传参要用name来引入,只是我测试了一下,query使用name来引入也可以传参,使用path也可以。如果有人知道原因可以告诉我一下,谢谢!
//query传参,使用name跳转
this.$router.push({name:'second',query: {queryId:'20180822',queryName: 'query'}
})
//query传参,使用path跳转
this.$router.push({path:'second',query: {queryId:'20180822',queryName: 'query'}
})
//query传参接收
this.queryName = this.$route.query.queryName;
this.queryId = this.$route.query.queryId;123456789101112131415161718192021
最终不管是path引入还是name引入效果都一样如下图
2·params传递参数
注:使用params传参只能使用name进行引入
-
使用params传参
//params传参 使用name
this.$router.push({name:'second',params: {id:'20180822',name: 'query'}
})
//params接收参数
this.id = this.$route.params.id ;
this.name = this.$route.params.name ;
//路由
{
path: '/second/:id/:name',
name: 'second',
component: () => import('@/view/second')
}1234567891011121314151617181920
效果如下图
需要注意的是:
-
params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
-
params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。
如果路由后面没有 /:id/:name效果如下图,地址栏没有参数
但是如果你刷新一下,就会发现页面失败,效果如下图
因此我们不可能让用户不要刷新,所以我们必须在路由后面加上 /:id/:name
-
如果使用path进行传参
//params传参 使用path
this.$router.push({path:'second',params: {id:'20180822',name: 'query'}
})
//params接收参数
this.id = this.$route.params.id ;
this.name = this.$route.params.name ;123456789101112
效果如下图
使用path传参什么效果都没有。
3.总结
-
传参可以使用params和query两种方式。
-
使用params传参只能用name来引入路由,即push里面只能是name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!。
-
使用query传参使用path来引入路由。
-
params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
-
二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。
34、子组件像父组件传递事件?
$emit方法
35、Vue中 <keep-alive>
的作用
1.keep-alive的作用以及好处
在做电商有关的项目中,当我们第一次进入列表页需要请求一下数据,当我从列表页进入详情页,详情页不缓存也需要请求下数据,然后返回列表页,这时候我们使用keep-alive来缓存组件,防止二次渲染,这样会大大的节省性能。
2.keep-alive的基本用法
需要缓存的组件内容直接在router中添加:
3.keep-alive的生命周期
当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。
36、如何让CSS只在当前组件中起作用?
在组件中的style前面加上scoped
37、如何获取dom?
ref="domName" 用法:this.$refs.domName
38、说出几种vue当中的指令和它的用法?
:v-model双向数据绑定; v-for循环; v-if v-show 显示与隐藏; v-on事件;v-once: 只绑定一次。
39、Vue Router的钩子函数有哪些?他们有哪些参数?
全局的:beforeEach、beforeResolve、afterEach 路由的:beforeEnter 组件的:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 参数:to、from、next;正对不同的钩子函数参数有所差异。
40、说明一下封装vue组件的原则和方法
页面尽量不要封装为组件,只将一些复用率高的部分封装为组件就行。例如新增或修改信息的弹框,七八个页面都要用,自然有理由封装为组件。
并且要高内聚低耦合,说白了万万不可依赖于某一个或某几个页面的逻辑