壹 ❀ 引
初學angularjs的同學對於$scope一定不會陌生,scope(作用域)是將view(視圖)與model(模板)關聯起來的橋梁,通過controller(控制器)對於model的數據操作,我們能輕易實現雙向綁定,這是一個簡單的例子:
<body ng-controller="myCtrl"> <input type="text" ng-model="name"> <div>{{name}}</div> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { $scope.name = '聽風是風'; });
隨着對於angularjs的深入學習,我們知道原來在angularjs版本1.2之后,數據除了綁定scope還能綁定this上,像這樣:
<body ng-controller="myCtrl as vm"> <input type="text" ng-model="vm.name"> <div>{{vm.name}}</div> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { this.name = '大家好,我是聽風是風'; });
從視覺角度來看,this好像也達到了scope的作用,那它兩真的就等同嗎?二者有什么區別呢?在第二個例子中,myCtrl as vm又是什么意思?本文就此展開探討。
貳 ❀ controller as做了什么
如果只是將數據綁定在this上,ng-controller不使用ctrl as的寫法,你會發現this上的數據在視圖中是無法被識別的。我們都知道控制器controller是一個構造函數,這里的controller as vm其實就是實例化了一個叫vm的實例而已,類似於這樣:
class myCtrl { constructor() { this.name = '聽風是風'; }; sayName() { console.log(this.name); } } let vm = new myCtrl(); vm.sayName() //聽風是風
這也是為什么非得通過vm才能訪問controller控制器中this屬性的原因;另外,as vm的vm也只是一個實例名而已,隨便你取什么名字都是OK的,並不是硬性要求。
叄 ❀ $scope與this有何區別
1.含義不同:
每個控制器controller都有一個關聯的$scope對象,控制器(構造函數)負責在其關聯的作用域($scope)上設置模型(model)屬性和行為。而視圖只能訪問在此對象和父作用域對象($scope)上定義的屬性方法。
而this就有點不同,了解Javascript的同學都知道,this指向其實是一個不太確定的東西,在你不知道this直接調用者是誰,你也無法判斷this指向誰,在angular中也是如此。
在angular中當你調用控制器的構造函數時,this就會指向控制器,比如前面我們提到ctrl as vm。而當你調用$scope上的方法時,this指向當前控制器的有效作用域。
<body ng-controller="myCtrl as vm"> <button ng-click="vm.demo1()">ctrl的this</button> <button ng-click="demo2()">scope的this</button> <button ng-click="demo3()">$scope中的this就是當前作用域</button> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { this.demo1 = function () { console.log(this); }; $scope.demo2 = function () { console.log(this); }; $scope.demo3 = function () { console.log(this === $scope); }; });
上述例子中,我們分別將方法綁定在this上與$scope上分別輸出this,以及判斷綁定在$scope時this是否等同於當前控制器的作用域,根據結果我們也驗證了前面的結論,this可能等於當前scope,也可能不等於。
當然一般情況下,我們會認為this和$scope不是同一個東西,在我們使用ctrl as vm時,其實只是在$scope中添加了一個key名為vm的對象屬性:
<body ng-controller="myCtrl as vm"> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { this.name = '聽風是風'; this.sayName = function () { console.log(1); }; console.log($scope); });
所以雖然this可能會等於$scope,但實例vm始終不會等於當前$scope,這點需要注意。另外一提的是在controller中常有使用let vm = this的做法,vm與this的關系也跟this指向有關,如下:
angular.module('myApp', []) .controller('myCtrl', function ($scope) { let vm = this vm.demo1 = function () { console.log(this === vm); // true }; $scope.demo3 = function () { console.log(this === $scope); // true console.log(vm === $scope); // false console.log(vm, this); // {demo1: ƒ} ChildScope{...} }; });
2.作用范圍不同
如果說$scope已經能解決日常開發需求,那為何還要推出新的ctrl as vm的寫法呢,其主要的一點,就是為了解決scope繼承導致作用域混亂的問題。在下面的例子中,即使子作用域沒有聲明name屬性,一樣能繼承來自父作用域的name:
<body ng-controller="parentCtrl"> <span>{{name}}</span> <div ng-controller="childCtrl"> <span>{{name}}</span> <span>{{age}}</span> </div> </body>
angular.module('myApp', []) .controller('parentCtrl', function ($scope) { $scope.name = '聽風是風'; }) .controller('childCtrl', function ($scope) { $scope.age = 26; });
在代碼結構比較復雜的情況下,你往往很難區分這個name屬性來自於哪里,而ctrl as正好解決了這個問題,下面的例子相較上方是不是看着更清晰呢:
<body ng-controller="parentCtrl as parent"> <span>{{parent.name}}</span> <div ng-controller="childCtrl as child"> <span>{{parent.name}}</span> <span>{{child.age}}</span> </div> </body>
angular.module('myApp', []) .controller('parentCtrl', function ($scope) { this.name = '聽風是風'; }) .controller('childCtrl', function ($scope) { this.age = 26; });
肆 ❀ 使用this與$scope的坑
在介紹完this與$scope的區別后,在日常開發中何時使用$scope與this也有些注意的地方,這里我列舉兩處大家可能會忽略的點:
1.directive中scope屬性為true時$scope與this表現不同
我們都知道在自定義指令directive開發中,提供了一個scope屬性,值分為false(不創建作用域),true與(創建作用域但不隔離)一個對象{}(創建隔離作用域)。
值為false的表現為,子會繼承父作用域的屬性,無論父子誰修改此屬性,雙方都會同步;
true的表現為,子會繼承父作用域的屬性,但只有修改父時子會同步,通過子修改此屬性,父並不會改變。{}表示子創建隔離作用域,即子不會繼承父任何屬性。
上面三種情況的描述其實都是值綁定在$scope上的情況,如果值綁定在this上,scope值為false或{}時,$scope.name與this.name表現一致,唯獨scope值為true時,如果你將值綁定在this上,修改子也會影響到父,直接看個例子:
<body ng-controller="myCtrl as vm"> scope: <input type="text" ng-model="name1"><br> this: <input type="text" ng-model="vm.name2"> <div echo></div> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { let vm = this $scope.name1 = '時間跳躍'; vm.name2 = '聽風是風'; }) .directive('echo', function () { return { restrict: 'EACM', scope: true, replace: true, template: '<div>scope:<input type="text" ng-model="name1"></br>this:<input type="text" ng-model="vm.name2"></div>', } })
其實仔細一想,我們本來就是在設置directive的scope繼承方式,this不符合這個規則也是情理之中,那為什么我們還能在子作用域使用父作用域中this的值呢,在子中輸出一下$scope就明白了:
在子作用域中通過$parent訪問父作用域,可以看到vm對象作為父作用域中的一條屬性存在,子修改父屬性,由於是繼承來的屬性,所以讓父也發生了改變。
2.directive中require只能訪問controller中綁在this上的屬性方法
我們知道自定義指令的require屬性能將其他指令的controller注入到自身,這樣就可以直接使用其它指令controller中定義過的方法屬性,但前提是這些方法屬性是定義在this上,而非$scope上:
<body ng-controller="myCtrl as vm"> <div echo></div> </body>
angular.module('myApp', []) .controller('myCtrl', function ($scope) {}) .directive('echo', function () { return { restrict: 'EACM', template: '<span><echo1></echo1></span>', controller: function ($scope) { $scope.name = '聽風是風'; this.sayName = function (name) { console.log('我的名字是' + name); } } } }) .directive('echo1', function () { return { restrict: 'EACM', require: '^echo', link: function (scope, ele, attr, ctrl) { console.log(ctrl); } } })
可以看到在指令echo1中link函數的第四個參數ctrl只能訪問到sayName方法,導致這個情況的原因是在angularjs源碼中,require所做的操作也是實例化了一個控制器實例,非this屬性都無法添加到實例上,這一點也是在自定義指令開發中需要注意的。
伍 ❀ 總
那么到這里,我們了解了ctrl as這種寫法的含義,並且知道了angualrjs中$scope與this的區別,this可能與$scope相等,當使用ctrl as vm時,vm只是成為了$scope中的一條屬性,所以vm與$scope永遠不相等。
我們還了解了在自定義指令開發中,為scope與this添加值時會帶來不同的影響,如果你對於angular 自定義指令開發有興趣,歡迎閱讀博主 angularjs 一篇文章看懂自定義指令directive 這篇文章。
另外我看了一眼文章配圖中標志性的摩托車,才反應過來這是電影阿基拉的同人作品,里面的兩個人物分別是男主金田正太郎與男二女友香織(男二帽子戴好..),這輛摩托在電影頭號玩家中也有作為彩蛋出現,那么到這里,本文結束。
參考
'this' vs $scope in AngularJS controllers