Skip to main content

语法

标识符

标识符指的是用来识别各种值的合法名称

  • 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_
  • 第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字 0-9

JavaScript 有一些保留字,不能用作标识符

变量

声明

  • let。声明块级作用域的变量
  • const。声明不可变的常量,但实际上如果声明一个对象,这个对象本身还是可以修改属性的
  • var。声明函数作用域的变量

var 会存在变量声明提升,letconst 则不存在变量声明提升

let/const 在进入块级作用域后,会因为提升的原因先创建,但不会被初始化,直到声明语句执行的时候才被初始化,初始化的时候如果使用 let 声明的变量没有赋值,则会默认赋值为 undefined,而 const 必须在初始化的时候赋值。而创建到初始化之间的代码片段就形成了暂时性死区

Q&A

下面的输出是什么,如何修改才能正确输出 0-4?

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

修改方案 1:将 var 替换为 let(利用块级作用域)。

for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}

原理解析(伪代码): 为什么 let 可以,而 var 不行? 因为 var 是函数作用域/全局作用域,循环 5 次实际上修改的是同一个内存地址的 i。而 let 是块级作用域,JS 引擎在每次循环时,都会为 let 重新创建一个新的词法环境(Lexical Environment),把当前的 i 值保存下来供闭包使用。引擎底层的处理逻辑类似于下面的伪代码:

// var 的底层情况:全局只有一个 i
var i = 0;
setTimeout(() => console.log(i), 0);
i = 1;
setTimeout(() => console.log(i), 0);
// ...当所有 setTimeout 真正执行时,i 早就变成了 5

// let 的底层情况:每次循环都生成了独立的作用域块
{
let i = 0;
setTimeout(() => console.log(i), 0);
}
{
let i = 1;
setTimeout(() => console.log(i), 0);
}
{
let i = 2;
setTimeout(() => console.log(i), 0);
}
// ...当 setTimeout 执行时,它们沿着各自的块级作用域链向上找,找到了当时被锁住的独立变量 i

修改方案 2:使用 IIFE(立即调用的函数表达式)形成闭包。

for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
}, 0);
})(i);
}

关键字

  • this。指向函数的执行上下文
  • function。定义函数
  • class。定义类
  • async function。 定义一个异步函数表达式
  • await。暂停或恢复执行异步函数,并等待promise的resolve/reject回调
  • new。创建构造函数实例

面试高频:箭头函数与普通函数的区别

在 ES6 中引入了箭头函数(Arrow Function),它不仅仅是普通函数的语法糖,在底层行为上有极其严格的区别。这是前端面试中几乎必问的考点。

特性普通函数 (function)箭头函数 (() => {})
this 的指向动态绑定。由调用方式决定(如作为对象方法调用、call/applynew 等)。静态绑定(词法作用域)。没有自己的 this,始终继承自外层执行环境的 this,且永远无法被 call/apply/bind 改变
arguments 对象有自己的 arguments 类数组对象,包含所有传入的参数。没有 arguments 对象。如果要获取不定参数,必须使用剩余参数(...args)。
作为构造函数 (new)可以被 new 调用,生成实例对象。不能被 new 调用,会抛出 TypeError。因为箭头函数没有 [[Construct]] 方法和 prototype 属性。
prototype 属性prototype 属性。没有 prototype 属性
yield 关键字可以作为 Generator 函数使用(function*)。不能用作 Generator 函数,内部不能使用 yield 关键字。

常见陷阱题:

const obj = {
name: "Jackson",
sayHi: () => {
console.log(this.name);
},
};
obj.sayHi(); // 输出什么?

答案:输出 undefined(或全局的 name)。因为对象字面量 {} 不产生作用域,箭头函数的 this 会向上寻找到全局作用域(浏览器中是 window),而不是 obj。如果要在对象方法中使用 this,必须使用普通函数。

.和 new 的优先级 new 后边紧跟对象,new 的优先级低;new 后边紧跟构造函数执行,new 的优先级高

  • new Foo.getName(). 的优先级高于 new
  • new Foo().getName()new Foo() 优先级更高

表达式

  • 属性访问符(obj.aobject["a"])
  • /ab+c/i。正则表达式
  • {}。对象初始化
  • []。数组初始化

运算符

  • 算术 +-*/%
  • 赋值 =
  • 复合,如 +=-=*=/=
  • 自增减++--
  • 比较 ><<=>===!====!==
  • 逻辑 &&||!??
  • 三元 ?:
  • 展开运算符 ...obj

+ 有一些额外的作用:

  • 连接字符串(5+'')
  • 转换为 Number(+'5'+false)

&&|| 也有一些额外的作用

  • 短路逻辑 let name = a || 'default'

以上只是罗列了一些常用表达式和运算符,更详细的参考 表达式和运算符

语句

  • 条件 if/else if/elseswitch
  • 循环 forwhiledo-whilefor...inlabelbreakcontinue
  • 异常捕获 try/catch/finally

注释

  • 单行 //
  • 多行 /** **/