javascript 高级程序设计 第四版 学习笔记 1

javascript的简短历史回顾

  • 1995年Netscape公司为了解决前端业务逻辑验证必须与服务器通信而导致用户等待的问题,开发出了javascript这种脚本语言。
  • 1998年ISO和IEC将ECMAScript标准作为正式的脚本语言标准,1.0。
  • ECMAScript 3.0标志着Javascript作为一门真正的编程语言的时代到来。
  • 2009年 ECMAScript 5(ECMAScript 3.1)发布,新功能包括原生的解析和序列化 JSON 数据的 JSON 对象、方便继承和高级属性定义的方法,以及新的增强 ECMAScript 引擎解释和执行代码能力的严格模式。
  • 2015年发布 ES6,包含了大概这个规范有史以来最重要的一批增强特性。ES6 正式支持了类、模块、迭代器、生成器、箭头函数、期约、反射、代理和众多新的数据类型。
  • 2016年ES7发布,这次修订只包含少量语法层面的增强,如 Array.prototype.includes 和指数操作符。
  • 2017年ES8发布,主要增加了异步函数(async/ await)、SharedArrayBuffer 及 Atomics API,以及 Object.values()/Object.entries()/Object.getOwnPropertyDescriptors()和字符串填充方法,另外明确支持对象字面量最后的逗号。
  • 2018年ES9发布,修订包括异步迭代、剩余和扩展属性、一组新的正则表达式特性、Promise finally(),以及模板字面量修订。
  • 2019年ES10发布,增加了 Array.prototype. flat()/flatMap()、String.prototype.trimStart()/trimEnd()、Object.fromEntries()方法,以及 Symbol.prototype.description 属性,明确定义了 Function.prototype.toString()的返回值并固定了 Array.prototype.sort()的顺序。另外,这次修订解决了与 JSON 字符串兼容的问题,并定义了 catch 子句的可选绑定。

在HTML中使用script

  • “,否则会报错。
  • 外部代码放在src属性中,且外部文件的优先级高于行内代码。
  • 对于不支持javascript的浏览器,使用

基础语法

  • 严格区分大小写
  • 标识符以 $,_或字母开头,不能是关键字
  • 单行注释// 多行注释 / /
  • ES5新增了严格模式(restrict mode)
  • 语句以;结尾

关键字与保留字

  • break
  • do
  • in
  • typeof
  • case
  • else
  • instanceof
  • var
  • catch
  • export
  • new
  • void
  • class
  • extends
  • return
  • while
  • const
  • finally
  • super
  • with
  • continue
  • for
  • switch
  • yield
  • debugger
  • function
  • this
  • default
  • if
  • throw
  • delete
  • import
  • try

var、let和const

var

var是ES6之前用来定义变量的关键字,var的问题有两个,一是使用var声明的变量会成为包含它的函数的局部变量,二是使用var定义的变量存在声明提升的问题。

1
2
3
4
5
function test() {
var message = "Hi ,XX";
}
test();
console.log(message);

变量message声明在函数test内部,因此是test的内的局部变量,后面的cosole自然就访问不到该变量,因此输出的结果为ReferenceError: message is not defined

1
2
3
4
5
function test() {
message = "Hi ,XX";
}
test();
console.log(message);

声明时,去掉var关键字,该变量会成为全局变量,最后输出的结果是: Hi, XX

1
2
3
4
5
function test() {
console.log(message);
var message = "Hi ,XX";
}
test();

var变量存在的另外一个问题就是声明提升,这里的例子中,message虽然是在console.log后面声明的,但是会被提升到代码块的最顶部,因此,这里输出的结果是undefined. 相当于下面的代码:

1
2
3
4
5
6
function test() {
var message;
console.log(message);
var message = "Hi ,XX";
}
test();

另外,var声明的变量是可以重复声明的,即下面的代码是合法的:

1
2
3
4
5
6

var message = "Hi, XX";
var message = "Hi, HX";
var message = "Hi, QD";

conosle.log(message);

let

let和const是ES6中引入的,与var不同的是let的作用域仅限于当前代码块,且不存在声明提升的现象,也不允许重复声明。

1
2
3
4
if (true){
let message = "Hi ,XX";
}
console.log(message);

因为message使用let声明,其作用域仅限于if代码块中,因此后面的console代码获取不到message的值,因此输出的结果也就是ReferenceError: message is not defined

1
2
3
4
5
if (true){
let message = "Hi ,XX";
let message = "hi, hx";
}
console.log(message);

let不支持重复声明,重复的声明会报错: SyntaxError: Identifier 'message' has already been declared

1
2
3
4
if (true){
console.log(message);
let message = "hi, hx";
}

let不存在声明提升,因此这里的代码会报错: ReferenceError: message is not defined

另外let与var的不同是,使用let声明的全局变量不会成为widnow对象的属性,而var声明的会是window的属性。

const

const的行为逻辑与let一致,唯一不同的是const在声明时必须给变量赋值,且赋值的变量不允许再改变。需要注意的是,虽然这里说变量不允许再改变的意思是变量的地址不允许再改变,如果const的是一个对象,那么对象内的属性不在这个限制内。

声明的风格与最佳实践

  • const优先,let次之
  • 不使用var

数据类型

ES6定义的6中简单数据类型

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol

一个复杂的类型

  • object

typeof 操作符

typeof操作符用来判断变量的类型,其返回值有如下几种:

  • undefined: 变量未定义
  • boolean: 布尔值
  • string: 字符
  • number: 数值
  • object: 对象
  • function: 函数
  • symbol:符号

Undefined

