vue 的学习记录之基础原理

当我第一次接触Vue时,看起来就像魔术一样。
拿下面这个简单的应用为例:

    <div id="app">
        <div>单价:${{ price }}</div>
        <div>数量:${{ quantity }}</div>
        <div>总计:${{ total }}</div>
    </div>
复制代码
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        price: 5,
        quantity: 2
      },
      computed: {
        total() {
          return this.price * this.quantity
        }
      }
    })
  </script>
复制代码

在控制台通过 vm.$data.price 改变它的值,页面上的值就发生了变化。
但是,Vue 如何知道 price 的值改变时,就要更改页面的内容,以及改变依赖它的值呢?

在没有使用Vue的时候,例如运行下面这段代码:

    let price = 5
    let quantity = 2
    let total = price * quantity  // 肯定输出 10
    
    price = 20
    
    console.log(`total is ${total}`)
复制代码

由于没有使用Vue ,结果肯定是 10

 >>> total is 10
复制代码

而在Vue中,total 可以随着 price 或者 quantity 的更新而改变。

>>> total is 40
复制代码

问题1

JavaScript是程序性的,如何才能在 price 或者 quantity 被修改时,重新计算 total?

解决方案1

首先,我们需要一个方法来告诉我们的应用程序,“这是一段计算 total 的代码,我可能在某个时间要运行它,现在先储存起来”。 然后,在 price 或者 quantity 变量被修改时,再次运行刚刚储存的代码。

    let price = 5
    let quantity = 2
    let total = 0
    let target = null
    
    target = () => { total = price * quantity }
复制代码

现在 target 中储存了计算 total 的函数,我们要把它储存起来,在需要的时候执行它。
写一个叫做 record 的函数,来做这个事情。

    let storage = []    // 把代码储存到这里
    function record() {
        storage.push(target)
    }
复制代码

储存完毕了,稍后需要运行的时候,我们就可以直接调用了,所以我们需要一个运行我们记录的所有代码的函数。

    function replay() {
        storage.forEach(run => run())
    }
复制代码

这个 replay 函数的作用,就是遍历我们储存在仓库的每一个函数,并执行它。

然后我们就可以在代码中使用了:

price = 20
console.log(total) // => 10
repaly()
console.log(total) // => 40
复制代码

这样看起来还是挺简单的,解决了我们的问题。下面是完整的代码片段。

    let price = 5
    let quantity = 2
    let total = 0
    let target = null
    let storage = []
    
    function record() {
        storage.push(target)
    }
    
    function replay() {
        storage.forEach(run => run())
    }
    
    target = () => { total = price * quantity }
    
    record()    // 储蓄
    target()    // 执行计算
    
    price = 20  // 更改价格
    console.log(total) // => 10
    replay()    // 重新计算
    console.log(total) // => 40
复制代码

问题2

我们想根据需要持续的记录目标,并且要有一定扩展性。

解决方案2

我们将 储存 + 执行 的行为封装到一个类中,实现一个观察者模式。

    class Observer {
        constructor() {
            this.subscribers = []
        }
        
        record() {
            if(target && !this.subscribers.includes(target)) { // 当 target 存在,并且还没有被储存过的时候,再储存
                this.subscribers.push(target)
            }
        }
        
        notify() {  // 替换了 replay 
            this.subscribers.forEach(sub => sub())
        }
    }
复制代码

那么,现在要实现储存 + 执行 的动作,就需要这样:

const obs = new Observer()

let price = 5
let quantity = 2
let total = 0
let target = () => { total = price * quantity }

obs.record()
target()
console.log(total)  // => 10jj

price = 20
console.log(total)  // => 10

obs.notify()        // => 40
console.log(total)

复制代码

效果还是一样的,但是处理 target 的地方还是有点怪怪的。我们可以封装一个 watcher 函数来处理。

target = () => { total = price * quantity }
obs.record()
target()
复制代码

