关于一些vue的面试题小整理(包含原生js的内容)

vue相关

1.vue生命周期

什么是Vue生命周期?

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期

Vue生命周期的作用是什么?

它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑

Vue生命周期总共有几个阶段?

它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

第一次页面加载会触发哪几个钩子?

第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

DOM渲染在哪个周期中就已经完成?

DOM 渲染在 mounted 中就已经完成了

每个生命周期适合哪些场景?

生命周期钩子的一些使用方法:

beforecreate : 可以在这加个loading事件,在加载实例时触发

created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用

mounted : 挂载元素,获取到DOM节点

updated : 如果对数据统一处理,在这里写上相应函数

beforeDestroy : 可以做一个确认停止事件的确认框

nextTick : 更新数据后立即操作dom

    • beforeCreate阶段:vue实例的挂载元素el和数据对象data都是undefined,还没有初始化。

    • created阶段:vue实例的数据对象data有了,可以访问里面的数据和方法,未挂载到DOM,el还没有

    • beforeMount阶段:vue实例的el和data都初始化了,但是挂载之前为虚拟的dom节点

    • mounted阶段:vue实例挂载到真实DOM上,就可以通过DOM获取DOM节点

    • beforeUpdate阶段:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器

    • updated阶段:虚拟DOM重新渲染和打补丁之后调用,组成新的DOM已经更新,避免在这个钩子函数中操作数据,防止死循环

    • beforeDestroy阶段:实例销毁前调用,实例还可以用,this能获取到实例,常用于销毁定时器,解绑事件

    • destroyed阶段:实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁


2.v-show与v-if区别

v-show是css切换,v-if是完整的销毁和重新创建

使用 频繁切换时用v-show,运行时较少改变时用v-if

v-if='false' v-if是条件渲染,当false的时候不会渲染


3.MVVM相关

vue采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty劫持data属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

MVVM

M - Model,Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑

V - View,View 代表 UI 组件,它负责将数据模型转化为 UI 展现出来

VM - ViewModel,ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View

image.png
    • View 接收用户交互请求

    • View 将请求转交给ViewModel

    • ViewModel 操作Model数据更新

    • Model 更新完数据,通知ViewModel数据发生变化

    • ViewModel 更新View数据

MVC

    • View 接受用户交互请求
    • View 将请求转交给Controller处理
    • Controller 操作Model进行数据更新保存
    • 数据更新保存之后,Model会通知View更新
    • View 更新变化数据使用户得到反馈

**

**

**

**MVVM模式和MVC有些类似,但有以下不同

    • ViewModel 替换了 Controller,在UI层之下
    • ViewModel 向 View 暴露它所需要的数据和指令对象
    • ViewModel 接收来自 Model 的数据

概括起来,MVVM是由MVC发展而来,通过在Model之上而在View之下增加一个非视觉的组件将来自Model的数据映射到View中。

image.png
image.png

4.说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

    • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
    • 基于上面一点,SPA 相对对服务器压力小;
    • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

    • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;

    • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;

    • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。


5、computed 和 watch 的区别和运用的场景?

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

    • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

    • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

computed的原理

computed 本质是一个惰性求值的观察者。

computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。

其内部通过 this.dirty 属性标记计算属性是否需要重新求值。

当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变 化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。 )

没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他 地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。 )


6.v-model 的原理

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

    • text 和 textarea 元素使用 value 属性和 input 事件;

    • checkbox 和 radio 使用 checked 属性和 change 事件;

    • select 字段将 value 作为 prop 并将 change 作为事件。

ini 复制代码
<input v-model='something'>
    
相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">


7.VUE和REACT 的区别?

react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流;

vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。

具体参照:

juejin.im/post/5e153e...


8. 为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

Proxy 的优势如下:

    • Proxy 可以直接监听对象而非属性;
    • Proxy 可以直接监听数组的变化;
    • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
    • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
    • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

    • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
image.png

9. Vue 组件 data 为什么必须是函数 ?

因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。


10、谈谈你对 keep-alive 的了解?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

    • 一般结合路由和动态组件一起使用,用于缓存组件;

    • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;

    • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。


11、Vue 组件间通信有哪几种方式?

(1)props / $emit 适用 父子组件通

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref$parent / $children 适用 父子组件通信

    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

    • $parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件

(4)$attrs/$listeners 适用于 隔代组件通信

    • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(5)provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。


