使用 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 跟 var 作用相似,主要区别如下:
if (true) {
var name = 'Matt';
console.log(name) // Matt
let age = 12;
console.log(age); // 12
}
console.log(name); // Matt
console.log(age); // 报错
var name;
var name;
let age;
let age; // SyntaxError;标识符age已经被声明过了
console.log(age); // ReferenceError:age 没有定义
let age = 12;
在解析代码时,虽然 JavaScript 引擎会注意到出现在块后面的 let 声明,但在此之前不能以任何方式来引用未声明的变量,在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone)。
var name = 'Matt';
console.log(window.name); // Matt
let age = 26;
console.log(window.age); // undefined
使用 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 行为与 let 基本相同,但是它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
| 类型 | 返回值 |
|---|---|
| undefined | undefined |
| 布尔值 | boolean |
| 字符串 | string |
| 数值 | number |
| 对象或null | object |
| 函数 | function |
| 符号 | symbol |
| 数据类型 | 转换为 true 的值 | 转换为 false 的值 |
|---|---|---|
| String | 非空字符串 | ""(空字符串) |
| Number | 非零数值(包含无穷值) | 0、NaN |
| Object | 任意对象 | null |
| Undefined | N/A | undefined |
NaN 表示“不是数值”
isNaN函数判断一个值能否转为数值Number()``parseInt()``parseFloat()可以将非数值转换为数值。
Number()可用于任何数据类型,转换规则如下:
valueof方法,并按照上述规则转换。如果结果为 NaN,则调用 toString()方法,再按照字符串规则转换parseInt()方法用于转换字符串,从第一个非空格字符开始转换,如果第一个字符不是数值字符或加减号则直接返回 NaN。否则依次检测每个字符直到字符串末尾或碰到非数值字符。此外,parseInt()方法也可以接受第二个参数指定数值的进制数parseFloat()方法与 parseInt()方法类似,需要注意的是只有第一次出现的小数点是有效的,并且始终忽略字符串开头的 0\n 换行\t 制表\b 退格\r 回车\f 换页\\``\'``\" 反斜杠及在字符串以对应引号标识时使用引号\xnn 以十六进制编码 nn 表示的字符\unnn 以十六进制编码 nnnn 表示的 Unicode 字符length属性可以获取字符串的长度ECMAScript 中的字符串是不可变的,一旦创建,它们的值就不能改变。
几乎所有的值都可以使用 toString()方法转换为字符串,null 和 undefined 没有 toString()方法。
可以使用 String()转型函数判断 null 和 undefined:
toString()方法,则调用该方法并返回结果也可以使用 值 + ""的方式转换为字符串。
${}可以实现字符串插值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 是 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实例都有如下属性和方法:
!!操作符相当于调用 Boolean() 函数,将操作数转换为布尔值**,与 Math.pow() 结果相同最佳实践:函数要么返回值要么不返回值,只在某一个条件下返回值的函数会带来麻烦