遷移iOS API到前端並實現先後端分離(非Node.js)

面向對象

  1. 封裝
    使用ES5的寫法。ES6 class不支持私有屬性,很多瀏覽器暫時不支持ES6語法,雖然有babel,仍是很容易搞成和IE8如下不兼容,不採用。
function A(){    
  var privateAttr = 'a'    
  /*使用私有屬性的公有方法必須在構造函數中聲明。爲了減小內存的損耗,能夠在構造函數中聲明get/set方法或者在prototype中實現帶有須要使用的 私有屬性做爲參數的公共方法,而後再在構造方法中聲明對外的公共方法 */   
  this.sayHello = function(){    
      this._sayHello(privateAttr)    
  }    
}    
A.prototype._sayHello = function(privateAttr){    
  console.log(""+privateAttr)    
}
複製代碼

靜態方法和屬性和其餘面嚮對象語言相似,爲類對象的屬性和方法。javascript

A.staticAttr  = 10    
A.staticFunction = function(){}    
複製代碼

另外一種方式,減小方法對內存的佔用,我愈來愈傾向於使用這種寫法。css

(function () {
       /*map用於存放該類全部對象的私有屬性。須要使用數組實現的兼容補丁。WeakMap也能夠是Map/Object。當心內存泄露!*/
        var map = new WeakMap()     
        window.A = function () {    
            var data = {    
                a:10,    
                b:20    
            }    
            map.set(this,data)    
        }    
        A.prototype.getA = function () {    
            var data = map.get(this)    
            return data.a    
        }    
        A.prototype.setA = function (a) {    
            var data = map.get(this)    
            data.a = a    
        }    
        A.prototype.getB = function () {    
            var data = map.get(this)    
            return data.b    
        }    
 })()
複製代碼
  1. 繼承
Function.prototype.extend = function(superClass,publicObject,staticObject) {    
          if(typeof this  === 'function'){    
              if(typeof superClass === 'function' ){    
                  var Super = function(){}    
                  Super.prototype = superClass.prototype    
                  this.prototype = new Super()    
                  this.prototype.constructor = this    
              }    
              //可能刪除如下代碼 
              if(typeof publicObject === 'object'){    
                  this.prototype.shallowCopy(publicObject)    
              }    
              if(typeof staticObject === 'object'){    
                  this.shallowCopy(staticObject)    
              }    
          }    
}    
function Super(){    
}    
function Sub(){    
  Super.call(this,....)    
}    
Sub.extend(Super) 
複製代碼
  1. 多態
    因爲部分公共方法存放在prototype中,部分存在對象中,建議使用變量存放父類方法,而後在子類方法中利用此變量調用父類方法。
function Super(){    
  this.sayHello = function(){}    
}    
function Sub(){    
  Super.call(this,....)    
  var superSayHello = this.sayHello //若是是原型方法則直接調用。 
  this.sayHello = function(){    
      superSayHello()    
      ...    
  }    
}    
Sub.extend(Super) 
複製代碼
  1. 重載
    經常使用的js重載是利用js的變量能夠保存不一樣類型的值,在函數內部使用條件判斷變量類型以實現重載。這裏使用在函數名後面加數字進行假重載,如下是對 構造函數BCSView的重載。
export function BCSView(element, style) {    
   //如下注釋代碼是想讓js和swift的建立對象的語法一致。 
   //swift建立對象語法 var view = BCSView() 
   //js部分類如Object,Date支持這種建立對象的語法,部分類如Set,Map不支持(Chrome下驗證) 
   // 並且多這麼一步會致使全部類都須要如此寫,不但麻煩並且還要包裝系統自帶不支持的類,故不爲了swift而swift。 
   // if (!this || this.constructor !== BCSView){ 
   // return new BCSView(element, style) 
   // } 
   Function.requireArgumentNumber(1)    
   this.layer = element    
   if(typeof style === 'object'){    
       style.position = 'absolute'    
   }else{    
       style = {position:'absolute'}    
   }    
   this.setStyle(style)    
   this.subViews = generateSubViews(this.layer)    
   /* 方便調試 */    
   this.layer.setAttribute('view',this.getClass())    
}    
   
