JavaScript历史
要了解JavaScript,首先要回顾一下JavaScript的诞生。
在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。
由于网景公司希望能在静态HTML页面上添加一些动态效果,于是一位叫布兰登·艾克(Brendan Eich)的哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
这里放上一张Brendan Eich的照片,向大师致敬!(他后来曾担任过Mozilla公司的首席技术官和短暂的首席执行官)
为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
ECMAScript
因为网景开发了JavaScript,一年后微软又模仿JavaScript开发了JScript,为了让JavaScript成为全球标准,几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。
所以简单说来就是,ECMAScript是一种语言标准,而JavaScript是网景公司对ECMAScript标准的一种实现。
那为什么不直接把JavaScript定为标准呢?因为JavaScript是网景的注册商标。
不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。
JavaScript版本
JavaScript语言是在10天时间内设计出来的,虽然语言的设计者水平非常NB,但谁也架不住“时间紧,任务重”,所以,JavaScript有很多设计缺陷,后面会慢慢讲到。
此外,由于JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本。
ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
(1)函数声明与函数表达式
函数声明
如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。函数的声明方式会得到提升,且如果有相同的函数,会覆盖。
函数表达式
如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式。
还有一种函数表达式就是被括号括住的(function foo(){})或者函数前加!等符号。
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分
new function bar(){}; // 表达式,因为它是new表达式
(function foo(){}); // 函数表达式:包含在分组操作符内
区别
函数声明会在任何表达式被解析和求值之前先被解析和求值,即使你的声明在代码的最后一行,它也会在同作用域内第一个表达式之前被解析/求值
函数声明在条件语句内虽然可以用,但是没有被标准化,也就是说不同的环境可能有不同的执行结果,所以这样情况下,最好使用函数表达式
(2)作用域
JavaScript语言的作用域仅存在于函数范围中。这是必须要牢记的一点,还有一点重要的就是作用域的提升规则。
传统的类C,Java,它们的作用域是块级作用域(block-level scope), 花括号就是一个作用域。但是对于JavaScript作用域是函数级作用域(function-level scope),比如if条件语句,就不算一个独立的作用域。
在JavaScript中,每个函数被调用时都会创建一个全新的上下文环境。因此,在函数内部定义的变量和函数就只能在函数内部访问,在外部无法访问,那么在该上下文环境中,调用的函数就提供了一个非常方便的方式来创建私有成员。也就是解释了JavaScript的作用域是function-level。
变量提升
对JavaScript解释器而言,所有的函数和变量声明都会被提升到最前面, 并且变量声明永远在前面,赋值在声明过程之后
函数的声明方式主要由两种:声明式和变量式。
function foo1(){} //声明式
var a = function foo2() {} //变量式
声明式会自动将声明放在前面并且执行赋值过程。而变量式则是先将声明提升,然后到赋值处再执行赋值
带有命名的函数变量式声明,是不会提升到作用域范围内的,比如:
var baz = function spam() {};
baz(); // vaild
spam(); // ReferenceError "spam is not defined"
tips:
任何时候,请使用var声明变量, 并放置在作用域的顶端.
推荐使用JSLint工具帮助验证js语法的规范。
(3)立即执行函数(自执行函数)
如果想让函数定义完就立即执行:
// 这么写会报错,因为这是一个函数定义:
function() {}()
// 常见的:立即执行匿名函数:
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的
// 但在前面加上一个布尔运算符就是表达式了,将执行后面的代码,也就合法实现调用,有好几种符号都可以保证匿名函数声明完就立即执行
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();
// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率
// http://twitter.com/kuvos/status/18209252090847232
new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()
在JavaScript中,如果我们需要实现block-level scope,有一种变通的方式,那就是通过自执行函数创建临时作用域:
function foo() {
var x = 1;
if (x) {
(function () {
var x = 2;
// some other code
}());
}
// x is still 1.
}
(4)闭包
闭包(closure):能够在外部访问函数内部的函数。它是Javascript语言的一个重点难点,也是js的特色,很多高级应用都要依靠闭包实现。
JavaScript变量的作用域是两种:全局变量和局部变量。Javascript语言的特殊之处,在于函数内部可以直接读取全局变量,函数外部无法读取函数内的局部变量。但通过闭包,可以在函数外面访问到内部的变量
闭包的用途
核心思想:闭包允许将函数与其所操作的某些数据(环境)关连起来。这类似于面向对象编程。在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。同时,闭包也是将函数内部和函数外部连接起来的一座桥梁。
a.读取函数内部的变量
function f1(){
var n=6;
function f2(){
alert(n); // 6
}
}
函数f2就包括在函数f1内部,f1内部的所有局部变量,对f2都可见。反之不行。这就是js特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。父对象的所有变量,对子对象都可见,反之则不成立。
b.使变量的值始终保持在内存中
function f1(){
var n=6;
function f2(){
alert(n++);
}
return f2; //注意这行代码
}
var result=f1();
result(); // 6
nAdd();
result(); // 7
这里我们在外部调用result函数,可以不断跟踪内部的n值,实际上函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
因为f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
c.用闭包模拟私有方法
对于私有方法,js并不提供原生的支持,但是可以使用闭包模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下是一个经典的例子,展现了如何使用闭包来定义公共函数,且其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):
/*使用闭包来定义公共函数,它可以访问私有函数和变量。这个方式也称为"模块模式",即定义模块的方法*/
/**
* 通过
* var modulename = (function(){
* ...
* })()
* 定义一个模块或对象
* */
var countermodule = (function () {
var praviteCounter = 0; /*模拟公有变量*/
function changeBy(val) { /*模拟公有函数,私有函数中可以去访问*/
praviteCounter += val;
}
/*返回一个(模拟的私有函数)组成的对象,其中可以访问公有函数与变量*/
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return praviteCounter;
}
};
})();
alert(countermodule.value()); /*定义完模块对象后就可以调用它及其方法*/
countermodule.decrement();
alert(countermodule.value());
countermodule.increment();
countermodule.increment();
alert(countermodule.value());
注意点:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法:退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值
闭包详细参考:
深入理解JavaScript系列(16):闭包(Closures)http://www.cnblogs.com/TomXu/archive/2012/01/31/2330252.html
(5)this对象
this是Javascript语言的一个关键字,代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。
比如:
function test(){
this.x = 1;
}
随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指调用函数的那个对象。
主要分四种情况:
(1)全局作用域下的this指向全局对象(浏览器下就是window,node.js下就是global)
(2)一般函数的this
声明一个函数,直接调用,this仍然指向全局对象
function test(){
return this
}
test() //会得到全局对象
严格模式下指向undefined。
内部函数的 this 也绑定全局对象,应该绑定到其外层函数对应的对象上,这是 JavaScript的缺陷,用that替换
(3)作为对象方法的函数的this指向调用该方法的对象(最常用)
var o = {
props:6,
f:function(){
return this.props
}
}
o.f() //得到6
(4)对象原型链上的this
var o = {
f:function(){
return this.a
}
var p = Object.Create(o) //以o为原型创建p
p.a=6
p.f() //p调用了原型的方法,原型的方法中的this先指向直接调用它的对象,即p
(5)使用 new 实例化对象时(即作为构造函数):
this指向新创建的对象,如果对象没有被声明值,则this值为undefined。
function test(){
this.a = 1
}
var b = new test() //this指向新生成的b对象
(6)一些特别的函数call(), apply()以及bind(),它们的作用都是改变函数的调用对象,this会指向这些函数的第一个参数那个对象
Function.prototype 上的 call 或者 apply 方法 以及 with等
它指向 函数参数里传入的对象。此时this 将会被显式设置为函数调用的第一个参数
function add(c,d){
return this.a + this.b + c + d
}
var o = {a:1,b:2}
add.call(o,3,4) //add()里的this指向传入的o对象
add.apply(o,[3,4]) //add()里的this指向传入的o对象
PS:js call()和apply()的区别:
区分apply,call就一句话,
foo.call(object1, arg1,arg2,arg3) == foo.apply(object1, arguments)==this.foo(arg1, arg2, arg3)
call, apply都属于Function.prototype的一个方法,所以每个Function对象实例,也就是每个方法都有call, apply属性。作用都是给一个函数传入对象和一些参数,给该对象执行该函数。
相同点:两个方法产生的作用是完全一样的:传递一些参数,调用该方法
不同点:方法传递的参数不同(call是分别传入各个参数,apply是将各个参数作为一个数据一次性传入)
bind()的this指向(有bind的会执行bind的值,没有bind的指向调用对象的值)
function f(){
return this.a
}
var g = f.bind({a:'test'})
console.log(g()) //会打印出test
var o = {a:6,f:f,g:g}
console.log(o.f(),o.g()) //会打印出6 test
(5)事件机制
几种常用的事件机制:
1.事件监听(addEventListener)
a.尽量不再使用html里οnclick=”fuctionname()”或者在js里写element.οnclick=function(){}
JS 代码与 HTML 代码耦合在一起,不便于维护和开发,且不好添加多个事件
b.element.addEventListener(事件名, 回调函数, use-capture) (removeEventListener()格式同样这三个参数)
use-capture参数: “捕获”阶段还是”冒泡”阶段中监听与触发(true捕获 false冒泡,默认false冒泡)
例子:
<button id="btn1">事件监听触发事件button>
<script>
btn1 = document.getElementById('btn1');
//1.先获取元素,再写三个参数
btn1.addEventListener('click', btn1Event, false); //2.第二个参数写已定义的函数名不要带括号,定义的函数再带括号会立即执行
function btn1Event() {
alert('btn1通过事件监听(在默认的冒泡阶段)触发了事件');
}
script>
事件触发过程:捕获(最不详细开始)->目标阶段->冒泡
2.事件代理
a.概念:
因为事件有冒泡机制,子节点的事件会顺着父级节点跑回去,
可以通过监听父级节点来实现监听子节点的功能,这就是事件代理
b.优势
(1)减少事件绑定,提升性能。之前如果需要绑定很多子节点的事件,现在只需要绑定一个父节点即可
(2)动态变化的 DOM 结构,仍可监听。当一个 DOM 动态创建之后,不会带有任何事件监听,除非重新执行事件监听函数,用事件监听无须担忧这个问题
例子1(jQuery实现事件代理,on()相当于addEventListener(),但on可以直接给子元素添加时间,更方便做事件代理,off相当于…):
<ul class="ul1">
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
<li><a href="#">监听我a>li>
ul>
<a href="#" class="add-more-items">添加更多节点a>
<script>
//3.
$('.ul1').on('click', 'a', liEvent);
function liEvent() {
alert('事件代理:绑定事件给父节点');
}
//动态生成的元素也能立即拥有事件代理的事件
$('.add-more-items').on('click', addMoreItems);
function addMoreItems() {
var item = $('我是动态生成的新节点也能调用通过父节点代理的事件 '); //5.要动态生成的元素外边加上$('')才能调用jQuery的方法与代理的事件
//一个元素里增加一个子节点用append()
$('.ul1').append(item);
}
/**
* jQuery on():
* $(selector).on(event,childSelector,data,function,map)
* event 必需。规定要从被选元素的一个或多个事件或命名空间,由空格分隔多个事件值
* childSelector 可选。只能添加到指定的子元素上的事件处理程序
* data 可选。规定传递到函数的额外数据。
* function 可选。规定当事件发生时运行的函数。
* map 规定事件映射 ({event:function, event:function, ...}),包含要添加到元素的一个或多个事件,以及当事件发生时运行的函数。
* */
script>
例子2(原生js实现事件代理,事件处理函数传入参数event,用event.target代表父元素里的子元素):
<div id="gaga">
<a href="#" gaga="xixi">1a>
<a href="#">10a>
<a href="#">100a>
<a href="#">1000a>
<a href="#">10000a>
<a href="#">100000a>
<a href="#">1000000a>
<a href="#">10000000a>
<a href="#">100000000a>
<a href="#">1000000000a>
<a href="#">10000000000a>
<a href="#">100000000000a>
div>
<script>
var gaga = document.getElementById("gaga");
gaga.addEventListener('click', eventOfChild, false);
function eventOfChild(event) {
var event = event || window.event;
var Target = event.srcElement || event.target; // srcElement这个只是支持firefox
alert(Target.tagName);
if (Target.tagName.toUpperCase() == "A") {
alert(Target.innerHTML);
}
return false; // 防止跳转
}
script>
(6)事件的Event对象
当一个事件被触发时,会创建一个事件对象(Event Object),这个对象里包含了一些有用的属性或者方法。事件对象会作为第一个参数,传递给我们的回调函数。可以使用类似于下面的代码,在浏览器中打印出事件对象:
var btn = document.getElementsByTagName('button');
btn[0].addEventListener('click', function(event) { //注意这块回调函数里的参数event就用来代表事件对象
console.log(event);
}, false);
比较常用的几个属性和方法:
type(string): 事件的名称,比如 “click”。
target(node): 事件要触发的目标节点。
currentTarget(node): 它就指向正在处理事件的元素:这恰是我们需要的。很不幸的是微软模型中并没有相似的属性, 你也可以使用”this”关键字。事件属性也提供了一个值可供访问:event.currentTarget。
bubbles (boolean): 表明该事件是否是在冒泡阶段触发的。
preventDefault (function): 这个方法可以禁止一切默认的行为,例如点击 a 标签时,会打开一个新页面,如果为 a 标签监听事件 click 同时调用该方法,则不会打开新页面。
stopPropagation (function): 很多时候,我们触发某个元素,会顺带触发出它父级身上的事件,这有时候是我们不想要的,大多数我们想要的还是事件相互独立。所以我们可以选择阻止事件冒泡,使用event.stopPropagation().
stopImmediatePropagation (function): 与 stopPropagation 类似,就是阻止触发其他监听函数。但是与 stopPropagation 不同的是,它更加 “强力”,阻止除了目标之外的事件触发,甚至阻止针对同一个目标节点的相同事件
cancelable (boolean): 这个属性表明该事件是否可以通过调用 event.preventDefault 方法来禁用默认行为。
eventPhase (number): 这个属性的数字表示当前事件触发在什么阶段。
0: none
1: 捕获
2: 目标
3: 冒泡
pageX 和 pageY (number): 这两个属性表示触发事件时,鼠标相对于页面的坐标。
isTrusted (boolean): 表明该事件是浏览器触发(用户真实操作触发),还是 JavaScript 代码触发的。
(7)原型,原型链与原型继承
a.原型:
在js中,每个函数都有一个原型属性prototype指向函数自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个__proto__**指向自己的原型,这样逐层深入直到Object对象的原型(null),这样就形成了原型链**。
tips:
prototype: 在函数身上,指向原型对象
proto: 在对象身上(包括函数创建的对象, 函数本身和原型对象),指向自身的原型
constructor: 在原型对象上,指向构造函数, 在多级继承的时候,指明构造函数方便在对象上扩展原型属性
Object.protp为null: 原型的顶端
原型的作用:
最主要的一点是数据共享,创建对象时,把公共的方法和属性挂载到原型上,避免资源浪费
b.原型链
原型对象也有自己的原型,直到对象的原型为 null 为止(也就是没有原型)。这种一级一级的链结构就称为原型链。
原型继承的模型就是JavaScript实现继承的原理。真正形成原型链的是每个对象的proto属性,而不是函数的prototype属性,这是很重要。
属性查找
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。
到查找到达原型链的顶部, 也就是 Object.prototype, (因为Object的原型的proto是null) 但是仍然没有找到指定的属性,就会返回 undefined。
c.原型继承
原型继承实现方法:
(i)原型模式:
var Parent = function(){
this.name = 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(){
this.name = 'child' ;
} ;
Child.prototype = new Parent() ;
var parent = new Parent() ;
var child = new Child() ;
console.log(parent.getName()) ; //parent
console.log(child.getName()) ; //child
这是最简单实现原型继承的方法,直接把父类的对象赋值给子类构造函数的原型,这样子类的对象就可以访问到父类以及父类构造函数的prototype中的属性
优点很明显,实现十分简单,不需要任何特殊的操作;同时缺点也很明显,如果子类需要做跟父类构造函数中相同的初始化动作,那么就得在子类构造函数中再重复一遍父类中的操作。
如果初始化工作不断增加,这种方式是不方便的。因此就有了下面一种改进的方式。
(ii)构造模式
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments) ;
} ;
Child.prototype = new Parent() ;
var parent = new Parent('myParent') ;
var child = new Child('myChild') ;
console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild
这样只需要在子类构造函数中执行一次父类的构造函数,同时又可以继承父类原型中的属性,这也比较符合原型的初衷,就是把需要复用的内容放在原型中,我们也只是继承了原型中可复用的内容。
原型继承与类继承的对比:
类继承 | 原型继承 |
---|---|
类不可变。在运行时,无法修改或者添加新的方法 | 原型是灵活的。它们可以是不可变的也可以是可变的 |
类可能会不支持多重继承 | 对象可以继承多个原型对象 |
基于类的继承比较复杂。你需要使用抽象类,接口和final类等等 | 原型继承比较简洁。你只有对象,你只需要对对象进行扩展就可以了 |
(8)js异步编程方法
js的执行环境是”单线程”(single thread),就是一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
这种模式的好处是实现起来简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为解决这个问题,js将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
“同步模式”就是上一段的模式,后一个任务等待前一个任务结束,然后再执行。
“异步模式”则是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
“异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,”异步模式”是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
js”异步模式”编程有4种方法,理解它们可以写出结构更合理、性能更出色、维护更方便的js程序。
1.回调函数
function f1(callback){ //或者直接在()里放一个匿名function
setTimeout(function(){
//f1的任务代码
callback();
},1000);
}
f1(f2);
2.事件监听(和事件委托与代理不是一回事)
f1.on('click',f2);
function f1(){
setTimeout(function(){
//f1的任务代码
f1.trigger('click');
},1000);
}
3发布/订阅
“事件”,可以理解成”信号”。假定存在一个”信号中心”,某个任务执行完就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称”观察者模式”(observer pattern)。
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
首先,f2向”信号中心”jQuery订阅”done”信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish(“done”)的意思是,f1执行完成后,向”信号中心”jQuery发布”done”信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质与”事件监听”类似,但是明显优于后者。因为我们可以通过查看”消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
4.Promises对象
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口,是ES6标准中非常重要的一个新特性。
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。
比如,f1的回调函数f2,可以写成:
f1().then(f2);
f1要进行如下改写(这里使用jQuery实现):
function f1(){
var dfd = $.Deferred();
setTimeout(function () {
// f1的任务代码
dfd.resolve();
}, 500);
return dfd.promise; //返回一个函数的promise对象
}
这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。
比如,指定多个回调函数:
f1().then(f2).then(f3);
再比如,指定发生错误时的回调函数:
f1().then(f2).fail(f3);
而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点是编写和理解相对比较难。
(1)promise
更多ES6的知识建议研究一下阮一峰前辈的《ECMAScript 6 入门》这本书(全书开源):
http://es6.ruanyifeng.com/#docs/promise
基础级
(1)js基本例子代码(页面下边还有 JavaScript 对象实例,JavaScript 浏览器对象实例,JavaScript HTML DOM 实例的链接):
http://www.runoob.com/js/js-examples.html
(2)gitbook上的前端工程师手册:
https://leohxj.gitbooks.io/front-end-database/content/
深入级
(1)汤姆大叔的深入理解JavaScript系列博客:
http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html