最近在看 赵泽欣 / 鄢学鹍 翻译的 蝴蝶书, 把一些读后感言记录在这里。

主要是把作者的建议跟 ES5/ES5.1/ES6 新添加的功能进行了对比

涉及到的一些定义

  • IIFE: Immediately Invoked Function Expression

    立即执行函数表达式, 一般用来隐藏私有变量, 避免临时变量污染全局空间

    常见的形式为:
(function(){
// function body
})()

更多请参见 这篇文章

  • Hoisting: 变量定义提升

请参考 JavaScript Scoping and Hoisting

  • 严格模式

一个 JavaScript 子集用作提供更彻底的错误检查。

更多内容请参考 MDN

  • ES6 - ECMAScript 2015

ECMA组织制订的 JavaScript 语言的第6个标准

JavaScript 详细的历史版本请参考 阮一峰大神写的 JavaScript语言的历史

以及这篇文章 ECMAScript版本历史

之前不知道的坑

  • 在块注释中使用正则表达式可能出问题

如果在快注释中出现 "*/", JavaScript 解释器无法正常工作了。 下图代码执行时会报语法错误


/*/\d*/*/
  • 尽量不要使用位运算

JavaScript 里面没有整数类型,只有双精度的浮点数。

因此位运算符会把它们的运算数从浮点数转换为整数,接着执行运算,然后再转换会浮点数。

在大多数语言中,这些运算符接近于硬件处理,所以非常快。

但 JavaScript 的执行环境一般接触不到硬件,所以非常慢

  • parseInt

如果一个字符串中有数字和其他字符,parseInt 会解析从字符串开头出现的数字,直到字符串结尾或者碰到不可解析的字符。

var num = parseInt('3abcd'); // 3

parseInt 接受参数,第二个参数表示要解析数字的基数。该值介于 2~36 之间。

如果字符串以 '0x' 或者 '0X' 开头,praseInt函数就会把第一个参数当作十六进制字符串来解析。

作者建议手动指定第二个参数来避免意外的转换。

parseInt('0x3a'); // 58

parseInt('0x3a', 10); // 0
  • 正则表达式函数对 g 标识的处理方法
函数 处理方式
RegExp.prototype.test 忽略 g 标识
RegExp.prototype.exec 每次调用 exec 返回一次匹配
String.prototype.search 忽略
String.prototype.split 忽略
String.prototype.replace - 如果函数参数是一个正则表达式并且带有 g 标识,它会替换所有的匹配。

- 如果表达式没有带 g 标识,它会仅替换第一个匹配
String.prototype.match - 没有 g 标识, 与调用regex.exec(string)相同


- 表达式使用 g 标识 生成一个包含所有匹配(忽略捕获分组)的数组

严格模式下已经改进的缺点

  • 全局的变量定义

传统的JS中,如果在函数中定义变量时没有使用 var 关键词, 那么这个变量就会被作为全局对象( 浏览器中的 window 对象, 或者 NodeJS 里面的 global 对象) 的属性.

严格模式下则不允许不适用 var/let/const 来定义变量。

// 使用 IIFE, 在函数中定义 name 变量并赋值
(function(){
name = "david";
})(); // 使用 hasOwnProperty window 对象 判断是否含有 name 属性
window.hasOwnProperty('name'); // true // 使用严格模式
function func(){
'use strict';
name = 'david'; // Uncaught ReferenceError: a is not defined
}
  • with 关键字不能使用

DC 不推荐使用 with 关键字来访问对象属性, 严格模式下已经禁用 with 关键字。

let

ES2016 引入了 let/const 来定义局部变量/常量。

let/const 的引入解决了书中的一些问题

  • 变量重复定义

用户可以使用 var 在同一个函数作用于里面定义多个同名的变量。但是使用 let 不可以

{
let a = 1;
let a = 10; // Uncaught SyntaxError: Identifier 'a' has already been declared
}
  • 拥有局部作用域
// 使用 var 定义的变量在 for 循环外可以引用
for (var i = 0; i < 10; i++) {}
console.log(i); //10 // 使用 let 定义的变量在 for 循环外不可使用
for(let j = 0; j < 10; j++) {}
console.log(j);// Error: j is not define
  • 变量提升

使用 let 初始化的变量不会进行 Hoisting 变量定义提升。

console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError var foo = 2;
let bar = 2;
  • 作用域

使用 let 在全局作用于定义的对象,也不过作为全局变量的属性。

let blog_link='http://www.cnblogs.com/zf-l/';
window.hasOwnProperty('blog_link'); //false
  • 不需要使用 IIFE 来解决闭包问题

在 JS 中,如果一个函数多次调用函数外部的变量。可能会出现不可预知的问题:

// 在一个循环中使用定时器打印外层的变量值
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 输出5次5
},0);
} // 通过给临时函数赋值来解决
for (var k = 0; k < 5; k++) {
(function (k) {
setTimeout(function () {
console.log(k); //输出0,1,2,3,4
},0);
})(k);
} // 使用 let 关键字
for (let j = 0; j < 5; j++) {
setTimeout(function () {
console.log(j); //输出0,1,2,3,4
},0);
}

