Eloquent JavaScript Notes

#第一章 JavaScript基础:值、变量、控制流程

##值

JavaScript中有6种基本类型的值:number、string、Boolean、object、function和undefined。

###数字

number类型的值就是数字值,例如:

123

标准的JavaScript数字是64位的浮点型值,其中使用11位存储数字的小数点位置,1位存储数字的符号,剩下的52位保存数值,也就是说,小于$2^{52}$的整数都可以安全地写成JavaScript数字。

小数:

9.81

非常大或非常小的数字可以用科学技术发表示,例如:

1.23e9

1.23e9表示$1.23 \times 10^9$

整数计算精确,但小数计算精确度不高,比如$\pi$就无法通过有限的小数数字来精确表示,应将小数是为近似值而不是精确值。

###算术运算

###字符串

string的书写方式是用引号将内容括起来:

"Patch my boat with chewing gum."
'You ain\'t never seen a donkey fly!'

单引号和双引号都可以用来标记string,但要注意引号前后要一致。

转义字符:当在加引号的文字中发现反斜杠(),那就意味着其后面的字符有特殊意义。

###一元操作符

并不是所有的运算符都是符号,有些运算符是单词。例如typedef运算符,它产生一个字符串值,该字符串命名给定值的类型。

typeof 4.5
-->  "number"
typeof "x"
-->  "string"

###布尔值、比较和布尔逻辑

布尔类型只有两个值:true和false。

###表达式和语句

表达式

语句:以分号结束,有些情况下,Javascript允许忽略语句后面的分号,但建议在程序里每个语句后都有分号。

一个表达式或语句改变程序的内部状态以影响后面的语句, 这些改变称为副作用(side effect).

##变量

变量

关键字和保留字

##环境

在给定时间存在的变量和变量值的集合叫做环境。当浏览器加载一个页面时,它创建一个新环境,并将这些标准的变量存于新环境中。

###函数

标准环境提供的很多只都是函数类型的,函数是包含在值中的一段程序。例如在浏览器环境中,变量alert拥有一个函数,用于弹出带消息的小对话框:

alert("Good Luck!")

执行函数里的代码称为调用或应用,完成这一个过程所使用的符号是括号。

alert 弹出对话框是一个副作用,很多函数由于产生了副作用而变得非常有用. 另外, 函数也可以产生值, 这种情况下它就不需要副作用了.

###prompt 和 confirm

confirm 函数让用户选择 OK/Cancel 问题, 如果用户单击 OK, 则返回 true, 如果单击 Cancel, 则返回 False.

confirm("Shall we, then?");

prompt 函数可用于询问一个开放式问题, 第一个参数就是该问题; 第二个参数使用户需要输入文本的开头部分, 可以在对话窗口中输入一行文本, 该函数会将其返回作为一个字符串.

prompt("Tell me everything you know.", "...");

##程序结构

……

##进一步认识类型

###Undefined 值

声明变量但不赋值, 那么该变量是空的, 如果获取这样的变量值, 那么将得到一个特殊的值, 叫做 undefined. 如果一个函数没有特别指定返回值, 他的返回值也是 undefined 值.

还有一个类似的值–null, 其含义是”该值已经被定义, 但是没有任何值”. undefined 与 null 之间的主要是理论上的区别, 不需要太多关注.

实际应用中, 通常需要检测某些对象是否有值, 用 something == undefine表达式.

null == undefined 会返回 true.

###自动类型转换

false == 0
--> true
"" == 0
--> true
"5" == 5
--> true

从以上表达式及其产生的值可以看出, JavaScript 使用的规则非常复杂难懂.
但大多数情况下, 它都只是将一个类型的值转化为另一个类型的值.

0, NaN 和空字符串转化为 false, 其他所有值转化为 true.

如果不想发生自动类型转换, 可以使用两个额外的操作符: === 和 !==.

null == undefined
--> false
false === 0
--> false
"" == 0
--> false
"5" == 5
--> false

###自动类型转换的风险

非字符串值加字符串, 将会把非字符串自动换换位字符串, 如果是数字和字符串相乘, 会尝试将字符串转换为数字.

