前端面试八股文经典

关于Vue

一.生命周期

发送请求在created还是mounted?

这个问题具体要看项目和业务的需要,因为父组件引入子组件时组件的加载顺序是:先执行父组件前三个生命周期,再执行子组件前四个生命周期,最后执行父组件的mounted,如果需要子组件的数据先于父组件数据加载出来,那么就需要父组件延后在mounted里发送请求如果组件间没有依赖关系,则在created和mounted都行

先执行生命周期函数(同步),最后再发送请求(异步),所以在前四个的哪个生命周期函数里都是可以直接发送请求

为什么axios发送请求不在beforeCreate中?beforeCreate和created的区别?

beforeCreate里面拿不到方法,所以当使用方法封装获取数据时,不适合在beforeCreate中调用;

在beforeCreate中没有$data且拿不到方法,在created中有$data,拿得到方法;

在created中如何获取dom

在异步代码(宏任务或请求中)就可以获取到dom

第二次或第n次进去组件会执行哪些生命周期?

        加入了keep-alive组件 只执行activated

      

        没有加入keep-alive组件 执行前四个

对keep-alive的理解

用来缓存组件的,可以提升性能

使用场景:提升项目的性能

具体实现:首页进入详情页时,判断是否是同一个详情页,不是就重新在activated发送请求

生命周期使用场景

created ——单组件请求

mounted——同步可以获取dom,如果子组件请求要先于父组件请求

activated——判断id是否相等,不相等时发送请求(解决重复请求问题)

destroyed——关闭页面记录视频播放的时间,初始化的时候从上一次的历史开始播放

各个生命周期阶段

创建

beforeCreate:在这个阶段属性和方法都不能使用

created:这里是实例创建完成之后,在这里完成了数据监测,可以使用数据,修改数据,不会触发updated,也不会更新视图

挂载

beforeMount:完成了模板的编译,虚拟DOM也完成创建,即将渲染,修改数据,不会触发updated

Mounted:把编译好的模版挂载到页面,这里可以发送异步请求也可以访问DOM节点

更新

beforeUpdate:组件数据更新之前使用,数据是新的,页面上的数据是旧的,组件即将更新,准备渲染,可以改数据

updated:render重新做了渲染,这时数据和页面都是新的,避免在此更新数据

销毁

beforeDestory:实例销毁前,在这里实例还可以用,可以清除定时器

destoryed:组件已经被销毁,全部都销毁了

keep-alive两个周期

activited:组件激活时

deactivited:组件被销毁时

在created和mounted发送请求的区别

created:在渲染前调用,通常先初始化属性再做渲染

mounted:渲染完成后,一般是初始化页面后,再对元素节点进行操作,在这里请求数据可能会出现闪屏问题,created不会

请求的数据对DOM有影响,则使用created,没有影响用mounted,一般多用created

二.关于组件

组件的通信

父给子传值:

属性传值

1.子组件通过props接收 子组件不可以直接修改父组件的数据

2.this.$parent.xxx 子组件可以直接修改父组件的数据

子给父传值:

1.this.$emit自定义事件

2.this.$children 是子组件数组,通过下标拿到

3.ref

子组件属性ref='child'

this.$refs.child.data 可以在父组件更改子组件数据

父传给孙或子:

3.依赖注入:provide inject

平辈传值bus.js

根组件

自己编写的组件,其根组件都是id=app的根div

id=app的根div,父组件是他自己

slot插槽

匿名插槽 具名插槽 作用域插槽(可以传值)

如何封装一个组件

Vue.extend()创建一个组件

Vue.components()注册组件

子组件使用props接收数据,通过emit()方法传输数据

原则:

        把功能拆分开

        尽量让组件原子化,一个组件管一件事情

        容器组件管数据,展示组件管视图

封装一个可复用的组件,需要满足什么条件

1.低耦合,组件之间的依赖越小越好

2.最好从父级传入信息,不要再公共组件中请求数据

3.传入的数据要进行校验

4.处理事件的方法写在父组件中

对组件的理解

1.是vue实例,有独一无二的组件名称

2.可以抽离单独的公共模块

3.提高代码的复用率

三.vuex

vuex的属性

state 全局共享属性

1.this.$store.state.xxx 可以直接修改state的值

2.辅助函数 mapState 不可以直接修改值

getters 针对state值进行二次封装

同上,有两个获取方法,不可以修改

mutations 存放同步方法

actions 存放异步方法 提交mutations

modules 用于vuex模块再次划分

actions和mutations的区别

相同点:这俩个都可以存全局方法,且return的值拿不到

不同点:actions返回的是一个Promise对象,是异步的,mutations是同步的

vuex的持久化存储

方法一:自己写localStorage 方法二:使用vuex-persistedstate插件

路由传参方式

params传参

this.$router.push({name:'index',params:{id:item.id}})

this.$route.params.id

路由属性传参

this.$router.push({name:'/index/${item.id}'})

动态路由配置 {path:'/index:id'}

query传参(可以解决页面刷新参数丢失的问题)

this.$router.push({

        name:'index',

        query:{id:item.id}

})

vuex刷新数据会丢失如何解决

1.把数据直接保存在浏览器缓存里(cookie localStorage sessionStorage)

2.页面刷新时,再次请求数据,达到可以动态更新的方法

        监听浏览器刷新事件,在刷新前把数据保存到sessionStorage里,刷新后请求数据,请求到了用vuex,没请求到用sessionStorage里的数据

vuex是单向数据流还是双向数据流

单项数据流,不允许在使用的vue里更改state值

vuex的响应式处理

vuex是vue的状态管理工具

vue中可以直接触发methods中的方法,vuex不可以。未来处理异步,当触发事件的时候,会通过dispatch来访问actions中的方法,actions中的commit会触发mutations中的方法从而修改state中的值,通过getter把数据更新到视图

Vuex.use(vuex),调用install方法,通过applyMixin(vue)在任意组件内执行this.$store就可以访问到store对象。vuex的state是响应式的,借助的就是vue的data,把state存到vue实例组件的data中

四.关于路由

 hash和history

1.hash有# history没有

2.hash找不到页面不会向后端发送请求,history会(加重带宽负担)

3.项目打包后,前端自测 hash可以看到页面,history不可以