12.请介绍一下你对vuex的理解?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

    • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。

    • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

    • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。

    • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

    • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

VUEX实现原理? (课程中的代码)


13.请介绍一下你对vue-router的理解?

vue-router 实现原理? (课程中的代码)

**

**

vue-router 有 3 种路由模式:hash、history、abstract,

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;

  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;

  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

hash 路由模式的实现主要是基于下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

javascript 复制代码
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现主要基于存在下面几个特性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

导航钩子函数(导航守卫)?

  • 全局守卫
  1. router.beforeEach 全局前置守卫 进入路由之前

  2. router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用

  3. router.afterEach 全局后置钩子 进入路由之后

javascript 复制代码
// main.js 入口文件
    import router from './router'; // 引入路由
    router.beforeEach((to, from, next) => { 
      next();
    });
    router.beforeResolve((to, from, next) => {
      next();
    });
    router.afterEach((to, from) => {
      console.log('afterEach 全局后置钩子');
    });
  • 路由独享的守卫 你可以在路由配置上直接定义 beforeEnter 守卫
javascript 复制代码
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
  • 组件内的守卫 你可以在路由组件内直接定义以下路由导航守卫
javascript 复制代码
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
    // 可以访问组件实例 `this`
    // 比如还未保存草稿,或者在用户离开前,
    将setInterval销毁,防止离开之后,定时器还在调用。
  }
}

14、Vue 中的 key 有什么作用?

Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

更准确 :因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,


15.ref的作用

    1. 获取dom元素this.$refs.box

    2. 获取子组件中的datathis.$refs.box.msg

    3. 调用子组件中的方法this.$refs.box.open()


30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)

面试完50个人后我写下这篇总结

公司要求会使用框架vue,面试题会被问及哪些?

Vue 项目性能优化 --- 实践指南

原生js相关

1.ES6的新特性:

let(声明变量)

const(声明常量,常量不能修改的量)

var、let、const的区别

  1. let和const声明变量不存在变量提升,如果要使用这个变量,我们需要在变量定义之后使用;

  2. let和const不能重复声明变量,如果重复声明会报错;

  3. 用let 和 const 在全局声明变量不会给window增加属性;

  4. let和const出现在代码块中,会把代码块(字面量声明对象除外)变成块级作用域,并且出现暂时 性死区 class(创建类)

import/export(基于ES6的模块规范创建导入/导出模块(文件/组件))

new set(数组去重)

Symbol(唯一的值) var a = Symbol('qqq')

...ary(展开运算符、剩余运算符)

${} 模板字符串

解构赋值 let {a} = obj; let [b] = ary

for of 循环

()=>{} 箭头函数

箭头函数与普通函数的区别:

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用new

  2. 箭头函数没有原型属性

3.this指向不同,箭头函数的this是定义时所在的对象,普通函数看前面有没有.,点前面是谁this 就是谁,没有.就是window

  1. 不可以使用arguments对象,该对象在函数体内不存在。

数组新增方法: some every filter reduce ...

对象新增方法: Object.assign() Object.values() Object.keys() Object.create()...


2.JS的数据类型:

**

**基本数据类型: number 数字; boolean 布尔值 :有两个值 true、false ;string 字符串

null 空对象; undefined 未定义的值(很多浏览器的初始值是undefined)

Symbol() 产生一个唯一的值,和谁都不重复

javascript 复制代码
null和undefined的区别:
null 是一个表示"无"的对象,转为数值时为 0
undefined 是一个表示"无"的原始值,转为数值时为 NaN  
  当声明的变量还未被初始化时,变量的默认值为 undefined
null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象  
undefined 表示 "缺少值",就是此处应该有一个值,但是还没有定义。
  典型用法是:
  1. 变量被声明了,但没有赋值时,就等于 undefined
  2. 调用函数时,应该提供的参数没有提供,该参数等于 undefined
  3. 对象没有赋值的属性,该属性的值为 undefined
  4. 函数没有返回值时,默认返回 undefined  
null 表示"没有对象",即该处不应该有值。
  典型用法是:
  1. 作为函数的参数,表示该函数的参数不是对象
  2. 作为对象原型链的终点

引用数据类型:

对象

.普通对象

.数组对象

.正则对象(匹配字符串的规则)

.日期对象

.函数对象

...

对象的存储过程:

  1. 开辟一个空间地址

  2. 把键值对存储到这个空间地址的堆内存中

  3. 把这个对象指针赋值给变量名

