Vue
Vue的生命周期有哪些,一般在哪一步发起请求及原因?
一般分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。
另有两个配合keep-alive组件使用:激活前/后
1. 创建前:beforeCreate:vue实例的挂载元素el和数据对象data都为undefined,还未初始化。
在当前阶段data、methodes、computed以及watch上的数据和方法都不能被访问。
2. 创建后:created:vue的数据对象data有了,el还没有。可以做一些初始数据的获取,
但当前阶段无法与dom进行交互,如果非要,可以通过this.$nextTick()异步来操作dom。
3. 载入前:beforeMount:vue实例的el和data都已经初始化了,但还是挂载之前为虚拟的dom节点。
当前阶段虚拟dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
4. 载入后:mounted:vue实例挂在完成。当前阶段真实dom挂在完毕,数据完成双向绑定,可以访问dom节点并操作。
5. 更新前:beforeUpdate:响应式数据更新时调用,发生在虚拟dom打补丁之前,
适合在更新之前访问现有的dom,比如手动移除已添加的事件监听器
6. 更新后:updated:虚拟dom重新渲染和补丁之后调用,新的dom已经更新,避免在这个钩子函数中操作数据,防止死循环
7. 销毁前:beforeDestroy:实例销毁前调用,this能获取到实例,常用于销毁定时器,解绑事件
8. 销毁后:destoryed:实例销毁后调用,所有事件监听器都会被移除,子实例也都会被销毁
9. 激活前:activated:keep-alive组件激活时调用
10. 激活后:deactivated:keep-alive组件停用后调用
第一次页面加载时会触发 创建前/后、载入前/后
keep-alive的使用
首先简述一下keep-alive的作用,kee-alive可以缓存不活动的的组件。
当组件之间进行相互切换的时候,默认会销毁,当重新切换回来时又重新初始化。
现在有需求切换回来不销毁组件,保持原来的状态,此时用keep-alive就可以实现了
tips:
Vue 3 中的 `<keep-alive>` 支持 `include` 和 `exclude` 属性,
允许有条件地缓存组件。这两个属性可以接受字符串、正则表达式或数组,以匹配组件的名称
生命周期钩子是如何实现的?
vue的生命周期钩子就是回调函数,创建组件实例的过程中调用对应的钩子方法。 核心是一个发布订阅模式,
将钩子订阅好,在对应的阶段进行发布
vue的父组件和子组件生命周期钩子执行顺序
父组件挂载一定是等子组件都挂在完成后,才算是父组件挂载完,所以父组件的mounted在子组件的mounted之后
渲染:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->子beforeMount -> 子mounted -> 父mounted
刷新:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
销毁:父beforeDestory ->子beforeDestory ->子destroyed ->父destoryed
父组件等待子组件完成后,才会执行自己对应完成的钩子。
vue组件间通信有哪几种方式?
有三种,一种是父子组件传值,一种是兄弟组件传值,一种是隔代组件传值
1. props/$emit 适用于父子传值
2. EventBus公交传值$emit/$on
3. $parent/$children访问父子
4. vuex数据管理传值
5. localStorage与sessionStorage组件传值
6. $attrs/$listeners隔代组件通信
7. provide/inject隔代组件通信
Vuex
Vuex是一个专门为vue开发的状态管理模式
1. state:储存状态(变量)
2. getters: 对数据获取之前的再次编译,可以理解为state的计算属性
3. mutations:修改状态的方法
4. actions:异步操作,然后调用mutations来修改状态
5. modules:store的子模块,让每一个模块拥有自己的state、mutation、action、getters,
使得结构非常清晰,方便管理。
vue-router中的query与params传参与接参
query:
传参:this.$router.push({path:'/xxx',query:{id:id}})
接参:this.$route.query.id
params:
传参:this.$router.push({name:'xxx',params:{id:id}})
接参:this.$route.params.id
params传参,push里面只能是name:‘xxx’,不能是path。因为params只能通过name来引入路由
query相当于get请求,页面跳转的时候,地址栏可以看见。
params相当于post请求,参数不会在地址栏中显示 传参是$router,接参是$route
vue-router有几种钩子函数,执行流程是怎样的?
钩子函数种类有 全局守卫,路由守卫,组件守卫
1. 导航被触发
2. 离开的组件调用beforeRouteLeave守卫
3. 调用全局的beforeEach守卫
4. 复用组件里调用beforeRouterUpadate守卫
5. 路由配置里的beforeEnter守卫
6. 解析异步路由组件
7. 在被激活的组件里调用beforeRouteEnter守卫
8. 调用全局beforeResolve守卫
9. 导航被确认
10. 调用全局afterEach钩子
11. dom更新
12. 用创建好的实例调用beforeRouteEnter守卫传next的回调函数
vue-router两种模式的区别?
vue-router一共有两种路由模式。hash、history
1.hash模式:hash + hashChange
hash在url中,但不被包括在http请求中,用来指导浏览器动作,对服务端安全无用。
hash不会重加载页面,通过监听hash的变化来执行js代码,从而实现页面的改变
hash —— 即地址栏 URL 中的 # 符号
2.history模式:historyApi+popState
只要刷新这个url就会请求服务器,每一次路由改变都会重新加载
一般场景下,hash 和 history 都可以,除非你更在意颜值,# 符号夹杂在 URL 里看起来确实有些不太美丽。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成,
URL 跳转而无须重新加载页面。—— Vue-router 官网。
spa单页面的理解,优缺点是什么?
spa(single-page application)仅在web页面初始化时加载相应的HTML、JavaScript和CSS,
一旦页面加载完成,spa不会因为用户的操作而进行页面的重新加载或跳转;而是利用路由机制
实现HTML内容的变换,可以避免页面的重新加载。
优点:
1. 用户体验好、快。内容改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
2. spa相对于服务器压力小
3. 前后端分离,架构清晰,前端负责交互逻辑,后端负责数据处理
缺点:
1. 首屏(初次)加载慢:为实现spa 页面,需要将加载页面的时候将js,css统一加载,部分页面按需加载;
2. 不利于SEO:由于所有的内容都在一个页面中动态替换展示,所以SEO上有天然的弱势
new Vue()发生了什么?
new Vue()是创建了Vue实例,内部执行了根实例的初始化过程。
Vue.use是干什么的?
vue.use是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法等。
请说一下响应式数据的理解?
根据数据类型来做不同处理,数组和对象类型当值变化时劫持
1. 对象内部通过defineReactive方法,使用Object.defineProperty()
监听数据属性的get来进行数据依赖收集,再通过set来完成数据更新的派发;
2. 数组则通过重写数组方法来实现的。扩展它的7个变更方法,通过监听这些方法做到依赖收集和派发更新;
(push/pop/shift/unshift/splice/reverse/sort)
vue3中使用proxy来实现响应式数据
Vue如何检测数组变化?
数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组方法以进行重写。
当数组调用这7个方法(push/pop/shift/unshift/splice/reverse/sort)时,执行ob.dep.notify()
进行派发通知Watcher更新
Vue.$set方法是如何实现的?
给对象和数组本身都增加了dep属性,当给对象新增不存在的属性则触发对象依赖的watcher去更新,
当修改数组索引时我们调用数组本身的splice方法去更新数组。
Vue的模板编译原理?
1. template模板转换成ast语法树, .vue文件中的template是通过vue-loader来进行处理的,并不是通过运行时的编译
2. 对静态语法做静态标记
3. 重新生成代码
模板引擎的实现原理就是new Function +with来实现的。
vue-loader中处理template属性主要靠的是vue-template-compiler
$nextTick() 和 $forceUpdate()与$set
this.$nextTick():
数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,
这个操作都应该放进this.$nextTick()的回调函数中。函数执行在DOM渲染完之后
this.$forceUpdate():
迫使Vue实例重新(rander)渲染虚拟DOM,注意并不是重新加载组件。
结合vue的生命周期,调用$forceUpdate后只会触发beforeUpdate和updated这两个钩子函数,
不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件
this.$set:
如果在vue实例创建之后添加新的属性到实例(data)上,它不会触发视图更新.
用this.$set添加的属性会触发视图更新
v-if与v-show的区别
v-if是真正的条件渲染,直到条件第一次变为真时,才会开始渲染
v-show不管初始条件是什么都会进行渲染,只是用于display来进行显示隐藏
watch、computed、和methods的区别:
computed:计算属性,依赖其他属性值来进行计算,并且computed的值有缓存,
只有它依赖的属性值发生变化,computed才会重新计算;
watch:监听,监听数据的变化,每当监听的数据变化时都会执行回调 进行后续操作;
methods: 是一个方法,methods没有缓存机制,每当重新触发渲染,调用方法会将函数再一次执行;
watch没有缓存,监听的数据发生变化,会直接触发相应操作,但是支持异步;
而computed有缓存,依赖数据发生变化时才会重新计算。
所以只有在执行异步或开销较大的操作时,才使用watch,否则用computed。
当一个属性受多个属性影响的时候就需要用到computed。
当一条数据影响多条数据的时候就需要用watch。
v-model的原理
v-model本质是一个语法糖,可以看成是value+input方法的语法糖。
可以通过model属性的prop和event属性来进行自定义。
vue的底层是采用数据劫持 结合 发布-订阅模式的方法,
通过object.defineProperty()来劫持属性的setter,getter。
数据变动时发布消息给订阅者,触发响应的监听回调
watch的属性详解
handle:watch中需要具体执行的方法
deep:深度监听,当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,
此时就需要deep属性对对象进行深度监听。 在选项参数中指定 deep: true
immediate:watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,
只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。
在选项参数中指定 immediate: true
//举例:
watch: {
cityName: {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
},
vue的插槽
插槽就是子组件中提供给父组件使用的一个占位符,用<slot></slot>表示,
父组件可以在这个占位符中填充任何模板代码,如HTML、组件等,填充的内容会替换子组件的<slot></slot>标签
1. 默认插槽:如果父组件没有传值到子组件,那子组件就用自己默认的内容显示在插槽上。
2. 具名插槽:子组件中插槽的位置用名称命名好后,父组件要用v-slot:名称来显示在子组件中相对应的插槽
3. 作用域插槽:作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,
该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
vue常用命令
v-if / v-else / v-else-if / v-model / v-bind / v-on / v-show / v-html / v-text
vue的性能优化
1. 尽量减少data中的数据
2. 防抖节流
3. key保证唯一
4. 更多的情况下使用v-if代替v-show
5. spa页面采用keep-alive 缓存组件
6. 第三方模块按需导入
7. 图片懒加载
8. 使用cdn加载第三方模块
9. 压缩代码
vue-router与location.href的用法区别
1.vue-router使用pushState进行路由更新,静态跳转,页面不会重新加载;location.href会触发浏览器,页面重新加载一次
2.vue-router使用diff算法,实现按需加载,减少dom操作
3.vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;
4.vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载
vue的mixin
mixin可以定义公用的变量或方法,但是mixin中的数据是不共享的,
也就是每个组件中的mixin实例都是不一样的,都是单独存在的个体,不存在相互影响的;
mixin混入对象值为函数的同名函数选项将会进行递归合并为数组,两个函数都会执行,
只不过先执行mixin中的同名函数;
mixin混入对象值为对象的同名对象将会进行替换,都优先执行组件内的同名对象,
也就是组件内的同名对象将mixin混入对象的同名对象进行覆盖;
vue阻止事件冒泡与js阻止事件冒泡
vue:
.stop:阻止事件冒泡
.prevent:阻止默认事件
js:
.stopPropagation():阻止事件冒泡
.preventDefault():阻止默认事件
vue中的虚拟DOM
什么是虚拟DOM:用一个JS对象来描述一个DOM节点。
为什么要有虚拟DOM:
因为vue是数据驱动视图的,数据发生变化视图就要随之更新,
而操作真实DOM是非常消耗性能的,因为浏览器把DOM设计的非常复杂
虚拟DOM的用途:
要尽可能的少操作DOM,要通过对比数据的变化的前后状态,计算出视图中哪些地方需要更新,
用JS的计算性能来操作DOM的性能,用JS模拟出一个DOM节点,称之为虚拟DOM节点。当数据发生
变化的时候,我们对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图
VNode类:
Vue中就存在了一个VNode类,通过这个类,我们就可以实例化出不同类型的虚拟DOM节点
VNode的作用:
在视图渲染之前,把写好的template模板先编译成VNode并缓存下来,等到数据发生变化页面需要重新渲染的
时候,再把数据发生变化后生成的VNode与前一次缓存下来的VNode进行对比,找出差异,然后有差异的VNode
对应真实的DOM节点就是需要重新渲染的节点,根据有差异的VNode创建出真实的DOM节点再插入视图中,
完成一次视图更新
vue3有什么变化
1. 改使用proxy实现响应式数据
2. api进行变化,vue2组件库与vue3组件库不互通
3. 创建vue实例用 import {createApp} from 'vue' ,const app = createApp()
4. 删除掉了过滤器,建议使用方法替换或者计算属性
5. 体积更小,通过 Tree-shaking 功能,Vue 3 能够更有效地减少打包体积,只包含必要的模块
6. 引入了 Composition API,可以与 Options API 一起使用,提供了更灵活的逻辑组合与复用
7. 重写了虚拟 DOM 的实现,优化了模板编译,使得组件初始化和更新性能提高了 1.3 到 2 倍,SSR 速度提高了 2 到 3 倍
8. Vue 3 支持多根节点组件,使得组件结构更加灵活
9. props 跟 $emit 优化: Vue 3 中子组件接收的值在 `props` 对象中,
需要通过 `props.name` 访问,而 emit 触发的事件需要在 `defineEmits` 中声明
10. Vue 3 引入了 `Teleport` 组件,允许将组件内容渲染到 DOM 中的任何位置
vue3中的ref 与 reactive
在 Vue 3 中,你可以使用 `ref` 来声明简单数据类型,也可以使用 `reactive` 来声明复杂数据类型。
实际上,`ref` 和 `reactive` 都可以用于任何类型的数据,但它们的使用场景和性能特点有所不同
import { ref, reactive } from 'vue';
// 使用 ref 声明简单数据类型
const count = ref(0);
// 使用 ref 声明复杂数据类型
const user = ref({
name: 'Kimi',
age: 30
});
// 使用 reactive 声明简单数据类型
const countReactive = reactive({
count: 0
});
// 使用 reactive 声明复杂数据类型
const userReactive = reactive({
name: 'Kimi',
age: 30,
friends: ['Alice', 'Bob']
});
在模板中使用这些响应式数据时,`ref` 需要使用 `.value` 来访问值:
<template>
<div>{{ count.value }}</div>
<div>{{ user.value.name }}</div>
</template>
而 `reactive` 则不需要:
<template>
<div>{{ countReactive.count }}</div>
<div>{{ userReactive.name }}</div>
</template>
在 Vue 3 中,`reactive` 对象中的 `ref` 属性会被自动解构
const user = reactive({ name : ref('aa')});
console.log(user.name); //为'aa',不需要user.name.value
Vue3.x响应式数据原理
1. Proxy 判断当前Reflect.get的返回值是否为Object,
如果是则在通过reactive方法做代理,这样就实现了深度观测。
2. 检测数组的时候可能触发多次get/set,怎么防止触发多次?
可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,
只有满足两个条件之一,才有可能执行trigger
Proxy 与 Object.defineProperty 优劣对比
1. Proxy可以直接监听对象而非属性
2. Proxy可以直接监听数组变化
3. Proxy有多达13种拦截方法,不限于apply、ovnKeys、deleteProperty、has等等是Object.defineProperty不具备的
4. Proxy返回的是是一个新对象,而Object.defineProprery只能遍历对象属性直接修改
5. Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
1. Object.defineProprety兼容性好,支持IE9,而Proxy存在浏览器兼容问题。
script setup 和通过 defineComponent 引入 setup
在 Vue 3 中,`<script setup>` 和通过 `defineComponent` 引入 `setup` 函数
是两种不同的组件定义方式,它们有以下区别:
1.script setup
是Vue 3.2+ 版本中引入的编译时语法糖,它提供了一种更简洁的方式来使用 Composition API。
使用 `<script setup>` 时,不需要显式地调用 `defineComponent`。
`<script setup>` 中定义的所有内容都被视为 `setup` 函数的内部逻辑,包括变量、函数和组件选项。
响应式引用在 `<script setup>` 中声明的响应式变量(使用 `ref` 或 `reactive`)可以直接在模板中使用,无需通过 `this` 访问。
编译时优化Vue 编译器可以对 `<script setup>` 中的代码进行优化,例如,未使用的组合式 API 导入可以被树摇(tree-shaken)。
2.引入 `setup`
使用 `defineComponent` 时,你需要显式地定义一个组件对象,并提供一个 `setup` 函数。
组件选项使用 `defineComponent` 时,你仍然需要使用 Vue 3 的组件选项(如 `props`、`emits`、`data` 等)。
响应式引用在 `setup` 函数中声明的响应式变量需要通过 `this` 或 `setup` 返回的对象来访问。
手动优化与 `<script setup>` 相比,使用 `defineComponent` 时,编译器的优化能力可能有限,因为代码的组织方式更传统。
以下是两种方式的代码示例:
使用 `<script setup>`:
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
使用 `defineComponent` 和 `setup`:
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
return {
count
};
}
});
</script>
总结来说,`<script setup>` 提供了一种更简洁和现代化的方式来使用 Composition API,
而 `defineComponent` 配合 `setup` 函数则更接近传统的组件定义方式。
选择哪种方式取决于你的个人偏好和项目需求。
vue3 生命周期变化
Vue 3 对这些生命周期钩子进行了重命名,以保持命名的一致性.
新的命名规则为:`setup`(相当于 Vue 2 的 `beforeCreate` 和 `created` 合并)、
`onBeforeMount`(相当于 `beforeMount`)、
`onMounted`(相当于 `mounted`)、
`onBeforeUpdate`(相当于 `beforeUpdate`)、
`onUpdated`(相当于 `updated`)、
`onBeforeUnmount`(相当于 `beforeDestroy`)、
`onUnmounted`(相当于 `destroyed`)
小程序
简单描述下微信小程序的相关文件类型
1. WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,
可以构建出页面的结构。内部主要是微信自己定义的一套组件
2. WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式
3. js 逻辑处理,网络请求
4. json 小程序设置,如页面注册,页面标题及tabBar
app.json 必须要有这个文件,如果没有这个文件,项目无法运行,因为微信框架把这个作为配置文件入口,
整个小程序的全局配置。包括页面注册,网络设置,以及小程序的 window 背景色,配置导航条样式,配置默认标题
app.js 必须要有这个文件,没有也是会报错!
但是这个文件创建一下就行 什么都不需要写以后我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量
app.wxss 可选
小程序的双向绑定和vue哪里不一样
小程序直接 this.data.a = 1 的属性是不可以同步到视图的,必须调用:
this.setData({
a:1
})
小程序页面间有哪些传递数据的方法
1.使用全局变量实现数据传递
在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面
App({
// 全局变量
globalData: {
userInfo: null
}
})
使用的时候,直接使用 getApp() 拿到存储的信息
2.使用 wx.navigateTo 与 wx.redirectTo 的时候,
可以将部分数据放在 url 里面,并在新页面 onLoad 的时候初始化
注意:wx.navigateTo 和 wx.redirectTo 不允许跳转到 tab 所包含的页面
注意:onLoad 只执行一次
3.使用本地缓存 Storage
小程序的生命周期函数
1. onLoad()页面加载时触发,只会调用一次。可获取当前页面路径的参数
2. onShow()页面显示时触发,一般用来发送数据请求
3. onReady()页面初次渲染完成触发,只会调用一次
4. onHide()页面隐藏时触发
5. onUnload()页面卸载时触发
6. onPullDownRefresh()下拉刷新时触发
7. onReachBottom()上翻到底时触发
小程序的路由区别
1. wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到tabbar页面
2. wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到tabbar页面
3. wx.switchTab(): 跳转到tabbar页面,关闭其他所有非tabbar页面
4. wx.navigateBack():关闭当前页面,返回上一页面或多级页面。
可通过getCurrentPages()获取当前的页面栈,决定返回第几层
5. wx.reLaunch():关闭所有页面,打开应用内的某个页面
微信小程序与h5的区别
1. 运行环境不同(小程序在微信运行,h5在浏览器运行)
2. 开发成本不同(h5需要兼容)
3. h5需要不断优化来提高用户体验
4. 小程序限制较多,页面大小不能超过2M。不能打开超过10个层级的页面
小程序的优点:小程序属于轻量级的app,但是限制在微信中,开发周期短,功能少,占用空间少
小程序的请求封装
用Promise封装一遍封装wx.request,使其可以传url,path,params,method
小程序关联微信公众号如何确定用户的唯一性
如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),
可通过 unionid 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),
用户的 unionid 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid 是相同的
小程序的openid与unionid
1.openid长度28,unionid长度29
2.openid在同一应用唯一,unionid在所有应用唯一。看到一个很好的解释:(openid
相当于个人在学校的学号,unionid相当于个人的身份证号)
这里的应用是在微信开发平台上的应用,例如一个用户绑定一个公众号就会有一个用户与公众号关联的openid
(在一个公众号内一个用户只有一个openid),所以一个用户可以有多个openid,
而unionid是一个用户身份的代表它在所有应用里面唯一。一个unionid最多只能有10个openid
3.对于pc端和客户端
微信平台中pc端是网页客户端是公众号所以它们的openid不一致,但是它们的unionid是一致的。
4.获取openid是不需要用户同意的,而获取unionid是需要用户授权同意的。
所以我们在绑定某个用户在某个公众号上是否授权只需要验证用户的openid是否和unionid有关联
小程序最多打开多少个页面
在微信小程序中打开的页面不能超过10个,达到10个页面后,就不能再打开新的页面。
小程序的分包加载
cn.vuejs.org/api/sfc-scr… 小程序的分包加载 uniapp小程序的分包加载
小程序如何实现下拉刷新
首先在全局 config 中的 window 配置 enablePullDownRefresh
在 Page 中定义 onPullDownRefresh 钩子函数,到达下拉刷新条件后,该钩子函数执行,发起请求方法
请求返回后,调用 wx.stopPullDownRefresh 停止下拉刷新
bindtap和catchtap的区别是什么(小程序的点击事件)
相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分
不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap阻止冒泡的
web综合问题
从浏览器地址栏输入url到显示页面的步骤
1.DNS域名解析;
2.建立TCP连接;
3.发送HTTP请求;
4.服务器处理请求;
5.返回响应结果;
6.关闭TCP连接;
7.浏览器解析HTML;
8.浏览器布局渲染;
get与post的区别
1. 位置不同: get是在url里发送,post在请求体里发送
2. 速度不同:get传输速度比post快
3. 大小不同:get发送数据有限制,post没有限制
4. 安全性不同 :post在请求体里发送,安全性更高
TCP三次握手用一句话概括
确认双方的接收与发送能力是否正常。
浏览器中有哪些宏任务和微任务
宏任务:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务:Promise、MutaionObserver、process.nextTick(Node.js 环境)
MVC与MVVM
MVC:Model-View- Controller的简写。即模型-视图-控制器。
M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。
使用MVC的目的就是将M和V的代码分离。MVC是单向通信。
也就是View跟Model,必须通过Controller来承上启下。
MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,
只不过是弱化了C的概念,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,
其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,
使开发更高效,结构更清晰,增加代码的复用性。
MVVM:Model-View-ViewModel的简写。即模型-视图-视图模型
模型(Model)指的是后端传递的数据。视图(View)指的是所看到的页面。
视图模型(ViewModel)是mvvm模式的核心,它是连接view和model的桥梁。
它有两个方向:一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面。
实现的方式是:数据绑定。二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。
实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
为什么会有跨域,跨域的解决方式
什么是跨域?:
跨域是指从一个域名去请求另一个域名的资源,严格来说,只要域名,协议,端口任何一个不同,就视为跨域
为什么有跨域:
为了网络安全起见,浏览器设置了一个同源策略,规定只有域名,端口,协议全部相同,就叫做同源。
当页面在执行一个脚本时,会检查访问的资源是否同源,如果不是,就会报错。
可是在实际开发中,经常会有跨域加载资源的需求,避免不了跨域请求,所以就出现了跨域。
跨域的解决方式:
1. JSONP :前端使用JSONP来解决跨域,但只支持get请求。
2. CORS:CORS需要浏览器和后端同时支持,浏览器会自动进行CORS通信,
实现CORS通信的关键是后端,只要后端实现了CORS,就实现了跨域,
服务端设置Access-Control-Allow-Origin 就可以开启CORS,
该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源
3. Node中间件代理
4. nginx反向代理
HTTP状态码及其含义
1XX:信息状态码
2XX:成功状态码
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
3XX:重定向
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。
4XX:客户端错误
400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁止访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
5XX: 服务器错误
500 Internal Server Error 最常见的服务器端错误。
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
https和http有什么区别
https是http的安全版本,也叫超文本安全传输,
https是有加密传输协议的通道,并且SSL提供了安全加密基础,
https主要是用于http的传输,并且在HTTP与TCP之间有一个特殊的加密/身份验证。
http是一种普通的传输协议,在互联网上,所有的文件都要遵守这个HTTP协议,
同时超文本也是http传输的基本部分,实现客户端和服务器的相互请求。
1、https的端口是443,而http的端口是80,且两者的连接方式不同;
2、http传输是明文的,而https是用ssl进行加密的,https的安全性更高;
3、https是需要申请证书的,而http不需要。
回流与重绘
一个页面从加载到完成,首先是构建DOM树,然后根据DOM节点的几何属性形成render树(渲染树),
当渲染树构建完成,页面就根据DOM树开始布局了,渲染树也根据设置的样式对应的渲染这些节点。
回流:增删DOM节点,修改一个元素的宽高,页面布局发生变化,DOM树结构发生变化,
那么肯定要重新构建DOM树,而DOM树与渲染树是紧密相连的,DOM树构建完,
渲染树也会随之对页面进行再次渲染,这个过程就叫回流。
重绘:给一个元素更换颜色,这样的行为是不会影响页面布局的,DOM树不会变化,
但颜色变了,渲染树得重新渲染页面,这就是重绘。
引起DOM树结构变化,页面布局变化的行为叫回流,且回流一定伴随重绘。
只是样式的变化,不会引起DOM树变化,页面布局变化的行为叫重绘,且重绘不一定会便随回流。
setTimeout和requestAnimationFrame有什么区别?
setTimeout和requestAnimationFrame两个动画api
性能层面:
当页面被隐藏或最小化时,定时器 setTimeout 仍在后台执行动画任务。
当页面处于未激活的状态下,该页面的屏幕刷新任 务会被系统暂停,requestAnimationFrame 也会停止。
requestAnimationFrame性能更好,使用 requestAnimationFrame 执行动画,
最大优势是能保证回调函数在屏幕每一次刷 新间隔中只被执行一次,这样就不会引起丢帧,动画也就不会卡顿。
setTimeout 通过设置一个间隔时间不断改变图像,达到动画效果。该方法在一些低端机 上会出现卡顿、抖动现象。
setTimeout 属于 JS 引擎,存在事件轮询,存在事件队列。
requestAnimationFrame 属于 GUI 引擎,发生在渲 染过程的中重绘重排部分,与电脑分辨路保持一致。
对浏览器内核的理解?
主要分成两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎
渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),
以及计算网页的显示方式,然后会输出至显示器或打印机。
浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。
所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核
JS引擎:解析和执行javascript来实现网页的动态效果
最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎
cookies,sessionStorage 和 localStorage 的区别?
cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)
cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
储存大小:
cookie数据大小不能超过4k
sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
过期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
sessionStorage 数据在当前浏览器窗口关闭后自动删除
cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
设置cookie的过期时间(不常问):
如果Cookie没有设置expires属性,那么 cookie 的生命周期只是在当前的会话中,
关闭浏览器意味着这次会话的结束,此时 cookie 随之失效。
Cookies.set('name', 'value', {
expires: 7, //7天
});
如果expires设置一个过去的时间点,那么这个cookie 会被立即删掉(失效)。
Pinia 和 Vuex
Pinia 和 Vuex 都是 Vue 的状态管理库,但它们之间存在一些关键的区别,这些区别影响了开发者在项目中的选择和使用方式。
1. **API 设计**:
- Pinia:更简洁,基于 Vue 3 的 Composition API。
- Vuex:更复杂,遵循传统的 Flux 架构。
2. **状态管理**:
- Pinia:状态分散在多个 store 中。
- Vuex:状态集中管理,存在一个大的单一状态树。
3. **TypeScript 支持**:
- Pinia:原生支持更好,类型推断更准确。
- Vuex:支持较弱,需要手动配置类型。
4. **性能**:
- Pinia:轻量级,性能更优。
- Vuex:稳定,但在大型应用中可能存在性能开销。
5. **社区和生态**:
- Pinia:社区较小,但增长迅速。
- Vuex:官方支持,社区和插件丰富。
6. **SSR 支持**:
- Pinia:内置支持服务端渲染。
- Vuex:需要额外配置来支持 SSR。
选择 Pinia 或 Vuex 取决于项目需求、团队熟悉度以及对 TypeScript 的需求。
Webpack 和 Vite
Webpack 和 Vite 都是现代前端开发中常用的构建工具,但它们在设计理念、性能、配置复杂度等方面存在一些区别:
1. **构建速度**:
- Webpack 的首次构建时间可能会比较长,尤其是当项目规模较大时,因为它需要预先打包整个应用程序。在开发过程中,每次代码修改后都需要重新构建,这可能会影响到开发体验。
- Vite 在开发环境下利用了 ES 模块原生支持的特点,不需要构建步骤即可启动服务,因此启动速度非常快。Vite 仅在请求文件时进行编译,这意味着开发服务器启动时非常快,热更新也非常迅速。
2. **开发模式**:
- Webpack 需要将代码打包到一个文件中,然后再在浏览器中进行热更新,这可能导致较长的等待时间。
- Vite 的开发模式是在浏览器中实时编译和构建代码,这意味着开发者可以在不断开页面的情况下进行实时更新,大大提高了开发效率。
3. **打包方式**:
- Vite 采用 Native ES Module 的方式服务源码,不会将代码打包,而是利用浏览器原生支持 ES Module 的方式,实现按需加载。
- Webpack 需要先打包代码,转换为浏览器可识别的模块格式,无法实现按需加载。
4. **配置文件的差异**:
- Vite 使用更简单的配置文件,通常不需要复杂的配置即可开始开发。
- Webpack 需要一个复杂的配置文件来设置各种插件和加载器,这可能对新手来说不太友好。
5. **生态系统支持**:
- Webpack 拥有庞大的社区支持,有大量的插件和加载器可用,非常适合大型和复杂的项目。
- Vite 相对较新,其社区正在快速增长,但可能还没有 Webpack 那样丰富的资源和插件。
6. **对 TypeScript 的支持**:
- Vite 内置支持 TypeScript,可以直接导入 TS 文件,而且热更新到浏览器的时间非常短。
- Webpack 需要安装相应的 loader 才能导入 TS 文件。
7. **性能提升**:
- Vite 依托支持原生 ESM 模块的现代浏览器,极大地降低了应用的启动和重新构建时间。
- Webpack 在生产环境的构建方面更加成熟,它有更多的优化和插件可供选择。
总的来说,如果你追求快速的开发体验和简化的配置,Vite 是一个非常好的选择。如果你需要一个高度可配置的构建流程,或者已经在使用 Webpack 并希望保持一致性,那么继续使用 Webpack 可能更合适。两者都在不断发展,未来可能会有更多的特性重叠。
XSS攻击
跨站脚本攻击(XSS-Cross-site scripting,为了和CSS层叠样式表区分所以取名XSS),是最普遍的Web应用安全漏洞。
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,
使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,
但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。
攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容
攻击方式:
1.XSS反射型攻击,恶意代码并没有保存在目标网站,通过引诱用户点击一个链接到目标网站的恶意链接来实施攻击的。
2.XSS存储型攻击,恶意代码被保存到目标网站的服务器中,这种攻击具有较强的稳定性和持久性,
比较常见场景是在博客,论坛等社交网站上,但OA系统,和CRM系统上也能看到它身影
解决办法:过滤输入和转义输出
1.在输入方面对所有用户提交内容进行可靠的输入验证,提交内容包括URL、查询关键字、http头、post数据等
2.在输出方面,在用户输内容中使用标签。标签内的内容不会解释,直接显示。
3.严格执行字符输入字数控制。
4.在脚本执行区中,应绝无用户输入。
CSRF攻击
跨站请求伪造 (CSRF-Cross-site request forgery)
是一种对网站的恶意利用,其通过伪装来自受信任用户的请求来利用受信任的网站。
理解:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:
以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。
防范方法:
1.验证 HTTP Referer 字段。 根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。
后端只需要对于每一个请求验证其 Referer 值
这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。
2.请求地址中添加 token 并验证,可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
JS
闭包
每个函数都是一个潜在的闭包,因为函数本身可以捕获定义时的作用域链。
但是,只有当函数引用了外部作用域的变量,并且在外部作用域释放后仍然被引用时,我们通常才说这个函数是一个闭包。
闭包是一个概念,它描述了函数执行完毕内存释放后,依然内存驻留的一个现象
闭包的优缺点:
1.优点:
1.可以将一个变量长期储存在内存中,用于缓存
2.可以避免全局变量的污染
3.加强封装性,是实现了对变量的隐藏和封装
2.闭包的缺点:
1.因为函数执行上下文AO执行完不被释放,所以会导致内存消耗很大,增加了内存消耗量,影响网页性能出现问题
2.而且过度的使用闭包可能会导致内存泄漏,或程序加载运行过慢卡顿等问题的出现
//举例
function outerFunction() {
var outerVar = 'I am outer';
function innerFunction() {
console.log(outerVar);
}
return innerFunction;
}
var result = outerFunction();
result(); // 输出 'I am outer'
在这个例子中,`innerFunction` 是一个闭包,因为它引用了外部函数 `outerFunction` 的变量 `outerVar`。
即使 `outerFunction` 执行完毕后,`innerFunction` 仍然可以访问 `outerVar`,
因为它“记住”了 `outerFunction` 的作用域。
JS的数据类型
五种基本数据类型(也就是简单数据类型),分别是:Undefined,Null,Boolean,Number和String。
还含有一种复杂的数据类型(也叫引用类型),就是对象:object;
注意Undefined和Null的区别,Undefined类型只有一个值,就是undefined,Null类型也只有一个值,也就是null
Undefined 其实就是已声明未赋值的变量输出的结果
null 其实就是一个不存在的对象的结果
Map 与 Object
在 JavaScript 中,`Map` 和 `Object` 是存储键值对的两种不同的数据结构,它们有以下主要区别:
1. **键的类型**:
- `Object` 的键(属性名)通常是字符串或符号(Symbols),但也可以是任何值,只要它能够被转换为字符串(这通常意味着对象的键实际上是字符串)。
- `Map` 可以使用任意类型的值作为键,包括函数、对象和任何原始类型。
2. **迭代顺序**:
- `Map` 对象保持键值对的插入顺序,当对 `Map` 进行迭代时,会按照元素的插入顺序返回键值对。
- `Object` 的属性遍历顺序在 ES6 之前是不确定的,ES6 规定了一种遍历顺序,但这种顺序并不是基于属性的创建顺序。
3. **内置方法和属性**:
- `Map` 提供了一些有用的内置方法,如 `map.size` 返回元素数量,`map.get(key)`、`map.set(key, value)`、`map.has(key)`、`map.delete(key)` 等。
- `Object` 没有这样的内置方法,通常需要使用 `Object.keys(obj)` 或其他相关方法来处理对象的键和值。
4. **性能**:
- 对于频繁增删键值对的场景,`Map` 通常提供更优的性能。
- `Object` 在处理小型或固定大小的键值对集合时性能表现良好,但随着属性数量的增加,性能可能下降。
5. **内置对象属性**:
- `Object` 的键实际上是对象的属性,这意味着它们可以通过 `obj.property` 的方式访问,也可以通过 `obj['property']` 访问。
- `Map` 的键值对只能通过 `map.get(key)` 和 `map.set(key, value)` 等方式访问。
6. **序列化**:
- `Object` 可以直接使用 `JSON.stringify()` 进行序列化。
- `Map` 不能直接序列化,需要先将 `Map` 转换为对象或数组。
7. **原型链**:
- `Object` 有原型,意味着它可以有继承的属性,这可能会导致一些意外情况,除非使用 `Object.create(null)` 创建一个没有原型的对象。
- `Map` 不包含键来自其原型链的风险。
8. **遍历方法**:
- `Map` 提供了多种遍历方法,如 `map.forEach()`、`for...of` 循环等。
- `Object` 的遍历通常需要配合 `Object.keys()`、`Object.values()` 或 `Object.entries()` 使用。
JavaScript原型,原型链
所有的引入类型(数组,函数,对象)都有一个_ _ proto _ _属性(隐式原型)
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,
这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。
当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的
就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象 - null
普通函数与箭头函数的区别
写法上的区别,还有this指向的区别。
1.this指向方面
普通函数:哪个对象调用普通函数,普通函数就指向该对象。默认指向window,严格模式下指向undefined;
箭头函数:箭头函数的this是继承的上一层对象。
2.arguments方面:
普通函数,可以通过arguments来实现重载;
箭头函数中,没有arguments,代替它功能是剩余参数rest(...)。
3.原型对象方面:
普通函数,是有自己的原型对象的;
箭头函数,没有原型对象。
4.new方面:
普通函数,可以作为构造函数,通过new实例化出子函数;
箭头函数,不能作为构造函数,使用new会报错。
5.简易程度:
箭头函数比普通函数的使用简短更多;同时箭头函数通常是匿名函数。
函数里的参数对象arguments
Javascript函数中的参数对象arguments是个对象,而不是数组。
但它可以类似数组那样通过数字下表访问其中的元素,而且它也有length属性标识它的元素的个数。
通常我们把它转换成数组用Array的slice函数,示例代码如下:
function fn() { var arr = Array.prototype.slice.call(arguments,0); alert(arr.length);}
this的指向
this总是指向函数的直接调用者(而非间接调用者)
如果有new关键字,this指向new出来的那个对象
定时器,计时器中的this指向window
构造函数
通过new函数名来实例化对象的函数叫构造函数。任何的函数都可以作为构造函数存在。
之所以有构造函数与普通函数之分,主要从功能上进行区别的,
构造函数的主要功能为初始化对象,特点是和new一起使用。
new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。
构造函数定义时首字母大写(规范)。对new理解:new 申请内存, 创建对象,当调用new时,
后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。
var arr = []为var arr = new Array(); 的语法糖。
var obj = {}为var obj = new Object(); 的语法糖
JS数组的方法
join():将数组中所有元素都转化为字符串并连接在一起。
reverse():将数组中的元素颠倒顺序。
concat():数组拼接的功能 ,返回新数组,原数组不受影响。
slice()截取数组生成新数组,原数组不受影响。
返回的数组包含第一个参数指定的位置和所有到但不含第二个参数指定位置之间的所有元素。
如果为负数,表示相对于数组中最后一个元素的位置。如果只有一个参数,表示到数组末尾。
splice()从数组中删除元素、插入元素到数组中或者同时完成这两种操作。
输入:第一个参数为指定插入或删除的起始位置,第二个参数为要删除的个数(传0为不删除)。
之后的参数表示需要插入到数组中的元素 。如果只有一个参数,默认删除参数后边的所有元素。
输出:返回一个由删除元素组成的数组。注意:新建了一个数组,并修改了原数组
push():在数组末尾添加一个或多个元素,并返回新数组长度
pop():从数组末尾删除1个元素(删且只删除1个), 并返回 被删除的元素
shift():在数组开始添加一个或多个元素,并返回新数组长度
unshift():在数组开始删除一个元素(删且只删除1个),并返回 被删除的元素
toString()和toLocaleString():将数组的每个元素转化为字符串,并且输入用逗号分隔的字符串列表。功能类似join();
indexOf()和lastIndexOf():indexOf() 两个参数:要查找的项和(可选的)表示查找起点位置的索引。
其中, 从数组的开头(位置 0)开始向后查找。没找到返回-1. 返回查找项的索引值
lastIndexOf() 从数组的末尾开始向前查找。返回查找项的索引值(索引值永远是正序的索引值),没找到返回-1
JS的事件循环机制:
Js代码从上往下执行,遇到同步任务直接执行,遇到异步任务判断是宏任务还是微任务,
如果是微任务就放到当前任务列的最低端,如果是宏任务则另开一个任务队列,
执行完当前任务列的同步任务以后再去执行微任务,执行完微任务以后再去下一个任务队列中执行
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,
异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,
推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
ajax原理
1. 浏览器让xhr去服务器获取数据
2. xhr跟服务器请求数据
3. 服务器返回数据给xhr
4. xhr告诉浏览器接收到服务器的数据了
5. 浏览器拿xhr返回的数据来响应页面
1. 创建ajax对象let xhr = new XMLHttpRequest()
2. 像服务器发送请求 xhr.open()
3. 服务器响应 xhr.send()
ES6、ES7、ES8、ES9、ES10新特性一览
ES6:
1. 模块化 module (导出:export /导入import)
2. 箭头函数
3. 模板字符串
4. 函数传参的默认参数(
const test = (a='a',b='b',c='c')=>{
return a+b+c
})
5. 解构赋值
6. promise
ES7:
1. includes(判断一个数组是否包含指定的值,返回true 或 false)
ES8:
1. async/await
2. Object.values()
3. Object.keys()
4. Object.entries()
ES9:
1. ...展开运算符
async/await
异步编程的最高境界就是不关心它是否是异步。async、await很好的解决了这一点,将异步强行转换为同步处理。
async/await与promise不存在谁代替谁的说法,因为async/await是寄生于Promise,Generater的语法糖。
async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
async/await是基于Promise实现的,它不能用于普通的回调函数。
async/await与Promise一样,是非阻塞的。
async/await使得异步代码看起来像同步代码,这正是它的魔力所在。
为什么Async/Await更好?
1)使用async函数可以让代码简洁很多,不需要像Promise一样需要些then,
不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。
2)错误处理:Async/Await 让 try/catch 可以同时处理同步和异步错误。
promise与async和await的区别 async和await的实现原理
promise
Promise是一个构造函数, 是异步编程的一种解决方案
let p = new Promise((resolve, reject) => {
//做一些异步操作
setTimeout(() => {
console.log('执行完成');
resolve('我是成功!!');
}, 2000);
})
在 JavaScript 中,`Promise` 对象提供了几个方法来处理异步操作。以下是 `Promise` 的主要方法:
1. **`.then(onFulfilled, onRejected)`**:
- 用于指定当 `Promise` 成功(fulfilled)或失败(rejected)时的回调函数。
- `onFulfilled` 是在 `Promise` 成功时调用的函数,它接收 `Promise` 的结果作为参数。
- `onRejected` 是在 `Promise` 失败时调用的函数,它接收 `Promise` 的拒绝原因作为参数。
2. **`.catch(onRejected)`**:
- 用于指定当 `Promise` 失败时的回调函数。
- `onRejected` 接收 `Promise` 的拒绝原因作为参数。
3. **`.finally(onFinally)`**:
- 用于指定当 `Promise` 完成(无论成功或失败)时的回调函数。
- `onFinally` 函数会在 `Promise` 的状态改变后被调用,不管 `Promise` 是被解决还是被拒绝。
4. **`Promise.all(iterable)`**:
- 用于将多个 `Promise` 实例包装成一个新的 `Promise`。
- `iterable` 是一个包含多个 `Promise` 实例的数组或其他可迭代对象。
- 新的 `Promise` 会在所有给定的 `Promise` 实例都成功解决时解决,如果其中任何一个 `Promise` 失败,则立即失败。
5. **`Promise.allSettled(iterable)`**:
- 用于将多个 `Promise` 实例包装成一个新的 `Promise`。
- `iterable` 是一个包含多个 `Promise` 实例的数组或其他可迭代对象。
- 新的 `Promise` 会等待所有给定的 `Promise` 实例都settled(即每个 `Promise` 都有了结果,无论是解决还是失败)。
6. **`Promise.race(iterable)`**:
- 用于将多个 `Promise` 实例包装成一个新的 `Promise`。
- `iterable` 是一个包含多个 `Promise` 实例的数组或其他可迭代对象。
- 新的 `Promise` 会在给定的 `Promise` 实例中第一个解决或失败的 `Promise` 的状态为准。
7. **`Promise.resolve(value)`**:
- 用于创建一个已经解决(fulfilled)的 `Promise` 实例。
- `value` 是传递给 `Promise` 的结果。
8. **`Promise.reject(reason)`**:
- 用于创建一个已经被拒绝(rejected)的 `Promise` 实例。
- `reason` 是传递给 `Promise` 的拒绝原因。
这些方法提供了强大的工具来处理异步操作,使得异步代码的编写和组织变得更加容易和高效。
let与var的区别
1.var是函数作用域,let是块级作用域。
在函数中声明了var,整个函数内都是有效的,比如说在for循环内定义的一个var变量,实际上其在for循环以外也是可以访问的
而let由于是块级作用域,所以如果在块级作用域内定义的变量,比如说在for循环内,
在其外面是不可被访问的,所以for循环推荐用let
2.let不能在定义之前访问该变量,但是var可以.
let必须先声明,在使用。而var先使用后声明也行,只不过直接使用但没有定义的时候,
其值是undefined。var有一个变量提升的过程,当整个函数作用域被创建的时候,
实际上var定义的变量都会被创建,并且如果此时没有初始化的话,则默认为初始化一个undefined
3.let不能被重新定义,但是var是可以的
深拷贝和浅拷贝
浅拷贝:共用一个地址,会互相影响
深拷贝:不共用一个地址,不会互相影响
深拷贝的实现:
deepCopy(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof (obj) === "object") {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof(obj[key]) === "object") {
objClone[key] = this.deepCopy(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
var deepCopy = function (source) {
var result = {};
for(var key in source) {
if(typeof source[key] === 'object') {
result[key] = deepCopy(source[key])
} else {
result[key] = source[key]
}
}
return result;
}
--------------------
JSON.parse(JSON.stringify(obj)):是利用JSON.stringify 将js对象序列化(JSON字符串),
再使用JSON.parse来反序列化(还原)js对象;序列化的作用是存储和传输。
//这种方法存在坑
1.如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式;
2.如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};
3.如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;
4.如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;
6.如果对象中存在循环引用的情况也无法实现深拷贝
JSON.parse(JSON.stringify(obj)) 实现深拷贝的一些坑
节流、防抖及使用场景
防抖(debounce):在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
防抖代码实现重在清零 clearTimeout
function debounce (f, wait) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
f(...args)
}, wait)
}
}
节流(throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
节流代码实现重在开锁关锁 timer=timeout; timer=null
function throttle (f, wait) {
let timer
return (...args) => {
if (timer) { return }
timer = setTimeout(() => {
f(...args)
timer = null
}, wait)
}
}
call,apply,bind的相同点与区别
相同点: 1.都是改变this指向的
2.第一个参数都是this要指向的对象
3.都可以利用后续参数传参
不同点: 1.call和bind的参数是依次传参,一一对应的
2.apply只有两个参数,第二个参数未数组
3.call和apply都是对函数直接进行调用,而bind方法返回的仍然是一个函数
例子:
var a ={
name:'一一',
age:'22',
sex:'女',
hobby:'写代码'
say:function(sex,hobby) {
console.log(this.name,this.age,sex,hobby)
}
}
var b = {
name:'二二',
age:'23',
}
a.say.call(b,'男','学习');
a.say.apply(b,['男','学习'])
bind可以向cally一样传参:
例如:
a.say.bind(b,'男','学习')();
但由于bind返回的仍然是一个函数,所以我们还可以在调用的时候再进行传参。
例如:
a.say.bind(b)('男','学习');
//结果为:"二二","23","男","学习"
JS的内存泄漏与垃圾回收机制
内存泄漏:
不再用到的数据、变量等仍然占用内存,且被占用的内存没有及时得到释放,即为内存泄漏。
垃圾回收机制:
JS具有自动寻找不再使用的变量并释放其所占用的内存的机制,即为垃圾回收机制。
内存泄漏的影响:
一次内存泄漏的危害或许可以忽略不计,但是内存泄漏堆积会导致很严重的后果。
比较严重的时候,无用的内存占用越来越高,可能导致系统卡顿,甚至导致进程崩溃。
常用的垃圾回收策略:
1.标记清理:
JavaScript 最常用的垃圾回收策略是标记清理(mark-and-sweep)。
当变量进入当前作用域,比如在函数内部声明一个变量时,这个变量会被加上存在于当前作用域中的标记。
而在当前作用域中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要当前
作用域中的代码在运行,就有可能用到它们。当变量离开当前作用域时,也会被加上离开当前作用域的标记。
2.引用记数:
另一种没那么常用的垃圾回收策略是引用计数(reference counting)。
其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。
如果同一个值又被赋给另一个变量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,
那么引用数减 1。当一个值的引用数为 0 时,就说明没办法再访问到这个值了,
因此可以安全地收回其内存了。垃圾回收程序下次运行的时候就会释放引用数为 0 的值的内存。
引用计数最早由 Netscape Navigator 3.0 采用,但很快就遇到了严重的问题:循环引用。
所谓循环引用,就是对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A。
CSS
flex布局
flex布局:弹性布局-display-flex,默认主轴排列(水平排列)
flex-direction:用来更改主轴的方向:row(默认水平排列,从左到右) | row-reverse(默认水平排列,从右到左) |
column(默认垂直排列,从上到下) | column-reverse(默认垂直排列,从下到上);
flex-wrap:是否换行:nowrap(默认不换行) | wrap(换行,由上到下) | wrap-reverse(换行,由上往下)
flex-flow:flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
justify-content:主轴的对齐方式: flex-start(左对齐) | flex-end(右对齐) | center(居中) |
space-between(两端对齐,中间间隔相等) | space-around(元素两侧间隔相等)
align-items:副轴的对齐方式:flex-start(起点对齐-由上到下) | flex-end(终点对齐-由下到上) |
center(居中) | baseline(第一行文字基线对齐) | stretch(铺满);
align-content:多根副轴的对齐方式:flex-start(起点对齐-由上到下) | flex-end(终点对齐-由下到上) |
center(居中) | baseline(第一行文字基线对齐) | stretch(铺满);
order:定义项目的排列顺序,数值越小,排列越靠前,默认为0
flex-grow:属性定义的放大比例,默认为0,默认不放大
align-self:单个元素的副轴对齐方式,默认为auto,表示继承父元素flex-start(起点对齐-由上到下) |
flex-end(终点对齐-由下到上) | center(居中) | baseline(第一行文字基线对齐) | stretch(铺满);
flex-shrink:定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)。
浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选
CSS3新特性
1.RGBA和透明度
2.background-image background-origin(content-box/padding-box/border-box) background-size background-repeat
3.word-wrap(对长的不可分割单词换行)word-wrap:break-word
4.文字阴影:text-shadow: 5px 5px 5px #FF0000;(水平阴影,垂直阴影,模糊距离,阴影颜色)
5.font-face属性:定义自己的字体
6.圆角(边框半径):border-radius 属性用于创建圆角
7.边框图片:border-image: url(border.png) 30 30 round
8.盒阴影:box-shadow: 10px 10px 5px #888888
9.媒体查询:定义两套css,当浏览器的尺寸变化时会采用不同的属性
CSS优先级算法如何计算
元素选择符: 1
class选择符: 10
id选择符:100
元素标签:1000
1.!important声明的样式优先级最高,如果冲突再进行计算。
2.如果优先级相同,则选择最后出现的样式。
3.继承得到的样式的优先级最低。
块级标签,行内标签和行内块标签的区别
块级标签:div p h1-h6 ul li header footer main nav section artical
块状标签能独占一行,能设置宽高
行内标签:a span em strong i
行内标签不能独占一行,默认在同一行排列,不能设置宽高
行内块标签:img ,textarea, input 表单元素类标签
行内块状标签同时具备行内块的特点
//三种标签的转换
display:block ; display:inline ; display:inline-block;
盒子模型
W3C盒子模型(标准盒模型)-box-sizing:content-box:
根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,
而内容周围的 padding 和 border 值是另外计算的;即在标准模式下的盒模型,
盒子实际内容(content)的width/height=我们设置的width/height;
盒子总宽度/高度=width/height+padding+border+margin。
IE盒子模型(怪异盒模型)-box-sizing:border-box:
在该模式下,浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和;
即在怪异模式下的盒模型,盒子的(content)宽度+内边距padding+边框border宽度=我们设置的width(height也是如此),
盒子总宽度/高度=width/height + margin = 内容区宽度/高度 + padding + border + margin。
box-sizing: inherit;// 规定应从父元素继承 box-sizing 属性的值。
问:两种模式下如何解决样式的兼容性问题?
答:建议不要给元素添加具有指定宽度的内边距,而是尝试将内边距或外边距添加到元素的父元素和子元素。
position定位
position有五个属性:static , relative , absolute , fixed , sticky
static:默认值,元素按照正常的文档进行排序,不会收top bottom left right的影响
relative:相对定位的元素会在文档流中进行偏移,受top , bottom , left , right 的影响。
absolute:绝对定位会脱离正常的文档流,相对于最近的父级元素定位,若父级没有relative,就相对于浏览器的窗口进行定位。
fixed:固定定位同样会脱离正常的文档流,一直相对于浏览器窗口定位,无论页面如何滑动,此元素总在屏幕的同一位置。
sticky:粘性定位需要指定top,left,right,bottom四个中的一个才能生效。设置的内容即为元素在可视区域中与边界的距离。
怎样使一个元素垂直居中
1.使用绝对定位
div{
position:absolute;
width:100px;
height:100px;
left:50%;
top:50%;
margin-left:-50px;
margin-top:-50px;
background-color:skyblue;
}
2.使用transform属性
div{
position:absolute;
width:100px;
height:100px;
left:50%;
top:50%;
transform:translate(-50%,-50%)
background-color:skyblue;
}
3.使用弹性布局
.container{
display:flex;
align-items:center;
justify-content:center;
}
.container div{
width:100px;
height:100px;
background-color:hotpink;
}
css选择器有哪些?哪些属性可以继承
1.id选择器(#id);
2.类选择器(.class);
3.标签选择器(div);
4.相邻选择器(h1+p);
5.子选择器(ul>li);
6.后代选择器(li a);
7.通配符(*);
8.属性选择器(a[rel = 'xyz'];
9.伪类选择器(a:hover,li:nth-child);
可继承样式:font-size font-family color ul li dl dd dt;
不可继承样式:border padding margin width height;
CSS中的伪类及伪元素有哪些?
伪类:
:active,将样式添加到被激活的元素。
:focus,将样式添加到被选中的元素。
:hover,当鼠标悬浮在元素上方是,向元素添加样式。
:link,将特殊的样式添加到未被访问过的链接。
:visited,将特殊的样式添加到被访问的链接。
:first-child,将特殊的样式添加到元素的第一个子元素。
:lang,允许创作者来定义指定的元素中使用的语言。
伪元素:
:first-letter,将特殊的样式添加到文本的首字母。
:first-line,将特殊的样式添加到文本的首行。
:before,在某元素之前插入某些内容。
:after,在某元素之后插入某些内容。
CSS中的BFC是什么?
BFC(Block Formatting Context)格式化上下文,是Web页面中盒模型布局的CSS渲染模式,
指一个独立的渲染区域或者说是一个隔离的独立容器。
形成BFC的条件有:
1、浮动元素,float 除 none 以外的值;
2、定位元素,position(absolute,fixed);
3、display 为以下其中之一的值 inline-block,table-cell,table-caption;
4、overflow 除了 visible 以外的值(hidden,auto,scroll);
特性:
1.内部的Box会在垂直方向上一个接一个的放置。
2.垂直方向上的距离由margin决定(外边距不会合并)
3.bfc的区域不会与float的元素区域重叠。
4.计算bfc的高度时,浮动元素也参与计算
5.bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。
杂
对前端工程化的理解
工程化是一种思想,而不是某种技术。其主要目的为了提高效率和降低成本,
即提高开发过程中的开发效率,减少不必要的重复工作时间等
应该从模块化、组件化、规范化、自动化4个方面去思考
# 模块化
模块化就是把一个大的文件,拆分成多个相互依赖的小文件,按一个个模块来划分
#组件化
页面上所有的东西都可以看成组件,页面是个大型组件,可以拆成若干个中型组件,
然后中型组件还可以再拆,拆成若干个小型组件
组件化≠模块化。模块化只是在文件层面上,对代码和资源的拆分;
组件化是在设计层面上,对于UI的拆分
目前市场上的组件化的框架,主要的有Vue,React,Angular2
# 规范化
在项目规划初期制定的好坏对于后期的开发有一定影响。包括的规范有
. 目录结构的制定
. 编码规范
. 前后端接口规范
. 文档规范
. 组件管理
. Git分支管理
. Commit描述规范
. 定期codeReview
. 视觉图标规范
#自动化
也就是简单重复的工作交给机器来做,自动化也就是有很多自动化工具代替我们来完成,
例如持续集成、自动化构建、自动化部署、自动化测试等等
. List item