导航故障

当前页跳当前页时会出现

解决:给router的push方法加上error处理

 $router和$route的区别

$router 包含路由,且包含整个路由的属性和方法

$route包含当前的路由对象

 路由守卫

全局守卫

函数 beforeEach路由进入之前

beforeResolve

afterEach路由进入之后

路由独享守卫

beforeEnter 路由进入之前

组件内守卫

beforeRouteEnter路由进入之前

beforeRouteUpdate路由更新之前

beforeRouteLeave路由离开之前

路由模式

前端自测用hash,但是项目上线要用history?

后端重定向url

前端的vue.config.js的publicPath记得改成./

module.exports = {
    publicPath:'./'
}

由于一些非node环境会忽略vue.config.js的代理配置,所以打包后的接口无法正常使用,

这时候需要再配置http文件,全局环境变量env.production和env.development的生产和开发模式,根据process.env自动识别即可

代码如下

另外的模式及其环境变量配置

SPA单页面

缺点:

        1.SEO优化不好

        2.性能不是特别好,都放一个div里面去了

路由传值

显示传值

传:this.$router.push({

                path:'/about',

                query:{

                        a:1'

                }

        })

接: this.$route.query.a

隐式传值

传: 

this.$router.push({

                name:'/about',

                params:{

                        a:1'

                }

        })

接: this.$route.params.a

路由导航守卫

        1.全局

beforeEach、beforeResolve、afterEach

        2.路由独享

beforeEnter

        3.组件内

beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

动态路由

场景:多个子路由,如详情页的跳转

router文件路径后面配置 / :xx

则路由访问后面可以加上/xx

路由拦截

配合路由导航守卫进行拦截

权限配置

刷新后二次加载路由

1.window.location.reload()

2.matcher

const router = createRouter()

export function resetRouter(){

        const newRouter = createRouter()

        router.matcher = newRouter.matcher

}

vue中如何强制刷新

1.location.reload()

2.this.$router.go(0)

3.provide inject配合reload

五.API

$set响应式修改

this.$set(目标数组,索引值,要改成什么) 

this.$set(目标对象,key值,要改成什么) 

$nextTick是一个异步函数 获取更新后的dom

$refs获取dom 

$el 获取当前组件的根节点

$data获取当前组件的数据

data定义数据

在return内定义可以修改;在return外定义不可以修改,因为没有get和set

computed计算属性

computed计算属性的结果值可以修改吗?可以,通过get和set可以修改

v-model绑定computed的值,可以修改它吗?可以,通过get和set可以修改

watch

完整写法

其中,immediate是一开始就监听,deep是深度监听,用于对象里面的数据发生改变时

computed和watch的区别

1.watch是监听,数据或者路由发生了改变才可以响应(执行),当前监听到数据改变了,才会执行内部代码。computed内部如果某个值改变了,计算属性会监听到进行返回

2.computed是计算属性,watch是监听,监听的是data中数据的变化

3.computed不支持异步,watch支持异步

4.computed支持缓存,依赖的属性值发生变化,计算属性才会重新计算,否则用缓存;watch不支持缓存

5.computed是第一次加载就监听,watch不监听

6.computed函数中必须有return,watch不用

computed和methods的区别

computed有缓存,methods没有缓存

v-for 和 v-if的优先级

vue2中v-for > v-if

vue3中v-if > v-for

$nextTick功能:获取更新后的dom

原理

$nextTick(callback){

        return Promise.resolve().then(()=>{

                callback();

        })

}

v-show和v-if区别

1.展示形式不同

v-if创建一个dom节点

v-show是display:none、block

2.使用场景不同

初次加载v-if要比v-show好,少加载盒子

频繁切换v-show要比v-if好,创建和删除开销太大,显示和隐藏开销小

v-if的效率比较低,v-show效率比较高

3.机制不同

v-if有一个局部编译/卸载的过程,切换这个过程中会适当的销毁和重建内部的事件监听和子组件,v-show只是简单的css切换

v-if才是真正的条件渲染,v-show从false变成true的时候不会触发组件的生命周期,v-if会触发生命周期

ref的作用:获取dom的

props和data优先级

props>methods>data>computed>watch

v-for里key值的作用

1.提高虚拟DOM更新的性能

2.若不设置key,可能会触发一些bug

3.为了触发过渡效果

scoped原理

1.作用:让样式在本组件生效,不影响其它组件

2.原理:给节点新增自定义属性,让css根据属性选择器添加样式

vue中样式穿透

1.css不加scoped

2.scss样式穿透

scss使用前需要下载: 

npm install sass-loader node-sass --save

父元素 /deep/ 子元素 需要加上scoped

3.stylus样式穿透

下载: npm install stylus stylus-loader --save

父元素 /deep/ 子元素 需要加上scoped

或者 父元素>>>子元素

vue中遍历全局的方法

六.自定义指令

全局指令和局部指令

七.关于vue3

vue2和vue3的区别

1.vue2和vue3双向绑定的方法不同

vue2 : Object.defineProperty() 后添加的属性是劫持不到的

vue3: new Proxy() 即使后添加的也可以劫持到 被劫持的对象不需要被循环

2.所以$set在vue3中没有

3.写法上的区别:

vue2是选项式api

vue3可以是选项式api也可以是组合式api或setup语法糖形式

Composition API 提供了更高的灵活性和更好的逻辑复用性,特别适合于复杂的应用场景

4.v-if和v-for优先级不同,ref和$children不同等

5.vue3支持碎片化(多个div根节点),vue2不支持

6.main.js不同

vue3为什么使用proxy

1.proxy可以代理整个对象,defineproperty只代理对象上的某个属性

2.proxy对代理对象的监听更加丰富

3.proxy代理对象会生成新的对象,不会修改被代理对象本身

4.proxy不兼容ie浏览器

vue3 hooks

说明:hooks(函数式),主要是让功能模块细分(提升项目的维护性)

解决问题: <script setup></script> 全部代码写一块太乱了

使用hooks分模块写功能函数

vue3响应式数据

1.ref(定义简单类型)

2.reactive定义复杂类型

3.toRef 解构对象属性

解构多个值用toRefs

vue3的性能为什么比vue2好

1.diff算法的优化