ini 复制代码
let obj = { 
  a:1, 
  fn:(function (val) {
    // 赋给fn的是自执行函数的执行结果 也就是一个undefined
    // 该自执行函数只会执行一次
    console.log(val);
  })(obj.a) 
};
let obj2 = obj;// 两者代表了同一个地址;
// 获取属性的值 obj.fn 或者 obj['fn']
// 新增属性: obj.c = 100 或者 obj['c'] = 100
// 真删除 delete obj.a (在严格模式下不支持该方法); 假删除: obj.a = null;

// 引用类型小习题
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
//=========================
const a = {};
const b = { key: "b" };
const c = { key: "c" };
a[b] = 123;
a[c] = 456;
console.log(a[b]);

基本数据类型与引用数据类型的区别:

基本数据类型是操作值,引用数据类型操作的是堆内存空间地址

javascript 复制代码
布尔值转换:0 NaN '' null undefined 转化成布尔值是false,其余的都是true
检验有效数字的方法:isNaN
常用的数据类型检测方式: typeof constructor instanceof Object.prototype.toString.call()

比较运算符:

== 相对比较:会进行默认的类型转化; 若转换之后的值相等,则结果就是true

=== 绝对比较,值不但要相同、类型也得相同。

引用数据类型之间的比较,就看是不是同一个地址;

逻辑运算符:

|| 表示或者,前边成立给前边,前边不成立给后边

&& 表示并且前边成立给后边,前边不成立给前边


3.定义函数的方法

·1.function声明

javascript 复制代码
//ES5
function getSum(){}
function (){}//匿名函数
//ES6
()=>{}

·2.函数表达式

javascript 复制代码
//ES5
var getSum=function(){}
//ES6
let getSum=()=>{}

**

·3.构造函数**

javascript 复制代码
const getSum = new Function('a', 'b' , 'return a + b')

4.JS作用域的理解

javascript 复制代码
JS中的作用域分为两种:
    全局作用域和函数作用域。
  函数作用域中定义的变量,只能在函数中调用,外界无法访问。
  没有块级作用域导致了if或for这样的逻辑语句中定义的变量可以被外界访问,
  因此ES6中新增了let和const命令来进行块级作用域的声明。
  
  //循环绑定的问题
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1);
  }
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1);
  }

//作用域链  变量的查找机制
// 上级作用域  函数在哪里定义的,那么该函数执行形成的作用的上级作用域就是谁
// 了解了上级作用域, 就比较容易查找变量对应的值

5.闭包的理解

简单来说闭包就是在函数里面声明函数,本质上说就是在函数内部和函数外部搭建起一座桥梁,使得子函数可以访问父函数中所有的局部变量,但是反之不可以,这只是闭包的作用之一,另一个作用,则是保护变量不受外界污染,使其一直存在内存中,在工作中我们还是少使用闭包的好,因为闭包太消耗内存,不到万不得已的时候尽量不使用。


6.数组

javascript 复制代码
// 数组去重
1、双for循环去重
2、利用对象的属性名不能重复去重
3、利用es6的Set不能重复去重
(具体代码自己查)
scss 复制代码
// 数组重组  (将name值相同的合并,并去除age的属性)
let ary = [    {name:1,age:2,number:1,son:'son1'},    {name:2,age:23,number:2,son:'son2'},    {name:2,age:22,number:3,son:'son3'},    {name:1,age:12,number:4,son:'son4'},    {name:1,age:42,number:5,son:'son5'}]
fn(ary) // 结果为
[  {    "name":1,    "list":[{"number":1,"son":"son1"},{"number":4,"son":"son4"},{"number":5,"son":"son5"}]
  },
  {
    "name":2,
    "list":[{"number":2,"son":"son2"},{"number":3,"son":"son3"}]
  }
]
function fn(ary){
    let arr = [];
    ary.forEach(item=>{
        let bol = arr.some(val=>{
            if(val.name===item.name){
                let obj = {};
                Object.keys(item).forEach(v=>{
                    if(v!='name'&&v!='age'){
                        obj[v] = item[v]
                    }
                })
                val.list.push(obj);
                return true
            }
        })
        if(!bol){
            let obj = {};
            Object.keys(item).forEach(v=>{
                if(v!='name'&&v!='age'){
                    obj[v] = item[v]
                }
            })
            arr.push({name:item.name,list:[obj]});
        }
    })
    return arr;
}
fn(ary)


