学习《JavaScript 数据结构与算法》读书笔记和勘误?

第1章 JavaScript简介

1.3 JavaScript基础

1.3.1 变量

变量作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var myVariable = 'global';
myOtherVariable = 'global';

function myFunction() {
var myVariable = 'local';
return myVariable;
}

function myOtherFunction() {
myOtherVariable = 'local';
return myOtherVariable;
}

console.log(myVariable); /* 'global' */
console.log(myFunction()); /* 'local' */
console.log(myVariable); /* 'global' */

console.log(myOtherVariable); /* 'global' */
console.log(myOtherFunction()); /* 'local' */
console.log(myOtherVariable); /* 'local' */

1.3.2 操作符

1
2
3
4
5
6
console.log('5 & 1 : ', (5 & 1)); /* 5 & 1 :  1 */
console.log('5 | 1 : ', (5 | 1)); /* 5 | 1 : 5 */
console.log('~5 : ', (~5)); /* ~5 : -6 */
console.log('5 ^ 1 : ', (5 ^ 1)); /* 5 ^ 1 : 4 */
console.log('5 << 1 : ', (5 << 1)); /* 5 << 1 : 10 */
console.log('5 << 1 : ', (5 << 1)); /* 5 << 1 : 10 */
位操作符 描述
&
` `
~
^ 异或
<< 左移
>> 右移

1.3.4 相等运算符

不同类型的值用相等操作符比较后的结果

类型(x) 类型(y) 结果
null undefined true
undefined null true
数字 字符串 x == toNumber(y)
字符串 数字 toNumber(x) == y
布尔值 任何类型 toNumber(x) == y
任何类型 布尔值 x == toNumber(y)
字符串或数字 对象 x == toPrimitive(y)
对象 字符串或数字 toPrimitive(x) == y

如果 x 和 y 是相同类型,JavaScript 会比较它们的值或对象值。其他没有列在这个表格中的情况都会返回false。

toNumber 和 toPrimitive 方法是内部的,并根据以下表格对其进行估值

toNumber 方法对不同类型返回的结果如下:

值类型 结果
undefined NaN
null +0
布尔值 如果是 true ,返回1;如果是 false ,返回+0
数字 数字对应的值
字符串 将字符串解析成数字。如果字符串中包含字母,返回NaN;如果是由数字字符组成的,转换为数字
对象 Number(toPrimitive(vale)) toNumber(toPrimitive(value))

toPrimitive方法对不同类型返回的结果如下:

值对象 结果
对象 如果对象的valueOf方法的结果是原始值,返回原始值。如果对象的toString方法返回原始值,就返回这个值;其他情况都返回一个错误

1.6 JavaScript 面向对象编程

面向对象编程(OOP)中,对象是类的实例。一个类定义了对象的特征。我们会创建很多类来表示算法和数据结构。例如我们声明了一个类来表示书:

1
2
3
4
5
function Book(title, pages, isbn) {
this.title = title;
this.pages = pages;
this.isbn = isbn;
}

用下面的代码实例化这个类

1
var book = new Book('title', 'pag', 'isbn')

然后,我们可以访问和修改对象的属性:

1
2
3
console.log(book.title);
book.title = 'new title';
console.log(book.title);

类可以包含函数。可以声明和使用函数,如下所示:

1
2
3
4
Book.prototype.printTitle = function() {
console.log(this.title);
}
book.printTitle();

也可以直接在类的定义里声明函数

1
2
3
4
5
6
7
8
9
function Book(title, pages, isbn) {
this.title = title;
this.pages = pages;
this.isbn = isbn;
this.printIsbn = function() {
console.log(this.isbn);
}
}
book.printIsbn();

在原型的例子里,printTitle 函数只会创建一次,在所有实例中共享。如果在类的定义里声明,就像前面的例子一样,则每个实例都会创建自己的函数副本。使用原型对下方法可以节约内存和降低实例化开销。不过原型方法只能声明公共函数和属性,而类定义可以声明只在类的内部访问的私有函数和属性。

1.9 ECMAScript 6 的功能

1.9.6 声明展开和剩余参数

在 ES5 中,我们可以用 apply() 函数把数组转化为参数。为此, ES6 有了展开操作符 ... 。举例来说,考虑我们上一节声明的 sum 函数。可以执行如下代码来传入参数 x, y, z 。

1
2
3
function sum(x = 1, y = 2, z = 3) {
return x + y + z;
}
1
2
var params = [3, 4, 5];
console.log(sum(...params));