2.静态提升(不变的内容不重新生成dom)

3.事件监听缓存

八.vue的性能优化

vue.js渐进式框架

vue.js只是一个核心库,逐步加上router,vuex,element等插件会变得越来越大,所以称渐进式

MVVM框架

web1.0时代 前后端一人开发

web2.0时代 ajax出现,前后端分离

MVVM框架 可以把一个大页面进行拆分,单个组件进行维护

MVVM的含义

M是data数据

V是视图,dom

VM是视图模型,即为vue源码,连接view和model的桥梁。核心是提供对view和model的双向数据绑定,当数据改变时,viewmodel能监听到数据的变化,自动更新视图,当用户操作视图的时候,viewmodel也可以监听到视图的变化,然后通知数据进行改动,实现了双向的数据绑定,viewmodel通过双向绑定将view和model连接起来,他们之间的同步是自动的,不需要人为干涉,我们只需要关注业务逻辑,不需要操作DOM,同时也不需要关注数据的状态问题,因为他是MVVM统一管理的

vue双向绑定数据的原理

        通过数据劫持和发布订阅者模式来实现,同时利用了Object.defineproperty()劫持各个属性的setter和getter,在数据发生改变的时候发布信息给订阅者,触发对应的监听回调渲染视图,也就是说数据和视图是同步的,数据发生变化时视图也发生变化,视图发生变化时数据也发生变化。

步骤:

        第一步:需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter

        第二步:compile模版解析指令,把模板中的变量替换成数据,然后初始化渲染视图,同时把每个指令对应的节点绑定上更新函数,添加订阅者,如果数据变化,收到通知,更新视图

        第三步:watcher订阅者是observer和compile之间的通信桥梁

                        1.在自身实例化的时候往订阅器内添加自己

                        2.自身要有update()方法

                        3.等待属性变动时,调用自身的update方法,触发compile之中的回调

        第四步:MVVM作为数据绑定的入口,整合了observer,compile和watcher三者,通过observer来监听自己的数据变化,通过compile解析模版指令,最后利用watcher把observer和compile联系起来。最终达到数据更新视图更新,视图更新数据更新的效果

vue的性能优化

1.编码优化

        不要把所有数据都放在data中

        v-for时给每个元素绑定事件作为事件代理(事件委托)

        keep-alive缓存组件

        尽可能拆分组件,提高复用性、维护性

        key值要唯一

        合理使用路由懒加载,异步组件

        数据持久化存储的使用尽量用防抖、节流

2.加载优化

        按需加载

        内容懒加载

        图片懒加载

3.用户体验

        骨架屏

4.SEO优化

        预渲染

        服务端渲染ssr

5.打包优化

        CDN形式加载第三方模块

        多线程打包

        抽离公共文件

6.缓存和压缩

        客户端缓存,服务端缓存

        服务端Gzip压缩

首屏优化如何实现

1.使用路由懒加载

2.非首屏组件使用异步组件

3.首屏中不重要的组件延迟加载

4.静态资源放CDN上

5.减少首屏上js,css等资源文件的大小

6.使用服务端渲染

7.尽量减少DOM的数量和层级

8.使用精灵图请求

9.做一些loading

10.开启Gzip压缩

11.图片懒加载

九.vue中其它机制

teleport传送门

加入自己写了弹出框为了不受组件限制,想要居页面中间,可以使用teleport传送到body去,实现居中

vue源码

new Vue相当于vue里面的一个构造器(一个Vue大对象),其中有

Vue {

        $data:{str:'123',b:'abd'}

        $el

        options

        eventFn

        str:'123'

        b:'abc'

}

根据Object.defineProperty可以获得strb

diff算法

作用:提升性能

虚拟DOM ==>把DOM数据化

环境:snabbdom

vue的修饰符

diff算法和虚拟DOM

虚拟DOM,描述元素和元素之间的关系,创建一个JS对象

如果组件内有响应的数据,数据发生改变的时候,render函数会生成一个新的虚拟DOM,这个新的虚拟DOM会和旧的虚拟DOM进行比较,找到需要修改的虚拟DOM内容,然后去真实的DOM中修改

diff算法就是虚拟DOM比对时用的,返回一个patch对象,这个对象的作用就是存储两个节点不同的地方,最后用patch里记录的信息更新真实DOM

diff算法步骤

1.JS对象表示真实的DOM结构,要生成一个虚拟DOM,再用虚拟DOM生成一个真实的DOM树,渲染到页面

2.状态改变生成新的虚拟DOM,与旧的虚拟DOM比对,比对的过程称为diff算法,利用patch记录差异

3.把记录的差异用在第一个虚拟DOM生成的真实DOM上,视图就更新了

vue(框架)和jquery(js的一个库)的区别

1.原理不同 vue是数据绑定,jquery要先获取dom再处理

2.操作不同

3.着重点不同 vue是数据驱动,jq着重于页面

4.未来发展不同

vue的过滤器

vue的特性,用来对文本进行格式化处理

使用的地方:插值表达式或者v-bind

分类:

        1.全局过滤器

        Vue.filter('add',function(v){

                return v < 10 ? '0'+v:v

        })

        2.本地过滤器

        和methods同级

        filter:{

                add:function(v){

                        return v< 10 ? '0'+v:v

                }

        }

使用:

<div>{ {33|add}}</div>

对项目文件的规划

axios如何封装

下载引入创建axios实例 请求响应拦截器 抛出 封装接口

封装和使用

elementui怎么做表单检验

1.在表单中加rules属性,然后在data里写检验规则

2.内部添加规则

3.自定义函数检验

nuxt.js

基于vue的应用框架,关注渲染,可以开发服务端渲染应用的配置

SSR:服务端渲染

        好处:

                SSR生成的是有内容的HTML页面,有利于搜索引擎的搜索

                优化了首屏加载时间

SPA的应用不利于搜索引擎的优化

SEO优化

1.SSR

2.预渲染 prerender-spa-plugin

关于Echarts

组件

title标题组件 show text link

toolbox工具栏组件 可以导出图片 数据视图 切换 缩放 show orient feature

tooltip tigger 触发类型

markPoint标注点

markLine图标标线

关于uni-app

1.分包

优化小程序的下载和启动速度

