MVC:模型Model與數(shù)據(未完成)

主題:如何在應用中使用MVC模型乳蛾,包括加載和操作遠程數(shù)據

包括以下分支:

1.為什么構建ORM類庫時,MVC和命名空間很重要
2.如何使用ORM類庫來管理模型數(shù)據
3.如何使用JSONP和跨域Ajax來遠程加載數(shù)據
4.如何通過使用HTML5本地存儲和將本地存儲提交至RESTful服務器鄙币,來實現(xiàn)模型數(shù)據持久化


1.為什么構建ORM類庫時肃叶,MVC和命名空間很重要

把數(shù)據管理工作遷到前臺的一個好處:

  • 客戶端數(shù)據存儲速度非常快十嘿,幾乎是瞬間讀取因惭,因為數(shù)據是直接從內存中獲得的。這會讓你的應用接口變得與眾不同绩衷,所有交互操作都會瞬間得到響應蹦魔,這極大地提升了用戶體驗激率。

那么怎么在前端,也就是客戶端架構數(shù)據存儲的模型呢勿决?

引入MVC乒躺,那么數(shù)據管理則歸入模型(M)。模型應當從視圖和控制器中解耦出來低缩。與數(shù)據操作和行為相關的邏輯都應當放入模型中嘉冒,通過命名空間進行管理(模塊化,引入需要的咆繁,再暴露出API)讳推。

但這里的命名空間主要指通過對象/類來封裝模型,避免模型暴露在全局玩般,導致可能的作用域污染银觅。

用對象封裝后,數(shù)據通常用數(shù)組來存儲(數(shù)組內又可以嵌套對象)坏为,或者用Ajax等調用遠程服務器數(shù)據的函數(shù)來獲取數(shù)據(然后也可存在前面的數(shù)組究驴,或者看具體場景)。

為了讓模型更加抽象久脯,可復用纳胧,需要把模型寫成類镰吆。在JS里則是用原型繼承

e.g:

var User = function(atts) {
    this.attributes = atts || {};
};

User.prototype.destory = function () {
    / ...  / 
};

(ps:這是一種不被推薦的類寫法帘撰,在這只用作例子說明)

對于那些不需要復用,只在User內部使用的函數(shù)或變量就可以不作為原型繼承万皿,直接寫在User里:

User.fetchRemote = function () {
    / .... /  
};

這個方法就只是User擁有摧找,而不會被實例繼承。

小結:通過命名空間和MVC的分層牢硅,能更好的對數(shù)據進行管理蹬耘,避免出現(xiàn)混亂的情況。


2.如何使用ORM類庫來管理模型數(shù)據

對象關系映射(ORM, Object-relational mapper)是除了JS外的編程語言中常見的一種數(shù)據結構减余。

由于現(xiàn)在前端承擔了更多的任務综苔,所以有了管理數(shù)據的需求,因此對象關系映射對于JS來說也成了一種非常有用的技術位岔,它可以用來做數(shù)據管理和用做模型如筛。

比如用ORM將模型和遠程服務綁在一起,然后模型實例的改變都會發(fā)起Ajax請求到服務器端(或get數(shù)據或put數(shù)據)抒抬⊙钆伲或者將模型實例和HTML元素綁定,模型實例的數(shù)據改變會在界面中反映出來擦剑。

如何自定義一個ORM

這里使用Object.create()妖胀。傳入一個原型對象作為參數(shù)芥颈,會返回一個繼承了這個原型對象的新對象。

JS的原型繼承機制十分強大赚抡,用這個繼承機制寫的Model十分靈活(因為在模型創(chuàng)建好后爬坑,還可以動態(tài)擴展屬性方法,不僅對模型本身擴展涂臣,對模型的實例也可以)妇垢。

現(xiàn)在創(chuàng)建Model對象:

var Model = { 
  inherited: function () {}, // 存放被繼承的模型
  created: function () {}, 

  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  }
};

create()函數(shù)返回新對象,這個對象繼承自Model對象肉康,我們用這個返回的新對象來賦值給新模型闯估。

init()函數(shù)返回新對象,這個對象繼承自Model.prototype吼和,即繼承自Model對象的新對象的實例涨薪。這么說有點繞,展示示例:

var Asset = Model.create();
var User = Model.create();  