function BCSView1(style,elementType) {    
   elementType = elementType || 'div'    
   var element = document.createElement(elementType)    
   if(this.constructor === BCSView1){    
       /*經過new BCSView1構建*/    
       return new BCSView(element,style)    
   }else{    
       BCSView.call(this,element,style)    
   }    
}  
複製代碼

html5兼容補丁(polyfill)

    從HTML5-Cross-browser-Polyfills 中挑選兼容性較好的補丁,並按照IE的版本分別打包。使用時根據須要調用browser.applyPatches(PatchEnum.Audio,....),全部可用的補丁名稱存放 在PatchEnum中,其中有很多補丁能夠兼容IE6。我沒有所有測試過這些補丁,並且還有部分是我的代碼...如下是驗證過兼容到IE6的補丁清單:html

  • JSON(默認啓用)
  • ES5(默認啓用,不兼容部分語法)
  • Dom2(默認啓用)
  • LocalStorage/SessionStorage
  • Promise
  • fetch(IE67不支持跨域) 和fetchJSONP
  • canvas
  • Video/Audio
  • Css3 BackgroundsBorders(PIE_IE678_uncompressed補丁)
  • Console(默認啓用,防止IE9及如下非調試時報錯)
  • Transform
  • Placeholder
  • CSS3Filter
  • MathJax

先後端分離

    目前,主流將B/S和C/S當作兩種獨立的架構。本人更願意將B/S當作是一種特殊C/S架構,http協議創建在tcp協議上就是佐證!這也是這個項 目叫作BC/S的緣由。BC/S即Browser client/Server架構的簡稱,即將瀏覽器看做和iOS/Android相似的終端(事實也是如此)。它們的開發思路很相似, 以iOS和前端做爲對比:html css對應iOS的StoryBoard(StoryBoard是xml文件)和配置文件,javascript對應OC/Swift。甚至於javascript有 document.getElementById API,而Android有findViewById....那麼如何在瀏覽器實現先後端分離呢?單頁面應用徹底能夠作到先後端分離,有問題的 是多頁面應用。可否在單頁面應用的基礎上,將json數據傳遞給新打開的頁面呢?答案是確定的。能夠用來在頁面間傳遞數據的有localStorage, sessionStorage,window.name。其中localStorage是全局的不合適;sessionStorage在Chrome打開新頁面的瞬間有bug(不知道算不算),並且IE6 的sessionStorage使用window.name進行兼容,故只能使用window.name。瀏覽器打開新頁面的API爲window.open(url,windowname....)。過程以下:前端

  • 舊頁面獲取到JSON數據後調用BCSViewController.prototype.openWindow方法,將json數據傳遞給新頁面
    (這裏的新頁面能夠是在本窗口打開的新頁面也能夠是在新窗口中打開的新頁面)的window.name
  • 新頁面打開後調用loadPageInfo方法,從window.name獲取JSON數據。
    有沒有可能發生會話上的問題?可否經過瀏覽器cookie相關API解決?

實現代碼以下:html5

  • 舊頁面獲取到JSON數據後調用
BCSViewController.prototype.openWindow = function (pageInfo,windowName,newWindow) {  
         Function.requireArgumentNumber(arguments,2)  
         Function.requireArgumentType(pageInfo,'object')  
        if(pageInfo instanceof Object){  
            var url,name,object,WindowNameEnum = window.WindowNameEnum  
            if (pageInfo.url) {  
                url = pageInfo.url  
            }else if(pageInfo.pathname){  
                url = location.href.replace(location.pathname,'') + pageInfo.pathname  
            }else{  
                throw new TypeError('invalid url & pathname')  
            }  
            windowName = windowName || WindowNameEnum.SELF  
            newWindow= newWindow ||window.open('',windowName)  
            if(newWindow){  
                if (newWindow.name) {  
                    object = JSON.parse(newWindow.name)  
                }else {  
                    object = {}  
                }  
                object[BCSViewController.key] = pageInfo  
                /*bluebird內部機制未知,但感受會在調用外部函數JSON.stringify時切換以讓出CPU。 此時打開另外一個頁面會致使頁面loadPageInfo執行,目前放在這個位置彷佛能夠順利執行*/  
                name = JSON.stringify(object)  
                newWindow.name = name  
                newWindow.location.assign(url)  
            }else{  
                throw new OpenWindowException('Failed to open window.Please check if url is correct ' +// jshint ignore:line 
                    'or popup new window function is blocked!')  
            }  
        }else{  
            throw new TypeError('invalid pageinfo') // jshint ignore:line 
        }  
    }  
