面试题
js
1. 箭头函数
箭头函数和普通函数的区别是:
- 没有自己的this,它内部的this使用的是上层函数作用域或者全局的this
- 返回值有隐式的返回和显示的返回两者
- 没有arguments,需要使用…args的方式获取形参列表
- 没有prototype,所以不能使用new关键字。也不可以使用call等方法改变this的指向
let obj = {
fn: () => {
console.log(this)
}
}
obj.fn() // window
let obj = {
fn: () => 123 // 隐式的返回
}
console.log(obj.fn())
2. let 和 var 的区别
let和var都是js中用于声明变量的关键字,他们的区别在于:
-
let 有块级作用域的概念,var只有函数作用域或者全局作用域的概念
{ let a = 100 var b = 200 } console.log(b) console.log(a)
-
var有变量提升,在变量声明前使用,值为undefined。
-
let 在同一作用域内,不可以重复声明相同名字的变量
3. new关键字在new的时候做了些什么
实现new关键字
// 模拟new关键字,创建构造函数
function Person(name,age) {
this.name = name;
this.age = age;
}
function createInstance(contrustor,...args) {
// 1. 创建一个新的对象
let newObj = {}
// 2. 设置原型 将新对象的隐式原型,指向显示原型
newObj.__proto = contrustor.prototype
// 3. 改变构造函数的this指向
let result = contrustor.apply(newObj,args)
// 4. 如果是对象类型就返回,不是就返回新创建的对象
let reObj = (typeof result === 'object' && result !== null) ? result : newObj
return reObj
}
let person = createInstance(Person,'jjs',14)
console.log(person.name)
那么我们可以发现如果构造函数有返回值,且为对象时那么new
表达式会返回这个对象,而不是新创建的对象。这允许构造函数在特定情况下覆盖默认的实例化行为。
也就是
// 模拟new关键字,创建构造函数
function Person(name,age) {
this.name = name;
this.age = age;
return {
a: 1
}
}
function createInstance(contrustor,...args) {
// 1. 创建一个新的对象
let newObj = {}
// 2. 设置原型 将新对象的隐式原型,指向显示原型
newObj.__proto = contrustor.prototype
// 3. 改变构造函数的this指向
let result = contrustor.apply(newObj,args)
// 4. 如果是对象类型就返回,不是就返回新创建的对象
let reObj = (typeof result === 'object' && result !== null) ? result : newObj
return reObj
}
let person = createInstance(Person,'jjs',14)
let person2 = new Person('jtt',18)
console.log(person.name,person.a)
console.log(person2.name,person2.a)
这种情况下,只读的到返回的对象
4. call,apply和bind的区别
他们的作用是,改变this的指向
区别是:
call: func.call(thisArg, arg1, arg2, …),第一个参数为要指定的this,以分散的形式将参数传入
上面那个例子,我们要使用call的话:
let result = contrustor.call(newObj,…args)
apply:方法调用一个函数,其具有一个指定的this
值,以及以一个数组(或类数组对象)的形式提供的参数
bind: 形式和call相同 func.bind(thisArg, arg1, arg2, …),第一个参数为要指定的this,以分散的形式将参数传入
区别是:call
和 apply
都是直接调用 bind
则是用于创建一个新的函数,这个新函数被bind
调用时的this
被永久绑定,之后无论这个新函数如何被调用,this
都不会改变。
5. localStorage,sessionStorage cookie和session的区别
-
存储位置和生命周期
localStorage,sessionStorage cookie存储在客户端,也就是浏览器上面。session存在服务器上面
localStorage,持久化存储。sessionStorage,仅在当前会话页面有效,页面关闭sessionStorage就会被删除
cookie 根据设置的过期时间而定,如果没有设置,那么浏览器关闭,cookie删除
session 受到服务器的超时时间的影响
-
存储的内容大小
localStorage,sessionStorage 存储空间较大,cookie存储空间较小一般为4kb,session不占用客户端内存,与服务端存储空间相关
6. js创建对象的几种方式
-
使用字面量的方式
let obj = {}
-
使用Object.create() 静态方法以一个现有对象作为原型,创建一个新对象。
需要一个参数,构造函数。相当于newObj.__porto = Fn.prototype
var personProto = { greet: function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); } }; var person = Object.create(personProto); person.firstName = "John"; person.lastName = "Doe"; person.greet(); // 输出: Hello, my name is John Doe
-
构造函数,使用new 关键字来创建对象
-
工厂函数,一个函数的返回值为对象,然后就可以通过调用这个函数来批量的产生对象。
function createPerson(firstName, lastName) { var obj = {}; obj.firstName = firstName; obj.lastName = lastName; obj.greet = function() { console.log("Hello, my name is " + this.firstName + " " + this.lastName); }; return obj; } var person = createPerson("John", "Doe"); person.greet(); // 输出: Hello, my name is John Doe
-
通过new Obejct的方式
-
通过class的方式
7.this的指向
有三种情况
- 作为对象的属性调用,如果不是箭头函数,那么this指向调用的那个对象
- 在函数当中:普通函数,非严格模式下是window或者golab,严格模式下是undefined
- 构造函数中,是实例对象
闭包
在什么时候产生,在什么时候死亡
闭包就是,内部函数引用到了外部函数的变量,导致外部函数空间不能被释放
在内部函数创建时产生,在内部函数销毁时闭包死亡
8. 继承
-
可以通过原型链继承
Child._proto _= Parent.prototype
Child.prototype.constructor = Child
案例:
这样的话,只能继承的到原型上面的方法,不能动态的去指定属性的值
function Parent() { this.name = 'Parent'; } Parent.prototype.sayName = function() { console.log(this.name); }; function Child() { this.age = 10; } // 继承Parent Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; var child = new Child(); child.sayName(); // 输出: Parent
-
构造函数继承
通过new的方式
-
组合继承,将借用构造函数和原型链继承结合起来
function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { console.log(this.name); }; function Child(name,age) { Parent.call(this,name) this.age = age; } // 继承Parent Child.prototype = new Parent() Child.prototype.constructor = Child; let child = new Child("jjs",20); let child2 = new Child("jtt",18); child2.sayName(); // 输出: Parent
9. 什么是事件循环机制
用于协调异步任务的顺序,传递信息以及处理用户交互。
由调用栈,执行队列,事件循环,三部分组成。
当主线程的同步代码执行完毕后,事件循环会开始工作。它首先检查微任务队列,如果有微任务,则依次执行微任务队列中的所有任务,直到微任务队列为空。然后,事件循环会从宏任务队列中取出第一个宏任务执行。每个宏任务执行完毕后,会再次清空微任务队列,并继续执行下一个宏任务,如此循环往复。
node的事件循环与浏览器上面的事件循环有什么区别
-
实现方式不同
node.js : 主要是处理i/o操作和网络请求,基于Libuv库利用底层操作系统的多线程特性去实现。
浏览器:主要处理,事件,用户的交互。单线程的,通过异步回调和触发事件来进行异步操作
-
宏任务和微任务的处理
node.js : 宏任务包括整体的代码、setTimeout、setInterval、I/O操作等,微任务则包括Promise的回调和process.nextTick() 先执行宏任务,清空当前微任务队列。执行下一次tick
浏览器:浏览器的宏任务同样包括setTimeout、setInterval、DOM操作、AJAX请求等,但微任务则包括Promise的回调、MutationObserver以及queueMicrotask等,先执行微任务再执行宏任务
10. Promise.all()、Promise.race()的区别
promise有三种状态
- Pending(等待中):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成。
- Rejected(已失败):意味着操作失败。
基本用法:
let promise = new Promise(function(resolve, reject) {
// 异步操作
if (/* 异步操作成功 */) {
resolve(value); // 将Promise的状态从"pending"变为"fulfilled",并将异步操作的结果,作为参数传递出去
} else {
reject(error); // 将Promise的状态从"pending"变为"rejected",并将错误作为参数传递出去
}
});
// 处理Promise的结果
promise.then(function(value) {
// 成功时调用
console.log(value);
}).catch(function(error) {
// 失败时调用
console.log(error);
});
Promise.race方法接受多个promise,返回最快成功的一个结果
Promise.all 方法接受一个数组,里面的promise都成功则为成功,有一个失败就都不成功
11. 怎么判断一个变量的类型为null
-
使用====
let value = null;
if(value === null) {console.log(“为null”)}
-
使用原型
let value = null;
if(Obejct.prototype.toString.call(value) == [object null]) {console.log(“为null”)}
-
使用es6
let value = null;
if(Object.is(value)) {console.log(“为null”)}
使用typeof可以确定哪些变量的类型?
undefined,number,string,Boolean,
12.实现图片懒加载
使用IntersectionObserver这个api就可以轻松的实现它
官方文档:IntersectionObserver.root - Web API | MDN (mozilla.org)
提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
实例属性(配置项):
root
属性用来获取当前 intersectionObserver
实例的根元素。
rootMargin
属性 可以理解为,懒加载的预加载,比如图片的懒加载,不是等图片元素进入可视区域之后,才去加载,而是检测到,离进入可视区域,在一定范围内部的时候,就提前加载(叫做预加载吧)。
threshold
: 1.0, 阈值为 1.0 意味着目标元素完全出现在 root 选项指定的元素中可见时,回调函数将会被执行。
案例:
let options = {
root:null, // 设置为null,顶层视口
rootMargin: '0px', // 距离视口多少时显示
threshold: 1.0 // 标元素完全出现在 root 选项指定的元素中可见
}
function callback(entries) {
for (let entrie of entries) {
if(entrie.isIntersecting) {
let img = entrie.target // 将dom元素存放到了target属性上面
img.src = "./img/test.gif"
}
}
}
let ob = new IntersectionObserver(callback,options)
let data = document.querySelectorAll('img')
data.forEach(item => {
ob.observe(item)
})
自定义指令的形式
<script setup generic="T" lang="ts">
import {useRouter} from "vue-router";
import {Directive} from "vue";
import defaultImg from "../assets/image/vue.svg";
const router = useRouter()
const vLazy: Directive =(el, binding) => {
let ob = new IntersectionObserver((entries) => {
for (const entry of entries) {
if(entry.isIntersecting) {
let imgDom:HTMLImageElement = entry.target as HTMLImageElement
import(binding.value).then(res => {
imgDom.src = res.default
ob.unobserve(el)
})
}
}
},{
threshold: 1
})
ob.observe(el)
}
</script>
<template>
<div>
<h1>我是b组件</h1>
<button @click="router.back()">返回</button>
<hr>
<img :src="defaultImg" v-lazy="'../assets/1.png'">
<img :src="defaultImg" v-lazy="'../assets/2.png'">
<img :src="defaultImg" v-lazy="'../assets/3.png'">
</div>
</template>
<style scoped>
img {
width: 200px;
display: block;
}
</style>
13 深浅拷贝
-
浅拷贝,只复制了引用,没有复制值
let arr = [1,2,3] let arr2 = arr arr[1] = 10 console.log(arr2) let obj = { name: 'jjs' } let obj2 = Object.assign(obj)
-
深拷贝是复制真正的值
// 使用JSON.parse,JSON.stringify() let obj = { a: 1, b: 3 } let obj1 = JSON.parse(JSON.stringify(obj)) // 使用递归的方式 function deepClone(obj) { let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if(obj[key] && typeof obj[key] === 'object') { newObj[key] = deepClone(obj[key]) }else { newObj[key] = obj[key] } } return newObj } let obj2 = deepClone(obj)
14. script标签加什么属性可以变为异步加载
可以使用async和defer两个属性
不同的是:
使用defer
属性的脚本会等到整个文档被解析完成后,再按照它们在文档中出现的顺序执行
使用async
脚本将独立于页面的其他部分异步下载,并且一旦下载完成,浏览器就会暂停解析HTML文档来执行这个脚本不会按照它们在页面上出现的顺序执行
css
1. 如何快速实现居中
(1)给父元素添加display: flex;,目标元素: margin: auto;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
*{
padding: 0;
margin: 0;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
}
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
margin: auto;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
</div>
</div>
</body>
</html>
(2) 给父元素 display: flex;justify-content: center;align-items: center;
.container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
}
(3)利用position进行定位
*{
padding: 0;
margin: 0;
}
.container {
width: 100vw;
height: 100vh;
position: relative;
}
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
2. margin和padding有什么不同
margin 是☞,边框与外部元素之间的距离,即元素与其他元素之间的距离,不会影响到元素内部
padding 是☞, 边框与内容区的一个距离,它作用于元素本身,影响元素内部的空间
怪异盒子模型:
在怪异盒子模型中,高度和宽度是指的总高度和总宽度。如果设置了高宽为100,再设置边框为10px,padding为20px,那么内容区就只剩70px
开启怪异盒模型: box-sizing: border-box;
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
box-sizing: border-box;
padding: 20px;
border: 3px #bfa solid;
}
3. vw和百分比的区别
vw是相对于,视口的宽度进行计算,百分比则是相对于父元素的宽度进行计算
4. 如何让字体变的更小
(1)transform: scale(.8);
(2)利用rem,em来进行让字体相对于元素变换
rem: 相对于根元素进行计算
em: 相对于父元素进行计算
*{
padding: 0;
margin: 0;
}
html {
font-size: 20px;
}
.container {
width: 100vw;
height: 100vh;
position: relative;
}
.content {
width: 200px;
height: 200px;
box-sizing: border-box;
padding: 20px;
border: 3px #bfa solid;
font-size: 30px;
}
.content>:nth-child(1) {
font-size: .5rem;
}
.content>:nth-child(2) {
font-size: .5em
}
5.回流与重绘
回流就是:改变了dom的几何结构,浏览器需要对页面的位置分布重新进行计算并进行绘制
导致回流的原因:页面第一次渲染,元素尺寸发生变化,字体大小发生变化,删除或者新增dom
重绘就是:改变了dom的样式而没有改变dom的几何结构,比如:背景颜色等,那么浏览器就会为该元素重新绘制
导致重绘的原因:visibility属性被修改,样式发生变化
visibility
作用:控制元素是否可见,有三个值:visible,可见,hidden:不可见但依然占据空间,collapse:不可见也不占据空间
visibility和display的区别
使用display:none;之后元素并不可见也不占用空间,如果想要不可见但依然占据空间还是要使用visibility:hidden;
减少回流与重绘:
合并dom操作,可以搞一个异步的队列,将多次dom操作统一执行
6.overflow
用于指定内容超出大小后,浏览器如何去处理溢出的内容。
他一共有四个属性:分别是hidden,auto,scroll,visible,他们分别的功能是:
visible: 默认值,不管溢出。设置和没设置一样
hidden:直接裁掉溢出的部分,那么如果是下面还有内容的话,也会一起被裁掉
auto: 如果内容溢出,则浏览器会显示滚动条以便查看其余的内容。如果内容没有溢出,则不显示滚动条
scroll: 不管有没有溢出,都会显示滚动条。没有溢出的话滚动条是失活的。
.content {
width: 200px;
height: 200px;
box-sizing: border-box;
padding: 20px;
border: 3px #bfa solid;
font-size: 30px;
overflow: auto;
}
.content>:nth-child(1) {
font-size: .5rem;
height: 300px;
}
.content>:nth-child(2) {
font-size: .5em
}
/**
这里出现了元素溢出,然后就会出现滚动条,下拉就可以看到,第二个子元素的内容
*/
7.position
它用于控制元素再页面上的定位方式
有五个属性值,分别是static,relative,absolute,fixed,sticky他们的功能是
static:静态定位,默认值,按正常文档流排序
relative:相对定位,相对于其在普通流中的父元素进行定位,可以使用left,top,bottom,right进行调位置
absolute: 绝对定位, 相对于最近的开启了position且值不为static的元素进行定位。会脱离文档流
fixed:固定定位,相对于浏览器的窗口进行定位,无论页面如何滑动,该元素都会保持在相同的位置
*{
padding: 0;
margin: 0;
}
html {
font-size: 20px;
}
.container {
width: 100%;
height: 100%;
position: relative;
}
.content {
width: 200px;
height: 200px;
box-sizing: border-box;
padding: 20px;
border: 3px #bfa solid;
font-size: 30px;
position: fixed;
top: 0;
}
.content>:nth-child(1) {
font-size: .5rem;
}
.content>:nth-child(2) {
font-size: .5em
}
.longer {
height: 1300px;
background-color: whitesmoke;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<div class="content">
<p>我是相对于html的字体</p>
<p>我是相对于父元素字体</p>
</div>
<div class="longer"></div>
</div>
</body>
</html>
sticky: 粘滞定位,是一种特殊的定位方式,它结合了相对定位和固定定位的特点,粘性定位的元素在滚动到一定位置之前表现为相对定位,即元素按照正常文档流进行排列;当页面滚动到特定位置时,元素会“粘”在指定位置,表现为固定定位
修改一下即可看到效果:
position: sticky;
top: 0px;
8. float
浮动布局,允许一个元素脱离文档流,向左或者向右浮动
主要有三个属性值:none,left ,right
三个属性值的作用是设置不浮动向右或者向左浮动
*{
padding: 0;
margin: 0;
}
html {
font-size: 20px;
}
.container {
border: 2px solid skyblue;
padding: 5px;
}
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
float: left;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<div class="content">
</div>
</div>
</body>
</html>
这样的话,就开启了浮动,但是也带了一个问题,就是高度塌陷
所以就需要清除浮动所带来的影响,三种解决,1. 使用伪类。2. 父元素一起浮动,3. 开启bfc
什么是bfc
bfc是一个独立的渲染环境,在计算高度时,也会计算浮动元素的高度,所以就解决了高度塌陷的问题
在同一个bfc的元素外边距会重叠,可以触发不同的bfc去计算,解决外边距重叠的问题
主要作用就是解决上下外边距重叠和清楚浮动所带来的影响
*{
padding: 0;
margin: 0;
}
html {
font-size: 20px;
}
.container {
border: 2px solid skyblue;
overflow: hidden; /* 触发bfc */
padding: 5px;
}
.content {
width: 200px;
height: 200px;
background-color: aquamarine;
float: left;
}
vue
1. 什么是单向数据流
在 Vue 中,数据流主要遵循一个父到子的方向。父组件通过 props
向子组件传递数据,而子组件不能直接修改通过 props
接收的数据。如果子组件需要基于接收到的数据做一些修改,它应该通过事件(如自定义事件)来通知父组件进行更改,然后由父组件来决定是否更新数据
案例:
<script setup lang="ts">
import BaseRefAndReactive from "./components/BaseRefAndReactive.vue";
import {ref} from "vue";
const list = ref([1,2,3,4,5])
const handleClick = (name:string,...args) => {
console.log(name,"父------>")
console.log(args)
}
</script>
<template>
<BaseRefAndReactive title="这是标题" :list="list" @on-click="handleClick"></BaseRefAndReactive>
</template>
<style scoped>
</style>
<script setup lang="ts">
withDefaults(defineProps<{
title: string,
list: number[]
}>(), {
title: 'default title',
list: () => [1,2,3]
})
let emit = defineEmits(["on-click"])
const handleClick = () => {
emit("on-click", "我是子组件1",123,"as",{name:"张三"})
}
</script>
<template>
<div>
<h1>{{ title }}</h1>
<ul>
<li v-for="(item,index) in list" :key="index">{{ item }}</li>
</ul>
<button @click="handleClick">传值</button>
</div>
</template>
<style scoped>
</style>
2. vue router如何进行传参
提供了两种方式进行传参
-
query
查询参数是通过URL的查询字符串来传递的,它们会被附加在URL的
?
后面,并以&
分隔。这种方式适合非敏感数据的传递,如搜索词等。query必须要是一个对象
import {useRouter} from 'vue-router' const router = useRouter() router.push({ path: '/foo', query: { userId: 123 }}) // 结果的URL将会是 /foo?userId=123
接受参数
import {useRoute} from 'vue-router' const route = useRoute() created() { // 访问查询参数 console.log(route.query.userId) // 输出:123 }
-
动态路由匹配
当路由路径中包含动态片段时,如
/user/:id
,可以通过params
来传递参数,但注意,这种方式与query
不同,它不会在URL中直接显示参数。它通常与路由的嵌套或命名视图一起使用,并通过name
来指定路由,然后使用params
来传递参数。使用params必须使用name的形式,使用path的形式不可以
import {useRouter} from 'vue-router' import {ref} from 'vue' const userID = ref(1) function toDetails (item:Data) { router.push({ name: "B", params: { id: userID } }) }
获取:
import {useRoute} from 'vue-router' const route = useRoute() created() { // 访问查询参数 console.log(route.params.userID) // 输出:1 }
-
两者的区别
query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
query 在路由配置不需要设置参数,而 params 必须设置
query 传递的参数会显示在地址栏中
params传参刷新会无效,但是 query 会保存传递过来的值,刷新不变 ;
3. vue-router hash模式和history模式的区别
-
url的区别
hash模式:http://localhost:3000/#/router
会包含一个#,#后面是路由的路径
history模式:http://localhost:3000/router
不包含#,端口号后面既是路由的路径
-
实现的方式不同
hash模式:原理是 HTML5 的
window.location.hash
属性,这个属性值的改变(即 URL 中#
后面的部分)不会导致页面重新加载history模式:利用了 HTML5 History API 来实现 URL 的跳转而不重新加载页面
4. vuex
Vuex是一个状态管理模式和库,用于集中式存储管理Vue.js应用中的所有组件的状态
有五个核心api
- state,是Vuex中的基本数据对象,用于存储应用的状态,它类似于组件的data属性
- getters:类似于组件的计算属性,可以利用state中的值,计算出新的值
- mutations:用于修改state里面的数据,必须是同步的函数,通过commit方法调用mutations
- actions:,可以执行异步操作(如API调用),并在操作完成后通过commit方法调用mutations来修改stat
- Modules:Vuex允许我们将store分割成模块(modules)。每个模块拥有自己的state、mutations、actions和getters等
5. public文件夹下的文件和src下的assert文件夹下的文件有什么区别
public文件夹下的文件在打包的时候不会被打包,直接放在根目录下,可以直接去访问,assert文件夹里的文件会随着src文件夹一起打包。
typescript
1. type和interface的区别
-
使用场景的不同
- Type Aliases (type) 通常用于简单的类型定义,如字面量类型、联合类型、元组等。
- Interfaces (interface) 更适合定义对象的形状,尤其是当对象包含方法时。同时,由于支持继承,它更适合构建复杂、可扩展的类型系统。
-
声明合并
interface,同名的话,会将两个同名的interface进行合并而type却不可以