官术网_书友最值得收藏!

5.2 AngularJS作用域繼承

5.2.1 JavaScript對象繼承機制

學習AngularJS作用域繼承之前,我們需要先了解一下JavaScript對象的繼承機制。JavaScript語言遵循ECMAScript規范,在ECMAScript 6規范之前,JavaScript語言并沒有類的概念,也不具備面向對象多態的特性,所以嚴格地講JavaScript并不是一門面向對象語言,而是一門基于對象的語言。

JavaScript構造對象通常有兩種方式。第一種方式是通過字面量創建,形式如下:

        var obj = {
            name:'jane',
            age:32
        };

另一種方式是通過對象的構造方法來創建,需要用到function關鍵字。JavaScript語言中的方法可以作為構造方法創建對象,比較容易讓人產生疑惑。例如,我們使用下面這段代碼定義一個構造方法:

        function Person(name, age){
            this.name = name;
            this.age = age;
            this.eat = function() {

            console.log('eat..');
            }
        }

但是我們把它作為普通方法調用就不會有問題。當使用構造方法創建對象時需要用到JavaScript的new關鍵字,使用方法如下:

        var person = new Person('Jane',32);

在實際項目中,當我們明確地使用function關鍵字定義一個構造方法時,構造方法名稱通常采用帕斯卡命名法,即每個單詞首字母大寫(例如:LoginController);而使用function定義一個普通方法時,方法名稱可采用駝峰命名法,駝峰命名法跟帕斯卡命名法相似,只是首字母為小寫(例如:checkUser),看上去像駝峰,因此而得名。

JavaScript語言為我們提供了幾個內置的構造方法,例如Object、Array 、String、Boolean等。我們可以直接使用這些構造方法創建對象。

了解了JavaScript對象的創建方法后,我們再來看看JavaScript的對象繼承機制。JavaScript語言有以下3種方式實現對象繼承。

1.構造方法原型鏈繼承

每個JavaScript構造方法都有一個名稱為prototype的屬性,可以指向另一個對象。當我們訪問對象屬性時(例如obj.name), JavaScript引擎會從對象的所有屬性中查找該屬性,如果找到就返回屬性值,如果沒有找到就繼續從prototype屬性指向的對象屬性中查找,如果仍然沒有找到,則會沿著prototype鏈一直查找下去,直到prototype鏈結束或找到對象為止。接下來我們看一個原型鏈繼承的案例,代碼如下:

代碼清單:ch05\ch05_02.html

        <!doctype html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>ch05_02</title>
        </head>
        <body>
            <script type="text/javascript">
                function Animal(){
                    this.eat = function(){
                        console.log('eat...');
                    }
                }
                function Cat(age){
                    this.age = age;
                }
                Cat.prototype = new Animal();
                var cat = new Cat(10);
                console.log("cat.age=" + cat.age);
                cat.eat();
            </script>
        </body>
        </html>

如上面的代碼所示,首先定義兩個構造方法Animal和Cat,把Cat的prototype屬性指向一個Animal對象:

        Cat.prototype = new Animal();

接下來通過new關鍵字創建一個Cat對象:

        var cat = new Cat(10);

在瀏覽器中運行該案例,控制臺輸出:

        cat.age=10
        eat...

Cat構造方法中并沒有定義eat()方法,而我們通過Cat實例調用eat()方法輸出了內容,說明Cat對象通過prototype屬性繼承了Animal對象的eat()方法。

2.使用apply、call方法實現繼承

由于JavaScript構造方法的apply()、call()方法可以改變對象構造中“this”的上下文環境,使特定的對象實例具有對象構造中所定義的屬性、方法,因此我們可以使用apply()、call()方法實現JavaScript對象的繼承,例如下面的案例:

代碼清單:ch05\ch05_03.html

        <!doctype html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>ch05_03</title>
        </head>
        <body>
            <script type="text/javascript">
                function Person(name, age){
                    this.name = name;
                    this.age = age;
                }
                function Student(name, age, love){
                    //Person.apply(this, [name, age]);
                    Person.call(this, name, age);
                    this.love = love;
                }
                var student = new Student('jane',23, 'pingpong');
                console.log("student.name=" + student.name);
                console.log("student.age=" + student.age);
                console.log("student.love=" + student.love);
            </script>
        </body>
        </html>

如上面的代碼所示,在本例中我們首先定義了Person構造方法,接著在Student構造方法中調用Person.call()方法實現了繼承,然后通過new關鍵字創建一個Student對象,在控制臺中輸出Student對象的屬性值。在瀏覽器中預覽ch05_03.html頁面,打開開發人員工具,控制臺輸出內容如下:

        student.name=jane
        student.age=23
        student.love=pingpong

說明Student對象繼承了Person對象的name和age屬性。apply()方法和call()方法的不同之處在于apply()方法只接收兩個參數,第二個參數是一個數組,而call()方法可以接收多個參數。

3.對象實例間繼承

在JavaScript語言中,對象可以繼承另外一個對象的屬性,例如下面的案例:

代碼清單:ch05\ch05_04.html

        <!doctype html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>ch05_04</title>
        </head>
        <body>
            <script type="text/javascript">
                function Person(name, age){
                    this.name = name;
                    this.age = age;
                }
                var person = new Person('jane',28);
                var student = Object.create(person);
                student.love = "pingpong";
                console.log(Object.getPrototypeOf(student));
                console.log("student.name→"+student.name);
                console.log("student.age→"+student.age);
                console.log("student.love→"+student.love);
            </script>
        </body>
        </html>

