JavaScript 语言精粹读书笔记
最近在看 赵泽欣 / 鄢学鹍 翻译的 蝴蝶书, 把一些读后感言记录在这里。
主要是把作者的建议跟 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 构造函数的几个缺点
- 没有私有环境,所有的属性都是公开的
- 无法访问 super (父类) 的方法
- 调用构造函数的时候忘记加 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 有两种使用模式:
- 把
super
当作函数来使用,会调用父类的构造函数 - 使用
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 语言精粹读书笔记的更多相关文章
- <JavaScript语言精粹>-读书笔记(一)
用object.hasOwnProperty(variable)来确定这个属性名是否为该对象成员,还是来自于原型链. for(my in obj){ if(obj.hasOwnProperty(my) ...
- JavaScript语言精粹-读书笔记
前言:很久之前读过一遍该书,近日得闲,重拾该书,详细研究一方,欢迎讨论指正. 目录: 1.精华 2.语法 3.对象 4.函数 5.继承 6.数组 7.正则表达式 8.方法 9.代码风格 10.优美的特 ...
- JavaScript语言精粹读书笔记 - JavaScript函数
JavaScript是披着C族语言外衣的LISP,除了词法上与C族语言相似以外,其他几乎没有相似之处. JavaScript 函数: 函数包含一组语句,他们是JavaScript的基础模块单元,用于代 ...
- <JavaScript语言精粹>--<读书笔记三>之replace()与正则
今天有人问我repalce(),他那个题目很有意思.我也不会做,于是我就去查,结果发现就是最基础的知识的延伸. 所以啊最基础的知识才是很重要的,千万不能忽略,抓起JS就写代码完全不知到所以然,只知道写 ...
- JavaScript语言精粹读书笔记- JavaScript对象
JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collecti ...
- 学习javascript语言精粹的笔记
1.枚举: 用for in 语句来遍历一个对象中所有的属性名,该枚举过程将会列出所有的属性也包括涵数和方法,如果我们想过滤掉那些不想要的值,最为常用的过滤器为hasOwnProperty方法,以及使用 ...
- 【Javascript语言精粹】笔记摘要
现在大部分编译语言中都流行要求强类型.其原理在于强类型允许编译器在编译时检测错误.我们能越早检测和修复错误,付出的代价越小.Javascript是一门弱类型的语言,所以Javascript编译器不能检 ...
- js语言精粹读书笔记一
一.语法 1.
- JavaScript语言精粹学习笔记
0.JavaScript的简单数据类型包括数字.字符创.布尔值(true/false).null和undefined值,其它值都是对象. 1.JavaScript只有一个数字类型,它在内部被表示为64 ...
随机推荐
- 为什么Java中的String类是不可变的?
String类是Java中的一个不可变类(immutable class). 简单来说,不可变类就是实例在被创建之后不可修改. 在<Effective Java> Item 15 中提到了 ...
- eclipse通过maven构建web项目步骤说明
1. File -> New -> Other ,搜索maven,选择Maven Project,点击Next 2.这里不需要改继续Next 3.这里需要注意,需要选择maven-arc ...
- TEXT宏
TEXT宏是windows程序设计中经常遇到的宏,定义在 <winnt.h>中 TCHAR *P = TEXT("this is a const string"); 如 ...
- unset与unlink
unset() -- 释放给定的变量 详见->http://www.kuqin.com/php5_doc/function.unset.html unlink() --删除文件 常用于用户 ...
- [Tjoi2013]循环格
[Tjoi2013]循环格 2014年3月18日1,7500 Description Input 第一行两个整数R,C.表示行和列,接下来R行,每行C个字符LRUD,表示左右上下. Output 一个 ...
- 【原】Spark 编程指南
尊重原创,注重版权,转贴请注明原文地址:http://www.cnblogs.com/vincent-hv/p/3322966.html 1.配置程序使用资源: System.setPropert ...
- FPGA在AD采集中的应用
AD转换,也叫模数转换,是将模拟信号转换为数字信号.目前包括电脑CPU,ARM,FPGA,处理的信号都只能是数字信号,所以数据信号在进入处理芯片前必须要进行AD转换. 在高速的AD转换中,FPGA以其 ...
- Druid连接池
Druid 连接池简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.J ...
- PHP 支付
蚂蚁金服开放平台 2.下载PHP的SDK&demo 3.申请应用 OR 使用沙箱环境 4.生成应用私钥&应用公钥 5.配置config.php 蚂蚁金服开放平台",对,没错, ...
- win10 UWP 序列化
将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. .NET Framewor ...