vue相关
1.VNode是什么?什么是虚拟dom?
在vue.js中存在一个VNode类,使用它可以实例化不同类型的vnode实例,这些实例可以被渲染成真实的dom对象。
虚拟dom是通过 js 对象来描述真实 DOM 结构和属性。例如:
//下面是一个a标签
<a class="demo" style="color: red" href="#">
文本内容
</a>
// 用VNode的实例对象来描述
{
tag: 'a',
data: {
calss: 'demo',
attrs: {
href: '#'
},
style: {
color: 'red'
}
},
children: ['文本内容']
}
由于真实dom频繁排版与重绘的效率低,对虚拟DOM进行频繁修改,然后一次性对比并修改真实DOM中需要改的部分,最后在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗。
举个例子:下面有一段html代码
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
</ul>
用js对象模拟如下
{
tag:'ul',
attrs:{
id:'list'
},
children:[
{
tag:'li',
attrs:{ className:'item' },
children:['Item 1']
},
{
tag:'li',
attrs:{ className:'item' },
children:['Item 2']
}
]
}
如果我们要把Item 2改成Item B,我们会先生成类似上面的js DOM结构如下
{
tag:'ul',
attrs:{
id:'list'
},
children:[
{
tag:'li',
attrs:{ className:'item' },
children:['Item 1']
},
{
tag:'li',
attrs:{ className:'item' },
children:['Item B'] //注意此处Item 2改编成了Item B
}
]
}
然后通过对比发现改变的只有Item B;现在提供两种改变的思路:
- 1.直接删掉所有Item,再把Item1 和 Item B加进来。
- 2.先生成最终js的DOM结构,然后对比之前的js DOM结构,找出差异部分再改变 。
只从思路上分析,第一种更加简单快捷;然而实际是,浏览器最最最耗费性能的就是DOM操作,dom操作是最昂贵的,现在的浏览器执行js是非常快的,所以虚拟DOM有其存在的价值。
vdom如何应用,核心API是什么?(这个可以先不看)
要我们自己去实现一个虚拟dom,大概过程应该有以下三步:
- compile:如何把真实DOM编译成vnode。
- diff:我们要如何知道oldVnode和newVnode之间有什么变化。
- patch:如果把这些变化用打补丁的方式更新到真实dom上去。
vue的虚拟dom实现参考了snabbdom.js的实现。
snabbdom.js核心API是两个函数,一个h函数(用来创建vnode),一个patch函数(用来对比变化并替换)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
var snabbdom = window.snabbdom;
//定义path
var path = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
//定义h
var h = snabbdom.h;
var container = document.getElementById('container');
//生成vnode
var vnode = h('ul#list',{},[
h('li.item',{},'Item 1'),
h('li.item',{},'Item 2')
]);
path(container,vnode);
document.getElementById('btn-change').addEventListener('click',function () {
//生成newVnode
var newVnode = h('ul#list',{},[
h('li.item',{},'Item 1'),
h('li.item',{},'Item B'),
h('li.item',{},'Item 3')
]);
path(vnode,newVnode);
});
</script>
</body>
</html>
diff算法大概结构
const vDom = {
tag:'ul',
attrs:{id:'list'},
children:[
{
tag:'li',
attrs: {className:'item'},
children:['item1']
},{
tag:'li',
attrs:{className:'item'},
children:['item2']
}
]
};
function createElement(vnode) {
let tag = vnode.tag,
attrs = vnode.attrs || {},
children = vnode.children || [];
if(!tag){return null;}
//创建真实的DOM元素
let elem = document.createElement(tag);
//属性
let attrName;
for(attrName in attrs){
if(attrs.hasOwnProperty(attrName)){
//给elem添加属性
elem.setAttribute(attrName,attrs[attrName]);
}
}
//子元素
children.forEach(function (childVNode) {
//给elem添加子元素
elem.appendChild(createElement(childVNode))
});
// 返回真实的dom元素
return elem;
}
const newVNode = {
tag:'ul',
attrs:{id:'list'},
children:[
{
tag:'li',
attrs: {className:'item'},
children:['item1']
},{
tag:'li',
attrs:{className:'item'},
children:['item222']
},{
tag:'li',
attrs:{className:'item'},
children:['item3']
}
]
};
function updateChildren(vNode,newVNode) {
let children = vNode.children || [],
newChildren = newVNode.children || [];
children.forEach(function (childVNode,index) {
let newChildVNode = newChildren[index];
if(childVNode.tag===newChildVNode.tag){
updateChildren(childVNode,newChildVNode);
}else{
replaceNode(childVNode,newChildVNode);
}
});
}
function replaceNode(vNode,newVNode) {
let elem = vNode.elem,
newElem = createElement(newVNode);
//替换
//...
}
//节点新增和删除
//节点重新排序
//节点属性、样式、事件绑定
//如何极致压榨性能
2.v-show 与 v-if 区别?
- v-show是css切换,v-if是完整的销毁和重新创建;
- 使用频繁切换时用v-show,运行时较少改变时用v-if;
- v-if="false" v-if是条件渲染,当false的时候不会渲染。
3.计算属性和 watch 的区别?
计算属性是自动监听依赖值的变化,从而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。
所以区别来源于用法,只是需要动态值,那就用计算属性;需要知道值的改变后执行业务逻辑,才用 watch。
4.computed 和 methods 有什么区别?
methods是一个方法,它可以接受参数,而computed不能,computed是可以缓存的,methods不会。
5.vue的生命周期
- beforeCreate
vue实例的挂载元素el和数据对象data都为undefined,还未初始化。 - created
vue实例的数据对象有了,el还没有。 - beforeMount:
挂载开始之前被调用,相关的render函数首次被调用(虚拟DOM),实例已完成以下的配置: 编译模板,把data里面的数据和模板生成html,完成了el和data 初始化,注意此时还没有挂在html到页面上。 - mounted:
挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次。 - 更新前后 beforeUpdate/updated
当data变化时,会触发beforeUpdate和updated方法。 - beforeDestory
在实例销毁之前调用,实例仍然完全可用,这一步还可以用this来获取实例,一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件。 - destoryed
在实例销毁之后调用,调用后,所以的事件监听器会被移出,所有的子实例也会被销毁,该钩子在服务器端渲染期间不被调用。
路由的跳转方式
- <router-link to='home'> router-link标签会渲染为<a>标签。
- 编程式导航 也就是通过js跳转 比如 router.push('/home')
6.nextTick()
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后,立即使用这个回调函数,获取更新后的 DOM。
// 修改数据
vm.msg = 'Hello'
// DOM 还未更新
Vue.nextTick(function () {
// DOM 更新
})
7.slot插槽
- 单个插槽
当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。 - 命名插槽
solt元素可以用一个特殊的特性name来进一步配置如何分发内容。多个插槽可以有不同的名字。这样可以将父组件模板中 slot 位置,和子组件 slot 元素产生关联,便于插槽内容对应传递
js相关
1.继承的方法有哪些?并解释各个继承详情。
/**1.构造函数继承**/
function parent(){
this.colors = ['red','blue'];
}
function children(){
parent.call( this );
}
var c1 = new children();
console.log( c1.colors ); // ['red','blue']
/**2.实例继承**/
function parent2(){
this.colors = ['red','blue'];
}
function children2(){}
children2.prototype = new parent2();
var c2_1 = new children2();
var c2_2 = new children2();
console.log( c2_1.colors ); // ['red','blue']
console.log( c2_2.colors ); // ['red','blue']
c2_1.colors.push('white');
console.log( c2_2.colors ); // ['red','blue','white']
/**3.组合继承**/
function parent3(){
this.colors = ['red','blue'];
}
function children3(){
parent3.call(this);
}
children3.protopype = new parent3();
var c3_1 = new children3();
var c3_2 = new children3();
console.log( c3_1.colors ); // ['red','blue']
console.log( c3_2.colors ); // ['red','blue']
c3_1.colors.push('white');
console.log( c3_2.colors ); // ['red','blue']
/**4.组合寄生继承(最优 记住这个就可以)**/
function parent4(){
this.colors = ['red','blue'];
}
function children4(){
parent4.call(this);
}
children4.prototype = Object.create(parent4);
children4.prototype.constructor = children4;
var c4_1 = new children4();
var c4_2 = new parent4();
console.log( c4_1 instanceof parent4 ); //false
console.log( c4_2 instanceof parent4 ); //true
2.什么是闭包?
简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
外部函数调用之后在其内部声明的变量本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。
function outer(){
var a = 1;
return function(){
console.log(a++);
}
}
var b = outer();
b();//1
b();//由于a的值不会被释放,再次调用结果为2
应用场景,比如单例模式,防抖函数,节流函数等。
/**单例模式 得到的始终是同一个对象*/
(function (global) {
function SimpleObj() {
var instance = null;
return function () {
if (instance == null) {
instance = {
name: 'simapleobj'
}
}
return instance;
}
}
global.getInstance = SimpleObj();
})(window)
getInstance(); //{name: "simapleobj"}
var a = getInstance();
console.log(a); //{name: "simapleobj"}
var b = getInstance();
console.log(b); //{name: "simapleobj"}
a.name = 'ACE';
console.log(a); //{name: "ACE"}
console.log(b); //{name: "ACE"}
var c = getInstance();
console.log(c); //{name: "ACE"}
getInstance(); //{name: "ACE"}
/*防抖函数*/
<input type="text">
<script>
var input = document.querySelector("input");
function debounce(func, delay) {
let time;
return function () {
//保存this指向
const context = this;
//保存传入的参数
const args = arguments;
//清除上次的延时器
clearTimeout(time);
//重新设置一个延时器,延迟为delay毫秒
time = setTimeout(function () {
func.apply(context, args)
}, delay)
}
}
var inputEvent = debounce(function () {
console.log("向后端发送请求");
}, 1500);
input.addEventListener("input", inputEvent);
</script>
3.原型链
每个函数有个prototype属性,指向其原型对象,原型对象有一个constructor属性指向构造函数;通过函数实例化(new)出来的对象有一个proto属性,也指向函数的原型对象;原型对象也有__proto__属性,指向原型对象的原型对象,通过一层层往上指,直到Object.prototype。
function foo(){}
let f = new foo();
console.log(f.__proto__===foo.prototype);//true
console.log(foo.prototype.constructor===foo);//true
4.如何解决跨域问题 详解转载
- 1.通过jsonp跨域(只支持get方式)
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过script标签,再请求一个带参网址实现跨域通信。
html代码
<body>
<script>
function test(data) {
alert(data.name);
}
</script>
<script src="http://127.0.0.1:3000/jsonp?callback=test"></script>
</body>
服务端代码(用的express)
router.get('/jsonp', function (req, res, next) {
res.set({
'Content-Type': 'json',
});
var data = {
"name": "Monkey"
};
data = JSON.stringify(data);
res.end(`${req.query.callback}(${data})`);
});
- 2.跨域资源共享(CORS)
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置;
若要带cookie请求:前后端都需要设置。
前端设置
1)原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
2)jQuery ajax
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
3)vue框架
//axios设置:
axios.defaults.withCredentials = true;
// vue-resource设置:
Vue.http.options.credentials = true;
服务端设置
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
/*
* 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
* 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
*/
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
- WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯。
实现求和sum,支持sum(1), sum(1,2,3,4), sum(1)(2)(3), console.log(sum(1)(2,3)(4)) = 10
function sum(...rest) {
let rst = 0;
rest.forEach(item => rst += item);
let temp = function (...innerRest) {
innerRest.forEach(item => rst += item)
return temp;
}
temp.toString = function () {
return rst;
}
return temp;
}
console.log(sum(1))
console.log(sum(1)(2))
console.log(sum(1, 2)(3))
console.log(sum(1, 2)(3)(4, 5))
chain = new Chain, chain.eat().sleep(5).eat().sleep(6).work()
class Chain {
constructor(name) {
console.log(name)
this.stack = [];
this.time = null;
}
async invoke() {
console.log("invoke")
if (this.time) {
clearTimeout(this.time)
}
this.time = setTimeout(async () => {
while (this.stack.length) {
await this.stack.shift()();
}
}, 3000)
}
eat() {
console.log("push eat")
this.stack.push(
function () {
return new Promise(function (reslove) {
console.log("eat...")
reslove()
})
}
)
this.invoke()
return this;
}
work() {
console.log("push work")
this.stack.push(
function () {
return new Promise(function (reslove) {
console.log("work...")
reslove()
})
}
)
this.invoke()
return this;
}
sleep(delay) {
console.log("push sleep")
this.stack.push(
function () {
return new Promise(function (reslove) {
setTimeout(function () {
console.log("sleep " + delay + " 秒")
reslove()
}, delay * 1000)
})
}
)
this.invoke()
return this;
}
}
const c = new Chain("zhangsan");
const d = c.eat().sleep(1).work().sleep(2).eat();
d.sleep(2).work()