JS由那三部分组成?
- ECMASript
- 文档对象模型(DOM)
- 浏览器对象模型(BOM)
操作数组的方法有那些?
- 高阶函数:map、filter、forEach、reduce、find、findIndex、every、some、
- push、unshift、shift、pop、splice、slice、join、concat、fill、indexOf、includes、reduceRight、reverse、sort、
会改变原数组的方法:push、unshift、shift、pop、splice、join、reverse、sort
说一下闭包,以及特点。
- 如何产生闭包?
-函数嵌套,内部函数引用了外部函数的(变量或函数)时,就产生了闭包
- 闭包的作用?
使用闭包函数执行完成后,内部函数引用外部函数的数据(变量或函数)在存在内存中
使函数外部可以操作到函数内部的数据
- 闭包的生命周期
1. 产生: 在内部嵌套函数定义执行(不是真实执行)完成时就产生了(不是在调用)
2. 死亡: 在内部嵌套函数成为垃圾对象时
function fn1() {
// 程序执行到此处时, 先函数提升然后变量提升(内部函数对象创建) 闭包出现
// 因为有 var a = undefined
var a = 2
function fun() {
console.log(++a)
}
return fun
}
var f = fn1()
f() // 3
f() // 4
// 此时没有死亡
f = null // 此时内部函数无引用指向 死亡
- 闭包的应用-定义js模块
- 具有特定功能的js模块
- 将所有的数据和方法都封装到一个函数的内部(私有的)
- 只向外部暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块导出的对象调用方法来实现对应的功能。
/*方法一*/
function my_module() {
var msg = 'songRuiXue'
function doSomething() {
console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
}
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
/*方法二*/
(function(window) {
var msg = 'songRuiXue'
function doSomething() {
console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
}
window.obj = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
调用
/*方法一*/
var obj = my_module()
obj.doSomething()
obj.doOtherthing()
/*方法二*/
obj.doSomething()
obj.doOtherthing()
事件代理(事件委托)?
事件流(event flow)过程:事件捕获 -> 目标阶段 -> 事件冒泡
阻止冒泡:event.stopPropagation() (停止传播)
阻止默认行为:event.preventDefault() return false
return false 不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()则只阻止事件往上冒泡,不阻止事件本身。
事件冒泡、事件捕获及事件代理
说一下for…in 和 for…of的区别?
for...of遍历获取的是对象的键值,for...in遍历获取的是对象的键名
for...of只遍历当前对象不会去遍历原型链,for...in会遍历对象的整个原型链,性能差。
对于数组的遍历,for...of只返回数组下标对应的属性值。for...in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性)。
总结:for...in主要用来遍历对象,不适合遍历数组。 for....of循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象
给 a b c 三个请求,希望 c 在 a b 获取结果之后再请求。
1. Promise.all
2. 使用数组实现
const fs = require('fs')
const arr = []
function fn (data) {
arr.push(data)
if (arr.length === 2) {
console.log(arr)
// 在此时可以执行 c 的逻辑
}
}
fs.readFile('./a.text', 'utf-8', (err, data) => {
fn(data)
})
fs.readFile('./b.text', 'utf-8', (err, data) => {
fn(data)
})
数据类型
基本数据类型(栈中存储的是值)
- String: 任意的字符串
- Number: 任意的数字
- Boolean: true / false
- undefined: undefined
- null: null
- Symbol: 不能当做构造函数使用,不能使用 new 关键字,可以用来表示对象的唯一键值。
- BigInt: 不能当做构造函数使用,不能使用 new 关键子,可以表示任意大的整数。
引用数据类型(栈中存储的是地址值,实际对象存储在堆)
- Object: 任意的对象
- Array: 一种特殊的对象(内部数据是有序的)
- Function: 一种特殊的对象,可以执行。
判断数据类型
- typeof:只能判断出基本数据类型,不能区分引用类型。
- instanceof:只能判断出引用类型,不能区分基本类型。
原理:判断实例对象的__proto__属性是否和构造函数的prototype属性指向同一个原型对象。如果没找到,则会在原型链上继续查找。 - ===: 全等于 / 注意尽量不用使用 == 因为会做数据类型转化。
- Object.prototype.toString:最佳的方法。
js中的数据结构
- 数组:数组是一组有序的值的集合,可以通过索引访问。js数组可以包含不同的数据类型,并且长度是动态的。
- 对象:对象用于存储键值对集合,并且内部是无序的,值可以是任意值。对象的键只能是字符串或符号。
- Map:类似于对象,Map是一种键值对的数据结构,但提供了更多的功能。Map的键可以是任何类型(对象或原始值),而对象的键只能是字符串或符号。
- Set:类似于数组,Set是一种只包含唯一值的数据结构。内部的值都是唯一的。Set提供了添加、删除和检查元素是否存在的方法。
- weakMap和weakSet
- stack(栈):先进后出
- 队列:先进先出
- 树
事件循环 (Event Loop)
javascript为什么是单线程?
js 作为主要运行在浏览器的脚本语言,主要的用处就是操作dom。
如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪一个线程的,如何判断优先级?
为了避免这种问题,js的单线程不会变化。
事件循环
事件循环就是通过异步执行任务的方法来解决js单线程的问题的。
- 一开始整个脚本作为一个宏任务执行。
- 执行过程中如果遇到同步任务直接执行,遇到宏任务加入宏任务队列,微任务加入微任务队列。
- 当前宏任务执行完毕后,回去微任务队列检查是否有微任务,如果有则依次执行,直到全部执行完毕。
- 执行完本轮的宏任务,检查宏任务队列并回到第2步继续执行此循环,直到宏任务与微任务队列都为空。
宏任务与微任务
js引擎把所有的任务分为两类,一类叫宏任务(macroTask),一类叫微任务(mincoTask)
宏任务:
script(整体代码)、setTimeout、setInterval、I/O、UI渲染、postMessage、MessageChannel
微任务:
new Promise().then()
经典题目1
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
- 整体script作为第一个宏任务进入主线程,输出
script start
- 遇到setTimeout,加入宏任务队列
- 遇到Promise,将其then回调函数加入微任务队列;第二个then回调函数也加入微任务队列
- 遇到同步任务,输出
script end
- 检测微任务队列,输出
promise1
、promise2
- 进入下一轮循环,执行setTimeout中的代码,输出
setTimeout
最后的执行结果:
script start
script end
promise1
promise2
setTimeout
经典题目2
async function async1() {
console.log('async1 start');
await asnyc2()
console.log('async1 end');
}
async function asnyc2() {
console.log('asnyc2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0)
async1()
new Promise(function(resolve) {
console.log('promise1');
resolve()
}).then(function() {
console.log('promise2');
})
console.log('script end');
- 整体script代码作为一个宏任务进入主线程中执行,输出
script start
- 遇到setTimeout将其加入宏任务队列
- 执行async1()函数,输出
async1 start
- 遇到await,执行asnyc2()函数,输出
asnyc2
并让出主线程。跳出async1()。并等待asnyc2()函数的结果。 - 在主线程中执行console.log(‘promise1’); 输出
promise1
- 遇到then()函数将其加入到微任务队列,代码向下执行并输出
script end
。第一次宏任务执行完毕。 - 跳回async1()函数,await等待的asnyc2()函数返回了结果。代码向下执行,输出
async1 end
。 - 检查微任务队列,输出
promise2
- 检查宏任务队列,输出
setTimeout
最后的执行结果:
script start
async1 start
asnyc2
promise1
script end
async1 end
promise2
setTimeout
async / await 的执行顺序详解
注意:针对浏览器,nodejs因为版本问题会有差异(如:10.16.0与20.0.0版本差异很大,可以自己试一下)主要以浏览器与node20.0.0版本进行分析
JavaScript中的 async / await 是AsyncFunction特性中的关键字,从字面意思上来解释的话:async 是 “异步“的简写,而await可以认为是 async wait 的简写。可以理解为:async用户申明一个function是异步的,而await用于等待一个异步方法执行完成。
async / await:
- async / await:是一种编写异步代码的新方法,之前异步代码的方案是primise和回调函数。
- async / await:是建立在Promise基础上的。
- async / await:和Promise一样也是异步非阻塞的。
- async / await:让异步代码看起来表现起来更加的像是同步代码。
- async / await:await 只能出现在 async 函数中。
async 怎么处理返回值
存在返回值
async function testAsync() {
return 'hello async'
}
const result = testAsync()
console.log(result); // Promise {<fulfilled>: "hello async"}
由以上结果可以看到:async函数返回的是一个 Promise 对象。如果在函数中 return 一个直接量,async会把这个直接量通过Promise.resolve()封装成 Promise 对象。
没有返回值(函数默认的返回值就是undefined)
async function testAsync1() {
console.log('这是没有返回值的async函数');
}
const result = testAsync1()
console.log(result); // Promise {<fulfilled>: undefined}
在没有await的情况下 async 函数的执行过程
没有await的情况下,执行async函数,它会立即执行,返回一个Promise对象,并且不会阻塞后面的语句。这和普通返回Promise对象的函数没有区别。
await 函数等待的机制
await从字面意思上来说就是等待。await是在等待一个表达式,这个表达式是一个Promise对象或者其他值。如:await 1、 await fun()
。
重要:
大部分人认为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上
await是一个让出线程的标志
。await 后面的函数会先执行一遍,然后就会跳出整个async函数,执行后面js栈中的代码。等到本轮事件循环执行完之后又跳回async函数中等待await后面表达式的返回值,如果返回值不是Promise则继续执行async函数后面的代码,否则将返回的Promise放入Promise队列。让出线程,等待结果,此时执行主线程其他的同步任务,执行栈为空后,再次进入线程。
async / await 执行顺序
经典题目1 await是一个让出线程的标志
function testSometing() {
console.log("执行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing(); // 关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
}
test();
var promise = new Promise((resolve) => {
console.log("promise start..");
resolve("promise");
}); // 关键点2
promise.then((val) => console.log(val));
console.log("test end...");
输出结果:
test start...
执行testSometing
promise start..
test end...
testSometing
执行testAsync
promise
hello async
执行过程
- 整体script代码作为一个宏任务进入主线程,代码自上而下,执行同步代码,输出
test start...
- 遇到 await,await后面的函数会先执行一遍,输出
执行testSometing
,然后让出主线程。跳出test函数
- 此时主线程继续向下执行,遇到 new Promise(),输出
promise start..
- 遇到 promise.then(),将其添加到微任务队列。
- 遇到同步代码,输出
test end...
。此时第一次宏任务执行完毕。 - 跳回test函数,等待之前的await后面的表达式的返回值。由于testSometing函数为同步函数,所以直接执行,输出
testSometing
。 - 继续执行,遇到 await,await后面的函数会先执行一遍,输出
执行testAsync
,然后让出主线程。跳出test函数
- 主线程代码执行完毕,此时读取微任务队列,发现promise.then()包含的回调函数,执行并输出
promise
- 清空微任务队列后,跳回
test函数
等待之前的await后面的表达式的返回值。该表达式返回一个Promise, await取到结果并输出hello async
。
总结
testSometing 与 testAsync 不论 是否被async关键字声明与否,执行结果均是以上结果。执行顺序受 await 关键字和函数返回值的影响。
JavaScript 中的 this
this 是什么?
只存在于函数中
任何函数的本质都是通过对象来调用的,如果没有直接指定那this就是window
所有的函数内部都有一个变量 this,箭头函数除外
值是调用函数的当前对象
如何确定 this 的值?
this 永远指向最后调用它的那个对象
fn(): window
p.fn(): p
var p = new Person(): p
fn.call(obj): obj
箭头函数没有this,它的this指向上层作用域中的this
严格模式下直接调用
fn()this 为 undefined
题目1:
function Person (color) {
console.log(this)
this.getColor = function () {
console.log(this)
}
}
Person('red') // this: window
var p = new Person("yello") // this: p
var obj = {}
p.getColor.call(obj, "pink") // this: obj
var test = p.getColor
test() // this: window
function fun1() {
console.log(this)
function fun2() {
console.log(this)
}
fun2() // this: window
}
fun1() // this: window
如何改变普通函数 的 this 指向
- 使用 _this = this,将this值提前用个变量保存起来。
- 使用 call、apply、bind
- new 实例化一个对象,this值指向新创建的实例对象
作用域与作用域链
什么是作用域?
作用域就是变量和函数可访问(起作用)的范围和区域,作用域的目的就是为了隔离变量和函数,保证不同作用域下的同名变量或函数不会冲突。
作用域的分类
- 静态作用域(词法作用域)。js就是属于词法作用域。
- 动态作用域
JS 作用域分类
-
全局作用域
-
函数作用域
-
块作用域(可以代替匿名函数自调用的写法)
什么是作用域链?
各个作用域的嵌套关系组成了一条作用域链,当代码在执行时,会创建一条变量对象的作用域链。
例子:
function foo() {
var a = 1
function bar() {
var a = 2
}
}
以上 bar 函数的 作用域链为: bar -> foo -> window
, foo的作用域链为: foo -> window
使用作用域链主要是
进行标识符(变量和函数)的查询,
标识符(变量和函数)的解析就是沿着作用域链一级一级的访问的过程,而作用域链就是保证对变量和函数的有序访问。