《JavaScript 高级程序设计》第1章 语言基础

2024年03月11日

1. 语言基础

变量

var 关键字

使用 var 声明变量需要注意其作用域和声明提升。
函数中声明的变量会称为包含它的函数的局部变量

javascript 复制代码
function test() {
  var message = "hi"; // 局部变量
}
test();
console.log(message); // 报错

如果去除上述代码中的 var 关键字,message 变量将会变成全局变量

javascript 复制代码
function test() {
  message = "hi"; // 全局变量
}
test();
console.log(message); // hi

注意:不要省略操作符定义全局变量,在局部作用域中定义的全局变量很难维护,也会造成困惑。
使用 var 关键字声明的变量会自动提升到函数作用域的顶部

javascript 复制代码
function foo() {
  console.log(age);
  var age = 27;
}
foo(); // undefined

let 声明

let 跟 var 作用相似,主要区别如下:

  1. let 声明的范围是块作用域,而 var 生命的范围是函数作用域
javascript 复制代码
if (true) {
  var name = 'Matt';
  console.log(name) // Matt
  let age = 12;
  console.log(age); // 12
}
console.log(name); // Matt
console.log(age); // 报错
  1. 同一个块级作用域下,let 不能重复声明变量
javascript 复制代码
var name;
var name;

let age;
let age; // SyntaxError;标识符age已经被声明过了
  1. let 声明不会在作用域中被提升
javascript 复制代码
console.log(age); // ReferenceError:age 没有定义
let age = 12;

在解析代码时,虽然 JavaScript 引擎会注意到出现在块后面的 let 声明,但在此之前不能以任何方式来引用未声明的变量,在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone)。

  1. let 在全局作用域中声明的变量不会成为 windows 对象的属性(var 声明的变量会)
javascript 复制代码
var name = 'Matt';
console.log(window.name); // Matt

let age = 26;
console.log(window.age); // undefined
  1. for 循环中的 let 声明与 var 表现不同

使用 var 声明迭代变量时,执行超时逻辑所有的 i 都是同一个变量,因而输出同一个最终值。

javascript 复制代码
for (var i = 0;i < 5;i++) {
  setTimeout(() => console.log(i), 0)
}
// 输出 5 5 5 5 5

使用 let 声明迭代变量,JavaScript 引擎会在后台为每个迭代循环声明一个新的迭代变量。

javascript 复制代码
for (let i = 0;i < 5;i++) {
  setTimeout(() => console.log(i), 0)
}
// 输出 0 1 2 3 4

这种每次迭代声明一个独立变量实例的行为也适用于 for-in 和 for-of 循环。
在普通 for 循环中,var 声明的迭代变量会泄露到循环外部,而 let 声明不会

javascript 复制代码
for (var i = 0; i < 10; i++) {}
console.log(i) // 10

for (let j = 0; i < 10; j++) {}
console.log(j) // ReferenceError: j 没有定义

const 声明

const 行为与 let 基本相同,但是它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

最佳实践

  1. 不使用 var
  2. const 优先,let 次之

数据类型

typeof 操作符

类型 返回值
undefined undefined
布尔值 boolean
字符串 string
数值 number
对象或null object
函数 function
符号 symbol

Undefined 类型

  • 永远不要显式的给某个变量设置 undefined 值
  • 未初始化的变量被自动赋予 undefined 值

Null 类型

  • null 值表示一个空对象指针
  • 定义将来要保存对象值的变量时可以用 null 来初始化

Boolean 类型

数据类型 转换为 true 的值 转换为 false 的值
String 非空字符串 ""(空字符串)
Number 非零数值(包含无穷值) 0、NaN
Object 任意对象 null
Undefined N/A undefined

Number 类型

  • 不要测试两个浮点数,因为浮点数的精度问题会存在舍入错误
  • 超过 JavaScript 表示范围的数会表示为 Infinity 或 -Infinity

NaN

NaN 表示“不是数值”

  • 任何设计 NaN 的操作都返回 NaN
  • NaN 不等于任何值,包括它自己
  • 可使用 isNaN函数判断一个值能否转为数值

数值转换

Number()``parseInt()``parseFloat()可以将非数值转换为数值。

  • Number()可用于任何数据类型,转换规则如下:
    • 布尔值,true 转换为 1,false 转换为 0
    • 数值,直接返回
    • null, 返回 0
    • undefined,返回 NaN
    • 字符串:
      • 字符串包含数值字符则转换为对应数值(忽略数值前面的0)
      • 空字符串转换为 0
      • 其余情况返回 NaN
    • 对象,调用valueof方法,并按照上述规则转换。如果结果为 NaN,则调用 toString()方法,再按照字符串规则转换
  • parseInt()方法用于转换字符串,从第一个非空格字符开始转换,如果第一个字符不是数值字符或加减号则直接返回 NaN。否则依次检测每个字符直到字符串末尾或碰到非数值字符。此外,parseInt()方法也可以接受第二个参数指定数值的进制数
  • parseFloat()方法与 parseInt()方法类似,需要注意的是只有第一次出现的小数点是有效的,并且始终忽略字符串开头的 0

String 类型

字符字面量

  • \n 换行
  • \t 制表
  • \b 退格
  • \r 回车
  • \f 换页
  • \\``\'``\" 反斜杠及在字符串以对应引号标识时使用引号
  • \xnn 以十六进制编码 nn 表示的字符
  • \unnn 以十六进制编码 nnnn 表示的 Unicode 字符
  • 使用 length属性可以获取字符串的长度