複製代碼
  • 新頁面中調用
BCSViewController.prototype.loadPageInfo = function (dataURL,errorCallback,method,data) {  
        var pageinfo,key = BCSViewController.key  
        if(window.name){  
            var object = JSON.parse(window.name)  
            pageinfo = object[key]  
            if(pageinfo){  
                try{  
                    delete object[key]  
                }catch(e){  
                    object[key] = undefined  
                }  
                window.name = JSON.stringify(object)  
            }  
        }  
        if(!pageinfo && dataURL ){  
            method = method || window.HttpMethodEnum.GET  
            var request = new XMLHttpRequest()  
            if (request !== null) {  
                /* window.location.search 用戶可能輸入參數 */  
                request.open(method, '' + dataURL + window.location.search, false)  
                request.onreadystatechange = function () {  
                    if (request.readyState === 4) {  
                        switch (request.status) {  
                            case 302:  
                            case 200:  
                                if (request.responseText) {  
                                    pageinfo = JSON.parse(request.responseText)  
                                }  
                                break  
                            default:  
                                errorCallback && errorCallback(request.status, request.statusText)  
                                break  
                        }  
                    }  
                }  
                request.send(data)  
            }else{  
                throw new TypeError('XMLHttpRequest is not supported')  
            }  
        }  
        if(pageinfo && pageinfo.title){  
            document.title = pageinfo.title  
        }  
        return pageinfo  
    }  
複製代碼

優勢:java

  1. 在瀏覽器端實現先後端分離
  2. 將頁面動態數據的渲染放在瀏覽器端,下降服務器的運算負擔。(沒有實際測試過,只是以爲生成JSON數據的開銷總歸比渲染頁面的開銷小)

缺點:
每一個頁面多一次請求。
示例:src/main/webapp/WEB-INF/html/bcs1.htmlgit

以iOS開發思想編寫前端應用

  1. MVVM
    MVC

    MVVM
        這是iOS很經典的MVC和MVVM設計模式圖,能夠應用到Android,理論上也能夠應用到html應用。可能寫慣了React或者Vue的朋友會 鄙視它。本人學了點React和Vue的皮毛,依然仍是以爲iOS的MVVM比較好(上文的先後端分離的方式可能會讓人以爲Vue不錯),我的見解,拒絕在這個問題 上打口水戰,就是這麼專制。哈哈。尤爲想吐槽的是React Redux和VueX。這兩個玩意兒無非是用來存放model數據,卻由於html應用一直在使用函數編程 思惟,將一個原本使用面向對象的單例設計模式便可解決的問題變成一個須要使用3-4步的框架才能搞定的問題。單例設計模式的意思是建立一個整個應用全 局都能訪問的對象,這個對象是其類的惟一一個實例對象。因爲能夠經過對象.constructor獲得構造函數,目前我的認爲較好的單例模式以下:
    a.java 懶漢餓漢隨便挑一個吧,js多線程應該沒什麼人用。
(function () {  
    var singleton = new Model()  
    function Model() {  
        if(singleton){  
            throw new TypeError(this.getClass() + ' could be instantiated only once!')  
        }  
        ...  
    }  
    Model.getInstance = function () {  
        return singleton  
    }  
    window.Model = Model //export 
})()  
複製代碼

b.swiftgithub

(function () {  
       var singleton = new Model()  
       function Model() {  
           if(singleton){  
                 throw new TypeError(this.getClass() + ' could be instantiated only once!')   
           }  
           ...  
       }  
       Model['default'] = singleton  
       window.Model = Model //export 
})() 
複製代碼

c. Symbol方式web

  1. 觀察者模式
    iOS有兩種觀察者模式的實現,一個是KVO,另外一個是NotificationCenter.default。和iOS相似的js版KVO使用defineProperty實現,其使用範圍和 Vue同樣,區別是KVO是單向的,而Vue是雙向的。KVO只支持公有直接屬性(不支持私有屬性和path)。 不支持defineProperty的瀏覽器如IE8及如下可 以使用NotificationCenter.default,沒有深刻研究pubsub,但應該就是pubsub機制,只不過爲符合swift語法,才寫成這個樣子。
