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