字符串特点

ECMAScript 中的字符串是不可变的,一旦创建,它们的值就不能改变。

转换为字符串

几乎所有的值都可以使用 toString()方法转换为字符串,null 和 undefined 没有 toString()方法。
可以使用 String()转型函数判断 null 和 undefined:

  • 如果值有 toString()方法,则调用该方法并返回结果
  • 如果值为 null 则返回 "null"
  • 如果值为 undefined 则返回 "undefined"

也可以使用 值 + ""的方式转换为字符串。

模板字面量与字符串插值

  • 模板字面量保留换行字符,可以跨行定义字符串
  • 使用${}可以实现字符串插值
javascript 复制代码
let value = 5;
let exponent = 'second';
// ES5 字符串插值实现
let interpolatedString = 
  value + 'to the ' + exponent + ' power is ' + (value * value);
// ES6 使用模板字面量实现
let interpolatedTemplateLiteral = 
  `${value} to the ${exponent} power is ${value * value}`;

console.log(interpolatedString); // 5 to the second power is 25
console.log(interpolatedTemplateLiteral); // 5 to the second power is 25

模板字面量标签函数

模板字面量支持定义标签函数来自定义插值行为。标签函数本身是一个常规函数,通过前缀到模板字面两来因公自定义行为。

javascript 复制代码
let a = 6;
let b = 9;

function simpleTag(strings, ...expressions) {
  console.log(strings);
  for (const expression of expressions) {
    console.log(exporession);
  }
  return 'foobar';
}
let taggedResult = simpleTag`${a} + ${b} = ${a+b}`;
// ["", " + ", ""]
// 6
// 9
// 15

console.log(taggedResult); // "foobar"

原始字符串

使用默认的String.raw标签函数可以获取原始的模板字面量内容(如换行符和 Unicode 字符)

javascript 复制代码
// Unicode示例
// \u00A9 是版权符号
console.log(`\u00A9`); // © 
console.log(String.raw`\u00A9`); // \u00A9

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

javascript 复制代码
function printRaw(strings) {
  for (const string of strings.raw) {
    console.log(string);
  }
}
printRaw`\u0049${'and'}\n`;
// \u0049
// \n

Symbol 类型

Symbol 是 ES6 新增的数据类型,其实例是唯一、不可变的,主要用于确保对象属性使用唯一标识符,不会发生属性冲突的危险。

基本用法

使用 Symbol()函数初始化符号,可以传入一个字符串参数作为对符号的描述。Symbol 不能与 new 关键字一起作为构造函数使用。

使用全局符号注册表

如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为 Key 在全局注册表中创建并重用符号。

javascript 复制代码
let fooGlobalSymbol = Symbol.for('foo'); // 创建新符号
let otherFooGlobalSymbol = Symbol.for('foo'); // 重用已有符号

console.log(fooGlobalSymbol === otherFooGlobalSymbol) // true

可以使用 Symbol.keyFor()来查询全局注册表,这个方法接受符号并返回全局符号对应的字符串 Key。

使用符号作为属性

凡是可以使用字符串或数值作为属性的地方都可以使用符号,包括对象字面量属性和使用Object.defineProperty()Object.defineProperties()定义的属性。

javascript 复制代码
let s1 = Symbol('foo'),
    s2 = Symbol('bar'),
    s3 = Symbol('baz'),
    s4 = Symbol('qux');
let o = {};
o[s1] = 'foo val';
Object.defineProperty(o, s2, {value: 'bar value'});
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()返回对象实例符号属性数组
  • Object.getOwnPropertyDescriptors()返回包含常规和符号属性描述符的对象
  • Reflect.ownKeys()返回两种类型的 Key

Object 类型

每个object实例都有如下属性和方法:

  • constructor 构造函数
  • hasOwnProperty(propertyName) 判断当前对象实例是否有给定的属性(字符串或符号)
  • isPrototypeOf(object) 判断当前对象是否为另一个对象的原型
  • propertyIsEnumerable(propertyName) 用于判断给定属性是否可以使用for-in枚举
  • toLocaleString() 返回字符串表示(本地化)
  • toString() 返回字符串表示
  • valueOf 返回对象对应的字符串数值或布尔值表示

操作符

  • 前缀递增递减操作符会在语句求值之前被改变,后缀递增递减操作符在语句被求值后才发生
  • 使用 !!操作符相当于调用 Boolean() 函数,将操作数转换为布尔值
  • ES7新增了指数操作符**,与 Math.pow() 结果相同
  • 关系操作符进行比较时,如果操作数都是字符串,则逐个比较字符串中对应字符的编码
  • 关系操作符在比较 NaN 时,无论是小于还是大于等于,结果都是 false
  • 由于相等和不相等操作符存在类型转换问题,推荐使用全等和不全等操作符

语句

  • if 语句
  • while do-while 语句
  • for for-in for-of 语句
  • 标签语句和break,continue 语句
  • with 语句(不推荐使用)
  • switch 语句

函数

最佳实践:函数要么返回值要么不返回值,只在某一个条件下返回值的函数会带来麻烦

相关文章

Vite项目配置本地HTTPS

React Native 开发环境安装踩坑

《JavaScript 高级程序设计》第10-16章