已经实现的建议添加的函数

DC 书中列出了好多有用的函数,其中一些已经在 ES5.x/ES6 中实现

  • Object.create

接受对象A作为参数,返回一个新的对象 B ,B 的原型是 A。

ES5 中已经实现。

Object.create = function(o){
var F = function(){};
F.prototype = o;
return new F();
}
  • Array.prototype.reduce
Array.method('reduce', function(f, value){
vai i;
for(i = 0; i < this.length; i+=1){
value = f(this[i], value);
return value;
}
})

除了 reduce 函数, ES5 中还定义了这些有用的高阶函数

Array.prototype.reduceLeft

Array.prototype.some

Array.prototype.every

Array.prototype.map

Array.prototype.filter

Array.prototype.forEach

  • 判断是否是数组

ES5 中定义了 Array.isArray 方法。 下面是书中的实现。

 var is_array = function(value){
return Object.prototype.toString.apply(value) === '[object Array]';
}
  • 柯里化

ES5 中可以通过 Function.prototype.bind 来实现函数的柯里化。下面是书中的实现方式


Function.method('curry', function(){
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function(){
return that.apply(null, args.concat(slice.apply(arguments)));
}
})

面向对象方面的改进

在蝴蝶书里面,作者指出了 JavaScript 构造函数的几个缺点

  1. 没有私有环境,所有的属性都是公开的
  2. 无法访问 super (父类) 的方法
  3. 调用构造函数的时候忘记加 new 方法会造成严重的危害

其中第一个缺点 ES6 中还没有解决,另外两个已经可以防止。

  • 使用 new 前缀调用构造函数

在 JavaScript 中, 如果在调用构造函数时忘记了在前面加上 new 前缀,那么 this 将不会绑定到一个新对象上。而是绑定到全局对象。

所以作者建议:

类名使用首字母大写的形式,并且不以首字母大写的形式拼写任何其他的东西。这样至少可以通过目视检查去发现是否缺少了 new 前缀。

一个更好的备选方案是不适用 new。

ES6 新添加了 class 语法, 使用 class 前缀定义的 类必须使用 new 前缀来调用,否则解释器就会报错。


class A{
constructor(){
console.log(new.target.name);
}
} A(); // Uncaught TypeError: Class constructor A cannot be invoked without 'new'

注: 在构造方法调用中,new.target 指向被 new 调用的构造函数。

  • 调用父类的方法和属性

ES6 提供了 super 关键字访问父类的构造函数和方法

super 有两种使用模式:

  1. super 当作函数来使用,会调用父类的构造函数
  2. 使用 super.prop 来访问父类的属性和方法

下面 MDN 中的例子说明了如何使用 super 调用父类的构造函数和方法


class Rectangle{
constructor(height, width) {
this.name = 'Rectangle';
this.height = height;
this.width = width;
}
sayName() {
console.log('Hi, I am a ', this.name + '.');
}
} class Square extends Rectangle{
constructor(length) {
this.height; // ReferenceError, super 必须在 this 之前调用 // 调用父类的构造函数,提供长方形的长度和宽度
super(length, length); // Note: 在子类中,super() 必须在使用 this 之前调用
// 否则解释器会抛出 reference error.
this.name = 'Square';
} get area() {
return this.height * this.width;
} set area(value) {
this.height = this.width = Math.sqrt(value);
} sayName() {
// 调用父类的同名方法
super.sayName();
console.log('Hi from Square .');
}
}

ES6 的其他改进

枚举数组

for in 可以用来遍历一个数组的所有属性, 遗憾的是, for in 无法保证属性的顺序,而大多数要遍历数组的场合都希望按照阿拉伯数字顺序来产生元素

ES6 提供了 for of 语法来遍历数组

// 使用 for 循环遍历数组
var i;
for(i = 0; i < myArray.length; i += 1){
console.log(myArray[i]);
} // 使用 for of 遍历数组
for(let i of myArray){
console.log(i);
}

不定参数

在 ES6 出现之前, JS 处理不定参数只能使用 arguments 关键字。在文中作者如是说

因为语言的一个设计错误, arguments 并不是一个真正的数组。它只是一个 "类似数组 (array-like)" 的对象。arguments 拥有一个 length 属性,但它没有任何数组的方法

下面的代码中分别使用 ES5 和 ES6 的语法实现了函数默认参数

var sum = function(){
var i, sum = 0;
for (i=0; i < arguments.length; i+=1){
sum += arguments[i];
}; return sum;
} document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108

相比较之下 ES6 中不定参数的实现方式就比较优雅, 跟 Python 中的 *args 类似。

var sum = function(...nums){
// 判断是否是数组
console.log(Array.isArray(nums)); // true
return nums.reduce((x,y)=>x+y);
} document.writeln(sum(4, 8, 15, 16, 23, 42)); // 108

参数默认值

ES6 之前函数不支持在参数列表中指定默认值。

作者往往使用 || 给函数参数选择是否使用默认值。ES6 提供了在函数形参中指定默认参数的方法。

下面的例子中分别使用 ES5 和 ES6 的语法定义默认参数