"Apollo" + 5
--> "Apollo5"
null + "ify"
--> "nullify"
"5" * 5
--> 25
"strawberry" * 5
--> NaN

#第二章 函数

##函数定义

function square(x){
  return x * x;
}

square(3);
--> 9

###定义顺序

位置无所谓, 因为: 在开始执行语句之前, 首先查找所有 function 的定义, 然后保存相关的 function, 所以不必思考多个函数的定义顺序.

###局部变量

函数重要特性: 内部创建的变量是函数的局部变量.

###嵌套作用域

###栈

###函数值

JavaScript 中的所有东西都是指, 包括 function, 函数名称可以像普通的变量一样使用, 而且其内容可以传递给表达式并用于更大的表达式.

var a = null;
function b(){return "B";}
(a || b)();
--> "B"

还可以利用匿名函数表达式.

var a = null;
(a || function(){return "B";})();
--> "B"

###闭包

函数栈和将函数作为值的能力带来一个有趣的问题: 如果创建局部变量的函数调用不在栈上, 那局部变量会发生什么变化?

function createFunction(){
  var local = 100;
  return function(){return local;};
}

一旦调用 createFunction, 就会创建一个局部变量(local), 并返回一个函数(该函数又返回这个创建的局部变量). 在 JavaScript 中, 只要这个局部变量是可达的, 就会尽力保存局部变量, 执行 createFunction(创建函数并执行)的返回值是100, 正是我们希望的.

这种特性称为闭包, 包裹一些局部变量的函数叫做一个闭包. 该行为不仅让我们不用担心变量是否依然存在, 而且还可以创造性地使用函数值.

例如, 下面的函数可以动态创建函数值: 将函数的参数加上指定的数字.

function makeAdder(amount){
  return function(number){
    return number + amount;
  };
}

var addTwo = makeAdder(2);
addTwo(3);
--> 5

var addThree = makeAdder(3);
addThree(3);
--> 6

###可选参数

alert("hello", "Good Evening", "How do you do?", "Good-bye");

alert 只接受一个参数, 当用上面的形式调用 alert 时, 不会出错, 只是忽略其他的参数.

JavaScript 不会限制传入函数的参数书目, 如果传入参数过多, 多余的参数被忽略掉, 如果传入参数过少, 缺失的参数则默认为 undefined.

这样做的坏处是可能导致传入错误的参数数目, 而且不会得到提醒.

好处是, 它可以使函数接受”可选参数”. 例如下面的 power 函数, 如果只传入一个参数, 那么它的行为就是平方:

function power(base, exponent){
  var result = 1;
  if(exponent === undefined)
    exponent = 2;
  for(var count = 0; count < exponent; count++)
    result += base;
  return result;
}

##技巧

###避免重复

###纯函数

纯函数指: 当使用这个函数, 同样的参数总是返回同样的值, 并且没有副作用.
通常来说, 如果我们想写的东西可以很自然地使用纯函数来表达, 就编写纯函数.

###递归

JavaScript 中, 递归会比循环慢很多.

#第三章 数据结构:对象与数组

##基本数据结构

###属性

有些 JavaScript 值有其他一些与其相关联的值, 这些相关联的值称为属性.
两种方式访问属性: 中括号或点标记法.

var text = "purple haze";
text["length"];
--> 11

text.length;
--> 11

点标记法是速记法, 只有属性名称是合法的变量名称时才能使用.

###对象值

大多数值类型, 例如字符串, 它们的属性是固定的, 不能增删属性.
但是, “对象”这个值类型不同, 它的属性可以自由添加删除和修改, 对象扮演的主要角色就是一个属性的集合.

例如, 像下面这样编写一个对象:

var cat = {coor:"gray", name: "Spot", size:46};
cat.size = 47;
cat.size;
--> 47

delete cat.size;
cat.size;
--> undefined

读取一个不存在的属性会得到一个 undefined 值, delete 用于删除属性.

如果用”=”操作符设置一个不存在的属性, 将会给对象添加一个新属性.

var empyt = {};
empty.notReally = 1000;
empyt;
--> {notReally: 1000}

