#第一章 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 定义的变量是局部的.