angularjs $scope與this的區別,controller as vm有何含義?


 壹 ❀ 引

初學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

Angular路由中的controller常常有as vm, 有何作用?

controller as引入的意義和方法?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM