原型
- 原型的實際應用
- 原型如何實現它的擴充套件性
原型的實際應用
jquery和zepto的簡單使用
<p>jquery test1</p>
<p>jquery test2</p>
<p>jquery test3</p>
<div id="div1">
<p>jquery test in div</p>
</div>
複製程式碼
var $p = $("p")
$p.css('font-size','40px') //css是原型方法
console.log($p.html()) //html是原型方法
var $div1 = $("#div1")
$div1.css('color','blue') //css是原型方法
console.log($div1.html()) //html是原型方法
複製程式碼
zepto如何使用原型
//空物件
var zepto = {}
zepto.init = function(){
//原始碼中,這裡的處理情況比較複雜。但因為是本次只是針對原型,因此這裡就弱化了
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))
return zepto.Z(dom,selector)
}
//即使用zepto時候的$
var $ = function(selector){
return zepto.init(selector)
}
複製程式碼
//這就是建構函式
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}
複製程式碼
$.fn = {
constructor:zepto.Z,
css:function(key,value){},
html:function(value){}
}
zepto.Z.prototype = Z.prototype = $.fn
複製程式碼
簡單的zepto實現
myZepto.js實現
(function(window) {
var zepto = {};
function Z(dom, selector) {
var i, len = dom ? dom.length : 0;
for (i = 0; i < len; i++) {
this[i] = dom[i];
}
this.length = len;
this.selector = selector || '';
}
zepto.Z = function(dom, selector) {
return new Z(dom, selector);
}
zepto.init = function(selector) {
var slice = Array.prototype.slice;
var dom = slice.call(document.querySelectorAll(selector));
return zepto.Z(dom, selector);
}
var $ = function(selector) {
return zepto.init(selector);
}
window.$ = $;
$.fn = {
css: function(key, value) {
console.log(key, value);
},
html: function() {
return "html";
}
}
Z.prototype = $.fn
})(window)
複製程式碼
jquery如何使用原型
var jQuery = function(selector){
//注意new關鍵字,第一步就找到了建構函式
return new jQuery.fn.init(selector);
}
//定義建構函式
var init = jQuery.fn.init = function(selector){
var slice = Array.prototype.slice;
var dom = slice.call(document.querySelectorAll(selector));
var i,len=dom?dom.length:0;
for(i = 0;i<len;i++) this[i] = dom[i];
this.length = len;
this.selector = selector || '';
}
//初始化jQuery.fn
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
css: function(key, value) {},
html: function(value) {}
}
//定義原型
init.prototype = jQuery.fn
複製程式碼
原型的擴充套件性
如何體現原型的擴充套件性
- 總結
zepto
和jquery
原型的使用 - 外掛機制
為什麼要把原型放在
$.fn
上
init.prototype = jQuery.fn;
zepto.Z.prototype = Z.prototype = $.fn
因為要擴充套件外掛
//做一個簡單的外掛
$.fn.getNodeName = function(){
return this[0].nodeName
}
複製程式碼
好處
- 只有
$
會暴露在window
全域性變數上 - 將外掛擴充套件統一到
$.fn.xxx
這一介面,方便使用
非同步
什麼是單執行緒,和非同步有什麼關係
- 單執行緒:只有一個執行緒,同一時間只能做一件事,兩段JS不能同時執行
- 原因:避免DOM渲染的衝突
- 解決方案:非同步
為什麼js只有一個執行緒:避免DOM渲染衝突
- 瀏覽器需要渲染DOM
- JS可以修改DOM結構
- JS執行的時候,瀏覽器DOM渲染會暫停
- 兩端JS也不能同時執行(都修改DOM就衝突了)
- webworker支援多執行緒,但是不能訪問DOM
什麼是event-loop
- 事件輪詢,JS實現非同步的具體解決方案
- 同步程式碼,直接執行(主執行緒)
- 非同步函式先放在非同步佇列中(任務佇列)
- 待同步函式執行完畢,輪詢執行非同步佇列的函式
setTimeout(function(){
console.log(1);
},100); //100ms之後才放入非同步佇列中,目前非同步佇列是空的
setTimeout(function(){
console.log(2); //直接放入非同步佇列
})
console.log(3) //直接執行
//執行3之後,非同步佇列中只有2,把2拿到主執行緒執行
//2執行完之後,非同步佇列中並沒有任務,所以一直輪詢非同步佇列
//直到100ms之後1放入非同步佇列,將1拿到主執行緒中執行
複製程式碼
$.ajax({
url:'./data.json',
success:function(){ //網路請求成功就把success放入非同步佇列
console.log('a');
}
})
setTimeout(function(){
console.log('b')
},100)
setTimeout(function(){
console.log('c');
})
console.log('d')
//列印結果:
//d //d
//c //c
//a //b
//b //a
//真實環境不會出現dacb
複製程式碼
解決方案存在的問題
- 問題一:沒按照書寫方式執行,可讀性差
- 問題二:callback中不容易模組化
是否用過jQuery的Deferred
- jQuery1.5的變化
- 使用jQuery Deferred
- 初步引入Promise概念
jQuery1.5之前
var ajax = $.ajax({
url:'./data.json',
success:function(){
console.log('success1');
console.log('success2');
console.log('success3');
},
error:function(){
console.log('error');
}
})
console.log(ajax); //返回一個XHR物件
複製程式碼
jQuery1.5之後
第一種寫法:
var ajax = $.ajax('./data.json');
ajax.done(function(){
console.log('success1')
})
.fai(function(){
console.log('fail')
})
.done(function(){
console.log('success2');
})
console.log(ajax); //deferred物件
複製程式碼
第二種寫法:
var ajax = $.ajax('./data.json');
ajax.then(function(){
console.log('success1')
},function(){
console.log('error1');
})
.then(function(){
console.log('success2');
},function(){
console.log('error');
})
複製程式碼
- 無法改變JS非同步和單執行緒的本質
- 只能從寫法上杜絕callback這種形式
- 它是一種語法糖,但是解耦了程式碼
- 很好的提現:開放封閉原則(對擴充套件開放對修改封閉)
使用jQuery Deferred
給出一段非常簡單的程式碼,使用setTimeout函式
var wait = function(){
var task = function(){
console.log('執行完成');
}
setTimeout(task,2000)
}
wait();
複製程式碼
新增需求:要在執行完成之後進行某些特別複雜的操作,程式碼可能會很多,而且分好幾步
function waitHandle(){
var dtd = $.Deferred(); //建立一個deferred物件
var wait = function(dtd){ // 要求傳入一個deferred物件
var task = function(){
console.log("執行完成");
dtd.resolve(); //表示非同步任務已完成
//dtd.reject() // 表示非同步任務失敗或者出錯
};
setTimeout(task,2000);
return dtd;
}
//注意,這裡已經要有返回值
return wait(dtd);
}
//使用
var w = waithandle()
w.then(function(){
console.log('ok1');
},function(){
console.log('err2');
})
.then(function(){
console.log('ok2');
},function(){
console.log('err2');
})
//還有w.wait w.fail
複製程式碼
總結:
dtd
的API
可分成兩類,用意不同
- 第一類:
dtd.resolve
、dtd.reject
- 第二類:
dtd.then
、dtd.done
、dtd.fail
- 這兩類應該分開,否則後果嚴重!
可以在上面程式碼中最後執行
dtd.reject()
試一下後果
使用
dtd.promise()
function waitHandle(){
var dtd = $.Deferred();
var wait = function(){
var task = function(){
console.log('執行完成');
dtd.resolve();
}
setTimeout(task,2000)
return dtd.promise(); //注意這裡返回的是promise,而不是直接返回deferred物件
}
return wait(dtd)
}
var w = waitHandle(); //promise物件
$.when(w).then(function(){
console.log('ok1');
},function(){
console.log('err1');
})
//w.reject() //w.reject is not a function
複製程式碼
監聽式呼叫:只能被動監聽,不能干預promise的成功和失敗
- 可以jQuery1.5對ajax的改變舉例
- 說明如何簡單的封裝、使用deferred
- 說明promise和Defrred的區別
要想深入瞭解它,就需要知道它的前世今生
Promise的基本使用和原理
基本語法
fucntion loadImg() {
var promise = new Promise(function(resolve,reject) {
var img = document.getElementById("img")
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject()
}
})
return promise
}
var src = ""
var result = loadImg(src)
result.then(function() {
console.log(1, img.width)
return img
}, fucntion() {
console.log('error 1')
}).then(function(img) {
console.log(1, img.width)
})
複製程式碼
異常捕獲
規定
then
只接受一個函式(只接受一個成功的回撥函式),最後統一用catch
捕獲異常
兩種異常情況:
- 程式邏輯異常
- 業務之內,
reject()
等情況
處理方案 在
reject("圖片路徑錯誤")
中把資訊傳出去,可以通過'.catch()'捕獲
var src = ""
var result = loadImg(src)
result.then(function() {
console.log(1, img.width)
return img
}).then(function(img) {
console.log(1, img.width)
}).catch(function(ex) {
//最後統一捕獲異常
console.log(ex)
})
複製程式碼
多個串聯
var scr1 = 'https://www.imooc.com/static/img/index/logo_new.png';
var result1 = loadImg(src1);
var src2 = 'https://www.imooc.com/static/img/index/logo_new1.png';
var result2 = loadImg(src2);
result1.then(function(img1) {
console.log('第一個圖片載入完成', img1.width);
return result2; //重要
}).then(function(img2) {
console.log('第二個圖片載入完成', img2.width);
}).catch(function(ex) {
console.log(ex);
})
複製程式碼
Promise.all
和Promise.race
Promise.all
接收一個promise
物件的陣列- 待全部完成後,統一執行
success
Promise.all([result1, result2]).then(datas => {
//接收到的datas是一個陣列,依次包含了多個promise返回的內容
console.log(datas[0]);
console.log(datas[1]);
})
複製程式碼
Promise.race
接收一個包含多個promise
物件的陣列
- 只要有一個完成,就執行
success
Promise.race([result1, result2]).then(data => {
//data即最先執行完成的promise的返回值
console.log(data);
})
複製程式碼
Promise標準
- 狀態
- 三種狀態:
pending
,fulfilled
,rejected
- 初始狀態:
pending
pending
變為fulfilled
,或者pending
變為rejected
- 狀態變化不可逆
- 初始狀態:
- then
promise
必須實現then
這個方法then()
必須接收兩個函式作為標準then
返回的必須是一個promise
例項
沒有return
就是預設返回的result
Promise總結
- 基本語法
- 如何異常捕獲(
Error
和reject
) - 多個串聯-鏈式執行的好處
Promise.all
和Promise.race
Promise
標準 - 狀態變化、then
函式
介紹一下async/await(和Promise的區別、聯絡)
es7
提案中,用的比較多,babel
也已支援
koa
框架 async/await
替換generator
解決的是非同步執行和編寫邏輯
then
只是將callback
拆分了
var w = watiHandle()
w.then(function(){...},function(){...})
.then(function(){...},function(){...})
複製程式碼
async/await
是最直接的同步寫法
const load = async function(){
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()
複製程式碼
用法
- 使用
await
,函式後面必須用async
標識await
後面跟的是一個Promise
例項- 需要
balbel-polyfill
(相容)
特點
- 使用了
Promise
,並沒有和Promise
衝突 - 完全是同步的寫法,再也沒有回撥函式
- 但是:改變不了
JS
單執行緒、非同步的本質
總結一下當前JS非同步的方案
- jQuery Defered
- promise
- async/await
- generator(能解決非同步並不是為了解決非同步)
虛擬DOM
vdom 是什麼?為何會存在 vdom?
什麼是vdom
- virtual dom,虛擬DOM
- 用JS模擬DOM結構
- DOM變化的對比,放在JS層來做(圖靈完備語言:能實現各種邏輯的語言)
- 提高重繪效能
DOM
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
</ul>
複製程式碼
虛擬DOM
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [{
tag: 'li',
attrs: { className: 'item' },
children: ['item1']
},
{
tag: 'li',
attrs: { className: 'item' },
children: ['item2']
}
]
}
//className代替class,因為class是js的保留字
複製程式碼
瀏覽器最耗費效能就是DOM操作,DOM操作非常昂貴
現在瀏覽器執行JS速度非常快
這就是vdom存在的原因
一個需求場景
//將該資料展示成一個表格
//隨便修改一個資訊,表格也隨著修改
[
{
name: 'zhangsan',
age: 20,
address: 'beijing'
},
{
name: 'lisi',
age: 21,
address: 'shanghai'
},
{
name: 'wangwu',
age: 22,
address: 'guangzhou'
}
]
複製程式碼
用
jQery
實現
//渲染函式
funciton render(data) {
//此處省略n行
}
//修改資訊
$('#btn-change').click(function(){
data[1].age = 30;
data[2].address = 'shenzhen';
render(data);
})
//初始化時渲染
render(data)
複製程式碼
//render函式具體寫法
function render(data) {
$container = $("#container");
//清空現有內容
$container.html('');
//拼接table
var $table = $('<table>')
$table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'))
data.forEach(item => {
$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td></tr>'))
$container.append($table)
});
}
//只執行了一次渲染,相對來說還是比較高效的
//DOM渲染是最昂貴的,只能儘量避免渲染
複製程式碼
遇到的問題
- DOM操作是“昂貴”的,JS執行效率高
- 儘量減少DOM操作,而不是"推倒重來"(清空重置)
- 專案越複雜,影響就越嚴重
- vdom可解決這個問題
var div = document.createElement('div');
var item,result = '';
for(item in div){
result += '|' + item;
}
console.log(result);
//瀏覽器預設建立出來的DOM節點,屬性是非常多的
//result
|align|title|lang|translate|dir|dataset|hidden|tabIndex|accessKey|draggable|spellcheck|autocapitalize|contentEditable|isContentEditable|inputMode|offsetParent|offsetTop|offsetLeft|offsetWidth|offsetHeight|style|innerText|outerText|onabort|onblur|oncancel|oncanplay|oncanplaythrough|onchange|onclick|onclose|oncontextmenu|oncuechange|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onemptied|onended|onerror|onfocus|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadstart|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onpause|onplay|onplaying|onprogress|onratechange|onreset|onresize|onscroll|onseeked|onseeking|onselect|onstalled|onsubmit|onsuspend|ontimeupdate|ontoggle|onvolumechange|onwaiting|onwheel|onauxclick|ongotpointercapture|onlostpointercapture|onpointerdown|onpointermove|onpointerup|onpointercancel|onpointerover|onpointerout|onpointerenter|onpointerleave|nonce|click|focus|blur|namespaceURI|prefix|localName|tagName|id|className|classList|slot|attributes|shadowRoot|assignedSlot|innerHTML|outerHTML|scrollTop|scrollLeft|scrollWidth|scrollHeight|clientTop|clientLeft|clientWidth|clientHeight|attributeStyleMap|onbeforecopy|onbeforecut|onbeforepaste|oncopy|oncut|onpaste|onsearch|onselectstart|previousElementSibling|nextElementSibling|children|firstElementChild|lastElementChild|childElementCount|onwebkitfullscreenchange|onwebkitfullscreenerror|setPointerCapture|releasePointerCapture|hasPointerCapture|hasAttributes|getAttributeNames|getAttribute|getAttributeNS|setAttribute|setAttributeNS|removeAttribute|removeAttributeNS|hasAttribute|hasAttributeNS|toggleAttribute|getAttributeNode|getAttributeNodeNS|setAttributeNode|setAttributeNodeNS|removeAttributeNode|closest|matches|webkitMatchesSelector|attachShadow|getElementsByTagName|getElementsByTagNameNS|getElementsByClassName|insertAdjacentElement|insertAdjacentText|insertAdjacentHTML|requestPointerLock|getClientRects|getBoundingClientRect|scrollIntoView|scrollIntoViewIfNeeded|animate|computedStyleMap|before|after|replaceWith|remove|prepend|append|querySelector|querySelectorAll|webkitRequestFullScreen|webkitRequestFullscreen|scroll|scrollTo|scrollBy|createShadowRoot|getDestinationInsertionPoints|ELEMENT_NODE|ATTRIBUTE_NODE|TEXT_NODE|CDATA_SECTION_NODE|ENTITY_REFERENCE_NODE|ENTITY_NODE|PROCESSING_INSTRUCTION_NODE|COMMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE|DOCUMENT_FRAGMENT_NODE|NOTATION_NODE|DOCUMENT_POSITION_DISCONNECTED|DOCUMENT_POSITION_PRECEDING|DOCUMENT_POSITION_FOLLOWING|DOCUMENT_POSITION_CONTAINS|DOCUMENT_POSITION_CONTAINED_BY|DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|nodeType|nodeName|baseURI|isConnected|ownerDocument|parentNode|parentElement|childNodes|firstChild|lastChild|previousSibling|nextSibling|nodeValue|textContent|hasChildNodes|getRootNode|normalize|cloneNode|isEqualNode|isSameNode|compareDocumentPosition|contains|lookupPrefix|lookupNamespaceURI|isDefaultNamespace|insertBefore|appendChild|replaceChild|removeChild|addEventListener|removeEventListener|dispatchEvent
複製程式碼
vdom
如何應用,核心API
是什麼
介紹
snabbdom
一個實現vdom
的庫,vue升級2.0借鑑了snabbdom
的演算法
var container = document.getElementById('container')
var vnode = h('div#container.two.classes', { on: { click: someFn } }, [
h('span', { style: { fontWeight: 'bold' }, 'This is bold' }),
'and this is just normal text',
h('a', { props: { href: '/foo' } }, 'I\'ll take you places')
])
//patch into empty DOM element - this modifies the DOM as a side effect
patch(container, vnode)
var newVnode = h('div#container.two.classes', { on: { click: anotherEventHandle } }, [
h('span', { style: { fontWeight: 'normal', fontStyle: 'italic' } }, 'this is now italic type'),
'and this is still just normal text',
h('a', { props: { href: '/bar' } }, 'I\'ll take you places')
])
//send `patch` invocation
patch(vnode, newVnode); //Snabbdom efficiently updates the old view to the new state
複製程式碼
h函式
{
tar: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {
className: 'item',
children: ['item1']
}
},
{
tag: 'li',
attrs: {
className: 'item'
},
children: ['item2']
}
]
}
複製程式碼
對應的
vnode
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'Item')
])
複製程式碼
patch
函式
var vnode = h('ul#list', {}, [
h('li.item', {}, 'Item1'),
h('li.item', {}, 'Item2')
])
var container = document.getElementById('container')
patch(container, vnode)
//模擬改變
var btnChange = document.getElementById('btn-change')
btnChange.addEventListener('click', function() {
var newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 111'),
h('li.item', {}, 'Item 222'),
h('li.item', {}, 'Item 333')
])
patch(vnode, newVnode)
})
複製程式碼
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/h.js"></script>
複製程式碼
<script type="text/javascript">
var snabbdom = window.snabbdom
//定義patch
var patch = 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.list',{},'Item1'),
h('li.list',{},'Item2'),
])
patch(container,vnode)
document.getElementById('btn-change').addEventListener('click',function(){
//生成newVode
var newVnode = h('ul#list',{},[
h('li.list',{},'Item1'),
h('li.item',{},'Item B'),
h('li.item',{},'Item 3')
])
patch(vnode,newVnode)
})
</script>
複製程式碼
重新實現前面的demo(用
snabbdom
實現)
var snabbdom = window.snabbdom
var patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
var h = snabbdom.h
var container = document.getElementById('container')
var btnChange = document.getElementById('btn-change')
var vnode
var data = [{
name: 'zhangsan',
age: 20,
address: 'beijing'
},
{
name: 'zhangsan',
age: 20,
address: 'shanghai'
},
{
name: 'zhangsan',
age: 20,
address: 'shenzhen'
}
]
data.unshift({
name:'姓名:',
age:'年齡:',
address:'地址:'
})
render(data);
function render(data){
var newVnode = h('table',{},data.map(function(item){
var tds = [],i
for(i in item){
if(item.hasOwnProperty(i)){
tds.push(h('td',{},item[i]+''))
}
}
return h('tr',{},tds)
}))
if(vnode){
patch(vnode,newVnode)
}else{
patch(container,newVnode)
}
vnode = newVnode //儲存當前vnode結果
}
btnChange.addEventListener('click',function(){
data[1].age = 30
data[2].address = '深圳'
//re-render
render(data)
})
複製程式碼
核心API
- h函式的用法
h('<標籤名>',{...屬性...},[...子元素...])
h('<標籤名>',{...屬性...},'...')
複製程式碼
- patch函式用法
patch(container,vnode)
patch(vnode,newVnode) //rerender
複製程式碼
介紹一下diff演算法
什麼是diff演算法
linux
中的diff:找出兩個檔案中的不同:
diff log1.txt log2.txt
複製程式碼
git diff
:修改之前和修改之後版本的差異
git diff xxxx.js
複製程式碼
網上的一些線上差異化網站
http://tool.oschina.net/diff/
複製程式碼
diff
演算法並不是vdom
提出的概念,一直存在
現在應用到vdom
中,對比的是兩個虛擬dom
去繁就簡
diff
演算法非常複雜,實現難度很大,原始碼量很大- 去繁就簡,講明白核心流程,不關心細節
- 面試官大部分也不清楚細節,但是很關心核心流程
- 去繁就簡之後,依然具有很大的挑戰性
vdom
為何要使用diff
DOM
操作是“昂貴”的,因此儘量減少DOM
操作- 找出本次
DOM
必須更新的節點來更新,其他的不更新 - “找出”的過程,就需要
diff
演算法
diff
演算法實現diff
實現的過程
patch(container,vnode)
如何用vnode生成真實的dom節點
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {
className: 'item'
},
children:['item 1']
}
]
}
複製程式碼
<ul id = "list">
<li class='item'>Item 1</li>
</ul>
複製程式碼
簡單實現演算法
function createElement(vnode) {
var tag = vnode.tag;
var attrs = vnode.attrs || {};
var children = vnode.children || [];
if (!tag) {
return null
}
//建立元素
var elem = document.createElement(tag);
//屬性
var attrName;
for (attrName in atts) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
//子元素
children.array.forEach(childVnode => {
elem.appendChild(createElement(childVnode))
});
return elem;
}
複製程式碼
patch(vnode,newVnode)
{
tag: 'ul',
attrs: { id: 'list' },
children: [{
tag: 'li',
attrs: { className: 'item' },
children: ["Item 1"]
},
{
tag: 'li',
attrs: {
className: 'item',
children: ['Item 2']
}
}
]
}
複製程式碼
對比:
{
tag: 'ul',
attrs: { id: 'list' },
children: [{
tag: 'li',
attrs: { className: 'item' },
children: ["Item 1"]
},
{
tag: 'li',
attrs: {
className: 'item',
children: ['Item 222']
}
},
{
tag: 'li',
attrs: {
className: 'item',
children: ['Item 3']
}
}
]
}
複製程式碼
簡單實現
function updateChildren(vnode, newVnode) {
var children = vnode.children || [];
var newChildren = newVnode.children || [];
//遍歷現有的children
children.forEach((child, index) => {
var newChild = newChildren[index];
if (newChild == null) {
return;
}
if (child.tag === newChild.tag) {
updateChildren(child, newChild)
} else {
replaceNode(child, newChild)
}
});
}
複製程式碼
- 節點新增和刪除
- 節點重新排序
- 節點屬性、樣式、事件繫結
- 如何積極壓榨效能
MVVM
和Vue
使用jQuery
和使用框架的區別
資料與檢視的分離,解耦(封閉開放原則)
jquery
中資料與檢視混合在一起了,不符合開放封閉原則vue
:通過Vue物件將資料和View
完全分離開來了
以資料驅動檢視
jquery
完全違背了這種理念,jquery
直接修改檢視,直接操作DOM
vue
對資料進行操作不再需要引用相應的DOM
物件,通過Vue
物件這個vm
實現相互的繫結。以資料驅動檢視,只關心資料變化,DOM
操作被封裝
對MVVM
的理解
MVC
MVVM
Model
:資料,模型View
:檢視、模板(檢視和模型是分離的)ViewModel
:連線Model
和View
MVVM
不算是創新,ViewModel
算是一種微創新
是從MVC
發展而來,結合前端場景的創新
如何實現MVVM
Vue
三要素
- 響應式:
vue
如何監聽到data
的每個屬性變化 - 模板引擎:vue的模板如何被解析,指令如何處理
- 渲染:
vue
的模板如何渲染成html
,以及渲染過程
vue
中如何實現響應式
什麼是響應式
- 修改
data
屬性之後,vue
立刻監聽到(然後立刻渲染頁面) data
屬性被代理到vm
上
Object.defineProperty
ES5
中加入的API
,所以Vue
不支援低版本瀏覽器(因為低版本瀏覽器不支援這個屬性)
var obj = {};
var _name = 'zhangsan';
Object.defineProperty(obj,"_name",{
get:function(){
console.log("get");
return _name;
},
set:function(newVal){
console.log(newVal);
_name = newVal;
}
});
console.log(obj.name) //可以監聽到
obj.name = 'list'
複製程式碼
模擬實現
var vm = new Vue({
el: '#app',
data: {
price: 100,
name: 'zhangsan'
}
})
複製程式碼
var vm = {}
var data = {
name: 'zhangsan',
price: 100
}
var key, value
for (key in data) {
//命中閉包。新建一個函式,保證key的獨立的作用域
(function(key) {
Object.defineProperty(vm, key, {
get: function() {
console.log('get')
return data[key]
},
set: function(newVal) {
console.log('set')
data[key] = newVal
}
})
})(key)
}
複製程式碼
vue
如何解析模板
模板是什麼
- 本質:字串;有邏輯,如
v-if
,if-else-if
,嵌入JS變數
... - 與
html
格式很像,但有很大區別(靜態),最終還是要轉化為html
顯示 - 模板最終必須轉換成
JS
程式碼- 1、因為有邏輯(
v-for
,v-if
),必須用JS
才能實現(圖靈完備) - 2、轉換為
html
渲染頁面,必須用JS
才能實現 - 因此,模板最重要轉換成一個
JS
函式(render
函式)
- 1、因為有邏輯(
render
函式
先了解
with()
的使用
function fn1() {
with(obj) {
console.log(name);
console.log(age);
getAddress()
}
}
複製程式碼
最簡單的一個示例
<div id="app">
<p>{{price}}</p>
</div>
複製程式碼
with(this) { //this:vm
return _c(
'div',
{
attrs: { "id": "app" }
},
[
_c('p',[_v(_s(price))] ) //price代理到了vm上
]
)
}
複製程式碼
//vm._c
ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); }
//vm._v
ƒ createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
//vm._s
ƒ toString (val) {
return val == null? '': typeof val === 'object'? JSON.stringify(val, null,2): String(val)
}
複製程式碼
- 模板中所有資訊都包含在了
render
函式中 this
即vm
price
即this.price
,即data
中的price
_c
即this._c
即vm._c
更復雜的一個例子
<div id="app">
<div>
<input v-model="title">
<button v-on:click="add">submit</button>
</div>
<div>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</div>
複製程式碼
如何尋找
render
函式:code.render
模板如何生成
render
函式:
vue2.0
開始就支援預編譯,我們在開發環境下寫模板
,經過編譯打包
,產生生產環境的render函式(JS
程式碼)
with(this){ // this 就是 vm
return _c(
'div',
{
attrs:{"id":"app"}
},
[
_c(
'div',
[
_c(
'input',
{
directives:[
{
name:"model",
rawName:"v-model",
value:(title),
expression:"title"
}
],
domProps:{
"value":(title)
},
on:{
"input":function($event){
if($event.target.composing)return;
title=$event.target.value
}
}
}
),
_v(" "),
_c(
'button',
{
on:{
"click":add
}
},
[_v("submit")]
)
]
),
_v(" "),
_c('div',
[
_c(
'ul',
_l((list),function(item){return _c('li',[_v(_s(item))])})
)
]
)
]
)
}
複製程式碼
//vm._l
function renderList(val,render) {
var ret, i, l, keys, key;
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
} else if (typeof val === 'number') {
ret = new Array(val);
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
} else if (isObject(val)) {
keys = Object.keys(val);
ret = new Array(keys.length);
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i];
ret[i] = render(val[key], key, i);
}
}
if (isDef(ret)) {
(ret)._isVList = true;
}
return ret
}
複製程式碼
v-model
是怎麼實現的?v-on:click
是怎麼實現的v-for
是怎麼實現的
render
函式與DOM
- 已經解決了模板中"邏輯"(
v-for
,v-if
)的問題 - 還剩下模板生成html的問題
- 另外,vm_c是什麼?
render
函式返回了什麼- vm._c其實就相當於
snabbdom
中的h
函式 render
函式執行之後,返回的是vnode
- vm._c其實就相當於
vm._update(vnode) {
const prevVnode = vm._vnode
vm._vnode = vnode
if (!prevVnode) {
vm.$sel = vm.__patch__(vm.$sel, vnode) //與snabbdom中的patch函式差不多
} else {
vm.$sel = vm.__patch__(prevVnode, vnode)
}
}
funciton updateComponent() {
//vm._render即上面的render函式,返回vnode
vm._update(vm._render())
}
複製程式碼
updateComponent
中實現了vdom
的patch
- 頁面首次渲染執行
updateComponent
data
中每次修改屬性,執行updataCommponent
vue
的實現流程
第一步:解析模板成
render
函式
with
的用法- 模板中所有的資訊都被
render
函式包含 - 模板中用到的
data
中的屬性,都變成了js變數 - 模板中的
v-model
、v-if
、v-on
都變成了js邏輯 render
函式返回vnode
第二步:響應式監聽
Object.defineProperty
- 將
data
屬性代理到vm
上
第三步:首次渲染,顯示頁面,且繫結依賴
- 初次渲染,執行
updateaComponent
,執行vm._render()
- 執行
render
函式,會訪問到vm.list
和vm.title
- 會被響應式的
get
方法監聽到(為什麼監聽get?直接監聽set不就行了嗎?)data
中有很多屬性,有些會被用到,有些可能不會被用到- 被用到的會走到
get
,不被用到的不會走get
- 未走到
get
中的屬性,set
的時候我們也無需關係 - 避免不必要的重複渲染
- 執行
updateComponent
,會走到vdom
的patch
方法 patch
將vnode
渲染成DOM
,初次渲染完成
第四步:
data
屬性變化,觸發rerender
- 屬性修改,被響應式的
set
監聽到 set
中執行updateaComponetn
updateComponent
重新執行vm._render()
- 生成的
vnode
和prevVnode
,通過patch
進行對比 - 渲染到
html
中
元件化和React
- JSX和vdom的關係
- 說一下setState的過程
- 闡述自己對React和Vue的認識
說一下對元件化的理解
todolist Demo1
命令列
npm i create-react-app -g
create-react-app react-test
npm start
- app.js
import Todo from './components/todo/index.js';
class App extends Component {
render() {
return (
<div className="App">
<Todo></Todo>
</div>
);
}
}
export default App;
複製程式碼
- src
=>
components=>
todo
import React, { Component } from 'react'
class Todo extends Component {
constructor(props) {
super(props)
}
render() {
return (
<p> this is todo component </p>
)
}
}
export default Todo
複製程式碼
todolist Demo2
list
=>
todo=>
index.js
import React, { Component } from 'react'
class List extends Component{
constructor(props){
super(props)
}
render() {
const list = this.props.data
return (
<ul>
{
list.map((item,index) =>{
return <li key={index}>{item}</li>
})
}
</ul>
)
}
}
export default List
複製程式碼
必須加key
todo.js
import React, { Component } from 'react'
import List from './list/index.js';
import Input from './input/index.js';
class Todo extends Component {
constructor(props) {
super(props)
this.state = {
list:[]
}
}
render() {
return (
<div>
<Input addTitle={this.addTitle.bind(this)}/>
<List data={this.state.list} />
</div>
)
}
//主動新增list
addTitle(title){
const currentList = this.state.list //獲取當前list
this.setState(
{
list:currentList.concat(title)
}
)
}
}
export default Todo
複製程式碼
list
=>
input=>
index.js
import React, { Component } from "react";
class Input extends Component {
constructor(props) {
super(props);
this.state = {
title: ""
};
}
render() {
return(
<div>
<input value={this.state.title} onChange={this.changeHandle.bind(this)} />
<button onClick={this.clickHandle.bind(this)}>submit</button>
</div>
)
}
changeHandle(event) {
this.setState({
title: event.target.value
});
}
clickHandle() {
const title = this.state.title;
//把title新增進列表
const addTitle = this.props.addTitle;
addTitle(title);
//清空
this.setState({
title: ""
});
}
}
export default Input
複製程式碼
說一下對元件化的理解
元件的封裝
- 檢視
- 資料
- 變化邏輯(資料驅動檢視)
元件的複用
- props傳遞
- 複用
JSX
本質是什麼
JSX
語法
html
形式- 引入JS變數和表示式
if...else...
- 迴圈
style
和className- 事件
JSX
解析成JS
- JSX其實是語法糖
- 開發環境將JSX編譯成JS程式碼
- JSX寫法大大降低了學習成本和編碼工作量
- 同時,
JSX
也會增加debug
成本
/*JSX程式碼*/
var profile = <div>
<img src="avater.png" className="profile"/>
<h3>{[user.firstName,user.lastName].join('')}</h3>
</div>
----------------------------------------------------------------------------
//最終解析成
var profile = React.createElement("div",null,
React.createElement("img",{src:"avater.png",className:"profile"}),
React.createElement("h3",null,[user.firstName,user.lastName].join("")))
複製程式碼
React.createElement
引數說明
React.createElement('div',{'id':'div1'},child1,child2,child3)
React.createElement('div',{'id':'div1'},[...])
複製程式碼
vdom
和h()
vue
的_c()
react
的createElement
render(){
const list = this.props.data
return{
<ul>
{
list.map((list,index)=>{
return <li key={index}>{item}</li>>
})
}
</ul>
}
}
----------------------------------------------------------------------------
function render(){
const list = this.props.data;
return React.createElement(
'ul',
'null',
list.map((item,index) => {
return React.createElement(
"li",
{key:index},
item
);
})
);
}
複製程式碼
JSX
獨立的標準
JSX
是React
引入的,但不是React
獨有的React
已經將它作為一個獨立標準開放,其他專案也可用React.createElement
是可以自定義修改的- 說明:本身功能已經完備;和其他標準相容和擴充套件性沒問題
Hybrid
- 移動端佔大部分流量,已經遠遠超過
pc
- 一線網際網路公司都有自己的
APP
- 這些
APP
中有很大比例的前端程式碼,拿微信舉例,你每天瀏覽微信的內容,很多都是前端
hybrid
是什麼,為何用hybrid,如何實現
hybrid
文字解釋
hybrid
即“混合”,即前端和客戶端的混合開發- 需前端開發人員和客戶端人員配合完成
- 某些環節也可能涉及到
server
端
存在價值,為何會用
hybrid
- 可以快速迭代更新【關鍵】(無須app稽核)
- 體驗流暢(和NA的體驗基本類似)
- 減少開發和溝通成本,雙端公用一套程式碼
webview
- 是app中的一個元件(app可以有
webview
,也可以沒有) - 用於載入h5頁面,即一個小型的瀏覽器核心
file://協議
- 其實在一開始接觸html開發,已經使用file協議了
- 載入本地檔案,快
- 網路載入,慢
- “協議”、“標準的重要性”
要做到和原生一樣的體驗,就必須要求載入速度特別的快,變態的快,和客戶端幾乎一樣的快
hybrid
適用場景
- 使用NA:體驗要求極致,變化不頻繁(如頭條的首頁)
- 使用hybrid:體驗要求高,變化頻繁(如頭條的新聞詳情頁)
- 使用h5:體驗無要求,不常用(如舉報,反饋等頁面)
hybrid
具體實現
- 前端做好靜態頁面(html js css),將檔案交給客戶端
- 客戶端拿到前端靜態頁面,以檔案的形式儲存在app中
- 客戶端在一個webview中
- 使用file協議載入靜態頁面
app釋出之後,如何實時更新
分析
- 要替換每個客戶端的靜態檔案
- 只能客戶端來做(客戶端是我們開發的)
- 客戶端去serve下載最新的靜態檔案
- 我們維護server的靜態檔案
具體實現
- 分版本有版本號,如201803211015
- 將靜態檔案壓縮成zip包,上傳到服務端
- 客戶端每次啟動,都去服務端檢查版本號
- 如果服務端版本號大於客戶端版本號,就去下載最新的zip包
- 下載完之後解壓包,然後將現有檔案覆蓋
hybrid
和h5
區別
優點:
- 體驗更好,跟NA體驗基本一致
- 可快速迭代,無須app稽核【關鍵】
缺點:
- 開發成本高。聯調、測試、查bug都比較麻煩
- 運維成本高。(參考此前講過的更新上線的流程)
適用場景:
hybrid
:產品的穩定功能,體驗要求高,迭代頻繁h5
:單次的運營活動(如xx紅包)或不常用功能hybrid
適合產品型,h5
適合運營型
JS和客戶端通訊
前端如何獲取內容
- 新聞詳情頁適用
hybrid
,前端如何獲取新聞內容 - 不能用ajax獲取。第一跨域(
ajax
是http
協議),第二速度慢。 - 客戶端獲取新聞內容,然後JS通訊拿到內容,再渲染。
JS和客戶端通訊的基本形式
schema協議簡介和使用
- 之前介紹了http(s)和file協議
- schema協議--前端和客戶端通訊的約定
function invokScan() {
window['_invok_scan_callback'] = function(result){
alert(result)
}
var iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = 'weixin://dl/scan?k1=v1&k1=v2&callback=_invok_scan_callback' //重要
var body = document.body
body.appendChild(iframe)
setTimeout(() => {
body.removeChild(iframe)
iframe = none
});
}
document.getElementById('btn').addEventListener('click', function() {
invokScan();
})
複製程式碼
schema使用的封裝
我們希望封裝後達到的效果:
/*傻瓜式呼叫,而且不用再自己定義全域性函式 */
window.invok.share({title:'xxxx',content:'xxx'},funciton(result){
if(result.error === 0){
alert('分享成功')
}else{
//分享失敗
alert(result.message)
}
})
複製程式碼
//分享
function invokeShare(data,callback){
_invoke('share',data,callback)
}
//登入
function invokeLogin(data,callback){
_invoke('login',data,callback)
}
//開啟掃一掃
function invokeScan(data,callback){
_invoke('scan',data,callback)
}
複製程式碼
//暴露給全域性
window.invoke = {
share:invokeShare,
login:invokeLogin,
scan:invokeScan
}
複製程式碼
function _invoke(action,data,callback){
//拼接schema協議
var schema = 'myapp://utils';
scheam += '/' + action;
scheam += '?a=a';
var key;
for(key in data){
if(data.hasOwnProperty(key)){
schema += '&' + key + '=' +data[key]
}
}
}
複製程式碼
//處理callback
var callbackName = ''
if(typeof callback == 'string'){
callbackName = callback
}else{
callbackName = action + Date.now()
window[callbackName] = callback
}
schema += '&callback' + callbackName
複製程式碼
(function(window, undefined) {
//呼叫schema封裝
function _invoke(action, data, callback) {
//拼裝schema協議
var schema = 'myapp://utils/' + action;
//拼接引數
schema += '?a=a';
var key;
for (key in data) {
if (data.hasOwnProperty(key)) {
schema += '&' + key + '=' + data[key];
}
}
//處理callback
var callbackName = '';
if (typeof callback === 'string') {
callbackName = callback
} else {
callbackName = action + Date.now()
window[callbackName] = callback
}
schema += 'allback = callbackName'
}
//暴露到全域性變數
window.invoke = {
share: function(data, callback) {
_invoke('share', data, callback)
},
scan: function(data, callback) {
_invoke('scan', data, callback)
},
login: function(data, callback) {
_invoke('login', data, callback)
}
}
})(window)
複製程式碼
內建上線
- 將以上封裝的程式碼打包,叫做
invoke.js
,內建到客戶端 - 客戶端每次啟動
webview
,都預設執行invoke.js
- 本地載入,沒有網路請求,黑客看到不到
schema
協議,更安全
總結
- 通訊的基本形式:呼叫能力,傳遞引數,監聽回撥
- 對schema協議的理解和使用
- 呼叫schema程式碼的封裝
- 內建上線:更快更安全