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

1. 语言基础

变量

var 关键字

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

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

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

function test() {
  message = "hi"; // 全局变量
}
test();
console.log(message); // hi

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

function foo() {
  console.log(age);
  var age = 27;
}
foo(); // undefined

let 声明

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

  1. let 声明的范围是块作用域,而 var 生命的范围是函数作用域
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 不能重复声明变量
var name;
var name;

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

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

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

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

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

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

使用 let 声明迭代变量,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 声明不会

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 类型

Null 类型

Boolean 类型

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

Number 类型

NaN

NaN 表示“不是数值”

数值转换

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

String 类型

字符字面量

字符串特点

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

转换为字符串

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

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

模板字面量与字符串插值

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

模板字面量标签函数

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

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 字符)

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

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

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 在全局注册表中创建并重用符号。

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

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

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

使用符号作为属性

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

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 类型

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

操作符

语句

函数

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

相关文章