如上面的代碼所示,在本例中我們用到了Object.create()方法,它的作用是以一個對象為原型創建另外一個對象,創建的對象和原對象具有相同的屬性,我們可以通過Object. getPrototypeOf()方法獲取新對象的原型。

在瀏覽器中運行ch05_04.html頁面,打開開發人員工具,控制臺輸出內容如下:

        Person
        student.name→jane
        student.age→28
        student.love→pingpong

結合源代碼,從輸出的日志信息可以看出,student對象繼承了Person對象的name和age屬性,調用Object.getPrototypeOf()方法獲取student對象的原型依然為Person。

本節中筆者對JavaScript對象的繼承方式進行了比較全面的學習,下一小節我們一起學習AngularJS如何使用原型方式實現作用域對象的繼承。

5.2.2 AngularJS作用域對象原型繼承

上一小節介紹了JavaScript語言中對象繼承的3種方式,其中AngularJS作用域對象繼承采用第一種方式,即構造方法原型鏈繼承。AngularJS作用域構造方法中提供了一個$new()成員方法,用于創建子作用域。AngularJS框架創建子作用域的過程大致如下:

        var parent = $rootScope;
        var child = parent.$new();

我們不妨了解一下$new()方法的定義,內容如下,有興趣的讀者可以參考AngularJS源碼。

        Scope.prototype = {
              constructor: Scope,
              $new: function(isolate, parent){
                var child;
                parent = parent || this;
                if(isolate){
                  child = new Scope();
                  child.$root = this.$root;
                } else {
                  // Only create a child scope class if somebody asks for one,
                  // but cache it to allow the VM to optimize lookups.
                  if(! this.$$ChildScope){
                    this.$$ChildScope = createChildScopeClass(this);
                  }
                  child = new this.$$ChildScope();
                }
                child.$parent = parent;
                child.$$prevSibling = parent.$$childTail;
                if(parent.$$childHead){
                  parent.$$childTail.$$nextSibling = child;
                  parent.$$childTail = child;
                } else {
                  parent.$$childHead = parent.$$childTail = child;
                }

                // When the new scope is not isolated or we inherit from `this`, and
                // the parent scope is destroyed, the property `$$destroyed` is
                // inherited prototypically. In all other cases, this property
                // needs to be set when the parent scope is destroyed.
                // The listener needs to be added after the parent is set
                if(isolate || parent ! = this)child.$on('$destroy',
                destroyChildScope);

                return child;
              },

              ......

        }

上面的代碼為AngularJS1.5.5版本中$new()方法的定義,如上面黑體代碼所示。其中,this.$$ChildScope為子作用域構造方法,由createChildScopeClass()方法調用返回。下面是createChildScopeClass()方法的定義:

        function createChildScopeClass(parent){
            function ChildScope() {
              this.$$watchers = this.$$nextSibling =
                    this.$$childHead = this.$$childTail = null;
              this.$$listeners = {};
              this.$$listenerCount = {};
              this.$$watchersCount = 0;
              this.$id = nextUid();
              this.$$ChildScope = null;
            }
            ChildScope.prototype = parent;
            return ChildScope;
        }

createChildScopeClass()方法中定義了ChildScope構造方法,ChildScope即為子作用域的構造方法,接著指定ChildScope的prototype屬性為parent, parent即為父作用域對象,這樣子作用域就繼承了父作用域的所有屬性。

上面我們了解了AngularJS作用域繼承機制,所有作用域對象都是$rootScope作用域的子作用域。還有一種情況需要我們考慮,即在AngularJS控制器中可以嵌套另外一個控制器,例如:

        <div ng-app>
            <div ng-controller="OuterController">

            <div ng-controller="InnerController">

            </div>
            </div>
        </div>

在上面的代碼片段中,ng-controller指令范圍內嵌套了另外一個ng-controller指令,這種情況下AngularJS是如何處理作用域對象繼承的呢?過程如下:

AngularJS框架遍歷DOM元素,查找到ng-app指令時啟動應用,創建$rootScope作用域。

然后AngularJS框架查找到第一個ng-controller指令,指向名稱為OuterController的控制器,并調用$rootScope.$new()方法,以原型繼承的方式創建$rootScope作用域的子作用域對象(記為$scope1)。當OuterController構造方法接收一個名稱為$scope的參數時,AngularJS實例化控制器對象時會把$scope1對象注入控制器對象中。

接下來AngularJS繼續遍歷DOM元素,遇到第二個嵌套的ng-controller指令時調用$scope1. new()方法,以$scope1為原型創建子作用域(記為$scope2, $scope2作用域對象能夠訪問$scope1作用域對象的所有屬性)。

除了ng-app、ng-controller指令會創建作用域對象外,AngularJS指令也可能會產生子作用域,在后面的章節中我們會接觸到。本節內容就介紹這么多,下節我們開始學習AngularJS作用域監視機制。

主站蜘蛛池模板: 白玉县| 正安县| 邯郸县| 镇赉县| 甘泉县| 滦平县| 京山县| 建水县| 合水县| 克什克腾旗| 五指山市| 囊谦县| 孟村| 大厂| 神木县| 福州市| 阳江市| 长葛市| 安化县| 利川市| 介休市| 五寨县| 正蓝旗| 中宁县| 浪卡子县| 武川县| 石首市| 莒南县| 克东县| 连山| 射洪县| 邓州市| 广宗县| 德庆县| 繁昌县| 郧西县| 务川| 龙游县| 宜州市| 辽阳市| 乐昌市|