vue 笔记

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>

<body>
    <div id="myDiv"></div>
    <!-- Vue的响应式原理 -->
    <!-- <script type="text/javascript" src="./index.js"></script> -->
    <!-- 发布订阅者模式 -->
    <!-- <script type="text/javascript" src="./publishSubscribe.js"></script> -->
    <!-- 观察者模式 -->
    <script type="text/javascript" src="./watcher.js"></script>
</body>

</html>

index.js

/**
 *  Vue的响应式原理
 */
var myDiv = document.getElementById('myDiv');
console.log('myDiv', myDiv);
let data = {
    msg: 'Hello'
}

let vm = {};
/**
 *  objObject.defineProperty的作用就是直接在对象上定义一个新属性 , 或者修改已存在的属性
 *  同事可以接收三个参数 
 *  示例 : 
 *  let obj = {
 *      msg: 'Hello' , 
 *  }
 *  Object.defineproperty(obj, prop, desc)
 *  obj : 第一个参数就是在哪一个对象身上添加或者修改属性  示例中obj
 *  prop :第二个参数就是要修改或者添加的属性名           示例中msg 
 *  desc : 配置项 , 一般就是一个对象  
 *          writable:	        是否可重写                      控制属性是否可以被修改 默认为flase
 *          value:  	        当前值                          用来设置属性值(可以理解为默认值)
 *          get:    	        读取时内部调用的函数             是获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为undefined
 *          set:               写入时内部调用的函数             是设置值的时候的方法,类型为 function ,设置值的时候会被调用,undefined
 *          enumerable: 	    是否可以遍历                    控制属性书否可以被枚举 默认为false
 *          configurable: 	    是否可再次修改配置项             控制属性是否可以被删除 默认为false
 * 
 *          *个人理解 vue中的setter和getter函数就是在调用这里的set和get方法
 */

Object.defineProperty(vm, 'msg', {
    //可枚举
    enumerable: true,
    //可配置
    configurable: true,
    //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是msg的值
    get() {
        console.log('get', data.msg);
        return data.msg;
    },
    //当设置值的时候执行
    //当有人修改person的msg属性时,set函数(setter)就会被调用,且会收到修改的具体值
    set(newValue) {
        console.log('set', newValue);
        if (newValue == data.msg) {
            return;
        }
        data.msg = newValue;
        myDiv.textContent = data.msg;
        /**
         * 在这个set方法中我们会发现 如果传入的值newValue 等等于原先的值 就return出去  如果不等于 就给原先的值重新赋值 
         */
    }
})

//测试
vm.msg = 'Hello world';
console.log(vm.msg)


/**
 *  Vue响应式原理就是
 *  vue(注入data , 并把data成员转成 setter getter)     → → →     Observe(数据劫持监听数据对象所有属性,     → → →     Dep(发布者 添加watcher,当数据变化通知所有观察者)
 *                  ↓                                              变动就拿到最新值通知Dep)                                             ↓
 *                  ↓                                                                                                                 ↓
 *                  ↓                                                                                                                 ↓
 *                  ↓                                                                                                                 ↓
 *             compiler(解析指令)                → → → →                  watcher(观察者 数据变化就更新视图)                       ← ← ← ←
 * 
 * 
 * Vue
 *        记录传入的选项,设置 data / data/ data/el
 *        把 data 的成员注入到 Vue 实例
 *        负责调用 Observer 实现数据响应式处理(数据劫持)
 *        负责调用 Compiler 编译指令/插值表达式等
 *
 * Observer
 *        负责把 data 中的成员转换成 getter/setter
 *        负责把多层属性转换成 getter/setter
 *        如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
 *        数据劫持
 *        添加 Dep 和 Watcher 的依赖关系
 *        数据变化发送通知
 *
 * Compiler
 *        负责编译模板,解析指令/插值表达式
 *        负责页面的首次渲染过程
 *        当数据变化后重新渲染
 *
 * Dep
 *        收集依赖,添加订阅者(watcher)
 *        通知所有订阅者
 *
 * Watcher
 *        自身实例化的时候往dep对象中添加自己
 *        当数据变化dep通知所有的 Watcher 实例更新视图
 */