小程序启动默认下载主包并启动页面,当页面进入分包时才会下载对应的分包

关于webpack

打包的优势

1.提高运行效率(减少上传失误)

2.对技术的支持(转换为浏览器可读的代码)

怎么打包的

webpack会把js,css,image看做一个模块,用import/require引入

找到入口文件,通过入口文件找到关联的依赖文件,把他们打包到一起

把bunble文件拆分成多个小的文件,异步按需加载所需的文件

如果是一个或多个文件引用,打包时只会生成一个文件

如果引用的文件没有调用,不会打包,引用的变量和方法没有调用也不会打包

对于多个入口文件,引入了相同的代码,可以用插件把它提取到公共文件中

关于git

关于Js

Js组成

ECMAScript:Js的核心内容,描述了语言的基础语法,比如var、for、数据类型(数组,字符串)

文档对象类型Dom:把整个html页面规划为元素构成的文档

浏览器对象类型Bom:对浏览器窗口进行访问和操作

Js内置对象

String Number Array Object Function Math Date RegExp(正则表达式)

Math :abs() sqrt() max() min()

Date: new Date() getYear()

Array

String: concat() length() slice() split()将字符串分隔成数组

操作数组的方法有哪些

push() pop() sort() splice() unshift() shift() reverse() concat() join()【将数组连接成为字符串】 map() filter() every() some() reduce() isArray() findIndex()

以下方法会改变原数组: push() pop() unshift() shift() sort() reverse() splice()

判断数据类型

typeof() 可以判断基本数据类型,不能判断引用数据类型

instanceof() 可以判断引用数据类型,不可以判断基本数据类型

constructor 两种类型都可以,但手动修改了构造函数的原型指向则无法判断

Object.prototype.toString.call() 可以判断两种数据类型

前端内存泄漏怎么理解

JS里已经分配内存地址的对象,但是由于长时间没有释放或没办法清除,造成长期占用内存的现象,会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的现象

造成内存泄漏的因素:一些未声明直接赋值的变量;一些未清空的定时器;过度的闭包;一些引用元素没有被清除

事件委托

又叫事件代理,利用了事件冒泡的机制实现

阻止事件冒泡:event.stopPropagation()

addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)

好处:提升性能,减少事件的绑定,也就减少了内存的占用

Js延迟加载

defer 、async

defer的使用

#index.html
<!DOCTYPE html>
<html>
<head>
    <title></title>
    #因为在这里还没有id为box的div,所以打印不出来,加上defer延时加载可以打印出来
    <script defer type="text/javascript" src='script.js'></script>
</head>
<body>
<div id='box'>1111111</div>
</body>
</html>
#script.js
console.log(document.getElementById('box'))

defer:等html全部解析完成(与js代码的下载同步执行),才会执行js代码,各个script之间都是按照引入顺序顺次执行

async:等js下载完执行js代码,但不需要等到html全部解析完成,各个script之间不是顺次执行

JS数据类型

基本类型:string,number,boolean,null,undefined,symbol,bigint

引用类型:object

基本数据类型和引用数据类型的区别

基本数据类型保存在栈内存当中,保存的是具体的值;引用数据类型保存在堆中,保存的是引用数据类型的地址

注意类型

 NaN是数值类型但不是一个具体的数字

typeof undefined还是undefined

各种类型运算

1+true (true=1,数值类型+别的等于数值类型) 等于2

undefined+1 (undefined不知道多大,数字类型+别的 等于数值类型) 等于NaN

'name'+true (字符串+别的 等于字符串xxx)等于nametrue

undefined和null的区别

null是设计js时借鉴java就有了的,null转换数值为0,null是一个表示“无”的对象,因此设计了undefined,是一个“无”的原始值(基本类型),转换数值为NaN

==和===

==:比较的是值   有一边会通过valueOf函数隐式转换

===:比较的是值和类型

JavaScript微任务和宏任务

JavaScript单线程

与用途有关,作为浏览器脚本语言,JavaScript的主要用途是与用户互动,操作DOM,这决定了1它只能是单线程的

如果用户操作删除DOM的同时,又在DOM节点上添加内容,那么Js无法工作

js代码执行流程

同步==》事件循环(先微任务==》宏任务)

setTimeout(function(){
    console.log('1')
})
new Promise((resolve)=>{
    console.log('1 promise'); //这个是同步的
    resolve();
}).then(()=>{
    console.log('微1')
}).then(()=>{
    console.log('微2')
})
console.log(2)//同步

先一起打印2和'1 promise' 再分别打印微1 ,微2,最后打印1

进入事件循环的有:请求、定时器、事件....

for(i=0;i<3;i++){
    setTimeout(function(){
        console.log(i);
    },1000*i)
}

注意!代码打印出来是3,因为先执行了for三次,i=3 再定时器打印3

且每隔一秒一次,打印三次

事件循环包含【微任务、宏任务】

微任务:promise.then

宏任务:setTimeout..

要执行宏任务的前提是清空了所有微任务

Js对象考题

对象是通过new操作符构建出来的,所以对象之间不相等

对象赋值另一个对象:引用类型

console.log([1,2,3] === [1,2,3] )
//false
//浅拷贝
var obj1 = {
    a:'hello'
}
var obj2 = obj1
obj2.a = 'world'
console.log(obj1)
//a:world

(function(){
    console.log(a) //undefined
    var a = 1
})()

对象的key都是string类型

//考题

var a = {}
var b = {
key:'a'
}
var c = {
key:'c'
}

a[b]='123' //a = { [Object Object] = '123' }
a[c]='456' //被覆盖 a = { [Object Object] = '456' }
console.log(a[b]) //456

对象中找属性或方法

原型链: 现在对象本身找==》在构造函数找 ==》在对象原型找==》 在构造函数原型找==》 在对象上一层原型找

function Fun(){
    //构造函数
    this.a = '在Fun函数中添加' //2
}
Fun.prototype.a = '在Fun原型中添加' //4
let obj = new Fun()
obj.a = '对象本身' //1
obj._proto_.a = '这是对象原型添加的' //3

Object.prototype.a = '这是Object添加的' //5
console.log(obj.a)

JS作用域与原型与this指向

考题1

