vue相關(guān)
1.VNode是什么策治?什么是虛擬dom赡模?
在vue.js中存在一個VNode類罢猪,使用它可以實例化不同類型的vnode實例,這些實例可以被渲染成真實的dom對象幸海。
虛擬dom是通過 js 對象來描述真實 DOM 結(jié)構(gòu)和屬性祟身。例如:
//下面是一個a標(biāo)簽
<a class="demo" style="color: red" href="#">
文本內(nèi)容
</a>
// 用VNode的實例對象來描述
{
tag: 'a',
data: {
calss: 'demo',
attrs: {
href: '#'
},
style: {
color: 'red'
}
},
children: ['文本內(nèi)容']
}
由于真實dom頻繁排版與重繪的效率低,對虛擬DOM進行頻繁修改物独,然后一次性對比并修改真實DOM中需要改的部分袜硫,最后在真實DOM中進行排版與重繪,減少過多DOM節(jié)點排版與重繪損耗挡篓。
舉個例子:下面有一段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結(jié)構(gòu)如下
{
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
}
]
}
然后通過對比發(fā)現(xiàn)改變的只有Item B;現(xiàn)在提供兩種改變的思路:
- 1.直接刪掉所有Item官研,再把Item1 和 Item B加進來秽澳。
- 2.先生成最終js的DOM結(jié)構(gòu),然后對比之前的js DOM結(jié)構(gòu)戏羽,找出差異部分再改變 担神。
只從思路上分析,第一種更加簡單快捷始花;然而實際是妄讯,瀏覽器最最最耗費性能的就是DOM操作,dom操作是最昂貴的酷宵,現(xiàn)在的瀏覽器執(zhí)行js是非澈ッ常快的,所以虛擬DOM有其存在的價值浇垦。
vdom如何應(yīng)用炕置,核心API是什么?(這個可以先不看)
要我們自己去實現(xiàn)一個虛擬dom,大概過程應(yīng)該有以下三步:
- compile:如何把真實DOM編譯成vnode朴摊。
- diff:我們要如何知道oldVnode和newVnode之間有什么變化默垄。
- patch:如果把這些變化用打補丁的方式更新到真實dom上去。
vue的虛擬dom實現(xiàn)參考了snabbdom.js的實現(xiàn)仍劈。
snabbdom.js核心API是兩個函數(shù)厕倍,一個h函數(shù)(用來創(chuàng)建vnode),一個patch函數(shù)(用來對比變化并替換)贩疙。
<!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算法大概結(jié)構(gòu)
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;}
//創(chuàng)建真實的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);
//替換
//...
}
//節(jié)點新增和刪除
//節(jié)點重新排序
//節(jié)點屬性讹弯、樣式、事件綁定
//如何極致壓榨性能
2.v-show 與 v-if 區(qū)別这溅?
- v-show是css切換组民,v-if是完整的銷毀和重新創(chuàng)建;
- 使用頻繁切換時用v-show,運行時較少改變時用v-if;
- v-if="false" v-if是條件渲染悲靴,當(dāng)false的時候不會渲染臭胜。
3.計算屬性和 watch 的區(qū)別?
計算屬性是自動監(jiān)聽依賴值的變化癞尚,從而動態(tài)返回內(nèi)容耸三,監(jiān)聽是一個過程,在監(jiān)聽的值變化時浇揩,可以觸發(fā)一個回調(diào)仪壮,并做一些事情。
所以區(qū)別來源于用法胳徽,只是需要動態(tài)值积锅,那就用計算屬性;需要知道值的改變后執(zhí)行業(yè)務(wù)邏輯养盗,才用 watch缚陷。
4.computed 和 methods 有什么區(qū)別?
methods是一個方法往核,它可以接受參數(shù)箫爷,而computed不能,computed是可以緩存的聂儒,methods不會蝶缀。
5.vue的生命周期
- beforeCreate
vue實例的掛載元素el和數(shù)據(jù)對象data都為undefined,還未初始化薄货。 - created
vue實例的數(shù)據(jù)對象有了,el還沒有碍论。 - beforeMount:
掛載開始之前被調(diào)用谅猾,相關(guān)的render函數(shù)首次被調(diào)用(虛擬DOM),實例已完成以下的配置: 編譯模板,把data里面的數(shù)據(jù)和模板生成html税娜,完成了el和data 初始化坐搔,注意此時還沒有掛在html到頁面上。 - mounted:
掛載完成敬矩,也就是模板中的HTML渲染到HTML頁面中概行,此時一般可以做一些ajax操作,mounted只會執(zhí)行一次弧岳。 - 更新前后 beforeUpdate/updated
當(dāng)data變化時凳忙,會觸發(fā)beforeUpdate和updated方法。 - beforeDestory
在實例銷毀之前調(diào)用禽炬,實例仍然完全可用涧卵,這一步還可以用this來獲取實例,一般在這一步做一些重置的操作腹尖,比如清除掉組件中的定時器 和 監(jiān)聽的dom事件柳恐。 - destoryed
在實例銷毀之后調(diào)用,調(diào)用后热幔,所以的事件監(jiān)聽器會被移出乐设,所有的子實例也會被銷毀,該鉤子在服務(wù)器端渲染期間不被調(diào)用绎巨。
路由的跳轉(zhuǎn)方式
- <router-link to='home'> router-link標(biāo)簽會渲染為<a>標(biāo)簽近尚。
- 編程式導(dǎo)航 也就是通過js跳轉(zhuǎn) 比如 router.push('/home')
6.nextTick()
在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后认烁,立即使用這個回調(diào)函數(shù)肿男,獲取更新后的 DOM。
// 修改數(shù)據(jù)
vm.msg = 'Hello'
// DOM 還未更新
Vue.nextTick(function () {
// DOM 更新
})
7.slot插槽
- 單個插槽
當(dāng)子組件模板只有一個沒有屬性的插槽時却嗡,父組件傳入的整個內(nèi)容片段將插入到插槽所在的 DOM 位置舶沛,并替換掉插槽標(biāo)簽本身。 - 命名插槽
solt元素可以用一個特殊的特性name來進一步配置如何分發(fā)內(nèi)容窗价。多個插槽可以有不同的名字如庭。這樣可以將父組件模板中 slot 位置,和子組件 slot 元素產(chǎn)生關(guān)聯(lián)撼港,便于插槽內(nèi)容對應(yīng)傳遞
js相關(guān)
1.繼承的方法有哪些坪它?并解釋各個繼承詳情。
/**1.構(gòu)造函數(shù)繼承**/
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.組合寄生繼承(最優(yōu) 記住這個就可以)**/
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.什么是閉包帝牡?
簡單講往毡,閉包就是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。
外部函數(shù)調(diào)用之后在其內(nèi)部聲明的變量本應(yīng)該被銷毀靶溜,但閉包的存在使我們?nèi)匀豢梢栽L問外部函數(shù)的變量對象开瞭,這就是閉包的重要概念懒震。
function outer(){
var a = 1;
return function(){
console.log(a++);
}
}
var b = outer();
b();//1
b();//由于a的值不會被釋放,再次調(diào)用結(jié)果為2
應(yīng)用場景嗤详,比如單例模式个扰,防抖函數(shù),節(jié)流函數(shù)等葱色。
/**單例模式 得到的始終是同一個對象*/
(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"}
/*防抖函數(shù)*/
<input type="text">
<script>
var input = document.querySelector("input");
function debounce(func, delay) {
let time;
return function () {
//保存this指向
const context = this;
//保存?zhèn)魅氲膮?shù)
const args = arguments;
//清除上次的延時器
clearTimeout(time);
//重新設(shè)置一個延時器递宅,延遲為delay毫秒
time = setTimeout(function () {
func.apply(context, args)
}, delay)
}
}
var inputEvent = debounce(function () {
console.log("向后端發(fā)送請求");
}, 1500);
input.addEventListener("input", inputEvent);
</script>
3.原型鏈
每個函數(shù)有個prototype屬性,指向其原型對象苍狰,原型對象有一個constructor屬性指向構(gòu)造函數(shù)办龄;通過函數(shù)實例化(new)出來的對象有一個proto屬性,也指向函數(shù)的原型對象舞痰;原型對象也有__proto__屬性土榴,指向原型對象的原型對象,通過一層層往上指响牛,直到Object.prototype玷禽。
function foo(){}
let f = new foo();
console.log(f.__proto__===foo.prototype);//true
console.log(foo.prototype.constructor===foo);//true
4.如何解決跨域問題 詳解轉(zhuǎn)載
- 1.通過jsonp跨域(只支持get方式)
通常為了減輕web服務(wù)器的負載,我們把js呀打、css矢赁,img等靜態(tài)資源分離到另一臺獨立域名的服務(wù)器上,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,而被瀏覽器允許,基于此原理赔退,我們可以通過script標(biāo)簽允跑,再請求一個帶參網(wǎng)址實現(xiàn)跨域通信辨绊。
html代碼
<body>
<script>
function test(data) {
alert(data.name);
}
</script>
<script src="http://127.0.0.1:3000/jsonp?callback=test"></script>
</body>
服務(wù)端代碼(用的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)
普通跨域請求:只服務(wù)端設(shè)置Access-Control-Allow-Origin即可,前端無須設(shè)置;
若要帶cookie請求:前后端都需要設(shè)置。
前端設(shè)置
1)原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端設(shè)置是否帶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 // 前端設(shè)置是否帶cookie
},
crossDomain: true, // 會讓請求頭中包含跨域的額外信息抄邀,但不會含cookie
...
});
3)vue框架
//axios設(shè)置:
axios.defaults.withCredentials = true;
// vue-resource設(shè)置:
Vue.http.options.credentials = true;
服務(wù)端設(shè)置
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 數(shù)據(jù)塊接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 數(shù)據(jù)接收完畢
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后臺設(shè)置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允許發(fā)送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允許訪問的域(協(xié)議+域名+端口)
/*
* 此處設(shè)置的cookie還是domain2的而非domain1,因為后端也不能跨域?qū)慶ookie(nginx反向代理可以實現(xiàn))昼榛,
* 但只要domain2中寫入一次cookie認證境肾,后面的跨域接口都能從domain2中獲取cookie,從而實現(xiàn)所有的接口都能跨域訪問
*/
'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協(xié)議跨域
WebSocket protocol是HTML5一種新的協(xié)議胆屿。它實現(xiàn)了瀏覽器與服務(wù)器全雙工通信奥喻,同時允許跨域通訊。
實現(xiàn)求和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()