這是用create()函數(shù)創(chuàng)建的新模型炫乓。

var user = User.init();

這是用init()函數(shù)返回的一個實例刚夺,繼承了Model.prototype的所有屬性和方法。相反末捣,這也意味著可以通過給Model.prototype添加屬性和方法侠姑,來動態(tài)的給所有實例添加這些屬性和方法。

當然箩做,我們不直接這么用莽红,而是用extend()和include()這兩個函數(shù)封裝添加模型的屬性方法添加實例的屬性方法這兩個功能:

var Model = {
  inherited: function () {},
  created: function () {},

  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  },

  // 添加繼承的模型的屬性
  extend: function (obj) {
    var extended = obj.extended;
    jQuery.extend(this, obj );
    if (extended) {
      extended(this);
    }
  },

  // 添加繼承的模型的實例的屬性
  include: function (obj) {
    var included = o.included;
    jQuery.extend(this.prototype, obj);
    if (included) {
      included(this);
    }
  } 
};

extend()給Asset和User模型添加屬性:

Model.extend({
    find: function () {}
};

Asset.find(); 
// Asset模型擁有了find,前提是先給Model添加find
// 不是基于原型的這種不能動態(tài)添加屬性方法

include()方法給user實例動態(tài)添加屬性邦邦,即不論創(chuàng)建時間的先后:

Model.include({
  init: function (atts) {
    if (atts) {
      this.load(atts);
    }
  },

  load: function (attributes) {
    for (var name in attributes) {
      this[name] = attributes[name];
    }
  }
});

// 實例便擁有了init()和load()方法
var asset = Asset.init( {name: "foo.png"} );

下面則是進一步完善的模型:

var Model = {
  created: function () {},
  
  prototype: {
    init: function () {}
  },

  create: function () {
    var object = Object.create(this);
    object.parent = this;
    object.prototype = object.fn = Object.create(this.prototype);

    object.created();
    this.inherited(object);
    return object;
  },

  init: function () {
    var instance = Object.create(this.prototype);
    instance.parent = this;
    instance.init.apply(instance, arguments);
    return instance;
  },

  // 添加繼承的模型的屬性
  extend: function (obj) {
    var extended = obj.extended;
    jQuery.extend(this, obj );
    if (extended) {
      extended(this);
    }
  },

  // 添加繼承的模型的實例的屬性
  include: function (obj) {
    var included = obj.included;
    jQuery.extend(this.prototype, obj);
    if (included) {
      included(this);
    }
  } 
};


Model.extend({
  // 一個繼承模型的實例想要持久化記錄數(shù)據
  created: function () {
    this.records = {};
  },
  
  find: function () {
    var record = this.records[id];
    if (!record) {
      throw("Unknown record");
    }
    return record.dup();
  }  
});

// 模型繼承時安吁,繼承的模型可以傳入自定義的屬性
Model.include({
  init: function (atts) {
    if (atts) {
      this.load(atts);
    }
  },

  load: function (attributes) {
    for (var name in attributes) {
      this[name] = attributes[name];
    }
  }
});


Model.include({
  // 初始化為true,因為本就是新增加的一條記錄
  newRecord: true, 

  create: function () {
    // 為每個實例加上ID
    if (!this.id) {
      this.id = Math.guid();
    }
    // 創(chuàng)建了一天燃辖,所以要把newRecord這個狀態(tài)判斷換位false
    this.newRecord = false;
    // 存入records
    this.parent.records[this.id] = this.dup();
  },

  destroy: function () {
    delete this.parent.records[this.id];
  },

  update: function () {
    this.parent.records[this.id] = this.dup();
  },

  dup: function () {
    return jQuery.extend(true, {}, this);
  }

  // 這個方法作用是每次給實例添加好數(shù)據后鬼店,都使用save來保存,
  // 并且有個判斷黔龟,如果是新記錄則創(chuàng)建妇智,已經存在則更新
  save: function () {
    this.newRecord ? this.create() : this.update();
  }
});


// 生成隨機ID
Math.guid = function(){
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
    function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    }).toUpperCase();      
};


3.如何使用JSONP和跨域Ajax來遠程加載數(shù)據

通過遠程服務器獲取數(shù)據,然后寫入ORM

需要的相關知識:

Ajax

