從設計理念到數據響應式

這是我參與8月更文挑戰的第10天,活動詳情查看:8月更文挑戰javascript

本文從vue系列的基本設計思路開始,到手寫基本api的實現。讓你們從實踐中體會vue的數據驅動的神祕之處。html

設計理念

你們都知道,vue是一個典型的MVVM框架。那什麼是MVVM、在vue中又是怎麼體現的呢?vue

MVVM

image.png

M

M表明的是模型Model。是展現在頁面中的數據,在vue中指向的是data中的數據模型。java

V

V表明的是視圖View。是展現的頁面,指向的是vue中的模板引擎(template模板)。git

VM

VM表明的是ViewModel。不須要經過咱們的操做將數據解析展現到視圖上,以及數據發生改變,頁面上的視圖自動會發生相應改變。github

那麼vue是如何將視圖和邏輯操做分開,這就頗有必要提到vue中數據驅動的特色。那麼這些數據又是如何在視圖中展現的呢? vue經過數據響應式監聽數據的變化並在視圖中更新;
模板引擎提供描述視圖的模板語法(類html。提供一些vue特有指令、插值);
渲染:將模板語法轉爲html(AST=>vdom=>dom)。
web

數據響應式

簡單響應式

vue中的監聽數據變化:
vue2: Object.defineProperty()
vue3: Proxy
二者簡單的響應數據案例能夠查看這篇文章api

<div id="app"></div>
<script> function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { return val }, set(newVal) { if (val != newVal) val = newVal // 數據發生變化通知視圖更新 update() } }) } function update() { content.innerHTML = `<h1>${obj.name}</h1>` } let obj = { name: 'clying' } let content = document.getElementById('app') update() // 響應式處理 defineReactive(obj, 'name', 'clying') setTimeout(() => { obj.name = 'deng' console.log(obj.name); }, 1000) </script>
複製代碼

運行代碼,咱們能夠看到頁面一開始展現clying,通過1秒以後變爲deng。 經過這個簡單的案例,咱們能夠實現簡單的obj對象響應式地對頁面進行渲染。數組

遞歸響應式

上述案例中只是對deep=1的對象進行了數據監聽。那麼具備深度的obj對象又是如何監聽變化的呢?
markdown

這時候就須要遍歷obj對象,對對象中的每一個屬性進行數據監聽。經過Object.keys返回一個obj中全部元素爲字符串的數組,對其進行setter和getter攔截。

// 先來一個具備深度的對象
let obj = {
  name: 'clying',
  arr: [
    1,
    {
      namearr: '2',
    },
  ],
  children: {
    name1: 'deng',
    children: {
      name2: 'clying deng',
    },
  },
}

function defineReactive(obj, key, val) {
  observe(val) // 子屬性可能仍爲對象,在對其進行攔截
  Object.defineProperty(obj, key, {
    get() {
      console.log('獲取', key)
      return val
    },
    set(newVal) {
      if (newVal !== val) val = newVal
      console.log('設置新值', key, obj[key])
    },
  })
}
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return
  Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]))
}
observe(obj)
obj.children.name1 = 'l'
複製代碼

image.png 當設置obj.children.name1時,先獲取到children屬性,發現children還是一個對象,繼續遍歷。獲取到children中的name1屬性時,發現不是對像,對name1進行攔截,設置新值。

動態添加屬性

當我想要根據上例,對obj動態添加一個age屬性時,實際上是沒有做用的。defineProperty沒法檢測新增屬性,這也涉及到vue2的一個弊端。這就要額外使用到vue2中的set API方法。

function set(obj, key, val) {
  defineReactive(obj, key, val)
}
set(obj, 'age', 22)
obj.age
複製代碼

能夠看出set方法其實也是利用defineProperty去添加新屬性,只是須要用戶手動調用。經過調用set,使obj中新屬性age能夠被攔截到。

image.png

注意set用法:
set方法對於接收的目標參數必須是響應式的,能夠在源碼set方法中看到,一開始就會去判斷傳入的目標值是不是原始值、undefined或null,若是是這些狀況直接警告。若是想要刪除屬性,相同的須要手動調用delete方法。
注:observer文件夾index.js 201行

數組響應式

defineProperty方法實際上是能夠攔截到像arr[0] = 1這種,經過index下標賦值的數組。可是它沒法支持數組中push、pop等數組的原型方法。咱們須要攔截數組的7個方法,重寫他們,就是幹!

由於咱們只是簡單的模仿,沒有寫Observe類。在此我將Observe類中拆成observe方法(判斷是數組仍是對象,分別監聽)、observeArray循環遍歷監聽數組屬性。

function observeArray(arr) {
  arr.forEach((_) => observe(_))
}
function observe(obj) {
  if (typeof obj !== 'object' || obj == null) return
  if (Array.isArray(obj)) {
    obj.__proto__ = arrayMethods // 繼承原型方法屬性
    observeArray(obj)
  } else {
    Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]))
  }
}
複製代碼

比較核心的仍是在 obj.__proto__ = arrayMethods中,使當前遍歷到的數組的原型鏈能夠指向咱們重寫的數組方法。

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse',
]
let oldArrayMethods = Array.prototype
let arrayMethods = Object.create(oldArrayMethods) //arrayMethods的原型指向Array數組的原型,能夠獲取數組原型方法
methodsToPatch.forEach((method) => {
  arrayMethods[method] = function (...args) {
    const result = oldArrayMethods[method].apply(this, args)
    let inserted // 當前用戶插入的元素
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      // 3個 新增屬性 splice 有刪除 新增的功能 arr.splice(0,1,{name:1})
      case 'splice':
        inserted = args.slice(2)
      default:
        break
    }
    // let ob = this.__ob__;
    // ob.observeArray(inserted); // 插入的是對象或者數組的話還須要再次遞歸監聽
    // update通知更新
    return result
  }
})
複製代碼

當咱們經過obj.arr.push(1);obj.arr[1].namearr = 2時,能夠看到控制檯輸出:

image.png

說明在push數組方法,和修改數組值時,數組均可以走到defineProperty中,被其攔截。
在此,數組插入的值多是對象或數組時,仍須要對其插入的值進行監聽。應該在Observe類中,先將這個實例保存(內部會含有observeArray方法)。而後,在arrayMethods中使用其observeArray方法,繼續進行深度劫持。

至此,關於數據響應式就能夠告一段落拉。若有不足,歡迎你們指正。

相關文章
相關標籤/搜索