function Model(){  
  var map = new ListMap()   
  //啓用KVO功能
  //enableKVO是在Object.prototype中的擴展,故請放棄使用jQuery的想法,也沒有使用jQuery必要! 
  this.enableKVO(map) 
}  
複製代碼
  1. BCSView和iOS UIView
    UIView是iOS整個UIKit框架的基石。UIView有個layer屬性。BCS仿照UI框架,BCSView一樣有layer屬性,而這個layer屬性就是html元素。開發者可 以經過調用BCSView的API操做layer,也能夠直接獲取layer。BCSView的layer的默認css樣式含有position = 'absolute'。 聽說這種樣式可讓 瀏覽器在進行重繪/重排時只針對這個元素,而不會影響到其餘元素,契合iOS的UIView,只能說軟件世界中不少原理實際上是同樣的。 雖然BCS山寨UIKit 框架,但考慮到實際狀況,並不可能徹底模仿。例如使用setStyle設置BCSView的外觀,iOS對外觀的設置實在太麻煩了....
BCSView.prototype.setStyle = function (cssObject) {  
        var cssText = ''  
        for(var name in cssObject){  
            if(cssObject.hasOwnProperty(name)){  
                cssText += name.replace(/([A-Z])/g,function(match){  
                        return '-'+match.toLowerCase()  
                    }) + ":" + cssObject[name] + ';'  
            }  
        }  
        if( typeof( this.layer.style.cssText ) !== 'undefined' ) {  
            this.layer.style.cssText += ';' + cssText  
        } else {  
            this.layer.setAttribute('style',cssText);  
        }  
    }
複製代碼

cssObject的內容和React相似:編程

var cssObject =  {  
                bottom:'0px',  
                width:'100%'  
    }  
複製代碼

看到這裏,作前端的哥們該吐槽了。本人雖然前端代碼寫的少,但也看了很多(右鍵就能看谷歌的css我會亂講?^_^),我的認爲css其實已經臃腫不堪,即 使使用less,stylus並不能改變現狀,由於html的功能愈來愈強大,頁面愈來愈複雜,跟iOS/Android APP並無太大差距。爲何不將特殊樣式直接寫 到元素的內聯樣式中,讓內嵌或者外部樣式只負責通用樣式,例如帶有公司風格的樣式,反而讓內聯樣式白白留空?style.cssText只會讓瀏覽器重繪一次 和設置class效果是同樣的。jQuery對樣式的修改可能也所有都是在內聯樣式中完成的。React和Vue對樣式的處理甚至組件這個概念也是化整爲零的思路, 畢竟同一個組件的元素,外觀,js代碼都在同一個文件中,方便修改。利用document.body生成的BCSView對象做爲全部view的window屬性。

  1. 手勢識別
    已經實現語法和swift相似的手勢識別器,以及NavigationController的基本功能(bar上的按鈕沒實現...)詳見例子 BC-S/src/main/webapp/WEB-INF/html/controller.html。其中test.js的代碼不是標準的MVVM設計模式,請勿吐槽。在山寨手勢識別時發現iOS 各個手勢識別器的識別機制並非很一致。猜想iOS的不一樣識別器的實現代碼是不一樣人寫的。我只想盡可能貼近iOS,並沒有改進想法。
  2. 調試
    使用Firefox Version46 3DView調試DOM,能夠得到和Xcode相似的體驗,不過不好。

未完成的工做

  • 我還沒想到表示swift協議/代理概念的好方法。目前是想弄個協議/代理的清單,讓開發者自行復制黏貼到代碼中,再實現相應的方法。
  • 極度缺少測試。
  • 大部分UI類未完成。
  • IE9以上,低版本Chrome,Firefox,Opera及手機端兼容驗證。
  • 驗證dom4.js已經包含KeyboardEvent,classList,document.head等polyfill。
  • 整理補丁和參考代碼做者清單。
  • 還沒有加入svg,webform 驗證和input屬性功能補丁。
  • 驗證patchBackgroundBorder能夠修復IE6 PNG圖片透明問題。

這個項目目前仍是半成品。我最大的問題是作前端的朋友對這個項目有何見解?有沒有應用到實際項目中的價值? 歡迎想貢獻代碼和提供測試幫助的朋友。
項目地址:github.com/Ken-W-P-Hua…

相關文章
相關標籤/搜索