JavaScript 高级程序设计(第 4 版)- 语言基础

2023-05-17 15:02:57 浏览数 (1)

# 变量

# let

  • 暂时性死区
    • let 声明的变量不会在作用域中被提升
    • let 声明之前的执行瞬间为“暂时性死区”,在此阶段引用任何后面才声明的变量都会抛出 ReferenceError
  • 全局声明
    • 使用 let 在全局作用域中声明的变量不会成为 window 对象的属性
  • 条件声明
    • 不能依赖条件声明模式
  • for 循环中的 let 声明
    • 使用 let 声明迭代变量时,JS 引擎在后台会为每个迭代循环声明一个新的迭代变量

# const

  • 行为与 let 基本相同,当其在声明变量时必须同时初始化变量,且不能在后期修改
  • const 声明的限制只适用于它指向的变量的引用

# 最佳实践

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

# 数据类型

  • 简单数据类型(原始类型): Undefined, Null, Boolean, Number, String, Symbol
  • 复杂数据类型: Object

# typeof

  • 用于确定任意变量的数据类型
  • 返回值
    • "undefined"
    • "boolean"
    • "string"
    • "number"
    • "object" 值为对象或者null
    • "function"
    • "symbol"

# undefined

  • var 或 let 声明但没有初始化时,就相当于给变量赋予了 undefined 值
  • undefined 是一个假值,但是一定要明确检测 undefined 字面值, 而不仅仅是假值,因为很多其他可能的值同样是假值

# Null

null 是一个空对象指针

代码语言:javascript复制
let car = null;
console.log(typeof car); // "object"

在定义将来要保存对象值的变量时,建议使用 null 来初始化

undefined 值是由 null 值派生而来,ECMA-262 将其定义为表面相等

代码语言:javascript复制
console.log(null == undefined); // true
console.log(null === undefined); // false

null 是一个假值,但是一定要明确检测 null 字面值

# Boolean

  • 有两个字面值 true 和 false

数据类型

转换为true的值

转换为false的值

Boolean

true

false

String

非空字符串

""(空字符串)

Number

非零数值(包括无穷值)

0,NaN

Object

任意对象

null

Undefined

N/A(不存在)

undefined

  • 如 if 等流控制语句会自动执行其他类型值到布尔值的转换

# Number

使用 IEEE754 格式表示整数和浮点数(双精度值)

代码语言:javascript复制
let intNum = 55; // 整数
let octalNum = 070; // 八进制
let hexNum = 0xA; // 十六进制

let floatNum1 = 0.1; // 浮点数
let floatNum2 = .1; // 有效,不推荐
let floatNum3 = 3.125e7; // 31250000
let floatNum4 = 3e-7; // 0.0000003

浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确

IEEE754 会引入舍入错误

值的范围

  • Number.MIN_VALUE 5e-324
  • Number.MAX_VALUE 1.7976931348623157e 308
  • 超出范围会自动转换成特殊值 Infinity(-Infinity)

NaN(Not a Number), 要返回数值的操作失败了

isNaN() 可判断是否不是数值

数值转换

  • Number()
    • 布尔值, true 转换为 1,false 转换为 0
    • 数值,直接返回
    • null,返回0
    • undefined,返回NaN
    • 字符串
      • 包含数值字符,转换为十进制数值
      • 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值
      • 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制对应的十进制整数值
      • 如果是空字符串(不包含字符),则返回0
      • 如果不包含上述情况,则返回 NaN
    • 对象,调用valueOf(),然后按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照字符串规则转换
  • parseInt()
    • 字符串最前面的空格会被忽略,从第一个非空格字符开始,如果第一个字符不是数值字符或加减号,会立即返回NaN
    • 能识别不同的整数格式(十进制、八进制、十六进制)
    • 接收第二个参数,用于指定底数(进制数)
  • parseFloat()
    • 和parseInt() 函数类似,从位置0开始检测每个字符
    • 始终忽略字符串开头的零,只能解析十进制值

# String

