1、变量提升和函数提升。
浏览器在执行代码之前,会有一个“预解析”阶段。
在“预解析”阶段,浏览器会做以下几件事情:
-
先找到所有通过 var 创建的变量,然后将变量保存到栈内存中,并赋值 undefined。
-
再找到所有声明式函数,然后将函数名以及函数地址保存到栈内存中。
“预解析”结束后,浏览器开始执行代码,每次需要访问数据时,浏览器都会到栈内存中查到对应的数据。
console.log(bar); // console.log(foo); // function foo() {} console.log(a); // undefined var a = 1; console.log(a); // 1 // 声明式函数 function foo() { } // 函数表达式 var bar = function() { }
2、有哪些方式可以判断一个数据是不是数组?
const arr = [1, 2, 3]; console.log(arr instanceof Array); // true console.log(Array.isArray(arr)); // true console.log(arr.constructor === Array); // true console.log(Object.prototype.toString.call(arr)); // [object Array] // typeof 判断数组并不准确 console.log(typeof arr); // object
3、JS 中 this 有哪些指向?
简单来说,this 可以大致分为三种情况:
-
全局的 this:指向 window
-
箭头函数的 this:指向箭头函数创建时所在作用域中的 this;
-
其他函数的 this:谁调用该函数,this 就指向谁;
而其中的第 3 种情况,我们还可以进行细分:
-
普通函数的 this:指向 window
-
对象方法的 this:指向调用该方法的对象;
-
构造函数的 this:指向 new 出来的实例对象;
-
事件方法的 this:指向绑定事件的元素节点;
-
回调函数的 this:
setTimeout/setInterval
中的指向 window(不同回调函数的 this 指向并不一样)
示例代码:
// 1. 全局的 this console.log(this); // window // 函数内的 this // 2. 普通函数:调用时通过“函数名()”的形式调用 const foo = function () { console.log(this); // window } foo(); // 3. 对象的方法:调用时通过“对象.函数名()”的形式调用 const student = { name: 'student', sayHello() { console.log(this); // 指向调用该函数的对象 } } const person = { name: 'person' }; person.sayHello = student.sayHello; person.sayHello(); // 4. 构造函数:调用时通过“new 函数名()”的形式调用 function Teacher() { console.log(this); // 指向 new 出来的实例对象 } new Teacher(); // 5. 事件方法:函数作为事件中的方法 div.onclick = function () { console.log(this); // 指向绑定事件的元素节点 } // 6. 回调函数:将函数作为参数进行传递 setTimeout(function () { console.log(this); // window }, 0) // 7. 箭头函数 // 箭头函数的 this,指向的是创建时所在外层作用域的 this。 const a = { name: 'a', sayHello() { const b = () => { console.log(11111, this); } return b; } } const b = a.sayHello(); b();
4、JS 事件循环(事件轮询)Event Loop
JS 是一门单线程语言,只有一个主线程。因此,在 JS 中,代码的执行流程如下:
-
所有的代码都是在主线程中执行,在执行过程中,会形成一个执行栈;
-
在主线程之外还有一个“任务队列”,系统会将异步代码放入到任务队列中,然后继续执行主线程的代码;
-
执行栈中所有的代码都执行完成后,系统会从“任务队列”读取已完成的任务,将任务添加到执行栈中,恢复执行;
主线程重复上面的三个步骤,就是“事件循环”。
console.log(1); console.log(2); setTimeout(() => { console.log(3); }, 0) setTimeout(() => { console.log(4); }, 0) console.log(5);
5、宏任务和微任务
任务队列中的代码可以分为宏任务和微任务。
-
宏任务:全局 script、setTimeout、setInterval
-
微任务:promise.then()/await
将任务进行分类后,执行顺序如下:
-
执行一个宏任务;
-
执行本轮循环中的所有微任务;
-
执行下一个宏任务;
-
执行本轮循环中的所有微任务;
async function async1() { console.log('1') await async2() console.log('2') } async function async2() { console.log('3') } console.log('4') setTimeout(function () { console.log('5') }, 0) async1() new Promise(function (resolve) { console.log('6') resolve() }).then(function () { console.log('7') }) console.log('8') // 宏任务:全局 script // 4 1 3 6 8 // 微任务:await、then // 2 7 // 宏任务:setTimeout // 5