0
点赞
收藏
分享

微信扫一扫

vue webpack和vue双向绑定原理

sin信仰 2023-10-17 阅读 19

Webpack

1. 介绍

webpack 是什么?引用 webpack 官网 的原话:

本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

作用如下图所示:

vue   webpack和vue双向绑定原理_前端

图中,webpack 将左侧繁杂的不同类型文件的模板依赖关系,包括 .js、.hbs、.cjs、.sass、.jpg、.png 等类型文件,打包成了 .js、.css、.jpg、.png 4种类型的静态资源。这就是 webpack 的作用,负责将项目中依赖的各个模块,打包成一个或多个文件。

2. 功能

  1. 模块化开发
    有了 webpack,我们可以搭建脚手架,将js、css、图片、字体等都做模块,然后在HTML中引入使用。
    在 webpack 中,一切皆模块,并且支持静态解析、按需打包、动态加载、代码分离等功能,可以帮助我们优化代码,提升性能。
  2. 转换器
    Javascript、CSS 的语法规范一直在更新,可能会出现某些浏览器兼容性问题,Webpack 可以将各种类型的资源文件转换为浏览器可以识别的格式。例如将 TypeScript 编译成 JavaScript、SCSS/Less 编译成 CSS、ES6 编译成 ES5 等。
  3. 代码分割
    Webpack 可以将应用程序拆分成多个代码块,只加载必要的部分,提高页面加载速度和性能。
  4. 动态修改
    Webpack 可以实现自动打开页面,并且在代码修改后实时更新页面,能够提高开发效率。
  5. 插件系统
    Webpack 可以通过插件扩展其功能,例如压缩代码、提取公共代码等。

vue2 的响应式是通过 Object.defineProperty 对数据进行劫持,并结合发布订阅者模式实现。vue2 利用 Object.defineProperty 创建一个 observe 来劫持监听所有的属性,把这些属性全部转为 getter 和 setter。Vue 中每个组件实例都会对应一个 watcher 实例,它会在组件渲染的过程中把使用过的数据属性通过 getter 收集为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

vue   webpack和vue双向绑定原理_前端_02

1. reduce 链式获取对象的值

const person={
    name:"尼古拉斯赵四",
    info:{
        address:{
            location: "东北铁岭",
            work: "二人转"
        }
    }
}

const arrs = ["info","address","location"];

const result = arrs.reduce((newobj,key)=>{
    console.log(newobj)
    return newobj[key]
},person)

console.log(result); // 东北铁岭

//如果不是一个数组而是一个字符串呢
const str="info.address.work";
// console.log(str.split(".")) => arrs
const result2 = str.split(".").reduce((newobj,key)=>{
    return newobj[key];
}, person)
console.log(result2); // 二人转

2. 发布-订阅者模式

1.vue响应原理:

发布-订阅者模式用于处理一对多的场景,一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

vue   webpack和vue双向绑定原理_前端_03

优点:低耦合性,易于代码维护。

缺点:若订阅的消息未发生,需消耗一定的时间和内存。

举个栗子

这里面以微信公众号为例:

  • 假如用户大脚订阅了 胡吃海喝这个公众号,那么当公众号胡吃海喝推送消息的时候,用户大脚就会收到相关的推送,点开可以查看推送的消息内容。
  • 但是公众号胡吃海喝并不关心订阅的它的是男人、女人还是未成年,它只负责发布自己的主体,只要是订阅公众号的用户均会收到该消息。
  • 作为用户大脚,不需要时刻打开手机查看公众号胡吃海喝是否有推动消息,因为在公众号推送消息的那一刻,大脚就会收到相关推送。
  • 当然了,用户大脚如果不想继续关注公众号胡吃海喝,那么可以取消关注,取关以后,公众号胡吃海喝再推送消息,大脚就无法收到了。

vue的发布订阅模式可以用下图简单描述:

vue   webpack和vue双向绑定原理_前端_04

接下来用代码实现简单的发布订阅:

//收集依赖/收集订阅
class Dep{
    constructor(){
        //这个subs数组,用来存放所有订阅者的信息
        this.subs=[]
    }
    //向subs数组中,添加订阅者的信息
    addSubs(watcher){
        this.subs.push(watcher)
    }
    //发布通知的方法
    notify(){
        this.subs.forEach((watcher)=>watcher.update()) 
    }
}

//订阅者的类
class Watcher{
    constructor(cb) {
        this.cb=cb;
    }
    //触发回调的方法
    update(){
        this.cb();
    }
}

const w1=new Watcher(()=>{
    console.log("我是第一个订阅者");
})

const w2=new Watcher(()=>{
    console.log("我是第二个订阅者!")
})

const d1=new Dep();
d1.addSubs(w1);
d1.addSubs(w2);

// 只要我们为vue中的数据重新赋值了,这个赋值的操作,会被vue监听到
// 然后vue把数据的变化,通知到每个订阅者!!
// 接下来,订阅者(DOM元素)要根据最新的数据,更新自己的内容
d1.notify();

3. 简单实现双向数据绑定

1. 创建一个js文件,vue.js

class Vue {
    constructor(options) {
        this.$data = options.data

        // 调用数据劫持的方法
        Observe(this.$data)

        // 属性代理
        Object.keys(this.$data).forEach((key) => {
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get() {
                    return this.$data[key]
                },
                set(newValue) {
                    this.$data[key] = newValue
                },
            })
        })

        // 调用模板编译的函数
        Compile(options.el, this)
    }
}