Ajax的新規(guī)范Fetch API

jQuery的Ajax接口

JSONP

跨域通信CORS

在解決了向服務器端的數(shù)據抓取后氏身,就需要把這些抓取的數(shù)據添加進我們創(chuàng)建的ORM巍棱。

具體來說,在用Ajax或者JSONP抓取數(shù)據后观谦,我們在其回調函數(shù)里加入我們預定義的處理函數(shù)拉盾,這個處理函數(shù)主要就是把json格式的數(shù)據通過遍歷處理,再創(chuàng)建實例豁状,更新進records對象中捉偏。

具體的代碼為(會合并進前面的完整Model里):

Model.extend({
  populate: function( values ) {
    // 重置model和records
    this.records = {};

    for (var i= 0; i < values.length; i++ ) {
      var record = this.created(values[i]);
      record.newRecord = false;
      this.records[record.id] = record;
    }    
  }
});

這個方法的用法示例:

// 當我們用jQuery的JSONP接口獲得了數(shù)據后...
jQuery.getJSON("/assets", function(result) {
    Asset.populate(result);
});

4.如何通過使用HTML5本地存儲和將本地存儲提交至RESTful服務器倒得,來實現(xiàn)模型數(shù)據持久化

通過本地緩存獲取數(shù)據,然后寫入ORM

這個就先要在相關位置設置“存儲緩存”夭禽,然后才在需要的方法調用緩存的數(shù)據霞掺。

因此先記錄一下Web Storage API:

這個API的作用是,使得網頁可以在瀏覽器端儲存數(shù)據讹躯。它分成兩類:sessionStorage和localStorage菩彬。

他們的不同之處:
  • sessionStorage保存的數(shù)據用于瀏覽器的一次會話,當會話結束(通常是該窗口關閉)潮梯,數(shù)據被清空骗灶;
  • localStorage保存的數(shù)據長期存在,下一次訪問該網站的時候秉馏,網頁可以直接讀取以前保存的數(shù)據耙旦。
他們的相同之處:
  • 保存期限的長短不同,這兩個對象的屬性和方法完全一樣萝究。

他們很像cookie機制的強化版免都,能夠動用大得多的存儲空間。另外帆竹,與Cookie一樣绕娘,它們也受同域限制。

怎么判斷瀏覽器是否支持這兩個對象栽连?代碼如下:

function checkStorageSupport() {
 
  // sessionStorage
  if (window.sessionStorage) {
    return true;
  } else {
    return false;
  }
   
  // localStorage
  if (window.localStorage) {
    return true;
  } else {
    return false;
  }
}

怎么使用這兩個對象呢险领?示例代碼如下:



最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市升酣,隨后出現(xiàn)的幾起案子舷暮,更是在濱河造成了極大的恐慌,老刑警劉巖噩茄,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異复颈,居然都是意外死亡绩聘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門耗啦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凿菩,“玉大人,你說我怎么就攤上這事帜讲⌒乒龋” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵似将,是天一觀的道長获黔。 經常有香客問我蚀苛,道長,這世上最難降的妖魔是什么玷氏? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任堵未,我火速辦了婚禮,結果婚禮上盏触,老公的妹妹穿的比我還像新娘渗蟹。我一直安慰自己,他們只是感情好赞辩,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布雌芽。 她就那樣靜靜地躺著,像睡著了一般辨嗽。 火紅的嫁衣襯著肌膚如雪膘怕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天召庞,我揣著相機與錄音岛心,去河邊找鬼。 笑死篮灼,一個胖子當著我的面吹牛忘古,可吹牛的內容都是我干的。 我是一名探鬼主播诅诱,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼髓堪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娘荡?” 一聲冷哼從身側響起干旁,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炮沐,沒想到半個月后争群,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡大年,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年换薄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔试。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡轻要,死狀恐怖,靈堂內的尸體忽然破棺而出垦缅,到底是詐尸還是另有隱情冲泥,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站凡恍,受9級特大地震影響志秃,放射性物質發(fā)生泄漏。R本人自食惡果不足惜咳焚,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一洽损、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧革半,春花似錦碑定、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至六敬,卻和暖如春碘赖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背外构。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工普泡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人审编。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓撼班,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垒酬。 傳聞我的和親對象是個殘疾皇子砰嘁,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容