表示零个或多个16位Unicode字符序列

字面量

含义

n

换行

t

制表

b

退格

r

回车

f

换页

\

反斜杠()

'

单引号,在字符串以单引号标示时使用

"

双引号,在字符串以双引号标示时使用

`

反引号,在字符串以反引号标示时使用

xnn

以十六进制编码nn表示的字符

unnnn

以十六进制编码nnnn表示的Unicode字符

字符串的特点

  • 不可变,一旦创建,其值就不能变了,要修改某个变量中的值符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量

转换为字符串

  • toString(): 返回当前值的字符串等价物
    • 可见于数值、布尔值、对象和字符串值,null和undefined值没有toString()方法
    • 一般无参数,数值调用该方法可以接受一个底数参数,默认十进制
  • 如果不确定一个值是不是null或undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串
    • 如果值有toString()则调用并返回结果
    • 如果值是null,返回"null"
    • 如果值是undefined,返回"undefined"

模板字面量

  • 保留换行字符,可以跨行定义字符串
  • 支持字符串插值,所有插入的值都会使用toString()强制转型为字符串
  • 支持定义标签函数,通过标签函数可以自定义插值行为
代码语言:javascript复制
let a = 6;
let b = 9;
function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
  console.log(strings);
  console.log(aValExpression);
  console.log(bValExpression);
  console.log(sumExpression);
  return 'foobar';
}
let untaggedResult = `${a}   ${b} = ${a b}`;
let taggedResult = simpleTag`${a}   ${b} = ${a b}`;
// ["", "   ", " = ", ""]
// 6
// 9
// 15
console.log(untaggedResult); // "6   9 = 15"
console.log(taggedResult); // "foobar"

  • 可以直接获取原始的模板字面量内容,而不是被转换后的字符表示

# Symbol

Symbol是原始值,且Symbol实例是唯一、不可变的。用于确保对象属性使用唯一标识符,不会发生属性冲突的危险

基本用法

代码语言:javascript复制
let sym = Symbol();
console.log(typeof sym); // symbol

let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();

let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');

console.log(genericSymbol === otherGenericSymbol); // false
console.log(fooSymbol === otherFooSymbol); // false

Symbol() 函数不能与new关键字一起作为构造函数使用,避免创建Symbol包装对象

使用全局Symbol注册表

  • 如果运行时的不同部分需要共享和重用Symbol实例,那么可以用一个字符串作为键,在全局Symbol注册表中创建并重用Symbol
代码语言:javascript复制
let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); // symbol
let otherFooGlobalSymbol = Symbol.for('foo'); // 重用已有符号

console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
// 查询全局注册表
console.log(Symbol.KeyFor(fooGlobalSymbol)); // foo

使用Symbol作为属性

  • 凡是可以使用字符串或数值作为属性的地方,都可以使用Symbol
  • 包括对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性
  • 对象字面量只能在计算属性语法中使用Symbol作为属性
代码语言:javascript复制
let s1 = Symbol('foo'),
    s2 = Symbol('bar'),
    s3 = Symbol('baz'),
    s4 = Symbol('qux');
let o = {
  [s1]: 'foo val'
};
// 或 o[s1] = 'foo val';
console.log(o); // {Symbol(foo): foo val}

Object.defineProperty(o, s2, { value: 'bar val' });
console.log(o); // {Symbol(foo): foo val, Symbol(bar): bar val }

Object.defineProperties(o, {
  [s3]: { value: 'baz val'},
  [s4]: { value: 'qux val'}
});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val, Symbol(baz): baz val, Symbol(qux): qux val}

Object.getOwnPropertyNames() 返回对象的常规属性数组

  • Object.getOwnPropertySymbols() 返回对象实例的Symbol属性数组
  • Object.getOwnPropertyDescriptors() 返回同时包含常规和Symbol属性描述符的对象
  • Reflect.ownKeys() 返回两种类型的键

常用内置Symbol

  • 内置Symbol都以Symbol工厂函数字符串属性的形式存在
  • 用途之一是重新定义它们,从而改变原生结构的行为

Symbol.asyncIterator

  • 表示实现异步迭代器API的函数
  • for-await-of 循环会利用这个函数执行异步迭代操作。循环时,会调用以Symbol.asyncIterator为键的函数,并期望这个函数返回一个实现迭代器API的对象。
代码语言:javascript复制
class Foo {
  async *[Symbol.asyncIterator]() {}
}
let f = new Foo();
console.log(f[Symbol.asyncIterator]());
// AsyncGenerator{<suspended>}

  • 由Symbol.asyncIterator函数生成的对象应该通过其next()方法陆续返回Promise实例。可以通过显式调用next()方法返回,也可以隐式地通过异步生成器函数返回
代码语言:javascript复制
class Emitter {
  constructor(max) {
    this.max = max;
    this.asyncIdx = 0;
  }
  async *[Symbol.asyncIterator]() {
    while (this.asyncIdx < this.max) {
      yield new Promise((resolve) => resolve(this.asyncIdx  ));
    }
  }
}
async function asyncCount() {
  let emitter = new Emitter(5);
  for await (const x of emitter) {
    console.log(x);
  }
}
asyncCount();
// 0
// 1
// 2
// 3
// 4

Symbol.hasInstance

  • 该Symbol作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用”
  • instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型
代码语言:javascript复制
function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true

  • 在 ES6中,instanceof 操作符会使用Symbol.hasInstance 函数来确定关系。以 Symbol.hasInstance 为键的函数会执行同样的操作,只是操作数对调了
代码语言:javascript复制
function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true

  • 该属性定义在Function的原型上,因此默认在所有函数和类上都可以调用

Symbol.isConcatSpreadable

  • 表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其他数组元素”
  • ES6 中的Array.prototype.concat() 方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例
  • 覆盖 Symbol.isConcatSpreadable 的值可以修改这个行为
  • 数组对象默认情况下会被打平到已有的数组, false 或假值会导致整个对象被追加到数组末尾。
  • 类数组对象默认情况下会被追加到数组末尾, true 或真值会导致这个类数组对象被打平到数组实例。
  • 其他不是类数组对象的对象在 Symbol.isConcatSpreadable 被设置为 true 的情况下将被忽略。
代码语言:javascript复制
let initial = ['foo'];

let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)]

let arrayLikeObject = { length: 1, 0: 'baz' };
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]
arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']

let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject)); // ['foo', Set(1)]
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo']

Symbol.iterator

  • 该Symbol作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由for-of语句使用”(即实现迭代器 API 的函数)
  • for-of 循环会利用这个函数执行迭代操作
代码语言:javascript复制
class Emitter {
  constructor(max) {
    this.max = max;
    this.idx = 0;
  }
  *[Symbol.iterator]() {
    while(this.idx < this.max) {
      yield this.idx  ;
    }
  }
}
function count() {
  let emitter = new Emitter(5);
  for (const x of emitter) {
    console.log(x);
  }
}
count();
// 0
// 1
// 2
// 3
// 4

Symbol.match

  • 该Symbol作为一个属性表示"一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用。"
  • 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
代码语言:javascript复制
console.log(RegExp.prototype[Symbol.match]); 
// ƒ [Symbol.match]() { [native code] }

console.log('foobar'.match(/bar/));
// ["bar", index: 3, input: "foobar", groups: undefined]

Symbol.replace

  • 该Symbol作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由 String.prototype.replace() 方法使用”
代码语言:javascript复制
console.log(RegExp.prototype[Symbol.replace]);
// ƒ [Symbol.replace]() { [native code] }
console.log('foobarbaz'.replace(/bar/, 'qux'));
// 'fooquxbaz'

Symbol.search

  • 该Symbol作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search() 方法使用”
代码语言:javascript复制
console.log(RegExp.prototype[Symbol.search]);
// ƒ [Symbol.search]() { [native code] }
console.log('foobar'.search(/bar/));
// 3

Symbol.species

  • 该Symbol作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”

Symbol.split

  • 该Symbol作为一个属性表示“一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split() 方法使用”

Symbol.toPrimitive

  • 该Symbol作为一个属性表示“一个方法,该方法将对象转换为相应的原始值。由ToPrimitive抽象操作使用”

Symbol.toStringTag

  • 该Symbol作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString()使用”
  • 通过toString()方法获取对象标识时,会检索由 Symbol.toStringTag 指定的实例标识符,默认为“Object”

Symbol.unscopables

  • 该Symbol作为一个属性表示“一个对象,该对象所有的以及继承的属性,都会从关联对的with环境中排出”

# Object

代码语言:javascript复制
let o = new Object();
let o2 = new Object; // 合法,但不推荐

  • 对象就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。
  • 开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法。
  • ECMAScript中的Object也是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在
    • constructor: 用于创建当前对象的函数。该属性的值就是Object()函数
    • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串或Symbol
    • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
    • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举
    • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
    • toString():返回对象的字符串表示
    • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。

# 操作符

# 一元操作符

递增/递减操作符

  • 使用前缀加或前缀减,变量的值会在语句被求值之前改变
  • 后缀递增或递减在语句被求值后才发生

一元加和减

代码语言:javascript复制
let num = 25;
num =  num;
console.log(num); // 25
num = -num;
console.log(num); // -25

# 位操作符

用于数值的底层操作,即操作内存中表示数据的比特(位)

ECMAScript中所有数值都以 IEEE 754 64位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,再进行位操作,之后再把结果转换为64位

有符号整数使用32位的前31位表示整数值。第32位(即符号位)表示数值的符号,如0表示正,1表示负。

  • 正值以真正的二进制格式存储,如果一个位是空的,则以0填充
  • 负值以一种称为二补数(补码)的二进制编码存储
    1. 确定绝对值的二进制表示
    2. 找到数值的一补数(或反码),即每个0变成1,每个1变成0
    3. 给结果加1

按位非

  • ~ ,返回数值的一补数
  • 按位非的最终效果是对数值取反并减1
代码语言:javascript复制
let num = 25; // 二进制    0000 0000 0000 0000 0000 0000 0001 1001
let num2 = ~num; // 二进制 1111 1111 1111 1111 1111 1111 1110 0110
console.log(num2); // -26

按位与

  • &,将两个数每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作

第一个数值的位

第二个数值的位

结果

1

1

1

1

0

0

0

1

0

0

0

0

按位或

  • |

第一个数值的位

第二个数值的位

结果

1

1

1

1

0

1

0

1

1

0

0

0

按位异或

  • ^

第一个数值的位

第二个数值的位

结果

1

1

0

1

0

1

0

1

1

0

0

0

左移

  • <<,按照指定的位数将数值的所有位向左移动
代码语言:javascript复制
let oldValue = 2; // 等于二进制 10
let newValue = oldValue << 5; // 等于二进制 100 0000,即十进制64

  • 左移会保留它所操作数值的符号

有符号右移

  • >>, 将数值的所有32位都向右移,同时保留符号(正或负)。
  • 有符号右移实际上是左移的逆运算
  • 右移后空位会出现在左侧,且在符号位之后。ECMAScript会用符号位的值来填充这些空位,以得到完整的数值

无符号右移

  • >>>,会将数值的所有32位都向右移
  • 对于正数,无符号右移与有符号右移结果相同
  • 对于负数,有时差异较大。无符号右移会给空位补0,而不管符号位是什么。

# 布尔操作符

  • 逻辑非 !
  • 逻辑与 &&
  • 逻辑或 ||

# 乘性操作符

  • 乘法 *
  • 除法 /
  • 取模 %

# 指数操作符

代码语言:javascript复制
console.log(Math.pow(3, 2)); // 9
console.log(3 ** 2); // 9

let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4

# 加性操作符

  • 加法操作符
    • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面
    • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,将两个字符串拼接在一起
  • 减法操作符

# 关系操作符

  • <
  • >
  • <=
  • >=

# 相等操作符

  • 等于和不等于(==,!=)
    • 先进性类型转换再确定操作数是否相等
    • null 和 undefined 相等(二者不能转换为其他类型的值再进行比较)
    • 如果任意操作数是NaN,相等操作符返回false,不相等操作符返回true
    • 如果操作数都是对象,则比较是不是同一个对象,如果指向同一个对象,则相等操作符返回true

    表达式 结果 null == undefined true "NaN" == NaN false 5 == NaN false NaN == NaN false NaN != NaN true false == 0 true true == 1 true true == 2 false undefined == 0 false null == 0 false "5" == 5 true

  • 全等和不全等(===, !==)
    • 比较相等时不转换操作数,只有两个操作数在不转换的前提下相等才返回true

# 条件操作符

代码语言:javascript复制
let max  = (num1 > num2) ? num1 : num2;

# 赋值操作符

  • 简单赋值 =
  • 复合赋值 *=、/=、%= 等等

# 逗号操作符

  • 逗号操作符可以用来在一条语句中执行多个操作

# 语句

if

代码语言:javascript复制
if (condition) statement1 else statement2

  • condition 可以是任何表达式,并且求值结果不一定是布尔值。ECMAScript 会自动调用 Boolean() 函数将这个表达式的值转换为布尔值
  • 最佳实践是使用语句块,即使一行代码要执行也是如此

do-while

  • 一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值

while

  • 一种先测试循环语句,即先检测退出条件,再执行循环体内的代码

for

  • 先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式
代码语言:javascript复制
for (initialization; expression; post-loop-expression) statement

for-in

  • 一种严格的迭代语句,用于枚举对象中的非符号键属性
代码语言:javascript复制
for (property in expression) statement

  • for-in语句不能保证返回对象属性的顺序
  • 如果for-in循环要迭代的变量是null或undefined,则不执行循环体

for-of

  • 一种严格的迭代语句,用于遍历可迭代对象的元素
代码语言:javascript复制
for (property in expression) statement

标签语句

  • 标签语句用于给语句加标签
代码语言:javascript复制
label: statement

break 和 continue

  • 为执行循环代码提供更严格的控制手段
  • break 语句用于立即退出循环,强制执行循环后的下一条语句
  • continue 语句也用于立即退出循环,但会再次从循环顶部开始执行
  • continue 语句也可以使用标签
代码语言:javascript复制
let num = 0;
outermost:
for (let i = 0; i < 10; i  ) {
  for (let j = 0; j < 10; j  ) {
    if (i == 5 && j == 5) {
      continue outermost;
    }
    num  ;
  }
}
console.log(num);

with

  • 将代码作用域设置为特定的对象
代码语言:javascript复制
with (expression) statement;

  • 主要场景是针对一个对象反复操作,此时将代码作用域设置为该对象能提供便利
代码语言:javascript复制
let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

  • 使用 with 语句,就可以少写一些代码
代码语言:javascript复制
with (location) {
  let qs = search.substring(1);
  let hostName = hostname;
  let url = href;
}

  • 严格模式不允许使用 with 语句,否则会抛出错误

switch

代码语言:javascript复制
switch (expression) {
  case value1:
    statement
    break;
  case value2:
    statement
    break;
  case value3:
    statement
    break;
  default:
    statement
}

  • 每个case相当于:如果表达式等于后面的值,则执行下面的语句
  • break 关键字会导致代码执行跳出switch语句,如果没有break,则代码会继续匹配下一个条件
  • default 关键字用于在任何条件都没有满足时指定默认执行的语句
  • switch 语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象
  • 条件的值不需要是常量,也可以是变量或表达式

# 函数

代码语言:javascript复制
function functionName(arg0, arg1, ..., argN) {
  statements
}

  • 严格模式对函数有一些限制
    • 函数不能以eval或arguments作为名称
    • 函数的参数不能叫eval或arguments
    • 两个命名参数不能拥有同一个名称

0 人点赞