// 定义一个数据劫持的方法
function Observe(obj) {
    // 递归终止条件
    if (!obj || typeof obj !== 'object') return
    const dep = new Dep()

    // 通过 Object.keys(obj) 获取到当前 obj 上的每个属性
    Object.keys(obj).forEach((key) => {
        // 当前被循环的 key 所对应的属性值
        let value = obj[key]
        // 把 value 这个子节点,进行递归
        Observe(value)
        // 需要为当前的 key 所对应的属性,添加 getter 和 setter
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                // 只要执行了下面这一行,那么刚才 new 的 Watcher 实例,
                // 就被放到了 dep.subs 这个数组中了
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set(newVal) {
                value = newVal
                Observe(value)
                // 通知每一个订阅者更新自己的文本
                dep.notify()
            },
        })
    })
}

// 对 HTML 结构进行模板编译的方法
function Compile(el, vm) {
    // 获取 el 对应的 DOM 元素
    vm.$el = document.querySelector(el)

    // 创建文档碎片,提高 DOM 操作的性能
    const fragment = document.createDocumentFragment()

    while ((childNode = vm.$el.firstChild)) {
        fragment.appendChild(childNode)
    }

    // 进行模板编译
    replace(fragment)

    vm.$el.appendChild(fragment)

    // 负责对 DOM 模板进行编译的方法
    function replace(node) {
        // 定义匹配插值表达式的正则
        const regMustache = /\{\{\s*(\S+)\s*\}\}/

        // 证明当前的 node 节点是一个文本子节点,需要进行正则的替换
        if (node.nodeType === 3) {
            // 注意:文本子节点,也是一个 DOM 对象,如果要获取文本子节点的字符串内容,需要调用 textContent 属性获取
            const text = node.textContent
            // 进行字符串的正则匹配与提取
            const execResult = regMustache.exec(text)
            console.log(execResult)
            if (execResult) {
                const value = execResult[1].split('.').reduce((newObj, k) => newObj[k], vm)
                node.textContent = text.replace(regMustache, value)
                // 在这个时候,创建 Watcher 类的实例
                new Watcher(vm, execResult[1], (newValue) => {
                    node.textContent = text.replace(regMustache, newValue)
                })
            }
            // 终止递归的条件
            return
        }

        // 判断当前的 node 节点是否为 input 输入框
        if (node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT') {
            // 得到当前元素的所有属性节点
            const attrs = Array.from(node.attributes)
            const findResult = attrs.find((x) => x.name === 'v-model')
            if (findResult) {
                // 获取到当前 v-model 属性的值   
                const expStr = findResult.value
                const value = expStr.split('.').reduce((newObj, k) => newObj[k], vm)
                node.value = value

                // 创建 Watcher 的实例
                new Watcher(vm, expStr, (newValue) => {
                    node.value = newValue
                })

                // 监听文本框的 input 输入事件,拿到文本框最新的值,把最新的值,更新到 vm 上即可
                node.addEventListener('input', (e) => {
                    const keyArr = expStr.split('.')
                    const obj = keyArr.slice(0, keyArr.length - 1).reduce((newObj, k) => newObj[k], vm)
                    const leafKey = keyArr[keyArr.length - 1]
                    obj[leafKey] = e.target.value
                })
            }
        }

        // 证明不是文本节点,可能是一个DOM元素,需要进行递归处理
        node.childNodes.forEach((child) => replace(child))
    }
}

// 依赖收集的类/收集 watcher 订阅者的类
class Dep {
    constructor() {
        // 所有的 watcher 都要存到这个数组中
        this.subs = []
    }

    // 向 subs 数组中,添加 watcher 的方法
    addSub(watcher) {
        this.subs.push(watcher)
    }

    // 负责通知每个 watcher 的方法
    notify() {
        this.subs.forEach((watcher) => watcher.update())
    }
}

// 订阅者的类
class Watcher {
    // cb 回调函数中,记录着当前 Watcher 如何更新自己的文本内容
    //   但是,只知道如何更新自己还不行,还必须拿到最新的数据,
    //    需要在 new Watcher 期间,把 vm 也传递进来(因为 vm 中保存着最新的数据)
    //   必须在 new Watcher 期间,指定 watcher 对应的数据的名字
    constructor(vm, key, cb) {
        this.vm = vm
        this.key = key
        this.cb = cb

        // 把创建的 Watcher 实例存到 Dep 实例的 subs 数组中 
        Dep.target = this
        key.split('.').reduce((newObj, k) => newObj[k], vm)
        Dep.target = null
    }

    // watcher 的实例,需要有 update 函数,从而让发布者能够通知我们进行更新!
    update() {
        const value = this.key.split('.').reduce((newObj, k) => newObj[k], this.vm)
        this.cb(value)
    }
}

2. 在页面中引入 vue.js 然后创建实例

<script src="./vue.js"></script>


<div id="app">
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}</p>
    <p>工作:{{info.work}}</p>
    <p>住址:{{info.address}}</p>
    <input type="text" v-model="phone" />
    <p>双向绑定的iPhone:{{phone}}</p>
</div>
<script>
        const vm=new Vue({
            el:"#app",
            data:{
                name:"海绵宝宝",
                age:3,
                info:{
                    work:"高级厨师",
                    address:"太平洋比奇堡"
                },
                phone:'123'
            }
        })
</script>

举报

相关推荐

0 条评论