Javascript 与 SPA单页Web富应用
书单推荐
# 《单页Web应用:JavaScript从前端到后端》
http://download.csdn.net/detail/epubitbook/8720475 # 《MVC的JavaScript Web富应用开发》
http://download.csdn.net/detail/u012070181/7361155
SPA单页Web富应用,顾名思义,就是只有一张Web页面的应用。浏览器一开始会加载必须的HTML、CSS 和 JavaScript,只有所有的操作都在这张页面上完成,这一切都是由JavaScript来控制。因此,单页Web应用必将包含大量的JavaScript代码,复杂度可想而知,模块化开发和框架设计的重要性不言而喻。
随着单页Web应用的崛起,各种框架也不断的涌现,如Vuejs、ReactJs、Angularjs、Backbone.js、Ember.Js等,还有RequireJS等模块加载器。但是,本书没有讲解这些框架和模块加载器,这也正是我喜欢书本的原因之一。作者自己很少使用框架,并且框架的限制过多,一旦不符合框架本身的设计哲学,结果可能适得其反。———— 事实上所有的框架使用都是如此。
但不管是使用框架还是按照书的方法开发,书中的思想都是适用的。
第一个SPA应用
<html>
<head>
<title></title>
<style type="text/css">
body{
width: 100%;
height:100%;
overflow:hidden;
background: #777;
}
#spa {
position: absolute;
top: 8px;
left: 8px;
right: 8px;
bottom: 8px;
border-radius: 8px 8px 0 8px;
background: #fff;
}
.spa-slider {
position: absolute;
bottom: 0;
right: 2px;
width: 300px;
height: 16px;
cursor: pointer;
border-radius: 8px 0 0 0;
background: #f00;
}
</style>
</head>
<body>
<div id="spa">
<div class="spa-slider"></div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
var spa = (function ( $ ) { var $chatSlider,toggleSlider,onClickSlider,initModule; var configMap = {
extended_height : 434,
extended_title : 'Click to retract',
retracted_height : 16,
retracted_title : 'Click to extend',
template_html : '<div class="spa-slider"></div>'
}, toggleSlider = function () {
var slider_height = $chatSlider.height(); if( slider_height === configMap.retracted_height) {
$chatSlider.animate({height: configMap.extended_height})
.attr('title',configMap.extended_title);
return true;
} else if (slider_height === configMap.extended_height) {
$chatSlider.animate({ height : configMap.retracted_height})
.attr('title',configMap.retracted_title);
return true;
} return false;
}; onClickSlider = function ( event ) {
toggleSlider();
return false;
}; initModule = function ( $container ) {
// render HTML
$container.html(configMap.template_html); $chatSlider = $container.find( '.spa-slider' ); $chatSlider.attr( 'title', configMap.retracted_title)
.click( onClickSlider ); return true;
}; return {
initModule : initModule
} }( jQuery )); $(function(){
console.log(spa);
spa.initModule($('#spa'));
})
</script>
</html>
变量作用域
要么全局,要么局部,而javascript中唯一能定义变量作用域的语块就只有函数。
换个方式来看,函数就像监狱(prison),在函数中定义的变量就像是囚犯(prisoner).
正如监狱限制囚犯不让他们从监狱逃脱一样,函数也限定了局部变量不让他们逃脱到函数之外:
function prison () {
var prisoner = 'i am local!'
}
prison();
console.log(prisoner); //prisoner is not defined
要是真这么简单就好了!!!这里有一个Javascript 作用域的陷阱,可以在函数中声明全局变量。成功帮助囚犯(prisoner)越狱。那就是只要忽略 var 关键字即可:
function prison () {
prisoner = 'i am local!' // 没有书写 var 关键字
}
prison();
console.log(prisoner); // i am local!
这种陷阱经常出现在for循环计数器中。所以请务必要避免这种错误:
# 错误的做法,没有书写var关键字
function prison () {
for( i = 0 ; i < 10 ; i++) {
// ...
}
}
prison();
console.log(i); // i is 10 # 正确的做法,养成 var 定义变量的方式
function prison () {
for( var i = 0 ; i < 10 ; i++) {
// ...
}
}
prison();
console.log(i); // i is not defined
变量提升
在JavaScript的函数中,当变量被声明时,声明会被提升到所在函数的顶部。然后被赋予undefined值。这就使得在函数的任意位置声明变量都将存在于整个函数中。尽管它在赋值之前,它的值一直是undefined。
function prison () {
console.log(prisoner); // undefined
var prisoner = "Lee";
console.log(prisoner); // Lee
}
全局作用域和变量提升的行为结合时,需要注意仍然遵循变量提升的规则
var prisoner = "Mp";
function prison () {
console.log(prisoner); // undefined
var prisoner = "Lee";
}
Javascript 对象 和 原型链
Javascript 对象是基于原型的,而当今其他广泛使用的语言全部是基于类的对象。
- 在基于类的编程中,使用类来描述对象是什么样子的;
- 在基于原型的编程中,我们需要先创建一个对象,然后告诉Javascript引擎,我们想要更多想这样的对象;
打个比方,如果建筑是基于类的系统,则建筑师会先画出房子的蓝图,然后房子按照该蓝图建造。
如果i建筑师基于原型的,建筑师会先建造一所房子,然后将房子都建成像这种模样的。
我们使用先前囚犯的示例,对比一下两种编程方式的不同。创建一名囚犯所要的条件有哪些,囚犯的属性包括名字(name)、囚犯ID、性别(sex)和年龄(age)。
基于类的编程方式,以C#为例
/* step 1 :定义类 */
public class Prisoner {
public int sex = 0;
public int age = 30;
public string name;
public string id; /* step 2 : 定义构造函数 */
public Prisoner ( string name, string id) {
this.name = name;
this.id = id;
}
} /* step 3 : 实例化对象 */
Prisoner firstPrisoner = new Prisoner( 'Joe', '12A' );
Prisoner secondPrisoner = new Prisoner( 'Sam', '2BC' );
基于原型的编程方式,以javascript为例
/* step 1:定义原型 */
var info = {
sex : 0,
age : 18
}; /* step 2:定义对象的构造函数 */
var Prisoner = function ( name, id ) {
this.name = name;
this.id = id;
} /* step 3:将构造函数关联到原型 */
Prisoner.prototype = info; /* step 4 :实例化对象 */
Prisoner firstPrisoner = new Prisoner( 'Joe', '12A' );
Prisoner secondPrisoner = new Prisoner( 'Sam', '2BC' );
通过两种不同的编程方式和step步骤的对比,我们发现它们都遵循类似的顺序,如果你习惯了类,则适应原型应该不难。但魔鬼隐藏在细节中,如果你没有学习基于原型的相关知识,就以其他语言的类编程理解方式一头扎进Javascirpt,很容易被某些看起来简单的东西绊倒。我们通过上述两个demo,来总结一下:
1、两者都首先创建了对象的模板
2、模板在基于类的编程中叫做类,在基于原型的编程中叫做原型对象
3、构造函数。在基于类的语言中,构造函数是在类的内部定义的。当实例化对象时,就十分的清晰明了。在Javascript中,对象的构造函数和原型对象是分开设置的。所以需要额外多一步来将它们连接在一起。
4、实例化对象,javascript使用new操作符。这实际上违背了它基于原型的核心思想,可能是试图让熟悉基于类继承的开发人员更容易理解。不幸的是,大部分开发人员容易搞混淆了。
基于第4个问题,我们的解决方案是使用 Object.create 作为 new 操作符的代替,使用它来创建Javascript对象时,更接近原型开发的感觉:
var info = {
sex : 0,
age : 18
}; Prisoner firstPrisoner = Object.create( info );
firstPrisoner.name = 'Joe';
firstPrisoner.id = '12A'; Prisoner secondPrisoner = Object.create( info );
secondPrisoner.name = 'Sam';
secondPrisoner.id = '2BC';
但这样手动设置每个对象的成员变量name和id是痛苦的,因为会有重复的代码显得不整洁。另一种更优的方案是,使用object.create + 工厂模式,来创建并返回最终的对象:
var info = {
sex : 0,
age : 18
}; var FactoryPrisoner = function ( name, id ) {
var prisoner = object.create( info );
prisoner.name = name;
prisoner.id = id;
return prisoner;
} var firstPrisoner = FactoryPrisoner( 'Joe', '12A' );
var secondPrisoner = FactoryPrisoner( 'Sam', '2BC' );
函数 —— 更深入的窥探
理解函数是理解Javascript的关键之一,是构建专业的单页应用的重要基础。
自执行匿名函数
在 javascript 的开发过程中,我们经常遇到一个问题:在全局作用域中定义的东西在每个地方都可用。但有时候我们不想和所有人共享这些内容,或因为这很可能覆盖对方的内容。比如自己在开发第三方库、创建javascript插件的时候,不希望使用者使用内部变量。
# demo 1
(function(){
var private_variable = "private";
})();
console.log(private_variable); // private_variable is not defined # demo 2
var prison = (function(){
return "Lee is in prison";
})();
console.log(prison); // Lee is in prison # demo 3
(function($){
console.log($);
})(jQurey)
模块模式
单页应用非常庞大,不能定义在一个文件中。我们应该将文件分为一个个的模块,每个模块都有它们自己的私有变量;
我们仍然使用自执行匿名函数来控制变量的作用域,并且把代码分为多个文件:
var prison = (function(){
var prisoner_name = 'Lee',
jail_term = '20 year term'; return {
prisoner : prisoner_name,
sentence : jail_term
};
})() console.log(prison.prisoner_name); // undefined
console.log(prison.prisoner); // Lee
console.log(prison.sentence); // 20 year term prison.prisoner_name = "Mp";
console.log(prison.prisoner); // Lee
这里出现一个问题, prison.prisoner_name 没有被更新,这里有2个原因:
1、它是在函数中使用了 var 关键词创建的局部变量。无法被外部访问;
2、它不是对象或者原型上的属性,所以它无法访问或者赋值;
也就是说,这些匿名函数中定义的属性,是无法通过外部直接调用或者赋值的。为了能更新它们,我们的做法是添加内部方法来访问并且修改内部的变量
var prison = (function(){
var prisoner_name = 'Lee'; return {
prisoner : prisoner_name,
update_prisoner_name : function(name) {
prisoner_name = name;
},
out_prisoner_name : function(){
return prisoner_name;
}
};
})() console.log(prison.prisoner); // Lee
prison.update_prisoner_name("Mp");
console.log(prison.prisoner); // Lee
console.log(prison.out_prisoner_name()); // Mp
我们注意到,我们依然不能在该对象或者原型上直接访问。我们只能使用匿名函数返回的对象中的方法来访问。这也是利用了【闭包】
什么是闭包?
闭包是一种抽象的概念,理解起来可能有些困难,所以在回答什么是闭包之前,先了解一些背景知识。
随着程序的运行,它们会因各种事情而占用计算机的内存,比如保存变量的值。如果程序运行了却从不释放不再需要的内存,久而久之会导致电脑崩溃。
在一些语言中,如C,内存的管理都是由程序员处理的。而像Java 和 Javascript,实现了自动释放内存的机制,当代码不再需要时,就自动从电脑的内存中移除它。这种自动化系统叫做垃圾回收器。关于内存是手动释放好还是自动释放好这里不讨论,只要知道Javascript 有垃圾回收机制就足够了。
那么问题来了,javascript垃圾回收器是怎么知道哪些东西【不再需要的】呢?万一把我想保留的变量移除了怎么办,有什么方法可以让某些变量不被回收?
我们先来回答第一个问题:javascript垃圾回收器比较一根筋!!
它想:既然函数已经执行完毕了,我们应该就不再需要访问该函数环境中的东西了。
所以当函数执行完毕时,函数中所有创建的东西就会从内存中移除。
回答第二个问题:有的,这种方法被称之为闭包!
var menu,
outer_function; outer_function = function () {
var fruit = 'apple';
var food = 'cake';
return function () {
return {
food : food,
fruit : fruit
}
}
} menu = outer_function(); // inner_function
console.log(menu()); // {food: "cake", fruit: "apple"}
从上面这个demo中我们发现,尽管outer_function()函数执行完毕了。理论上内部的两个变量 fruit 和 food 应该被销毁。但我们现在却可以使用menu()来获取。
这种解决方法就叫闭包。使用闭包的套路其实很简单:一个返回函数的函数!
Javascript 与 SPA单页Web富应用的更多相关文章
- 单页web应用(SPA)的简单介绍
单页 Web 应用 (single-page application 简称为 SPA) 是一种特殊的 Web 应用.它将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML.J ...
- 单页web应用是什么?它又会给传统网站带来哪些好处?
文章来源:<单页Web应用:JavaScript从前端到后端> 什么是单页应用? 单页应用是指在浏览器中运行的应用,它们在使用期间不会重新加载页面.像所有的应用一样,它旨在帮助用户完成任务 ...
- 单页Web应用优缺点
一.定义单页 Web 应用 (single-page application 简称为 SPA) 是一种特殊的 Web 应用.它将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HT ...
- 单页Web应用:
概念: Web应用程序: WEB应用程序一般是B(浏览器)/S(服务器)模式.Web应用程序首先是“应用程序”,和用标准的程序语言,如C.C++等编写出来的程序没有什么本质上的不同.然而Web应用程序 ...
- 论单页Web应用和RESTful架构
单页Web应用 概述 单页Web应用并不是突然诞生的一门新技术,而是web展示的一种新的尝试.它将所有的动作局限于一个Web页面,在加载站点首页的时候就加载站点需要的JavaScript和CSS.单页 ...
- 简陋的 ASP.NET CORE 单页Web应用程序“框架”
我对ASP.NET CORE了解不多,不知道是不是重复造轮子,也或者什么也不是,这个Demo是这样的: 1.非常简单或者说原始:2.将单页Web应用增加了一个页(Page)概念(相当于MVC的 Vie ...
- SPA(单页面web应用程序)
单页web应用(single page web application,SPA),就是只有一张web页面的应用,是加载单个HTML页面并在用户与应用程序交互时动态更新该页面的web应用程序. 浏览器一 ...
- 构建单页Web应用
摘自前端农民工的博客 让我们先来看几个网站: coding teambition cloud9 注意这几个网站的相同点,那就是在浏览器中,做了原先“应当”在客户端做的事情.它们的界面切换非常流畅,响应 ...
- 构建单页Web应用——简单概述
一.开发框架 ExtJS可以称为第一代单页应用框架的典型,它封装了各种UI组件,用户主要使用JavaScript来完成整个前端部分,甚至包括布局.随着功能逐渐增加,ExtJS的体积也逐渐增大,即使用于 ...
随机推荐
- MySql笔记之数据表
数据表:行称为记录 列称为字段 用来存储数据 一.数据类型 数据类型是指列.存储过程参数.表达式和局部变量的数据特征,它决定了数据的存储格式,代表了不同的信息类型. 在我们存储不同类型的数据时,为了 ...
- LCA【SP913】Qtree - Query on a tree II
Description 给定一棵n个点的树,边具有边权.要求作以下操作: DIST a b 询问点a至点b路径上的边权之和 KTH a b k 询问点a至点b有向路径上的第k个点的编号 有多组测试数据 ...
- POJ1325Machine Schedule(匈牙利算法)
Machine Schedule Time Limit: 1000MS Memory L ...
- 使用IIFE(立即执行函数)让变量私有化
今天去看了一个GITHUB上的开源项目,在客户端JS的脚本编写的时候,代码中多次使用了IIFE. 一开始我是懵逼的,不知道这种函数的意义何在,小菜鸟嘛. 后面我去研究了一番.发现了它的主要作用就是:让 ...
- 初步接触CERNVM
初步接触的来源是对ROOT数据分析工具的搜索,看到一个叫做Life as a Physicist的国外博客.知道了这个包含容器分发的软件,跟重要的是,这个欧洲核子中心开发的平台,对于我等科研人员是一大 ...
- POJ 1113 Wall(凸包)
[题目链接] http://poj.org/problem?id=1113 [题目大意] 给出一个城堡,要求求出距城堡距离大于L的地方建围墙将城堡围起来求所要围墙的长度 [题解] 画图易得答案为凸包的 ...
- 【线段树】bzoj3038 上帝造题的七分钟2 / bzoj3211 花神游历各国
暴力修改,记录一段是否全部为1或0,若全是了,则不再修改. 注意3211一定要判是否为0,否则会T得惨无人道. #include<cstdio> #include<cmath> ...
- 【尺取法】【Multiset】bzoj1342 [Baltic2007]Sound静音问题
O(n)地枚举所有长度为k的段,每次暴力转移. 转移的时候只是从最后插入一个数,从前面删去一个数. 计算的时候要取当前的max和min. 用multiset(∵元素是可重的)以上这些操作都是O(log ...
- JNI概述
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++). JNI 让你在利用强大 Java 平台的同时,使你仍然可以用 ...
- 【Git】GitHub for Windows使用(2) 分支
目录 1.回看客户端相关功能 2.新建一个分支 3.在新分支上修改文件 4.上传新建分支上的修改,并合并分支 5.删除分支 1.回看客户端相关功能 看看设置中的以下内容 2.新建一个分支 3.在新分支 ...