《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 作用相似,主要区别如下:
- 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); // 报错
- 同一个块级作用域下,let 不能重复声明变量
var name;
var name;
let age;
let age; // SyntaxError;标识符age已经被声明过了
- let 声明不会在作用域中被提升
console.log(age); // ReferenceError:age 没有定义
let age = 12;
在解析代码时,虽然 JavaScript 引擎会注意到出现在块后面的 let 声明,但在此之前不能以任何方式来引用未声明的变量,在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone)。
- let 在全局作用域中声明的变量不会成为 windows 对象的属性(var 声明的变量会)
var name = 'Matt';
console.log(window.name); // Matt
let age = 26;
console.log(window.age); // undefined
- 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 声明的变量会导致运行时错误。
最佳实践
- 不使用 var
- 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"
也可以使用 值 + ""的方式转换为字符串。
模板字面量与字符串插值
- 模板字面量保留换行字符,可以跨行定义字符串
- 使用
${}可以实现字符串插值
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.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 语句
函数
最佳实践:函数要么返回值要么不返回值,只在某一个条件下返回值的函数会带来麻烦