Undefined只有一个值: undefined,表示未定义。

1
2
var x;
let y;

不需要显式地声明undefined, 包含undefined的变量和未声明的变量是有区别的。

1
2
3
4
let x;
// y
console.log(x);
console.log(y);

x的值是undefined, 而y这句会报错。

Null

Null类型同样只有一个值: null。逻辑上标识一个空指针对象,可以显示地给一个对象赋值null.

null和undefined表面值相等,也就是

1
cosonle.lo(null == undefined) //true.

Boolean

Boolean类型只有两个值true,false。

Number类型

ES使用IEEE754数值格式,因此存在浮点数计算问题:

1
2
3
let a = 0.1
let b = 0.2
console.log(a + b == 0.3) //false

因为a+b的结果是0.300000000000004而非0.3

严格模式下 8进制无效。

ES可以表示的最小值存在于Number.MIN_VALUE中,最大值Number.MAX_VALUE。

如果超过可以标识的大小,则用Infinity和-Infinity表示。

NaN

NaN表示不是一个数值,例如 0/0

NaN不包含NaN在内的任何值,也就是说 NaN == NaN 始终等于false,因此ES提供了isNaN函数用来判断是否是NaN。

数值转换

Number()、parseInt() 和 parseFloat()函数。

字符类型 String

  • 字符的表示可以使用单引号(‘)、双引号(“)和反引号(`)。
  • 字符串是不可变量,因此任何操作都是先销毁后生成一个新的字符串对象。

字符串转换

几乎所有的值都有toString()方法,因此我们可以直接使用toString方法将值转换为字符串。

1
2
3
4
let x = 11;
console.log(x.toString())
console.log(1.2.toString())
consolel.log(true.toString())

这里需要注意10.toString()会报:SyntaxError: Invalid or unexpected token, 因为编译器无法断定10.应该解释为10.0还是 10 。 处理方法可以在10的后边加个空格:

1
console.log(10 .toString()) // 10

模板字面量

ES6引入了模板字面量,使用反引号保持内部字符串的格式,在定义模板时特别有用。

1
2
3
4
5
6
let text1 = "first line\n second line";
let text2 = `first line
second line`

console.log(text1);
console.log(text2);

字符串插值

字符串插值意思可以在模板字面量中使用${}将变量的值定义进去,举个例子,在之前我们拼接字符串最常使用的方法是:

1
2
3
4
5
6
7
let value = 5
let result = value + ' to the second power is ' + value * value

let result2 = `${value} to the second power is ${value * value}`

console.log(result);
console.log(result2)

两个输出的结果都是一样的,但是字符串插值看起来更容易理解和使用。

模板字面量标签函数

模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let a = 1;
let b = 2;

function testTag(strings, ...expressions) {
console.log(strings);
console.log(avalue);
console.log(bvalue);
console.log(sumvalue)

return "test";
}

let testResult = testTag`${a} + ${b} = ${a + b}`;

console.log(testResult)
------------------
[ '', ' + ', ' = ', '' ]
1
2
3
test

因为表达式参数的数量是可变的,所以通常应该使用剩余操作符(rest operator)将它们收集到一个数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = 1;
let b = 2;

function testTag(strings, ...expressions) {
console.log(strings);
for(const express of expressions){
console.log(express);
}

return "test";
}

let testResult = testTag`${a} + ${b} = ${a + b}`;

console.log(testResult)

其结果与之前的分开写的结果一致。

原始字符串

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。

1
2
3
4
5
console.log('\u00A9');
console.log(String.raw`\u00A9`);
-------------
©
\u00A9

另外,也可以通过标签函数的第一个参数,即字符串数组的.raw 属性取得每个字符串的原始内容。

符号类型

符号类型是ES6新增的类型,其实例是唯一的不可变的,作用是确保作为对象属性时,该属性是唯一的,不会跟其他属性发生冲突。不同于私有属性,符号属性是非字符串的唯一的对象属性。

创建符号的方式:

1
2
3
4
5
let s = Symbol();

console.log(typeof s);
---------
symbol

符号可以传入一个字符串作为描述,但这跟符号对象本身并无关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
let s1 = Symbol(),
s2 = Symbol();

console.log(s1 == s2);

let s3 = Symbol("a1"),
s4 = Symbol("a1");

console.log(s3 == s4);

----------
false
false

还可以使用 Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字
符串键。如果查询的不是全局符号,则返回 undefined

1
2
3
4
5
6
7
8
let s5 = Symbol.for("test");
let s6 = Symbol.for("test");

console.log(s5==s6)
console.log(Symbol.keyFor(s5))
-------------
true
test

全局符号是可以复用的。

使用符号作为属性

符号作为属性的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
et s7 = Symbol("a"),
s8 = Symbol("b"),
s9 = Symbol("c");

let o = {
[s7]: "test7",
[s9]: "test9"
}

o[s8] = 'test8'

console.log(o)
console.log(o[s9])
---------------
{ [Symbol(a)]: 'test7',
[Symbol(c)]: 'test9',
[Symbol(b)]: 'test8' }
test9

Object类型

object是一种复合的数据类型,通过new来创建。

1
let o = new Object();

比较重要的属性和方法列表:

  • constructor: 构造函数
  • hasOwnProperty: 判断当前对象实例(不是原型)上是否存在给定的实例,要检查的属性名必须是字符串(如 o.hasOwnProperty(“name”))或符号
  • isPropertyOf(object): 用于判断当前对象是否是另外一个对象的原型。
  • propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString()::返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
  • toString(): 返回对象的字符串表示
  • valueOf(): 返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。
你的支持我的动力