Eloquent JavaScript 笔记 四:Objects and Arrays

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. 记录log
var 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. 用map

map本身也是一个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, atan

Math.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;
}

好长的一章,终于看完了。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值