function Foo(){
    getName = function(){console.log(1)} //getName是window全局的
    return this
}
Foo.getName = function(){console.log(2)}
Foo.prototype.getName = function(){consle.log(3)}
var getName = function(){console.log(4)}
function getName(){
    console.log(5)
}

Foo.getName() //2
getName() //4 变量的优先级大于函数,所以不会打印5 ,Foo没有执行,不会看函数体的代码,也不会打印1
Foo().getName() //1 Foo执行了,window.getName赋值为打印1的语句,Foo()返回window,window.getName()执行打印1
getName() //1 由于上面已经执行了打印1 所以这里还是1
new Foo().getName() //3
//先在对象本身找,没有getName
//然后在构造函数中找,但Foo函数里面也没有自己的getName
//再去对象原型中找,但对象原型没有,由于对象的原型和构造函数的原型一样的,所以由Foo.prototype.getName = function(){console.log(3)}可以得到3

考题2

window.name = 'ByteDance'
function A(){
    this.name = 123
}
A.prototype.getA = function(){
    console.log(this)
    return this.name + 1
}
let a = new A()
//情况一
let funcA = a.getA //注意,无括号,单纯赋值
funcA() //有括号,在window执行,所以执行后,this指向window,console.log(funcA())打印ByteDance1

//情况二
let funcA = a.getA() //有括号,在原地执行,所以this指向A
funcA // 会打印A对象,name为123

考题3

var length = 10
function fn(){
    return this.length + 1
}
var obj = {
    length:5,
    test1:function(){
        return fn()
        //执行全局的fn
    },
    //test2:function(){
        //return this.length + 1
    //}
}

obj.test2 = fn //相当于注释部分

console.log(obj.test1()) //11
console.log(fn()===obj.test2()) //false 
console.log(obj.test1()==obj.test2()) false

注意

function fun(){
    return function(){
        this //只是赋值,而不是new,该this永远指向window
    }
}

JS数组去重

//使用new Set
var arr1 = [1,2,3,4,2,1]
console.log(Array.from(new Set(arr1)))
console.log([...new Set(arr)])
//打印出来都是[1,2,3,4]

function unique(arr){
    return [...new Set(arr)]
}
console.log(unique(arr1))

//自定义函数
var arr2 = [1,2,3,4,2,1]
function unique(arr){
    var brr = []
    for( var i=0; i<arr.length; i++){
    if( brr.indexOf(arr[i] == -1)){
        brr.push(arr[i])
    }
    }
    return brr
}
console.log( unique(arr2))

//排序后去重
var arr3 = [1,2,3,4,2,1]
function unique(arr){
    arr = arr.sort()
    var brr = []
    for(var i=0;i<arr.length;i++){
        if(arr[i]!==arr[i-1]){
            brr.push(arr[i])
        }
    }
    return brr;
}

闭包

闭包是什么?

就是函数里面出现另一个函数时,内部函数被外部函数返回并保存下来时会产生闭包,里层函数外边的和里层函数加起来的作用域区域

闭包关闭了函数的自由变量,如var a = 10 ,使其不能被回收

闭包可以解决什么问题?

同步执行完执行事件循环,但需要用到循环的变量时,要用到闭包

var lis = document.getElementsByTagName('li')
for(var i=0;i<lis.length;i++){
    (function(i){
        lis[i].onClick = function(){
            alert(i)
        }
        //为避免内存泄漏,可以把闭包的函数,lis[i]设置为空
        lis[i]=null
    })(i)
}

闭包有什么缺点?

可以重复利用变量,并且这个变量不会污染全局

变量会驻留在内存中,造成内存损耗问题

解决:把闭包的函数设置为null

内存泄漏(只有ie浏览器才会)

闭包使用场景

防抖、节流、函数嵌套函数避免全局污染的时候

判断是否为数组

方式一 isArray

var arr = [1,2,3]
console.log(Array.isArray(arr))

方式二 instanceof(可写可不写)

console.log(arr instanceof Array)

方式三 prototype

console.log(Object.prototype.toString.call(arr).indexOf('Array') > -1)
//call将this指向arr

方式四 isPrototypeOf()

console.log(Array.prototype.isPrototypeOf(arr))

方式五 constructor

console.log(arr.constructor.toString().indexOf('Array')>-1)

slice截取数组

slice从0开始,截取到(参数-1)

参数也可以是负数,表示从后面开始截取

const arr1 = [1,2,3,4]
const arr2 = arrl.slice(1,3)

//arr2 = [2,3]

splice 插入、删除、替换

const arr3 = [1,2,3,4,5]
const arr4 = arr3.splice(1,1,'你好')

//arr4 = [2] arr3 = [1,'你好',3,4,5]

找出多维数组的最大值(使用Math.max即可)

添加一个方法,在字符串前面加上前缀

//采用原型的方法
<script>
    String.prototype.addPrefix = function(str){
        return str + this
    }
    console.log('world'.addPrefix('hello'))
    //helloworld
</script>

统计一个字符串里面哪个字符出现的次数最多,是多少次

<script>
    var str = 'aaaabbbbccccbbbbbbbbd'
    var obj = {}
    for(var i=0;i<str.length;i++){
        if(obj[str[i]]){
            obj[str[i]]++
        }else{
            obj[str[i]]=1
        }
    }
    //统计最大值
    var max = 0
    for(var key in obj){
        if(max<obj[key]){
            max = obj[key]
        }
        if(obj[key]==max){
            console.log(`最多出现次数为{
  
  {max}},其字符串为{
  
  {key}}`)
        }
    }
</script>

new操作符具体做了什么

1.创建了一个空的对象

2.将空对象的原型,指向于构造函数的原型

        Foo.prototype==foo.__proto__

3.将空对象作为构造函数的上下文(改变this指向)

        没有new的时候this是指向window的,new之后this是指向新构造的对象

4.对构造函数有返回值的处理判断

        当返回值是基本类型时,new可以得到构造函数体的东西

        当返回值是引用类型时,new得到的是返回值的东西

function Foo(){
    this.name = '张三'
    return []
    //return 111 则会打印{name:'张三'}
}

console.log(new Foo())
//[]


new源码分析

function Fun(age,name){
    this.age = age;
    this.name = name;
}

