目前react,vue,angular等框架都支持單頁面程序,最近在回顧一些知識,想起剛畢業的時候接觸到knockout + require + director 構建的單頁面程序。因此寫一篇文章回顧一下之前的單頁面,或許對於如今瞭解單頁面程序有一些幫助。 javascript
從之前的多頁面程序,在只有js,html,jquery的年代,路由的跳轉經過連接跳轉,每一個頁面的header和footer會在每個頁面書寫一遍。那想建立一個單頁面程序。會有幾個問題?css
require.js的誕生,就是爲了兩個問題:html
咱們這裏使用require是爲了經過require來進行html和js的文件控制,require生來是用來控制js的,那麼怎麼控制html呢,能夠經過require的插件text進行html控制。vue
knockout是mvvm框架的鼻祖了。實現了雙向數據綁定的功能。 java
Knockout是一款很優秀的JavaScript庫,它能夠幫助你僅使用一個清晰整潔的底層數據模型(data model)便可建立一個富文本且具備良好的顯示和編輯功能的用戶界面。react
任什麼時候候你的局部UI內容須要自動更新(好比:依賴於用戶行爲的改變或者外部的數據源發生變化),KO均可以很簡單的幫你實現,而且很是易於維護。 jquery
咱們這裏使用的主要是knockout tempate的功能以及業務功能書寫的時候用的ko的數據綁定功能。template綁定經過模板將數據render到頁面。模板綁定對於構建嵌套結構的頁面很是方便。 git
注圖片來源:www.cnblogs.com/tangge/p/10…github
director.js 就是客戶端的路由註冊/解析器,它在不刷新頁面的狀況下,利用「#」符號組織不一樣的URL路徑,並根據不一樣的URL路徑來匹配不一樣的回調方法。通俗點說就是什麼樣的路徑幹什麼樣的事情。web
這裏用director主要是爲了作單頁面路由的切換調用js代碼。
上面已經說了,咱們須要經過require來進行對html,css的控制。
<script src="js/require.js" data-main="js/main"></script>
複製代碼
data-main屬性的做用是,指定網頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。因爲require.js默認的文件後綴名是js,因此能夠把main.js簡寫成main。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<main id="main"></main>
<script data-main="/Scripts/framework/main" src="/Scripts/lib/require.js"></script>
</body>
</html>
複製代碼
咱們的代碼裏面,加載require文件以後會執行main.js文件。
var paths = {
/* TODO: register all AMD modules by providing CamelCase aliases, exceptions are RequireJS plugins and named AMD modules, whose names are fixed */
/* follow files dictionary order */
'jquery': 'Scripts/lib/jquery',
'Routes': 'Scripts/framework/routes',
'knockout': 'Scripts/lib/knockout',
//framework
'Router': 'Scripts/lib/director',
'WebPageContrl': 'Scripts/framework/webPageContrl',
'AppRouter': 'Scripts/framework/router',
'Error-js': 'Scripts/app/Error',
'Error-html': 'templates/Error-html.html',
"knockout-amd-helpers": "Scripts/lib/knockout-amd-helpers",
"text": "Scripts/lib/text",
//bootstrap
'Custom': 'Scripts/lib/custom',
'Bootstrap': 'Scripts/lib/bootstrap.min',
//Customer
'CustomerIntroduction-html': 'templates/customer/CustomerIntroduction.html',
'CustomerIntroduction-js': 'Scripts/app/customer/CustomerIntroduction',
//require
'RequireIntroduction-html': 'templates/require/RequireIntroduction.html',
"RequireIntroduction-js": 'Scripts/app/require/RequireIntroduction',
'RequireCode-html': 'templates/require/RequireCode.html',
"RequireCode-js": 'Scripts/app/require/RequireCode',
//Javascript
'UnknowJavascriptSecond-html': 'templates/javascript/UnknowJavascriptSecond.html',
'UnknowJavascriptSecond-js': 'Scripts/app/javascript/UnknowJavascriptSecond',
};
var baseUrl = '/';
require.config({
baseUrl: baseUrl,
paths: paths,
shim: {
/* TODO: provide all needed shims for non-AMD modules */
'Router': {
exports: 'Router'
},
'Custom': {
exports: 'Custom'
},
'Custom': ['Bootstrap'],
'Bootstrap': ['jquery']
}
});
require(["jquery", "RequireIntroduction-js", "text!RequireIntroduction-html"],
function ($, module, html) {
console.log("Start test require html!");
$('#main').html(html);
console.log("Start test require js!");
module.TestRequireJs();
}
);
複製代碼
這個文件可能有點多了一點其餘的,因此這裏咱們還只看重點就能夠了。
使用require.config()方法,咱們能夠對模塊的加載行爲進行自定義。
require.config()就寫在主模塊(main.js)的頭部。參數就是一個對象,這個對象的paths屬性指定各個模塊的加載路徑。
咱們這裏將不少長文件路徑的文件經過別名定義。
require(["jquery", "RequireIntroduction-js", "text!RequireIntroduction-html"],
function ($, module, html) {
console.log("Start test require html!");
$('#main').html(html);
console.log("Start test require js!");
module.TestRequireJs();
}
);
複製代碼
這裏在獲取到當前頁面RequireIntroduction的html和js文件。經過jquery的html方法渲染html。獲取js模塊,調用js方法。
<!--RequireIntroduction-html-->
<div>
require introduction
</div>
複製代碼
// RequireIntroduction-js
define(function () {
function RequireIntroductionViewModel() {
var self = this;
self.TestRequireJs = () => {
console.log('testRequireJS');
}
}
return new RequireIntroductionViewModel();
});
複製代碼
下載所須要的文件knockout.js 和knockout-amd-helpers.js,knockout-amd-helpers.js在本章節中主要的做用在於knockout在渲染模板時能夠直接渲染整個html文件,而不用在當前web頁面中定義模板。
如今咱們想經過knockout template進行頁面的渲染。
<main id="main" data-bind="template: { name: 'RequireIntroduction-html', data: require('RequireIntroduction-js').data, afterRender: require('RequireIntroduction-js').afterRender }">
</main>
<script data-main="/Scripts/framework/main" src="/Scripts/lib/require.js"></script>
複製代碼
var paths = {
/* TODO: register all AMD modules by providing CamelCase aliases, exceptions are RequireJS plugins and named AMD modules, whose names are fixed */
/* follow files dictionary order */
'jquery': 'Scripts/lib/jquery',
'Routes': 'Scripts/framework/routes',
'knockout': 'Scripts/lib/knockout',
//framework
'Router': 'Scripts/lib/director',
'WebPageContrl': 'Scripts/framework/webPageContrl',
'AppRouter': 'Scripts/framework/router',
'Error-js': 'Scripts/app/Error',
'Error-html': 'templates/Error-html.html',
"knockout-amd-helpers": "Scripts/lib/knockout-amd-helpers",
"text": "Scripts/lib/text",
//bootstrap
'Custom': 'Scripts/lib/custom',
'Bootstrap': 'Scripts/lib/bootstrap.min',
//Customer
'CustomerIntroduction-html': 'templates/customer/CustomerIntroduction.html',
'CustomerIntroduction-js': 'Scripts/app/customer/CustomerIntroduction',
//require
'RequireIntroduction-html': 'templates/require/RequireIntroduction.html',
"RequireIntroduction-js": 'Scripts/app/require/RequireIntroduction',
'RequireCode-html': 'templates/require/RequireCode.html',
"RequireCode-js": 'Scripts/app/require/RequireCode',
//Javascript
'UnknowJavascriptSecond-html': 'templates/javascript/UnknowJavascriptSecond.html',
'UnknowJavascriptSecond-js': 'Scripts/app/javascript/UnknowJavascriptSecond',
};
var baseUrl = '/';
require.config({
baseUrl: baseUrl,
paths: paths,
shim: {
/* TODO: provide all needed shims for non-AMD modules */
'Router': {
exports: 'Router'
},
'Custom': {
exports: 'Custom'
},
'Custom': ['Bootstrap'],
'Bootstrap': ['jquery']
}
});
require(["knockout", "RequireIntroduction-js", "knockout-amd-helpers", "text"], function (ko, RequireIntroduction) {
ko.bindingHandlers.module.baseDir = "modules";
//fruits/vegetable modules have embedded template
ko.bindingHandlers.module.templateProperty = "embeddedTemplate";
ko.applyBindings(RequireIntroduction);
});
複製代碼
從簡要說明的時候咱們知道,須要用knockout的template來進行html渲染,可是這裏咱們沒有去定義具體的template,而是使用下面的代碼:
require(["knockout", "RequireIntroduction-js", "knockout-amd-helpers", "text"], function (ko, RequireIntroduction) {
ko.bindingHandlers.module.baseDir = "modules";
//fruits/vegetable modules have embedded template
ko.bindingHandlers.module.templateProperty = "embeddedTemplate";
ko.applyBindings(RequireIntroduction);
});
複製代碼
由於這裏採用了knockout-amd-helpers
該插件旨在成爲在 Knockout.js 中使用 AMD 模塊的輕量級且靈活的解決方案。這個庫最初設計用於require.js或curl.js。它提供了兩個主要功能:
上面的代碼執行的效果是什麼呢?
咱們書寫代碼的時候,如此調用就能夠從require當中加載當前key對應的html文件。
走到這裏,require已經和knockout聯繫在一塊兒。knockout進行模版渲染,require進行文件控制。
接下來主要的問題是: 監控路由變化,在系統代碼裏能夠動態處理當前路徑對應的html / js資源。而後對於js代碼的執行進行ko的綁定和生命週期方式的拆分。
/* * @Description: * @Author: rodchen * @Date: 2021-07-24 12:08:10 * @LastEditTime: 2021-07-24 21:33:46 * @LastEditors: rodchen */
define(['knockout', 'jquery', 'Router', 'Custom'], function (ko, $, Router) {
var initialRun = true;
function isEndSharp() { // url end with #
if (app.lastUrl != "" && location.toLocaleString().indexOf(app.lastUrl) != -1 && location.toLocaleString().indexOf('#') != -1 && location.hash == "") {
return true;
}
return false;
}
var app = {
// 每次路由切換,調用當前方法
initJS: function (pageName) {
require([pageName + '-js'], function (page) {
app.init(pageName, page);
});
},
init: function(pageName, pageData) {
if (isEndSharp()) {
return;
}
// js模塊的init方法執行
pageData.init();
// ko綁定的數據,數據綁定的數據源是js模塊裏的data模塊
app.page({
name: pageName, // 路由對應的page標識
data: pageData
});
// 初次執行,ko綁定
if (initialRun) {
ko.applyBindings(app, document.getElementsByTagName('html')[0]);
initialRun = false;
}
},
page: ko.observable({
name: '',
data: {
init: function() {}
}
}),
// knockout template 加載成功以後的回掉函數
afterRender: function() {
if (app.page().data.afterRender) {
app.page().data.afterRender();
}
}
};
return app;
});
複製代碼
/* * // router.js * @Description: * @Author: rodchen * @Date: 2021-07-24 12:08:10 * @LastEditTime: 2021-07-24 19:59:11 * @LastEditors: rodchen */
define(['WebPageContrl', 'Routes', 'Router'], function (WebPageContrl, Routes, Router) {
var routes = {};
$.each(Routes, function(key, value) {
var values = value.split(' ');
var pageName = values[0];
routes[key] = function() {
WebPageContrl.initJS(pageName); // 經過director路由的回掉函數,而後執行系統文件的initJS方法
};
});
var router = new Router(routes).configure({
notfound: function() {
routes['/error404/:code'](404);
}
});
var urlNotAtRoot = window.location.pathname && (window.location.pathname != '/');
if (urlNotAtRoot) {
router.init();
} else {
router.init('/');
}
return router;
});
複製代碼
// Routes
define({
'/error404/:code': 'Error /',
'/': 'CustomerIntroduction /',
'': 'CustomerIntroduction /',
//Customer
'/CustomerIntroduction': 'CustomerIntroduction /',
//Require
'/RequireIntroduction': 'RequireIntroduction /',
'/RequireCode': 'RequireCode /',
//Javascript
'/UnknowJavascriptSecond': 'UnknowJavascriptSecond /'
})
複製代碼
// 代碼執行 經過http-server執行,由於是相對路徑,須要掛靠在服務
npm install global http-server
複製代碼