属性名称如果不是一个合法的变量名称, 就不能用点标记法访问, 只能用中括号的形式访问. 另外, 创建对象时, 除了数字外, 属性名称都需要用引号引住.

var thing = {"gabba gabba": "hey", 5: 10};
thing["5"];
--> 10

thing[2+3];
--> 10

delete thing["gabba gabba"];

中括号内的部分可以为任意表达式, 中括号会将表达式转化为字符串来判断是否有该属性的名称, 所以也可以把变量名称作为属性名称.

var propertyName = "length";
var text = "coco";
text[propertyName];
--> 4

操作符”in”可以用来判断一个对象是否有某个属性, 它产生的是布尔值.

var chineseBox = {};
chineseBox.content = chineseBox;
"content" in chineseBox;
--> true

"content" in chineseBox.content;
--> true

###对象即集合

创建一个包含 Spot 的集合, 添加 White Fang 到集合里, 然后删除 Spot, 最后测试 Asoka 是否在集合里.

var set = {"Spot": true};
set["White Fang"] = true;
delete set["Spot"];
"Asoka" in set;
--> false

可以看出, 与上一节的属性操作非常一样, 只不过理解为集合操作.

###易变性

作为一个值类型, 对象值不能修改–不能去改变这些类型的县优质, 但是可以通过修改其属性进行更改.

###对象即集合: 数组

使用中括号创建([]).

数组长度: array.length

入栈: array.push(“aaa”)

出栈: array.pop()

取子集: array.slice(0, 6)

#第四章 错误处理

try{
}
catch(error){
}
finally{
}

#第五章 函数式编程

高阶函数

函数也是值, 下面的 action 参数就是一个函数.

function forEach(array, action){
  for(var i = 0; i< array.length; i++)
    action(array[i]);
}

forEach(["hello", "world", "test"], print)

还可以利用匿名函数, 编写 for循环之类的代码时可以省去很多无用的细节:

function sum(numbers){
  var total = 0;
  forEach(numbers, function(number){
    total += number;
  });
}

又如:

var paragraphs = mailArchive[mail].split("\n");
for(var i = 0; i < paragraphs.length; i++)
  handleParagraph(paragraphs[i]);

可以改写为:

forEach(mailArchive[mail].split("\n"), handleParagraph);

所以, 使用更为抽象(或高阶)的构造能够实现更多的信息而收到更少的干扰, forEach 在这里所起的作用是一个算法, 该算法实现一定的功能, 例如此处是”遍历数组”, 并将其抽象化. 程序中留有一定的”空白”, 这些空白用实现某个算法的函数经过参数传入来填充.

操作其他函数的函数称为高阶函数.

高阶函数可以用来概括很多正规函数难以描述的算法, 我们应该用一种更清晰的思路思考代码: 将算法分解成一些基本算法的组合, 使用名称来调用, 不需要一遍遍地输入代码, 避免产生大量凌乱的变量和循环.

修改函数

型修改传入的函数值, 下面的 negate 函数把传入的函数执行结果取反:

function negate(func){
  return function(x){
    return !func(x)
  };
}

var isNotNaN = negate(isNaN);
isNotNaN(NaN);
--> false

如果反转的函数参数多于一个, 那就使用 arguments 伪数组来访问传入的任意函数, 使用函数的 apply 方法来调用函数.

apply 接受两个参数, 第一个参数第六章再说, 第二个参数是函数必须使用的一个包含所有参数的数组.

function negate(func){
  return function(){
    return !func.apply(null, arguments);
  };
}

###归约函数

function reduce(combin, base, array){
  forEach(array, function(element){
    base = combine(base, element);
  });
  return base;
}

function add(a, b){
  return a + b;
}

function sum(numbers){
  return reduce(add, 0, numbers);
}

reduct重复调用一个函数, 将数组转化为一个单一的值.

因为”加”在 JavaScript 中是一个操作符而不是函数, 所以需要先编写一个 add函数.

###映射数组

function map(func, array){
  var result = [];
  forEach(array, function(element){
    result.push(func(element));
  });
  return result;
}

###操作符函数