// 数组扁平化
var arr = [  [1, 2, 2],
  [3, 4, 5, 5],
  [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];

function flat1(arr) {
  let temp = [];
  function fn(ary) {
    ary.forEach(item => {
      if (typeof item == 'object') {
        fn(item)
      } else {
        temp.push(item)
      }
    })
  }
  fn(arr)
  return temp;
}

function flat2() {
  return [].concat(...this.map(item => (Array.isArray(item) ? item.flat2() : [item])));
}
image.png

7.原型及原型链

原型

  • 函数都带有一个prototype 属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。
  • 原型对象都有一个constructor 属性,这个属性指向所关联的构造函数。

  • 每个对象都有一个__proto__ 属性[非标准的方法],这个属性指向构造函数的原型 prototype

原型链

  • 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会 通过 proto 属性去原型上查找,如果还没有 找到则会在构造函数的原型的__ proto__中查 找, 这样一层层向上查找就会形成一个作用域链,称为原型链

原型相关习题

ini 复制代码
// 第一题
function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype = {
    y: 400,
    getX: function () {
        console.log(this.x);
    },
    getY: function () {
        console.log(this.y);
    },
    sum: function () {
        console.log(this.x + this.y);
    }
};
var f1 = new Fn();
var f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
f1.sum();
Fn.prototype.sum();

// 第二题
function Foo() {
    getName = function () {console.log(1);};
    return this;
}
Foo.getName = function () {console.log(2);}; 
Foo.prototype.getName = function () {console.log(3);};
var getName = function () {console.log(4);};
function getName() {console.log(5);}

Foo.getName();
getName();
Foo().getName(); 
getName();
var a = new Foo.getName(); // 
var b = new Foo().getName();
var c = new new Foo().getName();
console.log(a,b,c);

// 第三题
function Person() {
    this.name = 'zhufeng'
};
Person.prototype.getName = function () {
    console.log(this.name)
    console.log(this.age)
};
Person.prototype.age = 5000;

var per1 = new Person;
per1.getName();
per1.age = 9;
per1.getName();
console.log(per1.age);
var per2 = new Person;
console.log(per2.age);

Object.create的作用:

javascript 复制代码
let obj = {a:123};
let o = Object.create(obj);
//该函数返回了一个新的空对象,但是该空对象的__proto__是指向了obj这个参数
// 手写Object.create
function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}

new的执行过程是怎么回事?

new操作符做了这些事:

    • 它创建了一个全新的对象
    • 它会被执行[[Prototype]](也就是__proto__)链接
    • 它使this指向新创建的对象
    • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
    • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
ini 复制代码
//模拟new
function objectFactory() {
  const obj = new Object();
  const Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;

  const ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

call,apply,bind三者的区别?

**

**apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数fun.apply(thisArg, [argsArray])

apply 和 call 基本类似,他们的区别只是传入的参数不同。

apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

javascript 复制代码
call做了什么:
  将函数设为对象的属性
  执行&删除这个函数
  指定this到函数并传入给定参数执行函数
  如果不传入参数,默认指向为 window
  
//实现一个call方法:
Function.prototype.myCall = function(context) {
  //此处没有考虑context非object情况
  context.fn = this;
  let args = [];
  for (let i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
  }
  context.fn(...args);
  let result = context.fn(...args);
  delete context.fn;
  return result;
};

// 模拟 apply
Function.prototype.myapply = function(context, arr) {
  var context = Object(context) || window;
  context.fn = this;

  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }

  delete context.fn;
  return result;
};


实现bind要做什么
返回一个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效

// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

实现类的继承

类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。

