参考文档:https://cn.vuejs.org/v2/guide/
angularJS通过脏检查来实现,当作用域内变量发生变化时会触发脏检查,脏检查会遍历当前虚拟dom树下所有的变量检查是否变化,如果变化则更新对应的真实dom,这个检查动作会执行多次,直到没有变量改变为止。要注意的是,这个遍历动作不会区分变量是否改变,即所有的变量在每次遍历过程中都会检查,这是由于angularJS没有保存变量关联关系的缘故。
Vue则会根据变量定义梳理出变量的关联关系,当变量变化时,Vue会检索出受影响的变量,并只计算和更新受影响的变量及其对应的真实dom。这意味着Vue在初始化时可能会额外多记录一些信息,但在映射时则只需要执行一次有目的的遍历。且Vue会缓存通过变量计算生成的新变量:当变量发生变化时,不受影响的计算生成变量不会重新计算。
下面用例子来验证一下:
注意:如果后续无特殊说明,angularJS默认的版本为1.4.6,Vue默认的版本为2.6.6。
验证angularJS在发生脏检查时不受影响的计算生成变量是否会重新计算:
下面直接上代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" src="angular.js">script>
head>
<body ng-app="app">
<div ng-controller="mainController">
<input type="text" ng-model="dataA">
<br>
<span>this is dataA:{{dataA}}span>
<br>
<input type="text" ng-model="dataB">
<br>
<span>this is dataB:{{dataB}}span>
<br>
<span>this is count:{{count()}}span>
div>
body>
<script>
var appModule = angular.module("app", []);
appModule.controller("mainController", ["$scope", function ($scope) {
$scope.count = function () {
console.log("enter count");
return parseInt($scope.dataB) + 1;
};
$scope.dataA = 1;
$scope.dataB = 2;
}]);
script>
html>
上例共包含3个变量:dataA、dataB和依赖dataB的计算生成变量count()。当我们把变量dataA的值修改为5时,可以看到count()虽然不依赖dataA,但其中的信息仍然被打印了(即count()在脏检查时被重新执行了一次),这意味着在angularJS中不受影响的计算生成变量在触发脏检查时是会重新计算的(angularJS并不感知哪些计算是必须的,哪些计算是冗余的):
有意思的是,如果把count()赋予给一个变量,则不会触发重新计算:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" src="angular.js">script>
head>
<body ng-app="app">
<div ng-controller="mainController">
<input type="text" ng-model="dataA">
<br>
<span>this is dataA:{{dataA}}span>
<br>
<input type="text" ng-model="dataB">
<br>
<span>this is dataB:{{dataB}}span>
<br>
<span>this is dataC:{{dataC}}span>
div>
body>
<script>
var appModule = angular.module("app", []);
appModule.controller("mainController", ["$scope", function ($scope) {
$scope.count = function () {
console.log("enter count");
return parseInt($scope.dataB) + 1;
};
$scope.dataA = 1;
$scope.dataB = 2;
$scope.dataC = $scope.count();
}]);
script>
html>
再次把变量dataA的值修改为5,可以看到count()中的信息没有被打印,这表示count()在脏检查时没有被重新执行。这是由于脏检查时dataC映射的是初始化时$scope.count();的结果(即3),所以count()在脏检查时不会被重新执行,当然,这也意味着现在我们修改dataB也不会触发dataC改变,因为此时dataC的值是3,与dataB没有任何关系。
如果希望脏检查时执行count(),则$scope.dataC = $scope.count();这一句应调整为$scope.dataC = $scope.count;,html中this is dataC:{{dataC}}这一句应调整为this is dataC:{{dataC()}}
验证Vue在发生变量变化时不受影响的计算生成变量是否会重新计算:
下面直接上代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
head>
<body>
<div id="main">
<input type="text" v-model="dataA">
<br>
<span>this is dataA:{{dataA}}span>
<br>
<input type="text" v-model="dataB">
<br>
<span>this is dataB:{{dataB}}span>
<br>
<span>this is count:{{count}}span>
div>
body>
<script>
new Vue({
el: '#main',
data: {
dataA: 1,
dataB: 2
},
computed: {
count: function () {
console.log("enter count");
return parseInt(this.dataB) + 1;
}
}
})
script>
html>
当前环境中包含2个普通属性dataA、dataB以及1个计算属性count。当我们改变dataA的值时,可以看到count中的信息没有打印,即count没有被重新计算:
而当我们改变dataB的值时,可以看到count中的信息打印了,即count发生了重新计算:
这就印证了之前提到的“Vue会根据变量定义梳理出变量的关联关系,当变量变化时,Vue会检索出受影响的变量,并只计算和更新受影响的变量及其对应的真实dom”这一点。
综合来说,我们可以推论:当虚拟dom数量较多且每次发生变化的变量较少时,Vue的性能会优于angularJS(这个分析当前仅仅是就处理逻辑给出的推论,并未进行真实代码验证)。
用一句话来概括,就是Vue支持以对象的方式在html模版中为class和style属性赋值(可能angularJS也支持,但我没有在代码中这么写过)。
下面用一个例子来详细说明:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
head>
<body>
<div id="main">
<div v-bind:class="{'classA': flag}">div>
div>
body>
<script>
new Vue({
el: '#main',
data: {
flag: true
}
})
script>
html>
主要关注
这一句:当flag为true时为div加上classA这个class(flase则不加),从页面上也可以确认这一点:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" src="../angular.js">script>
head>
<body ng-app="app">
<div ng-controller="mainController">
<div ng-class="flag ? 'classA' : ''">div>
div>
body>
<script>
var appModule = angular.module("app", []);
appModule.controller("mainController", ["$scope", function ($scope) {
$scope.flag = true;
}]);
script>
html>
两种写法效果相同,只是Vue支持的对象定义更简洁,且避免了字符串拼接麻烦还容易出错的问题。
angularJS中只要是定义在作用域内的变量(无论是对象还是数组)变更时,都会触发脏检查。
Vue中则需要调用Vue.set来进行变更,否则Vue无法感知。
Vue可以感知部分数组的行为,可感知行为列表如下:
push pop shift unshift splice reverse sort
不可感知行为:
vm.arrayList[index] = xxx;
替代方案:
Vue.set或vm.$set,或者用Vue可感知的splice来替换。
Vue.set(数组对象, 要变更的数组对象索引, 要变更的数组对象的值)。
vm.$set(数组对象, 要变更的数组对象索引, 要变更的数组对象的值)。
即:
Vue.set(vm.arrayList, index, xxx);
注意,是否可以使用Vue.set来扩展数组对象待验证(即:是否可以用Vue.set来为一个空数组赋值——为索引为0的元素赋值,赋值后数组长度为1)。
另外Vue.set或vm.$set也可以用于增加修改对象的属性(注意:对象是允许用Vue.set来为对象新增一个属性的)。
当需要为对象的多个属性赋值时,要这样赋值(相当于为vm.userProfile赋了一个新的对象):
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
而不能这样赋值(相当于为vm.userProfile新增了多个属性):
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
猜测Vue内部无法通过Object.assign()或_.extend()对对象进行二次封装,只能在初始化时封装或通过Vue.set进行二次封装。这里所谓的封装是指Vue为了能够监听对象变更而增加的监听逻辑。
在v-for中:(item, index) in items用于遍历数组,(value, key) in object用于遍历对象,其中index对应数组的索引,key对象对象的属性名。
在ng-repeat中:item in items track by $index,$index可以为数组的索引,也可以自定义为对象的key。
当使用v-for时,如果不用v-bind指定key,则性能会得到提升(Vue会复用元素),但代价是被遍历对象的元素顺序调整时不会在页面上体现(这是性能提升付出的代价)。通常还是建议在v-for时用v-bind指定key。
注意:v-bind指定的key,不能是对象或数组,只能是字符串或数值。
比较v-for和v-if并存时的优先级(对应angularJS下的ng-repeat和ng-if):
在Vue中v-for和v-if并存时v-for的优先级更高,这意味着我们可以用v-if来选择性地显示v-for中的元素。
下面继续用一个例子来说明:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
head>
<body>
<div id="main">
<div v-for="(item, index) in items" v-if="allowIndex.indexOf(index) !== -1">
<span>{{item}}span>
div>
div>
body>
<script>
new Vue({
el: '#main',
data: {
items: ["itemA", "itemB", "itemC"],
allowIndex: [0, 2]
}
})
script>
html>
最终显示为:
可以看到v-if去掉了不想显示的数组的第二个元素。
angularJS的ng-repeat和ng-if效果完全相同:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script type="text/javascript" src="../angular.js">script>
head>
<body ng-app="app">
<div ng-controller="mainController">
<div ng-repeat="item in items" ng-if="item !== 'itemA'">
<span>{{item}}span>
div>
div>
body>
<script>
var appModule = angular.module("app", []);
appModule.controller("mainController", ["$scope", function ($scope) {
$scope.items = ["itemA", "itemB", "itemC"];
}]);
script>
html>
angularJS中注册广播监听:
$scope.$on("监听事件名" , function(){//监听回调});
angularJS中触发向下广播:
$scope.$broadcast("监听事件名");
angularJS中触发向上广播:
$scope.$emit("监听事件名", data);//如果注册的监听函数允许传参则可以后面跟上data(对应传递的参数)
angularJS中通常广播监听会在directive中注册,而广播的触发则在controller、filter、service甚至directive都有可能,但均定在js中,而Vue则提供我们在html模版中定义。
Vue在html模版中注册广播监听(在Vue组件的html模版中定义):
<li v-on:remove="callback()">li>
广播监听回调函数定义:
new Vue({
...
methods: {
callback: function () {//监听回调}
}
})
Vue触发向上广播:
<button v-on:click="$emit('remove')">Removebutton>
相比较而言,Vue的广播事件的注册和调用更灵活,当然,代价是别人阅读代码的时候得html和js切换着看才能弄明白。
在angularJS中声明了一个名称为liQing的diective(restrict为E,即为element),那么在html中声明应为:
<li-qing>li-qing>
在Vue中声明了一个名称为li-qing的component,不仅可以像angularJS那样声明,还可以以下面的方式声明:
<li is="li-qing">li>
对Vue来说,两种声明方式没有区分,但第二种方式可以避免部分浏览器遇到无法解析标签会发生不可预知错误的可能。
Vue的事件监听方式挂在html模版上居多(如上面的广播),这样做的好处是:
angularJS也支持这样的定义方式,但angularJS同时也支持类似JQuery的事件绑定方式,如:
angular.element(".className").on("click", "a.submit-buttom", function () {
//dosomething
})
就代码维护角度而言,所有代码保持同一种风格是非常必要的,笔者个人也偏向Vue的写法,操作dom和数据处理解耦是大势所趋。
最后补充一点:angularJS和Vue都可以在html中调用的方法中以$event参数来传递原始dom对象。