var op = {
  "+": function(a, b) {return a + b;},
  "-": function(a, b) {return a - b;},
  "==": function(a, b) {return a == b;},
  "===": function(a, b) {return a === b;},
  "!": function(a){return !a;}
  /*......*/
};

使用:

reduct(op["+"], 0, [1,2,3,4,5])

#OOP

##对象

###定义方法

为对象添加方法: 附加一个函数即可.

var rabbit = {};
rabbit.speak = function(line){
  print("Says: ", line);
}

rabbit.speak("hello");

this: 当前对象

function speak(line){
  print("The ", this.adjective, " rabbit says: ", line);
}

var whiteRabbit = {adjective: "white", speak: speak};
var fatRabbit = {adjective: "fat", speak: speak};

whiteRabbit.speak("whitewhite");
fatRabbit.speak("fatfat");

apply函数的第一个参数就是 this, 用于代表函数所应用的对象.

###构造函数

new: 在调用函数时再前面加上 new, 那么它的 this 将指向另一个新的对象.
以 new 的方式创建的函数称为构造函数.

function Rabbit(adjective){
  this.adjective = adjective;
  this.speak = function(line){
    print("The ", this.adjective, " rabbit says: ", line);
  };
}

var killerRabbit = new Rabbit("killer");
killerRabbit.speak("GGGGG");

惯例: 构造函数的第一个字母大写, 与其他函数区分.

###从原型中创建

new的一些其他作用:

前面定义的 killerRabbit 有一个 constructor 属性, 指向它的 Rabbit 函数, 而不是用 new 创建的fatRabbit 也有这个属性, 但它指向 Object 函数.

constructor 属性是 rabbit 原型的一部分, 原型是 JavaScript 对象工作方式中的重要部分, 每个对象基于一个原型, 原型赋予一系列的基本特征. 最基本的原型是 Object, 输入{}就相当于输入 new Object().

###构造函数与原型

定义的每个函数都会自动获取一个 prototype 属性, 这个属性指向一个对象–该函数的原型.
该原型有一个 constructor 属性, 反过来指向它所属的函数.

查询一个属性时, JavaScript 首先查询该对象自身的所有属性, 如果有就找到; 如果没有, 继续查找该对象的原型, 然后查找原型的原型, 依次类推, 如果最后都没有找到, 返回 undefined.
设置属性时, 不是在原型上设置, 是在对象自身的属性上, 可以重载.

定义构造函数的一种新办法:

function Rabbit(adjective){
  this.adjective = adjective;
}
Rabbit.prototype.speak = function(line){
  print("The", this.adjective, " says: ", line);
}

###原型污染

###对象即词典

###指定接口

##原型继承

###类型定义工具
使用 new和 prototype 属性都是定义类型的特定方式, 但对于复杂需要大量继承的对象, 就笨了. 可以通过添加一些常用操作符来简化. 例如在对象上定义 inherit 和 method 方法:

Object.prototype.inherit = function(baseConstructor){
  this.prototype = clone(baseConstructor.prototype);
  this.prototype.constructor = this;
};

Object.prototype.method = function(name, func){
  this.prototype[name] = func;
};

可以这样使用这两个函数:

function StrangeArray(){}
StrangeArray.inherit(Array);
StrangeArray.method("push", function(value){
  Array.prototype.push.call(this, value);
  Array.prototype.push.call(this, value);
});

var strange = new StrangeArray();
strange.push(1);
--> [1,1]

网上还能找到更复杂灵活的JavaScript继承的代码.

###类型原型

原型本身是对象类型最重要的部分, 构造函数只是一个扩展, 一种特殊的方法.

###instanceof操作符

将对象放在操作符左边, 构造函数放在操作符右边, 将会返回一个布尔值.

[] instanceof Array;
--> true

###混合类型

多重继承: 从多个父类中派生一个对象类型.

#第七章 模块化

##模块

模块是任何函数和值的集合, 他们共同完成特定的工作.

###模块文件化

多个模块分别存放在多个 JavaScript 文件, HTML 中用 script 标签加载.

<script src="/js/utils.js" type = "text/javascript"></script>

##模块的形式

###使用函数作为局部命名空间

函数中 var 定义的变量是局部的.