可以更改为:

watcher(() => {
    total = price * quantity
})
复制代码

watcher 中,我们可以做一些事情:

function watcher(myFunc) {
    target = myFunc
    obs.record()    // 储存
    target()        // 计算
    target = null   // 重置为空
}
复制代码

watcher 函数接受一个参数 myFunc 参数,将其赋值给全局属性 target,调用 obs.record()添加为订阅者,然后调用 target 函数执行计算, 然后重置 target 函数为空。 现在,我们可以这样运行了:

price = 20
console.log(total)  // => 10
obs.notify()
console.log(total)  // => 40
复制代码

但是,此时和 Vue 的效果还是相差甚远。我们希望每个变量都有自己的 Observer 类。 当这个变量被访问时,可以将它的 target保存到订阅者数组中,当被更改时,运行储存在订阅者数组中的函数。

这时,就需要 Object.defineProperty() 函数闪亮登场了,它允许我们为属性定义 gettersetter 方法。
我们先看一下最简单的用法:

let data = { price: 5, quantity: 2 }

Object.defineProperty(data, 'price', {
    get() {
        console.log(`getting 执行了`)
    },
    set() {
        console.log(`setting 执行了`)
    }
})

data.price          // get 方法会执行
data.price = 20     // set 方法会执行

复制代码

现在,我们希望 get 方法执行的时候,返回一个值。 set 方法执行的时候,更新一个值,所以我们添加一个internalValue 变量来储存我们当前的 price

let data = { price: 5, quantity: 2 }
let internalValue = data.price

Object.defineProperty(data, 'price', {
    get() {
        console.log(`getting price: ${internalValue}`)
        return internalValue
    },
    set(newVal) {
        console.log(`setting price 为:${ newVal}`)
        internalValue = newVal
    }
})

total = data.price * data.quantity
data.price = 20

>>> getting price: 5
>>> setting price 为:20
复制代码

至此,我们获取并设置值的时候,都可以获得通知。
通过 Object.keys(data) 遍历数据,或者对象的键数组

let data = { price: 5, quantity: 2 }

Object.keys(data).forEach(key => {
    let internalValue = data[key]
    
    Object.defineProperty(data, key, {
        get() {
            console.log(`getting ${key} : ${internalValue}`)
            return internalValue
        },
        set(newVal) {
            console.log(`setting ${key} to: ${newVal}`)
            internalValue = newVal
        }
    })
})

total = data.price * data.quantity
data.price = 20

>>> getting price : 5
>>> getting quantity : 2
>>> setting price to: 20
复制代码

这时候,我们和 Observer 类合并起来

get => 调用 obs.record() 储存当前的 target
set => 调用 obs.notify() 重新运行 target

完整代码如下:

let data = { price: 5, quantity: 2 }
let target = null

class Observer {
    constructor() {
        this.subscribers = []
    }
    
    record() {
        if(target && !this.subscribers.includes(target)) {
            this.subscribers.push(target)
        }
    }

    notify() {
        this.subscribers.forEach(sub => sub())
    }
}

Object.keys(data).forEach(key => {
    let internalValue = data[key]
    
    const obs = new Observer()
    
    Object.defineProperty(data, 'price', {
        get() {
            obs.record()
            return internalValue
        },
        set(newVal) {
            internalValue = newVal
            obs.notify()
        }
    })
})

function watcher(myFunc) {
    target = myFunc
    target()
    target = null
}

watcher(() => {
    data.total = data.price * data.quantity
})
复制代码

现在我们再来试验一下吧,数据每次更改,我们的代码都重新运行了。

此时我们再看一下 Vue 的运行示意图

显然,Vue 更复杂,但是我们知道了基础知识,在以后深入了解 Vue 时,看看是否可以在源码中找到这种模式。

学习 Vue 官方文档的视频教学,Vue Mastery

转载于:https://juejin.im/post/5cda5b0f518825128d51a736

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值