Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
、MVVM簡介
如果你是第一次學前端,那么本節知識一定要了解,什么是MVVM。
MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將視圖 UI 和業務邏輯分開。當然這些事 ViewModel 已經幫我們做了,它可以取出 Model 的數據同時幫忙處理 View 中由于需要展示內容而涉及的業務邏輯。MVVM的核心是ViewModel層,負責轉換Model中的數據對象來讓數據變得更容易管理和使用。是一種簡化用戶界面的事件驅動編程方式。
下邊我們來畫張圖來大體了解下MVVM的工作原理圖:
該層向上與視圖層進行雙向數據綁定
向下與Model層通過接口請求進行數據交互
(1)View
View是視圖層, 也就是用戶界面。前端主要由HTH L和csS來構建, 為了更方便地展現vi eu to del或者Hodel層的數據, 已經產生了各種各樣的前后端模板語言, 比如FreeMarker,Thyme leaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS等也都有自己用來構建用戶界面的內置模板語言。
(2)Model
Model是指數據模型, 泛指后端進行的各種業務邏輯處理和數據操控, 主要圍繞數據庫系統展開。這里的難點主要在于需要和前端約定統一的接口規則
(3)ViewModel
ViewModel是由前端開發人員組織生成和維護的視圖數據層。在這一層, 前端開發者對從后端獲取的Model數據進行轉換處理, 做二次封裝, 以生成符合View層使用預期的視圖數據模型。
View Model所封裝出來的數據模型包括視圖的狀態和行為兩部分, 而Model層的數據模型是只包含狀態的
視圖狀態和行為都封裝在了View Model里。這樣的封裝使得View Model可以完整地去描述View層。由于實現了雙向綁定, View Model的內容會實時展現在View層, 這是激動人心的, 因為前端開發者再也不必低效又麻煩地通過操縱DOM去更新視圖。 MVVM框架已經把最臟最累的一塊做好了, 我們開發者只需要處理和維護View Model, 更新數據視圖就會自動得到相應更新,真正實現事件驅動編程。 View層展現的不是Model層的數據, 而是ViewModel的數據, 由ViewModel負責與Model層交互, 這就完全解耦了View層和Model層, 這個解耦是至關重要的, 它是前后端分離方案實施的重要一環。
2、為什么要使用MVVM
MVVM模式和MVC模式一樣,主要目的是分離視圖(View)和模型(Model),有幾大優點
(1) 低耦合。視圖(View)可以獨立于Model變化和修改,一個ViewModel可以綁定到不同的"View"上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。
(2) 可重用性。你可以把一些視圖邏輯放在一個ViewModel里面,讓很多view重用這段視圖邏輯。
(3)獨立開發。開發人員可以專注于業務邏輯和數據的開發(ViewModel),設計人員可以專注于頁面設計,使用Expression Blend可以很容易設計界面并生成xaml代碼。
(4)可測試。界面素來是比較難于測試的,測試可以針對ViewModel來寫
3、VUE概述
(1)什么是vue?
Vue是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合
這是官網給出的介紹,可能不是那么容易理解。簡單來說,Vue是一個視圖層框架,幫助我們更好的構建應用。
使用Vue和原生JS一個最顯著的差別就是,Vue不再對DOM直接進行操作,而是通過對數據的操作,來改變頁面。使用Vue構建的頁面,是有一個個的組件組成的,當組件中定義的數據發生變化時,組件的顯示也會跟著變化,且此過程無需刷新頁面。
(2)MVVM模式的實現者
Model:模型層, 在這里表示JavaScript對象 View:視圖層, 在這里表示DOM(HTML操作的元素) ViewModel:連接視圖和數據的中間件, Vue.js就是MVVM中的View Model層的實現者 在MVVM架構中, 是不允許數據和視圖直接通信的, 只能通過ViewModel來通信, 而View Model就是定義了一個Observer觀察者
ViewModel能夠觀察到數據的變化, 并對視圖對應的內容進行更新 ViewModel能夠監聽到視圖的變化, 并能夠通知數據發生改變 至此, 我們就明白了, Vue.js就是一個MV VM的實現者, 他的核心就是實現了DOM監聽與數據綁定
(3)為什么要使用Vue
易用:熟悉HTML、CSS、JavaScript之后,可快速度上手vue。學習曲線平穩。
輕量級:Vue.js壓縮后有只有20多kb,超快虛擬DOM
高效:吸取了Angular(模塊化) 和React(虛擬DOM) 的優勢, 并擁有自己獨特的功能
開源:文檔齊全,社區活躍度高
4、VUE之Hello World!
步驟一:創建空文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
</html>
步驟二:引入vue.js (本人下載的開發版的vue.js,跟本html文件放在了同一目錄下,所以直接引用)
<script type="text/javascript" src="vue.js"></script>
步驟三:創建vue實例
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
msg:'Hello World'
}
});
</script>
步驟四:數據與頁面元素綁定
<div id="app">
{{msg}}
</div>
完整的html
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app">
{{msg}}
</div>
<script type="text/javascript" src="vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el:'#app',
data:{
msg:'Hello World'
}
});
</script>
</body>
</html>
瀏覽器打開:
參數分析:
el : '#app' -- 綁定元素的ID(元素的掛載位置,值可以是CSS選擇器或者是DOM元素)
data : { msg : 'Hello World' } -- 模型數據,屬性名:msg 值:Hello World
{{msg}} : 在綁定的元素中使用{{ }}將Vue創建的名為msg的屬性包起來, 即可實現數據綁定功能,我們在調試狀態下手動修改下msg的值,在不刷新頁面的情況下就會展示我們修改后的值,這就是借助了Vue的數據綁定功能實現的。 MV VM模式中要求View Model層就是使用觀察者模式來實現數據的監聽與綁定, 以做到數據與視圖的快速響應
下一篇:VUE入門教程(二)之模板語法(指令)
于parseHTML函數代碼實在過于龐大,我這里就不一次性貼出源代碼了,大家可以前往(https://github.com/vuejs/vue/blob/dev/src/compiler/parser/html-parser.js)查看源代碼。
我們來總結一下該函數的主要功能:
1、匹配標簽的 "<" 字符
匹配的標簽名稱不能是:script、style、textarea
有如下情況:
1、注釋標簽 /^<!\--/
2、條件注釋 /^<!\[/
3、html文檔頭部 /^<!DOCTYPE [^>]+>/i
4、標簽結束 /^<\/ 開頭
5、標簽開始 /^</ 開頭
然后開始匹配標簽的屬性包括w3的標準屬性(id、class)或者自定義的任何屬性,以及vue的指令(v-、:、@)等,直到匹配到 "/>" 標簽的結尾。然后把已匹配的從字符串中刪除,一直 while 循環匹配。
解析開始標簽函數代碼:
function parseStartTag () {
// 標簽的開始 如<div
const start = html.match(startTagOpen)
if (start) {
const match = {
tagName: start[1], // 標簽名稱
attrs: [], // 標簽屬性
start: index // 開始位置
}
// 減去已匹配的長度
advance(start[0].length)
let end, attr
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
attr.start = index
v
advance(attr[0].length)
attr.end = index
match.attrs.push(attr) // 把匹配到的屬性添加到attrs數組
}
if (end) { // 標簽的結束符 ">"
match.unarySlash = end[1]
advance(end[0].length) // 減去已匹配的長度
match.end = index // 結束位置
return match
}
}
}
處理過后結構如下:
接下來就是處理組合屬性,調用 “handleStartTag” 函數
function handleStartTag (match) {
const tagName = match.tagName // 標簽名稱
const unarySlash = match.unarySlash // 一元標簽
if (expectHTML) {
if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
// 解析標簽結束
parseEndTag(lastTag)
}
if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
parseEndTag(tagName)
}
}
// 是否為一元標簽
const unary = isUnaryTag(tagName) || !!unarySlash
const l = match.attrs.length
// 標簽屬性集合
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
const value = args[3] || args[4] || args[5] || ''
const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines
attrs[i] = {
name: args[1], // 屬性名稱
value: decodeAttr(value, shouldDecodeNewlines) // 屬性值
}
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
// 開始位置
attrs[i].start = args.start + args[0].match(/^\s*/).length
// 結束位置
attrs[i].end = args.end
}
}
if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
lastTag = tagName
}
// 調用start函數
if (options.start) {
options.start(tagName, attrs, unary, match.start, match.end)
}
}
我們簡單說一下最后調用的start函數的作用:
1、判斷是否為svg標簽,并處理svg在ie下的兼容性問題
2、遍歷標簽屬性,驗證其名稱是否有效
3、標簽名是否為 style 或者 script ,如果在服務端會提示warn警告
4、檢查屬性是否存在 v-for、v-if、v-once指令
5、如果是更元素就驗證其合法性,不能是 slot 和 template 標簽,不能存在 v-for指令
以上就是界面html模板的開始標簽的分析,接下來我們來分析如何匹配結束標簽。
請看:Vue源碼全面解析三十 parseHTML函數(解析html(二)結束標簽)
如有錯誤,歡迎指正,謝謝。
ue的使用相信大家都很熟練了,使用起來簡單。但是大部分人不知道其內部的原理是怎么樣的,今天我們就來一起實現一個簡單的vue。
Object.defineProperty()
實現之前我們得先看一下Object.defineProperty的實現,因為vue主要是通過數據劫持來實現的,通過get、set來完成數據的讀取和更新。
var obj = {name:'wclimb'}
var age = 24
Object.defineProperty(obj,'age',{
enumerable: true, // 可枚舉
configurable: false, // 不能再define
get () {
return age
},
set (newVal) {
console.log('我改變了',age +' -> '+newVal);
age = newVal
}
})
> obj.age
> 24
> obj.age = 25;
> 我改變了 24 -> 25
> 25
從上面可以看到通過get獲取數據,通過set監聽到數據變化執行相應操作,還是不明白的話可以去看看Object.defineProperty文檔。
流程圖
html代碼結構
<div id="wrap">
<p v-html="test"></p>
<input type="text" v-model="form">
<input type="text" v-model="form">
<button @click="changeValue">改變值</button>
{{form}}
</div>
js調用
JavaScript
new Vue({
el: '#wrap',
data:{
form: '這是form的值',
test: '<strong>我是粗體</strong>',
},
methods:{
changeValue(){
console.log(this.form)
this.form = '值被我改變了,氣不氣?'
}
}
})
Vue結構
class Vue{
constructor(){}
proxyData(){}
observer(){}
compile(){}
compileText(){}
}
class Watcher{
constructor(){}
update(){}
}
Vue constructor 初始化
class Vue{
constructor(options = {}){
this.$el = document.querySelector(options.el);
let data = this.data = options.data;
// 代理data,使其能直接this.xxx的方式訪問data,正常的話需要this.data.xxx
Object.keys(data).forEach((key)=> {
this.proxyData(key);
});
this.methods = obj.methods // 事件方法
this.watcherTask = {}; // 需要監聽的任務列表
this.observer(data); // 初始化劫持監聽所有數據
this.compile(this.$el); // 解析dom
}
}
上面主要是初始化操作,針對傳過來的數據進行處理
proxyData 代理data
class Vue{
constructor(options = {}){
......
}
proxyData(key){
let that = this;
Object.defineProperty(that, key, {
configurable: false,
enumerable: true,
get () {
return that.data[key];
},
set (newVal) {
that.data[key] = newVal;
}
});
}
}
上面主要是代理data到最上層,this.xxx的方式直接訪問data
observer 劫持監聽
class Vue{
constructor(options = {}){
......
}
proxyData(key){
......
}
observer(data){
let that = this
Object.keys(data).forEach(key=>{
let value = data[key]
this.watcherTask[key] = []
Object.defineProperty(data,key,{
configurable: false,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue !== value){
value = newValue
that.watcherTask[key].forEach(task => {
task.update()
})
}
}
})
})
}
}
同樣是使用Object.defineProperty來監聽數據,初始化需要訂閱的數據。
把需要訂閱的數據到push到watcherTask里,等到時候需要更新的時候就可以批量更新數據了。下面就是;
遍歷訂閱池,批量更新視圖。
set(newValue){
if(newValue !== value){
value = newValue
// 批量更新視圖
that.watcherTask[key].forEach(task => {
task.update()
})
}
}
compile 解析dom
class Vue{
constructor(options = {}){
......
}
proxyData(key){
......
}
observer(data){
......
}
compile(el){
var nodes = el.childNodes;
for (let i = 0; i 0){
this.compile(node)
}
if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
node.addEventListener('input',(()=>{
let attrVal = node.getAttribute('v-model')
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'value'))
node.removeAttribute('v-model')
return () => {
this.data[attrVal] = node.value
}
})())
}
if(node.hasAttribute('v-html')){
let attrVal = node.getAttribute('v-html');
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
node.removeAttribute('v-html')
}
this.compileText(node,'innerHTML')
if(node.hasAttribute('@click')){
let attrVal = node.getAttribute('@click')
node.removeAttribute('@click')
node.addEventListener('click',e => {
this.methods[attrVal] && this.methods[attrVal].bind(this)()
})
}
}
}
},
compileText(node,type){
let reg = /{{(.*)}}/g, txt = node.textContent;
if(reg.test(txt)){
node.textContent = txt.replace(reg,(matched,value)=>{
let tpl = this.watcherTask[value] || []
tpl.push(new Watcher(node,this,value,type))
return value.split('.').reduce((val, key) => {
return this.data[key];
}, this.$el);
})
}
}
}
這里代碼比較多,我們拆分看你就會覺得很簡單了
if(node.hasAttribute('v-html')){
let attrVal = node.getAttribute('v-html');
this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))
node.removeAttribute('v-html')
}
上面這個首先判斷node節點上是否有v-html這種指令,如果存在的話,我們就發布訂閱,怎么發布訂閱呢?只需要把當前需要訂閱的數據push到watcherTask里面,然后到時候在設置值的時候就可以批量更新了,實現雙向數據綁定,也就是下面的操作
that.watcherTask[key].forEach(task => {
task.update()
})
然后push的值是一個Watcher的實例,首先他new的時候會先執行一次,執行的操作就是去把純雙花括號 -> 1,也就是說把我們寫好的模板數據更新到模板視圖上。
最后把當前元素屬性剔除出去,我們用Vue的時候也是看不到這種指令的,不剔除也不影響
至于Watcher是什么,看下面就知道了
Watcher
class Watcher{
constructor(el,vm,value,type){
this.el = el;
this.vm = vm;
this.value = value;
this.type = type;
this.update()
}
update(){
this.el[this.type] = this.vm.data[this.value]
}
}
之前發布訂閱之后走了這里面的操作,意思就是把當前元素如:node.innerHTML = ‘這是data里面的值’、node.value = ‘這個是表單的數據’
那么我們為什么不直接去更新呢,還需要update做什么,不是多此一舉嗎?
其實update記得嗎?我們在訂閱池里面需要批量更新,就是通過調用Watcher原型上的update方法。
效果
在線效果地址,大家可以瀏覽器看一下效果,由于本人太懶了,gif效果圖就先不放了,哈哈
*請認真填寫需求信息,我們會在24小時內與您取得聯系。