javascript 复制代码
function Parent(name) {
    this.parent = name
}
Parent.prototype.say = function() {
    console.log(`${this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
    // 将父类的构造函数绑定在子类上
    Parent.call(this, parent)
    this.child = name
}
/** 
 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

谈谈你对this指向的理解

this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象

改变 this 的指向我总结有以下几种方法:

    • 使用 ES6 的箭头函数
    • 在函数内部使用 _this = this
    • 使用 applycallbind
    • new 实例化一个对象
kotlin 复制代码
全局作用域下的this指向window
如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素
函数中的this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this就指向谁,如果没有点,指向window
自执行函数中的this永远指向window
定时器中函数的this指向window
构造函数中的this指向当前的实例
call、apply、bind可以改变函数的this指向
箭头函数中没有this,如果输出this,就会输出箭头函数定义时所在的作用域中的this

8.DOM

1).新建节点

document.createElement("元素名") // 新建一个元素节点

document.createAttribute("属性名") // 新建一个属性节点

document.createTextNode("文本内容") // 创建一个文本节点

document.createDocumentFragment() // 新建一个DOM片段

2).添加、移除、替换、插入:

appendChild() // 向节点的子节点末尾添加新的子节点

removerChild() // 移除

parentNode.replaceChild(newChild, oldChild );用新节点替换父节点中已有的子节点

insertBeform() // 在已有的子节点前插入一个新的子节点

3).查找

document.getElementById() // 通过元素id查找,唯一性

document.getElementByClassName() // 通过class名称查找

document.getElementsByTagName() // 通过标签名称查找

document.getElementsByName() // 通过元素的Name属性的值查找


DOM回流、重绘

DOM回流(reflow):页面中的元素增加、删除、大小、位置的改变,会引起浏览器重新计算 其他元素的位置,这种现象称为DOM回流。DOM回流非常消耗性能,尽量避免DOM回流

DOM重绘:元素的某些css样式如背景色、字体颜色等发生改变时,浏览器需要重新描绘渲 染这个元素,这种现象称为DOM重绘。

DOM 操作的读写分离

在JS中把设置样式和获取样式的两种操作分来来写, 设置样式的操作放在一起,读取样式的操作放在一起,这样可以有效的减少DOM的回流和重绘;

DOM事件:

事件的传播机制:先冒泡,然后是目标阶段 然后再去捕获,我们可以利用事件的冒泡来进行事件委托,、也就是可以在父元素上绑定事件,通过事件对象 e 来判断点击的具体元素;可以提供性能;

我们可以利用的 e.stopPropagation()来阻止冒泡;利用 e.preventDefault()来阻止默认事件;

事件中有0级事件绑定和2级事件绑定

JS 盒子模型

// client offset scroll width height left top

// clientWidth 内容宽度 + 左右padding

// offsetWidth clientWidth + 左右 border

// offsetTop 当前盒子的外边框到上级参照物的内边框的偏移量

// offsetParent 上级参照物:有定位的上级(包含 父级,祖父,曾祖父...)元素,所有所有上级都没有定位, 则参照物就是 body

// scroll 内容不溢出 等同于 client

// 内容溢出时 没有设置overflow 值是内容宽高 + 上或左padding

// 内容溢出时 有设置overflow时 值是内容宽高 + 上下或左右padding

// scrollTop 卷去内容的高度

// 13个属性 只有 scrollTop和scrollLeft时可以设置值的, 其他的都是只读属性


9.JS的异步编程

因为js是单线程的。浏览器遇到etTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码

常用的方式:setTimeout setIntervel ajax Promise asyc/await

宏任务(marcotask)微任务(microtask) 的执行顺序

先执行宏任务,然后在执行微任务;

JS中的宏任务:setTimeout setIntervel ajax

JS中的微任务:Promise.then Promise.catch await(可以理解成Promise.then)

JS的执行顺序是先同步 再异步;同步执行完成之前 异步不会执行

EventLoop 事件循环

EventQueue 事件队列

javascript 复制代码
// 第一题
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");
},0);

async1();

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
console.log('script end'); 




// 第二题
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");
});
setImmediate(()=>{
    console.log("setImmediate")
})
process.nextTick(()=>{
    console.log("process")
})
console.log('script end'); 

10.正则

javascript 复制代码
//解析 URL Params 为对象
 var str = 'http://www.zhufengpeixun.cn/?lx=1&from=wx&b=12&c=13#qqqq';
function getParam(url){
  var reg = /([^?=&]+)=([^?=&#]+)/g;
  let obj = {};
  url.match(reg).forEach(item=>{
    let a = item.split('='); // ['lx','1']
    obj[a[0]] = a[1]
  })
  return obj
}
getParam(str);
//=================================================

//模板引擎实现
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined

function render(template, data) {
  const reg = /{{(\w+)}}/; // 模板字符串正则
  if (reg.test(template)) { // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
  return template; // 如果模板没有模板字符串直接返回
}
//=================================================


// 出现次数最多的字符
 var str = 'sfgsdfgsertdgfsdfgsertwegdsfgertewgsdfgsdg';
function getMax2(str) {
  str = str.split('').sort().join('');// 把字符串进行排序
  let key = '',num = 0;
  str.replace(/(\w)\1*/g,function($0,$1){
    if($0.length > num){
      num = $0.length;
      key = $1;
    }
  })
  return{
    key,num
  }
}
getMax2(str);
//=================================================


// 千分符的实现
// 100,000,00
//方法1
var str = '1234567'; // 1,234,567
function moneyFormate(str){
  str = str.split('').reverse().join('')
  let s = '';
  for(let i = 0; i < str.length ; i++){
    i%3 == 2 ? s+=str[i]+',' : s+=str[i]
  }
  s = s.split('').reverse().join('')
  return s
}
moneyFormate(str);// 1,234,567

// 方法2
var str = '1234567';
function moneyFormate2(str){
  let s = '';
  // s = str.replace(/\d{1,3}(?=(\d{3})+$)/g,function(a){
  //     console.log(arguments)
  //     return a + ','
  // })
  s = str.replace(/(\d{1,3})(?=(\d{3})+$)/g,'$1,');
  return s;
}
moneyFormate2(str);
//=================================================


var str = '   sdfgsg   fsgfsd    ';
// 使用正则去除字符串的首尾空格
// 以 1 到 多个 空格开头或者结尾的 都替换成空;
var res = str.replace(/^ +| +$/g,'')

10.http&ajax

1.TCP/IP的三次握手和四次挥手

三次握手:

第一次握手:客户端向服务端发送SYN码数据包,表示客户端要求和服务端建立连接;

第二次握手:服务端收到客户端的连接请求后,会发送ACK数据包给客户端,表示你的连接 请求已经收到,询问客户端是否真的需要建立连接;

第三次握手:客户端收到ACK码以后会检验是否正确,如果正确,客户端会再次发送ACK码给 服务端,表示确认建立连接; (三次握手都成功以后才会建立连接,然后才会发送数据;)

四次挥手:

第一次挥手:当客户端发送数据结束后,会发送FIN码数据包给服务端,表示告知服务端客 户端的数据已经传递完了。

第二次挥手:当服务端收到FIN后,会发送ACK给客户端,表示服务端已经知道客户端传完 了。客户端收到ACK以后就会把传递数据给服务端的通道关闭;

第三次挥手:当服务端把响应的数据发送完毕后,会发送一个FIN给客户端,告知客户端响 应的数据已经发送完毕;

第四次挥手:当客户端收到FIN后,会发送一个ACK码数据包给服务端,告知服务端客户端已 经知道数据发送完毕;服务端收到ACK码后,可以安心的把数据传递通道关闭掉。

2.http常用状态码(http-status-code):

2xx:表示成功

200 OK 表示所有东西都正常

204 表示请求成功,但是服务端没有内容给你

3xx: 表示重定向

301 永久重定向(当访问一个永久重定向的网站的时候,一个域名被指向一个其他网站,且是永久的)

302 临时重定向

304 走缓存(服务端觉得你之前请求过这个东西,而且服务器上的那一份没有发生变化,告诉客户端用缓存 就行)

      • 301,Moved Permanently。永久重定向,该操作比较危险,需要谨慎操作:如果设置了301,但是一段时间后又想取消,但是浏览器中已经有了缓存,还是会重定向。
      • 302,Fount。临时重定向,但是会在重定向的时候改变 method: 把 POST 改成 GET,于是有了 307
      • 307,Temporary Redirect。临时重定向,在重定向时不会改变 method

4xx: 表示客户端错误

400 参数传递不当,导致的错误

401 权限不够导致的

403 服务端已经理解请求,但是拒绝响应

404 客户端请求的资源或者数据不存在(发现请求接口404,有两种情况一种是咱们写错接口了或者服 务端还没部署)

5xx: 表示服务端错误(遇到以5开头的错误去找服务端错误)

500 服务端内部错误

502 网关错误

3. 从浏览器输入URL按回车到页面显示都发生了什么?****

      • 浏览器根据URL进行DNS查询
        • 首先从DNS缓存中查询
        • 若未在缓存中找到,则不停的向上一级级请求DNS服务器
      • 取得IP地址,建立TCP连接
      • 构造HTTP请求报
        • 添加一些HTTP首部
        • 根据同源政策添加cookie
      • 在TCP连接上发送HTTP报文,等待响应
      • 服务器处理HTTP请求报文,返回响应HTTP响应报文
      • 浏览器处理服务器返回的HTTP响应报文,若为HTML则渲染页面,不包括脚本的简单渲染流程如下
        1. 解析DOM、CSSOM
        2. 根据DOM、CSSOM计算render tree
        3. 根据render tree进行layout
        4. paint,至此,用户可以看到页面了

4.HTTPS和HTTP的区别主要如下?

HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

https主要解决三个安全问题:

      1. 内容隐私
      2. 防篡改
      3. 确认对方身份

https并不是直接通过非对称加密传输过程,而是有握手过程,握手过程主要是和服务器做通讯,生成私有秘钥,最后通过该秘钥对称加密传输数据。还有验证证书的正确性。 证书验证过程保证了对方是合法的,并且中间人无法通过伪造证书方式进行攻击。

5.浏览器缓存?

强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

协商缓存:就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

协商缓存生效,返回304和Not Modified

协商缓存失效,返回200和请求结果协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存

6.ajax四步

  1. 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象

  2. 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息

  3. 设置响应 HTTP 请求状态变化的函数

  4. 发送 HTTP 请求

你使用过哪些ajax?

从原生的XHR到jquery ajax,再到现在的axios和fetch。

axios和fetch都是基于Promise的,一般我们在使用时都会进行二次封装

讲到fetch跟jquery ajax的区别,这也是它很奇怪的地方

当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ), 仅当网络故障时或请求被阻止时,才会标记为 reject。 默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)

一般我们再拦截器中都会写什么代码?

请求拦截中我们一半会把token写在这里,这样的话就不用每次请求都要写这个参数

还会做一个数据格式的处理,假如某个参数需要统一处理 可以放在这里,

响应拦截一半会做一个判断 请求失败的话直接调用失败提示框 这样不用每个接口都写同样的代码

也会再return时 return reponse.data;这样就可以不用每个数据接受的时候都加一个data.data

get请求和post请求有什么区别?什么时候使用post?

GET:一般用于信息获取,使用 URL 传递参数,对所发送信息的数量也有限制,一般在 2000 个字符 POST:一般用于修改服务器上的资源,对所发送的信息没有限制

在以下情况中,请使用 POST 请求: 1. 无法使用缓存文件(更新服务器上的文件或数据库) 2. 向服务器发送大量数据(POST 没有数据量限制) 3. 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

1、HTTP 协议 未规定 GET 和POST的长度限制

2、GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度

3、不同的浏览器和WEB服务器,限制的最大长度不一样

4、要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

Cookie 和 Session 的区别?

      • 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
      • 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
      • 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
      • 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
image.png

Token 相关?

        1. 客户端使用用户名跟密码请求登录
        2. 服务端收到请求,去验证用户名与密码
        3. 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
        4. 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
        5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
        6. 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
        • 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里

        • 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库

        • token 完全由应用管理,所以它可以避开同源策略

**

**

什么是同源策略?

同源策略是客户端脚本(尤其是 Javascript)的重要的安全度量标准。其目的是防止某个文档或脚本从多个不同源装载。 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。

为什么要有同源限制?

我们举例说明:比如一个黑客程序,他利用 Iframe 把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过 Javascript 读取到你的表单中 input 中的内容,这样用户名,密码就轻松到手了

工作中是怎么解决跨域的?

1.jsonp

  1. JSONP原理

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

**

**

2.cors

CORS 需要浏览器和后端同时支持。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。

3.proxy代理 (适用于本地开发)

。。。(其他的方式 可自行去掘金上搜 9种跨域的方式)

      • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案

      • JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

      • 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。

      • 日常工作中,用得比较多的跨域方案是cors和nginx反向代理

http1与http2

image.png

11.编程题

image.png
image.png

22 道高频 JavaScript 手写面试题及答案 》》

juejin.im/post/5e100c...


12.前端100问

juejin.im/post/5d23e7...


13.XSS和CSRF区别

  1. 跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表 CSS 混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。

  2. 跨站请求伪造(Cross-site request forgery),是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份,再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。

区别:

  • 原理不同,CSRF是利用网站A本身的漏洞,去请求网站A的api;XSS是向目标网站注入JS代码,然后执行JS里的代码。
  • CSRF需要用户先登录目标网站获取cookie,而XSS不需要登录
  • CSRF的目标是用户,XSS的目标是服务器
  • XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求
相关推荐
迷雾漫步者15 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-42 分钟前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人4 小时前
前端知识补充—CSS
前端·css
GISer_Jing4 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试