一、Vue
文章目录
- 一、Vue
- 1、vue 修改数据页面不重新渲染
- **数组/对象的响应式 ,vue 里面是怎么处理的?**
- 2、生命周期
- Vue 生命周期都有哪些?
- 父子组件生命周期执行顺序
- 3、watch 和 computed 的区别
- 4、组件通信(组件间传值)
- 5、$nextTick
- 6、修饰符
- 事件修饰符
- v-bind 修饰符
- 鼠标修饰符
- 表单相关修饰符
- 系统修饰符
- 按键修饰符
- 7、图片上传 TODO⭐⭐
- 8、v-for 和 v-if / v-if 和 v-show
- 9、Vue.use()
- 10、 跨域
- **什么是跨域**
- 跨域解决方法 1-代理
- 跨域解决方法 2-CORS
- 跨域解决方法 3-JSONP
- 11、cookie
- 12、[keep-alive 原理](https://blog-1gxfs2h1401f4f58-1254415986.tcloudbaseapp.com/%E9%9D%A2%E8%AF%95%E9%A2%98%E7%B3%BB%E5%88%97/%E4%BA%8C%E3%80%81%E5%AF%B9keep-alive%E7%9A%84%E7%90%86%E8%A7%A3.html#%E5%8E%9F%E7%90%86)
- keep-alive 是什么
- **使用场景**
- 缓存后如何获取数据
- 13、ref
- 14、scoped 原理是什么?
- 15、$router 和 $route
- 16、[发布订阅模式和观察者模式](https://juejin.cn/post/7055441354054172709#heading-3)
- 发布订阅模式
- 观察者模式
- 总结
- 17、[vue 响应式原理(数据劫持)](https://blog-1gxfs2h1401f4f58-1254415986.tcloudbaseapp.com/vue%E5%8E%9F%E7%90%86/vue2%E5%8E%9F%E7%90%86/1.%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86.html)
- 18、eventBus
- 19、v-model 与.sync
- 20、如何做样式穿透
- 21、你对SPA单页面的理解,它的优缺点分别是什么?
- 单页应用优缺点
- 22、怎么理解 Vue 的单向数据流
- 二、JS
- 1、手动实现`防抖`和`节流`
- 2、let、const、var 的区别
- 3、箭头函数与普通函数区别
- 4、Promise
- 5、数据类型
- 6、检测数据类型的常用方法
- 1 .typeof
- **2 . instanceof**
- **3 .constructor**
- **4 . 使用 Object.prototype.toString.call()检测对象类型**⭐
- **5 . 自己封装函数**
- **6、isArray**
- 7、数组的常用方法有哪些
- 一、操作方法
- 增
- 删
- 改
- 查
- 二、排序方法
- reverse()
- sort()
- 三、转换方法
- join()
- 四、迭代方法
- some()
- every()
- forEach()
- filter()
- map()
- 8、深浅拷贝
- 深拷贝和浅拷贝的区别
- 浅拷贝
- 深拷贝
- 9、闭包
- 10、数组去重
- **Set 去重**
- indexOf 去重
- splice 去重
- includes 去重
- **filter 去重**
- **findIndex**数组对象去重
- **Map**数组对象去重
- reduce 数组对象去重
- lodash 库数组和数组对象去重:
- 11、逻辑运算符 && 和 ||
- 12、new 的过程
- 13、事件循环 Event Loop (结合着14条讲)
- 14、async 和 await 宏任务 和 微任务
- 宏任务 和 微任务
- 1、什么是宏任务和微任务
- 2、宏任务和微任务的执行顺序
- async与await
- async
- await
- 15、**call、apply、bind 的区别**
- 16、继承
- Class 继承
- ES5 继承
- 17、原型链
- 18、堆和栈 TODO ⭐⭐
- 19、ES6 Set 和 Map TODO ⭐⭐
- 20、**includes**
- 21、**find**
- 三、Git
- 1、列举工作中常用的几个 git 命令?
- 四、HTML-C3
- 1、盒子水平垂直局中的方法
- flex 布局
- **Position + Transform**
- 利用**Position** + **margin:auto**
- **inline-block**
- **table 布局**
- 总结
- 内联元素居中布局
- 块级元素居中布局
- 2、BFC(经典面试题)
- 3、盒模型
- 4、flex:1
- 5、c3 新属性
- 6、
- 五、HTTP
- HTTP 缓存
- HTTP 状态码
- 信息响应
- 成功的响应
- 重定向
- 客户端错误
- 服务端错误
- 5、c3 新属性
- 6、
- 五、HTTP
- HTTP 缓存
- HTTP 状态码
- 信息响应
- 成功的响应
- 重定向
- 客户端错误
- 服务端错误
1、vue 修改数据页面不重新渲染
vue2
是用过Object.defineProperty
实现数据响应式, 组件初始化时,对 data 中的 item 进行递归遍历,对 item 的每一个属性进行劫持,添加 set , get 方法。我们后来 新加的属性 ,并没有通过Object.defineProperty
设置成响应式数据,修改后不会视图更新。
通过数组索引号修改了数组,界面会不会相应更新?为什么?
答:不会。vue 监听不到
vue 为什么没有提供 arr[下标] = val
变成响应式?
尤大:“因为性能问题,性能代价和获得的用户体验收益不成正比”
数组/对象的响应式 ,vue 里面是怎么处理的?
对象:使用了Object.defineProperty
中的 get 和 set
如何监测对象中的数据?
通过 setter 实现监视,且要在 new Vue 时就传⼊要监测的数据
- 对象中后追加的属性,Vue 默认不做响应式处理
- 如需给后添加的属性做响应式,请使⽤如下 API:
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
数组: Vue
重写了数组的原型,更准确的表达是拦截了数组的原型
如何监测数组中的数据?
通过包裹数组更新元素的⽅法实现,本质就是做了两件事:
调⽤原⽣对应的⽅法对数组进⾏更新
重新解析模板,进⽽更新⻚⾯
在 Vue 修改数组中的某个元素⼀定要⽤如下⽅法:
- 使 ⽤ 这 些 API :
push() pop() shift() unshift() splice() sort() reverse()
Vue.set()
或vm.$set()
- 覆盖整个数组
为什么对象和数组要分开处理?
对象
的属性通常比较少,对每一个属性都劫持set和get
,并不会消耗很多性能
数组
有可能有成千上万个元素,如果每一个元素都劫持set和get
,无疑消耗太多性能了所以
对象
通过defineProperty
进行正常的劫持set和get
数组
则通过修改数组原型上的部分方法
,来实现修改数组触发响应式
2、生命周期
Vue 生命周期都有哪些?
生命周期 | 执行时机 |
---|---|
beforeCreate | 在组件实例被创建之初、组件的属性⽣效之前被调用 |
created | 在组件实例已创建完毕。此时属性也已绑定,但真实 DOM 还未⽣成,$el 还不可⽤ |
beforeMount | 在组件挂载开始之前被调⽤。相关的 render 函数⾸次被调⽤ |
mounted | 在 el 被新建的 vm.$el 替换并挂载到实例上之后被调用 |
beforeUpdate | 在组件数据更新之前调⽤。发⽣在虚拟 DOM 打补丁之前 |
update | 在组件数据更新之后被调用 |
activited | 在组件被激活时调⽤(使用了 <keep-alive> 的情况下) |
deactivated | 在组件被销毁时调⽤(使用了 <keep-alive> 的情况下) |
beforeDestory | 在组件销毁前调⽤ |
destoryed | 在组件销毁后调⽤ |
父子组件生命周期执行顺序
加载渲染过程
->父beforeCreate
->父created
->父beforeMount
->子beforeCreate
->子created
->子beforeMount
->子mounted
->父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
keep-alive
可以实现组件缓存,当组件切换时不会对当前组件进行卸载
https://juejin.cn/post/6844903641866829838#heading-11
https://juejin.cn/post/7114252241166401573
3、watch 和 computed 的区别
官方文档:对于任何复杂逻辑,你都应当使用计算属性
扩展:vue 中 Computed、Methods、Watch 区别
computed(计算属性) | watch(监视属性/侦听器) |
---|---|
根据你所依赖的数据动态显示新的计算结果 不用再 data 中声明,否则报错 | data 的数据监听回调,依赖 data 的数据变化 直接使用 data 声明的数据 |
支持缓存 | 不支持缓存 |
不支持异步 | 支持异步 |
有 get 和 set 方法,当数据变化时,调用 set 方法 | 可以深度监视 deep,加载就调用 immediate 监听的函数接收两个函数,newVal 和 oldVla |
当需要进行数值计算,并且依赖于其它数据时,用 computed | 在某个数据变化时做一些事情或需要异步操作时,用 watch |
computed 能做的 | watch 都能做到 |
4、组件通信(组件间传值)
props
$emit
.sync
v-model
$parent / $children
$parent 获取父组件的实例,任意调用父组件的方法,修改父组件的数据ref
父组件获取子组件实例,任意调用子组件的方法获取子组件的属性provide / inject
prpvide 父组件内部提供数据 inject 嵌套的子组件可以注入数据$attrs / $listeners
$attrs(没有被 props 接收的所有自定义属性) $listeners(可以获取所有的父组件传递过来的自定义事件)eventBus
定义一个事件总线 使用$on
绑定$emit
触发vuex
- 路由传参
https://www.wpgdadatong.com/cn/blog/detail?BID=B3650
https://juejin.cn/post/7110223595359436813
5、$nextTick
NextTick 是什么
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
// 修改数据
this.message = "修改后的值";
// 此时DOM还没有更新
console.log(this.$el.textContent); // => '原始的值'
this.$nextTick(function () {
// DOM 更新了
console.log(this.$el.textContent); // => '修改后的值'
});
6、修饰符
引用:https://juejin.cn/post/7026867875990208543#heading-22
事件修饰符
stop
stop
修饰符的作用是阻止冒泡
prevent⭐
prevent
修饰符的作用是阻止默认事件(例如 a 标签的跳转)
capture
- 事件流默认冒泡,使用 capture 进行捕获捕获
self
self
修饰符作用是,只有点击事件绑定的本身才会触发事件
once
once
修饰符的作用是,事件只执行一次
native⭐
native
修饰符是加在自定义组件的事件上,保证事件能执行native
是用来是在父组件中给子组件绑定一个原生的事件,就将子组件变成了普通的 HTML 标签看待
passive
- 当我们在监听元素滚动事件的时候,会一直触发 onscroll 事件,在 pc 端是没啥问题的,但是在移动端,会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给 onscroll 事件整了一个.lazy 修饰符
v-bind 修饰符
sync⭐
-
当
父组件
传值进子组件
,子组件想要改变这个值时,可以这么做 -
// 父组件里 <children :foo.sync="bar"></children> // 子组件里 this.$emit('update:foo', newValue)
camel
.camel
修饰符允许在使用 DOM 模板时将 v-bind
property 名称驼峰化,例如 SVG 的 viewBox
property:
<svg :view-box.camel="viewBox"></svg>
鼠标修饰符
left
right
middle
- 这三个修饰符是鼠标的左中右按键触发的事件
表单相关修饰符
trim⭐
trim
修饰符的作用类似于 JavaScript 中的trim()
方法,作用是把v-model
绑定的值的首尾空格给过滤掉。
lazy
lazy
修饰符作用是,改变输入框的值时 value 不会改变,当光标离开输入框时,v-model
绑定的值 value 才会改变
number
number
修饰符的作用是将值转成数字,但是先输入字符串和先输入数字,是两种情况:
先输入数字的话,只取前面数字部分
先输入字母的话,
number
修饰符无效
系统修饰符
.ctrl
、.alt
、.shift
、.meta
按键修饰符
配合键盘事件使用:
.enter
.tab
.delete
.esc
.space
.up
.down
left
…
7、图片上传 TODO⭐⭐
FileReader 与 URL.createObjectURL 实现图片、视频上传预览
event.target.files 就是用户上传的图片信息
配合 cropperjs 可以实现图片裁剪功能
// 如果接口要求 Content-Type 是 multipart/form-data
// 则你必须传递 FormData 对象
8、v-for 和 v-if / v-if 和 v-show
为什么 v-if 和 v-for 不能同时使用?
-
v-if 不能和 v-for 一起使用的原因是 v-for 的优先级比 v-if 高,一起使用会造成性能浪费
-
解决方案有两种,把 v-if 放在 v-for 的外层或者把需要 v-for 的属性先从计算属性中过滤一次
-
v-if 和 v-for 的优先级问题在 vue3 中不需要考虑,vue3 更新了 v-if 和 v-for 的优先级,使 v-if 的优先级高于 v-for
v-if 和 v-show
v-show
隐藏则是为该元素添加display:none
。v-if
是将dom
元素整个添加或删除
v-show
由false
变为true
的时候不会触发组件的生命周期v-if
由false
变为true
的时候,触发组件的beforeCreate
、create
、beforeMount
、mounted
钩子,由true
变为false
的时候触发组件的beforeDestory
、destoryed
方法
性能消耗:v-if
有更高的切换消耗;v-show
有更高的初始渲染消耗;
如果需要非常频繁地切换,则使用 v-show 较好
如果在运行时条件很少改变,则使用 v-if 较好
9、Vue.use()
Vue.use
是用来安装插件的
用法:Vue.use(plugin)
- 如果插件是一个对象,必须提供
install
方法。 - 如果插件是一个函数,它会被作为 install 方法。install 方法调用时,
会将 Vue 作为参数传入
。 - Vue.use(plugin)调用之后,插件的 install 方法就会默认接受到一个参数,这个参数就是 Vue
总结:Vue.use 是官方提供给开发者的一个 api,用来注册、安装类似 Vuex、vue-router、ElementUI 之类的插件的。
10、 跨域
什么是跨域
跨域问题是浏览器的同源策略所导致的
其中,域名、协议、端口号相同,称之为同源,如果不同,称之为跨源或跨域
跨域常见的解决方法:
- 代理,常用
- CORS,常用
- JSONP
跨域解决方法 1-代理
代理适用的场景是:生产环境不发生跨域,但开发环境发生跨域
因此,只需要在开发环境使用代理解决跨域即可,这种代理又称之为开发代理
在实际开发中,只需要对开发服务器稍加配置即可完成
// vue 的开发服务器代理配置
// vue.config.js
module.exports = {
devServer: {
// 配置开发服务器
proxy: {
// 配置代理
"/api": {
// 若请求路径以 /api 开头
target: "http://dev.taobao.com", // 将其转发到 http://dev.taobao.com
},
},
},
};
跨域解决方法 2-CORS
阮一峰 CORS: https://www.ruanyifeng.com/blog/2016/04/cors.html
CORS
是基于http1.1
的一种跨域解决方案,它的全称是Cross-Origin Resource Sharing,跨域资源共享。CORS 需要浏览器和后端同时支持。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
只要同时满足以下两大条件,就属于简单请求。
凡是不同时满足下面两个条件,就属于非简单请求。
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP 的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
跨域解决方法 3-JSONP
JSONP 的做法是:当需要跨域请求时,不使用 AJAX,转而生成一个 script 元素去请求服务器,由于浏览器并不阻止 script 元素的请求,这样请求可以到达服务器。服务器拿到请求后,响应一段 JS 代码,这段代码实际上是一个函数调用,调用的是客户端预先生成好的函数,并把浏览器需要的数据作为参数传递到函数中,从而间接的把数据传递给客户端
JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。
11、cookie
什么是 cookie
cookie 是储存在用户本地终端上的数据,是网站为了识别用户和跟踪会话而存储在用户本地终端中的文本数据
怎么操作
可以使用
js-cookie
插件模块化开发时直接引入
import Cookies from 'js-cookie'
js-cookie.js 常用的 API 和方法
设置 cookie
Cookies.set("name", "value", { expires: 7, path: "" }); //7天过期 Cookies.set("name", { foo: "bar" }); //设置一个json
读取 cookie
Cookies.get("name"); //获取cookie Cookies.get(); //读取所有的cookie
删除 cookie
Cookies.remove("name"); //删除cookie时必须是同一个路径。
12、keep-alive 原理
keep-alive 是什么
- vue 自带的组件 >> 主要功能是缓存组件 >> 提升性能
- 使用场景:可以少网络请求,如果当前组件数据量比较大,就可以节省网络请求 >> 提升用户体验
- 举例:如果详情页面之间进行切换,就可以使用
keep-alive
进行缓存组件,防止同样的数据重复请求
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
keep-alive
可以设置以下props
属性:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存max
- 数字。最多可以缓存多少组件实例
关于keep-alive
的基本用法:
<keep-alive>
<component :is="view"></component>
</keep-alive>
匹配首先检查组件自身的 name
选项,如果 name
选项不可用,则匹配它的局部注册名称 (父组件 components
选项的键值),匿名组件不能被匹配
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
- 首次进入组件时:
beforeRouteEnter
>beforeCreate
>created
>mounted
>activated
> … … >beforeRouteLeave
>deactivated
- 再次进入组件时:
beforeRouteEnter
>activated
> … … >beforeRouteLeave
>deactivated
使用场景
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
举个栗子:
当我们从首页
–>列表页
–>商详页
–>再返回
,这时候列表页应该是需要keep-alive
从首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive
属性判断是否需要缓存
{
path: 'list',
name: 'itemList', // 列表页
component (resolve) {
require(['@/pages/item/list'], resolve)
},
meta: {
keepAlive: true,
title: '列表页'
}
}
使用<keep-alive>
<div id="app" class='wrapper'>
<keep-alive>
<!-- 需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
缓存后如何获取数据
解决方案可以有以下两种:
- beforeRouteEnter
- actived
13、ref
作用:
- 放到 dom 节点上 >> 获取原生 dom
- 组件身上 >> 获取组件实例 >> 可以获取组件内部所有的方法和数据
14、scoped 原理是什么?
作用:使样式私有化(模块化),不对全局造成污染
原理:动态的给组件加上一个 hash 值,用属性选择器去匹配
15、$router 和 $route
$route:当前的路由信息对象,获取到路由参数、路径
- $route.path: 字符串,对应当前路由的路径,总是解析为绝对路径,如
/foo/bar
。- $route.params: 一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
- ** r o u t e . q u e r y : ∗ ∗ 一个 k e y / v a l u e 对象,表示 U R L 查询参数。例如,对于路径 / f o o ? u s e r = 1 ,则有 route.query:** 一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 route.query:∗∗一个key/value对象,表示URL查询参数。例如,对于路径/foo?user=1,则有route.query.user == 1,如果没有查询参数,则是个空对象。
- $route.hash: 当前路由的 hash 值 (不带#) ,如果没有 hash 值,则为空字符串。锚点
- $route.fullPath: 完成解析后的 URL,包含查询参数和 hash 的完整路径。
- $route.matched: 数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
- $route.name: 当前路径名字
- $route.meta: 路由元信息
** r o u t e r ∗ ∗ :全局路由 v u e R o u t e r 的实例,挂载到 V u e 原型上 router**:全局路由 vueRouter 的实例,挂载到 Vue 原型上 router∗∗:全局路由vueRouter的实例,挂载到Vue原型上router 属性,可以获取到全局路由配置信息,跳转方法
$router.replace({path:‘home’}),//替换路由,没有历史记录
$router.push(‘/login’) ,跳转到指定路由
$router.back()
$router.go()
16、发布订阅模式和观察者模式
发布订阅模式
发布订阅模式中有三个角色,发布者
Publisher
,信息中心Event Channel
,订阅者Subscriber
。我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern)
通常是通过 on 事件订阅消息,emit 事件发布消息,remove 事件删除订阅
class PubSub {
constructor() {
// 事件中心
// 存储格式: warTask: [], routeTask: []
// 每种事件(任务)下存放其订阅者的回调函数
this.events = {};
}
// 订阅方法
subscribe(type, cb) {
if (!this.events[type]) {
}
this.events[type].push(cb);
}
// 发布方法
publish(type, ...args) {
if (this.events[type]) {
this.events[type].forEach((cb) => cb(...args));
}
}
// 取消订阅方法
unsubscribe(type, cb) {
if (this.events[type]) {
const cbIndex = this.events[type].findIndex((e) => e === cb);
if (cbIndex !== -1) {
this.events[type].splice(cbIndex, 1);
}
}
if (this.events[type].length === 0) {
delete this.events[type];
}
}
unsubscribeAll(type) {
if (this.events[type]) {
delete this.events[type];
}
}
}
观察者模式
当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式
- 观察者(订阅者) – Watcher
- update():当事件发生时,具体要做的事情
- 目标(发布者) – Dep
- subs 数组:存储所有的观察者
- addSub():添加观察者
- notify():当事件发生,调用所有观察者的 update() 方法
- 没有事件中心
总结
设计模式 | 观察者模式 | 发布订阅模式 |
---|---|---|
主体 | Watcher 观察者、Dep 目标对象 | Publisher 发布者、Event Channel 信息中心、Subscribe 订阅者 |
主体关系 | Dep 中通过 subs 记录 Watcher | Publisher 和 Subscribe 不想不知道对方,通过中介联系 |
优点 | 角色明确,Watcher 和 Dep 要遵循约定的成员方法 | 松散耦合,灵活度高,通常应用在异步编程中 |
缺点 | 紧耦合 | 当事件类型变多时,会增加维护成本 |
使用案例 | 双向数据绑定 | 事件总线 EventBus |
17、vue 响应式原理(数据劫持)
18、eventBus
19、v-model 与.sync
相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。
// v-model <son v-model="num"/> //父组件使用子组件 model:{ prop:'newValue', // 默认为 value 可以使用prop自定义属性名 event:"updateValue", // event 修改事件名 默认为input }, props: { // 子组件接收 value: { // 默认为value type: Number, } } // .sync <son :title.sync="doc.title"></son> // 父组件 props:{ title:{ type:... } } this.$emit('update:title', newTitle) // 子组件
区别点:格式不同: v-model=“num”, :num.sync=“num”
v-model
: @input + value
:num.sync
: @update:num另外需要特别注意的是:
v-model
只能用一次;.sync
可以有多个
20、如何做样式穿透
背景:修改当前组件嵌套的子组件内部的样式
问题:
- 如果不添加 scoped,可能影响全局样式
- 如果添加 scoped,嵌套的子组件内部样式不能生效
如何解决:添加/deep/
/ ::v-deep
scss: 使用
::v-deep
less: 使用
/deep/
21、你对SPA单页面的理解,它的优缺点分别是什么?
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
单页应用优缺点
优点:
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要重新加载整个页面,SPA 相对对服务器压力小;
- 良好的前后端分离,分工更明确
缺点:
- SEO(搜索引擎)难度较大
- 首次渲染速度相对较慢
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
22、怎么理解 Vue 的单向数据流
①单向数据流是指数据从父组件传向子组件,子组件没有权限直接修改该数据;
②子组件需要在 data 或者 computed 中重新定义变量来接收父组件传来的值,以便修改;
③子组件可以通过 $emit 的方式通知父组件修改值,再重新传回给子组件;
二、JS
1、手动实现防抖
和节流
防抖:在限定时间内,总是执行最后一次 ( 类似回城,打断就得重新回 )
节流:在限定时间内,只会执行第一次 ( 类似技能需要冷却时间到了才能用 )
/**
* debounce 防抖
* @param fn [function] 需要防抖的函数
* @param delay [number] 毫秒,防抖期限值
*/
function debounce(fn, delay) {
let timer = null; //借助闭包
return function (...arg) {
if (timer) {
//进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。
// 所以要取消当前的计时,重新开始计时
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arg); // 使用apply将fn函数的this指向修改为return后的function
}, delay); // 进入该分支说明当前并没有在计时,那么就开始一个计时
};
}
/**
* debounce 节流
* @param fn [function] 需要节流的函数
* @param delay [number] 毫秒
*/
function throttle(fn, delay) {
let valid = false; // 节流阀
return function (...arg) {
if (valid) {
//休息时间 暂不接客
return;
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = true;
setTimeout(() => {
fn.apply(this, arg);
valid = false;
}, delay);
};
}
2、let、const、var 的区别
1.是否存在变量提升?
var
声明的变量存在变量提升(将变量提升到当前作用域的顶部)。即变量可以在声明之前调用,值为undefined
let
和const
不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError
错2.是否存在暂时性死区?
let和const存在暂时性死区
。即只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响3.是否允许重复声明变量?
var
允许重复声明变量。
let
和const
在同一作用域不允许重复声明变量。4.是否存在块级作用域?
- var 不存在块级作用域。
- let 和 const 存在块级作用域。
- 块作用域由
{ }
包括,if
语句和for
语句里面的{ }
也属于块作用域5. 是否能修改声明的变量?
var
和let
可以。const
声明一个只读的常量。一旦声明,常量的值就不能改变。const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
3、箭头函数与普通函数区别
1.箭头函数是匿名函数,不能作为构造函数,不能使用 new
2.箭头函数内没有
arguments
,可以用展开运算符...
解决3.箭头函数的 this,始终指向父级上下文(箭头函数的
this
取决于定义位置父级的上下文
,跟使用位置没关系,普通函数this
指向调用的那个对象)4.箭头函数不能通过
call() 、 apply() 、bind()
方法直接修改它的 this 指向。(call、apply、bind
会默认忽略第一个参数,但是可以正常传参)5.箭头函数没有原型属性
4、Promise
-
状态
-
进行中:
pending
-
已成功:
resolved
-
已失败:
rejected
-
-
特点
-
对象的状态不受外界影响
-
一旦状态改变就不会再变,任何时候都可得到这个结果
-
-
声明:
new Promise((resolve, reject) => {})
-
出参
-
resolve:将状态从
未完成
变为成功
,在异步操作成功时调用,并将异步操作的结果作为参数传递出去 -
reject:将状态从
未完成
变为失败
,在异步操作失败时调用,并将异步操作的错误作为参数传递出去
-
① 什么是 Promise?
- Promise,简单说就是一个
容器
,包含异步操作结果的对象 - 从语法上说,promise 是一个
对象
,从它可以获取异步操作的的最终状态(成功或失败)。 - Promise 是一个
构造函数
,对外提供统一的 API,自己身上有 all、reject、resolve 等方法,原型上有 then、catch 等方法。
② Promise 有什么用?
解决回调地狱
③Promise 有哪些方法
-
**then():**分别指定
resolved状态
和rejected状态
的回调函数-
第一参数:状态变为
resolved
时调用 -
第二参数:状态变为
rejected
时调用(可选) -
链式调用 promise.then():
then
方法返回一个 Promise 对象,其允许方法链,从而创建一个 promise 链
-
-
catch():指定发生错误时的回调函数
-
Promise.resolve():将对象转为 Promise 对象 等价于
new Promise(resolve => resolve())
-
Promise 实例:原封不动地返回入参
-
thenable 对象:
thenable
对象指的是具有then
方法的对象Promise.resolve
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then
方法 -
不具有 then()的对象:将此对象转为 Promise 对象并返回,状态为
resolved
-
不带参数:返回 Promise 对象,状态为
resolved
-
-
Promise.reject():将对象转为状态为
rejected
的 Promise 对象(等价于new Promise((resolve, reject) => reject())
) -
Promise.all():
并发,发起多个并发请求,将多个实例包装成一个新实例(数组形式),然后在所有 promise 都被解决后执行一些操作(齐变更再返回)
- 成功:只有全部实例状态变成
fulfilled
( 成功 ),最终状态才会变成fulfilled
- 失败:其中一个实例状态变成
rejected
,最终状态就会变成rejected
- 每一个 promise 成功的值,会按照传入的顺序返回数组内
- 用 all 方法进行接口请求,就算其中有失败的情况,别的请求也会进行,但最后的状态还是
rejected
- 成功:只有全部实例状态变成
-
Promise.race():
赛跑机制
将多个实例包装成一个新实例,返回全部实例状态优先变更后的结果(先变更先返回)
- 成功失败:哪个实例率先改变状态就返回哪个实例的状态
- Promise.finally():指定不管最后状态如何都会执行的回调函数
- **Promise.allSettled()**⭐:将多个实例包装成一个新实例,返回全部实例状态变更后的状态数组(齐变更再返回)
- 成功:成员包含
status
和value
,status
为fulfilled
,value
为返回值 - 失败:成员包含
status
和value
,status
为rejected
,value
为错误原因
- 成功:成员包含
- Promise.any():将多个实例包装成一个新实例,返回全部实例状态变更后的结果数组(齐变更再返回)
-
成功:其中一个实例状态变成
fulfilled
,最终状态就会变成fulfilled
-
失败:只有全部实例状态变成
rejected
,最终状态才会变成rejected
- Promise.try():不想区分是否同步异步函数,包装函数为实例,使用
then()
指定下一步流程,使用catch()
捕获错误
常见的错误
-
Uncaught TypeError: undefined is not a promise
如果在控制台中收到
Uncaught TypeError: undefined is not a promise
错误,则请确保使用new Promise()
而不是Promise()
-
UnhandledPromiseRejectionWarning
这意味着调用的 promise 被拒绝,但是没有用于处理错误的 catch。 在 then 之后添加 catch 则可以正确地处理
扩展:手写 Promise
5、数据类型
原始数据类型(基本类型):按值访问,可以操作保存在变量中实际的值。
- 空值(
null
):用于未知的值 —— 只有一个null
值的独立类型。 - 未定义(
undefined
): 用于未定义的值 —— 只有一个undefined
值的独立类型。 - 布尔值(
boolean
):用于true
和false
。 - 数字(
number
):用于任何类型的数字:整数或浮点数,在±(253-1)
范围内的整数。 - 字符串(
string
):用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。 - 符号(
symbol
):用于唯一的标识符。
引用类型(复杂数据类型):引用类型的值是保存在内存中的对象。
- 对象(Object)
- 数组对象(Array)
- 函数对象(Function)
- 布尔对象(Boolean)
- 数字对象(Number)
- 字符串对象(String)
- 日期对象(Date)
- 正则对象(RegExp)
- 错误对象(Error)
⚠️ 注意: 与其他语言不同的是,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。所以引用类型的值是按引用访问的。
6、检测数据类型的常用方法
typeof、instanceof、constructor、Object.prototype.toString.call()
1 .typeof
console.log(
typeof 100, //"number"
typeof undefined, //"undefined"
typeof null, //"object"
typeof function () {
console.log("aaa");
}, //"function"
typeof new Number(100), //'object'
typeof new String("abc"), // 'string'
typeof new Boolean(true) //'boolean'
);
typeof 可以正常检测出:number、boolean、string、object、function、undefined、symbol、bigint
- 检测基本数据类型,null 会检测 object,因为 null 是一个空的引用对象
- 检测复杂数据类型,除 function 外,均为 object
2 . instanceof
instanceof
运算符需要指定一个构造函数,或者说指定一个特定的类型,用来判断这个构造函数的原型是否在给定对象的原型链上
基本数据类型中:Number,String,Boolean。字面量值不可以用 instanceof 检测,但是构造函数创建的值可以
注意:null 和 undefined 都返回了 false,这是因为它们的类型就是自己本身,并不是 Object 创建出来它们,所以返回了 false。
console.log(
100 instanceof Number, //false
undefined instanceof Object, //false
[1, 2, 3] instanceof Array, //true
new Error() instanceof Error //true
);
3 .constructor
constructor 是 prototype 对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用 constructor 属性的。可以检测出字面量方式创建的对象类型
如果输出一个类型的实例的 constructor,就如下所示:
console.log(new Number(123).constructor);
//ƒ Number() { [native code] }
可以看到它指向了 Number 的构造函数,因此,可以使用num.constructor === Number
来判断一个变量是不是 Number 类型的
除了 undefined 和 null 之外,其他类型都可以通过 constructor 属性来判断类型。
var num = 123;
var str = "abcdef";
var bool = true;
var arr = [1, 2, 3, 4];
// undefined和null没有constructor属性
console.log(
num.constructor === Number,
str.constructor === String,
bool.constructor === Boolean,
arr.constructor === Array
);
//所有结果均为true
4 . 使用 Object.prototype.toString.call()检测对象类型⭐
const toString = Object.prototype.toString;
toString.call(123); //"[object Number]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
可以使用Object.prototype.toString.call(obj).slice(8,-1)
来判断并截取
使用Object.prototype.toString.call()
的方式来判断一个变量的类型是最准确的方法
5 . 自己封装函数
function getType(obj) {
const type = typeof obj;
if (type !== "object") {
return type;
}
//如果不是object类型的数据,直接用typeof就能判断出来
//如果是object类型数据,准确判断类型必须使用Object.prototype.toString.call(obj)的方式才能判断
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)]$/, "$1");
}
6、isArray
isArray 可以检测出是否为数组
const arr = [];
Array.isArray(arr); // true
7、数组的常用方法有哪些
一、操作方法
增
下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
push( ) unshift( ) splice( ) concat( )
push()
push()
方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
unshift()
unshift()在数组开头添加任意多个值,然后返回新的数组长度
splice
传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回的是空数组
concat()
合并
首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
let colors = ["red"].concat("yellow", ["black"]);// ["red", "yellow", "black"]
删
下面三种都会影响原数组,最后一项不影响原数组:
pop() shift() splice() slice()
pop()
pop()
方法用于删除数组的最后一项,同时减少数组的length
值,返回被删除的项
shift()
shift()
方法用于删除数组的第一项,同时减少数组的length
值,返回被删除的项
splice()
传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
slice()
slice(开始索引, 结束索引) ,返回一个新数组,不会影响原始数组
改
即修改原来数组的内容,常用splice
splice()
传入三个参数,分别是(开始位置,要删除元素的数量,要插入的任意多个元素),返回删除元素的数组
会改变原数组
查
查找元素,返回元素坐标或者元素值
indexOf( ) includes( ) find( )
indexOf()
返回要查找的元素在数组中的位置,如果没找到则返回 -1
includes()
返回要查找的元素在数组中的位置,找到返回true
,否则false
find()
返回第一个匹配的元素
二、排序方法
reverse()sort()
reverse()
翻转
sort()
排序
function sortArr(a, b) {
return a - b; // 升序
return b - a; // 降序
}
三、转换方法
join()
join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串,转为字符串
四、迭代方法
常用来迭代数组的方法(除 forEach 外其他都不会对空数组进⾏检测、不会改变原始数组)有如下:
some() every() forEach() filter() map()
some()
对数组每一项都运行传入的测试函数,如果至少有 1 个元素返回 true ,则这个方法返回 true
every()
对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true
forEach()
对数组每一项都运行传入的函数,没有返回值
filter()
对数组每一项都运行传入的函数,函数返回 true
的项会组成数组之后返回
map()
映射 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
8、深浅拷贝
基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中
深拷贝和浅拷贝的区别
1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
浅拷贝
数组浅拷贝:
// 直接遍历
function shallowCopy(arr) {
const newArr = [];
arr.forEach((item) => newArr.push(item));
return newArr;
}
// slice
const arr2 = [1, 2, 3, 4, 5];
const newArr2 = arr2.slice();
// concat() 合并空数组实现
const arr3 = [11, 22, 33, 44, 55];
const newArr3 = arr3.concat([]);
对象浅拷贝:
// 直接遍历
function shallowCopy(obj) {
const newObj = {};
for (let item in obj) {
newObj[item] = obj[item];
}
return newObj;
}
// 使用拓展运算符
const obj2 = { name: "Bob", age: 17 };
const newObj2 = { ...obj2 };
深拷贝
用深拷贝最后要递归到全部是基本值,不然可能会陷入死循环/循环引用,导致栈溢出
TODO(待理解)⭐: 处理过的数据使用 map 结构缓存起来 >> 递归的时候碰到相同的数据 >> 直接使用缓存里面的
1. 先转换成字符串,在转换成(数组/对象) JSON.parse(JSON.stringify(XXXX))
有一个缺点 里面的函数不能拷贝
const array = [{ number: 1 }, { number: 2 }, { number: 3 }];
const str = JSON.stringify(array);
const copyArray = JSON.parse(str);
递归实现简单的深拷贝:
function deepClone(obj = {}) {
if (typeof obj !== "object" || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj;
}
// 初始化返回结果
let result;
obj instanceof Array ? (result = []) : (result = {});
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key]);
}
}
// 返回结果
return result;
}
小结
前提为拷贝类型为引用类型的情况下:
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
9、闭包
什么是闭包
通俗地讲闭包就是在一个函数里边再定义一个函数,这个内部函数一直保持有对外部函数中作用域的访问权限(小房间一直可以有大房子的访问权限)
闭包的作用
- 访问其他函数内部变量
- 保护变量不被 JS 的垃圾回收机制回收
- 避免全局变量被污染 方便调用上下文的局部变量 加强封装性
闭包的优点
(一)变量长期驻扎在内存中
(二)另一个就是可以重复使用变量,并且不会造成变量污染
① 全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。
② 局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
③ 闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染
闭包的缺点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
10、数组去重
https://segmentfault.com/a/1190000016418021
Set 去重
const arr = [1, 2, 3, 3, 3, 2, 3, 4, 5, 4, 4];
const newArr = [...new Set(arr)]; // [ 1, 2, 3, 4, 5 ]
indexOf 去重
function unique(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) newArr.push(arr[i]);
}
return newArr;
}
splice 去重
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
includes 去重
function unique(arr) {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
if (!newArr.includes(arr[i])) {
newArr.push(arr[i]);
}
}
return newArr;
}
filter 去重
function unique(arr) {
return arr.filter(function (item, index, arr) {
//当前元素,在原始数组中的第一个索引===当前索引值,否则返回当前元素
return arr.indexOf(item) === index;
});
}
findIndex数组对象去重
function unique(arr) {
// todo:待总结数组去重方法
// 方法一
return arr.filter((item, index) => {
return arr.findIndex((child) => child.id === item.id) === index;
});
}
Map数组对象去重
function unique(arr) {
const res = new Map();
return arr.filter((item) => !res.has(item.id) && res.set(item.id, 1));
}
reduce 数组对象去重
function unique(arr) {
let obj = {};
return arr.reduce((pre, item) => {
obj[item.id] ? "" : (obj[item.id] = true && pre.push(item));
return pre;
}, []);
}
lodash 库数组和数组对象去重:
import { isEqual, uniqWith, uniqBy } from "lodash";
let arr = [
{ id: 1, name: "sli", year: 2012 },
{ id: 2, name: "ap", year: 2015 },
{ id: 1, name: "alslion", year: 2012 },
{ id: 3, name: "pose", year: 2012 },
{ id: 3, name: "pose", year: 2012 },
];
// 根据id去掉相同的元素:
console.log(uniqBy(arr, "id"));
// 深检查数组每一项进行去重:
console.log(uniqWith(arr, isEqual));
11、逻辑运算符 && 和 ||
||运算符: 条件 1 || 条件 2
- 若条件 1 为 true、返回条件 1
- 若条件 1 为 false、不管 || 后面是 true 还是 false、都是返回||后面的值、即则返回条件 2;
console.log(0 || ""); // ''
console.log("" || 0); // 0
&&运算符: 条件 1 && 条件 2
- 若条件 1 为 false、无论条件 2 为 true 或 false, 都将返回条件 1 的值;
- 若条件 1 为 true, 无论条件 2 为 true 或者 false, 都将返回条件 2 的值;
console.log(0 && ""); // 0
console.log("" && 0); // ''
12、new 的过程
分析一下 new 的整个过程:
- 1、创建一个空对象
- 2、this 指向 obj,并调用构造函数
- 3、继承构造函数的原型
- 4、返回对象
简单实现一下 new:
function myNew(fn, ...args) {
// 第一步:创建一个空对象
const obj = {};
// 第二步:this指向obj,并调用构造函数
fn.apply(obj, args);
// 第三步:继承构造函数的原型
obj.__proto__ = fn.prototype;
// 第四步:返回对象
return obj;
}
13、事件循环 Event Loop (结合着14条讲)
是什么?
首先,
JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,如果前面一个任务耗时太长,后续的任务不得不等待,可能会导致程序假死的问题,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript
中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。过程不断重复就是事件循环
14、async 和 await 宏任务 和 微任务
https://www.mianshiya.com/qd/bf4a0bf261c7e2500090d9482499675f
宏任务 和 微任务
下面代码执行顺序是什么
console.log(1) // 同步
setTimeout(()=>{ // 宏任务
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise') // 同步
resolve()
}).then(()=>{
console.log('then') // 微任务
})
console.log(3) // 同步
1
=>'new Promise'
=> 3
=> 'then'
=> 2
1、什么是宏任务和微任务
Javascript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
微任务:
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务:
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
promise里面的代码是同步任务 promise的方法.then()等是异步任务 微任务
2、宏任务和微任务的执行顺序
每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
1、微任务比宏任务的执行时间要早
2、微任务在DOM渲染之前执行,宏任务在DOM渲染之后执行
async与await
async
是异步的意思,await
是等待。所以可以理解async
就是用来声明一个异步方法,而await
是用来等待异步方法执行
async
async
函数返回一个promise
对象
await
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值
不管await
后面跟着的是什么,await
都会阻塞后面的代码(加入微任务列表)
下面代码执行顺序是什么:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
分析过程:
- 执行整段代码,遇到
console.log('script start')
直接打印结果,输出script start
- 遇到定时器了,它是宏任务,先放着不执行
- 遇到
async1()
,执行async1
函数,先打印async1 start
,下面遇到await
怎么办?先执行async2
,打印async2
,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码 - 跳到
new Promise
这里,直接执行,打印promise1
,下面遇到.then()
,它是微任务,放到微任务列表等待执行 - 最后一行直接打印
script end
,现在同步代码执行完了,开始执行微任务,即await
下面的代码,打印async1 end
- 继续执行下一个微任务,即执行
then
的回调,打印promise2
- 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印
settimeout
所以最后的结果是:script start
、async1 start
、async2
、promise1
、script end
、async1 end
、promise2
、settimeout
15、call、apply、bind 的区别
- 都可以改变
this
指向 - call 和 apply 会
立即执行
,bind 不会,而是返回一个函数 - call 和 bind 可以接收
多个参数
,apply
只能接受两个,第二个是数组
- bind 参数可以分多次传入
16、继承
继承的是 属性 和 原型方法
Class 继承
ES6:https://es6.ruanyifeng.com/#docs/class-extends
Class 可以通过
extends
关键字实现继承,让子类继承父类的属性和方法子类如果写
constructor()
就必须要写super()
,且要写在最前面,否则报错,只有super()
方法才能让子类实例继承父类。
class Parent {
constructor(x,y) {
this.x = x
this.y = y
...
}
toString() {
...
}
}
class Son extends Parent {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
ES5 继承
js 中有很多中继承的方式,不过每一种继承方式都有优缺点,重点掌握 ES5 继承,别的继承方式基本都是 ES5 继承的语法糖
先创造子类实例,通过
Parent.call(this, arg1, arg2...)
将父类的属性方法添加到this
上,继承了父类的属性再通过
Son.prototype = Object.create( Father.prototype )
将父类的原型继承过来最后可以通过
Son.prototype.constructor = Son
将子类的原型指到子类身上
function Father(name) {
this.name = name;
}
Father.prototype.get = function () {
return "黑马";
};
// 继承的是 属性 和 原型方法
function Son(name) {
Father.call(this, name);
}
// Son.prototype = new Father() // 相互影响 会存在一个 {name:undefined}
// Object.create 创造出一个空对象
// 让当前对象的__proto__ 指向传入的对象
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
const son = new Son("程序员");
console.log(son);
console.log(son.get() + son.name); // 黑马程序员
17、原型链
什么是原型原型链?
每个函数都有一个 prototype 原型(原型就是对象),原型对象有一个 constructor 属性,指向的是构造函数
访问对象的某一个属性或者方法时,会从对象自身查找,如果查找不到,就会去原型链上去找,原型的最终目的就是让所有的实例能够共享其属性和方法
查找顺序:
自身 >
__proto__
> 构造函数的原型对象 >__proto__
> Object 的原型对象 >__proto__
> null
18、堆和栈 TODO ⭐⭐
https://juejin.cn/post/6844903618999500808
19、ES6 Set 和 Map TODO ⭐⭐
20、includes
includes()
方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
语法
arr.includes(valueToFind[, fromIndex])
-
valueToFind
需要查找的元素值。Note: 使用
includes()
比较字符串和字符时是区分大小写。 -
fromIndex
可选从
fromIndex
索引处开始查找valueToFind
。如果为负值,则按升序从array.length + fromIndex
的索引开始搜 (即使从末尾开始往前跳fromIndex
的绝对值个索引,然后往后搜寻)。默认为 0。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
21、find
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined
arr.find(callback[, thisArg])
-
callback
在数组每一项上执行的函数,接收 3 个参数:
element
当前遍历到的元素。index
可选当前遍历到的索引。array
可选数组本身。 -
thisArg
可选执行回调时用作
this
的对象。
三、Git
1、列举工作中常用的几个 git 命令?
git rm 删除工作区文件,并将这次删除放入暂存区
git add 增加指定文件到暂存区
git init 新建初始化 git 代码库
git status 显示有变更的文件
git branch 列出所有分支
git commit -m [message] 提交暂存区到仓库区,可选填备注信息 message
git checkout -b [branch] 新建分支,并切换到该分支
四、HTML-C3
1、盒子水平垂直局中的方法
flex 布局
/* html代码: */
<div class="wrap">
<div class="box">
</div>
</div>
/* CSS代码: */
.wrap{
/* 设置为弹性布局 */
display: flex;
/* 子元素在主轴对齐方式为居中 */
justify-content: center;
/* 交叉轴在y轴对齐 */
align-items: center;
}
Position + Transform
/* html代码: */
<div class="wrap">
<div class="box">
</div>
</div>
/* CSS代码: */
.wrap{
width: 600px;
height: 600px;
border: 2px solid black;
/* 设置为相对定位,用来作为绝对定位元素的容器块。 */
position: relative;
}
.box{
width:50px;
height: 50px;
/* 设置为绝对定位,位置在父容器的中心 */
position: absolute;
margin: auto;
left: 50%;
top:50%;
/* 向回移动自身一半的长宽 */
transform: translateX(-50%) translateY(-50%);
}
利用Position + margin:auto
<style>
.father {
margin: 100px auto;
width: 500px;
height: 300px;
border: 1px solid #0a3b98;
position: relative;
}
.son {
width: 100px;
height: 40px;
background: #f0a238;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
</style>
<div class="father">
<div class="son"></div>
</div>
inline-block
/* html代码: */
<div class="wrap">
<div class="box">
</div>
</div>
/* CSS代码: */
.wrap{
width: 600px;
border: 2px solid black;
/* 设置行高为600px */
line-height: 600px;
/* 让子盒子水平居中 */
text-align: center;
}
.box{
height: 50px;
width: 50px;
/* 设置为块级元素 */
display: inline-block;
/* 设置为垂直居中 */
vertical-align: middle;
background-color: red;
}
table 布局
<style>
.father {
display: table-cell;
width: 200px;
height: 200px;
background: skyblue;
vertical-align: middle;
text-align: center;
}
.son {
display: inline-block;
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="father">
<div class="son"></div>
</div>
总结
根据元素标签的性质,可以分为:
- 内联元素居中布局
- 块级元素居中布局
内联元素居中布局
水平居中
- 行内元素可设置:text-align: center
- flex 布局设置父元素:display: flex ; justify-content: center
垂直居中
- 单行文本父元素确认高度:height === line-height
- 多行文本父元素确认高度:disaply: table-cell; vertical-align: middle
块级元素居中布局
水平居中
- 定宽: margin: 0 auto
- 绝对定位 + left:50% + margin: 负自身一半
垂直居中
- position: absolute 设置 left、top、margin-left、margin-top(定高)
- display: table-cell
- transform: translate(x, y)
- flex (不定高,不定宽)
- grid(不定高,不定宽),兼容性相对比较差
2、BFC(经典面试题)
概念:
Block Formatting Context,翻译过来就是块级格式化上下文
bfc 实际是一种属性,拥有这种属性后,就会让该渲染区域独立,并且该渲染区域中的内容布局不会影响到外界
如何触发:
根元素(html)
float 属性不为 none
position 为 absolute 或 fixed
display 为 inline-block, table-cell, table-caption, flex, inline-flex
overflow 不为 visible
解决什么问题
- 外边距重叠
- 外边距重叠,要注意这不是 bug,规范就是这样的,当两个盒子上下同时拥有上下间距,会取最大值
- 清除浮动
- 当子盒子开启 float 后会影响后面的布局以及盒子高度
- 浮动覆盖
- 由于浮动导致盒子被覆盖
3、盒模型
盒模型主要分为 4 部分:内容、外边距、内边距、边框
Css3 盒子模型可以通过 box-sizing 来改变
标准盒模型(W3C 标准):content-box
盒子实际宽度加上 padding 和 border
ie 盒模型/怪异盒模型/c3 盒模型:box-sizing: border-box;
设置 width 后,实际盒子的宽度就固定为该宽度,包含了 内容 + padding + border
4、flex:1
flex:1 → {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;
}
- flex-grow:1
- flex-grow 是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用 flex-grow 来设置子盒子增大的占比
- flex-shrink: 1
- flex-shrink 用来设置子盒子超过父盒子的宽度后,进行缩小的比例取值
- flex-basis: 0%
- 设置盒子的基准宽度,并且 basis 和 width 同时存在会把 width 干掉
5、c3 新属性
- c3 盒模型 box-sizing
- flex 布局
- transition 过渡
- transform2D 转换
- background-size 背景缩放
- border-radius 圆角
- …
6、
五、HTTP
HTTP 缓存
https://juejin.cn/post/6844904153043435533#heading-1
HTTP 状态码
- 1xx - 信息响应 - 传达传输协议级别的信息
- 2xx - 成功的响应 - 表示客户端的请求已成功接受
- 3xx - 重定向 - 表示客户端必须采取一些额外的操作才能完成其请求
- 4xx - 客户端错误 - 此类错误状态码指向客户端
- 5xx - 服务端错误 - 服务器对这些错误状态代码负责
信息响应
- 100:接受的请求正在处理,信息类状态码
成功的响应
- 200:成功(OK) — 请求成功
- 201:正创建(Created) — 请求已完成,已创建新资源
重定向
- 301:永久移动(Moved Permanently) — 资源永久移动到新的 URL
- 302:临时移动(Moved Temporarily) — 资源临时移动到新的 URL
- 304:未修改(Not Modified)— 当协商缓存命中时会返回这个状态码。
客户端错误
此类错误状态代码指向客户端
- 400:请求错误(Bad Request) — 服务器无法理解和处理请求
- 401:未经授权(Unauthorized) — 需要验证,用户尚未验证
- 403:禁止(Forbidden) — 对资源的访问权限不足
- 404:未找到(Not Found) — 找不到请求的资源
服务端错误
-
500:内部服务器错误(Internal Server Error)— 通用未处理的服务器错误
-
503:服务不可用(Service Unavailable)— 服务器暂时无法处理请求
1.浏览器缓存机制
2.get 和 post 的区别3.$ nextTick(),原理
4.防抖节流
5.组件通信方式
6.生命周期
7.promise
8.promise.all 方法什么时候走成功什么时候走失败;
9.v-if、v-for
10、对象和数组的响应式区别;
11、keep-alive;
12、组件通信的方式;
13.vue 数据响应式如何实现
14.vue for 循环的时候 key 的作用;
15、call、apply、bind 区别
15、vue.use 做了什么东西
16、插槽 什么场景下使用插槽;
17、平时工作中封装过哪些组件,怎么封装这些组件;
18、响应拦截器里面都做什么事情;响应拦截器里面的回调什么时候调用;
19、 r o u t e 和 route 和 route和router
20、单页和多页面的缺点;
21、keepalive 的作用原理;
22、http 和 https 的区别;
23、状态码有哪些;
24、如何实现实时更新,比如股票数据;websocket
25、如何判断一个数据是数组还是对象
26、路由懒加载原理
27、data 为什么是对象而不是函数;
28、.catch 能捕获到 return 一个 reject 吗
29、如何做移动端的 rem 适配,除了 flexible,还要装 pxtorem;
30、如何解决跨域问题,生产环境如何解决跨域问题?什么是反向代理;跨是浏览器的机制;
31、回流和重绘
32、图片懒加载的原理
33、项目优化的方式各个方式:打包,发请求;
34、观察者模式和发布订阅模式区别
的宽度,父盒子的剩余空间可以利用 flex-grow 来设置子盒子增大的占比 -
flex-shrink: 1
- flex-shrink 用来设置子盒子超过父盒子的宽度后,进行缩小的比例取值
-
flex-basis: 0%
- 设置盒子的基准宽度,并且 basis 和 width 同时存在会把 width 干掉
5、c3 新属性
- c3 盒模型 box-sizing
- flex 布局
- transition 过渡
- transform2D 转换
- background-size 背景缩放
- border-radius 圆角
- …
6、
五、HTTP
HTTP 缓存
https://juejin.cn/post/6844904153043435533#heading-1
[外链图片转存中…(img-J96pnFCj-1705469393226)]
HTTP 状态码
- 1xx - 信息响应 - 传达传输协议级别的信息
- 2xx - 成功的响应 - 表示客户端的请求已成功接受
- 3xx - 重定向 - 表示客户端必须采取一些额外的操作才能完成其请求
- 4xx - 客户端错误 - 此类错误状态码指向客户端
- 5xx - 服务端错误 - 服务器对这些错误状态代码负责
信息响应
- 100:接受的请求正在处理,信息类状态码
成功的响应
- 200:成功(OK) — 请求成功
- 201:正创建(Created) — 请求已完成,已创建新资源
重定向
- 301:永久移动(Moved Permanently) — 资源永久移动到新的 URL
- 302:临时移动(Moved Temporarily) — 资源临时移动到新的 URL
- 304:未修改(Not Modified)— 当协商缓存命中时会返回这个状态码。
客户端错误
此类错误状态代码指向客户端
- 400:请求错误(Bad Request) — 服务器无法理解和处理请求
- 401:未经授权(Unauthorized) — 需要验证,用户尚未验证
- 403:禁止(Forbidden) — 对资源的访问权限不足
- 404:未找到(Not Found) — 找不到请求的资源
服务端错误
-
500:内部服务器错误(Internal Server Error)— 通用未处理的服务器错误
-
503:服务不可用(Service Unavailable)— 服务器暂时无法处理请求
1.浏览器缓存机制
2.get 和 post 的区别3.$ nextTick(),原理
4.防抖节流
5.组件通信方式
6.生命周期
7.promise
8.promise.all 方法什么时候走成功什么时候走失败;
9.v-if、v-for
10、对象和数组的响应式区别;
11、keep-alive;
12、组件通信的方式;
13.vue 数据响应式如何实现
14.vue for 循环的时候 key 的作用;
15、call、apply、bind 区别
15、vue.use 做了什么东西
16、插槽 什么场景下使用插槽;
17、平时工作中封装过哪些组件,怎么封装这些组件;
18、响应拦截器里面都做什么事情;响应拦截器里面的回调什么时候调用;
19、 r o u t e 和 route 和 route和router
20、单页和多页面的缺点;
21、keepalive 的作用原理;
22、http 和 https 的区别;
23、状态码有哪些;
24、如何实现实时更新,比如股票数据;websocket
25、如何判断一个数据是数组还是对象
26、路由懒加载原理
27、data 为什么是对象而不是函数;
28、.catch 能捕获到 return 一个 reject 吗
29、如何做移动端的 rem 适配,除了 flexible,还要装 pxtorem;
30、如何解决跨域问题,生产环境如何解决跨域问题?什么是反向代理;跨是浏览器的机制;
31、回流和重绘
32、图片懒加载的原理
33、项目优化的方式各个方式:打包,发请求;
34、观察者模式和发布订阅模式区别