function create(fn,...args){
    var obj = {}
    //创建一个空对象

    Object.setPrototypeOf(obj,fn.prototype)
    //将对象原型指向构造函数原型

    var result = fn.apply(obj,args)
    //将空对象作为构造函数的上下文

    return result instanceof Object?result:obj
    //对构造函数有返回值的处理判断
}
//创建create函数,实现new操作符的功能,fn是构造函数,args是参数

原型链

原型可以解决什么问题?

可以共享属性和方法;原型是一个普通对象,它为构造函数的实例共享属性和方法,所有实例中引用的原型都是同一个对象,使用prototype可以把方法挂在原型上,内存只保存一份

_proto_可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype)

原型链是什么?把原型串联起来就叫原型链 最顶端是null

Js继承

方式一:es6 

class Parent{
    constructor(){
        this.age = 18
    }
}

class Child extends Parent{
    constructor(){
        super()
        this.name='张三'
    }
}

let o1 = new Child()
console.log(o1,o1.name,o1.age)

方式二 原型链继承

function Parent(){
    this.age = 20
}
function Child(){
    this.name = '张三'
}
Child.prototype = new Parent()

let o2 = new Child()
console.log(o2,o2.name,o2.age)

方式三 构造函数继承

function Parent(){
    this.age = 20
}
function Child(){
    this.name = '张三'
    Parent.call(this)
}


let o3 = new Child()
console.log(o3,o3.name,o3.age)

方式四:组合式继承

function Parent(){
    this.age = 20
}
function Child(){
    this.name = '张三'
    Parent.call(this)
}

Child.prototype = new Parent()
let o4 = new Child()
console.log(o4,o4.name,o4.age)

函数.call() 函数.apply() 函数.bind()改变this指向

区别:

1.call和apply可以立即执行;call的性能比apply好。bind需要调用执行,返回的是函数


var str = '你好'
function fn(name,age){
    this.name = name
    this.age = age
    console.log(this,this.str) //全局方法this为window
}
var obj = {
    str: '哈哈哈哈'
}
fn.call(obj,'张三',18) //call立即执行,得到{str:'哈哈哈哈',name:'张三',age:18} '哈哈哈哈'
fn.apply(obj,['张三',18]) //apply立即执行,得到{str:'哈哈哈哈',name:'张三',age:18} '哈哈哈哈'
fn.bind(obj,'张三',18)() //加上括号才执行

2.call和bind的传参需要一个个写,apply的传参可以写成数组

3.使用场景

一般下使用call比较多

使用bind和apply取巧

//使用apply获取最大值(利用apply的传参是数组取巧)
var arr = [1,2,3,4,5,6,123]
console.log(Math.max.apply(null,arr))

//使用bind,实现点击再执行函数的情况
var btn = document.getElementById('btn')
var hls = document.getElementById('hls')
btn.onclick = function(){
    console.log(this.id)
}.bind(hls)

//点击打印 hls ,如果是apply和call,上来就打印hls

sort函数使用

默认以满点进行排序

传函数,从小到大进行排序

sort(function(a,b){

        return a-b

})

对对象的属性进行排序

var arr = [
    {name:'张三',age:18},
    {name:'李四',age:8},
    {name:'王五',age:80},
]

定义以age为排序的函数
function compare(age){
    return function(a,b){
        var val1 = a[age]
        var val2 = b[age]
        return val1 - val2
    }
}

var arr1 = arr.sort(compare('age'))
console.log(arr1)

sort排序的原理

v8引擎sort函数只给出了两种排序InsertionSort和QuickSort,数量小于10的数组使用InsertionSort,比10大的数组使用QuickSort,之前的版本是插入排序和快排,现在是冒泡

Js的原理

JS引擎 把JS代码编译成电脑可执行的代码,引擎有主流的v8引擎

运行上下文(包括事件循环) window和dom提供的外部api

调用栈 单线程操作

事件循环 调用栈空后会加入到调用栈里执行

回调

JS中关于this指向问题

1.全局对象中的this指向:指向window

2.全局作用域或者普通函数中的this:指向window

3.this永远指向最后调用它的对象

4.new关键词改变this指向

5.apply、call、bind改变this指向

6.箭头函数中的this 本身没有this,看外层是否有函数,有就是外层函数的this,没有就是window

7.匿名函数中的this 永远指向window,匿名函数的执行环境具有全局性

setTimeout最小执行时间是4ms setInterval最小执行事件是10ms

ES5和ES6的区别

ES5:ECMAScript5 第五次修订

ES6:ESMAScript6 第六次修订

ES6新特性

1.新增块级作用域(let const)

        不存在变量提升(未声明不可以使用)

        存在暂时性死去的问题(var不存在)

        块作用域的内容

        不能再同一个作用域内重复声明

2.新增了定义类的语法糖(class)

3.新增了一种基本数据类型(symbol)

4.新增了解构赋值

5.新增了函数参数的默认值

6.给数组新增了API

7.对象和数组新增了扩展运算符

8.Promise

        解决回调地狱的问题

        自身有all、reject、resolve、race方法

        原型上有then、catch

        把异步操作队列化,链式调用

        三种状态:pending(初始化)、fulfilled操作成功、rejected操作失败

        状态:pending=>fulfilled;pending=>rejected

        async await

                同步代码做异步的操作

                async表明函数内有异步操作,调用函数会返回promise

                await是组成async的表达式,结果取决于他等待的内容,如果是promise那就是promise的内容,如果是普通函数就进行链式调用

                await后的promise如果是reject状态,那么整个async会中断,后面的代码不执行

9.新增了模块化(import export)

10.新增了set和map数据结构

        set(集合)就是不重复

        map的key类型不受限制

11.新增了generator

12.新增了箭头函数

        箭头函数和普通函数的区别:箭头函数不能作为构造函数使用,没有arguments,箭头函数不能用call、apply、bind改变this指向

promise的原理是什么?

Promise构造函数,封装了一个异步操作并且还可以获取成功或失败的结果

Promise主要就是解决回调地狱的问题,之前的异步任务比较多且互相有依赖关系,使用回调函数处理容易形成回调地狱,代码可读性差,可维护性差

Promise的三种状态:pending,fulfilled,rejected

