1. 松鼠人
Jacques 有时会变成一只松鼠,他对此很苦恼。渐渐的,他发现了一些规律,如果白天接触了树木,晚上就会变成松鼠。但这不是全部规律,因为,即使他完全没有接触树木,有时也会变成松鼠。
为了找到变成松鼠的所有原因,Jacques决定每天记录日志,以期总结出更明确的规律。如何设计一个数据结构,来存储这种 “日志” 呢?
2. Array
var listOfNumbers = [2, 3, 5, 7, 11];
console.log(listOfNumbers[1]);
// → 3
console.log(listOfNumbers[1 - 1]);
// → 2
数组的定义:方括号,逗号分割。
访问数组元素:方括号,index 。index从0开始。
3. Properties
myString.length
Math.max
JavaScript中,除了null和undefined,所有的值都有properties。
访问property的方法有两种:
"hello".length
"hello"["length"]
第二种看起来有点奇怪,所以,通常我们用第一种方法。
第二种方法,是在方括号中放一个表达式(可以是任意表达式),该表达式的计算结果,就是array的property名称。例如:'hello'["len" + "gth"] 。
有两种情况,必须用方括号语法:
1. property的名字需要通过表达式来计算
2. property的名字中间有空格
array 中的元素本质上也是properties,和properties的存储/实现方法是一样的。
获取数组的长度:array.length
4. Methods
var doh = "Doh";
console.log(typeof doh.toUpperCase);
// → function
console.log(doh.toUpperCase());
// → DOH
Method 也是 Property,只是它的存储的数据是个function。
var mack = [];
mack.push("Mack");
mack.push("the", "Knife");
console.log(mack);
// → ["Mack", "the", "Knife"]
console.log(mack.join(" "));
// → Mack the Knife
console.log(mack.pop());
// → Knife
console.log(mack);
// → ["Mack", "the"]
array的一些methods:
push( )
pop( )
join( )
5. Objects
回到“松鼠人” Jacques 的日志问题。 一系列日志应该存储到一个数组中。 而一条日志是一个object,存储了当天的 “活动”, 以及这些活动的后果:是否变成松鼠。
var day1 = {
squirrel: false,
events: ["work", "touched tree", "pizza", "running",
"television"]
};
console.log(day1.squirrel);
// → false
console.log(day1.wolf);
// → undefined
day1.wolf = false;
console.log(day1.wolf);
// → false
创建Object
1. Object的内容包含在一组花括号中。 { ... }
2. 每个property是一组键值对,以冒号分割。 squirrel: false
3. 多个 property 之间以逗号分割。
4. properties 之间的空格、换行和缩进都不是必需的,它们的存在只是为了更清晰的格式,便于阅读。
5. property 的名字中可以包含空格,不必像变量名那么严格。如果有空格,需要用引号包起来,本质来讲,它是个字符串。
访问property
1. 当读取一个对象并不拥有的property时,程序不会出错,只是返回undefined 。2. 给对象的property赋值和给普通变量赋值的语法相同。如果赋值之前,该property不存在,则该语句会给这个对象创建一个新的property。
删除property
1. delete 操作符会彻底删除一个property,就像该对象从来没有拥有个这个property一样。
2. in 操作符用来判读一个对象是否拥有某个property
3. delete anObject.left;anObject.left = undefined;
这两行代码的区别是什么? 用 in 操作符检验一下。
var anObject = {left: 1, right: 2};
console.log(anObject.left);
// → 1
delete anObject.left;
console.log(anObject.left);
// → undefined
console.log("left" in anObject);
// → false
console.log("right" in anObject);
// → true
array 是object
typeof [1, 2];
// -> object
array 是一种特殊用途的object, 顺序存储一组数据,property的名字是数字。
日志的结构
var journal = [
{events: ["work", "touched tree", "pizza",
"running", "television"],
squirrel: false},
{events: ["work", "ice cream", "cauliflower",
"lasagna", "touched tree", "brushed teeth"],
squirrel: false},
{events: ["weekend", "cycling", "break",
"peanuts", "beer"],
squirrel: true},
/* and so on... */
];
6. Mutability
6.1. 变量的值是否可以修改
1. Boolean、Number、String类型的 “值” 不可修改,只能完整替换。
var s1 = "cat";
要想把其中的cat改成rat,不能只修改那一个字母c,而必须完整替换整个字符串:
s1 = "rat";
2. Object的值可修改(Array也是Object)
var cat = {name: "Tom", age: 10};
cat.name = "Jerry";
6.2. Object 是引用类型
1. 多个变量不能引用(指向)同一个Number、Boolean、String类型的值,当试图这么做时,会自动复制一份。
var s1 = "cat";
var s2 = s1;
s1 = "rat";
console.log(s2);
// -> cat
2. 多个变量可以引用(指向)同一个Object值。
var o1 = {value: 10};
var o2 = o1;
var o3 = {value: 10};
console.log(object1 == object2);
// → true
console.log(object1 == object3);
// → false
object1.value = 15;
console.log(object2.value);
// → 15
console.log(object3.value);
// → 10
3. 判断两个Object类型变量的相等:必须指向相同的Object,两个变量才相等。如果指向不同的变量,即使所有的property都相等,也不认为两个变量相等。
7. The Log
7.1. 记录logvar journal = [ ];
function addEntry(events, didITurnIntoASquirrel) {
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
}
addEntry(["work", "touched tree", "pizza", "running", "television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna", "touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts", "beer"], true);
7.2. Correlation
对于变量之间依赖程度的度量。
它的值介于 -1 到 1之间:
0表示无关联;
1表示二者完全相关,一个为true,另一个必然也为true。 -1表示反相关,一个为true,另一个必然为false。7.3. 计算公式
两个Boolean之间的相关性。我们用0来代表false,用1来代表true。得到下图的组合。其中的数字表示这种情况在log中出现的次数。(76, 9, 4, 1)只是随便写的几个数字,以便于下面更清楚的解释公式。
n1. 代表所有squirrel是1的区间之和,即 n10 + n11 = 4 + 1
n1. 代表所有squirrel是0的区间之和,即 n01 + n00 = 9 + 76
n.1 代表所有pizza是1的区间之和,即 n01 + n11 = 9 + 1
n.0 代表所有pizza是0的区间之和,即 n10 + n00 = 4 + 76
分子: 1*76 - 4*9 = 40
分母: 5 * 85 * 10 * 80 的平方根
结果: ϕ ≈ 0.069
8. Computing Correlation
上图是个2x2的表,表中的四个数据如何在程序中存储呢?
考虑用数组,看n的角标,00、01、10、11, 如果这四个数是二进制表示的话,恰好等于十进制的0,1,2,3. 那么我们可以把它们存储在一维数组中,角标等于数组的index。
var table = [76, 9, 4, 1];
用代码实现上面的公式
function phi(table) {
return (table[3] * table[0] - table[2] * table[1]) /
Math.sqrt((table[2] + table[3]) *
(table[0] + table[1]) *
(table[1] + table[3]) *
(table[0] + table[2]));
}
console.log(phi([76, 9, 4, 1]));
// → 0.068599434
从日志生成table
随书代码中,提供了一个jacques_journal.js文件,该文件中定义了一个 var JOURNAL 数组。该数组中保存了三个月的日志。针对某一个事件,例如:pizza,我们需要通过这个数组计算出上一节的table。
在一条记录中查找pizza事件
var day1 = {"events":["pizza","brushed teeth","computer","work","touched tree"],"squirrel":false};
function hasEvent(event, entry) {
return entry.events.indexOf(event) != -1;
}
console.log(hasEvent(day1, "pizza"));
// -> true
遍历JOURNAL,生成table
function tableFor(event, journal) {
var table = [0, 0, 0, 0];
for (var i = 0; i < journal.length; i++) {
var entry = journal[i], index = 0;
if (hasEvent(event, entry)) index += 1;
if (entry.squirrel) index += 2;
table[index] += 1;
}
return table;
}
console.log(tableFor("pizza", JOURNAL));
// → [76, 9, 4, 1]
9. Objects as Maps
JOURNAL日志中记录了很多事件,例如: pizza, touched tree, burshed teeth, computer, work 等等,我们可以用上一节的方法计算出每个 ”事件“ 和 “变成松鼠” 的相关度。 那么,如何存储这一堆的事件和相关度呢?
9.1. 可以用数组
var correlations = [{name: "pizza", value: 0.069}, {name: "touched tree", value: -0.081}];
缺点是,查找和遍历比较麻烦。
9.2. 用mapmap本身也是一个object,用事件名称作为property名字,用相关度作为property的值。
var map = {"pizza": 0.069, "touched tree": -.0.081};
这种方式,查找和遍历很方便。
var map = {};
function storePhi(event, phi) {
map[event] = phi;
}
storePhi("pizza", 0.069);
storePhi("touched tree", -0.081);
console.log("pizza" in map);
// → true
console.log(map["touched tree"]);
// → -0.081
for (var event in map) {
console.log("The correlation for '" + event + "' is " + map[event]);
}
// → The correlation for 'pizza' is 0.069
// → The correlation for 'touched tree' is -0.081
10. The Final Analysis
计算JOURNAL中所有事件和 “变松鼠” 的相关度。
function gatherCorrelations(journal) {
var phis = {};
for (var entry = 0; entry < journal.length; entry++) {
var events = journal[entry].events;
for (var i = 0; i < events.length; i++) {
var event = events[i];
if (!(event in phis)) {
phis[event] = 0;
}
}
}
for (var e in phis) {
phis[e] = phi(tableFor(e, journal));
}
return phis;
}
var correlations = gatherCorrelations(JOURNAL);
console.log(correlations.pizza);
// → 0.068599434
列出所有事件的相关度
for (var event in correlations)
console.log(event + ": " + correlations[event]);
忽略相关度小于0.1的事件
for (var event in correlations) {
var correlation = correlations[event];
if (correlation > 0.1 || correlation < -0.1)
console.log(event + ": " + correlation);
}
// → weekend: 0.1371988681
// → brushed teeth: -0.3805211953
// → candy: 0.1296407447
// → work: -0.1371988681
// → spaghetti: 0.2425356250
// → reading: 0.1106828054
// → peanuts: 0.5902679812
可以看到,相关度最大的两个事件是:peanuts, brushed teeth
1. peanuts 是正数,对 “变松鼠” 是正的影响。
2. brushed teeth 是负数,对 “变松鼠” 是负的影响,或者说,“不刷牙” 是导致 “变松鼠” 的一个重要原因。3. 把这两个事件合并成一个事件: "peanut teeth" (吃坚果不刷牙) , 计算一下这个新的事件与 “变松鼠” 的相关度。
for (var i = 0; i < JOURNAL.length; i++) {
var entry = JOURNAL[i];
if (hasEvent("peanuts", entry) && !hasEvent("brushed teeth", entry)) {
entry.events.push("peanut teeth");
}
}
console.log(phi(tableFor("peanut teeth", JOURNAL)));
// → 1
4.
相关度是1,表示只要 “吃坚果不刷牙” ,就一定会 “变松鼠” 。5. 至此,“松鼠人” 问题就分析完了。
11. Further Arrayology
创建一个todoList (先进先出)
var todoList = [];
function rememberTo(task) {
todoList.push(task);
}
function whatIsNext() {
return todoList.shift();
}
function urgentlyRememberTo(task) {
todoList.unshift(task);
}
这里用到了三个函数:
1. push, 在数组尾部追加
2. shift,返回头部第一个元素,并从数组中删除。
3. unshift,在头部追加
执行过程:
1. 有新的任务,调用rememberTo( ),在队列尾部追加一项。
2. 有新的任务,如果是紧急任务,调用urgentlyRememberTo( ),把该任务追加到队列头部。
3. 调用 whatIsNext( ) ,从任务队列中获取一项任务,开始执行。
查询
indexOf( )
console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
lastIndexOf( )
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3
分片
提取数组的一部分,创建一个新的数组
console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]
连接
[1,2].concat([3,4]);
// -> [1,2,3,4]
12. Strings and Their Properties
不能给Number、String、Boolean添加新的property
var myString = "Fido";
myString.myProperty = "value";
console.log(myString.myProperty);
// → undefined
String的内置properties
console.log("coconuts".slice(4, 7));
// → nut
console.log("coconut".indexOf("u"));
// → 5
console.log("one two three".indexOf("ee"));
// → 11
console.log(" okay \n ".trim());
// → okay
var string = "abc";
console.log(string.length);
// → 3
console.log(string.charAt(0));
// → a
console.log(string[1]);
// → b
13. The Arguments Object
函数调用时,传入的参数个数不必和函数的参数列表相同
function noArguments() {}
noArguments(1, 2, 3); // This is okay
function threeArguments(a, b, c) {}
threeArguments(); // And so is this
函数调用时,系统会自动创建一个变量 —— arguments,它是个数组,存储了所有传入的参数。
“松鼠人“,向journal中添加一条记录,注意其中的arguments 变量。
function addEntry(squirrel) {
var entry = {events: [], squirrel: squirrel};
for (var i = 1; i < arguments.length; i++) {
entry.events.push(arguments[i]);
}
journal.push(entry);
}
addEntry(true, "work", "touched tree", "pizza", "running", "television");
14. The Math Object
Math 是一个Object,这个Object的主要作用不是存储数据,而是提供一组数学运算方法,例如:min, max, sqrt等。为什么要把这些方法写到一个Object中呢,直接提供这些函数不就可以吗?本质上来讲,Math这个Object是提供了一个namespace,如果没有它,所有的数学函数都变成的全局变量。如果有那么多全局变量的话,如何避免它们与我们自己定义的变量发生冲突呢?
Math中的一些方法:
三角函数: cos, sin, tan, acos, asin, atanMath.PI
随机数:random
上取整:ceil
四舍五入:round
function randomPointOnCircle(radius) {
var angle = Math.random() * 2 * Math.PI;
return {x: radius * Math.cos(angle),
y: radius * Math.sin(angle)};
}
console.log(randomPointOnCircle(2));
// → {x: 0.3667, y: 1.966}
15. The Global Object
所有的全局变量会存储在一个对象中,在浏览器环境下,这个对象叫window。
var myVar = 10;
console.log("myVar" in window);
// → true
console.log(window.myVar);
// → 10
16. Exercise: The Sum Of a Range
function myRange(start, end) {
var ret = [];
for(var i=start; i<=end; i++) {
ret.push(i);
}
return ret;
}
function mySum(arr) {
var ret = 0;
for(var i=0; i<arr.length; i++) {
ret += arr[i];
}
return ret;
}
function myRange(start, end, step) {
step = step == undefined ? 1 : step;
var ret = [];
if (step > 0) {
for(var i=start; i<=end; i+=step) {
ret.push(i);
}
}
else {
for(var i=start; i>=end; i+=step) {
ret.push(i);
}
}
return ret;
}
17. Exercise: Reversing an Array
function reverseArray(arr) {
var ret = [];
for (var i=arr.length-1; i>=0; i--) {
ret.push(arr[i]);
}
return ret;
}
function reverseArrayInPlace(arr) {
var len = arr.length;
for (var i=0; i<len/2; i++) {
var temp = arr[i];
arr[i] = arr[len-i];
arr[len-i] = temp;
}
}
第一种方式是 “纯函数” 。
18. Exercise: A List
function arrayToList(arr) {
var head;
var tail;
for (var i=0; i<arr.length; i++) {
var list = {value: arr[i]};
if (i == 0) {
head = list;
}
else {
tail.list = list;
}
tail = list;
}
return head;
}
function listToArray(list) {
var ret = [];
while(list != undefined) {
ret.push(list.value);
list = list.list;
}
return ret;
}
function prepend(v, list) {
return {value: v, list: list};
}
function nth(list, index) {
var ret;
var cur = list;
for (var i=1; i<=index && cur != undefined; i++) {
cur = cur.list;
}
return cur.value;
}
function nthr(list, index) {
if (index == 0) {
return list.value;
}
else if (list.list == undefined) {
return undefined;
}
else {
return nthr(list, index - 1);
}
}
19.
Exercise: Deep Comparison
function deepEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
else if (typeof obj1 == "object" && typeof obj2 == "object") {
if (Object.keys(obj1).length == Object.keys(obj2).length) {
for (var akey in obj1) {
if (!deepEqual(obj1[akey], obj2[akey])) {
return false;
}
}
return true;
}
else {
return false;
}
}
else
return false;
}
好长的一章,终于看完了。