/**
 *  vue源码实现原理
 * Vue功能
        负责接收初始化的参数(选项)
        负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
        负责调用 observer 监听 data 中所有属性的变化
        负责调用 compiler 解析指令/插值表达式
        class Watcher {
            constructor(vm, key, cb) {
                this.vm = vm
                // data 中的属性名称
                this.key = key
                // 当数据变化的时候,调用 cb 更新视图
                this.cb = cb
                // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
                Dep.target = this
                // 触发一次 getter,让 dep 为当前 key 记录 watcher
                this.oldValue = vm[key]
                // 清空 target
                Dep.target = null
            }
            update() {
                const newValue = this.vm[this.key]
                if (this.oldValue === newValue) {
                   return
                }
                this.cb(newValue)
            }
        }
    
    _proxyData(){
        // loop data
        Object.keys(data).forEach(key => {
            ObjectFlags.defineProperty(this, key, {
                get(){
                    return data[key];
                },
                set(newValue){
                    if(data[key] === newValue){
                        return;
                    }
                    data[key] = newValue;
                }
            })
        })
    }
}
 * 
 * 
 * Observer功能
 *       负责把 data 选项中的属性转换成响应式数据
 *       data 中的某个属性也是对象,把该属性转换成响应式数据 deep reactive
 *       数据变化发送通知
 * 
 * class Observer {
    //$data => getter / setter 
    constructor(data) {
        this.walk(data);
    }
    //1. if not obj return
    //2. if obj loop and getter / setter
    walk(data) {
        if(!data || Object.prototype.toString(data).slice(8, -1) === 'object'){
            return;
        }
        //loop data
        Object.keys(data).forEach(key => {
            this.defineReactive(key, data[key]);
        })
    }
    //define reactive 
    defineReactive(data, key, val){
        const that = this;
        this.walk(val);
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get(){
                return val;
            },
            set(newValue){
                if(newValue === val){
                    return;
                }
                // newValue => reactive
                that.walk(newValue);
                val = newValue;
            }
        })
    }
}
 * 
 * 
 * Dep
class Dep {
  constructor () {
    // 存储所有的订阅者
    this.subs = []
  }
  // 添加订阅者
  addSub (sub) {
    if (sub && sub.update) { this.subs.push(sub)
    } 
  }
  // 通知观察者 
  notify () {
    this.subs.forEach(sub => sub.update()) 
  }
}
在 compiler.js 中收集依赖,发送通知
// defineReactive 中
// 创建 dep 对象收集依赖
const dep = new Dep()


// getter 中
// get 的过程中收集依赖
Dep.target && dep.addSub(Dep.target)

// setter 中
// 当数据变化之后,发送通知
dep.notify()
 * 
 * 
 * watcher
数据变化触发依赖,dep通知所有的watcher实例更新视图
自身实例化的时候往dep对象中添加自己的结构
class Watcher {
  constructor (vm, key, cb) {
    this.vm = vm
    // data 中的属性名称
    this.key = key
    // 当数据变化的时候,调用 cb 更新视图
    this.cb = cb
    // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
    Dep.target = this
    // 触发一次 getter,让 dep 为当前 key 记录 watcher
    this.oldValue = vm[key]
    // 清空 target
    Dep.target = null
  }
  update () {
    const newValue = this.vm[this.key]
    if (this.oldValue === newValue) {
      return
    }
    this.cb(newValue)
  }
}
在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化
// 因为在 textUpdater等中要使用 this
updaterFn && updaterFn.call(this, node, this.vm[key], key)

// v-text 指令的更新方法
textUpdater (node, value, key) {
  node.textContent = value
  // 每一个指令中创建一个 watcher,观察数据的变化
  new Watcher(this.vm, key, value => {
    node.textContent = value
  })
}
视图变化更新数据
// v-model 指令的更新方法
modelUpdater (node, value, key) {
  node.value = value
  // 每一个指令中创建一个 watcher,观察数据的变化
  new Watcher(this.vm, key, value => {
    node.value = value
  }
  // 监听视图的变化
  node.addEventListener('input', () => {
    this.vm[key] = node.value
  })
}
 */

publishSubscribe.js

/**
 *  发布订阅者模式
 *  发布订阅模式通俗讲 : 假设在我们的程序中, 存在一个信号中心 , 当某个任务执行完成 , 就像信号中心"发布(Public)"一个信号 ,而其他信号可以想限号中心"订阅(Subscribe)"这个信号
 *                      从而知道自己什么时候可以开始执行。这个就叫"发布/订阅模式"(publish-subscribe pattern)
 */

/**
 * vue中的发布订阅(手写实现)
 */


