zoukankan      html  css  js  c++  java
  • Vue官方文档和源码研读

    2021.08.31开始 运行时的源码在vue/dist/vue.runtime.esm.js里面,不过有些非函数的定义,打印不出来(看看vue源码断点怎么打比较合适)。看官方文档时候,直接百度中文的那段文字,经常搜不出来,可以先根据官方文档或自己定位到对应的代码段,然后搜索代码段的变量名,就能搜到了 

    1.官方文档 模板语法-插值-使用javascript表达式 最后,有这么一句话:模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量。

    开始我以为是vue.prototype.$aa = 1,不能访问这个$aa。看了源码之后,才明白。全局对象指的是Array、Object这类的js关键字,当然也可以自己定义。比如我写个js文件 export 1,然后在vue文件中import aaa from ./js文件,在模板语法中使用{{aaa}},就会报错。因为模板表达式的沙盒里没有aaa这个变量。想使用的话可以把它挂载到vueprototype上或者直接赋值给当前vue实例的data中的一个属性

    function makeMap (
      str,
      expectsLowerCase
    ) {
      var map = Object.create(null);
      var list = str.split(',');
      for (var i = 0; i < list.length; i++) {
        map[list[i]] = true;
      }
      return expectsLowerCase
        ? function (val) { return map[val.toLowerCase()]; }
        : function (val) { return map[val]; }
    }
    var allowedGlobals = makeMap(  //
        'Infinity,undefined,NaN,isFinite,isNaN,' +
        'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
        'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' +
        'require' // for Webpack/Browserify
      );
    var hasHandler = {
        has: function has (target, key) { //target就是当前vue实例,key就是要解析的值
          var has = key in target; //对象的in方法,只要key在target的原型链上,就会返回true,所以设置vue.prototype.$aa = 1之后,使用$aa是没问题的
    var isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); //这里判断的是全局变量或者以_开头的属性,这么做是由于渲染函数中会包含很多以 _开头的内部方法,如渲染函数里遇到的 _c_v 等等 if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } };

    最后的判断!has我们可以理解为你访问了一个没有定义在实例对象上(或原型链上)的属性,所以这个时候提示错误信息是合理,但是即便!has成立也不一定要提示错误信息,因为必须要满足!isAllowed,也就是说当你访问了一个虽然不在实例对象上(或原型链上)的属性,但如果你访问的是全局对象那么也是被允许的。这样我们就可以在模板中使用全局对象了

    2.在谷歌浏览器的控制台,打印一些数据时。发现同样的数据,显示的格式经常是不一样的,有时是data:{...},有时是data:{a:1},有时是data:{__ob__:Observer}。

    经过我的验证,发现没被挂载到vue实例上的(被浅拷贝的也算),会直接显示数据data:{a:1},被挂载到vue实例上并且对象长度小于等于4的,会显示data:{__ob__:Observer},其余显示的都是data:{...}

    拓展:给vue data属性的对象添加属性时,有以下几种情况。

    const a = {content:{a:1,b:2,c:3,d:4,e:5}}
    情况一 
    this.form = a;
    这样相当于将a的地址赋值给this.form,这时不管是打印a 还是this.form都是一样的,不会去判断对象长度是多少,点开查看(调用getter)之前都是显示{...}
    情况二
    this.form = {...a}或者this.form = Object.assign({},this.form,a).
    这时this.form===a是false,他俩这时的引用类型属性是共用一个地址,但是基本类型的值是互不干扰的。
    打印a,会判断长度,小于等于4的显示{__ob__:Observer},大于4的显示{...};
    打印this.form,不会判断长度,统一显示{...}
    情况三
    this.form = JSON.parse(JSON.stringify(a)) //深拷贝
    这时打印a,会直接显示所有的值,不会有{...}和{__ob__:Observer},因为它没有被vue影响
    打印this.form,不会判断长度,统一显示{...}
    但是,以上三种情况,当我直接打印this.form.a时,都会显示一个{__ob__:Observer}。

     具体原因要等以后看源码再了解了(2021.09.07)

      

    3.当你设置 vm.someData = 'new value',该组件不会立即重新渲染(这里指的是html里的dom元素 不会重新渲染)。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

    因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

    methods: {
      updateMessage: async function () {
        this.message = '已更新'
        console.log(this.$el.textContent) // => '未更新'
        await this.$nextTick()
        console.log(this.$el.textContent) // => '已更新'
      }
    }
    

      当我使用vue检测不到的方法来增减对象和数组时,可能是数据驱动不了视图,也可能是数据驱动了视图,但通过this.$refs.textContent是获取不到更新后的dom的,这时候就要用$nextTick了。(具体原因要等源码的研究了 2021.09.07)

    4.VUE事件修饰符 https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6

    注意: 特殊的系统修饰键有ctrl、alt 、shift、 meta(window键),例如 @key.alt.67  //alt+c   @click.ctrl   //ctrl+click

    修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 

    的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

    2.5.0新增的exact修饰符 允许你控制由精确的系统修饰符组合触发的事件。

    <!-- 即使 Alt 或 Shift 被一同按下时也会触发。只监听ctrl,不管同时有其他几个按键在按 -->  

    <button v-on:click.ctrl="onClick">A</button>

    <!-- 有且只有 Ctrl 被按下的时候才触发 -->

    <button v-on:click.ctrl.exact="onCtrlClick">A</button>

    <!-- 没有任何系统修饰符被按下的时候才触发 -->

    <button v-on:click.exact="onClick">A</button>

    鼠标修饰符有 .left .right .middle 

    5.复选框<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">

    // 当选中时
    vm.toggle === 'yes'
    // 当没有选中时
    vm.toggle === 'no'

    这里的 true-value 和 false-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。

    翻译:比如有这样的页面

    <input type="checkbox" v-model="picked" :true-value="value1" :false-value="value2">
            <label> 复选框 </label>
            <p> {{picked}} </p>
            <p>{{value1}} </p>
            <p>{{value2}} </p>
    data: {
                  picked:false,
                  value1:123,
                  value2:345
                }

     那么刚进页面时,picked的值就是false,这个value1和value2的值是不会影响picked的值的。

     但是一旦对这个chekcbox做了勾选或取消勾选的操作,这个picked的值就会变成123或者456。

    拓展:如果换成radio的话,刚进页面是一样的,但是一旦做了操作,picked的值就会变成null。

    6.阅读vuex官方文档时,对于rootState一直无法理解,官方文档对于它的定义是 根节点状态。但是有rootState.count这种用法,而我打印的时候,rootState底下就是几个module对象,没有.count这一层级。

    原因:参考了一些文档,发现.count确实是根节点的状态,并且想获取这个层级的值,就要在new Vuex.Store()时给根节点赋一个state对象。像这样

    const store = new Vuex.Store({
      state: {
        count: 1,
      },
      modules: {
        a: moduleA,
        b: moduleB,
      },
    })
    

      这时候在moduleA的action或者getters里面打印rootState,里面就有count和a、b两个modules里面的state对象。

    7.Vue 深入响应式原理说明:Vue 不能检测数组和对象的一些变化,也就是说数据更新时视图不会更新。但是实际使用中发现还是更新了。

    原因: 查了一些文档,没发现回答。自己又测试了一下,发现官方文档写的是没有问题的。操作的时候,如果只有arr.length=1或者arr[0] = 1这种操作时,不会触发视图更新。但是如果有其他的能被vue检测到的数据更新(this.count='xx),就会顺带把vue检测不到的数据变动也一起更新了,这样就实现了类似arr.length = 1也被vue检测到的效果。但是这样只更新了视图,并没有给这个新数据加上getter和setter,在控制台打印可以看到,有getter和setter的会默认不显示,没有的会直接显示出来。

    坏处:目前能想到的坏处,就是虽然视图更新了,但是vue的watch是监听不到数据变化的。因为这个数据没有setter,watch是靠监听setter来实现的。

        export default {
          data() {
            return {
              items: ['a', 'b', 'c'],
       count:1 } }, methods: { hah() { this.items.length = 2 // 不是响应性的,没有被vue检测到
          //this.count = 'xx' //是响应性的,会触发vue更新 setTimeout(() => { this.items.shift() },3000) }, }, watch: { items(new,old) { console.log(new,old) // 1,3 ,vue只能监听到一开始的长度3和shift之后的长度1 } } }

    源码解析:简单打断点看了一下,应该是this.count触发了它自己的set和watch.然后又触发了vue的$nextTick和render方法,vue就顺带把当前实例的数据都更新到视图上了。但是并没有给新属性加上setter和getter,也没有触发其他属性的watch。

    经验:以后对于vue检测不到的变动,还是都用this.$set来操作吧。因为不可能每次都正好有别的属性在更新,万一一开始有,后面又拿掉了那个更新的属性操作,vue就检测不到更新了,这是在埋bug.

    8.vue官方文档说到父组件引用子组件时的标签名和props在标签上的使用,是这样的

    使用 kebab-case    Vue.component('my-component-name', { /* ... */ })

    当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

    使用PascalCase     Vue.component('MyComponentName', { /* ... */ })

    当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的

    重点在最后一句,我在.vue文件中直接使用<MyComponent userName="a"/>是可用的,这是为啥?

    原因:vue官方文档经常提到的字符串模板和非字符串模板,其实是这样定义的

    1.字符串模板就是写在vue中的template中定义的模板,如.vue的单文件组件模板和定义组件时template属性值的模板。字符串模板不会在页面初始化参与页面的渲染,会被vue进行解析编译之后再被浏览器渲染,所以不受限于html结构和标签的命名。

    Vue.component('MyComponentA', {
        template: '<div MyId="123"><MyComponentB>hello, world</MyComponentB></div>'
    })
    
    <div id="app">
        <MyComponentA></MyComponentA>
    </div>

    2.dom模板(或非字符串模板、Html模板)就是写在html文件中,一打开就会被浏览器进行解析渲染的,所以要遵循html结构和标签的命名,否则浏览器不解析也就不能获取内容了。

    下面的例子不会被正确渲染, 会被解析成mycomponent,但是注册的vue的组件是MyComponent,因此无法渲染。

    <!DOCTYPE <html>
        <head>
            <meta charset="utf-8">
            <title>Vue Component</title>
        </head>
        <body>
            <div id="app"> 
                Hello Vue
                <MyComponent></MyComponent>
            </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
        <script >
            //全局注册
            Vue.component('MyComponent', {
                template: '<div>组件类容</div>'
            });
            new Vue ({
                el: '#app'
            });
        </script>
        </body>
    </html>
    

      

    所以,下面的例子就可以正常显示了:

    <!DOCTYPE <html>
        <head>
            <meta charset="utf-8">
            <title>Vue Component</title>
        </head>
        <body>
            <div id="app"> 
                Hello Vue
                <my-component></my-component>
            </div>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
        <script >
            //全局注册
            Vue.component('my-component', {
                template: '<div>组件类容</div>'
            });
            new Vue ({
                el: '#app'
            });
        </script>
        </body>
    </html>

    因为html对大小写不敏感,所以在DOM模板中使用组件必须使用kebab-case命名法(短横线命名)。
    因此,对于组件名称的命名,可参考如下实现:

    /*-- 在单文件组件、JSX和字符串模板中 --*/
    <MyComponent/>
    /*-- 在 DOM 模板中 --*/
    <my-component></my-component>
    或者
    /*-- 在所有地方 --*/
    <my-component></my-component>
    

      

    9,vuex官方文档说只能通过mutation来修改state,而实际使用中,发现this.$store.state.a = 132是可以生效的,并且其他组件也都可以访问,只不过它没有被设置set和get,想要这两个用this.$set就可以了。不过在vuex的严格模式下会报错。

    开启严格模式,仅需在创建 store 的时候传入 strict: true
    const store = new Vuex.Store({ // ... strict: true 或者
     strict: process.env.NODE_ENV !== 'production'
    })

    在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

    总结:为了防止vuex的数据改变来源难以追踪,使用中统一用mutation来改变state。也可以在开发过程中开启vuex严格模式(生产环境中必须关闭)。

  • 相关阅读:
    【数据结构】线性表&&顺序表详解和代码实例
    【智能算法】超详细的遗传算法(Genetic Algorithm)解析和TSP求解代码详解
    【智能算法】用模拟退火(SA, Simulated Annealing)算法解决旅行商问题 (TSP, Traveling Salesman Problem)
    【智能算法】迭代局部搜索(Iterated Local Search, ILS)详解
    10. js时间格式转换
    2. 解决svn working copy locked问题
    1. easyui tree 初始化的两种方式
    10. js截取最后一个斜杠后面的字符串
    2. apache整合tomcat部署集群
    1. apache如何启动
  • 原文地址:https://www.cnblogs.com/xuzhenlei/p/15212924.html
Copyright © 2011-2022 走看看