tengfeima / calendarplugin Goto Github PK
View Code? Open in Web Editor NEWLicense: MIT License
License: MIT License
打开jQuery源码,首先你会看到这样的代码结构:
(function( window, undefined ) {
// jquery code
})(window);
通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间。这点非常有用也是一个JS框架必须支持的功能,jQuery被应用在成千上万的JavaScript程序中,必须确保jQuery创建的变量不能和导入他的程序所使用的变量发生冲突。接下来看看在 自调用匿名函数 中都实现了什么功能,按照代码顺序排列:
(function( window, undefined ) {
// 构造jQuery对象
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
}
// 工具函数 Utilities
// 异步队列 Deferred
// 浏览器测试 Support
// 数据缓存 Data
// 队列 queue
// 属性操作 Attribute
// 事件处理 Event
// 选择器 Sizzle
// DOM遍历
// DOM操作
// CSS操作
// 异步请求 Ajax
// 动画 FX
// 坐标和大小
window.jQuery = window.$ = jQuery;
})(window);
jQuery对象不是通过 new jQuery 创建的,而是通过 new jQuery.fn.init 创建的。
(function( window, undefined ) {
var jQuery = (function() {
// 构建jQuery对象
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
}
// jQuery对象原型
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
// selector有以下7种分支情况:
// DOM元素
// body(优化)
// 字符串:HTML标签、HTML字符串、#id、选择器表达式
// 函数(作为ready回调函数)
// 最后返回伪数组
}
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
// 合并内容到第一个参数中,后续大部分功能都通过该函数扩展
// 通过jQuery.fn.extend扩展的函数,大部分都会调用通过jQuery.extend扩展的同名函数
jQuery.extend = jQuery.fn.extend = function() {};
// 在jQuery上扩展静态方法
jQuery.extend({
// ready bindReady
// isPlainObject isEmptyObject
// parseJSON parseXML
// globalEval
// each makeArray inArray merge grep map
// proxy
// access
// uaMatch
// sub
// browser
});
// 到这里,jQuery对象构造完成,后边的代码都是对jQuery或jQuery对象的扩展
return jQuery;
})();
window.jQuery = window.$ = jQuery;
})(window);
所有挂载到jQuery.fn的方法,相当于挂载到了jQuery.prototype,即挂载到了jQuery 函数上(一开始的 jQuery = function( selector, context ) ),但是最后都相当于挂载到了 jQuery.fn.init.prototype,即相当于挂载到了一开始的jQuery 函数返回的对象上,即挂载到了我们最终使用的jQuery对象上。
本文选自国外的一份JavaScript测试题,下文是题目和答案。有兴趣的同学可以先到这个链接做一下这些JavaScript测试题,看看能否全答对。http://perfectionkills.com/javascript-quiz/
(function(){
return typeof arguments;
})();
答案:"object"
arguments是对象,伪数组有两件事要注意这里:
参数不是数组,它是一个数组一样的物体,你可以使用方括号和整数索引的元素,但方法通常可在一个如推上不存在参数数组
Array.prototype.slice.call(arguments); 转成数组
当然arguments即使是数组,返回的依然是"object",因为数组也是对象,附加:typeof 对类型的判断
https://developer.mozilla.org/zh-CN/docs/JavaScript/Reference/Operators/typeof
var f = function g(){ return 23; };
typeof g();
答案:会发生错误
因为function g(){ return 23; }是函数表达式,事实上只有事一个名字,不是一个函数声明
函数实际上是绑定到变量f,不是g.
指定的标识符在函数表达式虽然有其用途:堆栈跟踪是清晰而不是充斥着无名的函数,你可以有一个匿名函数递归调用本身不使用argument.callee
附非常详细的帖子函数表达式
http://kangax.github.io/nfe/
(function(x){
delete x;
return x;
})(1);
答案: 1
参数不可删除
var y = 1, x = y = typeof x;
x;
"undefined"
通过重写代码如下结果:
var a, b; 展开就是 var a; var b;.
A = B = C;相当于 B = C = B;
知道了这一点,我们重写并得到:
var y = 1;
y = typeof x;
var x = y;
x;
当执行
y = typeof x时,x 还没有被定义,所以y成为字符串"undefined",然后被分配到x
(function f(f){
return typeof f();
})(function(){ return 1; });
答案:"number"
为了便于理解我们继续分解:
第一部分
var baz = function(){ return 1; };
第二部分
(function f(f){
return typeof f();
})(baz);
在这里,函数f接受一个参数是另一个函数,f函数内部执行这个实参函数并且返回类型
无论是从调用该函数返回,即使参数名称f与函数名冲突,函数接受本身作为自己的参数,然后调用,此时就看谁更具有更高的优先级了,显然,参数的优先级更高,所以实际执行的是return typeof 1
var foo = {
bar: function() { return this.baz; },
baz: 1
};
(function(){
return typeof arguments[0]();
})(foo.bar);
答案:"undefined"
为什么是"undefined"?.
我们必须要知道this运算符是怎么工作的.
JS语言精粹总结的很精炼:
1 纯粹的函数调用
2 作为对象方法的调用
3 作为构造函数调用
4 apply调用
我们看看题目是属于那种环境?
在arguments0中执行了一个方法,arguments[0]就是foo.bar方法
注意:这在foo.bar中的this是没有绑定到foo
虽然 foo.bar 传递给了函数,但是真正执行的时候,函数 bar 的上下文环境是 arguments ,并不是 foo
arguemnts[0] 可以理解为 arguments.0(不过写代码就不要这样了,语法会错误的),所以这样看来,上下文环境是 arguemnts 就没问题了,所以在执行baz的时候自然this就是window了,window 上没有baz属性,返回的就是undefined, typeof调用的话就转换成"undefined"了
var foo = {
bar: function(){ return this.baz; },
baz: 1
}
typeof (f = foo.bar)();
答案:"undefined"
继续改写一下:
var foo = {
bar: function(){ return this.baz; },
baz: 1
}
f = foo.bar;
typeof f();
把foo.bar存储给f然后调用,所以this在foo.bar引用的是全局对象,所以就没有baz属性了
换句话说
foo.bar执行的时候上下文是 foo,但是当 把 foo.bar 赋值给 f 的时候,f 的上下文环境是 window ,是没有 baz 的,所以是 ”undefined"
var f = (function f(){ return "1"; }, function g(){ return 2; })();
typeof f;
答案:"number"
逗号操作符的使用可以很混淆,但这段说明它的行为:
var x = (1, 2, 3);
x;
x的值是3,这表明,当你有一系列的组合在一起,并由逗号分隔的表达式,它们从左到右进行计算,但只有最后一个表达式的结果保存。由于同样的原因,这个问题可以改写为减少混乱:
var f = (function g(){ return 2; })();
typeof f;
var x = 1;
if (function f(){}) {
x += typeof f;
}
x;
答案:"1undefined"
这里有个难点
if 中的 function f(){} 要如何处理?
函数声明的实际规则如下:
函数声明只能出现在程序或函数体内。从句法上讲,它们 不能出现在Block(块)({ ... })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement语句, 而不能包含函数声明这样的源元素。另一方面,仔细看一看规则也会发现,唯一可能让表达式出现在Block(块)中情形,就是让它作为表达式语句的一部分。但是,规范明确规定了表达式语句不能以关键字function开头。而这实际上就是说,函数表达式同样也不能出现在Statement语句或Block(块)中(因为Block(块)就是由Statement语句构成的)。
假设代码我们不妨变一下:
var x = 1;
if (function(){}) {
x += typeof f;
}
x;
var x = 1;
x += typeof f;
x;
f在这了没有被定义,所以typeof f 是字符串"undefined" ,字符与数字相加结果也是一个字符串,
所以最后的x就是"1undefined"了
(function f(){
function f(){ return 1; }
return f();
function f(){ return 2; }
})();
答案:2
如果是一直看下来的话,这个题目应该是比较简单
简单的来说在执行return之前,函数声明会在任何表达式被解析和求值之前先被解析和求值,
即使你的声明在代码的最后一行,它也会在同作用域内第一个表达式之前被解析/求值,
参考如下例子,函数fn是在alert之后声明的,但是在alert执行的时候,fn已经有定义了
alert(fn());
function fn() {
return 'Hello world!';
}
所以题目中函数提升了两次,第二次把第一次覆盖了,
所以 return 后面的 f 是 return 语句的下一条语句声明的函数 f 。
注意自执行函数 (function f (){})(); 中的 f 并没有函数提升效果,它是表达式
function f(){ return f; }
new f() instanceof f;
答案:false
怎样去理解?
new f()
首先这个操作会创建一个新对象并调用构造函数函数这一新的对象作为它的当前上下文对象
简单的说
new f();
依稀记得高级程序设计里面是这么说的:
1 创建空对象。
2 将类的prototype中的属性和方法复制到实例中。
3 将第一步创建的空对象做为类的参数调用类的构造函数
默认如果没有覆盖这个空对象的话,返回this
var a = new Object;
a instanceof Object 为 true
我们在看 f() 返回了 return f;
那么也就是说这个新的对象是是自身,构造函数本身在 new 的过程中会返回一个表示该对象的实例。
但是函数的返回值覆盖了这个实例,这个new 就形同虚设
果f的形式为 function f(){return this}或function f(){}就不一样
var a = new f();
a instanceof f // false
值得注意的是 instanceof 检测的是原型
var x = [typeof x, typeof y][1];
typeof typeof x;
答案:这题目比较简单,注意下返回类型即可
即 x = typeof y = 'undefind'.
typeof 返回的是string类型就可以了
typeof typeof必然就是'string'了.
function(foo){
return typeof foo.bar;
})({ foo: { bar: 1 } });
答案:"undefined"
又是一个恶心的题目,纯文字游戏,大家看仔细看
先分解一下
var baz = { foo: { bar: 1 } };
(function(foo){
return typeof foo.bar;
})(baz);
去掉函数关联
var baz = { foo: { bar: 1 } };
var foo = baz;
typeof foo.bar;
最后,通过替代我们除去中间变量foo
var baz = { foo: { bar: 1 } };
typeof baz.bar;
所以现在就很清晰了,属性中没有定义baz;它被定义为baz.foo上了,所以结果是:”undefined"
with (function(x, undefined){}) length;
答案:2
with用得很少,with 语句就是用于暂修改作用域链的或者通常用来缩短特定情形下必须写的代码量
使用with语句的JavaScript代码很难优化,因此它的运算速度比不使用with语句的等价代码要慢得多。
而且,在with语句中的函数定义和变量初始化可能会产生令人惊讶的、相抵触的行为,因此我们避免使用with语句
with的用法是这样的:
with(object) {},在大括号里面,可以引用object的属性而不用使用object.attr这种形式。
这道题里面,with接受了一个对象,只不过这个对象是函数,函数有length属性,
代表形参的个数,所以上面返回的值是2
AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD
CMD 规范在这里:seajs/seajs#242
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。
还有不少⋯⋯
这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。
目前这些规范的实现都能达成浏览器端模块化开发的目的。
区别:
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
另外,SeaJS 和 RequireJS 的差异,可以参考:seajs/seajs#277
这篇介绍BFC的文章简单的解释配合简单的例子,理解起来很方便,所以推荐给刚接触BFC的同学学习:
BFC 已经是一个耳听熟闻的词语了,网上有许多关于 BFC 的文章,介绍了如何触发 BFC 以及 BFC 的一些用处(如清浮动,防止 margin 重叠等)。虽然我知道如何利用 BFC 解决这些问题,但当别人问我 BFC 是什么,我还是不能很有底气地解释清楚。于是这两天仔细阅读了CSS2.1 spec 和许多文章来全面地理解BFC。
在解释 BFC 是什么之前,需要先介绍 Box、Formatting Context的概念。
Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由很多个 Box 组成的。元素的类型和 display 属性,决定了这个 Box 的类型。 不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染。让我们看看有哪些盒子:
block-level box:display 属性为 block, list-item, table 的元素,会生成 block-level box。并且参与 block fomatting context;
inline-level box:display 属性为 inline, inline-block, inline-table 的元素,会生成 inline-level box。并且参与 inline formatting context;
run-in box: css3 中才有, 这儿先不讲了。
Formatting context 是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。最常见的 Formatting context 有 Block fomatting context (简称BFC)和 Inline formatting context (简称IFC)。
CSS2.1 中只有 BFC 和 IFC, CSS3 中还增加了 GFC 和 FFC。
BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
内部的Box会在垂直方向,一个接一个地放置。
Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
BFC的区域不会与float box重叠。
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
计算BFC的高度时,浮动元素也参与计算。
根元素
float属性不为none
position为absolute或fixed
display为inline-block, table-cell, table-caption, flex, inline-flex
overflow不为visible
代码
<style>
body {
width: 300px;
position: relative;
}
.aside {
width: 100px;
height: 150px;
float: left;
background: #f66;
}
.main {
height: 200px;
background: #fcc;
}
</style>
<body>
<div class="aside"></div>
<div class="main"></div>
</body>
根据BFC布局规则第3条:
每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
因此,虽然存在浮动的元素aslide,但main的左边依然会与包含块的左边相接触。
根据BFC布局规则第四条:
BFC的区域不会与float box重叠。
我们可以通过通过触发main生成BFC, 来实现自适应两栏布局。
.main {
overflow: hidden;
}
当触发main生成BFC后,这个新的BFC不会与浮动的aside重叠。因此会根据包含块的宽度,和aside的宽度,自动变窄。效果如下:
代码
<style>
.par {
border: 5px solid #fcc;
width: 300px;
}
.child {
border: 5px solid #f66;
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
根据BFC布局规则第六条:
计算BFC的高度时,浮动元素也参与计算
为达到清除内部浮动,我们可以触发par生成BFC,那么par在计算高度时,par内部的浮动元素child也会参与计算。
代码:
.par {
overflow: hidden;
}
代码:
<style>
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p>
<p>Hehe</p>
</body>
两个p之间的距离为100px,发送了margin重叠。
根据BFC布局规则第二条:
Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
我们可以在p外面包裹一层容器,并触发该容器生成一个BFC。那么两个P便不属于同一个BFC,就不会发生margin重叠了。
代码:
<style>
.wrap {
overflow: hidden;
}
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p>
<div class="wrap">
<p>Hehe</p>
</div>
</body>
其实以上的几个例子都体现了BFC布局规则第五条:
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
因为BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理。
给原文控的传送门 http://www.nczonline.net/blog/2010/01/05/interviewing-the-front-end-engineer/
---正文---
前端工程师大多自学成才, 面试起来很有意思. 初创公司or大公司都说招个好前端挺难的, 就是因为缺乏对前端的了解, 面试也不知道该问啥. 身在前端这么些年, 我自己总结了一套面试技巧, 效果还行。
有些前来应聘的人说我是个唱黑脸的, 其实俺也不是故意的. 也许就因为我提的问题太琐碎太注重细节(吧). 之前我在blog上贴过: 面试过我这一关( http://www.nczonline.net/blog/2007/03/27/surviving-an-interview-with-me/ ), 好前端是怎样炼成的( http://www.nczonline.net/blog/2007/08/15/what-makes-a-good-front-end-engineer/ ). 我提的问题基本也就围绕这两篇. 脑筋急转弯或逻辑问题我不会考, 我只用几个简单问题考察你是否称职。
基础知识
这年头上网搜点东西太容易了. 但是有些知识看过不代表会用. 我感觉有些基础知识还是应该是直接装到前端人的脑子里的. 事事都得Google一下, 那还怎么如期完成工作. 因此面试中要是有人敢说"这个不知道, 但是我搜一下就有了", 在我这就得给他挂上号. 下面这些知识我希望前端工程师能不查资料当场说出。
1.DOM结构: 节点间关系如何, 如何逐个遍历
2.DOM操作: 节点的添加, 删除, 移动, 复制, 创建, 选择
3.事件: 事件的基本应用, IE与标准事件模型的主要区别
4.XMLHttpRequest: 这是个啥, 一个完整的GET请求流程是怎样完成的, 出现错误如何处理
5.标准模式与怪异模式: 两者如何触发, 意义何在
6.盒模型: margin, padding, border关系如何, IE8之前的版本有何异常
7.块级与行内元素: 它们与周围元素关系如何, 用CSS处理起来有何异同
8.浮动: 如何应用, 有什么常见bug, 如果解决
9.HTML与XHTML: 区别何在, 如何取舍
10.JSON: 这是个啥, 为啥要用它, 具体怎么用, 再问点实现细节.
重复:以上这些都应该是脑内直接取用的基础知识, 面试时我先问的问题就是要了解你的知识储备情况, 应该说这么一串东西还不算很详尽, 但是要当我同事起码都得掌握。
问题不多
我坚信面试时考官问得越少越好. 一个劲地让应聘人转换思维方式太抓狂了, 也不平等. 一般我会提三个大问题, 但是每个都涵盖很多知识点. 要解决问题通常要n个步骤, 而且给我留下继续追问的机会, 比如这个:
有这么一个显示股票价格的页面, 有个按钮, 按一下就能刷新报价但不刷新整个网页. 请描述你对此问题的实现方式. 假设服务器返回的股价总是无误.
这么个问题包括了前面提到的N多基础知识: DOM结构, DOM操作, 事件监听, XHR, JSON. 还能接着问~比如不仅要更新股价, 还要在别处加点更新提示之类的. 要是碰到经验丰富的应聘人, 我就把话题引向XML和JSON的对比, 安全相关, 或者负载之类.
而且我希望应聘者不要用框架/库. 我要看看不用库的时候写出来的代码. 库总在演变, 对特定库API的了解没啥大用. /YUI2→3变的是挺大的./ 我们要招的是对库内部运作有充分了解, 并且能徒手重现的人.
解决问题
前端工作的一大乐趣是同一个问题可以有多种解决方案, 而方案间彼此淘汰的决定权在于应用环境. 我提问的时候常常要求应聘人换个方式来解决问题, 比如假设一下由于XX原因某路不通那么该如何是好. 这么问有两个目的:
其一, 这能了解到应聘者对知识的掌握是否仅停留在死记硬背阶段. 有些人背书功夫一流, 一张嘴给你感觉相当牛X. 但是他们不懂"此路不通"的机理, 也找不出个候选方案. 要是他们回答说"我就纳闷了, 这么办还有啥不好的吗". 在我看来这就是超出他们知识范围, 从而要用复制粘贴来的方案解决问题了.
其二, 这反映出他们对浏览器的理解程度(这也算是"脑内直接取用"的级别). 如果他们能了解浏览器在相关方面的运作机制, 很容易找出个候选方案.
对前端人来说这种能力至关重要. 前端开发中有些东西本该一切正常但某些浏览器就是X萎(对, 说你呢IE6)的情况太多了. 一招不灵又不懂得变招那么你还是嫩了点.
这能力在我眼里另有妙处: 一旦我了解了应聘者的知识范围, 我会出一个在这范围边界上的题. 我想看看这人基于既有知识解决新问题的能力, 这问题过程中每一步我都有准备小提示(卡死了就不好玩了, 卡太久还浪费时间), 我会观察对方解决问题的思维流程, 看着他当着我的面学一样新东西.
注: 以上相关问题都紧紧围绕客户端技术的实现. 我感觉考太抽象的东西考不出分析实际问题的能力来. 就如让速写画家作一幅油画人像 - 纯属扯淡也问不出个123.
激情
优秀前端恐怕最重要的就是激情. 前端知识学校都不教, 我们必须能自学. 而且客户端技术发展太快, 必须时常吸收新内容武装自己. 我倒不能强迫谁谁去追blog, 但是面试的时候必须有货.
激情这玩意要怎么考? 简单. 我只问这个:"当前你最关注的Web技术是啥?" 这问题永不过时, 想答错也不容易, 除非交白卷. 现在就问的话, 答案可以是WebSocket, HTML5, WebGL, 客户端数据库/爵爷你看到了么/等等. 有激情的web开发人总会坚持了解业界新动态, 坚持学新东西, 这样的人我愿意拉来做同事. 当然接下来我还要深入问一点细节以免有人顺嘴胡吹.
最后
了解诸如计算机科学, Web设计之类的知识也很有用, 不过这只能算是锦上添花. 从基础进高阶, 不难. 但是工作中从头开始学基础, 太难. 对于号称资深的应聘者, 对其能力的期望自然也会高一些. 面试0经验应届生的话那么完全又是另一套方案. 本文所提及的知识还只能说是基础.
跟新接手面试任务的考官讲面试, 我总会强调: 其实就问你自己一个事: 愿不愿意与这么一个人共事. 只要有一方面不ok, 结论就是no.
开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。
通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。
但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象。
简单说,deferred对象就是jQuery的回调函数解决方案。在英语中,defer的意思是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。
它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。
首先,回顾一下jQuery的ajax操作的传统写法:
$.ajax({
url: "test.html",
success: function(){
alert("哈哈,成功了!");
},
error:function(){
alert("出错啦!");
}
});
在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。
现在,新的写法是这样的:
$.ajax("test.html")
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。
deferred对象的一大好处,就是它允许你自由添加多个回调函数。
还是以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,怎么办?
很简单,直接把它加在后面就行了。
$.ajax("test.html")
.done(function(){ alert("哈哈,成功了!");} )
.fail(function(){ alert("出错啦!"); } )
.done(function(){ alert("第二个回调函数!");} );
回调函数可以添加任意多个,它们按照添加顺序执行。
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的方法$.when():
$.when($.ajax("test1.html"), $.ajax("test2.html"))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。
我们来看一个具体的例子。假定有一个很耗时的操作wait:
var wait = function(){
var tasks = function(){
alert("执行完毕!");
};
setTimeout(tasks,5000);
};
我们为它指定回调函数,应该怎么做呢?
很自然的,你会想到,可以使用$.when():
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd;
};
现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
wait()函数运行完,就会自动运行done()方法指定的回调函数。
如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?
要说清楚这个问题,就要引入一个新概念"执行状态"。jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。
前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法。
类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从"未完成"改为"已失败",从而触发fail()方法。
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.reject(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。
请看下面的代码:
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd;
};
$.when(wait(dtd))
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
dtd.resolve();
我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。
为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。
请看下面的代码:
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd.promise(); // 返回promise对象
};
var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
$.when(d)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
d.resolve(); // 此时,这个语句是无效的
在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。
不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。
var wait = function(dtd){
var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
return dtd.promise(); // 返回promise对象
};
$.when(wait())
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。
这时,wait函数还是保持不变,我们直接把它传入$.Deferred():
$.Deferred(wait)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。
除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。
var dtd = $.Deferred(); // 生成Deferred对象
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变Deferred对象的执行状态
};
setTimeout(tasks,5000);
};
dtd.promise(wait);
wait.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
wait(dtd);
这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
前面已经讲到了deferred对象的多种方法,下面做一个总结:
(1) $.Deferred() 生成一个deferred对象。
(2) deferred.done() 指定操作成功时的回调函数
(3) deferred.fail() 指定操作失败时的回调函数
(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
(5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。
(7) $.when() 为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(8)deferred.then()
有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
$.when($.ajax( "/main.php" ))
.then(successFunc, failureFunc );
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
(9)deferred.always()
这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
$.ajax( "test.html" )
.always( function() { alert("已执行!");} );
多重样式(Multiple Styles):如果外部样式、内部样式和内联样式同时应用于同一个元素,就是使多重样式的情况。
一般情况下,优先级如下:
(外部样式)External style sheet <(内部样式)Internal style sheet <(内联样式)Inline style
有个例外的情况,就是如果外部样式放在内部样式的后面,则外部样式将覆盖内部样式。
示例如下:
<head>
<style type="text/css">
/* 内部样式 */
h3{color:green;}
</style>
<!-- 外部样式 style.css -->
<link rel="stylesheet" type="text/css" href="style.css"/>
<!-- 设置:h3{color:blue;} -->
</head>
<body>
<h3>测试!</h3>
</body>
<html>
<head>
<style type="text/css">
#redP p {
/* 权值 = 100+1=101 */
color:#F00; /* 红色 */
}
#redP .red em {
/* 权值 = 100+10+1=111 */
color:#00F; /* 蓝色 */
}
#redP p span em {
/* 权值 = 100+1+1+1=103 */
color:#FF0;/*黄色*/
}
</style>
</head>
<body>
<div id="redP">
<p class="red">red
<span><em>em red</em></span>
</p>
<p>red</p>
</div>
</body>
</html>
结果:em标签内的数据显示为蓝色。
A 选择器都有一个权值,权值越大越优先;
B 当权值相等时,后出现的样式表设置要优于先出现的样式表设置;
C 创作者的规则高于浏览者:即网页编写者设置的CSS 样式的优先权高于浏览器所设置的样式;
D 继承的CSS 样式不如后来指定的CSS 样式;
E 在同一组属性设置中标有“!important”规则的优先级最大;示例如下:
<html>
<head>
<style type="text/css">
#redP p{
/*两个color属性在同一组*/
color:#00f !important; /* 优先级最大 */
color:#f00;
}
</style>
</head>
<body>
<div id="redP">
<p>color</p>
<p>color</p>
</div>
</body>
</html>
结果:在Firefox 下显示为蓝色;在IE 6 下显示为红色;
HTTP协议的主要特点可概括如下:
1.支持客户/服务器模式。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET 请求获取Request-URI所标识的资源
POST 在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应消息报头
PUT 请求服务器存储一个资源,并用Request-URI作为其标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
GET和POST的区别
1)GET请求的数据是放在HTTP包头中的,也就是URL之后
2)GET提交的数据比较少,最多1024B,因为GET数据是附在URL之后的,而URL则会受到不同环境的限制的,比如说IE对其限制为2K+35,而POST可以传送更多的数据(理论上是没有限制的,但一般也会受不同的环境,如浏览器、操作系统、服务器处理能力等限制,IIS4可支持80KB,IIS5可支持100KB)。
3)Post的安全性要比Get高,因为Get时,参数数据是明文传输的,而且使用GET的话,还可能造成Cross-site request forgery攻击。而POST数据则可以加密的,但GET的速度可能会快些。
#一行,描述请求方法、URL、htto协议等信息
<request-line>
#请求头信息
<headers>
<CRLF>
#request body。<headers>使用/r/n占用一行来标记结束
[<request-body><CRLF>]
request-line中的url一定是使用application/x-www-form-urlencoded编码的,这个与请求方法无关。
Content-Type指明了request body的编码方式。
Content-Length指明了request body的长度,该长度务必等于request-body真正的字符数。少于会导致request-body的确实,多于服务器会等待知道超时才返回已读取的数据(功能没影响,但效率显然差)。
GET请求
GET /hello/index.html HTTP/1.1
#【↑】request line
#【↓】request headers
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Host: localhost:8000
#发送完关闭连接 or 等待
Connection: Keep-Alive
Cookie: JSESSIONID=BBBA54D519F7A320A54211F0107F5EA6
POST请求
#url的部分仍然是urlencoded
POST /hello/checkUser.html?opt=xxx HTTP/1.1
Referer: http://localhost:8000/hello/index.html
Accept: */*
Accept-Language: zh-cn
#纯文本的编码:不编码
Content-Type: text/plain
Accept-Encoding: gzip, deflate
Host: localhost:8000
Content-Length: 20
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: JSESSIONID=BBBA54D519F7A320A54211F0107F5EA6
#【↑】/r/n
#【↓】request body
12345678901234567890
HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文
状态行格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK //客户端请求成功
400 Bad Request //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报 //头域一起使用
403 Forbidden //服务器收到请求,但是拒绝提供服务
404 Not Found //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后, //可能恢复正常
eg:HTTP/1.1 200 OK (CRLF)
说到prototype,就不得不先说下new的过程。
我们先看看这样一段代码:
<script type="text/javascript">
var Person = function () { };
var p = new Person();
</script>
很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:
<script type="text/javascript">
var Person = function () { };
var p = new Person();
alert(p.__proto__ === Person.prototype);
</script>
这段代码会返回true。说明我们步骤2的正确。
那么proto是什么?我们在这里简单地说下。每个对象都会在其内部初始化一个属性,就是proto,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去proto里找这个属性,这个proto又会有自己的proto,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
按照标准,proto是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。
好,概念说清了,让我们看一下下面这些代码:
<script type="text/javascript">
var Person = function () { };
Person.prototype.Say = function () {
alert("Person say");
}
var p = new Person();
p.Say();
</script>
这段代码很简单,相信每个人都这样写过,那就让我们看下为什么p可以访问Person的Say。
首先var p=new Person();可以得出p.proto=Person.prototype。那么当我们调用p.Say()时,首先p中没有Say这个属性,于是,他就需要到他的proto中去找,也就是Person.prototype,而我们在上面定义了Person.prototype.Say=function(){}; 于是,就找到了这个方法。
好,接下来,让我们看个更复杂的。
<script type="text/javascript">
var Person = function () { };
Person.prototype.Say = function () {
alert("Person say");
}
Person.prototype.Salary = 50000;
var Programmer = function () { };
Programmer.prototype = new Person();
Programmer.prototype.WriteCode = function () {
alert("programmer writes code");
};
Programmer.prototype.Salary = 500;
var p = new Programmer();
p.Say();
p.WriteCode();
alert(p.Salary);
</script>
我们来做这样的推导:
var p=new Programmer()可以得出p.proto=Programmer.prototype;
而在上面我们指定了Programmer.prototype=new Person();我们来这样拆分,var p1=new Person();Programmer.prototype=p1;那么:
p1.proto=Person.prototype;
Programmer.prototype.proto=Person.prototype;
由根据上面得到p.proto=Programmer.prototype。可以得到p.proto.proto=Person.prototype。
好,算清楚了之后我们来看上面的结果,p.Say()。由于p没有Say这个属性,于是去p.proto,也就是Programmer.prototype,也就是p1中去找,由于p1中也没有Say,那就去p.proto.proto,也就是Person.prototype中去找,于是就找到了alert(“Person say”)的方法。
其余的也都是同样的道理。
这也就是原型链的实现原理。
最后,其实prototype只是一个假象,他在实现原型链中只是起到了一个辅助作用,换句话说,他只是在new的时候有着一定的价值,而原型链的本质,其实在于proto!
function parent(){
this.x=10;
}
function child(){
var parentObj=new parent();
for(var p in parentObj)this[p]=parentObj[p];
}
var childObj=new child();
alert(childObj.x);
function parent(){
this.x=10;
}
function child(){
this.parent=parent;
this.parent();
delete this.parent;
}
var childObj=new child();
alert(childObj.x);
function parent(){
this.x=10;
}
function child(){
parent.call(this);
}
var childObj=new child();
alert(childObj.x);
function parent(){
}
parent.prototype.x=1;
function child(){
}
for(var p in parent.prototype)child.prototype[p]=parent.prototype[p];
child.prototype.y=2;
var childObj=new child();
alert(childObj.x);
function parent(string){
var child=new Function("this.x=10;"+string);
return child;
}
var child=new parent("this.y=20;");
var childObj=new child();
alert(childObj.y);
function parent(){
this.x=10;
}
function child(){
}
child.prototype=new parent();
var childObj=new child();
alert(childObj.x);
function parent(){
this.x=10;
}
function child(){
var ret=new parent();
ret.y=20;
return ret;
}
var childObj=new child();
alert(childObj.x);
一、document.getElementById() 根据Id获取元素节点
<div id="div1">
<p id="p1">
我是第一个P</p>
<p id="p2">
我是第二个P</p>
</div>
window.onload = function () {
var str = document.getElementById("p1").innerHTML;
alert(str); //弹出 我是第一个P
}
二、document.getElementsByName() 根据name获取元素节点
<div id="div1">
<p id="p1">
我是第一个P</p>
<p id="p2">
我是第二个P</p>
<input type="text" value="请输入值" name="userName" />
<input type="button" value="确定" onclick="fun1()">
</div>
function fun1() {
var username = document.getElementsByName("userName")[0].value;
alert(username); //输出userName里输入的值
}
三、document.getElementsByTagName() 根据HTML标签名获取元素节点,注意getElements***的选择器返回的是一个NodeList对象,能根据索引号选择其中1个,可以遍历输出。
<div id="div1">
<p id="p1">
我是第一个P</p>
<p id="p2">
我是第二个P</p>
</div>
window.onload = function () {
var str = document.getElementsByTagName("p")[1].innerHTML;
alert(str); //输出 我是第二个P,因为获取的是索引为1的P,索引从0开始
}
window.onload = function () {
var arr = document.getElementsByTagName("p");
for (var i = 0; i < arr.length; i++) {
alert(arr[i].innerHTML);
}
}
window.onload = function () {
var node = document.getElementById("div1");
var node1 = document.getElementsByTagName("p")[1]; //从获取到的元素再获取
alert(node1.innerHTML);
}
四、document.getElementsByClassName() 根据class获取元素节点
<div id="div1">
<p id="p1" class="class1">
我是第一个P</p>
<p id="p2">
我是第二个P</p>
</div>
window.onload = function () {
var node = document.getElementsByClassName("class1")[0];
alert(node.innerHTML);
}
五、javascript中的CSS选择器
document.querySelector() //根据CSS选择器的规则,返回第一个匹配到的元素
document.querySelectorAll() //根据CSS选择器的规则,返回所有匹配到的元素
<div id="div1">
<p id="p1" class="class1">
我是第一个P</p>
<p id="p2" class="class2">
我是第二个P</p>
</div>
window.onload = function () {
var node = document.querySelector("#div1 > p");
alert(node.innerHTML); //输出 我是第一个P
var node1 = document.querySelector(".class2");
alert(node1.innerHTML); //输出 我是第二个P
var nodelist = document.querySelectorAll("p");
alert(nodelist[0].innerHTML + " - " + nodelist[1].innerHTML); //输出 我是第一个P - 我是第二个P
}
六、文档结构和遍历
(1)作为节点数的文档
1、parentNode 获取该节点的父节点
2、childNodes 获取该节点的子节点数组
3、firstChild 获取该节点的第一个子节点
4、lastChild 获取该节点的最后一个子节点
5、nextSibling 获取该节点的下一个兄弟元素
6、previoursSibling 获取该节点的上一个兄弟元素
7、nodeType 节点的类型,9代表Document节点,1代表Element节点,3代表Text节点,8代表Comment节点,11代表DocumentFragment节点
8、nodeVlue Text节点或Comment节点的文本内容
9、nodeName 元素的标签名(如P,SPAN,#text(文本节点),DIV),以大写形式表示
注意,以上6个方法连元素节点也算一个节点。
<div id="div1">
<p id="p1" class="class1">
我是第一个P</p>
<p id="p2" class="class2">
我是第二个P</p>
</div>
window.onload = function () {
var node1 = document.querySelector(".class2");
alert(node1.parentNode.innerHTML); //输出 <p id="p1" class="class1">我是第一个P</p><p id="p2" class="class2">我是第二个P</p>
var nodelist = document.getElementById("div1");
var arr = nodelist.childNodes;
alert(arr[1].innerHTML + " - " + arr[3].innerHTML); //输出 我是第一个P - 我是第二个P 为什么是1,3呢?因为本方法文本节点也会获取,
也就是说0,2,4是文本节点
}
<div id="div1">
文本1
<p id="p1" class="class1">
我是第一个P</p>
文本2
<p id="p2" class="class2">
我是第二个P</p>
文本3
</div>
window.onload = function () { //依次输出,文本1,我是第一个P,文本2,我是第二个P,文本3
var node = document.getElementById("div1");
for (var i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType == 1) {
alert(node.childNodes[i].innerHTML);
}
else if (node.childNodes[i].nodeType == 3) {
alert(node.childNodes[i].nodeValue);
}
}
}
(2)作为元素树的文档
1、firstElementChild 第一个子元素节点
2、lastElementChild 最后一个子元素节点
3、nextElementSibling 下一个兄弟元素节点
4、previousElementSibling 前一个兄弟元素节点
5、childElementCount 子元素节点个数量
注意,此5个方法文本节点不算进去
<div id="div1">
<p id="p1" class="class1">
我是第一个P</p>
<p id="p2" class="class2">
我是第二个P</p>
</div>
window.onload = function () {
var node = document.getElementById("div1");
var node1 = node.firstElementChild;
var node2 = node.lastElementChild;
alert(node.childElementCount); //输出2,div1一共有两个非文档子元素节点
alert(node1.innerHTML); //输出 我是第一个P
alert(node2.innerHTML); //输出 我是第二个P
alert(node2.previousElementSibling.innerHTML); //输出 我是第一个P(第二个元素节点的上一个非文本元素节点是P1)
alert(node1.nextElementSibling.innerHTML); //输出 我是第二个P(第一个元素节点的下一个兄弟非文本节点是P2)
}
七、javascript操作HTML属性
1、属性的读取,此处要注意的是,某些HTML属性名称在javascript之中是保留字,因此会有些许不同,如class,lable中的for在javascript中变为htmlFor,className。
<div id="div1">
<p id="p1" class="class1"> 我是第一个P</p>
<img src="123.jpg" alt="我是一张图片" id="img1" />
<input type="text" value="我是一个文本框" id="input1" />
</div>
window.onload = function () {
var nodeText = document.getElementById("input1");
alert(nodeText.value); //输出 我是一个文本框
var nodeImg = document.getElementById("img1");
alert(nodeImg.alt); //输出 我是一张图片
var nodeP = document.getElementById("p1");
alert(nodeP.className); //输出 class1 注意获取class是className,如果写成nodeP.class则输出undefined
}
2、属性的设置,此处同样要注意的是保留字
<div id="div1">
<img src="1big.jpg" alt="我是一张图片" id="img1" onclick="fun1()" />
</div>
function fun1() {
document.getElementById("img1").src = "1small.jpg"; //改变图片的路径属性。实现的效果为,当点击图片时,大图变小图。
}
3、非标准HTML属性
getAttribute(); //注意这两个方法是不必理会javascript保留字的,HTML属性是什么就怎么写。
setAttribute();
<div id="div1">
<img src="1big.jpg" alt="我是一张图片" class="imgClass" id="img1" onclick="fun1()" />
</div>
function fun1() {
document.getElementById("img1").setAttribute("src", "1small.jpg");
alert(document.getElementById("img1").getAttribute("class"));
}
4、Attr节点的属性
attributes属性 非Element对象返回null,Element一半返回Attr对象。Attr对象是一个特殊的Node,通过name与value获取属性名称与值。
如:document.getElementById("img1")[0];
document.getElementById("img1").src;
<div id="div1">
<img src="1big.jpg" alt="我是一张图片" class="imgClass" id="img1" onclick="fun1()" />
</div>
function fun1() {
alert(document.getElementById("img1").attributes[0].name); //输出 onclick 注意,通过索引器访问是写在右面在排前面,从0开始
alert(document.getElementById("img1").attributes.src.value); //输出1big.jpg
document.getElementById("img1").attributes.src.value = "1small.jpg"; //点击后改变src属性,实现了点击大图变小图效果
}
八、元素的内容
1、innerText、textContent innerText与textContent的区别,当文本为空时,innerText是"",而textContent是undefined
2、innerHTML
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第<b>二</b>个P</p>
</div>
window.onload = function () {
alert(document.getElementById("p1").innerText); //注意火狐浏览器不支持innerText
alert(document.getElementById("p1").textContent); //基本都支持textContent
document.getElementById("p1").textContent = "我是p1,javascript改变了我"; //设置文档Text
alert(document.getElementById("p2").textContent);
alert(document.getElementById("p2").innerHTML); //innerHTML与innerText的区别,就是对HTML代码的输出方式Text不会输出HTML代码
}
九、创建,插入,删除节点
1、document.createTextNode() 创建一个文本节点
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
</div>
window.onload = function () {
var textNode = document.createTextNode("<p>我是一个javascript新建的节点</p>");
document.getElementById("div1").appendChild(textNode);
}
完成后HTML变为:
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
我是一个javascript新建的节点
</div>
2、document.createElement() 创建一个元素节点
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
</div>
window.onload = function () {
var pNode = document.createElement("p");
pNode.textContent = "新建一个P节点";
document.getElementById("div1").appendChild(pNode);
}
执行之后HTML代码变为:
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
<p>新建一个P节点</p>
</div>
3、插入节点
appendChild() //将一个节点插入到调用节点的最后面
insertBefore() //接受两个参数,第一个为待插入的节点,第二个指明在哪个节点前面,如果不传入第二个参数,则跟appendChild一样,放在最后。
<div id="div1">
<p id="p1">我是第一个P</p>
</div>
window.onload = function () {
var pNode1 = document.createElement("p");
pNode1.textContent = "insertBefore插入的节点";
var pNode2 = document.createElement("p");
pNode2.textContent = "appendChild插入的节点";
document.getElementById("div1").appendChild(pNode2);
document.getElementById("div1").insertBefore(pNode1,document.getElementById("p1"));
}
执行之后HTML代码为:
<div id="div1">
<p>insertBefore插入的节点</p>
<p id="p1">我是第一个P</p>
<p>appendChild插入的节点</p>
</div>
十、删除和替换节点。
1、removeChild(); 由父元素调用,删除一个子节点。注意是直接父元素调用,删除直接子元素才有效,删除孙子元素就没有效果了。
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
</div>
window.onload = function () {
var div1 = document.getElementById("div1");
div1.removeChild(document.getElementById("p2"));
}
执行之后代码变为:
<div id="div1">
<p id="p1">我是第一个P</p> //注意到第二个P元素已经被移除了
</div>
2、replaceChild() //删除一个子节点,并用一个新节点代替它,第一个参数为新建的节点,第二个节点为被替换的节点
<div id="div1">
<p id="p1">我是第一个P</p>
<p id="p2">我是第二个P</p>
</div>
window.onload = function () {
var div1 = document.getElementById("div1");
var span1 = document.createElement("span");
span1.textContent = "我是一个新建的span";
div1.replaceChild(span1,document.getElementById("p2"));
}
执行完成后HTML代码变为:
<div id="div1">
<p id="p1">我是第一个P</p>
<span>我是一个新建的span</span> //留意到p2节点已经被替换为span1节点了
</div>
十一、javascript操作元素CSS
通过元素的style属性可以随意读取和设置元素的CSS样式,例子:
<head>
<title></title>
<script type="text/javascript">
window.onload = function () {
alert(document.getElementById("div1").style.backgroundColor);
document.getElementById("div1").style.backgroundColor = "yellow";
}
</script>
</head>
<body>
<div id="div1" style="width:100px; height:100px; background-color:red"></div>
</body>
数据结构与算法是大多前端程序员的短板,传统的前端开发都是在跟浏览器兼容作斗争很少会涉及到复杂的结构设计。本文总结了常用排序算法的JavaScript实现。
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
function insertionSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
for (var i = 1; i < array.length; i++) {
var key = array[i];
var j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
return array;
} else {
return 'array is not an Array!';
}
}
最佳情况:输入数组按升序排列。T(n) = O(n)
最坏情况:输入数组按降序排列。T(n) = O(n^2)
平均情况:T(n) = O(n^2)
二分插入(Binary-insert-sort)排序是一种在直接插入排序算法上进行小改动的排序算法。其与直接插入排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。
function binaryInsertionSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
for (var i = 1; i < array.length; i++) {
var key = array[i], left = 0, right = i - 1;
while (left <= right) {
var middle = parseInt((left + right) / 2);
if (key < array[middle]) {
right = middle - 1;
} else {
left = middle + 1;
}
}
for (var j = i - 1; j >= left; j--) {
array[j + 1] = array[j];
}
array[left] = key;
}
return array;
} else {
return 'array is not an Array!';
}
}
最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n^2)
平均情况:T(n) = O(n^2)
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
function selectionSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
var len = array.length, temp;
for (var i = 0; i < len - 1; i++) {
var min = array[i];
for (var j = i + 1; j < len; j++) {
if (array[j] < min) {
temp = min;
min = array[j];
array[j] = temp;
}
}
array[i] = min;
}
return array;
} else {
return 'array is not an Array!';
}
}
最佳情况,最差情况,平均情况:T(n) = O(n^2)
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
function bubbleSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
var len = array.length, temp;
for (var i = 0; i < len - 1; i++) {
for (var j = len - 1; j >= i; j--) {
if (array[j] < array[j - 1]) {
temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
return array;
} else {
return 'array is not an Array!';
}
}
最佳情况:T(n) = O(n)
最差情况:T(n) = O(n^2)
平均情况:T(n) = O(n^2)
快速排序的基本**:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
//方法一
function quickSort(array, left, right) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array' && typeof left === 'number' && typeof right === 'number') {
if (left < right) {
var x = array[right], i = left - 1, temp;
for (var j = left; j <= right; j++) {
if (array[j] <= x) {
i++;
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
quickSort(array, left, i - 1);
quickSort(array, i + 1, right);
};
} else {
return 'array is not an Array or left or right is not a number!';
}
}
var aaa = [3, 5, 2, 9, 1];
quickSort(aaa, 0, aaa.length - 1);
console.log(aaa);
//方法二
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
最佳情况:T(n) = O(nlogn)
最差情况:T(n) = O(n^2)
平均情况:T(n) = O(nlogn)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
/*方法说明:堆排序
@param array 待排序数组*/
function heapSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
//建堆
var heapSize = array.length, temp;
for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
heapify(array, i, heapSize);
}
//堆排序
for (var j = heapSize - 1; j >= 1; j--) {
temp = array[0];
array[0] = array[j];
array[j] = temp;
heapify(array, 0, --heapSize);
}
} else {
return 'array is not an Array!';
}
}
/*方法说明:维护堆的性质
@param arr 数组
@param x 数组下标
@param len 堆大小*/
function heapify(arr, x, len) {
if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') {
var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp;
if (l < len && arr[l] > arr[largest]) {
largest = l;
}
if (r < len && arr[r] > arr[largest]) {
largest = r;
}
if (largest != x) {
temp = arr[x];
arr[x] = arr[largest];
arr[largest] = temp;
heapify(arr, largest, len);
}
} else {
return 'arr is not an Array or x is not a number!';
}
}
最佳情况,最差情况,平均情况:T(n) = O(nlogn)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
function mergeSort(array, p, r) {
if (p < r) {
var q = Math.floor((p + r) / 2);
mergeSort(array, p, q);
mergeSort(array, q + 1, r);
merge(array, p, q, r);
}
}
function merge(array, p, q, r) {
var n1 = q - p + 1, n2 = r - q, left = [], right = [], m = n = 0;
for (var i = 0; i < n1; i++) {
left[i] = array[p + i];
}
for (var j = 0; j < n2; j++) {
right[j] = array[q + 1 + j];
}
left[n1] = right[n2] = Number.MAX_VALUE;
for (var k = p; k <= r; k++) {
if (left[m] <= right[n]) {
array[k] = left[m];
m++;
} else {
array[k] = right[n];
n++;
}
}
}
最佳情况:T(n) = O(n)
最差情况:T(n) = O(nlogn)
平均情况:T(n) = O(nlogn)
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
/*方法说明:桶排序
@param array 数组
@param num 桶的数量*/
function bucketSort(array, num) {
if (array.length <= 1) {
return array;
}
var len = array.length, buckets = [], result = [], min = max = array[0], regex = '/^[1-9]+[0-9]*$/', space, n = 0;
num = num || ((num > 1 && regex.test(num)) ? num : 10);
for (var i = 1; i < len; i++) {
min = min <= array[i] ? min : array[i];
max = max >= array[i] ? max : array[i];
}
space = (max - min + 1) / num;
for (var j = 0; j < len; j++) {
var index = Math.floor((array[j] - min) / space);
if (buckets[index]) { // 非空桶,插入排序
var k = buckets[index].length - 1;
while (k >= 0 && buckets[index][k] > array[j]) {
buckets[index][k + 1] = buckets[index][k];
k--;
}
buckets[index][k + 1] = array[j];
} else { //空桶,初始化
buckets[index] = [];
buckets[index].push(array[j]);
}
}
while (n < num) {
result = result.concat(buckets[n]);
n++;
}
return result;
}
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
function countingSort(array) {
var len = array.length, B = [], C = [], min = max = array[0];
for (var i = 0; i < len; i++) {
min = min <= array[i] ? min : array[i];
max = max >= array[i] ? max : array[i];
C[array[i]] = C[array[i]] ? C[array[i]] + 1 : 1;
}
for (var j = min; j < max; j++) {
C[j + 1] = (C[j + 1] || 0) + (C[j] || 0);
}
for (var k = len - 1; k >=0; k--) {
B[C[array[k]] - 1] = array[k];
C[array[k]]--;
}
return B;
}
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心。在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路。
近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。
在callback的模型里边,我们假设需要执行一个异步队列,代码看起来可能像这样:
loadImg('a.jpg', function() {
loadImg('b.jpg', function() {
loadImg('c.jpg', function() {
console.log('all done!');
});
});
});
这也就是我们常说的回调金字塔,当异步的任务很多的时候,维护大量的callback将是一场灾难。当今Node.js大热,好像很多团队都要用它来做点东西以沾沾“洋气”,曾经跟一个运维的同学聊天,他们也是打算使用Node.js做一些事情,可是一想到js的层层回调就望而却步。
好,扯淡完毕,下面进入正题。
Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。
所谓Promise,字面上可以理解为“承诺”,就是说A调用B,B返回一个“承诺”给A,然后A就可以在写计划的时候这么写:当B返回结果给我的时候,A执行方案S1,反之如果B因为什么原因没有给到A想要的结果,那么A执行应急方案S2,这样一来,所有的潜在风险都在A的可控范围之内了。
上面这句话,翻译成代码类似:
var resB = B();
var runA = function() {
resB.then(execS1, execS2);
};
runA();
只看上面这行代码,好像看不出什么特别之处。但现实情况可能比这个复杂许多,A要完成一件事,可能要依赖不止B一个人的响应,可能需要同时向多个人询问,当收到所有的应答之后再执行下一步的方案。最终翻译成代码可能像这样:
var resB = B();
var resC = C();
...
var runA = function() {
reqB
.then(resC, execS2)
.then(resD, execS3)
.then(resE, execS4)
...
.then(execS1);
};
runA();
在这里,当每一个被询问者做出不符合预期的应答时都用了不同的处理机制。事实上,Promise规范没有要求这样做,你甚至可以不做任何的处理(即不传入then的第二个参数)或者统一处理。
好了,下面我们来认识下Promise/A+规范:
一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。
可以看到,Promise规范的内容并不算多,大家可以试着自己实现以下Promise。
以下是笔者自己在参考许多类Promise库之后简单实现的一个Promise,代码请移步promiseA。
简单分析下思路:
构造函数Promise接受一个函数resolver,可以理解为传入一个异步任务,resolver接受两个参数,一个是成功时的回调,一个是失败时的回调,这两参数和通过then传入的参数是对等的。
其次是then的实现,由于Promise要求then必须返回一个promise,所以在then调用的时候会新生成一个promise,挂在当前promise的_next上,同一个promise多次调用都只会返回之前生成的_next。
由于then方法接受的两个参数都是可选的,而且类型也没限制,可以是函数,也可以是一个具体的值,还可以是另一个promise。下面是then的具体实现:
Promise.prototype.then = function(resolve, reject) {
var next = this._next || (this._next = Promise());
var status = this.status;
var x;
if('pending' === status) {
isFn(resolve) && this._resolves.push(resolve);
isFn(reject) && this._rejects.push(reject);
return next;
}
if('resolved' === status) {
if(!isFn(resolve)) {
next.resolve(resolve);
} else {
try {
x = resolve(this.value);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;
}
if('rejected' === status) {
if(!isFn(reject)) {
next.reject(reject);
} else {
try {
x = reject(this.reason);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;
}
};
这里,then做了简化,其他promise类库的实现比这个要复杂得多,同时功能也更多,比如还有第三个参数——notify,表示promise当前的进度,这在设计文件上传等时很有用。对then的各种参数的处理是最复杂的部分,有兴趣的同学可以参看其他类Promise库的实现。
在then的基础上,应该还需要至少两个方法,分别是完成promise的状态从pending到resolved或rejected的转换,同时执行相应的回调队列,即resolve()和reject()方法。
到此,一个简单的promise就设计完成了,下面简单实现下两个promise化的函数:
function sleep(ms) {
return function(v) {
var p = Promise();
setTimeout(function() {
p.resolve(v);
});
return p;
};
};
function getImg(url) {
var p = Promise();
var img = new Image();
img.onload = function() {
p.resolve(this);
};
img.onerror = function(err) {
p.reject(err);
};
img.url = url;
return p;
};
由于Promise构造函数接受一个异步任务作为参数,所以getImg还可以这样调用:
function getImg(url) {
return Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(this);
};
img.onerror = function(err) {
reject(err);
};
img.url = url;
});
};
接下来(见证奇迹的时刻),假设有一个BT的需求要这么实现:异步获取一个json配置,解析json数据拿到里边的图片,然后按顺序队列加载图片,没张图片加载时给个loading效果
function addImg(img) {
$('#list').find('> li:last-child').html('').append(img);
};
function prepend() {
$('<li>')
.html('loading...')
.appendTo($('#list'));
};
function run() {
$('#done').hide();
getData('map.json')
.then(function(data) {
$('h4').html(data.name);
return data.list.reduce(function(promise, item) {
return promise
.then(prepend)
.then(sleep(1000))
.then(function() {
return getImg(item.url);
})
.then(addImg);
}, Promise.resolve());
})
.then(sleep(300))
.then(function() {
$('#done').show();
});
};
$('#run').on('click', run);
这里的sleep只是为了看效果加的,可猛击查看demo!当然,Node.js的例子可查看这里。
在这里,Promise.resolve(v)静态方法只是简单返回一个以v为肯定结果的promise,v可不传入,也可以是一个函数或者是一个包含then方法的对象或函数(即thenable)。
类似的静态方法还有Promise.cast(promise),生成一个以promise为肯定结果的promise;
Promise.reject(reason),生成一个以reason为否定结果的promise。
我们实际的使用场景可能很复杂,往往需要多个异步的任务穿插执行,并行或者串行同在。这时候,可以对Promise进行各种扩展,比如实现Promise.all(),接受promises队列并等待他们完成再继续,再比如Promise.any(),promises队列中有任何一个处于完成态时即触发下一步操作。
标准的Promise
可参考html5rocks的这篇文章JavaScript Promises,目前高级浏览器如Chrome、Firefox都已经内置了Promise对象,提供更多的操作接口,比如Promise.all(),支持传入一个promises数组,当所有promises都完成时执行then,还有就是更加友好强大的异常捕获,应对日常的异步编程,应该足够了。
第三方库的Promise
现今流行的各大js库,几乎都不同程度的实现了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出来的大都是Deferred对象,以jQuery(Zepto类似)为例,实现上面的getImg():
function getImg(url) {
var def = $.Deferred();
var img = new Image();
img.onload = function() {
def.resolve(this);
};
img.onerror = function(err) {
def.reject(err);
};
img.src = url;
return def.promise();
}
当然,jQuery中,很多的操作都返回的是Deferred或promise,如animate、ajax:
// animate
$('.box')
.animate({'opacity': 0}, 1000)
.promise()
.then(function() {
console.log('done');
});
// ajax
$.ajax(options).then(success, fail);
$.ajax(options).done(success).fail(fail);
// ajax queue
$.when($.ajax(options1), $.ajax(options2))
.then(function() {
console.log('all done.');
}, function() {
console.error('There something wrong.');
});
jQuery还实现了done()和fail()方法,其实都是then方法的shortcut。
处理promises队列,jQuery实现的是$.when()方法,用法和Promise.all()类似。
其他类库,这里值得一提的是when.js,本身代码不多,完整实现Promise,同时支持browser和Node.js,而且提供更加丰富的API,是个不错的选择。这里限于篇幅,不再展开。
尾声
我们看到,不管Promise实现怎么复杂,但是它的用法却很简单,组织的代码很清晰,从此不用再受callback的折磨了。
最后,Promise是如此的优雅!但Promise也只是解决了回调的深层嵌套的问题,真正简化JavaScript异步编程的还是Generator,在Node.js端,建议考虑Generator。
参考文献
JavaScript Promises
JavaScript Promises(中文)
when.js
Asynch JS: The Power Of $.Deferred
jQuery: $.Deferred()
规则01:尽量减少HTTP请求
前端优化的黄金准则指导着前端页面的优化策略:只有10%-20%的最终用户响应时间花在接受请求的HTML文档上,剩下的80%-90%时间花在为HTML文档所引用的所有组件(图片、脚本、样式表等)进行的HTTP请求上。因此,改善响应时间的最简单途径就是减少组件的数量,并由此减少HTTP请求的数量。
① 图片地图:允许在一张图片上关联多个URL,而目标URL的选择取决于用户单击了图片上的哪个位置。
② CSS Sprites:将多个图片合并到一张单独的图片,这样就大大减少了页面中图片的HTTP请求。
③ 内联图片和脚本使用data:URL(Base64编码)模式直接将图片包含在Web页面中而无需进行HTTP请求。但是此种方法存在明显缺陷:- 不受IE的欢迎;- 图片太大不宜采用这种方式,因为Base64编码之后会增加图片大小,这样页面整体的下载量会变大;- 内联图片在页面跳转的时候不会被缓存。(大图片可以使用浏览器的本地缓存,在首次访问的时候保存到浏览器缓存中,典型的是HTML5的manifest缓存机制以及LocalStorage等)。
④ 样式表的合并将页面样式定义、脚本、页面本身代码严格区分开,但是样式表、脚本也不是分割越细越好,因为没多引用一个样式表就增加一次HTPP请求,能合并的样式表尽量合并。一个网站有一个公用样式表定义,每个页面只要有一个样式表就OK啦。
规则02:使用内容发布网络(CDN的使用)
什么叫内容发布网络(CDN)?它是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。主要用于发布页面静态资源:图片、css文件、js文件等。如此,能轻易地提高响应速度。
规则03:添加Expires头
把一些css、js、图片在首次访问的时候全部缓存到浏览器本地,Web服务器使用Expires头来告诉浏览器它可以使用一个组件的当前副本,直到指定的deadline为止。
规则04:压缩组件(使用Gzip方式)
规则05:将CSS样式表放在顶部
如果将css样式定义放在页面中或者页面底部,会出现短暂白屏或者某一区域短暂白板的情况,这和浏览器的运营机制有关的,不管页面如何加载,页面都是逐步呈现的。所以在每做一个页面的时候,用Link标签把每一个样式表定义放在head中。
规则06:将javascript脚本放在底部
浏览器在加载css文件时,页面逐步呈现会被阻止,直到所有css文件加载完毕,所以要把css文件的引用放到head中去,这样在加载css文件时不会组织页面的呈现。但是对于js文件,在使用的时候,它下面所有也页面内容的呈现都会被阻塞,将脚本放在页面越靠下的地方,就意味着越多的内容能够逐步呈现。
规则07:避免使用CSS表达式
css表达式的频繁求值会导致css表达式性能低下。如果想用css表达式,可以选用只求值一次的表达式或者使用事件处理来改变css的值。
规则08:使用外部javascript和CSS
内联js和css其实比外部文件有更快的响应速度,那为什么还要用外部呢?因为使用外部的js和css可以让浏览器缓存他们,这样不仅HTML文档大小减少,而且不会增加HTTP请求数量。另外,使用外部js和css可以提高组件的可复用性。
规则09:减少DNS查询
缓存DNS查询可以很好地提高网页性能,以在使用页面中URL、图片、js文件、css文件等时,不要使用过多不同的主机名。
规则10:精简javascript
移除不必要的字符减小js文件大小,改善加载时间。包括所有的注释、不必要的空白字符。改写代码,比如函数和变量的名字会被改成很短的字符串,这样使js代码更简练更难阅读。
规则11:避免重定向
最常见的Redirect就是301和302两种。
规则12:删除重复脚本
将你使用的js代码模块化,可以很好地避免这个问题
规则13:配置ETag
Etag是URL的Entity Tag,用于标示URL对象是否改变,区分不同语言和Session等等。
规则14:使Ajax可缓存
针对页面中主动的Ajax请求返回的数据要缓存到本地,当然这个是针对短期内不会变化的数据。如果不确定数据变化周期的话,可以增加一个修改标识的判断,我正常处理过程中会给一些Ajax请求返回的数据增加一个MD5值的判断,每次请求会判断当前MD5是否变化,如果变化了取最新的数据,如果不变化,则不变。
Patrick Catanzariti 是一名自由Web开发工程师,近日国外网站 s i t e p o i n t 刊登了一篇他写的《 JavaScript Beyond the Web 》,文内介绍了JavaScript在物联网中的作用,很有意思。现 将文章翻译如下:
短短几年间,在人们眼中JavaScript已迅速成长为最有价值的语言。在Netscape Navigator浏览器初期,Javascript的诞生让我们眼前一亮,我惊奇的发现,我竟然可以修改网页上对话框的文字了。
从那时起,JavaScript成长的速度远远超过了我的预期,我们现在所看到功能强大的Web应用、移动应用、Windows 8 应用,甚至整个服务器都在使用JavaScript。
我认为最激动人心的是这种语言,现在还可以用来控制和监控你的移动电话、开关灯具、机器人,增强Google Glass,以及手或手指之间的感应等。
在这篇文章中,我会介绍一些JavaScript在“物联网”里的应用,让JavaScript开发者从而了解并能从现在开始可以从事这方面的工作。
如果你是一名JavaSc ript程序员,想通过自己的技术将互联网世界无缝的衔接起来,就要比其他程序员思考的更多,现在身边的一些高科技产品就具备着很多即诱人又有创新性的机遇。
Ninja Blocks
Ninja Blocks是一个在云端实现调用和响应电脑的设备。通过通信设备在433 mhz频段(一套共同的频率为远程控制设备)或通过USB连接。但是它需要有一个JavaScript API连接到您自己的服务器并接入 Ninja Blocks 平台。
Ninja Blocks正在每天研发和发布新的功能 ,包括以下几点:
通过Twitter开灯
通过摄像头控制消防枪
通过远程控制空调温度及开启时间
通过手机短信控制灯的颜色
Arduino
Arduino是通过一个开放平台来 控制电子设备,现已有创建完结果可供使用的JavaScript API与Arduino的平台接口。
技术开发相关负责人:
Johnny Five,主做开源JS Arduino框架
node-arduino,主做 Arduinos工作节点程序包
Noduino,主做Arduinos切换控制另一个节点和JS基本框架
我看过几个Arduino项目的例子:
通过JavaScript控制Nodebots
激光竖琴
神奇画板时钟
Raspberry Pi
Raspberry Pi是一台功能齐全、价格低廉的小型电脑,你可以将其插入电视里做任何事情,从观看高清电视到编辑电子表格等。
对于所有JavaScript爱好者来说, pijs.io 通讯云平台 允许你在上面JavaScript开发嵌入式应用程序。
另外,还可以在 Raspberry Pi 安装节点,将它作为一个服务器与设备之间传递信息。
功能
可基于Node、MongoDB、HTML5和Web Sockets运行家庭自动化系统
使用Pi上的Instagram,可以实现《飞屋旅行记》中迷你房子飞越巴黎的场景
你甚至可以得到一个“pi Cust”,把你的Raspberry Pi安装进Ninja Block里
Tessel
Tessel是一个把单片机添加到硬件设备里提供WiFi功能,通过它可以与互联网进行通信,还可以接入物联网 。它旨在通过JavaScript开发人员惯用工作流程和技能,尽可能的简化硬件设备转换成软件的成本。Tessel不仅仅只有一个JavaScript API功能,还有许多令人欣喜的功能展现给JavasScript开发者。可惜的是,需要等到2014年才可以。
功能
通过网络远程控制开发自用小工具
通过添加 Tessel模块,在硬件设备中实现 加速度传感器 、RFID、GPS或其它功能
Espruino
Espruino是一个微型JavaScript解释器,外观类似于Tessel,但其价格更低廉。在仅有有8 kb RAM的板子上,并有预置模块可以即插即用。
功能
功能类似于Tessel,可以与发动机、电能等联接,但是更适用于需要电力较小的项目。
On{X}
On{X}是一种Android应用程序,它通过JavaScript API控制你的设备和响应事件,比如本文输入、地理定位和电池寿命等。还 可以通过网络远程添加或删除设备的规则。
功能
当你站在一个地方超过20分钟,自动开启 Foursquare
当 手 机 电 力 耗 尽时 会给你的朋友发布 通知消息
如果某天会下雨,会提醒你带雨伞
Leap Motion
Leap Motion是一个令人难以置信的小装置,可以感知和回应你的手、手指和笔的动作。通过USB将Leap Motion连接到电脑就可以实现。 最厉害的是,他们已经创建了一个可以直接玩的JavaScript API,如果选定其中一个设备,你将会感觉到你已经进入虚拟的未来。
功能
创建一个绘图应用程序让用户用手指可以在空中画图
在你的网页上为用户 添加旋转功能和更多的细节,使其可以通过手的动作自然的调整它的大小等等。
可以通过手和手指的滑动控制游戏,像水果忍者
WearScript
WearScript是一个允许您通过JavaScript运行谷歌眼镜的代码库,他们为谷歌眼镜开发一个开放的应用系统,,可以快速、简单的使开发者不依靠谷歌应用商店来分享他们的创作。
功能
开发应用程序,并使谷歌眼镜回应所在位置并显示信息
创建语音来激活简单应用程序, 使生活中的每一天都更具互联网的“气息”
总结
这篇文章是JavaScript开发界最简单的介绍, 就现有技术水平而言,有关“物联网”的内容可能是正确的。但你更应该换一种角度,跳出思维定势,这样才能创造如魔法一般的奇迹。
个人觉得,让前端工程师从浏览器的技术细节上去了解 CSS 的内部实现,对 CSS 技能的掌握并不会有太多帮助。
CSS 是一门感性的语言,从技术上去深挖 CSS 在浏览器上的实现原理(How),个人觉得还不如从设计上去思考为什么会有这些现象(Why)。
比如 winter 文章中提及的几个概念,从设计上来理解的话,是很简单的,同时不需要涉及 XFC 等渲染引擎实现者才需要关心的名词。
最基础的
CSS 的基本功能是排版布局,我们拿出一张报纸,或一本书,会找出一些基本元素:
标题
段落
图片
加粗的文字
……
稍微抽象以下,以上各种元素可分为两类:块元素(标题、段落等)和行内元素(图片、粗体等)。
排版布局,首先要搞定的是多个块元素的排列。段落是个典型的块元素,多个段落之间,一般一段一段往下排就好了。这就是块元素排列的默认规律。(winter 用 BFC 解释了半天-.-)
块元素默认垂直竖排,对应的,行内元素默认水平横排。
基础概念就这么简单。
复杂一点点
在实际需求中,两个块元素有时也需要横向并排。这就引出了浮动(float)概念。通过给块元素设定宽度,并指定浮动方向,就可以实现块元素的横向并排。当然,还有很多其他技巧来实现块元素的横向并排。但只要你从感性上理解了「浮动」和「定位」的画面,其他一些布局技巧,比如负边距等,就都是 hack for fun 了。
HTML 有很好的包容性,比如允许块元素和行内元素混排,这时可以直观的理解成浏览器自动创建了一些匿名块元素来包裹行内元素。这种感性的图像化理解,对设计师非常友好。
overflow 也是解决非常实在的问题:文字溢出时,怎么处理。并不需要扯上什么 BFC 才能理解。
再举一个列子:「外边距合并」。基本规律是,在同一个布局层级里,当两个外边距相遇时,会「大鱼吃小鱼」,合并成一个。与其去研究浏览器是怎么实现的,个人觉得不如从设计层面上想想为什么会有这个需求:
从上图中可以看出,外边距合并,是为了让排版看起来更一致、更舒服。有了这层认识,不合并才奇怪呢。同时也能理解为什么水平方向上,也需要存在外边距合并。一切为了排版更美观。
其他不多说,CSS 的绝大部分概念,都可以从设计的需求上去理解。用设计的眼光去看很多技术细节,对 CSS 来说,更自然、舒服,个人觉得也更容易理解。
最后
重复下我的观点:
CSS 是一门感性的语言,从技术上去深挖 CSS 在浏览器上的实现原理(How),个人觉得还不如从设计上去思考为什么会有这些现象(Why)。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.