// class EventEmitter { constructor(){:这是一个ES6类的定义,它创建了一个名为EventEmitter的类。
class EventEmitter {
    constructor() {
        /**
         * this.subs = []; 在这个类的构造函数中 创建了一个变量subs 这个变量用来存储时间类型和对应的事件处理函数
         */
        this.subs = [];
    }
    /**
     * 
     * @param {*} eventType 表示事件的类型 click change submit...等事件
     * @param {*} fn        表示事件处理函数
     */
    $on(eventType, fn) {
        /**
         *  this.subs[eventType] = this.subs[eventType] || []; 
         *  用来初始化时间类型对应的处理函数数组,如果订阅过该时间类型,就创建一个新的数组
         */
        this.subs[eventType] = this.subs[eventType] || [];
        /**
         *  this.subs[eventType].push
         *  将事件处理函数fn添加到事件类型evenTyp对应的数组中 , 这样可以在时间触发时执行这些处理函数
         */
        this.subs[eventType].push(fn);
    }
    /**
     * 
     * @param {*} eventType  定义一个方法 用于触发事件 , 接收参数为要触发时间的类型
     */
    $emit(eventType) {
        /**
         *  this.subs[eventType] && this.subs[eventType].forEach(v => v());
         *  这行代码首先检查是存在否订阅传入的evenType 类型的事件处理函数 如果有就遍历执行数组中的每个时间处理函数执行 
         *  这样就实现了时间的触发 , 所有订阅了改时间的处理函数都会被执行 
         */
        this.subs[eventType] && this.subs[eventType].forEach(v => v());
    }
}
//测试
/**
 *  创建一个EventEmitter示例 bus
 *  然后通过bus.$on方法取注册两个事件处理函数用来 处理click事件
 *  最后通过bus.$emit('click') 触发click事件, 相应的事件处理函数被执行 , 打印出相应的消息
 *  bus.$emit('change')并未被执行 是因为我们没有注册 change事件处理函数 所以没有打印结果
 *  如果想让bus.$emit('change')执行 我们可以通过bus.$on注册事件处理函数
 *  例 : bus.$on('change' , function(){
 *            console.log('我可以被正常执行了');
 *        })
 *       
 */
let bus = new EventEmitter();
//registe event
bus.$on('click', function(){
    console.log('click'); //click
})

bus.$on('click', function(){
    console.log('click1') //click1
})

bus.$emit('click');

bus.$emit('change');

watcher.js

/**
 *  观察者模式
 *  由具体的目标调度 例如当事件触发时 , Dep就会调用观察者方法 所以观察者模式的订阅者和发布者存在依赖
 */

/**
 *  class Dep{} 定义一个名为Dep的类
 */
class Dep {
    /**
     *  constructor(){} 类的构造函数,用于初始化对象
     */
    constructor() {
        /**
         *  this.subs = []; 在构造函数内部创建一个名为subs的空数组用于存储所有订阅者
         */
        this.subs = [];
    }
    /**
     * 
     * @param {*}  addSub() 定义一个addSub方法 用于将所有的订阅者添加到sub数组中
     *                      在这个方法中 使用条件语句 sub && sub.update确保传入的sub对象存在update方法 然后再将其添加到订阅者列表中
     */
    addSub(sub) {
        (sub && sub.update) && this.subs.push(sub);
    }
    /**
     *  @param {*}  notify() 定义一个notify方法 , 用于通知所有订阅者执行其update方法 , 
     *                       通过遍历subs数组并对每个订阅者调用其update()方法
     */
    notify() {
        this.subs.forEach(sub => {
            sub.update();
        })
    }
}
/**
 *  class Watcher {} 定义了一个名为 Watcher 的类
 */
class Watcher {
    /**
     *   @param {*}  update() Watcher 类的方法,当被调用时,它会在控制台中输出 "update"
     */
    update() {
        console.log('update');
    }
}

//测试
/**
 *  let dep = new Dep();:创建一个 Dep 类的实例,即一个发布者对象。
 */
let dep = new Dep();
/**
 *  let watcher = new Watcher();:创建一个 Watcher 类的实例,即一个订阅者对象。
 */
let watcher = new Watcher();
/**
 *  dep.addSub(watcher);:将 watcher 对象添加到 dep 的订阅者列表中,以便 watcher 可以接收来自 dep 的通知。
 */
dep.addSub(watcher);
/**
 *  dep.notify();:调用 dep 的 notify 方法,触发所有订阅者的 update 方法,这将在控制台中输出 "update"。
 */
dep.notify();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值