javascript面试题
# 深浅拷贝
浅拷贝
浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
浅拷贝的api有
Object.assignArray.prototype.slice()Array.prototype.concat()
深拷贝
深拷贝开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
_.cloneDeep()
JQuery.extend()
JSON.stringify() 但是这种方式存在弊端,会忽略
undefined、symbol和函数循环递归
<script> let person = { name: "false", age: "38", course: [ "react", "uni-app", ], book: { bname: "如何增发", price: "99" } } function deepClone(oldObj) { // if arry or obj? 创建对象格式的空值 let newObj = oldObj instanceof Array ? [] : {} // 遍历对象或者数组 使用 for in for (let key in oldObj) { // 判断当前遍历的是否为对象 if (typeof oldObj[key] === 'object') { // 如果是对象递归进行拷贝 newObj[key] = deepClone(oldObj[key]); } else { // 否则把属性直接拷贝到新对象 newObj[key] = oldObj[key] } // 循环结束后,返回新对象 } return newObj } let p = deepClone(person) console.log(p); </script>
区别
- 浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
- 但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
总结
前提为拷贝类型为引用类型的情况下:
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
# 构造函数
什么是构造函数
描述同一类型的所有对象的统一结构的函数
通用
new函数名 来实例化对象的函数叫做构造函数,构造函数名为大写开头有什么用
构造函数是为了对初始化的对象添加属性和方法用的
场景
为何使用:
- 用{}一次只能创建一个对象。 如果想创建多个相同结构的对象时, 代码就会很多重复! ——极其不便于将来的维护。
只要想反复创建多个相同结构,只是内容不同的对象时,都用构造函数。
代码演示
function 类型名(形参1,形参2,形参3){ this.属性名=形参1; this.xxx=xxx; this.方法名=function(){} } let 对象名= new 类型名(实参值1,实参值2,...)
# 原型与原型链
原型:
什么是原型、
替所有实例化的对象集中保存共有属性值和方法的父对象
原型链
是由多级父对象逐级继承形成的链式结构
保存着: 一个对象可用的所有属性和方法
控制着: 属性和方法的使用顺序:
就近原则: 先子级后父级
重写
- 是什么:在子对象中定义一个和父对象中成员同名的自有成员、
- 何时:从父对象继承来的个别成员不好用时,就可以在子对象中定义同名成员,来覆盖父对象中的同名成员。
# this指向问题
this指向谁是在函数调用时指定的,this保存在函数作用域对象中
- 普通函数中的this==>
window- 严格模式下的函数this==>
undefined
- 严格模式下的函数this==>
- 定时器中的this==>
window - 自调用函数中的this==>
window - 方法中的this==>
.前对象 - 构造函数中的this==>
实例化后的对象- 原型对象中的this==
实例化后的对象:此处不能为箭头函数,箭头函数this指向外部作用域
- 原型对象中的this==
# 改变this指向方法
call
调用一次,临时替换一次this;可传递多个参数;
函数.call(要替换this的对象,实参,参数n)- 自动调用该函数
- 将函数的this替换为该对象
- 传递实参
fun2.call(lilei, ...["miku", "gumi"]) //使用call传递多个参数时,如果为数组,需要使用扩展运算符数组打散apply
apply的实参值是放在数组中的(必须为数组),自动拆散数组,提供this,也是临时替换
apply(lilei,['miku','gumi'])bind
创建函数副本,永久绑定this,
var 新函数=原函数.bind(替换this的对象,实参1,实参n)执行过程:
- 创建一个和原函数一模一样的新函数副本
- 永久替换新函数种的this为指定对象
- 永久替换部分形参变量为固定的实参值
如何选择(对比与区别)
- 只在一次调用函数时,临时替换一次this: call
- 既要替换一次this,又要拆散数组再传参: apply
- 创建新函数副本,并永久绑定this: bind
# null与undefined
undefined表示 “无”的原始值
let a; //已经声明,未赋值let obj ={} console.log(obj.name) //对象的某个属性不存在function fn(a,b){ console.log(a,b) //函数调用时缺少参数 } fn(1)function fn(){ console.log('hellow') } console.log(fn()) //函数的默认返回值
null 表示 “无” 对象 0
//手动释放内存 let obj = {} obj = null作为函数的参数(不是对象)
原型链的顶端
# 闭包
# 闭包是什么
函数里面返回一个函数,这个函数又使用了外层函数的变量
# es6新特性
# 1. let const 与var
let
为啥会出现let代替var?
var声明提前,扰乱正常的代码执行顺序
var变量覆盖,同名变量会覆盖之前定义的
var没有块级作用域,代码块内的变量会超出代码块范围,影响外部的变量
let的优点:
- 不会被声明提前-保证程序顺序执行
- 让程序块,也变成了块级作用域-保证块内的变量,不会影响块外的变量。
const
用于定义常量,在定义后变量的值/地址不能改变
const声明后必须赋值
没有变量提升
模板字符串
是什么
- 模板字符串是 支持换行、动态拼接内容的特殊字符串格式
${}中可以放什么
变量、算术计算、三目、对象属性、创建对象、调用函数、访问数组元素 ——有返回值的合法的js表达式
# 箭头函数
与普通函数区别:
- 箭头函数没有arguments对象,使用reset
- 几乎所有匿名函数都可用箭头函数简化 箭头函数是对大多数匿名函数的简写
箭头函数this指向问题
箭头函数的this指向的是外部函数
对象中的方法能不能使用箭头函数?
不行,对象中箭头函数的this指向的是外部函数,所以指向window
解决:直接方法名括号fun(){}
箭头函数this指向外部,还有作用域吗?
箭头函数只让this,指向外部作用域的this
箭头函数内的局部变量,依然只能在箭头函数内使用。出了箭头函数不能使用! 所以,箭头函数依然是作用域!只不过影响this而已!不影响局部变量
箭头函数可以使用call apply bind替换this吗
箭头函数底层相当于.bind()绑定外层this
- call不能使用
- apply不会报错,但this依旧指向window,数组参数能够传递
- bind不会报错,但this依旧指向window,参数能够传递
# 参数增强
参数默认值
设置了形参默认值之后,如果没有传递实参,则使用默认值
function(形参=默认值,形参=默认值...){}
剩余参数
let 函数名=>(...数组名arr)=>{}
传进来的参数会自动添加到数组arr中去
生成的数组是纯正的数组类型,所以使用数组家所有函数
不确定实参值个数时D:\web-note-DN\web-note\All-专题总结\01demo.html, 都可用”…数组名”代替arguments接住所有或剩余实参值。
展开运算符
调用函数时:
函数名(...数组arr)
将数组拆散多个实参值,依次传给函数的每个形参变量
区别剩余运算符和展示运算符
- 1). 定义函数时,fun(...arr){}形参列表中的...,表示收集参数
- 2). 调用函数时,fun(...['miku','gumi'])实参列表中的...,表示拆散传参
# class
旧js中,构造函数和原型对象是分开定义的,不符合封装的概念,由此催生出了class
如何通过class定义类:
用class{}包裹原构造函数和原型对象方法
使用constructor(){}定义构造函数
下面add(){}方法自动成为Fun类下的原型方法
class Fun { constructor(ename, eage) { this.ename = ename; this.eage = eage; } add() { console.log(this.eage) console.log(this.ename) } }静态属性
es6放弃了原型对象中保存共有属性的方式,改为静态属性
static 共有属性名=属性值
标有
static的静态属性,都是保存在构造函数对象身上。如何访问:类型名.共有属性名
# 新增方法
# filter
let arry = [1,2,3,4,5,6,7]
//current 当前值; index 当前下标; current这个数组
console.log(arry.filter((current,index,array)=>current>6)) //7
# forEach
没有返回值
不能用break中断
遍历的是value
let arry = [1,2,3,4,5,6,7]
arry.forEach((current,index)=>{
})
# map
有返回值(数组) 默认每一项return是undefined
不能break中断
let arry = [1,2,3,4,5,6,7]
arry.map((current,index)=>{
return 'hellow'
})
# 解构赋值
数组解构
var [变量1, 变量2, ...] =数组、
如果想省略掉数组中某个下标的值,使用逗号省略掉某一位即可
对象解构
从一个大的对象中只提取出个别属性值单独使用 var { 属性名1:变量1, 属性名2:变量2,... }=对象
每当对象中:左边的属性名和:右边的变量名刚好相同时,其实只写一个名字即可!
var { name,age}=lilei;
参数解构
基于函数的参数进行解构
声明时 function fun({属性名1:参数1,属性名2:参数2})
调用时 函数名(属性名:实参值,属性名2:实参值2)
# 如何交换两个变量的值,不声明第三个变量
let a = 1;
let b = 2;
[a,b]=[b,a]
# es6快速去重
const arry =[2,2,2,3,4,5,6,6,7]
let item = [...new Set(arry)]
console.log(item) //[2, 3, 4, 5, 6, 7]
# Promise异步的理解
const promise = new Promise((resolve,reject)=>{
console.log(1); //同步队列
resolve()
console.log(2) //同步队列
})
promise.then(res=>{//先执行之后的js代码 在执行异步执行then
console.log(3);
})
console.log(4);
//1 2 4 3
# 递归求1-100的和
const fun = (num1, num2) => {
const num = num1 + num2;
if (num2 + 1 > 100) {
return num;
}
return fun(num, num2 + 1);
};
let sum = fun(1, 2);
console.log(sum); 5050
# 事件
pc事件名
| 事件名 | 触发方式 | 备注 |
|---|---|---|
| click | 单击放手时 | pc单击事件 |
| input | 当一个input,select,textarea元素的 value 被修改时,会触发 input 事件。 | 获取value:e.srcElement.value |
事件对象
- 自动赋值给在事件处理函数的第一个参数 一般写成e或者event
事件对象属性
| 属性 | 描述 |
|---|---|
| target | 事件对象 (位于DOM树最上面的元素). |
| screenX | 点击事件发生时鼠标对应的屏幕x轴坐标 |
| screenY | 点击事件发生时鼠标对应的屏幕y轴坐标. |
| clientX | 点击事件发生时鼠标对应的浏览器窗口的x轴坐标. |
| clientY | 点击事件发生时鼠标对应的浏览器窗口的y轴坐标. |
取消默认事件:e.preventDefault();
禁止事件冒泡:e.stopPropagation();
移动端事件
| 事件名 | 触发方式 | 备注 |
|---|---|---|
| touchstart | 触摸开始(手指放在触摸屏上) | |
| touchmove | 拖动(手指在触摸屏上移动) | |
| touchend | 触摸结束(手指从触摸屏上移开) | |
| touchenter | 移动的手指进入一个dom元素。 | |
| touchleave | 移动的手指离开一个dom元素。 | |
# 流程控制
条件判断
ifswitch
# 数据类型转换语法糖
数字与字符串互转
+'12' //纯数字字符串转为number类型 失败为NaN
12+ '' //数字转字符串 ‘12’
# 空值判断和常用操作语法糖
//常量空值判断
0 NaN undefined '' false //结果均为false
!!value //false
if(!value) return //为空停止执行下去
value && fun() //不为空就执行 fun()
// 对象值不存在时为undefined 所以以上也能用于判断对象值,但要考虑是否为0或者''时是否会造成程序异常
res.key|| '' // 如果res对象下key为空值 将自动返回第二个值
# 日期与monet.js
# 原生js(本机时间可以被更改)
//返回本机时间(以下均为),未格式化
new Date() //Tue Jan 04 2022 11:35:25 GMT+0800 (中国标准时间)
//返回当前天数
new Date().getDate() // 4
# Promise
es6新特性
promise出现前提:回调地狱
- 在异步任务的结束调用传入的函数
- 调用函数 ,传入函数并等待异步任务执行完调用第二个函数
如果要先后执行的任务多了!就会形成很深的嵌套结构——>回调地狱。
极其不优雅,极其不便于维护
promise三种状态:
- pending挂起
- fulfilled成功
- rejected出错
promise使用场景:
今后,只要多个异步任务必须顺序执行时,
都要用promise技术来代替回调函数方式 当一个任务比较耗费时间时,避免阻塞,就可以使用promise
Promise 处理始终是异步的,因为所有 promise 行为都会通过内部的 “promise jobs” 队列,也被称为“微任务队列”(ES8 术语)。
因此,.then/catch/finally 处理程序(handler)总是在当前代码完成后才会被调用。
如果我们需要确保一段代码在 .then/catch/finally 之后被执行,我们可以将它添加到链式调用的 .then 中。
如何使用
function fun() {
return new Promise((resolve, reject) => {
setTimeout(() => { //异步耗时任务
console.log("异步任务");
if (Math.floor(Math.random() * 10) >= 5) {
resolve('成功') //执行成功的回调
} else {
reject('失败') //执行失败的回调
}
}, 2000)
})
}
fun().then(res => {
console.log(res); //成功
}).catch(err => {
console.log(err); //失败
}).finally(()=>{
console.log() //无论成功还是失败都会执行 (用于无论请求是否成功都会停止loding或者按钮加载状态)
})
每个promise对象都有三个方法then()和cacth(),finally()
调用resolve()表示任务执行成功,Promise的状态置为
fullfiled(成功)new promise()自动调用then()函数执行下一项任务
调用reject()表示任务执行失败,Promise的状态置为
rejected(出错)new Promise()会自动调用.catch()执行错误处理代码
finally()在prmose任务执行后执行,无论prmose的结果如何都会执行
# 链式调用
你可以重复调用.then 方法(catch/finally都可以,但用的少)处理成功后的结果,可以返回新值,接下来的.then()方法会拿到这个新值
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
将多个.then添加到一个promise上,并不是链式调用,以下案例会分别执行
promise.then(function(result) { alert(result); // 1 return result * 2; });
promise.then(function(result) { alert(result); // 1 return result * 2; });
promise.then(function(result) { alert(result); // 1 return result * 2; });
# Prmose并行运行异步任务
如何使用Promise处理多个任务,并且任务之间需要前个任务的结果,每个任务还是耗时的?
案例:使用定时器模拟耗时任务,每个任务都会在前个任务执行完在执行下一个
function fun() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
})
}
fun().then(res => {
console.log(res); //2
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res*2)
},2000)
})
}).then(res=>{
console.log(res); //4
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res*2)
},2000)
})
}).then(res=>{
console.log(res); //8
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res*2)
},2000)
})
})
# Promise错误捕获
将
.catch()放置在链的末尾,一旦任意一个Promise被reject,.catch()都会捕获它Promise中隐式try...catch
//一旦执行到 throw new Error() Prmoise的状态会直接失败,并将错误信息传递给catch(err=>{...进一步处理}) new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(error=>{...进一步处理]}); // Error: Whoops!如果在链式调用中 其中一个.then()失败了,将停止继续运行下一个
.then,并且将错误传递给最近的.catch()中如果没有
.catch()捕获错误,将中止异步任务的继续执行,反之,如果在.catch()后还有.then()任务的存在,将继续执行下去每个promise链式调用的最后都建议使用.catch()捕获错误
# Promise Api
prmoise all
并行执行任务,并且等到所有任务执行完毕后再进行处理
语法:接受多个prmoise对象,并且需要包含在数组中,会等待所有prmoise执行完毕后,才会Promise的状态才会改变为resolve
Promise.all的.then参数为每个Promise成功后传递的参数的数组,并且他们的在数组中的位置是不变的,无论任意一项任务执行有多缓慢
let promise = Promise.all([ new Promise(resolve=>{setTimeout(()=>resolve(1),5000)}), new Promise(resolve=>{setTimeout(()=>resolve(2),2000)}), new Promise(resolve=>{setTimeout(()=>resolve(3),2000)}), ]).then(res=>{ console.log(res); //[1,2,3] });错误机制:如果任意一个 promise 被 reject,由
Promise.all返回的 promise 就会立即 reject,并且带有的就是这个 error。Promise.allSettled
例如,我们想要获取(fetch)多个用户的信息。即使其中一个请求失败,我们仍然对其他的感兴趣。让我们使用
Promise.allSettled:let promise = Promise.allSettled([ new Promise(resolve=>{setTimeout(()=>resolve(1),5000)}), new Promise((resolve,reject)=>{setTimeout(()=>reject(2),2000)}), new Promise(resolve=>{setTimeout(()=>resolve(3),2000)}), ]).then(res=>{ console.log(res); //[{},{},{}] });不同的是 allSettled resolve 的res是数组,并且每个都包含了执行promise的结果的对象,无论结果是成功还是失败
0: {status: 'fulfilled', value: 1} 1: {status: 'rejected', reason: 2} //失败的结果 2: {status: 'fulfilled', value: 3}Promise.race
与prmoise.all类似,但只会等待第一个 settled的Promise并获取结果(或error)
Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1Promise.resolve/reject
当一个函数被期望返回一个 promise 时,这个方法用于兼容性。
这里的兼容性是指,我们直接从缓存中获取了当前操作的结果
value,但是期望返回的是一个 promise,所以可以使用Promise.resolve(value)将value“封装”进 promise,以满足期望返回一个 promise 的这个需求。Promise.resolve(value)用结果value创建一个 resolved 的 promise。Promise.reject(error)用error创建一个 rejected 的 promise。
# async/await
Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
它可以将一个函数转换成Promise对象,总是返回Prmoise,其值自动被包装成resolved的promise中
# 微任务和宏任务
微任务
案例:
let promise = Promise.resolve(); promise.then(() => alert("promise done!")); alert("code finished"); // 这个 alert 先显示微任务包含哪些:Promise的.then/catch/finally处理程序
微任务队列:
当同步代码执行完才会执行微任务队列中的代码
或者,简单地说,当一个 promise 准备就绪时,它的
.then/catch/finally处理程序(handler)就会被放入队列中:但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码,它会从队列中获取任务并执行它。宏任务
宏任务包括
script(整体代码,最优先执行的代码) setTimeout setInterval I/O UI交互事件 postMessage MessageChannel setImmediate(Node.js 环境)总结
宏任务先执行,然后是微任务