精华内容
下载资源
问答
  • vue实现响应式网页
    2022-04-15 12:55:24

    前言

    此为自己学习vue源码响应式部分的一个小demo,按照自己的理解实现改变变量a,变量b也跟着改变的效果,整体实现较为简单,不考虑任何复杂场景,如有问题请大家指正

    效果

    data.age改变后,comouted.computedB的值也改变了

    node index.js
    // hello, computedb is 22
    // hello, computedb is 59
    

    代码

    可以直接复制到浏览器控制台运行

    // 发布者
    class Observer {
      constructor() {
        this.listeners = [];
      }
      collect(t) {
        if (t) {
          this.listeners.push(t);
        }
      }
      publish() {
        this.listeners.forEach((item) => {
          item();
        });
      }
    }
    
    // 订阅者
    class Watcher {
      constructor(v) {
        this.val = v;
      }
    }
    // 初始化方法,把数据变成一个 发布者
    function becomeObserver(obj) {
      Object.keys(obj).forEach((item) => {
        const ob = new Observer();
        let val = obj[item];
        Object.defineProperty(obj, item, {
          get() {
            // 收集订阅者
            ob.collect(
              currentWatcher && currentWatcher.val ? currentWatcher.val : ""
            );
            return val;
          },
          set(newV) {
            val = newV;
            // 通知订阅者
            ob.publish();
          },
        });
      });
    }
    // 订阅方法, 把comouted 、watcher 等变为一个订阅者
    function becomeWatcher(obj) {
      Object.keys(obj).forEach((item) => {
        const val = obj[item];
        Object.defineProperty(obj, item, {
          get() {
            // 把自己这个watcher放到对应的发布者的listeners里
            let pos = watcherList.findIndex((item) => item.val === val);
            if (pos !== -1) {
              currentWatcher = watcherList[pos];
            } else {
              watcherList.push(new Watcher(val));
              currentWatcher = watcherList[watcherList.length - 1];
            }
            return val;
          },
        });
      });
    }
    // 记录所有的订阅者
    let watcherList = [];
    // 记录当前正在使用的订阅者
    let currentWatcher = null;
    // data属性 ,对应vue组件中的data
    var data = {
      age: 2,
    };
    // 对应vue组件里的comouted属性
    var computed = {
      computedB() {
        const val = data.age + 20;
        console.log("hello, computedb is", val);
        return val;
      },
    };
    
    // 初始化data
    becomeObserver(data);
    // 初始化computed
    becomeWatcher(computed);
    
    // computed 调用, 模仿在vue 中使用computed属性
    computed.computedB();
    
    data.age = 39;
    
    
    

    说明

    我的理解:vue 的响应式就是一个发布订阅模式,发布者是 data,订阅者是 computed、watch、组件…,每次 data 改变,相应的 computed、watch 都会执行,页面也会刷新(组件执行)。

    关键点1: 发布订阅双方是谁?

    1. 发布者:data
    2. 订阅者:computed、watch、组件

    关键点2:发布者需要做两个操作,收集订阅者、通知订阅者,在哪里做这两个操作?

    根据目前我们对 vue 的了解,都知道是在 get 的时候收集订阅者,在 set 的时候通知订阅者

    关键点3:每个 comouted、watch 的属性都是一个订阅者,那个属性 a 在 get 的时候该收集哪一个呢?

    哪个订阅者调用了 a,那么 a 就该收集该订阅者。

    关键点4:属性 a 在 get 的时候,怎么知道是订阅者 b 调用了自己

    订阅者 b 在调用属性 a 的时候(也就是在执行订阅者 b 的 get 的时候)需要把自己保存在全局变量 currentWatcher 中,那么属性 a 可以通过 currentWatcher 拿到订阅者 b 了

    关键点5: 订阅者 b 是什么时候放到全局的 currentWacther 里的

    从上面也可以看出是订阅者 b 在被使用的时候(get 的时候)记录到 currentWacther 的

    关键点6:代码里的 watcherList 是干嘛的

    订阅者 b 不能 get 一次就生成一个新的 Watcher,最终导致重复执行 comouted 的某个属性。所要用 watcherList 来记录已经被变成订阅者的 comouted、watche 等的属性。

    End

    文章地址

    更多相关内容
  • 最近深入学习了Vue实现响应式的部分源码,将我的些许收获和思考记录下来,希望能对看到这篇文章的人有所帮助。有什么问题欢迎指出,大家共同进步。 什么是响应式系统 一句话概括:数据变更驱动视图更新。这样我们就...
  • 1.首先vue3是通过proxy实现响应式,先来简单描述proxy let obj = new Proxy(你要代理的对象,{ get(target,key){ target是当前对象,key是键 return target[key] }, set(target,key,newValue){ target[key...
    这一篇主要讲解VUE3的响应式,这个系列会随着博主的不断学习一直更新
    1.首先vue3是通过proxy实现的响应式,先来简单描述proxy
    let obj = new Proxy(你要代理的对象,{
    	get(target,key){
    		target是当前对象,key是键
    		return target[key]
    	},
    	set(target,key,newValue){
    		target[key]= newValue
    	}
    })
    2.编写开始
    首先我们需要存储所有需要触发的effect桶,也就是集合,
    这个集合:
     // WeakMap 由target和Map组合,所有的对象都在这里
      const bucket = new WeakMap();
     // Map由key和set组合
    
     这个桶设计完毕,然后我们写一个html,点击按钮页面的值需要改变
       <div>
            <span class="content"></span>
            <p class="description"></p>
            <button onclick="modify()">按钮</button>
      </div>
    
        //需要代理的数据
       const data = { text: 'hello world!', name: 'gq' };
      // 数据集合
      const bucket = new WeakMap();
      //所有触发的函数
      let activeEffect = undefined;
        const obj = new Proxy(data, {
    	 get(target, key) {
    	  当外界函数获取的数据的时候,将数据丢到桶里面
           track(target, key);
           return target[key];
          },
          修改的时候,再次执行函数
    	 set(target, key, newValue) {
    	     target[key] = newValue;
    	     trigger(target, key);          
    	 }
     	})
     	
    	function track(target, key){
    		如果没有收集到依赖
    		if(!activeEffect) return;
    		// 这WeakMap中有这个对象吗?
    		let  depsMap =  bucket.get(target);
    		//没有的话新增一个 由target和Map组合
    		if(!depsMap){
    			bucket.set(target,(depsMap = new Map()));
    		}
    		// 这个Map中有这个键吗?
    		let deps =  depsMap.get(key);
    		// 如果没有
    		if(!deps){
    			depsMap.set(key,deps = new Set());
    		}
    		//最后用这个set将这个键放到里面去
    		deps.add(activeEffect);
    	}
    
    	function  trigger(target, key){
    		const depsMap =  bucket.get(target);
    		if(!depsMap) return;
    		const effects = depsMap.get(key);
    		//再次执行getValue这里函数,这个时候由于有newValue
    		// 所有页面更新
    		effects && effects.forEach(fn=>fn());
    	}
    	
    	 依赖收集,目前只支持单个函数,等下次就会讲到多个
    	function effect(fn) {
    		  activeEffect = fn;
    	   	 fn();
    	 }
    	 
        function getValue() {
         	document.querySelector('.content').innerHTML = obj.text;
           document.querySelector('.description').innerHTML = obj.name; 
           }
            
           effect(getValue);
           
          修改数据
         function modify() {
                obj.text = 'hello vue3'
                obj.name = 'zs'
         }
    
    展开全文
  • Vue数据响应式

    千次阅读 2022-04-01 06:35:05
    数据响应式 主要原理:深入响应式原理 内容:深入理解options.data Vue对data做了什么? const myData = { n: 0 }; console.log(myData); new Vue({ data: myData, template: ` <div>{{n}}</div> ...

    数据响应式

    主要原理:深入响应式原理
    内容:深入理解options.data

    Vue对data做了什么?

    const myData = {
      n: 0
    };
    console.log(myData); 
    
    new Vue({
      data: myData,
      template: `
        <div>{{n}}</div>
      `
    }).$mount("#app");
    
    setTimeout(() => {
      myData.n += 10;
      console.log(myData); 
    }, 3000);
    
    

    3s后n变成10,这次没有在vm里面加,而是在外面,说明在外面也可以变更myData。

    一般我们是在vm内变更myData的

    new Vue({
      template:`
      <div>
        {{n}}
        <button @click="add"> +1 </button>
      </div>
      `,
      methods:{
        add(){
        //this.n +=10
          myData.n +=10
        }
      }
    }).$mount("#app")
    

    平常我们都用this.n,今天试试myData.n
    分别打印出myData刚声明和3s后的结果,如果第1次是n:0,那第2次就应该是n:10,看下结果:
    在这里插入图片描述

    第2次并不是n:10,那这个n:(...)是什么呢?
    我们需要先学习ES6getter、setter
    实例:要想得到姓名就要调用函数obj1.姓名()所以括号不能省,但是ES6的get语法可以实现obj1.姓名删掉括号。

    // 需求一,姓名不要括号也能得出值
    let obj1 = {
      : "高",
      : "圆圆",
      get 姓名() { //以函数的形式定义的属性
        return this.+ this.; 
      }
    };
    console.log("需求一:" + obj1.姓名);
    // 总结:getter就是不加括号的函数而已。
    
    // 需求二:姓名可以被写
    let obj2 = {
      : "高",
      : "圆圆",
      get 姓名() {
        return this.+ this.;
      },
      set 姓名(xxx){
        this.= xxx[0]
        this.= xxx.substring(1)
      }
    };
    obj2.姓名 = '高媛媛'
    console.log(`需求二:姓 ${obj2.},名 ${obj2.}`)
    // 总结:setter用= xxx触发set函数
    

    把obj2打出来
    在这里插入图片描述

    浏览器说你确实可以对姓名进行读和写,但是并不存在一个叫姓名的属性。但是你可以通过get和set设置它。

    推断1: 由此推断之前得到的n也是一个getter、setter
    get 姓名、set 姓名说明姓名:(...)不是一个真实的属性
    推断2:n:(...)并不存在属性n,而是有个get n、set n,它们来模拟对n的读写操作。

    那为什么要把n变成get n、set n呢?
    需要再学下Object.defineProperty()
    之前在使用getter、setter时,是在定义这个对象时直接使用的。在定义完一个对象之后,要想再添加新的get set时只能用Object.defineProperty()

    Object.defineProperty(obj2,'xxx',{ 
    //给obj2添加虚拟属性xxx,注意xxx是不存在的。
      var _xxx=0 //_xxx是用来存放set值的
      get(){
        return _xxx
      },
      set(value){
        _xxx=value
      }
    })
    
    let data0 = { n: 0 }
    // 需求一:用Object.defineProperty定义n
    let data1 = {}
    Object.defineProperty(data1, 'n', { //给data1添加虚拟属性n,n=0
      value: 0
    })
    console.log(`需求一:${data1.n}`) //0
    // 这语法把事情搞复杂了?非也,继续看。
    
    // 需求二:n不能小于0
    let data2 = {}
    data2._n = 0 //用_n存储n的值
    Object.defineProperty(data2, 'n', {
      get(){
        return this._n
      },
      set(value){ 
      //set可以添加判断:小于0直接return,否则将n值置为最新value
        if(value < 0) return 
        this._n = value 
      }
    })
    console.log(`需求二:${data2.n}`)
    data2.n = -1
    console.log(`需求二:${data2.n} 设置为 -1 失败`)
    data2.n = 1
    console.log(`需求二:${data2.n} 设置为 1 成功`)
    // 那如果对方直接用data2._n呢?_n可以直接设置为-1呀
    
    // 需求三:使用代理obj
    //括号里是匿名对象,直接改为没有名字的对象{n:0},data可有可无
    let data3 = proxy({ data:{n:0} }) 
    function proxy({data}){ //接收data属性
      const obj = {}
      //理论应该遍历data的所有key,这里做了简化
      Object.defineProperty(obj, 'n', { 
        get(){
          return data.n //当你取obj.n,就返回data.n
        },
        set(value){
          if(value<0)return
          data.n = value //当你设置obj.n,就设置data.n
        }
      })
      return obj
    }
    // data3 就是 obj
    console.log(`需求三:${data3.n}`)
    data3.n = -1
    console.log(`需求三:${data3.n},设置为 -1 失败`)
    data3.n = 1
    console.log(`需求三:${data3.n},设置为 1 成功`)
    //杠精说,你看下面代码
    
    // 需求四:绕过代理,通过引用
    let myData = {n:0}
    let data4 = proxy({ data:myData })//括号里是匿名对象,无法访问
    console.log(`杠精:${data4.n}`)
    myData.n = -1
    console.log(`杠精:${data4.n},设置为 -1 失败了吗!?`)
    
    // 需求五:就算用户擅自修改myData,也要拦截他
    let myData5 = {n:0}
    let data5 = proxy2({ data:myData5 }) //data5就是myData5的代理对象
    //监听data
    function proxy2({data}){ 
      let value = data.n //拿到n,记录下来
      Object.defineProperty(data, 'n', { 
        get(){ return value },
        set(newValue){
          if(newValue<0)return
          value = newValue
        }
      })
    //代理的逻辑
      const obj = {}
      Object.defineProperty(obj, 'n', {
        get(){ return data.n },
        set(value){
          if(value<0)return
          data.n = value //通过替身value就不能直接修改data.n啦
        }
      })
      return obj
    }
    console.log(`需求五:${data5.n}`)
    myData5.n = -1
    console.log(`需求五:${data5.n},设置为 -1 失败了`)
    myData5.n = 1
    console.log(`需求五:${data5.n},设置为 1 成功了`)
    
    // 这代码看着眼熟吗?
    let data5 = proxy2({ data:myData5 }) 
    let vm = new Vue({data: myData})
    现在我们可以说说 new Vue 做了什么了
    

    小结

    4.Object.defineProperty
    可以给对象添加属性value
    可以给对象添加getter/setter
    getter/setter用于对属性的读写进行监控

    啥是代理(设计模式)
    对myData对象的属性读写,全权由另一个对象vm负责
    那么vm就是myData的代理
    比如myData.n不用,偏要用vm.n来操作myData.n

    vm = new Vue({data: myData})
    一.会让vm成为myData的代理(proxy)
    二.会对myData的所有属性进行监控
    为什么要监控,为了防止myData的属性变了,vm不知道
    vm知道了又如何?
    知道属性变了可以render(data)刷新呀
    UI=render(data)

    注意: 全程这个对象n:0都没有被我扔掉过,一直在改这个对象里面的东西,不是搞出了一个新对象。我是把这个对象的n给覆盖掉了,变成get n、set n,我没有把这个对象删掉,因为如果我把这个对象删掉生成新的对象,那关联就断开了,不是同一个对象了。

    我全程都是在这个对象上面修改,修改后得到一个被修改的对象。然后在被修改的基础上,生成了一个新的代理。

    明白2件事
    1.Vue会对data后面的{n:0}进行窜改,给它加监听。
    2.会新生成一个对象,这个对象会代理篡改后的对象。

    Object.defineProperty
    可以给对象添加属性value
    可以给对象添加getter/setter
    getter/setter用于对属性的读写进行监控

    数据响应式

    若一个物体能对外界的刺激做出反应,它就是响应式的

    Vue的data是响应式
    const vm=new Vue({data:{n:0}})
    我如果修改vm.n,那么UI中的n就会响应我
    Vue 2通过Object.defineProperty来实现数据响应式
    响应式网页是啥?
    如果我改变窗口大小,网页内容会做出响应,那就是响应式网页。

    Vue的data的bug

    目前你已经知道了数据响应式,但面试不会考常态一般考变态。

    Object.defineProperty的问题
    Object.defineProperty(obj,'n',{...})
    必须要有一个’n’,才能监听 & 代理obj.n对吧
    如果前端开发者比较水,没有给n怎么办
    示例1. Vue会给出一个警告

    new Vue({
      data: {},
      template: `
        <div>{{n}}</div>
      `
    }).$mount("#app");
    

    示例2. Vue只会检查第一层属性

    new Vue({
      data: {
        obj: {
          a: 0 //obj.a会被Vue监听 & 代理
        //b:undefined  
        }
      },
      template: `
        <div>
          {{obj.b}}
          <button @click="setB">set b</button>
        </div>
      `,
      methods: {
        setB() {
          this.obj.b = 1; //页面中不会显示1
        //Vue.set(this.obj,'b',1)
        //this.$set(this.obj,'b',1) 
        }
      }
    }).$mount("#app");
    

    解决办法
    1.把k都声明好,后面不再加属性 比如b:undefined
    2.使用Vue.set或者this.$set

    $是为了防止重名
    比如Vue.set(this.obj,'b')或者this.$set(this.obj,'b',1)

    Vue.set和this.$set作用:
    新增key
    自动创建代理和监听(如果没有创建过)
    触发UI更新(但不会立刻更新)

    数组的变异方法

    data中有数组,没法提前声明所有key怎么办?
    示例

    new Vue({
      data: {
        array: ["a", "b", "c"]
      },
      template: `
        <div>
          {{array}}
          <button @click="setD">set d</button>
        </div>
      `,
      methods: {
        setD() {
        //this.array[3] = "d"; 页面中不会显示'd'
        //this.$set(this.array,3,'d') 增加下标的方式实现添加
        this.array.push('d')//尤雨溪的做法
        console.log(this.array)
        }
      }
    }).$mount("#app");
    

    在这里插入图片描述

    尤雨溪的做法:篡改数组的API
    Vue在这个对象,你以为这个是数组对象,你传给Vue之后,Vue就会篡改这个数组。它会在中间加一层原型。这个原型有7个方法,这7个方法跟以前是同名的但是代码被尤雨溪改了,会帮你set(监听,每次push都会通知Vue)。也就是说push会做2件事情:调以前的push,调完后通知Vue添加监听和代理。
    这7个API都会被Vue篡改,调用后会更新UI

    总结
    1.对象中新增的key
    Vue无法事先监听和代理
    要使用set来新增key,创建监听和代理,更新UI
    最好提前把属性都写出来,不要新增key
    但数组做不到「不新增key」
    2.数组中新增的key
    也可用set来新增key,更新UI
    不过尤雨溪篡改了7个API方便你对数组进行增删
    这7个API会自动处理监听和代理,并更新UI
    结论:数组新增key最后通过7个API
    在这里插入图片描述

    面试题
    说说你对Vue数据响应式的理解
    Vue数据响应式,使得数据更新时得到及时的渲染。vue通过Object.defineProperty()给数据对象添加value属性,设置getter和setter监控属性的读写,并使用vm对象负责数据对象的代理,当属性更新时,调用rander()更新。

    更多文章,请点击 我的博客

    展开全文
  • 第一版 结合Vue3响应式、React中断等方案,以及使用fabric.js 作为canvas渲染库为整体架构,开发的web端的ppt功能。 目前仍然在开发过程中 联系可发邮箱 或者 13282803106vx
  • 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理) 目录由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)前言1.Object.defineProperty2.Proxy2.1.Proxy的基本使用2.2.Proxy的set...

    由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)

    前言

    为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数据发生变化,依赖该数据的函数就会重新执行,达到更新的效果。那么我们如果想监听对象中的属性被设置和获取的过程,可以怎么做呢?

    1.Object.defineProperty

    在ES6之前,如果想监听对象属性的获取和设置,可以借助Object.defineProperty方法的存取属性描述符来实现,具体怎么用呢?我们来看一下。

    const obj = {
      name: 'curry',
      age: 30
    }
    
    // 1.拿到obj所有的key
    const keys = Object.keys(obj)
    
    // 2.遍历obj所有的key,并设置存取属性描述符
    keys.forEach(key => {
      let value = obj[key]
    
      Object.defineProperty(obj, key, {
        get: function() {
          console.log(`obj对象的${key}属性被访问啦!`)
          return value
        },
        set: function(newValue) {
          console.log(`obj对象的${key}属性被设置啦!`)
          value = newValue
        }
      })
    })
    
    // 设置:
    obj.name = 'kobe' // obj对象的name属性被设置啦!
    obj.age = 24 // obj对象的age属性被设置啦!
    // 访问:
    console.log(obj.name) // obj对象的name属性被访问啦!
    console.log(obj.age) // obj对象的age属性被访问啦!
    

    在Vue2.x中响应式原理实现的核心就是使用的Object.defineProperty,而在Vue3.x中响应式原理的核心被换成了Proxy,为什么要这样做呢?主要是Object.defineProperty用来监听对象属性变化,有以下缺点:

    • 首先,Object.defineProperty设计的初衷就不是为了去监听对象属性的,因为它的主要使用功能就是用来定义对象属性的;
    • 其次,Object.defineProperty在监听对象属性功能上有所缺陷,如果想监听对象新增属性、删除属性等等,它是无能为力的;

    2.Proxy

    在ES6中,新增了一个Proxy类,翻译为代理,它可用于帮助我们创建一个代理对象,之后我们可以在这个代理对象上进行许多的操作。

    2.1.Proxy的基本使用

    如果希望监听一个对象的相关操作,当Object.defineProperty不能满足我们的需求时,那么可以使用Proxy创建一个代理对象,在代理对象上,我们可以监听对原对象进行了哪些操作。下面将上面的例子用Proxy来实现,看看效果。

    基本语法:const p = new Proxy(target, handler)

    • target:需要代理的目标对象;
    • handler:定义的各种操作代理对象的行为(也称为捕获器);
    const obj = {
      name: 'curry',
      age: 30
    }
    
    // 创建obj的代理对象
    const objProxy = new Proxy(obj, {
      // 获取对象属性值的捕获器
      get: function(target, key) {
        console.log(`obj对象的${key}属性被访问啦!`)
        return target[key]
      },
      // 设置对象属性值的捕获器
      set: function(target, key, newValue) {
        console.log(`obj对象的${key}属性被设置啦!`)
        target[key] = newValue
      }
    })
    
    // 之后的操作都是拿代理对象objProxy
    // 设置:
    objProxy.name = 'kobe' // obj对象的name属性被设置啦!
    objProxy.age = 24 // obj对象的age属性被设置啦!
    // 访问:
    console.log(objProxy.name) // obj对象的name属性被访问啦!
    console.log(objProxy.age) // obj对象的age属性被访问啦!
    // 可以发现原对象obj同时发生了改变
    console.log(obj) // { name: 'kobe', age: 24 }
    

    2.2.Proxy的set和get捕获器

    在上面的例子中,其实已经使用到了set和get捕获器,而set和get捕获器是最为常用的捕获器,下面具体来看看这两个捕获器吧。

    (1)set捕获器

    set函数可接收四个参数:

    • target:目标对象(被代理对象);
    • property:将被设置的属性key;
    • value:设置的新属性值;
    • receiver:调用的代理对象;

    (2)get捕获器

    get函数可接收三个参数:

    • target:目标对象;
    • property:被获取的属性key;
    • receiver:调用的代理对象;

    2.3.Proxy的apply和construct捕获器

    上面所讲的都是对对象属性的操作进行监听,其实Proxy提供了更为强大的功能,可以帮助我们监听函数的调用方式。

    • apply:监听函数是否使用apply方式调用。
    • construct:监听函数是否使用new操作符调用。
    function fn(x, y) {
      return x + y
    }
    
    const fnProxy = new Proxy(fn, {
      /*
        target: 目标函数(fn)
        thisArg: 指定的this对象,也就是被调用时的上下文对象({ name: 'curry' })
        argumentsList: 被调用时传递的参数列表([1, 2])
      */
      apply: function(target, thisArg, argumentsList) {
        console.log('fn函数使用apply进行了调用')
        return target.apply(thisArg, argumentsList)
      },
      /*
        target: 目标函数(fn)
        argumentsList: 被调用时传递的参数列表
        newTarget: 最初被调用的构造函数(fnProxy)
      */
      construct: function(target, argumentsList, newTarget) {
        console.log('fn函数使用new进行了调用')
        return new target(...argumentsList)
      }
    })
    
    fnProxy.apply({ name: 'curry' }, [1, 2]) // fn函数使用apply进行了调用
    new fnProxy() // fn函数使用new进行了调用
    

    2.4.Proxy所有的捕获器

    除了上面提到的4种捕获器,Proxy还给我们提供了其它9种捕获器,一共是13个捕获器,下面对这13个捕获器进行简单总结,下面表格的捕获器分别对应对象上的一些操作方法。

    捕获器handler捕获对象
    get()属性读取操作
    set()属性设置操作
    has()in操作符
    deleteProperty()delete操作符
    apply()函数调用操作
    construct()new操作符
    getPrototypeOf()Object.getPrototypeOf()
    setPrototypeOf()Object.setPrototypeOf()
    isExtensible()Object.isExtensible()
    preventExtensions()Object.perventExtensions()
    getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor()
    defineProperty()Object.defineProperty()
    ownKeys()Object.getOwnPropertySymbols()

    Proxy捕获器具体用法可查阅MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

    3.Reflect

    在ES6中,还新增了一个API为Reflect,翻译为反射,为一个内置对象,一般用于搭配Proxy进行使用。

    3.1.Reflect有什么作用呢?

    可能会有人疑惑,为什么在这里提到Reflect,它具体有什么作用呢?怎么搭配Proxy进行使用呢?

    • Reflect上提供了很多操作JavaScript对象的方法,类似于Object上操作对象的方法;
    • 比如:Reflect.getPrototypeOf()类似于Object.getPrototypeOf()Reflect.defineProperty()类似于Object.defineProperty()
    • 既然Object已经提供了这些方法,为什么还提出Reflect这个API呢?
      • 这里涉及到早期ECMA规范问题,Object本是作为一个构造函数用于创建对象,然而却将这么多方法放到Object上,本就是不合适的;
      • 所以,ES6为了让Object职责单一化,新增了Reflect,将Object上这些操作对象的方法添加到Reflect上,且Reflect不能作为构造函数进行new调用

    3.2.Reflect的基本使用

    在上述Proxy中,操作对象的方法都可以换成对应的Reflect上的方法,基本使用如下:

    const obj = {
      name: 'curry',
      age: 30
    }
    
    // 创建obj的代理对象
    const objProxy = new Proxy(obj, {
      // 获取对象属性值的捕获器
      get: function(target, key) {
        console.log(`obj对象的${key}属性被访问啦!`)
        return Reflect.get(target, key)
      },
      // 设置对象属性值的捕获器
      set: function(target, key, newValue) {
        console.log(`obj对象的${key}属性被设置啦!`)
        Reflect.set(target, key, newValue)
      },
      // 删除对象属性的捕获器
      deleteProperty: function(target, key) {
        console.log(`obj对象的${key}属性被删除啦!`)
        Reflect.deleteProperty(target, key)
      }
    })
    
    // 设置:
    objProxy.name = 'kobe' // obj对象的name属性被设置啦!
    objProxy.age = 24 // obj对象的age属性被设置啦!
    // 访问:
    console.log(objProxy.name) // obj对象的name属性被访问啦!
    console.log(objProxy.age) // obj对象的age属性被访问啦!
    // 删除:
    delete objProxy.name // obj对象的name属性被删除啦!
    

    3.3.Reflect上常见的方法

    对比Object,我们来看一下Reflect上常见的操作对象的方法(静态方法):

    Reflect方法类似于
    get(target, propertyKey [, receiver])获取对象某个属性值,target[name]
    set(target, propertyKey, value [, receiver])将值分配给属性的函数,返回一个boolean
    has(target, propertyKey)判断一个对象是否存在某个属性,和in运算符功能相同
    deleteProperty(target, propertyKey)delete操作符,相当于执行delete target[name]
    apply(target, thisArgument, argumentsList)对一个函数进行调用操作,可以传入一个数组作为调用参数,Function.prototype.apply()
    construct(target, argumentsList [, newTarget])对构造函数进行new操作,new target(…args)
    getPrototypeOf(target)Object.getPrototype()
    setPrototypeOf(target, prototype)设置对象原型的函数,返回一个boolean
    isExtensible(target)Object.isExtensible()
    preventExtensions(target)Object.preventExtensions(),返回一个boolean
    getOwnPropertyDescriptor(target, propertyKey)Object.getOwnPropertyDescriptor(),如果对象中存在该属性,则返回对应属性描述符,否则返回undefined
    defineProperty(target, propertyKey, attributes)Object.defineProperty(),设置成功返回true
    ownKeys(target)返回一个包含所有自身属性(不包含继承属性)的数组,类似于Object.keys(),但是不会受enumerable影响

    具体Reflect和Object对象之间的关系和使用方法,可以参考MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

    3.4.Reflect的construct方法

    construct方法有什么作用呢?具体的应用场景是什么?这里提一个需求,就明白construct方法的作用了。

    需求:创建Person和Student两个构造函数,最终的实例对象执行的是Person中的代码,带上实例对象的类型是Student。

    construct可接收的参数:

    • target:被运行的目标构造函数(Person);
    • argumentsList:类数组对象,参数列表;
    • newTarget:作为新创建对象原型对象的constructor属性(Student);
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    
    function Student() {}
    
    const stu = Reflect.construct(Person, ['curry', 30], Student)
    console.log(stu)
    console.log(stu.__proto__ === Student.prototype)
    

    打印结果:实例对象的类型为Student,并且实例对象原型指向Student构造函数的原型。

    在这里插入图片描述

    Reflect的construct方法就可以用于类继承的实现,可在babel工具中查看ES6转ES5后的代码,就是使用的Reflect的construct方法:

    在这里插入图片描述

    4.receiver的作用

    在介绍Proxy的set和get捕获器的时候,其中有个参数叫receiver,具体什么是调用的代理对象呢?它的作用是什么?

    如果原对象(需要被代理的对象)它有自己的getter和setter服务器属性时,那么就可以通过receiver来改变里面的this。

    // 假设obj的age为私有属性,需要通过getter和setter来访问和设置
    const obj = {
      name: 'curry',
      _age: 30,
      get age() {
        return this._age
      },
      set age(newValue) {
        this._age = newValue
      }
    }
    
    const objProxy = new Proxy(obj, {
      get: function(target, key, reveiver) {
        console.log(`obj对象的${key}属性被访问啦!`)
        return Reflect.get(target, key)
      },
      set: function(target, key, newValue, reveiver) {
        console.log(`obj对象的${key}属性被设置啦!`)
        Reflect.set(target, key, newValue)
      }
    })
    
    // 设置:
    objProxy.name = 'kobe'
    objProxy.age = 24
    // 访问:
    console.log(objProxy.name)
    console.log(objProxy.age)
    

    在没有使用receiver的情况下的打印结果为:name和age属性都被访问一次和设置一次。

    在这里插入图片描述

    但是由于原对象obj中对age进行了拦截操作,我们看一下age具体的访问步骤

    • 首先,打印objProxy.age会被代理对象objProxy中的get捕获器所捕获;
    • 紧接着Reflect.get(target, key)对obj中的age进行了访问,又会被obj中的get访问器所拦截,返回this._age
    • 很显然在执行this._age的时候_age在这里是被访问了的,而这里的this指向的原对象obj;
    • 一般地,通过this._age的时候,应该也是要被代理对象的get捕获器所捕获的,那么就需要将这里的this修改成objProxy,相当于objProxy._age,在代理对象objProxy中就可以被get捕获到了;
    • receiver的作用就在这里,把原对象中this改成其代理对象,同理age被设置也是一样的,访问和设置信息都需要被打印两次;
    // 假设obj的age为私有属性,需要通过getter和setter来访问和设置
    const obj = {
      name: 'curry',
      _age: 30,
      get age() {
        return this._age
      },
      set age(newValue) {
        this._age = newValue
      }
    }
    
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        console.log(`obj对象的${key}属性被访问啦!`)
        return Reflect.get(target, key, receiver)
      },
      set: function(target, key, newValue, receiver) {
        console.log(`obj对象的${key}属性被设置啦!`)
        Reflect.set(target, key, newValue, receiver)
      }
    })
    
    // 设置:
    objProxy.name = 'kobe'
    objProxy.age = 24
    // 访问:
    console.log(objProxy.name)
    console.log(objProxy.age)
    

    再来看一下打印结果:

    在这里插入图片描述

    也可以打印receiver,在浏览器中进行查看,其实就是这里的objProxy:

    在这里插入图片描述

    5.响应式原理的实现

    5.1.什么是响应式呢?

    当某个变量值发生变化时,会自动去执行某一些代码。如下代码,当变量num发生变化时,对num有所依赖的代码可以自动执行。

    let num = 30
    
    console.log(num) // 当num方式变化时,这段代码能自动执行
    console.log(num * 30) // 当num方式变化时,这段代码能自动执行
    
    num = 1
    
    • 像上面这一种自动响应数据变化的代码机制,就称之为响应式;
    • 在开发中,一般都是监听某一个对象中属性的变化,然后自动去执行某一些代码块,而这些代码块一般都存放在一个函数中,因为函数可以方便我们再次执行这些代码,只需再次调用函数即可;

    5.2.收集响应式函数的实现

    在响应式中,需要执行的代码可能不止一行,而且也不可能一行行去执行,所以可以将这些代码放到一个函数中,当数据发生变化,自动去执行某一个函数。但是在开发中有那么多函数,怎么判断哪些函数需要响应式?哪些又不需要呢?

    • 封装一个watchFn的函数,将需要响应式的函数传入;
    • watchFn的主要职责就是将这些需要响应式的函数收集起来,存放到一个数组reactiveFns中;
    const obj = {
      name: 'curry',
      age: 30
    }
    
    // 定义一个存放响应式函数的数组
    const reactiveFns = []
    // 封装一个用于收集响应式函数的函数
    function watchFn(fn) {
      reactiveFns.push(fn)
    }
    
    watchFn(function() {
      let newName = obj.name
      console.log(newName)
      console.log('1:' + obj.name)
    })
    
    watchFn(function() {
      console.log('2:' + obj.name)
    })
    
    obj.name = 'kobe'
    // 当obj中的属性值发送变化时,遍历执行那些收集的响应式函数
    reactiveFns.forEach(fn => {
      fn()
    })
    

    在这里插入图片描述

    5.3.收集响应式函数的优化

    上面实现的收集响应式函数,目前是存放到一个数组中来保存的,而且只是对name属性的的依赖进行了收集,如果age属性也需要收集,不可能都存放到一个数组里面,而且属性值改变后,还需要通过手动去遍历调用,显而易见是很麻烦的,下面做一些优化。

    • 封装一个类,专门用于收集这些响应式函数;
    • 类中添加一个notify的方法,用于遍历调用这些响应式函数;
    • 对于不同的属性,就分别去实例化这个类,那么每个属性就可以对应一个对象,并且对象中有一个存放它的响应式数组的属性reactiveFns
    class Depend {
      constructor() {
        // 用于存放响应式函数
        this.reactiveFns = []
      }
    
      // 用户添加响应式函数
      addDependFn(fn) {
        this.reactiveFns.push(fn)
      }
    
      // 用于执行响应式函数
      notify() {
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }
    
    const obj = {
      name: 'curry',
      age: 30
    }
    
    const dep = new Depend()
    // 在watchFn中使用dep的addDependFn来收集
    function watchFn(fn) {
      dep.addDependFn(fn)
    }
    
    watchFn(function() {
      let newName = obj.name
      console.log(newName)
      console.log('1:' + obj.name)
    })
    
    watchFn(function() {
      console.log('2:' + obj.name)
    })
    
    obj.name = 'kobe'
    // name属性发生改变,直接调用notify
    dep.notify()
    

    5.4.自动监听对象的变化

    在修改对象属性值后,还是需要手动去调用其notify函数来通知响应式函数执行,其实可以做到自动监听对象属性的变化,来自动调用notify函数,这个想必就很容易了,在前面做了那么多功课,就是为了这里,不管是用Object.defineProperty还是Proxy都可以实现对象的监听,这里我使用功能更加强大的Proxy,并结合Reflect来实现。

    class Depend {
      constructor() {
        // 用于存放响应式函数
        this.reactiveFns = []
      }
    
      // 用户添加响应式函数
      addDependFn(fn) {
        this.reactiveFns.push(fn)
      }
    
      // 用于执行响应式函数
      notify() {
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }
    
    const obj = {
      name: 'curry',
      age: 30
    }
    
    const dep = new Depend()
    // 在watchFn中使用dep的addDependFn来收集
    function watchFn(fn) {
      dep.addDependFn(fn)
    }
    
    // 创建一个Proxy
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        return Reflect.get(target, key, receiver)
      },
      set: function(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // 当set捕获器捕获到属性变化时,自动去调用notify
        dep.notify()
      }
    })
    
    watchFn(function() {
      let newName = objProxy.name
      console.log(newName)
      console.log('1:' + objProxy.name)
    })
    
    watchFn(function() {
      console.log('2:' + objProxy.name)
    })
    
    objProxy.name = 'kobe'
    objProxy.name = 'klay'
    objProxy.name = 'james'
    

    注意:后面使用到的obj对象,需都换成代理对象objProxy,这样储能监听到属性值是否被设置了。

    打印结果:name属性修改了三次,对应依赖函数就执行了三次。

    在这里插入图片描述

    5.5.对象依赖的管理(数据存储结构设计)

    在上面实现响应式过程中,都是基于一个对象的一个属性,如果有多个对象,这多个对象中有不同或者相同的属性呢?我们应该这样去单独管理不同对象中每个属性所对应的依赖呢?应该要做到当某一个对象中的某一个属性发生变化时,只去执行对这个对象中这个属性有依赖的函数,下面就来讲一下怎样进行数据存储,能够达到我们的期望。

    在ES16中,给我们新提供了两个新特性,分别是Map和WeakMap,这两个类都可以用于存放数据,类似于对象,存放的是键值对,但是Map和WeakMap的key可以存放对象,而且WeakMap对对象的引用是弱引用。如果对这两个类不太熟悉,可以去看看上一篇文章:ES6-ES12简单知识点总结

    • 将不同的对象存放到WeakMap中作为key,其value存放对应的Map;
    • Map中存放对应对象的属性作为key,其value存放对应的依赖对象;
    • 依赖对象中存放有该属性对应响应式函数数组;

    如果有以下obj1和obj2两个对象,来看一下它们大致的存储形式:

    const obj1 = { name: 'curry', age: 30 }
    const obj2 = { name: 'kobe', age: 24 }
    

    在这里插入图片描述

    5.6.对象依赖管理的实现

    已经确定了怎么存储了,下面就来实现一下吧。

    • 封装一个getDepend函数,主要用于根据对象和key,来找到对应的dep;
    • 如果没有找到就先进行创建存储;
    // 1.创建一个WeakMap存储结构,存放对象
    const objWeakMap = new WeakMap()
    // 2.封装一个获取dep的函数
    function getDepend(obj, key) {
      // 2.1.根据对象,获取对应的map
      let map = objWeakMap.get(obj)
      // 如果是第一次获取这个map,那么需要先创建一个map
      if (!map) {
        map = new Map()
        // 将map存到objWeakMap中对应key上
        objWeakMap.set(obj, map)
      }
    
      // 2.2.根据对象的属性,获取对应的dep
      let dep = map.get(key)
      // 如果是第一次获取这个dep,那么需要先创建一个dep
      if (!dep) {
        dep = new Depend()
        // 将dep存到map中对应的key上
        map.set(key, dep)
      }
    
      // 2.3最终将dep返回出去
      return dep
    }
    

    在Proxy的捕获器中获取对应的dep:

    // 创建一个Proxy
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        return Reflect.get(target, key, receiver)
      },
      set: function(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // 根据当前对象target和设置的key,去获取对应的dep
        const dep = getDepend(target, key)
        console.log(dep)
        // 当set捕获器捕获到属性变化时,自动去调用notify
        dep.notify()
      }
    })
    

    在这里插入图片描述

    5.7.对象的依赖收集优化

    可以发现上面打印的结果中的响应式函数数组全部为空,是因为在前面收集响应式函数是通过watchFn来收集的,而在getDepend中并没有去收集对应的响应式函数,所以返回的dep对象里面的数组全部就为空了。如果对响应式函数,还需要通过自己一个个去收集,是不太容易的,所以可以监听响应式函数中依赖了哪一个对象属性,让Proxy的get捕获器去收集就行了。

    • 既然get需要监听到响应式函数访问了哪些属性,那么响应式函数在被添加之前肯定是要执行一次的;
    • 如何在Proxy中拿到当前需要被收集的响应式函数呢?可以借助全局变量;
    • 下面就来对watchFn进行改造;
    // 定义一个全局变量,存放当前需要收集的响应式函数
    let currentReactiveFn = null
    function watchFn(fn) {
      currentReactiveFn = fn
      // 先调用一次函数,提醒Proxy的get捕获器需要收集响应式函数了
      fn()
      // 收集完成将currentReactiveFn重置
      currentReactiveFn = null
    }
    

    Proxy中get捕获器具体需要执行的操作:

    // 创建一个Proxy
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        const dep = getDepend(target, key)
        // 拿到全局的currentReactiveFn进行添加
        dep.addDependFn(currentReactiveFn)
        return Reflect.get(target, key, receiver)
      },
      set: function(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // 根据当前对象target和设置的key,去获取对应的dep
        const dep = getDepend(target, key)
        console.log(dep)
        // 当set捕获器捕获到属性变化时,自动去调用notify
        dep.notify()
      }
    })
    

    下面测试一下看看效果:

    watchFn(function() {
      console.log('1:我依赖了name属性')
      console.log(objProxy.name)
    })
    watchFn(function() {
      console.log('2:我依赖了name属性')
      console.log(objProxy.name)
    })
    
    watchFn(function() {
      console.log('1:我依赖了age属性')
      console.log(objProxy.age)
    })
    watchFn(function() {
      console.log('2:我依赖了age属性')
      console.log(objProxy.age)
    })
    
    console.log('----------以上为初始化执行,以下为修改后执行-------------')
    
    objProxy.name = 'kobe'
    objProxy.age = 24
    

    在这里插入图片描述

    5.8.Depend类优化

    截止到上面,大部分响应式原理已经实现了,但是还存在一些小问题需要优化。

    • 优化一:既然currentReactiveFn可以在全局拿到,何不在Depend类中就对它进行收集呢。改造方法addDependFn
    • 优化二:如果一个响应式函数中多次访问了某个属性,就都会去到Proxy的get捕获器,该响应式函数会被重复收集,在调用时就会调用多次。当属性发生变化后,依赖这个属性的响应式函数被调用一次就可以了。改造reactiveFns,将数组改成Set,Set可以避免元素重复,注意添加元素使用add
    // 将currentReactiveFn放到Depend之前,方便其拿到
    let currentReactiveFn = null
    
    class Depend {
      constructor() {
        // 用于存放响应式函数
        this.reactiveFns = new Set()
      }
    
      // 用户添加响应式函数
      addDependFn() {
        // 先判断一下currentReactiveFn是否有值
        if (currentReactiveFn) {
          this.reactiveFns.add(currentReactiveFn)
        }
      }
    
      // 用于执行响应式函数
      notify() {
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }
    

    Proxy中就不用去收集响应式函数了,直接调用addDependFn即可:

    // 创建一个Proxy
    const objProxy = new Proxy(obj, {
      get: function(target, key, receiver) {
        const dep = getDepend(target, key)
        // 直接调用addDepend方法,让它去收集
        dep.addDependFn()
        return Reflect.get(target, key, receiver)
      },
      set: function(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        // 根据当前对象target和设置的key,去获取对应的dep
        const dep = getDepend(target, key)
        // 当set捕获器捕获到属性变化时,自动去调用notify
        dep.notify()
      }
    })
    

    5.9.多个对象实现响应式

    前面都只讲了一个对象实现响应式的实现,如果有多个对象需要实现可响应式呢?将Proxy封装一下,外面套一层函数即可,调用该函数,返回该对象的代理对象。

    function reactive(obj) {
      return new Proxy(obj, {
        get: function(target, key, receiver) {
          const dep = getDepend(target, key)
          // 直接调用addDepend方法,让它去收集
          dep.addDependFn()
          return Reflect.get(target, key, receiver)
        },
        set: function(target, key, newValue, receiver) {
          Reflect.set(target, key, newValue, receiver)
          // 根据当前对象target和设置的key,去获取对应的dep
          const dep = getDepend(target, key)
          // 当set捕获器捕获到属性变化时,自动去调用notify
          dep.notify()
        }
      })
    }
    

    看一下具体使用效果:

    const obj1 = { name: 'curry', age: 30 }
    const obj2 = { weight: '130', height: '180' }
    
    const obj1Proxy = reactive(obj1)
    const obj2Proxy = reactive(obj2)
    
    watchFn(function() {
      console.log('我依赖了obj1的name属性')
      console.log(obj1Proxy.name)
    })
    watchFn(function() {
      console.log('我依赖了age属性')
      console.log(obj1Proxy.age)
    })
    
    watchFn(function() {
      console.log('我依赖了obj2的weight属性')
      console.log(obj2Proxy.weight)
    })
    watchFn(function() {
      console.log('我依赖了obj2的height属性')
      console.log(obj2Proxy.height)
    })
    
    console.log('----------以上为初始化执行,以下为修改后执行-------------')
    
    obj1Proxy.name = 'kobe'
    obj1Proxy.age = 24
    obj2Proxy.weight = 100
    obj2Proxy.height = 165
    

    在这里插入图片描述

    5.10.总结整理

    通过上面9步完成了最终响应式原理的实现,下面对其进行整理一下:

    • watchFn函数:传入该函数的函数都是需要被收集为响应式函数的,对响应式函数进行初始化调用,使Proxy的get捕获器能捕获到属性访问;

      function watchFn(fn) {
        currentReactiveFn = fn
        // 先调用一次函数,提醒Proxy的get捕获器需要收集响应式函数了
        fn()
        // 收集完成将currentReactiveFn重置
        currentReactiveFn = null
      }
      
    • Depend类reactiveFns用于存放响应式函数,addDependFn方法实现对响应式函数的收集,notify方法实现当属性值变化时,去调用对应的响应式函数;

      // 将currentReactiveFn放到Depend之前,方便其拿到
      let currentReactiveFn = null
      
      class Depend {
        constructor() {
          // 用于存放响应式函数
          this.reactiveFns = new Set()
        }
      
        // 用户添加响应式函数
        addDependFn() {
          // 先判断一下currentReactiveFn是否有值
          if (currentReactiveFn) {
            this.reactiveFns.add(currentReactiveFn)
          }
        }
      
        // 用于执行响应式函数
        notify() {
          this.reactiveFns.forEach(fn => {
            fn()
          })
        }
      }
      
    • reactive函数:实现将普通对象转成代理对象,从而将其转变为可响应式对象;

      function reactive(obj) {
        return new Proxy(obj, {
          get: function(target, key, receiver) {
            const dep = getDepend(target, key)
            // 直接调用addDepend方法,让它去收集
            dep.addDependFn()
            return Reflect.get(target, key, receiver)
          },
          set: function(target, key, newValue, receiver) {
            Reflect.set(target, key, newValue, receiver)
            // 根据当前对象target和设置的key,去获取对应的dep
            const dep = getDepend(target, key)
            // 当set捕获器捕获到属性变化时,自动去调用notify
            dep.notify()
          }
        })
      }
      
    • getDepend函数:根据指定的对象和对象属性(key)去查找对应的dep对象;

      // 1.创建一个WeakMap存储结构,存放对象
      const objWeakMap = new WeakMap()
      // 2.封装一个获取dep的函数
      function getDepend(obj, key) {
        // 2.1.根据对象,获取对应的map
        let map = objWeakMap.get(obj)
        // 如果是第一次获取这个map,那么需要先创建一个map
        if (!map) {
          map = new Map()
          // 将map存到objWeakMap中对应key上
          objWeakMap.set(obj, map)
        }
      
        // 2.2.根据对象的属性,获取对应的dep
        let dep = map.get(key)
        // 如果是第一次获取这个dep,那么需要先创建一个dep
        if (!dep) {
          dep = new Depend()
          // 将dep存到map中对应的key上
          map.set(key, dep)
        }
      
        // 2.3最终将dep返回出去
        return dep
      }
      

    总结:以上通过Proxy来监听对象操作的实现响应式的方法就是Vue3响应式原理了。

    6.Vue2响应式原理的实现

    Vue3响应式原理已经实现了,那么Vue2只需要将Proxy换成Object.defineProperty就可以了。

    • 将reactive函数改一下即可;
    function reactive(obj) {
      // 1.拿到obj所有的key
      const keys = Object.keys(obj)
    
      // 2.遍历所有的keys,添加存取属性描述符
      keys.forEach(key => {
        let value = obj[key]
    
        Object.defineProperty(obj, key, {
          get: function() {
            const dep = getDepend(obj, key)
            // 直接调用addDepend方法,让它去收集
            dep.addDependFn()
            return value
          },
          set: function(newValue) {
            value = newValue
            // 根据当前对象设置的key,去获取对应的dep
            const dep = getDepend(obj, key)
            // 监听到属性变化时,自动去调用notify
            dep.notify()
          }
        })
      })
    
      // 3.将obj返回
      return obj
    }
    
    展开全文
  • Vue Vue3的响应式

    2022-04-19 10:53:53
    回顾 vue2.x 的响应式 实现原理: 。对象类型:通过object.defineProperty()对属性的读取、修改进行拦截(数据劫持) 。数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹) Object....
  • Vue 数据响应式

    2021-09-01 10:33:09
    响应式就是一个物体能对外界的刺激做出反应 Vue 的 data 就是响应式 const vm = new Vue({data:{n:0}}) 如果修改 vm.n,那么 UI 中的 n 就会响应 ...改变窗口大小,网页内容会做出响应,就是响应式网页 ...
  • 浅聊vue3响应式

    2022-02-18 14:22:37
    随着vue3的生态越来越成熟,外面也越来越多的企业应用到了vue3,当然面试也有少企业会问到vue3的一些面试题,其中比较常问到的就是vue3的响应式vue2的有什么区别?这里就简单的聊下其他的区别并且简单的聊下下vue3...
  • 一个Vue.js项目 构建设置 # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev # build for production with minification npm run build # build for production and view...
  • 深入理解Vue响应式原理

    千次阅读 多人点赞 2022-06-07 20:49:59
    本文,由浅入深,详细讲述了Vue响应式原理
  • VUE双端响应式顶部导航栏(带吸顶效果)
  • 1. Vue 2的响应式原理 Vue.js 一个核心思想是数据驱动。所谓数据驱动是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据。vue.js里面只需要改变数据,Vue.js通过Directives指令去对DOM...
  • Vue 项目前端响应式布局及框架搭建

    千次阅读 2022-05-21 14:20:48
    Vue 项目前端响应式布局及框架搭建
  • 只要记住了几个重要的指令就可以完成简单的响应式网站 一般在pc端的屏幕宽度肯定大与768 h5端小于476 所以在响应式布局的时候会定义2个参数 一个是md 一个是cols 行 : b-row 列 : b-col 总共为12份 //意思pc端...
  • Vue框架设置响应式布局

    万次阅读 多人点赞 2018-09-03 09:51:20
    回到主题,自适应响应式布局这个话题古老而新颖,从最早的不同分辨率的屏幕,到现在遍布各个生活角落的移动设备,响应式布局已经成了几乎所有互联网公司必备的条件。在我的映像中,bootstrap一直是响应式的头号选择...
  • 实际上,有很多方法可以创建响应式网站导航,甚至还有一些jQuery插件可在一秒钟内完成 。 然而,而不是应用即时的解决方案,在这篇文章中,我们将引导您如何建立从地上一个简单的导航和使用CSS3媒体查询和一个小...
  • Vue3响应式原理

    2022-04-26 20:50:13
    vue共使用了三种响应式机制:defineProperty、Proxy、value setter 1.defineProperty【Vue2】 let getDouble = n=>n*2 let obj = {} let count = 1 let double = getDouble(count) Object.defineProperty...
  • 先下载插件cnpm install px2rem-loader lib-flexible --save 在main.js中引入import ‘lib-flexible/flexible.js’ 在build中utils.js添加如下代码 ...在app.vue中改变html的font-size html{ .
  • Vue响应式编程

    千次阅读 2019-09-11 18:49:18
    Time: 20190911 ...Vue在这个过程中扮演了怎样的角色呢? Vue会时刻监视着data对象的变化。下面看一个例子: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT...
  • 依赖: nuxt bootstrap-vue core-js
  • 一套基于Vue3框架开发的实用、高端、炫酷的响应式前端网页
  • 1、NavBar.vue上代码: 里面ul>标签放置一个插槽,用来放置多个li>标签。为什么要放插槽呢??因为在一些地方我们可能还会用到像这样模板,只是那时的li>标签内容不一样了。所以插槽就很好解决了,下次使用...
  • vue响应式原理一

    2022-03-19 14:27:02
    本节课,我们将使用vue源码中相似的技能,来构建一个简单的响应式系统。这会让你更好的理解vue的设计模式,也会让你对wathcer 和dep这两个类更为熟悉。 响应式系统 第一次见到vue响应式系统的时候你也许会觉得很...
  • vue 响应式布局组件A few years ago I started to hear a lot about Web Components. I got pretty excited and then totally forgot about them as the noise settled down. It turns out there has been some good...
  • 移动端响应式尺寸单位配置/二倍图
  • vue数据的响应式原理

    千次阅读 2019-08-01 17:33:45
    一:vue数据的响应式原理 把一个普通的javascript对象传给Vue实例的data选项, Vue将遍历此对象所有的属性,并用Object.defineProperty把这些属性全部转换为getter/setter Vue内部会对数据进行劫持操作,进而追踪...
  • Vue中的响应式是指,data()中的数据一旦发生了改变,页面就会自动进行相应的改变,给予用户一个网页在随着用户操作进行“响应式”更新的感受。 所以需要解决的就是,如何监听到数据的改变呢?因为这样在能在数据改变...
  • Vue监听屏幕宽度实现响应式

    千次阅读 2019-09-10 02:17:30
    今天在做页面的时候突然想搞一个能在手机端也能完美显示的网页,这就意味着要做响应式,也就是要监听网页的宽度。 用Vue-cli搭建好项目后 在app.vue中写一个方法来监听屏幕宽度 setMobile(){ addEventListener...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,625
精华内容 4,250
关键字:

vue实现响应式网页