- JavaScript
- 概念
- 1.说一下闭包/闭包解决了什么问题?
- 不恰当使用闭包会造成什么问题
- 2.如何解决回调地狱?(3种方式)
- 3.说一下事件委托?
- 4.说说深浅拷贝?
- 5.freeze函数?
- 6.执行上下文
- *7.js有哪些代码规范
- 8.栈和堆的区别?
- 9.让数组乱序/数组打乱?
- 10.什么是数组扁平化?
- 11.js基本数据类型?
- 12.数组常用方法?
- 13.*js语言特性?
- 14.改变this指向(apply,bind,call)?
- 15.const定义的数据可以改变吗?
- 16.异步加载的几种方式?
- 17.事件循环?| js的执行机制?
- *18.如何原生实现instanceof?
- 19.什么是函数柯里化(bind方法就是)?
- 20.typeof判断数据类型的原理
- 21.this关键字在不同环境下的指向?
- 22.原型和原型链(构造函数<==>原型 实例=>原型)
- 23.js是单线程,单线程有什么好处?
- 24.querySelector返回值是什么?querySelectorAll呢?
- 25.函数传参是引用吗?修改形参会影响实参?
- 26.如何判断变量是否是数组
- 27.如何把对象转成key/value的二维数组
- 28.说几种遍历数组的方法
- 29.js的执行过程
- 30.一个超长字符串能存在栈内存中吗?
- 31.for of | for in实现原理
- 32.new一个对象发生了什么?
- 33.==和===有什么区别?
- 34.实现一个同步的sleep函数
- 35.字符串去重
- 36.怎么判断两个对象相等(转成字符串判断)
- 37.如何生成100个元素为1的数组?
- 38.typeof和instanceof的区别
- 39.字符串翻转
- 40.手写promise.race
- 41.js数组哪些方法改变自身,哪些方法不改变
- 42.说说Object.defineProperty
- 43.如何全部替代一个子串为另一个
- 44.0.1+0.2不等于0.3?
- 45.类数组转数组
- 46.//TODO
- 47.js数组去重(原生js)
- 48.什么是作用域?什么是作用域链?
- 49.定时器准时吗
- 50.promise.all错误处理
- 51.异步编程的实现方式
- 52.js如何实现多线程
- 53.js 模块化机制
- esm特性
- 54.剪贴板相关操作
- 59.js中的数组特点|数组如何存储
- 60.input事件和change事件区别
- 61.with作用
- 62.e.target和e.currentTarget区别
- 63.json相关
- 64.剩余参数和arguments对象区别是什么
- 65.数组转树
# JavaScript
# 概念
- 基础类型赋值只改变本身,引用类型赋值全部修改(栈内存中保存的地址指针指向的是堆内存中同一个位置)
- 所有微任务总会在下一个宏任务之前全部执行完毕,直至微任务队列清空,过程中如果有微任务产生微任务会继续执行新的微任务
- alert弹出的数据都会转化成字符串
- js是单线程,浏览器是多进程
- JSON.stringify会过滤掉undefined,不会过滤掉null
# 1.说一下闭包/闭包解决了什么问题?
闭包就是能够读取其他函数内部变量的函数(函数执行会形成私有上下文,这种保存私有变量的机制就是闭包) 用来解决函数外部因作用域访问不到函数内部的问题。 (即子函数在外部调用,子函数所在的父函数的作用域不会释放) (函数作用域是独立的,封闭的。但是闭包是能够读取其他函数内部变量的一个函数) 我们在函数中嵌套一个函数,里面的函数去访问外面函数的变量,被访问的变量会始终保存在内存中
function init() {
var name = "Mozilla";
// name 是一个被 init 创建的局部变量
function displayName() {
// displayName() 是内部函数,一个闭包
alert(name); //使用了父函数中声明的变量
}
displayName();
}
init();
2
3
4
5
6
7
8
9
10
# 不恰当使用闭包会造成什么问题
会造成内存泄漏(要把变量定义在函数内,而不是全局变量,这样程序执行完就会销毁变量)。 防抖和节流都有用到闭包
# 常见问题
// var变量提升相当于提前声明了i
// var i = 0;
for (var i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i) // 1s后输出6个6=>不能用const,用let是1s后输出0-5
}, 1000)
}
2
3
4
5
6
7
# 2.如何解决回调地狱?(3种方式)
promise,async/await,generator
# 3.说一下事件委托?
利用事件的冒泡机制来实现事件委托 不在元素上直接设置监听函数,而是在父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上的事件被触发。举例:ul,li监听。 ( 子元素事件冒泡到父元素,需要父元素也有该事件)
# 事件捕获和冒泡过程
- 事件捕获(老版本浏览器不支持,用得较少):从window一级一级往下,最后到元素接收到=》从外到内
- 事件冒泡(IE):从元素一级一级往上,到window接收=》从内到外
# 冒泡和捕获哪个先?
先捕获后冒泡
# dom事件流?
- window->document->body->div->body->document->window
- 事件流的典型应用就是事件委托。事件委托利用了事件冒泡,把事件加到父级上,就可以管理一类型的所有事件。 (ul和li,把事件加到ul上,可以监控到li)
# event.preventDefault和event.stopPropagation有什么区别
- event.preventDefault()可以阻止默认事件但是允许冒泡事件
- event.stopPropagation()可以阻止冒泡但是允许默认事件
# *浏览器的事件模型?
- 事件模型的过程主要分三个阶段:捕获阶段,目标阶段,冒泡阶段
- addEventListener(第三个参数如果是true,就代表在捕获阶段执行,否则在冒泡阶段执行=>对应useCapture:true) =>第三个参数也可以是个options
# 4.说说深浅拷贝?
(深浅拷贝区别是对于引用类型如object,array而言的)
- 深浅拷贝,基本类型的时候,改变都不会使原数据改变。
- 引用类型,浅拷贝改变会使原数据改变,深拷贝不会。 (浅拷贝和赋值也有区别,赋值和原数据指向的是同一对象,浅拷贝不是)
- 因为引用类型,浅拷贝拷贝的是内存地址,深拷贝拷贝的是值,但是内存地址不同,所以不会。
- 浅拷贝实现是通过对对象各个属性依次进行拷贝,拷贝的是引用
# 浅拷贝方式
- Object.assign() =》当对象只有一层是深拷贝
- es6的扩展运算法...
- slice() // 通过对象序列化和字符串反序列化也可实现
# 深拷贝实现是通过递归的方式将值进行拷贝
深拷贝方式:
- JSON.stringify() 函数,日期,正则在JSON.stringify时都会出现问题
- 遍历递归来拷贝(非原型属性)
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
2
实现浅拷贝
function clone(target) {
if (target === null) {
return null;
}
// 判断原来是数组还是对象
let cloneTarget = Array.isArray(target) ? [] : {}
for (const key in target) {
if (target.hasOwnProperty(key)) { // 判断是否存在该属性,不包括原型链
cloneTarget[key] = target[key]
}
}
return cloneTarget;
}
2
3
4
5
6
7
8
9
10
11
12
13
实现深拷贝
function deepCopy(obj) {
var result = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
result[key] = deepCopy(obj[key]); //递归复制
} else {
result[key] = obj[key];
}
}
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
// 利用 WeakMap 解决循环引用
let map = new WeakMap()
function deepClone(obj) {
if (obj instanceof Object) {
if (map.has(obj)) {
return map.get(obj)
}
let newObj
if (obj instanceof Array) {
newObj = []
} else if (obj instanceof Function) {
newObj = function() {
return obj.apply(this, arguments)
}
} else if (obj instanceof RegExp) {
// 拼接正则
newobj = new RegExp(obj.source, obj.flags)
} else if (obj instanceof Date) {
newobj = new Date(obj)
} else {
newObj = {}
}
// 克隆一份对象出来
let desc = Object.getOwnPropertyDescriptors(obj)
let clone = Object.create(Object.getPrototypeOf(obj), desc)
map.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key])
}
}
return newObj
}
return obj
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 5.freeze函数?
Object.freeze表示冻结该对象,被冻结的对象无法被修改(包括新增属性,删除属性,以及可枚举性,可配置性等),但是允许访问。var a = Object.freeze({})
# 6.执行上下文
- js用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。
- 当函数调用时,会入栈一个新的执行上下文,函数调用结束,这个执行上下文会出栈
# *7.js有哪些代码规范
- 尽量用===代替==,防止转换的时候出现预期外的异常
- switch 必须带有default
- if 使用大括号,不要省略
- 分清楚常量和变量=>const,let
- 使用分号(避免代码格式化的时候出现预期外的问题)
# 8.栈和堆的区别?
let a = 20; 都存在栈中 let a = {m: 29} a存在栈中,{m:29}作为对象存在堆中,当我们要访问堆内存中的引用数据类型,首先要从栈中获取该对象的地址引用。
- 栈存储基础数据类型,堆存储引用类型
- 栈按值访问,堆按引用地址访问
- 栈存储的值大小固定,会自动分配空间,堆存储的值大小不固定,可动态调整
# 堆内存?
- 不能直接访问和操作堆内存,只能操作对象在栈内存的引用地址,通过这个地址可以找到堆内存中的对象
- 比如引用类型的赋值操作,是把原来对象的栈内存地址拷贝一份给新的对象,他们都指向同一个堆内存对象,所以成功给这个对象赋值
# 9.让数组乱序/数组打乱?
_.shuffle([1, 2, 3, 4]);//需要通过第三方库lodash
function shuffle(arr) { return arr.sort(() => { return (Math.random() - 0.5); }); }
# 10.什么是数组扁平化?
数组扁平化就是将一个多维数组转换为一个一维数组。
- es6的flat arr.flat(Infinity) 数组才有的属性
- 数组转化成字符串 使用toString再用split变成以逗号分割的数组(再用map遍历转成数字返回) arr.toString().split(',')
- 循环递归=>循环判断子元素是否存在数组,如果是就把所有元素展开放到一个空数组中再重新赋值,直到没有数组 while(arr.some(item => Array.isArray(item)) { arr = [].concat(...arr) }
# flatMap
会返回扁平化深度为1的数组,与flat区别就是参数传的是一个函数
const arr1 = [1, 2, [3], [4, 5], 6, [7,8,9]];
console.log(arr1.flatMap(num => num)); // [1,2,3,4,5,6,7,8,9]
2
# 11.js基本数据类型?
- string,boolean,number,undefined,null,symbol(es6新增),(bigInt=>大于2^53 - 1的数,就是最大安全整数Number.MAX_SAFE_INTEGER)
- 引用数据类型:对象(Array,Math,Map,Set,RegExp),函数
- 转数字规则:null为0,undefined为NaN,symbol报错,字符串非数字(或进制)转NaN。NaN不等于自身
# typeof可以判断null吗
不能,null通过xx === null判断=》typeof null为null是早期的bug (typeof判断函数为function,其他都是Object)
# 判断一个数是整数?
- %1 判断是不是0
- Number.isInteger()
# 2^53次方怎么表示?
- 2 ** 53
- Math.pow(2,53)
# bigInt怎么表示?
整数后面加n即可
# isNaN和Number.isNaN区别?
(NaN通过isNaN(123)来判断(NaN指的是not a number,number的范围是-2^53~2^53)) isNaN会把参数转成数值,不能转换的都会返回true Number.isNaN会先判断是否是数字,是数字再判断是否是NaN
# Null和Undefined的区别?
- null表示定义了一个空值, 变量声明了没有赋值就是undefined
- null参与计算等于0,undefined参与计算返回NaN(Number)
# 12.数组常用方法?
push(),pop(),shift(),unshift(),splice(),sort(),map() 会改变数组的方法:push(),pop(),shift(),unshift(),splice(),reverse(),sort()
# 13.*js语言特性?
- 可以在浏览器运行
- 不用预编译,直接解析执行代码
# 14.改变this指向(apply,bind,call)?
call,bind,apply都可以,箭头函数和proxy也可以改变=》proxy指向代理对象
fn.call(obj, 1,2) call接收一个参数列表
fn.bind(obj,1,2)也是接收一个参数列表,但是返回的是一个函数
fn.apply(obj,[1,2])apply接收的是一个参数数组
2
3
# 总结
- call和bind都是接收一个参数列表,apply接收的是一个参数数组
- bind返回的是一个函数,需要执行才生效
# 15.const定义的数据可以改变吗?
- 定义的是基本类型不可以改变,定义的是引用类型就可以改变,比如对象
- const定义引用类型(对象,数组),不能修改指针指向的地址,但可以修改地址指向的数据
# 16.异步加载的几种方式?
- script的defer属性:defer属性规定是否对脚本执行进行延迟,直到页面加载为止。可以保证脚本按顺序执行(如果脚本不改变文档的内容,可以通过这种方式加快文档处理的速度,不是所有浏览器都支持)
- script的async属性:async是h5新增特性,只适用外部引入的脚本,一旦脚本可用会异步执行。不能保证脚本按顺序执行,不是所有浏览器都支持
- $(document).ready()。必须引入jquery
# defer和async区别?
js脚本分成加载、解析、执行几个步骤,脚本加载且执行会阻塞dom渲染(所以js一般放最后)
# 相同点
都是异步加载
# 不同点
- async加载完立即执行,可能会阻塞dom解析
- defer加载后延迟到dom解析完才执行(在domContentLoaded之前)
# 17.事件循环?| js的执行机制?
事件循环分成同步和异步=》微任务和宏任务都是异步的 (js是单线程,一次只能执行一个任务,执行机制就是事件循环。浏览器是多线程) (先执行一个宏任务=》)执行代码,过程中把宏任务和微任务分别放入各自的任务队列,等代码块执行完就立即执行微任务队列中所有的微任务, 当所有微任务都执行完就开始下一次宏任务
# 执行微任务过程中产生了微任务
同样会加到当前的微任务队列中,等微任务队列都清空了才开始下次宏任务
# 为什么区分微任务和宏任务?
微任务是线程之间的切换,不用进行上下文切换,一次事件循环可以完成所有微任务。
宏任务是进程之间的切换,每次需要切换上下文,一次事件循环只能执行一个宏任务。
目的为了实现插队。
宏任务:js脚本,定时器,IO操作,ajax=》宏任务不一定都是异步任务,如js脚本
微任务:Promise.then,catch,finally
# 为什么微任务会比宏任务先执行?
因为当主线程代码执行完后,在事件循环执行之前,会尝试dom渲染,微任务是在dom渲染之前执行,宏任务是在dom渲染完成后执行。
# *18.如何原生实现instanceof?
function fakeInstanceOf (instance, parent) {
if (typeof instance !== "object" && typeof instance !== "function") return false;
while (instance.__proto__) {
if (instance.__proto__ === parent.prototype) {
return true;
}
instance.__proto__ = instance.__proto__.__proto__;
}
return false;
}
2
3
4
5
6
7
8
9
10
# 19.什么是函数柯里化(bind方法就是)?
函数柯里化实际是一种转换,返回的是一个函数。是将多变量函数拆解为单变量的多个函数。f(a,b,c)=>f(a)(b)(c) a,b,c各自都是一个函数内的变量
# 20.typeof判断数据类型的原理
typeof是通过判断存储的机器码的第三位来进行类型判断(全0表示null,全0也表示对象,所以不能用typeof判断null)
# 21.this关键字在不同环境下的指向?
this在浏览器指向window
普通函数中的this指向window
定时器的this指向window
箭头函数的this取决于外部环境
事件中的this指向事件的调用者
构造函数和原型对象中的this都是指向new出来的实例对象
类中的this指向构造器new出来的实例对象
谁触发函数,函数里的this就指向谁
fun() // window.fun()里面的this指向window
- 多层对象调用,函数被外层对象调用,指向的也是上一级对象
var obj = {
method: {
fn: function() {
console.log(this)
}
}
}
obj.method.fn() // 指向method
2
3
4
5
6
7
8
- 箭头函数调用,箭头函数本身没有this,实际调用的是上一级的this
var obj = {
fun: ()=> {
console.log(this)
},
fun2: function() {
console.log(this)
}
}
obj.fun() // window
obj.fun2() // obj
2
3
4
5
6
7
8
9
10
# 22.原型和原型链(构造函数<==>原型 实例=>原型)
构造函数通过prototype指向原型,原型通过constructor指向构造函数,原型(Parent.prototype)通过_proto_指向原型的原型(Object.prototype)
# 原型链
当访问一个实例对象的属性时,如果本身没有这个属性,就会去原型对象中查找,一直找到Object为止,原型间一层层的关系就构成了原型链。最终指向null (不是所有对象都有原型(通过对象创建的对象都有原型,空对象及Object.prototype(原型的最顶端)没有原型)) 实例(p)=>原型(Parent.prototype)=>原型(Object.prototype)=>null // 通过_proto_一层层往上找
# Object.create和原型链(MDN)
- object.create(proto)是创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
- 每个对象拥有一个原型对象。对象以原型为模版,继承了方法和属性。原型对象可能也有原型,一层一层以此类推,就构成了原型链
- 这些属性和方法定义在构造函数的prototype中,而不是对象实例本身
- js通过在对象实例和构造器之间建立一个链接_proto_在构造器中找到这些属性和方法
- 每个实例对象都从原型中继承了一个constructor属性,指向了构造函数
- prototype是内部属性,外部无法直接访问。但是继承者的p._proto_和被继承的Parent.prototype指向统一对象
- 顶层原型对象是object,object的原型对象是null(实例的_proto_等于原型的prototype)
- js中的继承通过原型链继承实现,所以js是基于原型的语言。java是面向对象语言,创建对象后会把类的属性和方法复制到对象中
- 函数默认有prototype属性,prototype中有constructor属性,指向函数本身
# 构造函数与普通函数最核心区别
构造函数通过new来调用,普通函数不需要new (opens new window)
# 23.js是单线程,单线程有什么好处?
不用在意线程状态同步问题,没有死锁的情况。
# 24.querySelector返回值是什么?querySelectorAll呢?
querySelector返回的是匹配的第一个元素,querySelectorAll返回的是一个nodeList
for (var div of divs) { 遍历nodeList
div.style.color = "blue";
}
2
3
# 25.函数传参是引用吗?修改形参会影响实参?
- 函数传参是引用。 不影响。会开辟一个新的内存地址指向这个参数,修改的是这个新地址指向的值。
- 基本类型存储在栈中,引用类型存储在堆中,引用类型存储的是一个指针,指向存储对象的内存地址。
- 基本类型的值可以直接访问,引用类型的值通过指针去访问(js不允许直接访问堆内存中的位置)。
# 26.如何判断变量是否是数组
arr instanceof Array
Object.prototype.toString.call(arr) === '[object Array]' //同样也可以用来判断对象类型
Array.isArray(arr)
2
3
# 27.如何把对象转成key/value的二维数组
object.entries({a:3})
# 28.说几种遍历数组的方法
forEach(缺点中途无法退出),map,some,every,filter,for of,
# 29.js的执行过程
- 词法分析=》输入的字符串会变成token(var a = 2 会被分割成4个单元 var|a|=|2)
- 语法分析=》token解析成抽象语法树
- 解释执行-=》(后续遍历AST)会在执行上下文执行
# 30.一个超长字符串能存在栈内存中吗?
不能,因为字符串数据一直都是存在堆中的,栈只存了地址指针 数字=》小整数存在栈中,其他存在堆中 只有固定大小的对象和值才能在栈上分配空间,string内部是可变的字符数组
# 31.for of | for in实现原理
都是基于js的迭代器实现 for in是利用了js中的in操作符,当使用in在某个对象上,如果该对象中存在此属性的原型链就会返回true,否则返回false for of是基于js的生成器(generate)实现
# 32.new一个对象发生了什么?
- 新建一个空对象
- 为新对象添加属性_proto_并指向构造函数原型
- 绑定this,执行构造函数
- 如果构造函数没有返回对象,就返回这个新对象
# 手写new
function create() {
// 创建⼀个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执⾏构造函数
let result = Con.apply(obj, arguments)
// 如果构造器没有手动返回对象,则返回第一步的对象
return typeof result === 'object' ? result : obj
}
2
3
4
5
6
7
8
9
10
11
12
# 33.==和===有什么区别?
前者只要求值相等,后者要求值和类型都要相等
# 34.实现一个同步的sleep函数
function sleep(ttl) {
const now = Date.now();
ttl *= 1000;
while (Date.now() - now < ttl) {}
}
2
3
4
5
# 35.字符串去重
const str = 'aaavvvdwww'
var rs = [].filter.call(str, (item, index, origin) => origin.indexOf(item) == index).join('');
item当前字符、index当前字符索引、origin原字符串
2
3
let newArr = [...new Set(str.split(""))].join(""); 最优解
let newArr = Array.from(new Set(str.split(""))).join("")
2
# 36.怎么判断两个对象相等(转成字符串判断)
JSON.stringify(obj)==JSON.stringify(obj2)
# 37.如何生成100个元素为1的数组?
- Array(100).fill(1); => 相类似的是Array(100).map(x=>1)将会创建一个稀疏数组 [empty * 100]
- Array.from(Array(100), x => 1); | Array.from({length: 100}, x=>1)
- Array.apply(null, Array(100)).map(x => 1);
# 38.typeof和instanceof的区别
- typeof判断基础数据类型,null除外
- instanceof判断引用数据类型
# instanceof原理=》基于原型链
内部通过原型链的方式来判断是否为构造函数的实例
# 手写instanceof
let myInstanceof = (target,origin) => {
while(target) {
if(target.__proto__===origin.prototype) {
return true
}
target = target.__proto__
}
return false
}
let a = [1,2,3]
console.log(myInstanceof(a,Array)); // true
console.log(myInstanceof(a,Number)); // false
2
3
4
5
6
7
8
9
10
11
12
# typeof和instanceof常见判断
- typeof判断引用类型除函数都是object
- instanceof不能判断基本类型 111 instanceof Number =》 false
# 39.字符串翻转
let a = 'test'
console.log(a.split("").reverse().join(""))
2
# 手动实现reverse函数
function reverse(s) {
let r = "";
for (const c of s) {
r = c + r;
}
return r;
}
2
3
4
5
6
7
# 40.手写promise.race
Promise.race = (promiseArray) => {
return new Promise((resolve, reject) => {
promiseArray.forEach((item) => {
Promise.resolve(item).then(
(val) => {
resolve(val);
},
(reason) => {
reject(reason);
}
);
});
});
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# 41.js数组哪些方法改变自身,哪些方法不改变
- 不改变原数组的方法:concat/join/map/forEach/filter/slice/findIndex
- 改变原数组的方法:push/unshift/pop/shift/sort/splice/reverse =>就是vue中重写的数组
# 42.说说Object.defineProperty
// 普通
let o = {};o.a=3;
// Object.defineProperty
let o = {};
Object.defineProperty(o, 'a', {
configurable: false, // 是否可删除,默认false删除无效
enumerable: false, // 是否可枚举 默认false
writable: true, // 是否可更改,默认false修改无效
value: 3 默认undefined
})
// 可拦截对象属性进行get,set操作
Object.defineProperty(o, "a", {
configurable: false,
enumerable: false,
get() {
return this._a;
},
set(a) {
this._a = a * 10;
},
});
o.a=1; // 赋值调用set方法,取值调用get方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 43.如何全部替代一个子串为另一个
- str.split('foo').join('bar')
- str.replaceAll('foo', 'bar'),在ESNext中,支持性不好
# 44.0.1+0.2不等于0.3?
计算机对于浮点数无法准确表达为二进制,此时再转成10进制就不是我们预期的结果0.2+0.4,0.7+0.1
# 45.类数组转数组
类数组指类似数组的对象。有length属性 (key是0,1,2这样的,类似数组的下标。常见的有arguments) Array.from(arr)可以将类数组转数组
# 46.//TODO
# 47.js数组去重(原生js)
Array.from(new Set(arr)) arr.filter((item, index) => { // 返回当前元素在数组中第一个索引,重复出现的返回都是false,就被过滤掉了 return arr.indexOf(item, 0) === index; })
# 48.什么是作用域?什么是作用域链?
作用域就是变量的可用范围。分成全局作用域,块级作用域({}只在花括号内) 当访问一个变量,首先会在当前作用域查找,找不到就会去上一级作用域查找,直到全局作用域,这个过程就是作用域链
# js有哪些块级作用域
let,const,以及try-catch的catch语句
# 49.定时器准时吗
定时器实际可能不准时,受事件循环影响
# 为什么setTimeout有最小时延4ms
(不同浏览器的最小时延不一致,chrome最小时延是1ms)定时器嵌套的层级很多超过了5层那么最低是4ms 设置延迟过低会导致 CPU-spinning(会导致计算机没办法进入低功耗模式,这样耗电就比较严重),经过测试后选定了最小时延4ms
# 如何解决最小时延
可以通过window.postMessage()=>也是宏任务,和addEventListener('message')结合模拟定时器功能
# 50.promise.all错误处理
场景:获取多个用户的信息,即使一个用户失败,也不影响其他用户信息的获取=》此时使用Promise.allSettled; 在所有promise都fulfilled或rejected后,返回一个数组
[
{ status: "fulfilled", value: result} // 成功响应
{ status: "rejected", reason: error} // 表示错误
]
2
3
4
原生错误处理:对每个promise进行错误处理,处理后返回res或err,最终到promise.all的还是一个数组,因为错误都被捕获了,所以不会一个reject就挂掉
async function handleAllFunc(fun){
try{
let res = await fun
return res
}catch(err){
return err
}
}
let requestArr = [p1(), p2(), p3()]
Promise.all(requestArr.map(item => handleAllFunc(item))).then(res => {
console.log('结果', res);
}).catch(err => {
console.log('错误', err);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
# 51.异步编程的实现方式
- 回调函数 简单,但不利于维护,耦合度高(回调地狱)
- promise 链式调用
- async 结构清晰更容易理解
# 52.js如何实现多线程
通过worker类 使用这个类的时候,会向浏览器申请一个新的线程来单独执行一个js
# 53.js 模块化机制
没有模块化:
- 变量和方法不容易维护,容易污染全局作用域
- 加载资源通过script从上到下 有模块化就解决了以上问题
# amd和cmd(sea.js推广过程产生)区别
- amd加载完模块立即执行,cmd加载完等require才执行
- amd是异步引入模块,cmd是同步引入模块
# umd
- umd兼容cjs和esm=》以amd为基础,加了特殊处理去兼容cjs (umd和esm前后端通用)
# cjs(commonjs)和esm(es module)区别?
- cjs是module.export导出,require导入,esm是export导出,import
- cjs是动态导入,esm是静态导入支持tree shaking
- cjs是被加载时运行,esm是编译时候运行。
- cjs输出的是值的浅拷贝,esm输出值的引用(输出值拷贝,输出后改变值,输出值不变。输出值引用会变)
- cjs会缓存,第一次被加载会完整运行整个文件并输出一个对象,拷贝在内存中,下次加载文件,直接去内存中取
# esm,cjs,amd,cmd,umd定义
esm和cjs都是用来模块规范=》导入导出 amd(异步模块定义),cmd和umd(都是通用模块定义)
# esm特性
自动采用严格模式,忽略use strict esm是通过cors去请求外部js模块 esm的script标签会延迟执行脚本
# 54.剪贴板相关操作
可通过Clipboard API来操作(是异步的)=》navigator.clipboard.writeText(text);
# 不支持clipboard API的浏览器
先通过Selection API选中,再通过document.execCommand('copy')进行拷贝
# 59.js中的数组特点|数组如何存储
- 有两种存储方式,快数组和慢数组。
- 初始化空数组会用快数组(默认空数组初始化大小为4),快数组使用连续的内存空间,当数组长度达到最大时,会进行动态扩容(老容量*1.5+16)。
- 转成慢数组,以哈希表(key,value)的方式存储数据(不需要连续的内存空间)
# 快数组什么时候转成慢数组
新加入的索引值比当前容量大等于1024或者快数组新容量是扩容后容量的三倍还多的时候都会由 快数组 转成 慢数组
# 60.input事件和change事件区别
- input:只要在input框内输入内容就触发
- change:只有input框内内容改变且失去焦点才触发
# 61.with作用
可以用来对对象的属性进行解析,但是不推荐使用,存在数据泄漏的问题 =>如果在with里面赋值一个不存在该对象上的属性,会自动创建全局属性,而不是给这个对象添加新属性
a.age = 18
a.name = "aa"
// 等价于
with(a) {
age = 18
name = "aa"
}
2
3
4
5
6
7
# 62.e.target和e.currentTarget区别
- 绑定的事件所在元素没有子元素,两者一样
- 事件绑定在父元素 e.currentTarget无论点击父元素还是子元素都正常执行(e.currentTarget都是parent) e.target点击父元素无错,点击子元素报错(因为事件绑定在父元素上,此时e.target是children)
# 63.json相关
以下代码输出什么?
const obj = {
a: 3,
b: 4,
c: null,
d: undefined,
get e() {},
};
console.log(JSON.stringify(obj));
// {"a":3,"b":4,"c":null} 对于undefined和function会被忽略掉
2
3
4
5
6
7
8
9
10
# 64.剩余参数和arguments对象区别是什么
剩余参数只包含没有对应形参的实参,而arguments对象包含了传给函数的所有实参 arguments对象不是一个真实数组,而剩余参数是真实的数组,所以剩余参数可以直接使用数组的所有方法,arguments需要转成真实数组才能使用 =>arguments是类数组
# 65.数组转树
const arr = [{id: 1, title: 1, parent: 0}, {id: 2, title: 2, parent: 1}, {id: 3, title: 3, parent: 1}]
function transTree(arr, id) {
const res = []
for (const item of arr) {
if (item.parent === id) {
res.push({...item, children: transTree(arr, item.id)})
}
}
return res;
}
console.log(JSON.stringify(transTree(arr, 0)))
2
3
4
5
6
7
8
9
10
11