缺点:无法取消Promise,一旦创建立即执行,不能中途取消;如果不设置回调,Promise内部抛出的错误不会反映;若当前处于pending状态时,无法得知目前在哪个阶段

原理:构造一个Promise实例,实例需要传递函数的参数,这个函数有两个形参,分别都是函数类型,一个是resolve一个是reject

promise上还有then方法,这个方法就是来制定状态改变时的确定操作,resolve是执行第一个函数,reject是执行第二个函数

async await 和promise的区别

1.都是处理异步请求的方式

2.promise是ES6,async await是ES7的语法

3.async await是基于promise实现的,都是非阻塞性的

优缺点:

1.Promise返回对象要用then和catch方法处理和捕获,链式调用容易造成代码不好维护的问题;async await是通过try catch进行捕获异常的

2.async await 让代码看起来像同步,只要遇到await就会返回结果,然后在执行后面的操作。promise.then的方式返回,会出现请求还没返回,就执行了后面的操作

深拷贝与浅拷贝

浅拷贝的两种方式 只复制引用,而未复制真正的值

浅拷贝方式一
var arr1=['a','b','c','d']
var arr2 = arr1
arr1[0]='你好吗'
arr2[1]='还行'
console.log(arr1,arr2)
//['你好吗','还行','c','d'],['你好吗','还行','c','d']

方式二
var obj1 = {a:1,b:2}
var obj2 = Object.assign(obj1)
obj1.a = '100'
obj2.b = '你怎么样'
console.log(obj1,obj2)
//{a:100,b:'你怎么样'}

深拷贝 复制真正的值

var obj3 = {
    a:1,
    b:2
}
var obj4 = JSON.parse(JSON.stringify(obj3))
obj3.a = '100'
obj4.b = '你怎么样'
console.log(obj3,obj4)

//{a:100,b:2},{a:1,b:'你怎么样'}

var obj4 = {
    a:1,
    b:2,
    arr:[1,2,3,4]
}
function copyObj(obj){
    if(Array.isArray(obj)){
        var newObj = []
    }else{
        var newObj = {}
    }
    for(let key in obj){
        if(typeof obj[key] == 'object'){
            newObj[key] = copyObj(obj[key])
        }else{
            newObj[key] = obj[key]
        }
    }
    return newObj
}

console.log(copyObj(obj4))

 如何实现深拷贝

1.使用扩展运算符

        let obj1 = {...obj} 缺点:只能拷贝一层,即只能浅拷贝

2.JSON.parse(JSON.stringify()) 缺点:不会拷贝内部函数

3.利用递归函数实现

let origin = {
    name:'张三',
    age:18,
    say(){
        console.log('say hello')
    },
    arr:[[1,2],3,4,5]
}
function extend(origin,deep){
    let obj = {}
    if(origin instanceof Array){
        obj = []
    }
    for(let key in origin){
        let value = origin[key]
        //是深拷贝,且递归的属性是对象,且不为空,则把该属性再递归一次,否则就直接复制给空的obj的key
        obj[key] = (!!deep && typeof value === 'object' && value !== null)?extend(value,deep):value
    }
}

localStorage、sessionStorage、cookie(cookie的存储语法略有不同 document.cookie = 'name=789')

区别

1.数据存放有效期

sessionStorage:会话级别的存储方式,仅在当前浏览器窗口关闭之前有效

localStorage:始终有效,窗口或者浏览器关闭也有效,所以叫持久化存储

cookie: 只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效

只有cookie可以设置过期,localStorage和sessionStorage不可以

2.存储大小的限制

cookie的存储量不可以超过4k

localStorage、sessionStorage不可以超过5M

3.cookie兼容性好,请求头自带cookie

4.localStorage 兼容性好,但保存值的类型被限定,浏览器在隐私模式下不可读取,不能被爬虫

5.indexdDB:键值对形式存储,可以快速读取,适合WEB场景

递归注意事项

递归的定义:一个函数可以调用函数本身,那么这个函数就是递归函数,函数内部调用自己

递归里面需要有退出条件return

Js事件处理机制

主线程 执行栈 任务队列(将任务进行排列) 宏任务 微任务

主线程先执行同步任务,然后才去执行任务队列里的异步任务,如果执行宏任务前有微任务,先执行微任务,这样循环就是事件循环。

ajax是什么?怎么实现的?

创建交互式网页应用的网页开发技术,实现在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容

原理:通过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过JS操作DOM更新页面

工作流程:

1.创建XmlHttpRequest对象xmh

2.通过xmh对象里的open()方法和服务器建立连接

3.构建请求所需的数据,并通过xmh对象的send()发送给服务器

4.通过xmh对象的onreadystate change事件监听服务器和你的通信状态

5.接收并处理服务器响应的数据结果

6.把处理的数据更新到HTML页面上

get和post有什么区别

1.get一般是获取数据,post则是提交数据

2.get参数会放在url上,有长度限制,post的参数放在body上,没有长度限制

3.get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据

4.get请求会被缓存,post请求不会

5.get请求会被保存在浏览器历史记录,post不会

6.get请求只能进行url编码,post请求支持很多种

token 身份验证令牌

可以存在localStorage里和cookie里

token:用户每次请求通过账号密码登录后,服务器把这些凭证通过加密等一系列操作后得到的字符串

1.存在localStorage里,后期每次请求接口都需要把它当做一个字段传给后台

2.存cookie中,会自动发送,缺点是不能跨域,内存比localStorage小

存localStorage有XSS攻击 存cookie有CSRF攻击,但还是存localStorage比较好,如果做好了对应的措施,利大于弊

token的登录流程

1.客户端账号密码登录

2.服务器收到请求后,验证账号密码

3.验证成功返回token

4.客户端接收token保存到cookie或localStorage

5.客户端向服务器请求资源时,要携带token

6.服务端收到请求,验证token,验证成功返回给客户端数据

页面渲染的过程

DNS解析

建立TCP连接

发送HTTP请求

服务器处理请求

渲染页面

        浏览器会获取HTML和CSS的资源,然后把HTML解析成DOM树

        再把CSS解析成CSSOM

        把DOM和CSSOM合并成渲染树

        布局

        把渲染树的每个节点渲染到屏幕上(绘制)

