js 在执行代码时可分为全局上下文、函数上下文、eval 上下文,代码执行过程:创建全局上下文(caller),自上而下执行全局上下文;遇到函数时,函数上下文(callee)被 push 到执行栈顶端;开始执行函数中的代码,caller 被挂起;函数执行完后,callee 被移除出执行栈,继续执行全局上下文
声明标识符
let
- 作用域是块级作用域
- 不存在变量声明提前
- 不能重复定义
- 存在暂时性死区
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
// 上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp
// 导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
// 以下死区比较隐蔽
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
const
- 一般用来声明常量,且声明的常量是不允许改变的
- 块级作用域
- 存在暂时性死区
var
- 作用域是函数作用域
- 存在变量提升
变量提升
明确两点:
- 函数声明优于变量声明
- 函数表达式会将变量提升,但是代码在执行的时候才会被赋值
test();
console.log(test); // 输出下面的函数test(),符合第一点
function test() {
console.log("我是函数");
}
console.log(test);
var test = "我是变量";
console.log(test); // 输出'我是变量',符合第二点
var test = function (params) {
console.log("我是函数表达式");
};
console.log(test);
test();
this
this 的指向,是在调用函数时根据上下文所动态决定的
- 默认绑定
- 隐式绑定(根据调用关系决定)
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
},
};
var fn1 = foo.fn;
fn1(); // this 指向window,这就是隐式丢失
foo.fn(); // this 指向foo,这也就是隐式绑定,指向最后调用它的对象
- 显式绑定(call/apply/bind/new 绑定)
new 绑定的优先级比显式 bind 绑定更高。如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;如果返回的不是一个对象,那么 this 仍然指向实例
- 箭头函数
function foo() {
return (a) => {
console.log(this.a);
};
}
const obj1 = {
a: 2,
};
const obj2 = {
a: 3,
};
const bar = foo.call(obj1);
console.log(bar.call(obj2)); // 输出2,箭头函数的绑定无法被修改
—>详解 this
call/apply/bind 的区别和实现
三者作用是相同的,都可以改变 this 的指向,区别在于传参不同。call 传的参数是参数列表,相当于...[参数数组]
,apply 是参数数组,而 bind 是直接改变这个函数的 this 指向并且返回一个新的函数,而不是直接调用立刻执行。
作用域和作用域链
作用域其实可理解为执行上下文中声明的变量和变量的作用范围。
作用域链: 当访问一个变量时,解释器会首先在当前作用域查找,如果没有找到,就去父作用域找,直到找到该变量或者不在父作用域中,这就是作用域链。
闭包
函数嵌套函数时,内层函数调用外层函数变量,并且在全局下可访问,这就是闭包
栈溢出
- 递归
面试题:有一个函数,参数是一个函数,返回值也是一个函数,返回的函数功能和入参的函数相似,但这个函数只能执行 3 次,再次执行无效,如何实现
function sayHi() {
console.log("hi");
}
function threeTimes(fn) {
let times = 0;
return () => {
if (times++ < 3) {
fn();
}
};
}
const newFn = threeTimes(sayHi);
newFn();
newFn();
newFn();
newFn();
newFn(); // 后面两次执行都无任何反应
面试题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, 1000);
}
// serTimeout是异步操作,等待外部循环结束后再执行,此时i=6,于是输出一堆66666
// 解决办法1:立即执行函数
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, 1000);
})(i);
}
// 解决办法2:使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, 1000);
}
// 每个 let 会和代码块结合起来形成块级作用域