three cores of data visualization:

  • analysis
  • design
  • construction

推荐书籍《visualization analysis & design》

使用https://vizhub.com/进行编程学习,这个网站好像是Curran Kelleher自己创建的一个教学网站。

JS知识点

该部分的学习除了上述课程以外,还参照了廖雪峰的JavaScript教程。

  • JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到<head>中。由<script>...</script>包含的代码就是JavaScript代码,它将直接被浏览器执行。或者代码放在一个单独的.js文件中,然后在HTML中通过<script src="..."></script>引入这个文件
  • 如果在一个页面中引入多个文件,并且在<script></script>中间还有js代码,浏览器将将会按照顺序依次执行。
  • JavaScript的设计者希望用null表示一个空的值,而undefined表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用nullundefined仅仅在判断函数参数是否传递的情况下有用。
  • 变量名是大小写英文、数字、$_的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如ifwhile等。申明一个变量用varletconst语句,尽量使用const
  • var声明的变量的作用域是函数。内部函数可以访问外部函数定义的变量,并且会覆盖重名的变量。函数在定义的时候会扫描整个函数体的语句,把所有var变量的声明提升到函数顶部。因此为了防止出现混乱,应该在函数顶部使用var定义。更好的做法是使用let定义变量,这样的变量有块级作用域。constlet都有块级作用域,但是const无法改变
  • 如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量。在strict模式下这种声明方法是不允许的。启用strict模式的方法是在代码的第一行写上'use strict';
  • 在``包围的字符串中可以使用$加变量名来获得变量的值并转换成字符串,并且可以得到多行字符串。字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
  • 可以通过()=>{}的方式声明函数,不需要关键字function
  • 数组的长度保存在length属性中。如果修改length的值改变数组的大小。如果通过索引赋值,索引超过了范围,同样会引起数组大小的变化
  • 数组可以通过数组的forEach方法遍历数组
array.forEach(item => {
	//对item的操作
});

这种方法的好处是可以可以把里面的函数变成其他的,灵活的进行遍历

  • 数组使用map方法可以得到另一个数组,每个数组的值是函数处理过的值
let func = item => {
	//对item的操作
};
let tmp = array.map(item);
  • 数组可使用filter方法过滤元素,需要传入的函数返回布尔值
let tmp = array.filter(item => {})
  • 数组的sort()方法默认将数字转化为字符串,返回仍然是当前字符串。可以通过传入一个函数,分别返回-1,0,1来确定排序顺序。
  • 数组使用join方法可以将数组元素看成字符串拼接起来
  • 使用解构赋值可以使得代码更加简洁。如果是数组的话使用[],如果是对象的话使用{}
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

//更换属性名
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

//添加默认值
var person = {
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

//有时候变量已经声明,则正确的写法也会报错
// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =
//正确写法
({x, y} = { name: '小明', x: 100, y: 200});
  • JSON.stringify(array, null, 2); 将数组转化为字符串,第二个参数一般用不上,第三个参数是添加换行的,否则挤在一起
  • JSON.parse(str)可以将上面的字符串再转换成数组
  • 对象最后一个键值对不要加逗号,否则有的浏览器会报错
  • 访问属性是通过.操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''括起来
  • 检测对象是否拥有某一属性可以用in操作符。有可能这个属性是继承得到的。为了判断一个对象是否是对象本身拥有的,可以用hasOwmProperty()方法。
var a =  ['A', 'B', 'C'];
for (let i in a){
	console.log(i);	//'0' '1' '2'
	console.log(a[i]); //'A' 'B' 'C'
}
var xiaohong = {
    name: '小红',
    'middle-school': 'No.1 Middle School'
};

例如上面的属性名middle-school就不是一个有效的变量名,只能用xiaohong['middle-school']的方法进行访问。

  • for ... in ...可以把一个对象的所有属性名依次循环出来,可以用hasOwnProperty()过滤对象继承的属性,数组也是对象,不过属性名为数组的下标。需要注意的是得到的是属性名类型为String类型
  • JavaScript中对象的键值是字符串类型的。如果想要让键值为其他类型的,可以使用Map。可以用二维数组对Map进行初始化,常用的方法有set get has delete
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95

m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
  • SetMap相似,但是不存储value。
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
s.add(4);
s; // Set {1, 2, 3, "3", 4}
s.add(4);
s; // 仍然是Set {1, 2, 3, "3", 4}

s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
  • 为了解决MapSet不能使用下标访问的问题,我们可以使用for ... of ...循环来遍历,只循环集合本身的元素,后添加的元素不会循环
  • 同样的我们可以使用forEach方法进行循环。不过需要注意使用方法
    第三个参数是调用者本身
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
    console.log(element);
});

var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
    console.log(value);
});

var a = ['A', 'B', 'C'];
a.forEach(function (element) {
    console.log(element);
});
  • 函数如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined
  • 函数有多种定义方法
function abs(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

var abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};//不要忘记分号

var abs = x => x>0 ? x : -x;
  • 由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。为了避免参数不存在,可以使用typeof运算符对参数进行检查。
function abs(x) {
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}
  • 特殊参数使用:arguments是调用者传入的所有参数的数组。...rest参数可以获得所有额外的参数。如果传入的参数连正常定义的参数都没有填满rest参数会接受一个空数组(不是undefined)。
  • 需要注意return后面不要随意换行,JS会自动加分号,可能导致错误。
  • 方法的this指针始终指向调用者,如果直接调用函数一般this指向window。在strict模式下,直接调用函数的this指针指向undefined。只有对象直接调用函数的this指针是有效的,内层嵌套的函数的this指针是无效的,解决方法是一个临时变量将this保存起来。
  • 对于上面的问题,还可以使用applycall方法解决。apply还能动态改变函数的行为
  • 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9
  • 在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器
'use strict';

function create_counter(initial) {
    var x = initial || 0;	//initial如果没有定义为假
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
  • 闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2和pow3:
'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343
  • 如果使用箭头函数要返回一个对象,而且对象只有一个键值对的时候要把对象用括号括起来
  • 箭头函数和匿名函数的区别:箭头函数内部的this总是指向词法作用域,也就是外层调用者。由于已经绑定,所以无法使用call或者apply()this进行绑定,传入的第一个参数被忽略。
  • setTimeOut(()=>{} , x)可以用来睡眠一样的操作,前面传入的是一个函数,后面是睡眠的秒数,单位是毫秒,睡眠xms后运行前面的函数
let waitSeconds = numSeconds => new Promise(resolve =>{
    const message = `${numSeconds} seconds have passed`;
    setTimeout(()=>{resolve(message)}, numSeconds*1000);
});
waitSeconds(2).then(message => console.log(message));

模仿视频在vizhub上的实现的代码:https://vizhub.com/Edward-Elric233/97bd1319ee774022babd69cd4cca220e

不过需要注意一点的是在这个网页上的import文件需要在文件名前面加上./,Curran老师在Youtube上说明了自己不用加也可以成功运行的原因是Bug。