js
js数据类型
js数据类型分为两类,基本类型和引用类型
基本类型有:String、Boolean、Number、null、undefined,Symbol
引用类型有:Object、Function、Array
如何判断数据类型
一共有6种方法可以判断
- typeof
- instanceof
- Object.prototype.toString
- Array.isArray()
1.typeof
typeof 可以对基本类型(除了null)进行判断
2.instanceof
instanceof 检测的是原型 instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
例 A instanceof B 判断 A 对象的原型链上是否有右边这个构造函数的 prototype 属性,返回一个布尔值
实现原理
instanceof(A, B) = {
const a = A._proto_
const b = B.prototype
return a === b ? true : false
}
例:
[] instanceof Array // true
[] instanceof Object // true
3.Object.prototype.toString
每个对象都有一个 toString() 方法
Object.prototype.toString.call('张三') // [object String]
Object.prototype.toString.call(10) // [object Number]
4.Array.isArray()
Array.isArray() 用于确定传递的值是否是一个 Array, 返回值是布尔类型
原型链
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型__proto__
(也就是它的构造函数的显式原型 prototype)中寻找,如果还是没有,就会沿着这种方式寻找,直到 Object 构造函数为止。这个搜索的过程形成的链状关系就是原型链。
js继承
有五种方式实现继承,ES6的extends也可以实现继承,ts中类型继承就使用了这种方法
-
原型链继承,就是直接让子类的原型对象指向父类的实例(
Son.prototype=new Parent()
),当子类实例找不到对应的属性和方法时,由于js的继承规则,它会往他的原型对象,也就是父类实力上找,从而实现对父类属性和方法的继承。这种继承因为子类的实例原型都指向同一个父类实例,因此修改某个子类的属性会影响所有子类的实例。
-
构造函数继承,就是使用call()方法把父类的this挂到子类this上(
Parent.call(this)
),这样就能避免实例之间共享一个原型实例。这种继承无法继承父类原型上的属性和方法。
-
组合式继承:原型链继承 + 构造函数继承,这种继承子类原型中会存在两份相同的属性和方法
-
寄生组合式继承,对组合式继承的优化,把子类原型对象指向父类实例改为指向父类原型对象,但是因为子类和父类原型指向同一个对象,
那么修改子类的的原型也会影响父类,我们可以给父类的原型做一个浅拷贝再修改子类的原型(
Son.prototype=Object.assign(Parent.prototype)
)
闭包
闭包就是能够读取其他函数内部变量的函数
。它延长了变量的作用范围
经典应用场景有函数防抖与节流,
// 节流
function throttle(fn, timeout) {
let timer = null
return function (...arg) {
if(timer) return
timer = setTimeout(() => {
fn.apply(this, arg)
timer = null
}, timeout)
}
}
// 防抖
function debounce(fn, timeout){
let timer = null
return function(...arg){
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arg)
}, timeout)
}
}
判断 this 指向
this指向一共有五种情况
- 普通函数指向 window
- 对象.方法的 this 指向这个对象
- 构造函数的 this 指向 new 创建的对象
- 箭头函数没有 this, 如果调用 this 它指向外层的函数
- call,apply 调用时, this 指向指定的那个对象
call apply bind的使用
首先它们都可以改变 this 指向
它们的不同在于
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
call它可以用来判断对象类型 具体是使用Object.prototype.toString.call()
方法判断
apply它可以将一个伪数组转成真正的数组Array.prototype.slice.apply({0: 1, length: 1})
[1],
当然使用ES6的Array.from(arr)
方法更为简单。
new的背后做哪些事情
- 创建一个空对象
- this 指向这个对象
- 对象赋值
- 返回实例对象
防抖和节流
防抖和节流都是为了减少一个函数无用的触发次数,以便提高性能或者说避免资源浪费。
它们又各有不同
函数防抖是单位时间内,频繁触发事件,只会触发最后一次,它可以用在用户输入搜索联想,用户在不断输入值时,用防抖来节约请求资源。
函数节流是单位时间内,频繁触发事件,只会触发一次,可以通过监听滚动事件,比如是否滑到底部自动加载更多来实现节流
promise使用
Promise
是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then
方法和catch、finally
方法会返回一个新的Promise
,所以可以允许我们链式调用,解决了传统的回调地狱问题。
它有then和catch方法和finally方法,这三个方法都会返回一个Promise
catch不管被连接到哪里,都能捕获上层未捕捉过的错误
什么是回调地狱,如何解决
回调函数中层层嵌套回调函数的情况就叫做回调地狱
目前我知道回调地狱有三种解决方式
- 拆解函数
- 事件发布/监听模式,我们可以监听某一事件,当事件发生时,进行相应回调操作,当某些操作完成后,通过发布事件触发回调。
Promise
Promise是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then
方法和catch、finally
方法会返回一个新的Promise
,所以可以允许我们链式调用,解决了传统的回调地狱问题。
高阶函数
满足下列条件之一:
- 函数作为参数被传递
- 函数作为返回值输出
就是高阶函数
函数柯里化
柯里化(Currying)是一种编程思想,函数执行产生一个闭包,把一些信息预先存储起来,目的是供下级上下文使用。
for in和for of的区别
它们两者都可以用于遍历,不过for in
遍历的是数组的索引(index
),而for of
遍历的是数组元素值(value
)
for in
更适合遍历对象,当然也可以遍历数组,但是会存在一些问题
index
索引为字符串型数字,不能直接进行几何运算- 遍历顺序有可能不是按照实际数组的内部顺序
for of
遍历的是数组元素值,而且for of
遍历的只是数组内的元素,不包括原型属性和索引
for...of
不能循环普通的对象,需要通过和Object.keys()
搭配使用
*递归及应用场景
递归就是函数内部自己调用自己
应用场景: 树形组件
eventloop事件循环
JavaScript是单线程的语言,Event Loop是javascript的一种执行机制
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器UI线程的渲染工作
- 检查是否有
Web Worker
任务,有则执行 - 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
微任务包括:MutationObserver
、Promise.then()或catch()
宏任务包括:script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
。
事件冒泡与事件捕获
事件冒泡和事件捕获都是为了解决页面中事件流(事件发生顺序)的问题。
事件冒泡: 事件会从最内层的元素开始发生,一直向上传播,直到document对象。
事件捕获与事件冒泡相反
addEventListener方法的第三个参数就是可以选择事件捕获还是事件冒泡,默认冒泡(false)
阻止冒泡; event.stopPropagation( )
最常见的应用: 事件委托,将元素的事件委托给它的父级或者更外级的元素处理,实现原理就是事件冒泡
事件委托
子元素的事件委托给父元素,利用事件冒泡
如何添加和删除事件:
appendChild(node) 添加
removeChild(node) 删除
replaceChild(new,old) 替换
insertBefore(new,old) 在前面插入
如何减少重绘和回流
当元素的尺寸或者位置发生了变化,就需要重新计算渲染树,这就是回流
DOM样式发生了变化,但没有影响DOM的几何属性时,会触发重绘,而不会触发回流。
优化方式:
- 使用class替代style
- 使用
resize、scroll
时进行防抖和节流处理 - 不使用visibility使用display:none来控制显示隐藏
js延迟加载的方式
就是等页面加载完成后再加载js文件,有助于提高页面加载速度
一般有以下几种方式
- defer属性
- async属性
- 动态创建DOM方式
- 使用定时器(setTimeout)
- 让js最后加载
一般使用定时器,如果都是同步方法就放在最后加载
await async的使用方式
就是语法糖
垃圾回收机制
深拷贝和浅拷贝
数组常用api
reduce的使用格式
vue
Vue组件中data为什么必须是函数
对象为引用类型,当重用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。
单个组件的生命周期钩子:
beforeCreate => created =>beforeMount => Mounted =>beforeUpdate => updated =>beforeDestroy=> destroyed
*组件之间传参方式: (写demo)
组件之间通信总的来说有三种情况
- 父子通信
- 兄弟通信
- 跨级通信
- props 父传子
- $emit 子传父 (最常用的父子通讯方式)
- ( p a r e n t s / parents/ parents/children )
- $refs 常用于父组件调用子组件的方法(如:列表数据变化通知子组件重新渲染列表等)
- Vuex 用于任意组件的任意通讯
- EventBus (常用任意两个组件之间通讯)
- ( provide/inject ) (祖先及其后代传值)常用一些多个组件嵌套封装,一个顶层组件内部的后代组件需要用到顶层组件的数据就使用这种方式
- ( a t t r s / attrs/ attrs/listeners ) (常用于对原生组件的封装) $attrs 可以获取父组件传进来但没有通过props接收的属性
- .sync (可以帮我们实现父组件向子组件传递的数据 的双向绑定)
- v-model(和 .sync 类似,可以实现将父组件传给子组件的数据为双向绑定,子组件通过 $emit 修改父组件的数据)
- $root(可以拿到 App.vue 里的数据和方法)
- slot(把子组件的数据通过插槽的方式传给父组件使用,然后再插回来)Bus
计算属性和watch的使用区别:
计算属性和watch都是处理这样一件事情:当某一个数据发生变化的时候,所有依赖这个数据的“相关”数据“`自动”发生变化
它们也有不同点
- 计算属性是基于它们的响应式依赖进行缓存的,它的属性值会默认走缓存,而watch 像是一个 data 的数据监听回调,当依赖的 data 的数据变化,执行回调,watch不支持缓存
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,一般用computed
- computed有两个方法,get和set,get方法函数的返回值就是属性的属性值,当数据变化时,调用set方法。而watch方法可以传入两个值,一个新值,一个旧值,一般在某个数据变化时做一些事情
- computed不支持异步,watch支持异步
哈希路由和history路由的区别
- 最明显的是在显示上,
hash
模式的URL
中会夹杂着#
号,而history
没有。 Vue
底层对它们的实现方式不同。hash
模式是依靠onhashchange
事件(监听location.hash
的改变),而history
模式是主要是依靠的HTML5 history
中新增的两个方法,pushState()
可以改变url
地址且不会发送请求,replaceState()
可以读取历史记录栈,还可以对浏览器记录进行修改。history
模式需要后端的支持
一般 企业内部或商家使用的系统或平台用哈希模式比较多
消费者平台一般使用history
vue路由钩子beforeEach的参数
总共有三个参数
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由next: Function
: 放行 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。(中断或者转跳)
一般在验证用户是否登录时会使用
mutation和action的使用区别
mutation和action都是Vuex的属性
它们有四点不同
- action 提交的是 mutation,而不是直接变更状态。mutation可以直接变更状态。
- action 可以包含任意异步操作。mutation只能是同步操作。
- 提交方式不同,action是用dispatch方法,而mutation是用commit方法
- 接收参数不同,mutation第一个参数是state,而action第一个参数是context,它包含了state,commit,dispatch,getters等方法
vue2常用指令有哪些
vue的指令是用来增强标签的功能的
常用的指令有这么几类
- 用来渲染数据的 v-for v-show v-html v-text v-bind
- 用来和用户交互的 v-model v-on
在实际开发中 v-for @ v-model 的使用是非常高频的,如果觉得指令不满足需求还可以自定义指令
v-mode和.sync的对比
相同点
都是语法糖,都可以实现父子组件中的数据的双向通信。
v-model: @input + value
:num.sync: @update:num
另外需要特别注意的是: v-model
只能用一次;.sync
可以有多个。
在日常开发的过程中,v-model指令是经常用到的,一般来说 v-model 指令在表单及元素上创建双向数据绑定,但 v-model 本质是语法糖。但提到语法糖,这里就不得不提另一个与v-model有相似功能的双向绑定语法糖了,这就是 .sync修饰符。
v-model
它是一种语法糖,依赖:value和 @input实现,一般情况,我们使用v-model主要是用于数据的双向绑定,可以十分方便的获取到用户的输入值。但在某些特殊情况下,我们也可以将v-model用于父子组件之间数据的双向绑定。
vuex的基本使用步骤
vuex是vue里面的状态管理模式,把组件的共享状态抽取出来,任何组件都能获取状态或者触发行为。
使用vuex前需要使用Vue.use(Vuex)
调用插件。
vuex主要有五部分:
- state:包含了
store
中存储的各个状态。 - getter: 类似于 Vue 中的计算属性,根据其他 getter 或 state 计算返回值。
- mutation: 一组方法,是改变
store
中状态的执行者。 - action: 一组方法,其中可以含有异步操作
- module:模块化
v-if和v-show的区别
v-if和v-show都能够控制元素的显示和隐藏
它们之间也有区别
- 实现方式不同,v-if是对组件销毁和创建来实现的,v-show是通过css的display:none来实现
- 性能消耗不同,基于这种实现方式,v-if的性能消耗更高
在做条件渲染是可以使用v-if,比如登录的时候切换登录注册状态,还有控制按钮级别的权限也只能使用v-if
v-show可以使用在比如展开显示详情上,折叠面板。
vue2中$nextTick
Vue的响应式并不是只数据发生变化之后,DOM就立刻发生变化,
放在$nextTick
当中的操作不会立即执行,而是等数据更新、DOM更新完成之后再执行,这样我们拿到的肯定就是最新的了。
- created中获取DOM的操作需要使用它
- 如果想要获取最新值,就用它不会立即执行,而是等数据更新、DOM更新完成之后再执行,这样我们拿到的肯定就是最新的了。
vue2中vue.use是怎么用的
- 不同组件使用情况不同
- VueRouter,MintUI就需要Vue.use(VueRouter)
、
Vue.use(MintUI) - axios不需要
- 自定义插件Vue.use()
-
如果括号里是对象,该对象要有一个install方法,那么Vue.vue就是调用了install方法
-
如果xxxx是一个function,那么Vue.vue就会让方法直接执行
vue2中响应式的原理
vue2中如何自定义指令
主要是通过Vue.directive
方法进行注册
应用场景:
防抖
图片懒加载
vue2中过滤器是怎么使用的
一般原来过滤数据,如时间格式
vue2和vue3的区别
主要有以下几点区别:
- 生命周期变化 mounted -> onmounted,unmounted -> onUnmounted
- vue3是组合式api,vue2是选项式api
- template下不用包含一个根标签
- v-model语法糖调整了
- Vue3 的 hook函数 相当于 vue2 的 mixin
key的作用
key 一般配合v-for使用
- v-for会尽可能复用数据,列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素。
- key来给每个节点做一个唯一标识
- key的作用主要是为了高效的更新虚拟DOM
vuex的getter的作用
相当于vue中的计算属性
网络相关
http协议请求消息Request的三个内容
分为起始行、消息头和消息体
-
起始行分为HTTP请求起始行和HTTP响应的起始行
HTTP请求起始行就是请求行 GET/POST 方法 URL,HTTP版本
HTTP响应的起始行称为状态行 200 404 500 302
-
消息头 有限的字段名,有限的字段值。 例 Content-Encoding: gzip
-
消息体(请求正文) 不确定。要看请求的内容是啥。
比如GET请求就是没有消息体的,POST请求的消息体一般用来放置表单数据。
URL的输入到浏览器解析过程
分为三个阶段
-
用户输入阶段,用户在地址栏输入内容之后,浏览器会判断用户输入是否是合法的URL,如果是合法的URL就开始进行加载。
-
发起URL请求阶段,这个阶段浏览器首先会构建请求行信息,然后会查找缓存,如果没有缓存会进行DNS解析域名对应的IP和端口号,
然后通过TCP三次握手与服务器建立连接,然后进行数据传输。然后发送HTTP请求,服务器处理请求并返回请求报文,最后断开TCP连接,通过四次挥手来断开连接。
-
页面渲染阶段
- 解析HTML形成DOM树
- 解析CSS形成CSSOM 树
- 合并DOM树和CSSOM树形成渲染树
请求头中的contentType有什么用处
http发送post请求的时候,需要指定Content-Type,用于客户端告诉服务器实际发送的数据类型。一般是json类型
get请求和post请求的区别
- get请求参数放在url上,而post请求参数放在请求体上
- get请求参数长度有限制,post请求参数长度可以非常大
- post请求比get请求安全一点,所以登录一般都用post请求
- get请求能缓存,post不能
本地持久化的方式和区别
有4种方法可以实现持久化
- cookie
- sessionStorage
- localStorage
跨域问题及解决方案
跨域本质是浏览器基于同源策略的一种安全手段,协议,主机,端口号相同
主要有3种方法解决
- jsonp,这种方法只能解决get请求跨域,所以不常用
- cors,这种方法需要后端配置
- proxy代理,我们可以通过
webpack
为我们起一个本地服务器作为请求的代理对象
常见http的状态码
- 2开头,请求成功
- 3开头,重定向,把请求的网页移动到新位置
- 4开头,客户端错误,比如请求错误
- 5开头,一般是服务端错误
https和http的区别
它们有四点区别
- 端口不同,https使用443端口,http使用80端口
- https需要申请证书
- http是超文本传输协议是明文传输;HTTPS是经过SSL加密的协议,传输更安全
- HTTPS比HTTP慢,因为HTTPS除了TCP握手的三个包,还要加上SSL握手的九个包
CSRF和XSS
CSRF(跨站请求伪造)就是比如黑客在自己的站点上放置了其他网站的链接,比如微博,默认情况下,浏览器会带着微博的cookie访问这个网址,
如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么账号就会被劫持
防御方法:
-
验证token
-
验证请求头的Referer
-
设置cookie的samesite,可以让cookie不随跨域请求发出
XSS就是利用网页开发留下的漏洞,注入恶意代码,最常见实在评论区插入攻击脚本
防御方法:
- 设置httpOnly防止js获取cookie
- 开启白名单,组织白名单以外的资源加载