断开TCP连接

DOM树和渲染树的区别:DOM树和HTML标签一一对应,包含head和隐藏元素,渲染树不包含

精灵图和base64的区别

精灵图:把多张小图正和到一张大图上,利用定位的一些属性把小图显示在页面上,当访问页面时可以减少请求,提高加载速度

base64:传输8Bit字节代码的编码方式,把原本二进制形式转为64个字符的单位,最后组成字符串

base64是会和html和css一起下载到浏览器中,减少请求,减少跨域问题,但是一些低版本不支持,若base64体积比原图片大,不利于css的加载

svg格式

基于XML语法格式的图像格式,可缩放矢量图,其他图像是基于像素的,SVG是属于对图像形状的描述,本质是文本文件,体积小,加载快,并且不管放大多少倍都不会失真

svg的使用:

1.直接插入页面 <svg></svg>

2.作为文件引入<img src='oic.svg'/>

3.转为base64引入页面

JWT是什么

JSON Web Token通过JSON形式作为在web应用中的令牌,可以在各方之间安全得把信息作为JSON对象传输

JWT的使用:信息传输、授权签名

JWT的认证流程

1.前端把账号密码发送给后端接口

2.后端核对账号密码成功后,把用户id等其他信息作为JWT负载,把它的头部分别进行base64编码拼接后签名,形成一个JWT(token)

3.前端每次请求都会把JWT放在HTTP请求头的Authorization字段内

4.后端检查是否存在,存在就验证JWT的有效性(签名是否正确,token是否过期)

5.验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果

JWT好处:简洁,包含了各种信息,因为Token是JSON加密的形式存在客户端,所以JWT是跨语言的,原则上任何Web形式都支持

npm是什么

node package manager , node的包管理和分发工具,已经成为分发node模块的标准,是JS的运行环境

npm的组成:网站、注册表、命令行工具

HTTP协议请求头和相应头的内容

1.请求头

Accept:浏览器告诉服务器所支持的数据类型

Host:浏览器告诉服务器我想访问服务器的哪台主机

Referer:浏览器告诉服务器我是从哪里来的(防盗链)

User-Agent:浏览器类型、版本信息

Date:浏览器告诉服务器我是什么时候访问的

Connection:连接方式

Cookie

X-Request-With:请求方式

2.响应头

Location:告诉浏览器你要去找谁

Server:告诉浏览器服务器的类型

Content-Type:告诉浏览器返回的数据类型

Refresh:控制了定时刷新

浏览器的缓存策略

强缓存(本地缓存):不发请求,直接使用缓存里的内容,浏览器把JS、CSS、image等存到内存中,下次用户访问直接从内存中取,提高性能

弱缓存(协商缓存):需要向后台发请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器直接用缓存中的内容

在HTTP1.0和1.1中,两种策略的触发条件都不一样

浏览器同源策略

http://www.   aaa.com:8080/index/vue.js

协议  子域名  主域名    端口号 资源

同源策略是浏览器的核心,如果没有这个策略就会遭受网络攻击

主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域

三个允许跨域加载资源的标签:img link script

跨域是可以发送请求的,后端也会正常返回结果,只不过结果被浏览器拦截了

解决跨域:JSONP、CORS、websocket、反向代理

防抖和节流

都是应对页面中频繁触发事件的优化方案

防抖:避免事件重复触发

使用场景:1.频繁和服务端交互 2.输入框的自动保存事件

节流:把频繁触发的事件减少,每隔一段时间执行

使用场景:scroll事件

什么是JSON

JSON是一种纯字符串形式的数据,它本身不提供任何方法,适合在网络中传输

JSON数据存储在.json文件中,也可以把JSON数据以字符串形式保存在数据库、cookie中

JS提供了JSON.parse() JSON.stringify()方法

什么时候使用json:定义接口;序列化;生成token;配置文件package.json

当数据没有请求过来怎么办

使用默认的数据渲染;使用if判断语句

关于无感登录

什么是无感登录:token过期后通过一系列操作仍能登录

如何实现?

1.在响应中拦截,判断token返回过期后,调用刷新token的接口(常用)

2.后端返回过期时间,前端判断token的过期时间,去调用刷新token的接口

3.写定时器,定时刷新token接口

流程:

1.登录成功后保存token和refresh_token

2.在响应拦截器中对401状态码引入刷新token的api方法调用

3.替换保存本地新的token

4.把错误对象里的token替换

5.再次发送未完成的请求

6.如果refresh_token过期了,判断是否过期,过期了就清除所有token重新登录

大文件上传

分片上传

1.把需要上传的文件按照一定规则,分割成相同大小的数据块

2.初始化一个分片上传任务,返回本次分片上传的唯一标识

3.按照一定的规则把各个数据块上传

4.发送完成后,服务端会判断数据上传的完整性,如果完整,那么就会把数据库合并成原始文件

断点续传:

服务端返回从哪里开始,浏览器自己处理

关于Css

说一下css的盒模型

在html页面中的所有元素都可以看成是一个盒子

盒子的组成:内容content、内边距padding、边框border、外边距margin

盒模型的类型:

        标准盒模型(box-sizing:content-box) margin + border + padding + content

        IE盒模型(box-sizing:border-box) margin + content(包含border和padding)

css选择器的优先级

css的特性:继承性,层叠性,优先级

css样式优先级: 

!important>行内样式>id>类/伪类/属性>标签>全局选择器(*)

隐藏元素的方式

display:none 元素在页面上消失,不占据空间

opacity:0 占据空间

visibility:hidden 元素消失,占据空间

position:absolute

clip-path(切除元素)

px和rem

px是像素,显示器上给我们呈现画面的像素,每个像素大小一样,是绝对单位长度

rem是相对单位,相对于html根节点的font-size的值,eg:html的font-size:62.5%,则1rem=10px(16px*62.5%=10px)

Css的哪些属性可以被继承

常见可以被继承的属性:

1.字体的一些属性:font

2.文本的一些属性:line-height

3.元素的可见性:visibility:hidden

4.表格布局的属性:border-spacing

5.列表的属性:list-style

6.页面样式的属性:page

7.声音的样式属性

css预处理器

scss less

增加了变量(@global)、函数、混入等强大的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值