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严格模式(生产环境中必须关闭)。

  • 相关阅读:
    缓慢变化纬的解决方法
    行转列且有序
    异常处理
    继承
    js的隐式转化
    初步了解微任务
    axios中断请求AbortController
    Vue解决V-HTML指令潜在的XSS攻击('v-html' directive can lead to XSS attack vue/no-v-html)
    axios下载后台传过来的流文件并设置下载文件名(如excel)
    axios异步获取文件流数据
  • 原文地址:https://www.cnblogs.com/xuzhenlei/p/15212924.html
Copyright © 2011-2022 走看看