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 521bf3a..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 d025df7..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 单线程的本质。 @@ -271,7 +271,7 @@ function launcher() { running--; if(items.length> 0) { launcher(); - } else if(running == 0) { + } else if(running === 0) { final(results); } }); diff --git a/docs/async/promise.md b/docs/async/promise.md index 68b5c07..cb629d7 100644 --- a/docs/async/promise.md +++ b/docs/async/promise.md @@ -274,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/basic/grammar.md b/docs/basic/grammar.md index fdf6f33..b0dea02 100644 --- a/docs/basic/grammar.md +++ b/docs/basic/grammar.md @@ -238,7 +238,7 @@ JavaScript 提供`if`结构和`switch`结构,完成条件判断,即只有满 ### if 结构 -`if`结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。所谓布尔值,指的是 JavaScript 的两个特殊值,`true`表示真,`false`表示`伪`。 +`if`结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。所谓布尔值,指的是 JavaScript 的两个特殊值,`true`表示"真",`false`表示"伪"。 ```javascript if (布尔值) @@ -485,7 +485,7 @@ var msg = '数字' + n + '是' + (n % 2 === 0 ? '偶数' : '奇数'); ### while 循环 -`While`语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。 +`while`语句包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。 ```javascript while (条件) @@ -727,4 +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 06a4131..f5c266b 100644 --- a/docs/basic/history.md +++ b/docs/basic/history.md @@ -125,7 +125,7 @@ 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 的应用领域。 @@ -181,7 +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/bom/arraybuffer.md b/docs/bom/arraybuffer.md index fea14b8..89a5ddd 100644 --- a/docs/bom/arraybuffer.md +++ b/docs/bom/arraybuffer.md @@ -73,7 +73,7 @@ myBlob.type // "text/html" `Blob`具有一个实例方法`slice`,用来拷贝原来的数据,返回的也是一个`Blob`实例。 ```javascript -myBlob.slice(start,end, contentType) +myBlob.slice(start, end, contentType) ``` `slice`方法有三个参数,都是可选的。它们依次是起始的字节位置(默认为0)、结束的字节位置(默认为`size`属性的值,该位置本身将不包含在拷贝的数据之中)、新实例的数据类型(默认为空字符串)。 @@ -164,8 +164,8 @@ FileReader 对象提供四个方法,处理 Blob 对象。Blob 对象作为参 ```javascript // HTML 代码如下 -// -// +// +// function readfile(f) { var reader = new FileReader(); reader.readAsText(f); @@ -210,3 +210,4 @@ function typefile(file) { }; } ``` + diff --git a/docs/bom/cookie.md b/docs/bom/cookie.md index a18ff85..e0ae045 100644 --- a/docs/bom/cookie.md +++ b/docs/bom/cookie.md @@ -4,13 +4,15 @@ Cookie 是服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。 -Cookie 主要保存状态信息,以下是一些主要用途。 +HTTP 协议不带有状态,有些请求需要区分状态,就通过 Cookie 附带字符串,让服务器返回不一样的回应。举例来说,用户登录以后,服务器往往会在网站上留下一个 Cookie,记录用户编号(比如`id=1234`),以后每次浏览器向服务器请求数据,就会带上这个字符串,服务器从而知道是谁在请求,应该回应什么内容。 -- 对话(session)管理:保存登录、购物车等需要记录的信息。 +Cookie 的目的就是区分用户,以及放置状态信息,它的使用场景主要如下。 + +- 对话(session)管理:保存登录状态、购物车等需要记录的信息。 - 个性化信息:保存用户的偏好,比如网页的字体大小、背景色等等。 - 追踪用户:记录和分析用户行为。 -Cookie 不是一种理想的客户端储存机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端储存应该使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。 +Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。 每个 Cookie 都有以下几方面的元数据。 @@ -20,7 +22,9 @@ Cookie 不是一种理想的客户端储存机制。它的容量很小(4KB) - 所属域名(默认为当前域名) - 生效的路径(默认为当前网址) -举例来说,用户访问网址`www.example.com`,服务器在浏览器写入一个 Cookie。这个 Cookie 的所属域名为`www.example.com`,生效路径为根路径`/`。如果 Cookie 的生效路径设为`/forums`,那么这个 Cookie 只有在访问`www.example.com/forums`及其子路径时才有效。以后,浏览器访问某个路径之前,就会找出对该域名和路径有效,并且还没有到期的 Cookie,一起发送给服务器。 +举例来说,用户访问网址`www.example.com`,服务器在浏览器写入一个 Cookie。这个 Cookie 的所属域名为`www.example.com`,生效路径为根路径`/`。 + +如果 Cookie 的生效路径设为`/forums`,那么这个 Cookie 只有在访问`www.example.com/forums`及其子路径时才有效。以后,浏览器访问某个路径之前,就会找出对该域名和路径有效,并且还没有到期的 Cookie,一起发送给服务器。 用户可以设置浏览器不接受 Cookie,也可以设置不向服务器发送 Cookie。`window.navigator.cookieEnabled`属性返回一个布尔值,表示浏览器是否打开 Cookie 功能。 @@ -34,9 +38,17 @@ window.navigator.cookieEnabled // true document.cookie // "id=foo;key=bar" ``` -不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过4KB。超过限制以后,Cookie 将被忽略,不会被设置。 +不同浏览器对 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 时,`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 协议 @@ -163,9 +175,19 @@ Set-Cookie: id=a3fWa; Expires=2015年10月21日 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。 -`Path`属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,`Path`属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,`PATH`属性是`/`,那么请求`/docs`路径也会包含该 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`。 + +另一个例子是,当前域名为`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 @@ -197,7 +219,7 @@ Set-Cookie:id=a3fWa; ``` -用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,表单一般都带有一个随机 token,告诉服务器这是真实请求。 +用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止这种攻击,官网的表单一般都带有一个随机 token,官网服务器通过验证这个随机 token,确认是否为真实请求。 ```html
@@ -206,7 +228,7 @@ Set-Cookie:id=a3fWa;
``` -这种第三方网站引导发出的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。比如,Facebook 在第三方网站插入一张看不见的图片。 +这种第三方网站引导而附带发送的 Cookie,就称为第三方 Cookie。它除了用于 CSRF 攻击,还可以用于用户追踪。比如,Facebook 在第三方网站插入一张看不见的图片。 ```html @@ -320,7 +342,7 @@ document.cookie = "foo=bar; expires=2020年12月31日 23:59:59 GMT"; 各个属性的写入注意点如下。 - `path`属性必须为绝对路径,默认为当前路径。 -- `domain`属性值必须是当前发送 Cookie 的域名的一部分。比如,当前域名是`example.com`,就不能将其设为`foo.com`。该属性默认为当前的一级域名(不含二级域名)。 +- `domain`属性值必须是当前发送 Cookie 的域名的一部分。比如,当前域名是`example.com`,就不能将其设为`foo.com`。该属性默认为当前的一级域名(不含二级域名)。如果显式设置该属性,则该域名的任意子域名也可以读取 Cookie。 - `max-age`属性的值为秒数。 - `expires`属性的值为 UTC 格式,可以使用`Date.prototype.toUTCString()`进行日期格式转换。 @@ -330,9 +352,11 @@ document.cookie = "foo=bar; expires=2020年12月31日 23:59:59 GMT"; document.cookie = 'fontSize=14; ' + 'expires=' + someDate.toGMTString() + '; ' + 'path=/subdirectory; ' - + 'domain=*.example.com'; + + 'domain=example.com'; ``` +注意,上面的`domain`属性,以前的写法是`.example.com`,表示子域名也可以读取该 Cookie,新的写法可以省略前面的点。 + Cookie 的属性一旦设置完成,就没有办法读取这些属性的值。 删除一个现存 Cookie 的唯一方法,是设置它的`expires`属性为一个过去的日期。 diff --git a/docs/bom/cors.md b/docs/bom/cors.md index 368ea0c..566e1f0 100644 --- a/docs/bom/cors.md +++ b/docs/bom/cors.md @@ -1,12 +1,12 @@ # CORS 通信 -CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出`XMLHttpRequest`请求,从而克服了 AJAX 只能同源使用的限制。 +CORS 是一个 W3C 标准,全称是"跨源资源共享"(Cross-origin resource sharing),或者通俗地称为"跨域资源共享"。它允许浏览器向跨源的服务器,发出`XMLHttpRequest`请求,从而克服了 AJAX 只能同源使用的限制。 ## 简介 CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。 -整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。 +整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。 ## 两种请求 @@ -30,7 +30,7 @@ CORS 请求分成两类:简单请求(simple request)和非简单请求(n 凡是不同时满足上面两个条件,就属于非简单请求。一句话,简单请求就是简单的 HTTP 方法与简单的 HTTP 头信息的结合。 -这样划分的原因是,表单在历史上一直可以跨域发出请求。简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化,否则开发者可能转而使用表单,规避 CORS 的限制。对于非简单请求,浏览器会采用新的处理方式。 +这样划分的原因是,表单在历史上一直可以跨源发出请求。简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化,否则开发者可能转而使用表单,规避 CORS 的限制。对于非简单请求,浏览器会采用新的处理方式。 ## 简单请求 @@ -38,7 +38,7 @@ CORS 请求分成两类:简单请求(simple request)和非简单请求(n 对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个`Origin`字段。 -下面是一个例子,浏览器发现这次跨域 AJAX 请求是简单请求,就自动在头信息之中,添加一个`Origin`字段。 +下面是一个例子,浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个`Origin`字段。 ```http GET /cors HTTP/1.1 @@ -99,7 +99,7 @@ xhr.withCredentials = true; xhr.withCredentials = false; ``` -需要注意的是,如果服务器要求浏览器发送 Cookie,`Access-Control-Allow-Origin`就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨域)原网页代码中的`document.cookie`也无法读取服务器域名下的 Cookie。 +需要注意的是,如果服务器要求浏览器发送 Cookie,`Access-Control-Allow-Origin`就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的`document.cookie`也无法读取服务器域名下的 Cookie。 ## 非简单请求 @@ -107,7 +107,7 @@ xhr.withCredentials = false; 非简单请求是那种对服务器提出特殊要求的请求,比如请求方法是`PUT`或`DELETE`,或者`Content-Type`字段的类型是`application/json`。 -非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的`XMLHttpRequest`请求,否则就报错。这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器收到大量`DELETE`和`PUT`请求,这些传统的表单不可能跨域发出的请求。 +非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的`XMLHttpRequest`请求,否则就报错。这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器收到大量`DELETE`和`PUT`请求,这些传统的表单不可能跨源发出的请求。 下面是一段浏览器的 JavaScript 脚本。 @@ -162,7 +162,6 @@ Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive -Content-Type: text/plain ``` 上面的 HTTP 回应中,关键的是`Access-Control-Allow-Origin`字段,表示`http://api.bob.com`可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。 @@ -200,7 +199,7 @@ Access-Control-Max-Age: 1728000 **(1)`Access-Control-Allow-Methods`** -该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 +该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨源请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 **(2)`Access-Control-Allow-Headers`** diff --git a/docs/bom/engine.md b/docs/bom/engine.md index b42197c..8c0761e 100644 --- a/docs/bom/engine.md +++ b/docs/bom/engine.md @@ -6,7 +6,7 @@ JavaScript 是浏览器的内置脚本语言。也就是说,浏览器内置了 ## 代码嵌入网页的方法 -网页中嵌入 JavaScript 代码,主要有三种方法。 +网页中嵌入 JavaScript 代码,主要有四种方法。 - ` +``` + +上面代码中,`document.currentScript`就是`>`元素节点。 + ### document.implementation `document.implementation`属性返回一个`DOMImplementation`对象。该对象有三个方法,主要用于创建独立于当前文档的新的 Document 对象。 @@ -349,7 +366,7 @@ document.replaceChild( ); ``` -上面代码中,第一步生成一个新的 HTML 文档`doc`,然后用它的根元素`document.documentElement`替换掉`document.documentElement`。这会使得当前文档的内容全部消失,变成`hello world`。 +上面代码中,第一步生成一个新的 HTML 文档`doc`,然后用它的根元素`doc.documentElement`替换掉`document.documentElement`。这会使得当前文档的内容全部消失,变成`hello world`。 ## 方法 @@ -482,7 +499,7 @@ document.querySelectorAll('DIV, A, SCRIPT'); ### document.getElementsByTagName() -`document.getElementsByTagName`方法搜索 HTML 标签名,返回符合条件的元素。它的返回值是一个类似数组对象(`HTMLCollection`实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集。 +`document.getElementsByTagName()`方法搜索 HTML 标签名,返回符合条件的元素。它的返回值是一个类似数组对象(`HTMLCollection`实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集。 ```javascript var paras = document.getElementsByTagName('p'); @@ -491,7 +508,7 @@ paras instanceof HTMLCollection // true 上面代码返回当前文档的所有`p`元素节点。 -HTML 标签名是大小写不敏感的,因此`getElementsByTagName`方法也是大小写不敏感的。另外,返回结果中,各个成员的顺序就是它们在文档中出现的顺序。 +HTML 标签名是大小写不敏感的,因此`getElementsByTagName()`方法的参数也是大小写不敏感的。另外,返回结果中,各个成员的顺序就是它们在文档中出现的顺序。 如果传入`*`,就可以返回文档中所有 HTML 元素。 @@ -510,7 +527,7 @@ var spans = firstPara.getElementsByTagName('span'); ### document.getElementsByClassName() -`document.getElementsByClassName`方法返回一个类似数组的对象(`HTMLCollection`实例),包括了所有`class`名字符合指定条件的元素,元素的变化实时反映在返回结果中。 +`document.getElementsByClassName()`方法返回一个类似数组的对象(`HTMLCollection`实例),包括了所有`class`名字符合指定条件的元素,元素的变化实时反映在返回结果中。 ```javascript var elements = document.getElementsByClassName(names); @@ -528,7 +545,7 @@ var elements = document.getElementsByClassName('foo bar'); 注意,正常模式下,CSS 的`class`是大小写敏感的。(`quirks mode`下,大小写不敏感。) -与`getElementsByTagName`方法一样,`getElementsByClassName`方法不仅可以在`document`对象上调用,也可以在任何元素节点上调用。 +与`getElementsByTagName()`方法一样,`getElementsByClassName()`方法不仅可以在`document`对象上调用,也可以在任何元素节点上调用。 ```javascript // 非document对象上调用 @@ -537,7 +554,7 @@ var elements = rootElement.getElementsByClassName(names); ### document.getElementsByName() -`document.getElementsByName`方法用于选择拥有`name`属性的 HTML 元素(比如`
`、``、``、``、``和``等),返回一个类似数组的的对象(`NodeList`实例),因为`name`属性相同的元素可能不止一个。 +`document.getElementsByName()`方法用于选择拥有`name`属性的 HTML 元素(比如``、``、``、``、``和``等),返回一个类似数组的的对象(`NodeList`实例),因为`name`属性相同的元素可能不止一个。 ```javascript // 表单为 @@ -547,7 +564,7 @@ forms[0].tagName // "FORM" ### document.getElementById() -`document.getElementById`方法返回匹配指定`id`属性的元素节点。如果没有发现匹配的节点,则返回`null`。 +`document.getElementById()`方法返回匹配指定`id`属性的元素节点。如果没有发现匹配的节点,则返回`null`。 ```javascript var elem = document.getElementById('para1'); @@ -555,7 +572,7 @@ var elem = document.getElementById('para1'); 注意,该方法的参数是大小写敏感的。比如,如果某个节点的`id`属性是`main`,那么`document.getElementById('Main')`将返回`null`。 -`document.getElementById`方法与`document.querySelector`方法都能获取元素节点,不同之处是`document.querySelector`方法的参数使用 CSS 选择器语法,`document.getElementById`方法的参数是元素的`id`属性。 +`document.getElementById()`方法与`document.querySelector()`方法都能获取元素节点,不同之处是`document.querySelector()`方法的参数使用 CSS 选择器语法,`document.getElementById()`方法的参数是元素的`id`属性。 ```javascript document.getElementById('myElement') @@ -568,7 +585,7 @@ document.querySelector('#myElement') ### document.elementFromPoint(),document.elementsFromPoint() -`document.elementFromPoint`方法返回位于页面指定位置最上层的元素节点。 +`document.elementFromPoint()`方法返回位于页面指定位置最上层的元素节点。 ```javascript var element = document.elementFromPoint(50, 50); @@ -702,7 +719,7 @@ var element = document.getElementById('ul'); element.appendChild(docfrag); ``` -上面代码中,文档片断`docfrag`包含四个`
  • `节点,这些子节点被一次性插入了当前文档。 +上面代码中,文档片段`docfrag`包含四个`
  • `节点,这些子节点被一次性插入了当前文档。 ### document.createEvent() diff --git a/docs/dom/element.md b/docs/dom/element.md index 7593f9e..6a73887 100644 --- a/docs/dom/element.md +++ b/docs/dom/element.md @@ -1,5 +1,7 @@ # Element 节点 +## 简介 + `Element`节点对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个`Element`节点对象(以下简称元素节点)。 元素节点的`nodeType`属性都是`1`。 @@ -10,7 +12,9 @@ p.nodeName // "P" p.nodeType // 1 ``` -`Element`对象继承了`Node`接口,因此`Node`的属性和方法在`Element`对象都存在。此外,不同的 HTML 元素对应的元素节点是不一样的,浏览器使用不同的构造函数,生成不同的元素节点,比如``元素的节点对象由`HTMLAnchorElement`构造函数生成,` +
    +
    ``` diff --git a/docs/events/mouse.md b/docs/events/mouse.md index eb1a919..cd1d463 100644 --- a/docs/events/mouse.md +++ b/docs/events/mouse.md @@ -2,23 +2,30 @@ ## 鼠标事件的种类 -鼠标事件指与鼠标相关的事件,继承了`MouseEvent`接口。具体的事件主要有以下一些。 +鼠标事件主要有下面这些,所有事件都继承了`MouseEvent`接口(详见后文)。 + +(1)点击事件 + +鼠标点击相关的有四个事件。 - `click`:按下鼠标(通常是按下主按钮)时触发。 - `dblclick`:在同一个元素上双击鼠标时触发。 - `mousedown`:按下鼠标键时触发。 - `mouseup`:释放按下的鼠标键时触发。 + +`click`事件可以看成是两个事件组成的:用户在同一个位置先触发`mousedown`,再触发`mouseup`。因此,触发顺序是,`mousedown`首先触发,`mouseup`接着触发,`click`最后触发。 + +双击时,`dblclick`事件则会在`mousedown`、`mouseup`、`click`之后触发。 + +(2)移动事件 + +鼠标移动相关的有五个事件。 + - `mousemove`:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。 - `mouseenter`:鼠标进入一个节点时触发,进入子节点不会触发这个事件(详见后文)。 - `mouseover`:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件(详见后文)。 - `mouseout`:鼠标离开一个节点时触发,离开父节点也会触发这个事件(详见后文)。 - `mouseleave`:鼠标离开一个节点时触发,离开父节点不会触发这个事件(详见后文)。 -- `contextmenu`:按下鼠标右键时(上下文菜单出现前)触发,或者按下"上下文菜单键"时触发。 -- `wheel`:滚动鼠标的滚轮时触发,该事件继承的是`WheelEvent`接口。 - -`click`事件指的是,用户在同一个位置先完成`mousedown`动作,再完成`mouseup`动作。因此,触发顺序是,`mousedown`首先触发,`mouseup`接着触发,`click`最后触发。 - -`dblclick`事件则会在`mousedown`、`mouseup`、`click`之后触发。 `mouseover`事件和`mouseenter`事件,都是鼠标进入一个节点时触发。两者的区别是,`mouseenter`事件只触发一次,而只要鼠标在节点内部移动,`mouseover`事件会在子节点上触发多次。 @@ -27,7 +34,7 @@
    • item 1
    • item 2
    • -
    • item 3
    • +
    • item 3
    */ @@ -62,7 +69,7 @@ ul.addEventListener('mouseover', function (event) {
    • item 1
    • item 2
    • -
    • item 3
    • +
    • item 3
    */ @@ -90,19 +97,24 @@ ul.addEventListener('mouseout', function (event) { 上面代码中,在父节点内部离开子节点,不会触发`mouseleave`事件,但是会触发`mouseout`事件。 -## MouseEvent 接口概述 +(3)其他事件 + +- `contextmenu`:按下鼠标右键时(上下文菜单出现前)触发,或者按下"上下文"菜单键时触发。 +- `wheel`:滚动鼠标的滚轮时触发,该事件继承的是`WheelEvent`接口。 + +## MouseEvent 接口 `MouseEvent`接口代表了鼠标相关的事件,单击(click)、双击(dblclick)、松开鼠标键(mouseup)、按下鼠标键(mousedown)等动作,所产生的事件对象都是`MouseEvent`实例。此外,滚轮事件和拖拉事件也是`MouseEvent`实例。 -`MouseEvent`接口继承了`Event`接口,所以拥有`Event`的所有属性和方法。它还有自己的属性和方法。 +`MouseEvent`接口继承了`Event`接口,所以拥有`Event`的所有属性和方法,并且还提供鼠标独有的属性和方法。 -浏览器原生提供一个`MouseEvent`构造函数,用于新建一个`MouseEvent`实例。 +浏览器原生提供一个`MouseEvent()`构造函数,用于新建一个`MouseEvent`实例。 ```javascript var event = new MouseEvent(type, options); ``` -`MouseEvent`构造函数接受两个参数。第一个参数是字符串,表示事件名称;第二个参数是一个事件配置对象,该参数可选。除了`Event`接口的实例配置属性,该对象可以配置以下属性,所有属性都是可选的。 +`MouseEvent()`构造函数接受两个参数。第一个参数是字符串,表示事件名称;第二个参数是一个事件配置对象,该参数可选。除了`Event`接口的实例配置属性,该对象可以配置以下属性,所有属性都是可选的。 - `screenX`:数值,鼠标相对于屏幕的水平位置(单位像素),默认值为0,设置该属性不会移动鼠标。 - `screenY`:数值,鼠标相对于屏幕的垂直位置(单位像素),其他与`screenX`相同。 @@ -313,7 +325,7 @@ document.body.addEventListener( ```javascript /* HTML 代码如下 -
    +
    */ @@ -325,10 +337,10 @@ inner.addEventListener('mouseover', function (event) { inner.addEventListener('mouseenter', function (event) { console.log('进入' + event.target.id + ' 离开' + event.relatedTarget.id); }); -inner.addEventListener('mouseout', function () { +inner.addEventListener('mouseout', function (event) { console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id); }); -inner.addEventListener("mouseleave", function (){ +inner.addEventListener("mouseleave", function (event){ console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id); }); @@ -384,3 +396,4 @@ var wheelEvent = new WheelEvent(type, options); - `WheelEvent.deltaY`:数值,表示滚轮的垂直滚动量。 - `WheelEvent.deltaZ`:数值,表示滚轮的 Z 轴滚动量。 - `WheelEvent.deltaMode`:数值,表示上面三个属性的单位,`0`是像素,`1`是行,`2`是页。 + diff --git a/docs/features/conversion.md b/docs/features/conversion.md index deea015..f495f4b 100644 --- a/docs/features/conversion.md +++ b/docs/features/conversion.md @@ -322,13 +322,13 @@ if ('abc') { - [1, 2, 3] // NaN ``` -自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用`String`函数进行转换。如果该位置即可以是字符串,也可能是数值,那么默认转为数值。 +自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用`String()`函数进行转换。如果该位置既可以是字符串,也可能是数值,那么默认转为数值。 -由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用`Boolean`、`Number`和`String`函数进行显式转换。 +由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用`Boolean()`、`Number()`和`String()`函数进行显式转换。 ### 自动转换为布尔值 -JavaScript 遇到预期为布尔值的地方(比如`if`语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用`Boolean`函数。 +JavaScript 遇到预期为布尔值的地方(比如`if`语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用`Boolean()`函数。 因此除了以下五个值,其他都是自动转为`true`。 @@ -351,7 +351,7 @@ if ( !undefined } // true ``` -下面两种写法,有时也用于将一个表达式转为布尔值。它们内部调用的也是`Boolean`函数。 +下面两种写法,有时也用于将一个表达式转为布尔值。它们内部调用的也是`Boolean()`函数。 ```javascript // 写法一 @@ -392,7 +392,7 @@ obj.width + 20 // "10020" ### 自动转换为数值 -JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用`Number`函数。 +JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用`Number()`函数。 除了加法运算符(`+`)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。 diff --git a/docs/features/error.md b/docs/features/error.md index c0b8ebb..670e377 100644 --- a/docs/features/error.md +++ b/docs/features/error.md @@ -9,7 +9,7 @@ var err = new Error('出错了'); err.message // "出错了" ``` -上面代码中,我们调用`Error`构造函数,生成一个实例对象`err`。`Error`构造函数接受一个参数,表示错误提示,可以从实例的`message`属性读到这个参数。抛出`Error`实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。 +上面代码中,我们调用`Error()`构造函数,生成一个实例对象`err`。`Error()`构造函数接受一个参数,表示错误提示,可以从实例的`message`属性读到这个参数。抛出`Error`实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。 JavaScript 语言标准只提到,`Error`实例对象必须有`message`属性,表示出错时的提示信息,没有提到其他属性。大多数 JavaScript 引擎,对`Error`实例还提供`name`和`stack`属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。 @@ -79,19 +79,15 @@ unknownVariable // Uncaught ReferenceError: unknownVariable is not defined ``` -另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者`this`赋值。 +另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果赋值。 ```javascript // 等号左侧不是变量 console.log() = 1 // Uncaught ReferenceError: Invalid left-hand side in assignment - -// this 对象不能手动赋值 -this = 1 -// ReferenceError: Invalid left-hand side in assignment ``` -上面代码对函数`console.log`的运行结果和`this`赋值,结果都引发了`ReferenceError`错误。 +上面代码对函数`console.log`的运行结果赋值,结果引发了`ReferenceError`错误。 ### RangeError 对象 @@ -109,7 +105,7 @@ new Array(-1) ```javascript new 123 -// Uncaught TypeError: number is not a func +// Uncaught TypeError: 123 is not a constructor var obj = {}; obj.unknownMethod() @@ -170,13 +166,15 @@ new UserError('这是自定义的错误!'); `throw`语句的作用是手动中断程序执行,抛出一个错误。 ```javascript +var x = -1; + if (x <= 0) { throw new Error('x 必须为正数'); } -// Uncaught ReferenceError: x is not defined +// Uncaught Error: x 必须为正数 ``` -上面代码中,如果变量`x`小于等于`0`,就手动抛出一个错误,告诉用户`x`的值不正确,整个程序就会在这里中断执行。可以看到,`throw`抛出的错误就是它的参数,这里是一个`Error`实例。 +上面代码中,如果变量`x`小于等于`0`,就手动抛出一个错误,告诉用户`x`的值不正确,整个程序就会在这里中断执行。可以看到,`throw`抛出的错误就是它的参数,这里是一个`Error`对象的实例。 `throw`也可以抛出自定义错误。 @@ -406,7 +404,7 @@ result 上面代码中,`catch`代码块结束执行之前,会先执行`finally`代码块。 -`catch`代码块之中,触发转入`finally`代码快的标志,不仅有`return`语句,还有`throw`语句。 +`catch`代码块之中,触发转入`finally`代码块的标志,不仅有`return`语句,还有`throw`语句。 ```javascript function f() { diff --git a/docs/features/style.md b/docs/features/style.md index 8947aca..0ea77e1 100644 --- a/docs/features/style.md +++ b/docs/features/style.md @@ -486,7 +486,7 @@ function doAction(action) { ## 参考链接 -- Eric Elliott, Programming JavaScript Applications, [Chapter 2. JavaScript Style Guide](http://chimera.labs.oreilly.com/books/1234000000262/ch02.html), O'Reilly, 2013 +- Eric Elliott, Programming JavaScript Applications, [Chapter 2. JavaScript Style Guide](https://www.oreilly.com/library/view/programming-javascript-applications/9781491950289/), O'Reilly, 2014 - Axel Rauschmayer, [A meta style guide for JavaScript](http://www.2ality.com/2013/07/meta-style-guide.html) - Axel Rauschmayer, [Automatic semicolon insertion in JavaScript](http://www.2ality.com/2011/05/semicolon-insertion.html) - Rod Vagg, [JavaScript and Semicolons](http://dailyjs.com/2012/04/19/semicolons/) diff --git a/docs/oop/new.md b/docs/oop/new.md index aad7aeb..ae7d32d 100644 --- a/docs/oop/new.md +++ b/docs/oop/new.md @@ -12,11 +12,11 @@ JavaScript 语言具有很强的面向对象编程能力,本章介绍 JavaScri **(1)对象是单个实物的抽象。** -一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。 +一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个远程服务器连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。 **(2)对象是一个容器,封装了属性(property)和方法(method)。** -属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为`animal`对象,使用"属性"记录具体是那一种动物,使用"方法"表示动物的某种行为(奔跑、捕猎、休息等等)。 +属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为`animal`对象,使用"属性"记录具体是哪一种动物,使用"方法"表示动物的某种行为(奔跑、捕猎、休息等等)。 ## 构造函数 @@ -26,7 +26,7 @@ JavaScript 语言具有很强的面向对象编程能力,本章介绍 JavaScri JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓"构造函数",就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。 -构造函数就是一个普通的函数,但是有自己的特征和用法。 +构造函数就是一个普通的函数,但具有自己的特征和用法。 ```javascript var Vehicle = function () { diff --git a/docs/oop/object.md b/docs/oop/object.md index f376b16..a7e0340 100644 --- a/docs/oop/object.md +++ b/docs/oop/object.md @@ -62,7 +62,7 @@ F.call(f); 生成实例对象的常用方法是,使用`new`命令让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢? -JavaScript 提供了`Object.create`方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。 +JavaScript 提供了`Object.create()`方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。 ```javascript // 原型对象 @@ -80,9 +80,9 @@ B.print() // hello B.print === A.print // true ``` -上面代码中,`Object.create`方法以`A`对象为原型,生成了`B`对象。`B`继承了`A`的所有属性和方法。 +上面代码中,`Object.create()`方法以`A`对象为原型,生成了`B`对象。`B`继承了`A`的所有属性和方法。 -实际上,`Object.create`方法可以用下面的代码代替。 +实际上,`Object.create()`方法可以用下面的代码代替。 ```javascript if (typeof Object.create !== 'function') { @@ -94,7 +94,7 @@ if (typeof Object.create !== 'function') { } ``` -上面代码表明,`Object.create`方法的实质是新建一个空的构造函数`F`,然后让`F.prototype`属性指向参数对象`obj`,最后返回一个`F`的实例,从而实现让该实例继承`obj`的属性。 +上面代码表明,`Object.create()`方法的实质是新建一个空的构造函数`F`,然后让`F.prototype`属性指向参数对象`obj`,最后返回一个`F`的实例,从而实现让该实例继承`obj`的属性。 下面三种方式生成的新对象是等价的。 @@ -104,7 +104,7 @@ var obj2 = Object.create(Object.prototype); var obj3 = new Object(); ``` -如果想要生成一个不继承任何属性(比如没有`toString`和`valueOf`方法)的对象,可以将`Object.create`的参数设为`null`。 +如果想要生成一个不继承任何属性(比如没有`toString()`和`valueOf()`方法)的对象,可以将`Object.create()`的参数设为`null`。 ```javascript var obj = Object.create(null); @@ -113,9 +113,9 @@ obj.valueOf() // TypeError: Object [object Object] has no method 'valueOf' ``` -上面代码中,对象`obj`的原型是`null`,它就不具备一些定义在`Object.prototype`对象上面的属性,比如`valueOf`方法。 +上面代码中,对象`obj`的原型是`null`,它就不具备一些定义在`Object.prototype`对象上面的属性,比如`valueOf()`方法。 -使用`Object.create`方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。 +使用`Object.create()`方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。 ```javascript Object.create() @@ -124,7 +124,7 @@ Object.create(123) // TypeError: Object prototype may only be an Object or null ``` -`Object.create`方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。 +`Object.create()`方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。 ```javascript var obj1 = { p: 1 }; @@ -136,7 +136,7 @@ obj2.p // 2 上面代码中,修改对象原型`obj1`会影响到实例对象`obj2`。 -除了对象的原型,`Object.create`方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性。 +除了对象的原型,`Object.create()`方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性。 ```javascript var obj = Object.create({}, { @@ -160,7 +160,7 @@ obj.p1 = 123; obj.p2 = 'abc'; ``` -`Object.create`方法生成的对象,继承了它的原型对象的构造函数。 +`Object.create()`方法生成的对象,继承了它的原型对象的构造函数。 ```javascript function A() {} diff --git a/docs/oop/prototype.md b/docs/oop/prototype.md index f01c701..d1ae8df 100644 --- a/docs/oop/prototype.md +++ b/docs/oop/prototype.md @@ -262,7 +262,7 @@ v instanceof Vehicle // true 上面代码中,对象`v`是构造函数`Vehicle`的实例,所以返回`true`。 -`instanceof`运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。 +`instanceof`运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。 ```javascript v instanceof Vehicle @@ -270,7 +270,7 @@ v instanceof Vehicle Vehicle.prototype.isPrototypeOf(v) ``` -上面代码中,`Object.prototype.isPrototypeOf`的详细解释见后文。 +上面代码中,`Vehicle`是对象`v`的构造函数,它的原型对象是`Vehicle.prototype`,`isPrototypeOf()`方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型,详细解释见后文。 由于`instanceof`检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回`true`。 @@ -298,10 +298,10 @@ null instanceof Object // false ```javascript var obj = Object.create(null); typeof obj // "object" -Object.create(null) instanceof Object // false +obj instanceof Object // false ``` -上面代码中,`Object.create(null)`返回一个新对象`obj`,它的原型是`null`(`Object.create`的详细介绍见后文)。右边的构造函数`Object`的`prototype`属性,不在左边的原型链上,因此`instanceof`就认为`obj`不是`Object`的实例。但是,只要一个对象的原型不是`null`,`instanceof`运算符的判断就不会失真。 +上面代码中,`Object.create(null)`返回一个新对象`obj`,它的原型是`null`(`Object.create()`的详细介绍见后文)。右边的构造函数`Object`的`prototype`属性,不在左边的原型链上,因此`instanceof`就认为`obj`不是`Object`的实例。这是唯一的`instanceof`运算符判断会失真的情况(一个对象的原型是`null`)。 `instanceof`运算符的一个用处,是判断值的类型。 @@ -637,4 +637,4 @@ var module1 = (function (,ドル YAHOO) { ## 参考链接 -- [JavaScript Modules: A Beginner’s Guide](https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc), by Preethi Kasireddy +- [JavaScript Modules: A Beginner’s Guide](https://www.freecodecamp.org/news/javascript-modules-a-beginner-s-guide-783f7d7a5fcc), by Preethi Kasireddy diff --git a/docs/oop/strict.md b/docs/oop/strict.md index 3dffbe6..cc339c8 100644 --- a/docs/oop/strict.md +++ b/docs/oop/strict.md @@ -12,7 +12,7 @@ - 明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。 - 增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。 -- 提高编译器效率,增加运行速度。 +- 提高编译器效率,提升运行速度。 - 为未来新版本的 JavaScript 语法做好铺垫。 总之,严格模式体现了 JavaScript 更合理、更安全、更严谨的发展方向。 @@ -108,7 +108,7 @@ function notStrict() { ```javascript // 对只读属性赋值会报错 'use strict'; -Object.defineProperty({}, 'a', { +var obj = Object.defineProperty({}, 'a', { value: 37, writable: false }); diff --git a/docs/oop/this.md b/docs/oop/this.md index ca6158b..6792c30 100644 --- a/docs/oop/this.md +++ b/docs/oop/this.md @@ -679,26 +679,26 @@ Array.prototype.slice.apply({length: 1}) // [undefined] 前面的按钮点击事件的例子,可以改写如下。 ```javascript -var o = new Object(); +var obj = new Object(); -o.f = function () { - console.log(this === o); +var func = function () { + console.log(this === obj); } -var f = function (){ - o.f.apply(o); - // 或者 o.f.call(o); +var handler = function (){ + func.apply(obj); + // 或者 func.call(obj); }; // jQuery 的写法 -$('#button').on('click', f); +$('#button').on('click', handler); ``` -上面代码中,点击按钮以后,控制台将会显示`true`。由于`apply`方法(或者`call`方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。更简洁的写法是采用下面介绍的`bind`方法。 +上面代码中,点击按钮以后,控制台将会显示`true`。由于`apply()`方法(或者`call()`方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。更简洁的写法是采用下面介绍的`bind()`方法。 ### Function.prototype.bind() -`bind`方法用于将函数体内的`this`绑定到某个对象,然后返回一个新函数。 +`bind()`方法用于将函数体内的`this`绑定到某个对象,然后返回一个新函数。 ```javascript var d = new Date(); @@ -708,16 +708,16 @@ var print = d.getTime; print() // Uncaught TypeError: this is not a Date object. ``` -上面代码中,我们将`d.getTime`方法赋给变量`print`,然后调用`print`就报错了。这是因为`getTime`方法内部的`this`,绑定`Date`对象的实例,赋给变量`print`以后,内部的`this`已经不指向`Date`对象的实例了。 +上面代码中,我们将`d.getTime()`方法赋给变量`print`,然后调用`print()`就报错了。这是因为`getTime()`方法内部的`this`,绑定`Date`对象的实例,赋给变量`print`以后,内部的`this`已经不指向`Date`对象的实例了。 -`bind`方法可以解决这个问题。 +`bind()`方法可以解决这个问题。 ```javascript var print = d.getTime.bind(d); print() // 1481869925657 ``` -上面代码中,`bind`方法将`getTime`方法内部的`this`绑定到`d`对象,这时就可以安全地将这个方法赋值给其他变量了。 +上面代码中,`bind()`方法将`getTime()`方法内部的`this`绑定到`d`对象,这时就可以安全地将这个方法赋值给其他变量了。 `bind`方法的参数就是所要绑定`this`的对象,下面是一个更清晰的例子。 @@ -734,7 +734,7 @@ func(); counter.count // 1 ``` -上面代码中,`counter.inc`方法被赋值给变量`func`。这时必须用`bind`方法将`inc`内部的`this`,绑定到`counter`,否则就会出错。 +上面代码中,`counter.inc()`方法被赋值给变量`func`。这时必须用`bind()`方法将`inc()`内部的`this`,绑定到`counter`,否则就会出错。 `this`绑定到其他对象也是可以的。 @@ -754,9 +754,9 @@ func(); obj.count // 101 ``` -上面代码中,`bind`方法将`inc`方法内部的`this`,绑定到`obj`对象。结果调用`func`函数以后,递增的就是`obj`内部的`count`属性。 +上面代码中,`bind()`方法将`inc()`方法内部的`this`,绑定到`obj`对象。结果调用`func`函数以后,递增的就是`obj`内部的`count`属性。 -`bind`还可以接受更多的参数,将这些参数绑定原函数的参数。 +`bind()`还可以接受更多的参数,将这些参数绑定原函数的参数。 ```javascript var add = function (x, y) { @@ -772,9 +772,9 @@ var newAdd = add.bind(obj, 5); newAdd(5) // 20 ``` -上面代码中,`bind`方法除了绑定`this`对象,还将`add`函数的第一个参数`x`绑定成`5`,然后返回一个新函数`newAdd`,这个函数只要再接受一个参数`y`就能运行了。 +上面代码中,`bind()`方法除了绑定`this`对象,还将`add()`函数的第一个参数`x`绑定成`5`,然后返回一个新函数`newAdd()`,这个函数只要再接受一个参数`y`就能运行了。 -如果`bind`方法的第一个参数是`null`或`undefined`,等于将`this`绑定到全局对象,函数运行时`this`指向顶层对象(浏览器为`window`)。 +如果`bind()`方法的第一个参数是`null`或`undefined`,等于将`this`绑定到全局对象,函数运行时`this`指向顶层对象(浏览器为`window`)。 ```javascript function add(x, y) { @@ -785,19 +785,19 @@ var plus5 = add.bind(null, 5); plus5(10) // 15 ``` -上面代码中,函数`add`内部并没有`this`,使用`bind`方法的主要目的是绑定参数`x`,以后每次运行新函数`plus5`,就只需要提供另一个参数`y`就够了。而且因为`add`内部没有`this`,所以`bind`的第一个参数是`null`,不过这里如果是其他对象,也没有影响。 +上面代码中,函数`add()`内部并没有`this`,使用`bind()`方法的主要目的是绑定参数`x`,以后每次运行新函数`plus5()`,就只需要提供另一个参数`y`就够了。而且因为`add()`内部没有`this`,所以`bind()`的第一个参数是`null`,不过这里如果是其他对象,也没有影响。 -`bind`方法有一些使用注意点。 +`bind()`方法有一些使用注意点。 **(1)每一次返回一个新函数** -`bind`方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。 +`bind()`方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。 ```javascript element.addEventListener('click', o.m.bind(o)); ``` -上面代码中,`click`事件绑定`bind`方法生成的一个匿名函数。这样会导致无法取消绑定,所以,下面的代码是无效的。 +上面代码中,`click`事件绑定`bind()`方法生成的一个匿名函数。这样会导致无法取消绑定,所以下面的代码是无效的。 ```javascript element.removeEventListener('click', o.m.bind(o)); @@ -814,7 +814,7 @@ element.removeEventListener('click', listener); **(2)结合回调函数使用** -回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含`this`的方法直接当作回调函数。解决方法就是使用`bind`方法,将`counter.inc`绑定`counter`。 +回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含`this`的方法直接当作回调函数。解决方法就是使用`bind()`方法,将`counter.inc()`绑定`counter`。 ```javascript var counter = { @@ -833,7 +833,7 @@ callIt(counter.inc.bind(counter)); counter.count // 1 ``` -上面代码中,`callIt`方法会调用回调函数。这时如果直接把`counter.inc`传入,调用时`counter.inc`内部的`this`就会指向全局对象。使用`bind`方法将`counter.inc`绑定`counter`以后,就不会有这个问题,`this`总是指向`counter`。 +上面代码中,`callIt()`方法会调用回调函数。这时如果直接把`counter.inc`传入,调用时`counter.inc()`内部的`this`就会指向全局对象。使用`bind()`方法将`counter.inc`绑定`counter`以后,就不会有这个问题,`this`总是指向`counter`。 还有一种情况比较隐蔽,就是某些数组方法可以接受一个函数当作参数。这些函数内部的`this`指向,很可能也会出错。 @@ -852,7 +852,7 @@ obj.print() // 没有任何输出 ``` -上面代码中,`obj.print`内部`this.times`的`this`是指向`obj`的,这个没有问题。但是,`forEach`方法的回调函数内部的`this.name`却是指向全局对象,导致没有办法取到值。稍微改动一下,就可以看得更清楚。 +上面代码中,`obj.print`内部`this.times`的`this`是指向`obj`的,这个没有问题。但是,`forEach()`方法的回调函数内部的`this.name`却是指向全局对象,导致没有办法取到值。稍微改动一下,就可以看得更清楚。 ```javascript obj.print = function () { @@ -867,7 +867,7 @@ obj.print() // true ``` -解决这个问题,也是通过`bind`方法绑定`this`。 +解决这个问题,也是通过`bind()`方法绑定`this`。 ```javascript obj.print = function () { @@ -882,9 +882,9 @@ obj.print() // 张三 ``` -**(3)结合`call`方法使用** +**(3)结合`call()`方法使用** -利用`bind`方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的`slice`方法为例。 +利用`bind()`方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的`slice()`方法为例。 ```javascript [1, 2, 3].slice(0, 1) // [1] @@ -892,9 +892,9 @@ obj.print() Array.prototype.slice.call([1, 2, 3], 0, 1) // [1] ``` -上面的代码中,数组的`slice`方法从`[1, 2, 3]`里面,按照指定位置和长度切分出另一个数组。这样做的本质是在`[1, 2, 3]`上面调用`Array.prototype.slice`方法,因此可以用`call`方法表达这个过程,得到同样的结果。 +上面的代码中,数组的`slice`方法从`[1, 2, 3]`里面,按照指定的开始位置和结束位置,切分出另一个数组。这样做的本质是在`[1, 2, 3]`上面调用`Array.prototype.slice()`方法,因此可以用`call`方法表达这个过程,得到同样的结果。 -`call`方法实质上是调用`Function.prototype.call`方法,因此上面的表达式可以用`bind`方法改写。 +`call()`方法实质上是调用`Function.prototype.call()`方法,因此上面的表达式可以用`bind()`方法改写。 ```javascript var slice = Function.prototype.call.bind(Array.prototype.slice); diff --git a/docs/operators/bit.md b/docs/operators/bit.md index f02a028..3c86e3d 100644 --- a/docs/operators/bit.md +++ b/docs/operators/bit.md @@ -303,7 +303,7 @@ var FLAG_D = 8; // 1000 上面代码设置 A、B、C、D 四个开关,每个开关分别占有一个二进制位。 -然后,就可以用二进制与运算检验,当前设置是否打开了指定开关。 +然后,就可以用二进制与运算,检查当前设置是否打开了指定开关。 ```javascript var flags = 5; // 二进制的0101 @@ -331,6 +331,8 @@ var mask = FLAG_A | FLAG_B | FLAG_D; flags = flags | mask; ``` +上面代码中,计算后得到的`flags`变量,代表三个开关的二进制位都打开了。 + 二进制与运算可以将当前设置中凡是与开关设置不一样的项,全部关闭。 ```javascript @@ -351,6 +353,6 @@ flags = ~flags; ## 参考链接 -- Michal Budzynski, [JavaScript: The less known parts. Bitwise Operators](http://michalbe.blogspot.co.uk/2013/03/javascript-less-known-parts-bitwise.html) +- Michal Budzynski, [JavaScript: The less known parts. Bitwise Operators](https://michalbe.blogspot.com/2013/03/javascript-less-known-parts-bitwise.html) - Axel Rauschmayer, [Basic JavaScript for the impatient programmer](http://www.2ality.com/2013/06/basic-javascript.html) - Mozilla Developer Network, [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) diff --git a/docs/operators/boolean.md b/docs/operators/boolean.md index 87c6805..caeed9c 100644 --- a/docs/operators/boolean.md +++ b/docs/operators/boolean.md @@ -96,7 +96,7 @@ true && 'foo' && '' && 4 && 'foo' && true // 3 ``` -上面代码中,例一里面,第一个布尔值为`false`的表达式为第三个表达式,所以得到一个空字符串。例二里面,所有表达式的布尔值都是`true`,所有返回最后一个表达式的值`3`。 +上面代码中,例一里面,第一个布尔值为`false`的表达式为第三个表达式,所以得到一个空字符串。例二里面,所有表达式的布尔值都是`true`,所以返回最后一个表达式的值`3`。 ## 或运算符(||) diff --git a/docs/operators/comparison.md b/docs/operators/comparison.md index c348da5..002254a 100644 --- a/docs/operators/comparison.md +++ b/docs/operators/comparison.md @@ -74,7 +74,7 @@ true> false // true 上面代码中,字符串和布尔值都会先转成数值,再进行比较。 -这里需要注意与`NaN`的比较。任何值(包括`NaN`本身)与`NaN`比较,返回的都是`false`。 +这里需要注意与`NaN`的比较。任何值(包括`NaN`本身)与`NaN`使用非相等运算符进行比较,返回的都是`false`。 ```javascript 1> NaN // false @@ -99,7 +99,7 @@ x> '11' // true x.valueOf = function () { return '1' }; x> '11' // false -// 等同于 [2].valueOf()> '11' +// 等同于 (function () { return '1' })()> '11' // 即 '1'> '11' ``` @@ -114,8 +114,8 @@ x> '11' // false // 等同于 [2].valueOf().toString()> [11].valueOf().toString() // 即 '2'> '11' -{ x: 2 }>= { x: 1 } // true -// 等同于 { x: 2 }.valueOf().toString()>= { x: 1 }.valueOf().toString() +({ x: 2 })>= ({ x: 1 }) // true +// 等同于 ({ x: 2 }).valueOf().toString()>= ({ x: 1 }).valueOf().toString() // 即 '[object Object]'>= '[object Object]' ``` @@ -227,7 +227,7 @@ v1 === v2 // true 1 === 1.0 ``` -比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。下面分成四种情况,讨论不同类型的值互相比较的规则。 +比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。下面分成几种情况,讨论不同类型的值互相比较的规则。 **(1)原始类型值** @@ -272,38 +272,61 @@ v1 === v2 // true 对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较。 +具体来说,先调用对象的`valueOf()`方法,如果得到原始类型的值,就按照上一小节的规则,互相比较;如果得到的还是对象,则再调用`toString()`方法,得到字符串形式,再进行比较。 + +下面是数组与原始类型值比较的例子。 + ```javascript -// 对象与数值比较时,对象转为数值 +// 数组与数值的比较 [1] == 1 // true -// 等同于 Number([1]) == 1 -// 对象与字符串比较时,对象转为字符串 +// 数组与字符串的比较 [1] == '1' // true -// 等同于 String([1]) == '1' [1, 2] == '1,2' // true -// 等同于 String([1, 2]) == '1,2' -// 对象与布尔值比较时,两边都转为数值 +// 对象与布尔值的比较 [1] == true // true -// 等同于 Number([1]) == Number(true) [2] == true // false -// 等同于 Number([2]) == Number(true) ``` -上面代码中,数组`[1]`与数值进行比较,会先转成数值,再进行比较;与字符串进行比较,会先转成字符串,再进行比较;与布尔值进行比较,对象和布尔值都会先转成数值,再进行比较。 +上面例子中,JavaScript 引擎会先对数组`[1]`调用数组的`valueOf()`方法,由于返回的还是一个数组,所以会接着调用数组的`toString()`方法,得到字符串形式,再按照上一小节的规则进行比较。 + +下面是一个更直接的例子。 + +```javascript +const obj = { + valueOf: function () { + console.log('执行 valueOf()'); + return obj; + }, + toString: function () { + console.log('执行 toString()'); + return 'foo'; + } +}; + +obj == 'foo' +// 执行 valueOf() +// 执行 toString() +// true +``` + +上面例子中,`obj`是一个自定义了`valueOf()`和`toString()`方法的对象。这个对象与字符串`'foo'`进行比较时,会依次调用`valueOf()`和`toString()`方法,最后返回`'foo'`,所以比较结果是`true`。 **(3)undefined 和 null** -`undefined`和`null`与其他类型的值比较时,结果都为`false`,它们互相比较时结果为`true`。 +`undefined`和`null`只有与自身比较,或者互相比较时,才会返回`true`;与其他类型的值比较时,结果都为`false`。 ```javascript +undefined == undefined // true +null == null // true +undefined == null // true + false == null // false false == undefined // false 0 == null // false 0 == undefined // false - -undefined == null // true ``` **(4)相等运算符的缺点** diff --git a/docs/operators/priority.md b/docs/operators/priority.md index 8b2a65a..d347f87 100644 --- a/docs/operators/priority.md +++ b/docs/operators/priority.md @@ -162,34 +162,56 @@ f() // 1 ### 左结合与右结合 -对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的"左结合"(left-to-right associativity),即从左边开始计算。 +对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题。 + +```javascript +a OP b OP c +``` + +上面代码中,`OP`表示运算符。它可以有两种解释方式。 + +```javascript +// 方式一 +(a OP b) OP c + +// 方式二 +a OP (b OP c) +``` + +上面的两种方式,得到的计算结果往往是不一样的。方式一是将左侧两个运算数结合在一起,采用这种解释方式的运算符,称为"左结合"(left-to-right associativity)运算符;方式二是将右侧两个运算数结合在一起,这样的运算符称为"右结合"运算符(right-to-left associativity)。 + +JavaScript 语言的大多数运算符是"左结合",请看下面加法运算符的例子。 ```javascript x + y + z + +// 引擎解释如下 +(x + y) + z ``` -上面代码先计算最左边的`x`与`y`的和,然后再计算与`z`的和。 +上面代码中,`x`与`y`结合在一起,它们的预算结果再与`z`进行运算。 -但是少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的"右结合"(right-to-left associativity)。其中,最主要的是赋值运算符(`=`)和三元条件运算符(`?:`)。 +少数运算符是"右结合",其中最主要的是赋值运算符(`=`)和三元条件运算符(`?:`)。 ```javascript w = x = y = z; q = a ? b : c ? d : e ? f : g; ``` -上面代码的运算结果,相当于下面的样子。 +上面代码的解释方式如下。 ```javascript w = (x = (y = z)); q = a ? b : (c ? d : (e ? f : g)); ``` -上面的两行代码,各有三个等号运算符和三个三元运算符,都是先计算最右边的那个运算符。 +上面的两行代码,都是右侧的运算数结合在一起。 -指数运算符(`**`)也是右结合的。 +另外,指数运算符(`**`)也是右结合。 ```javascript -// 相当于 2 ** (3 ** 2) 2 ** 3 ** 2 +// 相当于 2 ** (3 ** 2) // 512 ``` + diff --git a/docs/stdlib/array.md b/docs/stdlib/array.md index b8736d4..e7b4359 100644 --- a/docs/stdlib/array.md +++ b/docs/stdlib/array.md @@ -10,17 +10,19 @@ arr.length // 2 arr // [ empty x 2 ] ``` -上面代码中,`Array`构造函数的参数`2`,表示生成一个两个成员的数组,每个位置都是空值。 +上面代码中,`Array()`构造函数的参数`2`,表示生成一个两个成员的数组,每个位置都是空值。 -如果没有使用`new`,运行结果也是一样的。 +如果没有使用`new`关键字,运行结果也是一样的。 ```javascript -var arr = new Array(2); -// 等同于 var arr = Array(2); +// 等同于 +var arr = new Array(2); ``` -`Array`构造函数有一个很大的缺陷,就是不同的参数,会导致它的行为不一致。 +考虑到语义性,以及与其他构造函数用法保持一致,建议总是加上`new`。 + +`Array()`构造函数有一个很大的缺陷,不同的参数个数会导致不一致的行为。 ```javascript // 无参数时,返回一个空数组 @@ -44,7 +46,7 @@ new Array(1, 2) // [1, 2] new Array('a', 'b', 'c') // ['a', 'b', 'c'] ``` -可以看到,`Array`作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组字面量是更好的做法。 +可以看到,`Array()`作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组字面量是更好的做法。 ```javascript // bad @@ -54,7 +56,7 @@ var arr = new Array(1, 2); var arr = [1, 2]; ``` -注意,如果参数是一个正整数,返回数组的成员都是空位。虽然读取的时候返回`undefined`,但实际上该位置没有任何值。虽然可以取到`length`属性,但是取不到键名。 +注意,如果参数是一个正整数,返回数组的成员都是空位。虽然读取的时候返回`undefined`,但实际上该位置没有任何值。虽然这时可以读取到`length`属性,但是取不到键名。 ```javascript var a = new Array(3); @@ -70,7 +72,7 @@ b[0] // undefined 0 in b // true ``` -上面代码中,`a`是一个长度为3的空数组,`b`是一个三个成员都是`undefined`的数组。读取键值的时候,`a`和`b`都返回`undefined`,但是`a`的键位都是空的,`b`的键位是有值的。 +上面代码中,`a`是`Array()`生成的一个长度为3的空数组,`b`是一个三个成员都是`undefined`的数组,这两个数组是不一样的。读取键值的时候,`a`和`b`都返回`undefined`,但是`a`的键名(成员的序号)都是空的,`b`的键名是有值的。 ## 静态方法 @@ -282,13 +284,13 @@ a // ["c", "b", "a"] ### slice() -`slice`方法用于提取目标数组的一部分,返回一个新数组,原数组不变。 +`slice()`方法用于提取目标数组的一部分,返回一个新数组,原数组不变。 ```javascript arr.slice(start, end); ``` -它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。 +它的第一个参数为起始位置(从0开始,会包括在返回的新数组之中),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。 ```javascript var a = ['a', 'b', 'c']; @@ -300,9 +302,9 @@ a.slice(2, 6) // ["c"] a.slice() // ["a", "b", "c"] ``` -上面代码中,最后一个例子`slice`没有参数,实际上等于返回一个原数组的拷贝。 +上面代码中,最后一个例子`slice()`没有参数,实际上等于返回一个原数组的拷贝。 -如果`slice`方法的参数是负数,则表示倒数计算的位置。 +如果`slice()`方法的参数是负数,则表示倒数计算的位置。 ```javascript var a = ['a', 'b', 'c']; @@ -320,7 +322,7 @@ a.slice(4) // [] a.slice(2, 1) // [] ``` -`slice`方法的一个重要应用,是将类似数组的对象转为真正的数组。 +`slice()`方法的一个重要应用,是将类似数组的对象转为真正的数组。 ```javascript Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) @@ -330,11 +332,11 @@ Array.prototype.slice.call(document.querySelectorAll("div")); Array.prototype.slice.call(arguments); ``` -上面代码的参数都不是数组,但是通过`call`方法,在它们上面调用`slice`方法,就可以把它们转为真正的数组。 +上面代码的参数都不是数组,但是通过`call`方法,在它们上面调用`slice()`方法,就可以把它们转为真正的数组。 ### splice() -`splice`方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。 +`splice()`方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。 ```javascript arr.splice(start, count, addElement1, addElement2, ...); @@ -402,7 +404,7 @@ a // [1, 2] // [10111, 1101, 111] ``` -上面代码的最后两个例子,需要特殊注意。`sort`方法不是按照大小排序,而是按照字典顺序。也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以`101`排在`11`的前面。 +上面代码的最后两个例子,需要特殊注意。`sort()`方法不是按照大小排序,而是按照字典顺序。也就是说,数值会被先转成字符串,再按照字典顺序进行比较,所以`101`排在`11`的前面。 如果想让`sort`方法按照自定义方式排序,可以传入一个函数作为参数。 @@ -430,9 +432,21 @@ a // [1, 2] // ] ``` +注意,自定义的排序函数应该返回数值,否则不同的浏览器可能有不同的实现,不能保证结果都一致。 + +```javascript +// bad +[1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a> b) + +// good +[1, 4, 2, 6, 0, 6, 2, 6].sort((a, b) => a - b) +``` + +上面代码中,前一种排序算法返回的是布尔值,这是不推荐使用的。后一种是数值,才是更好的写法。 + ### map() -`map`方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。 +`map()`方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。 ```javascript var numbers = [1, 2, 3]; @@ -448,7 +462,7 @@ numbers 上面代码中,`numbers`数组的所有成员依次执行参数函数,运行结果组成一个新数组返回,原数组没有变化。 -`map`方法接受一个函数作为参数。该函数调用时,`map`方法向它传入三个参数:当前成员、当前位置和数组本身。 +`map()`方法接受一个函数作为参数。该函数调用时,`map()`方法向它传入三个参数:当前成员、当前位置和数组本身。 ```javascript [1, 2, 3].map(function(elem, index, arr) { @@ -457,9 +471,9 @@ numbers // [0, 2, 6] ``` -上面代码中,`map`方法的回调函数有三个参数,`elem`为当前成员的值,`index`为当前成员的位置,`arr`为原数组(`[1, 2, 3]`)。 +上面代码中,`map()`方法的回调函数有三个参数,`elem`为当前成员的值,`index`为当前成员的位置,`arr`为原数组(`[1, 2, 3]`)。 -`map`方法还可以接受第二个参数,用来绑定回调函数内部的`this`变量(详见《this 变量》一章)。 +`map()`方法还可以接受第二个参数,用来绑定回调函数内部的`this`变量(详见《this 变量》一章)。 ```javascript var arr = ['a', 'b', 'c']; @@ -470,9 +484,9 @@ var arr = ['a', 'b', 'c']; // ['b', 'c'] ``` -上面代码通过`map`方法的第二个参数,将回调函数内部的`this`对象,指向`arr`数组。 +上面代码通过`map()`方法的第二个参数,将回调函数内部的`this`对象,指向`arr`数组。 -如果数组有空位,`map`方法的回调函数在这个位置不会执行,会跳过数组的空位。 +如果数组有空位,`map()`方法的回调函数在这个位置不会执行,会跳过数组的空位。 ```javascript var f = function (n) { return 'a' }; @@ -482,13 +496,13 @@ var f = function (n) { return 'a' }; [1, , 2].map(f) // ["a", , "a"] ``` -上面代码中,`map`方法不会跳过`undefined`和`null`,但是会跳过空位。 +上面代码中,`map()`方法不会跳过`undefined`和`null`,但是会跳过空位。 ### forEach() -`forEach`方法与`map`方法很相似,也是对数组的所有成员依次执行参数函数。但是,`forEach`方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用`map`方法,否则使用`forEach`方法。 +`forEach()`方法与`map()`方法很相似,也是对数组的所有成员依次执行参数函数。但是,`forEach()`方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用`map()`方法,否则使用`forEach()`方法。 -`forEach`的用法与`map`方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组。 +`forEach()`的用法与`map()`方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组。 ```javascript function log(element, index, array) { @@ -501,9 +515,9 @@ function log(element, index, array) { // [2] = 9 ``` -上面代码中,`forEach`遍历数组不是为了得到返回值,而是为了在屏幕输出内容,所以不必使用`map`方法。 +上面代码中,`forEach()`遍历数组不是为了得到返回值,而是为了在屏幕输出内容,所以不必使用`map()`方法。 -`forEach`方法也可以接受第二个参数,绑定参数函数的`this`变量。 +`forEach()`方法也可以接受第二个参数,绑定参数函数的`this`变量。 ```javascript var out = []; @@ -515,9 +529,9 @@ var out = []; out // [1, 4, 9] ``` -上面代码中,空数组`out`是`forEach`方法的第二个参数,结果,回调函数内部的`this`关键字就指向`out`。 +上面代码中,空数组`out`是`forEach()`方法的第二个参数,结果,回调函数内部的`this`关键字就指向`out`。 -注意,`forEach`方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用`for`循环。 +注意,`forEach()`方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用`for`循环。 ```javascript var arr = [1, 2, 3]; @@ -529,9 +543,9 @@ for (var i = 0; i < arr.length; i++) { // 1 ``` -上面代码中,执行到数组的第二个成员时,就会中断执行。`forEach`方法做不到这一点。 +上面代码中,执行到数组的第二个成员时,就会中断执行。`forEach()`方法做不到这一点。 -`forEach`方法也会跳过数组的空位。 +`forEach()`方法也会跳过数组的空位。 ```javascript var log = function (n) { @@ -553,11 +567,11 @@ var log = function (n) { // 3 ``` -上面代码中,`forEach`方法不会跳过`undefined`和`null`,但会跳过空位。 +上面代码中,`forEach()`方法不会跳过`undefined`和`null`,但会跳过空位。 ### filter() -`filter`方法用于过滤数组成员,满足条件的成员组成一个新数组返回。 +`filter()`方法用于过滤数组成员,满足条件的成员组成一个新数组返回。 它的参数是一个函数,所有数组成员依次执行该函数,返回结果为`true`的成员组成一个新数组返回。该方法不会改变原数组。 @@ -577,9 +591,9 @@ arr.filter(Boolean) // [1, "a"] ``` -上面代码中,`filter`方法返回数组`arr`里面所有布尔值为`true`的成员。 +上面代码中,`filter()`方法返回数组`arr`里面所有布尔值为`true`的成员。 -`filter`方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组。 +`filter()`方法的参数函数可以接受三个参数:当前成员,当前位置和整个数组。 ```javascript [1, 2, 3, 4, 5].filter(function (elem, index, arr) { @@ -590,7 +604,7 @@ arr.filter(Boolean) 上面代码返回偶数位置的成员组成的新数组。 -`filter`方法还可以接受第二个参数,用来绑定参数函数内部的`this`变量。 +`filter()`方法还可以接受第二个参数,用来绑定参数函数内部的`this`变量。 ```javascript var obj = { MAX: 3 }; @@ -602,7 +616,7 @@ var arr = [2, 8, 3, 4, 1, 3, 2, 9]; arr.filter(myFilter, obj) // [8, 4, 9] ``` -上面代码中,过滤器`myFilter`内部有`this`变量,它可以被`filter`方法的第二个参数`obj`绑定,返回大于`3`的成员。 +上面代码中,过滤器`myFilter()`内部有`this`变量,它可以被`filter()`方法的第二个参数`obj`绑定,返回大于`3`的成员。 ### some(),every() @@ -647,7 +661,7 @@ function isEven(x) { return x % 2 === 0 } ### reduce(),reduceRight() -`reduce`方法和`reduceRight`方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,`reduce`是从左到右处理(从第一个成员到最后一个成员),`reduceRight`则是从右到左(从最后一个成员到第一个成员),其他完全一样。 +`reduce()`方法和`reduceRight()`方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,`reduce()`是从左到右处理(从第一个成员到最后一个成员),`reduceRight()`则是从右到左(从最后一个成员到第一个成员),其他完全一样。 ```javascript [1, 2, 3, 4, 5].reduce(function (a, b) { @@ -661,18 +675,33 @@ function isEven(x) { return x % 2 === 0 } //最后结果:15 ``` -上面代码中,`reduce`方法求出数组所有成员的和。第一次执行,`a`是数组的第一个成员`1`,`b`是数组的第二个成员`2`。第二次执行,`a`为上一轮的返回值`3`,`b`为第三个成员`3`。第三次执行,`a`为上一轮的返回值`6`,`b`为第四个成员`4`。第四次执行,`a`为上一轮返回值`10`,`b`为第五个成员`5`。至此所有成员遍历完成,整个方法的返回值就是最后一轮的返回值`15`。 +上面代码中,`reduce()`方法用来求出数组所有成员的和。`reduce()`的参数是一个函数,数组每个成员都会依次执行这个函数。如果数组有 n 个成员,这个参数函数就会执行 n - 1 次。 -`reduce`方法和`reduceRight`方法的第一个参数都是一个函数。该函数接受以下四个参数。 +- 第一次执行:`a`是数组的第一个成员`1`,`b`是数组的第二个成员`2`。 +- 第二次执行:`a`为上一轮的返回值`3`,`b`为第三个成员`3`。 +- 第三次执行:`a`为上一轮的返回值`6`,`b`为第四个成员`4`。 +- 第四次执行:`a`为上一轮返回值`10`,`b`为第五个成员`5`。至此所有成员遍历完成,整个方法的返回值就是最后一轮的返回值`15`。 -1. 累积变量,默认为数组的第一个成员 -2. 当前变量,默认为数组的第二个成员 -3. 当前位置(从0开始) -4. 原数组 +`reduce()`方法和`reduceRight()`方法的第一个参数都是一个函数。该函数接受以下四个参数。 + +1. 累积变量。第一次执行时,默认为数组的第一个成员;以后每次执行时,都是上一轮的返回值。 +2. 当前变量。第一次执行时,默认为数组的第二个成员;以后每次执行时,都是下一个成员。 +3. 当前位置。一个整数,表示第二个参数(当前变量)的位置,默认为`1`。 +4. 原数组。 这四个参数之中,只有前两个是必须的,后两个则是可选的。 -如果要对累积变量指定初值,可以把它放在`reduce`方法和`reduceRight`方法的第二个参数。 +```javascript +[1, 2, 3, 4, 5].reduce(function ( + a, // 累积变量,必须 + b, // 当前变量,必须 + i, // 当前位置,可选 + arr // 原数组,可选 +) { + // ... ... +``` + +如果要对累积变量指定初值,可以把它放在`reduce()`方法和`reduceRight()`方法的第二个参数。 ```javascript [1, 2, 3, 4, 5].reduce(function (a, b) { @@ -681,9 +710,9 @@ function isEven(x) { return x % 2 === 0 } // 25 ``` -上面代码指定参数`a`的初值为10,所以数组从10开始累加,最终结果为25。注意,这时`b`是从数组的第一个成员开始遍历。 +上面代码指定参数`a`的初值为10,所以数组从10开始累加,最终结果为25。注意,这时`b`是从数组的第一个成员开始遍历,参数函数会执行5次。 -上面的第二个参数相当于设定了默认值,处理空数组时尤其有用。 +建议总是加上第二个参数,这样比较符合直觉,每个数组成员都会依次执行`reduce()`方法的参数函数。另外,第二个参数可以防止空数组报错。 ```javascript function add(prev, cur) { @@ -696,9 +725,9 @@ function add(prev, cur) { // 1 ``` -上面代码中,由于空数组取不到初始值,`reduce`方法会报错。这时,加上第二个参数,就能保证总是会返回一个值。 +上面代码中,由于空数组取不到累积变量的初始值,`reduce()`方法会报错。这时,加上第二个参数,就能保证总是会返回一个值。 -下面是一个`reduceRight`方法的例子。 +下面是一个`reduceRight()`方法的例子。 ```javascript function subtract(prev, cur) { @@ -709,9 +738,9 @@ function subtract(prev, cur) { [3, 2, 1].reduceRight(subtract) // -4 ``` -上面代码中,`reduce`方法相当于`3`减去`2`再减去`1`,`reduceRight`方法相当于`1`减去`2`再减去`3`。 +上面代码中,`reduce()`方法相当于`3`减去`2`再减去`1`,`reduceRight`方法相当于`1`减去`2`再减去`3`。 -由于这两个方法会遍历数组,所以实际上还可以用来做一些遍历相关的操作。比如,找出字符长度最长的数组成员。 +由于这两个方法会遍历数组,所以实际上可以用来做一些遍历相关的操作。比如,找出字符长度最长的数组成员。 ```javascript function findLongest(entries) { @@ -723,7 +752,7 @@ function findLongest(entries) { findLongest(['aaa', 'bb', 'c']) // "aaa" ``` -上面代码中,`reduce`的参数函数会将字符长度较长的那个数组成员,作为累积值。这导致遍历所有成员之后,累积值就是字符长度最长的那个成员。 +上面代码中,`reduce()`的参数函数会将字符长度较长的那个数组成员,作为累积值。这导致遍历所有成员之后,累积值就是字符长度最长的那个成员。 ### indexOf(),lastIndexOf() diff --git a/docs/stdlib/attributes.md b/docs/stdlib/attributes.md index 851ac0a..cad729f 100644 --- a/docs/stdlib/attributes.md +++ b/docs/stdlib/attributes.md @@ -33,7 +33,7 @@ JavaScript 提供了一个内部数据结构,用来描述对象的属性,控 (4)`configurable` -`configurable`是一个布尔值,表示可配置性,默认为`true`。如果设为`false`,将阻止某些操作改写该属性,比如无法删除该属性,也不得改变该属性的属性描述对象(`value`属性除外)。也就是说,`configurable`属性控制了属性描述对象的可写性。 +`configurable`是一个布尔值,表示属性的可配置性,默认为`true`。如果设为`false`,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性(`value`属性除外)。也就是说,`configurable`属性控制了属性描述对象的可写性。 (5)`get` @@ -350,7 +350,7 @@ JSON.stringify(obj) // "{}" ### configurable -`configurable`(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,`configurable`为`false`时,`value`、`writable`、`enumerable`和`configurable`都不能被修改了。 +`configurable`(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,`configurable`为`false`时,`writable`、`enumerable`和`configurable`都不能被修改了。 ```javascript var obj = Object.defineProperty({}, 'p', { @@ -360,9 +360,6 @@ var obj = Object.defineProperty({}, 'p', { configurable: false }); -Object.defineProperty(obj, 'p', {value: 2}) -// TypeError: Cannot redefine property: p - Object.defineProperty(obj, 'p', {writable: true}) // TypeError: Cannot redefine property: p @@ -371,11 +368,14 @@ Object.defineProperty(obj, 'p', {enumerable: true}) Object.defineProperty(obj, 'p', {configurable: true}) // TypeError: Cannot redefine property: p + +Object.defineProperty(obj, 'p', {value: 2}) +// TypeError: Cannot redefine property: p ``` -上面代码中,`obj.p`的`configurable`为`false`。然后,改动`value`、`writable`、`enumerable`、`configurable`,结果都报错。 +上面代码中,`obj.p`的`configurable`属性为`false`。然后,改动`writable`、`enumerable`、`configurable`,结果都报错。 -注意,`writable`只有在`false`改为`true`会报错,`true`改为`false`是允许的。 +注意,`writable`属性只有在`false`改为`true`时会报错,`true`改为`false`是允许的。 ```javascript var obj = Object.defineProperty({}, 'p', { @@ -387,7 +387,7 @@ Object.defineProperty(obj, 'p', {writable: false}) // 修改成功 ``` -至于`value`,只要`writable`和`configurable`有一个为`true`,就允许改动。 +`value`属性的情况比较特殊。只要`writable`和`configurable`有一个为`true`,就允许改动`value`。 ```javascript var o1 = Object.defineProperty({}, 'p', { @@ -409,7 +409,7 @@ Object.defineProperty(o2, 'p', {value: 2}) // 修改成功 ``` -另外,`writable`为`false`时,直接目标属性赋值,不报错,但不会成功。 +另外,`writable`为`false`时,直接对目标属性赋值,不报错,但不会成功。 ```javascript var obj = Object.defineProperty({}, 'p', { @@ -445,7 +445,7 @@ obj.p2 // 2 除了直接定义以外,属性还可以用存取器(accessor)定义。其中,存值函数称为`setter`,使用属性描述对象的`set`属性;取值函数称为`getter`,使用属性描述对象的`get`属性。 -一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如某个属性禁止赋值。 +一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如定制属性的读取和赋值行为。 ```javascript var obj = Object.defineProperty({}, 'p', { @@ -466,6 +466,7 @@ obj.p = 123 // "setter: 123" JavaScript 还提供了存取器的另一种写法。 ```javascript +// 写法二 var obj = { get p() { return 'getter'; @@ -476,7 +477,7 @@ var obj = { }; ``` -上面的写法与定义属性描述对象是等价的,而且使用更广泛。 +上面两种写法,虽然属性`p`的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属性`p`的`configurable`和`enumerable`都为`false`,从而导致属性`p`是不可遍历的;第二种写法,属性`p`的`configurable`和`enumerable`都为`true`,因此属性`p`是可遍历的。实际开发中,写法二更常用。 注意,取值函数`get`不能接受参数,存值函数`set`只能接受一个参数(即属性的值)。 @@ -634,7 +635,7 @@ Object.getOwnPropertyDescriptor(obj, 'p') // configurable: false // } -Object.defineProperty(o, 'p', { +Object.defineProperty(obj, 'p', { enumerable: false }) // TypeError: Cannot redefine property: p diff --git a/docs/stdlib/date.md b/docs/stdlib/date.md index 357913c..d0475da 100644 --- a/docs/stdlib/date.md +++ b/docs/stdlib/date.md @@ -317,15 +317,15 @@ d.toTimeString() // "00:00:00 GMT+0800 (CST)" var d = new Date(2013, 0, 1); d.toLocaleString() -// 中文版浏览器为"2013年1月1日 上午12:00:00" +// 中文版浏览器为"2013/1/1 00:00:00" // 英文版浏览器为"1/1/2013 12:00:00 AM" d.toLocaleDateString() -// 中文版浏览器为"2013年1月1日" +// 中文版浏览器为"2013/1/1" // 英文版浏览器为"1/1/2013" d.toLocaleTimeString() -// 中文版浏览器为"上午12:00:00" +// 中文版浏览器为"00:00:00" // 英文版浏览器为"12:00:00 AM" ``` @@ -337,28 +337,38 @@ dateObj.toLocaleDateString([locales[, options]]) dateObj.toLocaleTimeString([locales[, options]]) ``` -这两个参数中,`locales`是一个指定所用语言的字符串,`options`是一个配置对象。下面是`locales`的例子。 +这两个参数中,`locales`是一个指定所用语言的字符串,`options`是一个配置对象。下面是`locales`的例子,分别采用`en-US`和`zh-CN`语言设定。 ```javascript var d = new Date(2013, 0, 1); d.toLocaleString('en-US') // "1/1/2013, 12:00:00 AM" -d.toLocaleString('zh-CN') // "2013/1/1 上午12:00:00" +d.toLocaleString('zh-CN') // "2013/1/1 00:00:00" d.toLocaleDateString('en-US') // "1/1/2013" d.toLocaleDateString('zh-CN') // "2013/1/1" d.toLocaleTimeString('en-US') // "12:00:00 AM" -d.toLocaleTimeString('zh-CN') // "上午12:00:00" +d.toLocaleTimeString('zh-CN') // "00:00:00" ``` -下面是`options`的例子。 +`options`配置对象有以下属性。 + +- `dateStyle`:可能的值为`full`、`long`、`medium`、`short`。 +- `timeStyle`:可能的值为`full`、`long`、`medium`、`short`。 +- `month`:可能的值为`numeric`、`2-digit`、`long`、`short`、`narrow`。 +- `year`:可能的值为`numeric`、`2-digit`。 +- `weekday`:可能的值为`long`、`short`、`narrow`。 +- `day`、`hour`、`minute`、`second`:可能的值为`numeric`、`2-digit`。 +- `timeZone`:可能的值为 IANA 的时区数据库。 +- `timeZoneName`:可能的值为`long`、`short`。 +- `hour12`:24小时周期还是12小时周期,可能的值为`true`、`false`。 + +下面是用法实例。 ```javascript var d = new Date(2013, 0, 1); -// 时间格式 -// 下面的设置是,星期和月份为完整文字,年份和日期为数字 d.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', @@ -367,7 +377,13 @@ d.toLocaleDateString('en-US', { }) // "Tuesday, January 1, 2013" -// 指定时区 +d.toLocaleDateString('en-US', { + day: "2-digit", + month: "long", + year: "2-digit" +}); +// "January 01, 13" + d.toLocaleTimeString('en-US', { timeZone: 'UTC', timeZoneName: 'short' @@ -380,7 +396,6 @@ d.toLocaleTimeString('en-US', { }) // "12:00:00 AM China Standard Time" -// 小时周期为12还是24 d.toLocaleTimeString('en-US', { hour12: false }) @@ -464,7 +479,7 @@ d.getUTCDate() // 5 - `setDate(date)`:设置实例对象对应的每个月的几号(1-31),返回改变后毫秒时间戳。 - `setFullYear(year [, month, date])`:设置四位年份。 - `setHours(hour [, min, sec, ms])`:设置小时(0-23)。 -- `setMilliseconds()`:设置毫秒(0-999)。 +- `setMilliseconds(ms)`:设置毫秒(0-999)。 - `setMinutes(min [, sec, ms])`:设置分钟(0-59)。 - `setMonth(month [, date])`:设置月份(0-11)。 - `setSeconds(sec [, ms])`:设置秒(0-59)。 @@ -480,7 +495,7 @@ d.setDate(9) // 1357660800000 d // Wed Jan 09 2013 00:00:00 GMT+0800 (CST) ``` -`set*`方法的参数都会自动折算。以`setDate`为例,如果参数超过当月的最大天数,则向下一个月顺延,如果参数是负数,表示从上个月的最后一天开始减去的天数。 +`set*`方法的参数都会自动折算。以`setDate()`为例,如果参数超过当月的最大天数,则向下一个月顺延,如果参数是负数,表示从上个月的最后一天开始减去的天数。 ```javascript var d1 = new Date('January 6, 2013'); @@ -490,10 +505,12 @@ d1 // Fri Feb 01 2013 00:00:00 GMT+0800 (CST) var d2 = new Date ('January 6, 2013'); -d.setDate(-1) // 1356796800000 -d // Sun Dec 30 2012 00:00:00 GMT+0800 (CST) +d2.setDate(-1) // 1356796800000 +d2 // Sun Dec 30 2012 00:00:00 GMT+0800 (CST) ``` +上面代码中,`d1.setDate(32)`将日期设为1月份的32号,因为1月份只有31号,所以自动折算为2月1日。`d2.setDate(-1)`表示设为上个月的倒数第二天,即12月30日。 + `set`类方法和`get`类方法,可以结合使用,得到相对时间。 ```javascript @@ -528,5 +545,5 @@ d // Sun Jan 06 2013 06:00:00 GMT+0800 (CST) ## 参考链接 -- Rakhitha Nimesh,[Getting Started with the Date Object](http://jspro.com/raw-javascript/beginners-guide-to-javascript-date-and-time/) -- Ilya Kantor, [Date/Time functions](http://javascript.info/tutorial/datetime-functions) +- Rakhitha Nimesh,[Getting Started with the Date Object](https://www.sitepoint.com/beginners-guide-to-javascript-date-and-time/) +- Ilya Kantor, [Date/Time functions](https://javascript.info/date) diff --git a/docs/stdlib/json.md b/docs/stdlib/json.md index b4250aa..c395d46 100644 --- a/docs/stdlib/json.md +++ b/docs/stdlib/json.md @@ -59,7 +59,7 @@ JSON 对值的类型和格式有严格的规定。 ### 基本用法 -`JSON.stringify`方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被`JSON.parse`方法还原。 +`JSON.stringify()`方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被`JSON.parse()`方法还原。 ```javascript JSON.stringify('abc') // ""abc"" @@ -93,7 +93,7 @@ JSON.stringify('false') // "\"false\"" 上面代码中,如果不是内层的双引号,将来还原的时候,引擎就无法知道原始值是布尔值还是字符串。 -如果对象的属性是`undefined`、函数或 XML 对象,该属性会被`JSON.stringify`过滤。 +如果对象的属性是`undefined`、函数或 XML 对象,该属性会被`JSON.stringify()`过滤。 ```javascript var obj = { @@ -121,7 +121,7 @@ JSON.stringify(arr) // "[null,null]" JSON.stringify(/foo/) // "{}" ``` -`JSON.stringify`方法会忽略对象的不可遍历的属性。 +`JSON.stringify()`方法会忽略对象的不可遍历的属性。 ```javascript var obj = {}; @@ -143,7 +143,7 @@ JSON.stringify(obj); // "{"foo":1}" ### 第二个参数 -`JSON.stringify`方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性。 +`JSON.stringify()`方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。 ```javascript var obj = { @@ -158,7 +158,7 @@ JSON.stringify(obj, selectedProperties) // "{"prop1":"value1","prop2":"value2"}" ``` -上面代码中,`JSON.stringify`方法的第二个参数指定,只转`prop1`和`prop2`两个属性。 +上面代码中,`JSON.stringify()`方法的第二个参数指定,只转`prop1`和`prop2`两个属性。 这个类似白名单的数组,只对对象的属性有效,对数组无效。 @@ -172,7 +172,7 @@ JSON.stringify({0: 'a', 1: 'b'}, ['0']) 上面代码中,第二个参数指定 JSON 格式只转`0`号属性,实际上对数组是无效的,只对对象有效。 -第二个参数还可以是一个函数,用来更改`JSON.stringify`的返回值。 +第二个参数还可以是一个函数,用来更改`JSON.stringify()`的返回值。 ```javascript function f(key, value) { @@ -191,26 +191,26 @@ JSON.stringify({ a: 1, b: 2 }, f) 注意,这个处理函数是递归处理所有的键。 ```javascript -var o = {a: {b: 1}}; +var obj = {a: {b: 1}}; function f(key, value) { console.log("["+ key +"]:" + value); return value; } -JSON.stringify(o, f) +JSON.stringify(obj, f) // []:[object Object] // [a]:[object Object] // [b]:1 // '{"a":{"b":1}}' ``` -上面代码中,对象`o`一共会被`f`函数处理三次,最后那行是`JSON.stringify`的输出。第一次键名为空,键值是整个对象`o`;第二次键名为`a`,键值是`{b: 1}`;第三次键名为`b`,键值为1。 +上面代码中,对象`obj`一共会被`f`函数处理三次,输出的最后那行是`JSON.stringify()`的默认输出。第一次键名为空,键值是整个对象`obj`;第二次键名为`a`,键值是`{b: 1}`;第三次键名为`b`,键值为1。 递归处理中,每一次处理的对象,都是前一次返回的值。 ```javascript -var o = {a: 1}; +var obj = {a: 1}; function f(key, value) { if (typeof value === 'object') { @@ -219,11 +219,11 @@ function f(key, value) { return value * 2; } -JSON.stringify(o, f) +JSON.stringify(obj, f) // "{"b": 4}" ``` -上面代码中,`f`函数修改了对象`o`,接着`JSON.stringify`方法就递归处理修改后的对象`o`。 +上面代码中,`f`函数修改了对象`obj`,接着`JSON.stringify()`方法就递归处理修改后的对象`obj`。 如果处理函数返回`undefined`或没有返回值,则该属性会被忽略。 @@ -243,7 +243,26 @@ JSON.stringify({ a: "abc", b: 123 }, f) ### 第三个参数 -`JSON.stringify`还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。如果是数字,表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会添加在每行前面。 +`JSON.stringify()`还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。 + +默认返回的是单行字符串,对于大型的 JSON 对象,可读性非常差。第三个参数使得每个属性单独占据一行,并且将每个属性前面添加指定的前缀(不超过10个字符)。 + +```javascript +// 默认输出 +JSON.stringify({ p1: 1, p2: 2 }) +// JSON.stringify({ p1: 1, p2: 2 }) + +// 分行输出 +JSON.stringify({ p1: 1, p2: 2 }, null, '\t') +// { +// "p1": 1, +// "p2": 2 +// } +``` + +上面例子中,第三个属性`\t`在每个属性前面添加一个制表符,然后分行显示。 + +第三个属性如果是一个数字,则表示每个属性前面添加的空格(最多不超过10个)。 ```javascript JSON.stringify({ p1: 1, p2: 2 }, null, 2); @@ -253,19 +272,11 @@ JSON.stringify({ p1: 1, p2: 2 }, null, 2); "p2": 2 }" */ - -JSON.stringify({ p1:1, p2:2 }, null, '|-'); -/* -"{ -|-"p1": 1, -|-"p2": 2 -}" -*/ ``` -### 参数对象的 toJSON 方法 +### 参数对象的 toJSON() 方法 -如果参数对象有自定义的`toJSON`方法,那么`JSON.stringify`会使用这个方法的返回值作为参数,而忽略原对象的其他属性。 +如果参数对象有自定义的`toJSON()`方法,那么`JSON.stringify()`会使用这个方法的返回值作为参数,而忽略原对象的其他属性。 下面是一个普通的对象。 @@ -283,7 +294,7 @@ JSON.stringify(user) // "{"firstName":"三","lastName":"张","fullName":"张三"}" ``` -现在,为这个对象加上`toJSON`方法。 +现在,为这个对象加上`toJSON()`方法。 ```javascript var user = { @@ -305,9 +316,9 @@ JSON.stringify(user) // "{"name":"张三"}" ``` -上面代码中,`JSON.stringify`发现参数对象有`toJSON`方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。 +上面代码中,`JSON.stringify()`发现参数对象有`toJSON()`方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。 -`Date`对象就有一个自己的`toJSON`方法。 +`Date`对象就有一个自己的`toJSON()`方法。 ```javascript var date = new Date('2015-01-01'); @@ -315,9 +326,9 @@ date.toJSON() // "2015-01-01T00:00:00.000Z" JSON.stringify(date) // ""2015-01-01T00:00:00.000Z"" ``` -上面代码中,`JSON.stringify`发现处理的是`Date`对象实例,就会调用这个实例对象的`toJSON`方法,将该方法的返回值作为参数。 +上面代码中,`JSON.stringify()`发现处理的是`Date`对象实例,就会调用这个实例对象的`toJSON()`方法,将该方法的返回值作为参数。 -`toJSON`方法的一个应用是,将正则对象自动转为字符串。因为`JSON.stringify`默认不能转换正则对象,但是设置了`toJSON`方法以后,就可以转换正则对象了。 +`toJSON()`方法的一个应用是,将正则对象自动转为字符串。因为`JSON.stringify()`默认不能转换正则对象,但是设置了`toJSON()`方法以后,就可以转换正则对象了。 ```javascript var obj = { @@ -336,7 +347,7 @@ JSON.stringify(/foo/) // ""/foo/"" ## JSON.parse() -`JSON.parse`方法用于将 JSON 字符串转换成对应的值。 +`JSON.parse()`方法用于将 JSON 字符串转换成对应的值。 ```javascript JSON.parse('{}') // {} @@ -349,7 +360,7 @@ var o = JSON.parse('{"name": "张三"}'); o.name // 张三 ``` -如果传入的字符串不是有效的 JSON 格式,`JSON.parse`方法将报错。 +如果传入的字符串不是有效的 JSON 格式,`JSON.parse()`方法将报错。 ```javascript JSON.parse("'String'") // illegal single quotes @@ -358,7 +369,7 @@ JSON.parse("'String'") // illegal single quotes 上面代码中,双引号字符串中是一个单引号字符串,因为单引号字符串不符合 JSON 格式,所以报错。 -为了处理解析错误,可以将`JSON.parse`方法放在`try...catch`代码块中。 +为了处理解析错误,可以将`JSON.parse()`方法放在`try...catch`代码块中。 ```javascript try { @@ -368,7 +379,7 @@ try { } ``` -`JSON.parse`方法可以接受一个处理函数,作为第二个参数,用法与`JSON.stringify`方法类似。 +`JSON.parse()`方法可以接受一个处理函数,作为第二个参数,用法与`JSON.stringify()`方法类似。 ```javascript function f(key, value) { @@ -382,7 +393,16 @@ JSON.parse('{"a": 1, "b": 2}', f) // {a: 11, b: 2} ``` -上面代码中,`JSON.parse`的第二个参数是一个函数,如果键名是`a`,该函数会将键值加上10。 +上面代码中,`JSON.parse()`的第二个参数是一个函数,如果键名是`a`,该函数会将键值加上10。 + +`JSON.parse()`和`JSON.stringify()`可以结合使用,像下面这样写,实现对象的深拷贝。 + +```javascript +JSON.parse(JSON.stringify(obj)) +``` + +上面这种写法,可以深度克隆一个对象,但是对象内部不能有 JSON +不允许的数据类型,比如函数、正则对象、日期对象等。 ## 参考链接 diff --git a/docs/stdlib/math.md b/docs/stdlib/math.md index de60029..a5e8947 100644 --- a/docs/stdlib/math.md +++ b/docs/stdlib/math.md @@ -37,7 +37,7 @@ Math.SQRT2 // 1.4142135623730951 - `Math.floor()`:向下取整 - `Math.max()`:最大值 - `Math.min()`:最小值 -- `Math.pow()`:指数运算 +- `Math.pow()`:幂运算 - `Math.sqrt()`:平方根 - `Math.log()`:自然对数 - `Math.exp()`:`e`的指数 @@ -66,14 +66,14 @@ Math.max() // -Infinity ### Math.floor(),Math.ceil() -`Math.floor`方法返回小于参数值的最大整数(地板值)。 +`Math.floor`方法返回小于或等于参数值的最大整数(地板值)。 ```javascript Math.floor(3.2) // 3 Math.floor(-3.2) // -4 ``` -`Math.ceil`方法返回大于参数值的最小整数(天花板值)。 +`Math.ceil`方法返回大于或等于参数值的最小整数(天花板值)。 ```javascript Math.ceil(3.2) // 4 @@ -121,7 +121,7 @@ Math.round(-1.6) // -2 ### Math.pow() -`Math.pow`方法返回以第一个参数为底数、第二个参数为幂的指数值。 +`Math.pow`方法返回以第一个参数为底数、第二个参数为指数的幂运算值。 ```javascript // 等同于 2 ** 2 diff --git a/docs/stdlib/number.md b/docs/stdlib/number.md index d585fff..53fb8c5 100644 --- a/docs/stdlib/number.md +++ b/docs/stdlib/number.md @@ -117,7 +117,7 @@ Number.MIN_SAFE_INTEGER // -9007199254740991 上面代码中,`10`和`10.005`先转成2位小数,然后转成字符串。其中`10`必须放在括号里,否则后面的点会被处理成小数点。 -`toFixed()`方法的参数为小数位数,有效范围为0到20,超出这个范围将抛出 RangeError 错误。 +`toFixed()`方法的参数为小数位数,有效范围为0到100,超出这个范围将抛出 RangeError 错误。 由于浮点数的原因,小数`5`的四舍五入是不确定的,使用的时候必须小心。 @@ -140,11 +140,11 @@ Number.MIN_SAFE_INTEGER // -9007199254740991 (1234).toExponential(2) // "1.23e+3" ``` -`toExponential`方法的参数是小数点后有效数字的位数,范围为0到20,超出这个范围,会抛出一个 RangeError 错误。 +`toExponential`方法的参数是小数点后有效数字的位数,范围为0到100,超出这个范围,会抛出一个 RangeError 错误。 ### Number.prototype.toPrecision() -`toPrecision`方法用于将一个数转为指定位数的有效数字。 +`Number.prototype.toPrecision()`方法用于将一个数转为指定位数的有效数字。 ```javascript (12.34).toPrecision(1) // "1e+1" @@ -154,9 +154,9 @@ Number.MIN_SAFE_INTEGER // -9007199254740991 (12.34).toPrecision(5) // "12.340" ``` -`toPrecision`方法的参数为有效数字的位数,范围是1到21,超出这个范围会抛出 RangeError 错误。 +该方法的参数为有效数字的位数,范围是1到100,超出这个范围会抛出 RangeError 错误。 -`toPrecision`方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。 +该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。 ```javascript (12.35).toPrecision(3) // "12.3" @@ -165,6 +165,41 @@ Number.MIN_SAFE_INTEGER // -9007199254740991 (12.45).toPrecision(3) // "12.4" ``` +### Number.prototype.toLocaleString() + +`Number.prototype.toLocaleString()`方法接受一个地区码作为参数,返回一个字符串,表示当前数字在该地区的当地书写形式。 + +```javascript +(123).toLocaleString('zh-Hans-CN-u-nu-hanidec') +// "一二三" +``` + +该方法还可以接受第二个参数配置对象,用来定制指定用途的返回字符串。该对象的`style`属性指定输出样式,默认值是`decimal`,表示输出十进制形式。如果值为`percent`,表示输出百分数。 + +```javascript +(123).toLocaleString('zh-Hans-CN', { style: 'percent' }) +// "12,300%" +``` + +如果`style`属性的值为`currency`,则可以搭配`currency`属性,输出指定格式的货币字符串形式。 + +```javascript +(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' }) +// "123円.00" + +(123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' }) +// "123,00 €" + +(123).toLocaleString('en-US', { style: 'currency', currency: 'USD' }) +// "123ドル.00" +``` + +如果`Number.prototype.toLocaleString()`省略了参数,则由浏览器自行决定如何处理,通常会使用操作系统的地区设定。注意,该方法如果使用浏览器不认识的地区码,会抛出一个错误。 + +```javascript +(123).toLocaleString('123') // 出错 +``` + ## 自定义方法 与其他对象一样,`Number.prototype`对象上面可以自定义方法,被`Number`的实例继承。 diff --git a/docs/stdlib/object.md b/docs/stdlib/object.md index 51ba1e6..783175c 100644 --- a/docs/stdlib/object.md +++ b/docs/stdlib/object.md @@ -56,7 +56,7 @@ obj instanceof Object // true `instanceof`运算符用来验证,一个对象是否为指定的构造函数的实例。`obj instanceof Object`返回`true`,就表示`obj`对象是`Object`的实例。 -如果参数是原始类型的值,`Object`方法将其转为对应的包装对象的实例(参见《原始类型的包装对象》一章)。 +如果参数是原始类型的值,`Object`方法将其转为对应的包装对象的实例(参见《包装对象》一章)。 ```javascript var obj = Object(1); @@ -156,7 +156,7 @@ var obj = { Object.getOwnPropertyNames(obj) // ["p1", "p2"] ``` -对于一般的对象来说,`Object.keys()`和`Object.getOwnPropertyNames()`返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。`Object.keys`方法只返回可枚举的属性(详见《对象属性的描述对象》一章),`Object.getOwnPropertyNames`方法还返回不可枚举的属性名。 +对于一般的对象来说,`Object.keys()`和`Object.getOwnPropertyNames()`返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。`Object.keys`方法只返回可枚举的属性(详见《属性描述对象》一章),`Object.getOwnPropertyNames`方法还返回不可枚举的属性名。 ```javascript var a = ['Hello', 'World']; @@ -231,7 +231,7 @@ obj.valueOf() === obj // true 上面代码比较`obj.valueOf()`与`obj`本身,两者是一样的。 -`valueOf`方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法(详见《数据类型转换》一章)。 +`valueOf`方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法(详见《数据类型的转换》一章)。 ```javascript var obj = new Object(); @@ -308,7 +308,7 @@ var obj = {}; obj.toString() // "[object Object]" ``` -上面代码调用空对象的`toString`方法,结果返回一个字符串`object Object`,其中第二个`Object`表示该值的构造函数。这是一个十分有用的判断数据类型的方法。 +上面代码调用空对象的`toString`方法,结果返回一个字符串`[object Object]`,其中第二个`Object`表示该值的构造函数。这是一个十分有用的判断数据类型的方法。 由于实例对象可能会自定义`toString`方法,覆盖掉`Object.prototype.toString`方法,所以为了得到类型字符串,最好直接使用`Object.prototype.toString`方法。通过函数的`call`方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。 diff --git a/docs/stdlib/regexp.md b/docs/stdlib/regexp.md index 3ba5f86..5a1d884 100644 --- a/docs/stdlib/regexp.md +++ b/docs/stdlib/regexp.md @@ -104,7 +104,7 @@ r.lastIndex = 4; r.test(s) // false r.lastIndex // 0 -r.test(s) +r.test(s) // true ``` 上面代码指定从字符串的第五个位置开始搜索,这个位置为空,所以返回`false`。同时,`lastIndex`属性重置为`0`,所以第二次执行`r.test(s)`会返回`true`。 @@ -137,7 +137,7 @@ new RegExp('').test('abc') ### RegExp.prototype.exec() -正则实例对象的`exec`方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回`null`。 +正则实例对象的`exec()`方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回`null`。 ```javascript var s = '_x_x'; @@ -150,7 +150,7 @@ r2.exec(s) // null 上面代码中,正则对象`r1`匹配成功,返回一个数组,成员是匹配结果;正则对象`r2`匹配失败,返回`null`。 -如果正则表示式包含圆括号(即含有"组匹配"),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的`length`属性等于组匹配的数量再加1。 +如果正则表达式包含圆括号(即含有"组匹配"),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的`length`属性等于组匹配的数量再加1。 ```javascript var s = '_x_x'; @@ -159,12 +159,12 @@ var r = /_(x)/; r.exec(s) // ["_x", "x"] ``` -上面代码的`exec`方法,返回一个数组。第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果。 +上面代码的`exec()`方法,返回一个数组。第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果。 -`exec`方法的返回数组还包含以下两个属性: +`exec()`方法的返回数组还包含以下两个属性: - `input`:整个原字符串。 -- `index`:整个模式匹配成功的开始位置(从0开始计数)。 +- `index`:模式匹配成功的开始位置(从0开始计数)。 ```javascript var r = /a(b+)a/; @@ -178,7 +178,7 @@ arr.input // "_abbba_aba_" 上面代码中的`index`属性等于1,是因为从原字符串的第二个位置开始匹配成功。 -如果正则表达式加上`g`修饰符,则可以使用多次`exec`方法,下一次搜索的位置从上一次匹配成功结束的位置开始。 +如果正则表达式加上`g`修饰符,则可以使用多次`exec()`方法,下一次搜索的位置从上一次匹配成功结束的位置开始。 ```javascript var reg = /a/g; @@ -204,7 +204,7 @@ r4 // null reg.lastIndex // 0 ``` -上面代码连续用了四次`exec`方法,前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配结束以后,整个字符串已经到达尾部,匹配结果返回`null`,正则实例对象的`lastIndex`属性也重置为`0`,意味着第四次匹配将从头开始。 +上面代码连续用了四次`exec()`方法,前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配结束以后,整个字符串已经到达尾部,匹配结果返回`null`,正则实例对象的`lastIndex`属性也重置为`0`,意味着第四次匹配将从头开始。 利用`g`修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。 @@ -222,7 +222,7 @@ while(true) { // #8:a ``` -上面代码中,只要`exec`方法不返回`null`,就会一直循环下去,每次输出匹配的位置和匹配的文本。 +上面代码中,只要`exec()`方法不返回`null`,就会一直循环下去,每次输出匹配的位置和匹配的文本。 正则实例对象的`lastIndex`属性不仅可读,还可写。设置了`g`修饰符的时候,只要手动设置了`lastIndex`的值,就会从指定位置开始匹配。 @@ -298,7 +298,7 @@ str.replace(search, replacement) 'aaa'.replace(/a/g, 'b') // "bbb" ``` -上面代码中,最后一个正则表达式使用了`g`修饰符,导致所有的`b`都被替换掉了。 +上面代码中,最后一个正则表达式使用了`g`修饰符,导致所有的`a`都被替换掉了。 `replace`方法的一个应用,就是消除字符串首尾两端的空格。 @@ -485,7 +485,7 @@ str.split(separator, [limit]) 上面代码指的是,`a`和`b`之间有一个空格或者一个制表符。 -其他的元字符还包括`\`、`\*`、`+`、`?`、`()`、`[]`、`{}`等,将在下文解释。 +其他的元字符还包括`\`、`*`、`+`、`?`、`()`、`[]`、`{}`等,将在下文解释。 ### 转义符 @@ -694,7 +694,7 @@ var html = "Hello\nworld!"; ### 贪婪模式 -上一小节的三个量词符,默认情况下都是最大可能匹配,即匹配直到下一个字符不满足匹配规则为止。这被称为贪婪模式。 +上一小节的三个量词符,默认情况下都是最大可能匹配,即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。 ```javascript var s = 'aaa'; @@ -703,29 +703,31 @@ s.match(/a+/) // ["aaa"] 上面代码中,模式是`/a+/`,表示匹配1个`a`或多个`a`,那么到底会匹配几个`a`呢?因为默认是贪婪模式,会一直匹配到字符`a`不出现为止,所以匹配结果是3个`a`。 -如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。 +除了贪婪模式,还有非贪婪模式,即最小可能匹配。只要一发现匹配,就返回结果,不要往下检查。如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。 ```javascript var s = 'aaa'; s.match(/a+?/) // ["a"] ``` -上面代码中,模式结尾添加了一个问号`/a+?/`,这时就改为非贪婪模式,一旦条件满足,就不再往下匹配。 +上面例子中,模式结尾添加了一个问号`/a+?/`,这时就改为非贪婪模式,一旦条件满足,就不再往下匹配,`+?`表示只要发现一个`a`,就不再往下匹配了。 -除了非贪婪模式的加号,还有非贪婪模式的星号(`*`)和非贪婪模式的问号(`?`)。 +除了非贪婪模式的加号(`+?`),还有非贪婪模式的星号(`*?`)和非贪婪模式的问号(`??`)。 - `+?`:表示某个模式出现1次或多次,匹配时采用非贪婪模式。 - `*?`:表示某个模式出现0次或多次,匹配时采用非贪婪模式。 - `??`:表格某个模式出现0次或1次,匹配时采用非贪婪模式。 ```javascript -'abb'.match(/ab*b/) // ["abb"] -'abb'.match(/ab*?b/) // ["ab"] +'abb'.match(/ab*/) // ["abb"] +'abb'.match(/ab*?/) // ["a"] -'abb'.match(/ab?b/) // ["abb"] -'abb'.match(/ab??b/) // ["ab"] +'abb'.match(/ab?/) // ["ab"] +'abb'.match(/ab??/) // ["a"] ``` +上面例子中,`/ab*/`表示如果`a`后面有多个`b`,那么匹配尽可能多的`b`;`/ab*?/`表示匹配尽可能少的`b`,也就是0个`b`。 + ### 修饰符 修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部。 diff --git a/docs/stdlib/string.md b/docs/stdlib/string.md index 08feda3..63d21fa 100644 --- a/docs/stdlib/string.md +++ b/docs/stdlib/string.md @@ -110,7 +110,7 @@ s.charAt(s.length - 1) // "c" ### String.prototype.charCodeAt() -`charCodeAt`方法返回字符串指定位置的 Unicode 码点(十进制表示),相当于`String.fromCharCode()`的逆操作。 +`charCodeAt()`方法返回字符串指定位置的 Unicode 码点(十进制表示),相当于`String.fromCharCode()`的逆操作。 ```javascript 'abc'.charCodeAt(1) // 98 @@ -131,7 +131,7 @@ s.charAt(s.length - 1) // "c" 'abc'.charCodeAt(4) // NaN ``` -注意,`charCodeAt`方法返回的 Unicode 码点不会大于65536(0xFFFF),也就是说,只返回两个字节的字符的码点。如果遇到码点大于 65536 的字符(四个字节的字符),必需连续使用两次`charCodeAt`,不仅读入`charCodeAt(i)`,还要读入`charCodeAt(i+1)`,将两个值放在一起,才能得到准确的字符。 +注意,`charCodeAt`方法返回的 Unicode 码点不会大于65536(0xFFFF),也就是说,只返回两个字节的字符的码点。如果遇到码点大于 65536 的字符(四个字节的字符),必须连续使用两次`charCodeAt`,不仅读入`charCodeAt(i)`,还要读入`charCodeAt(i+1)`,将两个值放在一起,才能得到准确的字符。 ### String.prototype.concat() @@ -166,7 +166,7 @@ one + two + three // "33" ### String.prototype.slice() -`slice`方法用于从原字符串取出子字符串并返回,不改变原字符串。它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置)。 +`slice()`方法用于从原字符串取出子字符串并返回,不改变原字符串。它的第一个参数是子字符串的开始位置,第二个参数是子字符串的结束位置(不含该位置)。 ```javascript 'JavaScript'.slice(0, 4) // "Java" @@ -186,7 +186,7 @@ one + two + three // "33" 'JavaScript'.slice(-2, -1) // "p" ``` -如果第一个参数大于第二个参数,`slice`方法返回一个空字符串。 +如果第一个参数大于第二个参数(正数情况下),`slice()`方法返回一个空字符串。 ```javascript 'JavaScript'.slice(2, 1) // "" diff --git a/docs/stdlib/wrapper.md b/docs/stdlib/wrapper.md index 7c3367b..7e89bf3 100644 --- a/docs/stdlib/wrapper.md +++ b/docs/stdlib/wrapper.md @@ -124,5 +124,5 @@ Number.prototype.double = function () { (123).double() // 246 ``` -上面代码在`String`和`Number`这两个对象的原型上面,分别自定义了一个方法,从而可以在所有实例对象上调用。注意,最后一张的`123`外面必须要加上圆括号,否则后面的点运算符(`.`)会被解释成小数点。 +上面代码在`String`和`Number`这两个对象的原型上面,分别自定义了一个方法,从而可以在所有实例对象上调用。注意,最后一行的`123`外面必须要加上圆括号,否则后面的点运算符(`.`)会被解释成小数点。 diff --git a/docs/types/array.md b/docs/types/array.md index d2fde5b..94944bf 100644 --- a/docs/types/array.md +++ b/docs/types/array.md @@ -124,7 +124,7 @@ arr.length // 1001 上面代码表示,数组的数字键不需要连续,`length`属性的值总是比最大的那个整数键大`1`。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。 -`length`属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到`length`设置的值。 +`length`属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到`length`设置的值。 ```javascript var arr = [ 'a', 'b', 'c' ]; @@ -303,7 +303,7 @@ var a = [1, , 1]; a.length // 3 ``` -上面代码表明,数组的空位不影响`length`属性。 +上面代码表明,数组的空位不影响`length`属性。虽然这个位置没有值,引擎依然认为这个位置是有效的。 需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。 diff --git a/docs/types/function.md b/docs/types/function.md index 71df9bb..751bca5 100644 --- a/docs/types/function.md +++ b/docs/types/function.md @@ -1,6 +1,6 @@ # 函数 -函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。 +函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数有唯一对应的返回值。 ## 概述 @@ -186,7 +186,9 @@ f(); f = function () {}; ``` -上面代码第二行,调用`f`的时候,`f`只是被声明了,还没有被赋值,等于`undefined`,所以会报错。因此,如果同时采用`function`命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。 +上面代码第二行,调用`f`的时候,`f`只是被声明了,还没有被赋值,等于`undefined`,所以会报错。 + +注意,如果像下面例子那样,采用`function`命令和`var`赋值语句声明同一个函数,由于存在函数提升,最后会采用`var`赋值语句的定义。 ```javascript var f = function () { @@ -200,6 +202,8 @@ function f() { f() // 1 ``` +上面例子中,表面上后面声明的函数`f`,应该覆盖前面的`var`赋值语句,但是由于存在函数提升,实际上正好反过来。 + ## 函数的属性和方法 ### name 属性 @@ -256,7 +260,7 @@ f.length // 2 ### toString() -函数的`toString`方法返回一个字符串,内容是函数的源码。 +函数的`toString()`方法返回一个字符串,内容是函数的源码。 ```javascript function f() { @@ -273,6 +277,8 @@ f.toString() // } ``` +上面示例中,函数`f`的`toString()`方法返回了`f`的源码,包含换行符在内。 + 对于那些原生的函数,`toString()`方法返回`function (){[native code]}`。 ```javascript @@ -280,7 +286,7 @@ Math.sqrt.toString() // "function sqrt() { [native code] }" ``` -上面代码中,`Math.sqrt`是 JavaScript 引擎提供的原生函数,`toString()`方法就返回原生代码的提示。 +上面代码中,`Math.sqrt()`是 JavaScript 引擎提供的原生函数,`toString()`方法就返回原生代码的提示。 函数内部的注释也可以返回。 @@ -315,6 +321,8 @@ multiline(f); // 多行注释" ``` +上面示例中,函数`f`内部有一个多行注释,`toString()`方法拿到`f`的源码后,去掉首尾两行,就得到了一个多行字符串。 + ## 函数作用域 ### 定义 @@ -546,7 +554,7 @@ f(obj); obj // [1, 2, 3] ``` -上面代码中,在函数`f`内部,参数对象`obj`被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(`o`)的值实际是参数`obj`的地址,重新对`o`赋值导致`o`指向另一个地址,保存在原地址上的值当然不受影响。 +上面代码中,在函数`f()`内部,参数对象`obj`被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(`o`)的值实际是参数`obj`的地址,重新对`o`赋值导致`o`指向另一个地址,保存在原地址上的值当然不受影响。 ### 同名参数 @@ -560,7 +568,7 @@ function f(a, a) { f(1, 2) // 2 ``` -上面代码中,函数`f`有两个参数,且参数名都是`a`。取值的时候,以后面的`a`为准,即使后面的`a`没有值或被省略,也是以其为准。 +上面代码中,函数`f()`有两个参数,且参数名都是`a`。取值的时候,以后面的`a`为准,即使后面的`a`没有值或被省略,也是以其为准。 ```javascript function f(a, a) { @@ -570,7 +578,7 @@ function f(a, a) { f(1) // undefined ``` -调用函数`f`的时候,没有提供第二个参数,`a`的取值就变成了`undefined`。这时,如果要获得第一个`a`的值,可以使用`arguments`对象。 +调用函数`f()`的时候,没有提供第二个参数,`a`的取值就变成了`undefined`。这时,如果要获得第一个`a`的值,可以使用`arguments`对象。 ```javascript function f(a, a) { @@ -613,7 +621,7 @@ var f = function(a, b) { f(1, 1) // 5 ``` -上面代码中,函数`f`调用时传入的参数,在函数内部被修改成`3`和`2`。 +上面代码中,函数`f()`调用时传入的参数,在函数内部被修改成`3`和`2`。 严格模式下,`arguments`对象与函数参数不具有联动关系。也就是说,修改`arguments`对象不会影响到实际的函数参数。 @@ -691,7 +699,7 @@ f1() // 999 上面代码中,函数`f1`可以读取全局变量`n`。 -但是,函数外部无法读取函数内部声明的变量。 +但是,正常情况下,函数外部无法读取函数内部声明的变量。 ```javascript function f1() { @@ -736,7 +744,7 @@ result(); // 999 闭包就是函数`f2`,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。闭包最大的特点,就是它可以"记住"诞生的环境,比如`f2`记住了它诞生的环境`f1`,所以从`f2`可以得到`f1`的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 -闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。 +闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。 ```javascript function createIncrementor(start) { @@ -754,7 +762,7 @@ inc() // 7 上面代码中,`start`是函数`createIncrementor`的内部变量。通过闭包,`start`的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包`inc`使得函数`createIncrementor`的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。 -为什么会这样呢?原因就在于`inc`始终在内存中,而`inc`的存在依赖于`createIncrementor`,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。 +为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的`inc`)用到了外层变量(`start`),导致外层函数(`createIncrementor`)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。 闭包的另一个用处,是封装对象的私有属性和私有方法。 @@ -786,7 +794,7 @@ p1.getAge() // 25 ### 立即调用的函数表达式(IIFE) -在 JavaScript 中,圆括号`()`是一种运算符,跟在函数名之后,表示调用该函数。比如,`print()`就表示调用`print`函数。 +根据 JavaScript 的语法,圆括号`()`跟在函数名之后,表示调用该函数。比如,`print()`就表示调用`print`函数。 有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。 @@ -795,7 +803,7 @@ function(){ /* code */ }(); // SyntaxError: Unexpected token ( ``` -产生这个错误的原因是,`function`这个关键字即可以当作语句,也可以当作表达式。 +产生这个错误的原因是,`function`这个关键字既可以当作语句,也可以当作表达式。 ```javascript // 语句 @@ -805,9 +813,18 @@ function f() {} var f = function f() {} ``` -为了避免解析上的歧义,JavaScript 引擎规定,如果`function`关键字出现在行首,一律解释成语句。因此,JavaScript 引擎看到行首是`function`关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。 +当作表达式时,函数可以定义后直接加圆括号调用。 + +```javascript +var f = function f(){ return 1}(); +f // 1 +``` + +上面的代码中,函数定义后直接加圆括号调用,没有报错。原因就是`function`作为表达式,引擎就把函数定义当作一个值。这种情况下,就不会报错。 + +为了避免解析的歧义,JavaScript 规定,如果`function`关键字出现在行首,一律解释成语句。因此,引擎看到行首是`function`关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。 -解决方法就是不要让`function`出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。 +函数定义后立即调用的解决方法,就是不要让`function`出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。 ```javascript (function(){ /* code */ }()); @@ -815,7 +832,7 @@ var f = function f() {} (function(){ /* code */ })(); ``` -上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做"立即调用的函数表达式"(Immediately-Invoked Function Expression),简称 IIFE。 +上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表达式,而不是函数定义语句,所以就避免了错误。这就叫做"立即调用的函数表达式"(Immediately-Invoked Function Expression),简称 IIFE。 注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。 @@ -931,7 +948,7 @@ a // 2 上面代码中,严格模式下,`eval`内部还是改写了外部变量,可见安全风险依然存在。 -总之,`eval`的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,所以一般不推荐使用。通常情况下,`eval`最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的`JSON.parse`方法。 +总之,`eval`的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,一般不推荐使用。通常情况下,`eval`最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的`JSON.parse`方法。 ### eval 的别名调用 @@ -975,8 +992,8 @@ window.eval('...') ## 参考链接 - Ben Alman, [Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) -- Mark Daggett, [Functions Explained](http://markdaggett.com/blog/2013/02/15/functions-explained/) -- Juriy Zaytsev, [Named function expressions demystified](http://kangax.github.com/nfe/) +- Mark Daggett, [Functions Explained](https://web.archive.org/web/20160911170816/http://markdaggett.com/blog/2013/02/15/functions-explained/) +- Juriy Zaytsev, [Named function expressions demystified](http://kangax.github.io/nfe/) - Marco Rogers polotek, [What is the arguments object?](http://docs.nodejitsu.com/articles/javascript-conventions/what-is-the-arguments-object) - Juriy Zaytsev, [Global eval. What are the options?](http://perfectionkills.com/global-eval-what-are-the-options/) - Axel Rauschmayer, [Evaluating JavaScript code via eval() and new Function()](http://www.2ality.com/2014/01/eval.html) diff --git a/docs/types/general.md b/docs/types/general.md index 3c0ea7e..6d512ef 100644 --- a/docs/types/general.md +++ b/docs/types/general.md @@ -2,12 +2,12 @@ ## 简介 -JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值,本教程不涉及。) +JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了 Symbol 和 BigInt 数据类型,本教程不涉及。) -- 数值(number):整数和小数(比如`1`和`3.14`) +- 数值(number):整数和小数(比如`1`和`3.14`)。 - 字符串(string):文本(比如`Hello World`)。 -- 布尔值(boolean):表示真伪的两个特殊值,即`true`(真)和`false`(假) -- `undefined`:表示"未定义"或不存在,即由于目前没有定义,所以此处暂时没有任何值 +- 布尔值(boolean):表示真伪的两个特殊值,即`true`(真)和`false`(假)。 +- `undefined`:表示"未定义"或不存在,即由于目前没有定义,所以此处暂时没有任何值。 - `null`:表示空值,即此处的值为空。 - 对象(object):各种值组成的集合。 diff --git a/docs/types/null-undefined-boolean.md b/docs/types/null-undefined-boolean.md index 9a76679..129dda3 100644 --- a/docs/types/null-undefined-boolean.md +++ b/docs/types/null-undefined-boolean.md @@ -35,7 +35,7 @@ undefined == null 既然含义与用法都差不多,为什么要同时设置两个这样的值,这不是无端增加复杂度,令初学者困扰吗?这与历史原因有关。 -1995年 JavaScript 诞生时,最初像 Java 一样,只设置了`null`表示"无"。根据 C 语言的传统,`null`可以自动转为`0`。 +1995年 JavaScript 诞生时,最初像 Java 一样,只设置了`null`表示"无"。根据 C 语言的传统,`null`可以自动转为`0`。 ```javascript Number(null) // 0 @@ -46,7 +46,7 @@ Number(null) // 0 但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。首先,第一版的 JavaScript 里面,`null`就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示"无"的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果`null`自动转为0,很不容易发现错误。 -因此,他又设计了一个`undefined`。区别是这样的:`null`是一个表示"空"的对象,转为数值时为`0`;`undefined`是一个表示"此处无定义"的原始值,转为数值时为`NaN`。 +因此,他又设计了一个`undefined`。区别是这样的:`null`是一个表示"空"的对象,转为数值时为`0`;`undefined`是一个表示"此处无定义"的原始值,转为数值时为`NaN`。 ```javascript Number(undefined) // NaN diff --git a/docs/types/number.md b/docs/types/number.md index af8954e..53ebfb5 100644 --- a/docs/types/number.md +++ b/docs/types/number.md @@ -43,7 +43,7 @@ JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整 上面公式是正常情况下(指数部分在0到2047之间),一个数在 JavaScript 内部实际的表示形式。 -精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253到253,都可以精确表示。 +精度最多只能到53个二进制位,这意味着,绝对值小于2的53次方的整数,即-253 + 1 到 253 - 1,都可以精确表示。 ```javascript Math.pow(2, 53) @@ -280,6 +280,12 @@ NaN * 32 // NaN NaN / 32 // NaN ``` +但是,ES6 引入指数运算符(`**`)后,出现了一个例外。 + +```javascript +NaN ** 0 // 1 +``` + ### Infinity **(1)含义** @@ -552,7 +558,15 @@ parseFloat('3.14more non-digit characters') // 3.14 parseFloat('\t\v\r12.34\n ') // 12.34 ``` -如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回`NaN`。 +如果参数不是字符串,则会先转为字符串再转换。 + +```javascript +parseFloat([1.23]) // 1.23 +// 等同于 +parseFloat(String([1.23])) // 1.23 +``` + +如果字符串的第一个字符不能转化为浮点数,则返回`NaN`。 ```javascript parseFloat([]) // NaN @@ -651,4 +665,4 @@ isFinite(-1) // true ## 参考链接 - Dr. Axel Rauschmayer, [How numbers are encoded in JavaScript](http://www.2ality.com/2012/04/number-encoding.html) -- Humphry, [JavaScript 中 Number 的一些表示上/下限](http://blog.segmentfault.com/humphry/1190000000407658) +- Humphry, [JavaScript 中 Number 的一些表示上/下限](https://segmentfault.com/a/1190000000407658) diff --git a/docs/types/object.md b/docs/types/object.md index 9e50984..3ff539c 100644 --- a/docs/types/object.md +++ b/docs/types/object.md @@ -497,5 +497,5 @@ console.log(temp.p1 + temp.p2); - Dr. Axel Rauschmayer,[Object properties in JavaScript](http://www.2ality.com/2012/10/javascript-properties.html) - Lakshan Perera, [Revisiting JavaScript Objects](http://www.laktek.com/2012/12/29/revisiting-javascript-objects/) -- Angus Croll, [The Secret Life of JavaScript Primitives](http://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/)i +- Angus Croll, [The Secret Life of JavaScript Primitives](http://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/) - Dr. Axel Rauschmayer, [JavaScript’s with statement and why it’s deprecated](http://www.2ality.com/2011/06/with-statement.html) diff --git a/package.json b/package.json index a7eeda3..479b7bb 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,9 @@ "build-and-commit": "npm run build && npm run commit", "commit": "gh-pages --dist dist --dest dist/javascript --branch master --add --repo git@github.com:wangdoc/website.git", "chapter": "loppo chapter", + "server": "loppo server", "test": "echo \"Error: no test specified\" && exit 1" }, - "husky": { - "hooks": { - "pre-push": "npm update" - } - }, "repository": { "type": "git", "url": "git+https://github.com/wangdoc/javascript-tutorial.git" @@ -34,9 +30,8 @@ }, "homepage": "https://github.com/wangdoc/javascript-tutorial", "dependencies": { - "gh-pages": "latest", - "husky": "^3.0.5", - "loppo": "latest", - "loppo-theme-wangdoc": "latest" + "gh-pages": "6.x", + "loppo": "^0.6.24", + "loppo-theme-wangdoc": "^0.7.3" } }

    AltStyle によって変換されたページ (->オリジナル) /