// 使用 || 在函数体中定义默认值
var factorial = function factorial(i, a){
a = a || 1;
if(i<2){
return a;
}
return factorial(i-1, a*i);
} console.log(factorial(4)); // 24 // 使用 ES6 的默认参数重新定义 factorial 函数
var factorial = function factorial(i, a=1){
if(i<2){
return a;
}
return factorial(i-1, a*i);
} console.log(factorial(4)); // 24

有争议的部分

自动分号

DC 建议 JS 语句每行的结尾都要加分号。但是随着技术的发展,有不同的声音提出来:

其他语法建议

  • 使用字符串的 slice 替换 substring

    slice 可以接受负数作为参数,完全可以替代 substring

ES6 入门资料

下面的列表我整理的一些学习 ES6 的资料和书籍,供各位看官参考

转载请注明出处: [zf-l](http://www.cnblogs.com/zf-l/p/notes_js_good_parts.html)

JavaScript 语言精粹读书笔记的更多相关文章

  1. <JavaScript语言精粹>-读书笔记(一)

    用object.hasOwnProperty(variable)来确定这个属性名是否为该对象成员,还是来自于原型链. for(my in obj){ if(obj.hasOwnProperty(my) ...

  2. JavaScript语言精粹-读书笔记

    前言:很久之前读过一遍该书,近日得闲,重拾该书,详细研究一方,欢迎讨论指正. 目录: 1.精华 2.语法 3.对象 4.函数 5.继承 6.数组 7.正则表达式 8.方法 9.代码风格 10.优美的特 ...

  3. JavaScript语言精粹读书笔记 - JavaScript函数

    JavaScript是披着C族语言外衣的LISP,除了词法上与C族语言相似以外,其他几乎没有相似之处. JavaScript 函数: 函数包含一组语句,他们是JavaScript的基础模块单元,用于代 ...

  4. <JavaScript语言精粹>--<读书笔记三>之replace()与正则

    今天有人问我repalce(),他那个题目很有意思.我也不会做,于是我就去查,结果发现就是最基础的知识的延伸. 所以啊最基础的知识才是很重要的,千万不能忽略,抓起JS就写代码完全不知到所以然,只知道写 ...

  5. JavaScript语言精粹读书笔记- JavaScript对象

    JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collecti ...

  6. 学习javascript语言精粹的笔记

    1.枚举: 用for in 语句来遍历一个对象中所有的属性名,该枚举过程将会列出所有的属性也包括涵数和方法,如果我们想过滤掉那些不想要的值,最为常用的过滤器为hasOwnProperty方法,以及使用 ...

  7. 【Javascript语言精粹】笔记摘要

    现在大部分编译语言中都流行要求强类型.其原理在于强类型允许编译器在编译时检测错误.我们能越早检测和修复错误,付出的代价越小.Javascript是一门弱类型的语言,所以Javascript编译器不能检 ...

  8. js语言精粹读书笔记一

    一.语法 1.

  9. JavaScript语言精粹学习笔记

    0.JavaScript的简单数据类型包括数字.字符创.布尔值(true/false).null和undefined值,其它值都是对象. 1.JavaScript只有一个数字类型,它在内部被表示为64 ...

随机推荐

  1. 为什么Java中的String类是不可变的?

    String类是Java中的一个不可变类(immutable class). 简单来说,不可变类就是实例在被创建之后不可修改. 在<Effective Java> Item 15 中提到了 ...

  2. eclipse通过maven构建web项目步骤说明

    1.  File -> New -> Other ,搜索maven,选择Maven Project,点击Next 2.这里不需要改继续Next 3.这里需要注意,需要选择maven-arc ...

  3. TEXT宏

    TEXT宏是windows程序设计中经常遇到的宏,定义在 <winnt.h>中 TCHAR *P = TEXT("this is a const string"); 如 ...

  4. unset与unlink

    unset() -- 释放给定的变量 详见->http://www.kuqin.com/php5_doc/function.unset.html unlink() --删除文件    常用于用户 ...

  5. [Tjoi2013]循环格

    [Tjoi2013]循环格 2014年3月18日1,7500 Description Input 第一行两个整数R,C.表示行和列,接下来R行,每行C个字符LRUD,表示左右上下. Output 一个 ...

  6. 【原】Spark 编程指南

    尊重原创,注重版权,转贴请注明原文地址:http://www.cnblogs.com/vincent-hv/p/3322966.html   1.配置程序使用资源: System.setPropert ...

  7. FPGA在AD采集中的应用

    AD转换,也叫模数转换,是将模拟信号转换为数字信号.目前包括电脑CPU,ARM,FPGA,处理的信号都只能是数字信号,所以数据信号在进入处理芯片前必须要进行AD转换. 在高速的AD转换中,FPGA以其 ...

  8. Druid连接池

    Druid 连接池简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.J ...

  9. PHP 支付

    蚂蚁金服开放平台 2.下载PHP的SDK&demo 3.申请应用 OR 使用沙箱环境 4.生成应用私钥&应用公钥 5.配置config.php 蚂蚁金服开放平台",对,没错, ...

  10. win10 UWP 序列化

    将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. .NET Framewor ...