knockout.js的學習筆記2


本節換一種方式解讀,把我消化過的東西反芻出來可能這樣大家容易理解些,knockout.js大量使用閉包,非常難讀。

我們從viewModel看起:

          function MyViewModel() {
                this.firstName = $.observable('Planet');
                this.lastName = $.observable('Earth');

                this.fullName = $.computed({
                    getter: function () {
                        return this.firstName() + " " + this.lastName();
                    },
                    setter: function (value) {
                        var lastSpacePos = value.lastIndexOf(" ");
                        if (lastSpacePos > 0) { // Ignore values with no space character
                            this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
                            this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
                        }
                    },
                    scope: this
                });
            }
            var a = new MyViewModel();
            a.fullName("xxx yyy")

這里包含兩種observable,沒有依賴的與有依賴的,有依賴的通過沒有依賴的計算出來,因此叫做computed!

但不管怎么樣,它們都是返回一個函數,我們通過如下代碼就可以模擬它們了:

            //注:這里會用到mass Framework的種子模塊的API https://github.com/RubyLouvre/mass-Framework/blob/master/src/mass.js
            //observable的傳參必須是基本類型
            var validValueType = $.oneObject("Null,NaN,Undefined,Boolean,Number,String");
           $.observable = function(value){
                var v = value;//將上一次的傳參保存到v中,ret與它構成閉包
                function ret(neo){
                    if(arguments.length){ //setter
                        if(!validValueType[$.type(neo)]){
                            $.error("arguments must be primitive type!")
                            return ret
                        }
                        if(v !== neo ){
                            v = neo;
                        }
                        return ret;
                    }else{                //getter
                        return v;
                    }
                }
                value = validValueType[$.type(value)] ? value : void 0;
                ret(arguments[0]);//必須先執行一次
                return ret
            }

            $.computed = function(obj, scope){//為一個惰性函數,會重寫自身
                //computed是由多個$.observable組成
                var getter, setter
                if(typeof obj == "function"){
                    getter = obj
                }else if(obj && typeof obj == "object"){
                    getter = obj.getter;
                    setter = obj.setter;
                    scope  = obj.scope;
                }
                var v
                var ret = function(neo){
                    if(arguments.length ){
                        if(typeof setter == "function"){//setter不一定存在的
                            if(!validValueType[$.type(neo)]){
                                $.error("arguments must be primitive type!")
                                return ret
                            }
                            if(v !== neo ){
                                setter.call(scope, neo);
                                v = neo;
                            }
                        }
                        return ret;
                    }else{
                        v = getter.call(scope);
                        return v;
                    }
                }
                ret(); //必須先執行一次
                return ret;
            }

因此當我們執行new MyViewModel(),就會依次執行$.observable, $.observable, $.computed, $.computed中的參數的getter, getter再調用兩個observable。

問題來了,當我們調用computed時,總會通知其依賴(即firstName ,lastName)進行更新,但firstName 發生改變時沒有手段通知fullName 進行更新。ko把這邏輯寫在dependencyDetection模塊中。我簡化如下:

            $.dependencyDetection = (function () {
                var _frames = [];
                return {
                    begin: function (ret) {
                        _frames.push(ret);
                    },
                    end: function () {
                        _frames.pop();
                    },
                    collect: function (self) {
                        if (_frames.length > 0) {
                            if(!self.list)
                                self.list = [];
                            var fn = _frames[_frames.length - 1];
                            if ( self.list.indexOf( fn ) >= 0)
                                return;
                            self.list.push(fn);
                        }
                    }
                };
            })();

我們把它加入到 $.computed 與 $.observable中,再添加一個發布更新函數valueWillMutate

            var validValueType = $.oneObject("Null,NaN,Undefined,Boolean,Number,String")
            $.dependencyDetection = (function () {
                var _frames = [];
                return {
                    begin: function (ret) {
                        _frames.push(ret);
                    },
                    end: function () {
                        _frames.pop();
                    },
                    collect: function (self) {
                        if (_frames.length > 0) {
                            if(!self.list)
                                self.list = [];
                            var fn = _frames[_frames.length - 1];
                            if ( self.list.indexOf( fn ) >= 0)
                                return;
                            self.list.push(fn);
                        }
                    }
                };
            })();
            $.valueWillMutate = function(observable){
                var list = observable.list
                if($.type(list,"Array")){
                    for(var i = 0, el; el = list[i++];){
                        el();
                    }
                }
            }
            $.observable = function(value){
                var v = value;//將上一次的傳參保存到v中,ret與它構成閉包
                function ret(neo){
                    if(arguments.length){ //setter
                        if(!validValueType[$.type(neo)]){
                            $.error("arguments must be primitive type!")
                            return ret
                        }
                        if(v !== neo ){
                            v = neo;
                            $.valueWillMutate(ret);//向依賴者發送通知
                        }
                        return ret;
                    }else{                //getter
                        $.dependencyDetection.collect(ret);//收集被依賴者
                        return v;
                    }
                }
                value = validValueType[$.type(value)] ? value : void 0;
                ret(arguments[0]);//必須先執行一次
                return ret
            }

            $.computed = function(obj, scope){//為一個惰性函數,會重寫自身
                //computed是由多個$.observable組成
                var getter, setter
                if(typeof obj == "function"){
                    getter = obj
                }else if(obj && typeof obj == "object"){
                    getter = obj.getter;
                    setter = obj.setter;
                    scope  = obj.scope;
                }
                var v
                var ret = function(neo){
                    if(arguments.length ){
                        if(typeof setter == "function"){//setter不一定存在的
                            if(!validValueType[$.type(neo)]){
                                $.error("arguments must be primitive type!")
                                return ret
                            }
                            if(v !== neo ){
                                setter.call(scope, neo);
                                v = neo;
                            }
                        }
                        return ret;
                    }else{
                        $.dependencyDetection.begin(ret);//讓其依賴知道自己的存在
                        v = getter.call(scope);
                        $.dependencyDetection.end();
                        return v;
                    }
                }
                ret(); //必須先執行一次
                return ret;
            }
            function MyViewModel() {
                this.firstName = $.observable('Planet');
                this.lastName = $.observable('Earth');

                this.fullName = $.computed({
                    getter: function () {
                        return this.firstName() + " " + this.lastName();
                    },
                    setter: function (value) {
                        var lastSpacePos = value.lastIndexOf(" ");
                        if (lastSpacePos > 0) { // Ignore values with no space character
                            this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
                            this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
                        }
                    },
                    scope: this
                });
                this.card = $.computed(function(){
                    return this.fullName() +" 屌絲"
                },this)
            }
            var a = new MyViewModel();
            //============測試代碼============
            $.log(a.firstName())//Planet
            $.log(a.lastName())//Earth
            $.log(a.fullName())//Planet Earth 通過上面兩個計算出來
            a.fullName("xxx yyy");//更新fullName會自動更新firstName與lastName
            $.log(a.firstName())//xxx
            $.log(a.lastName())//yyy
            a.firstName("ooo");//更新firstName會自動更新fullName
            $.log(a.fullName())//ooo yyy
            $.log(a.card())//ooo yyy 屌絲

到這里viewModel中的每個域(firstName, lastName, fullName)只要存在依賴關系都能相互通知了。


免責聲明!

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



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