以上代码和下面的 ES5 代码的效果是相同的:

1
2
var params = [3, 4, 5];
console.log(sum.apply(undefined, params));

在函数中,展开操作符也可以代替 arguments ,当作剩余参数使用。考虑如下例子:

1
2
3
4
function restParamaterFunction(x, y, ...a) {
return (x + y) * a.length;
}
console.log(restParamaterFunction(1, 2, 'hello', true, 7)); // 输出9

以上代码和下面代码的效果是相同的:

1
2
3
4
function restParamaterFunction(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
return (x + y) * a.length;
}

增强的对象属性

ES6引入了数组解构的概念,可以用来一次初始化多个变量

1
var [x, y] = ['a', 'b'];

以上代码和下面代码的效果是相同的:

1
2
var x = 'a';
var y = 'b';

数组解构也可以用来进行值的互换,而不需要创建临时变量,如下:

1
[x, y] = [y, x];

以上代码和下面代码的效果是相同的:

1
2
3
var temp = x;
x = y;
y = temp;

还有一个属性简写的功能,它是对象结构的另一种方式

1
2
3
var [x, y] = ['a', 'b'];
var obj = {x, y};
console.log(obj); // {x: 'a', y: 'b'}

以上代码和下面代码的效果是相同的:

1
2
3
4
var x = 'a';
var y = 'b';
var obj2 = {x: x, y: y};
console.log(obj2); // {x: 'a', y: 'b'}

本节我们要讨论的最后一个功能是方法属性。这使得开发着可以在对象中声明看起来像是属性的函数。下面是一个例子:

1
2
3
4
5
6
7
var hello = {
name: 'abcdef',
printHello() {
console.log('Hello');
}
};
console.log(hello.printHello());

以上代码也可以写成下面这样:

1
2
3
4
5
6
var hello = {
name: 'abcdef',
printHello: function printHello() {
console.log('Hello');
}
};

1.9.6 使用类进行面向对象编程

ES6还引入了一种更简洁的声明类的方式
之前:

1
2
3
4
5
6
7
8
function Book(title, pages, isbn) {
this.title = title;
this.pages = pages;
this.isbn = isbn;
}
Book.prototype.printTitle = function() {
console.log(this.title);
}

可用ES6简化为

1
2
3
4
5
6
7
8
9
10
class Book {
constructor (title, pages, isbn) {
this.title = title;
this.pages = pages;
this.isbn = isbn;
}
printIsbn() {
console.log(this.isbn);
}
}

两段代码具有相同的效果和输出

1
2
3
4
let book = new Book('title', 'pages', 'isbn');
console.log(book.title); /* 输出图书标题 */
book.title = 'new title'; /* 更新图书标题 */
console.log(book.title); /* 输出图书标题 */

继承

除了新的声明类的方式,类的继承也有了简化的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ITBook extends Book {
constructor(title, pages, isbn, technology) {
super(title. pages, isbn);
this.technology = technology;
}
printTechnology() {
console.log(this.technology);
}
}

let jsBook = new ITBook('学习JS算法', '200', '1234567890', 'JavaScript');
console.log(jsBook.title);
console.log(jsBook.printTechnology());

我们可以用 extends 关键字扩展一个类并继承它的行为。在构造函数中,我们也可以通过 super 关键字引用父类的构造函数。

使用属性存取器

使用新的类语法也可以为属性创建存取器函数。虽然不像其他面向对象语言(封装概念),类的属性不是私有的,但最好还是遵循一种命名模式。
下面的例子是一个声明了 getset 函数的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
}

let lotrChar = new Person('Frodo');
console.log(lotrChar.name); /* 执行 get 函数 */
lotrChar.name = 'Gandalf'; /* 执行 set 函数 */
console.log(lotrChar.name);
lotrChar._name = 'Sam'; /* _name 并非真正的私有属性,我们仍然可以引用它 */
console.log(lotrChar.name);

要声明 getset 函数,只需要在我们要暴露和函数名前面加上 getset 关键字。我们可以用相同的名字声明类属性,或者在属性名前面加下划线,让这个属性看起来像是私有的。
然后,只要像普通属性一样,引用它们的名字,就可以执行 getset 函数。

其他功能

列表迭代器、类型数组、 SetMapWeakSetWeakMap 、模块、尾调用、 Symbol 等等
查看ES6全部功能和规范