必备
文章目录
- 必备-17.突击
- W3C
- HTML/CSS
- HTML5新增标签元素
- css新增伪类/伪元素
- JS
- JS六座大山
- 函数防抖节流(柯里化)
- 手写call/apply/bind
- 图片懒加载
- 冒泡排序
- 快速排序
- 数组深浅拷贝
- 对象深浅拷贝
- ES6新增
- 封装-判断标准普通对象
- 封装-时间字符串格式化
- vue
- 路由懒加载
- 动态路由表
- vue.use()
- DOM
- 虚拟DOM
- vue/react的DOM渲染
- DOM DIFF
- 跨域身份验证方法
- axios
- axios二次封装
- http
- http请求分类
必备-17.突击
W3C
- W3C:万维网联盟,制定了一系列标准。
- 网页主要由三部分组成:结构、样式、行为
- 对应的三方面的规范:
- 结构:HTML和XML
- 样式:CSS
- 行为:ECMAScript2015(6)
HTML/CSS
HTML5新增标签元素
- 用于绘画的
canvas
元素 - 用于媒介回放的
video
和audio
元素 - 新的结构标签:
header、section、footer、aside、nav、article
- 新的表单控件:
type=number、email、color、date、time、url、tel、calendar、range、search
css新增伪类/伪元素
- :before:在元素内部最前添加内容
- :after:在元素内部最后添加内容
- li:first-child:第一个子li标签
- li:last-child:最后一个子li标签
- li:nth-child(n):匹配当前父标签中所有子标签排序的第n个标签,如果不是li则没有匹配的
- li:nth-last-child(n):从后往前排序
- li:nth-of-type(n):匹配当前父标签中同类型子标签排序的第n个标签
- li:nth-last-of-type(n):从后往前排序
- li:first-of-type
- li:last-of-type
- li:only-of-type
- :first-line:获取文本第一行
- :first-letter:获取文本第一个字
- :empty:选择空标签
- :focus:选择当前获取焦点的表单元素
- :enabled:选择可用的表单元素
- :disabled:选择禁用的表单元素
- :checked:选择选中的单选框或复选框
- :root:获取根元素元素
JS
JS六座大山
- 堆栈内存:闭包作用域
- 面向对象:原型原型链、this、继承、数据类型检测
- DOM操作:DOM方法、DOM事件
- Ajax:HTTP网络协议、跨域
- 同步异步编程
- ES6新规范
函数防抖节流(柯里化)
防抖和节流的区别:如果持续触发事件,防抖是最后触发事件n秒后才执行一次,节流的函数是每n秒后可执行一次
-
防抖:防止"老年帕金森",如果事件在n秒内被触发了多次,就重新计时,事件绑定的函数只能在最后一次触发的n秒后执行
-
**思路:**每次触发函数之前,都清除之前的定时器,即使没有定时器,
clearTimeout(timer)
也不会报错,以input为例
function debounce(fn,...args1) {//利用闭包保存timeout属性let timeout = null; // 创建一个标记用来存放定时器的返回值return function (...args2) {clearTimeout(timeout); // 清除之前的定时器,不管有没有let args=args2.concat(args1)timeout = setTimeout(()=>{ //必须用箭头函数,让函数内的this指向fn.apply(this, args);//修改执行函数的this指向,并且把参数全部传给它}, 500);};}function sayHi() {console.log('防抖成功');}var inp = document.getElementById('inp');inp.addEventListener('input', debounce(sayHi,1,2,3)); // 防抖
-
-
节流:让高频率触发的事件,在n秒内只执行一次绑定的函数,节流的作用就是稀释高频事件绑定函数的执行次数:以
window.onscroll
为例-
**思路:**每次触发事件时,都先判断当前函数有没有被锁住,如果有就不再执行,如果没有就执行,并在执行完成后解锁
-
function throttle(fn,...args1) {//throttle的作用是形成闭包let lock = true;//俗称节流锁,true表示可以执行绑定的函数,false表示不可以执行return function (...args2) {if (!lock) return;//timer=false时,表示上次触发事件的函数还没执行lock = false;//走到这里,先锁上,不让0.5秒内多次执行函数let args=args2.concat(args1)setTimeout(() => {//必须用箭头函数,让this指向DOM元素fn.apply(this, args);//修改fn中的this指向并传参}, 500)}}function sayHello() {console.log("haha")}let input=document.getElementById("inp");input.addEventListener("input",throttle(sayHello,2,3,4))
-
手写call/apply/bind
-
区别:
- call:让函数中的this指向我们指定的对象,并把后面的参数以散列的形式传给函数
- apply:与call的区别就是,后面的参数是以数组的形式传给函数的
- bind:生成一个改变了this指向的新函数,不立即执行,调用新函数才执行,底层用的call实现的
-
call:
-
**思路:**利用obj.fn(),fn中的this指向obj的原理实现
-
Function.prototype.mycall=function(obj,...args){if(obj) obj=window;//如果this是undefined或null,this指向windowif( !/^(object|function)$/.test(typeof obj)) Object(obj);//将不是对象类型的函数转为对象obj[Symbol("myfn")]=this;//将函数放到对象中,为了避免与原生方法属性名冲突,用Symbollet result=obj[Symbol("myfn")](...args);//这时的myfn里面的this指向objdelete obj[Symbol("myfn")];//不留痕迹return result;}//调用let obj={}fn.mycall(obj,1,2,3)
-
-
apply:
-
思路:与call唯一的区别就是,传实参时用数组
-
Function.prototype.myapply=function(obj,params){if(obj) obj=window;//如果this是undefined或null,this指向windowif( !/^(object|function)$/.test(typeof obj)) Object(obj);//将不是对象类型的函数转为对象obj[Symbol("myfn")]=this;//将函数放到对象中,为了避免与原生方法属性名冲突,用Symbollet result=obj[Symbol("myfn")](...params);//这时的myfn里面的this指向objdelete obj[Symbol("myfn")];//不留痕迹return result;}//调用let obj={}fn.mycall(obj,1,2,3)
-
-
bind:
-
**思路:**基于call实现,通过return返回一个新的函数
-
Function.prototype.mybind=function(obj,...args){return function(...arg){this.call(obj,...arg,...args);} }
-
图片懒加载
-
原生js:使用原生的
new IntersectionObserver()
-
**思路:**interstectionObserver()用来监控DOM元素与浏览器窗口的交集,通过
isIntersecting
属性来决定图片的加载 -
let observer = new IntersectionObserver(function(changes){//changes是监控的DOM节点集合changes.forEach((item,index)=>{if(item.isIntersectng){//修改img的src路径,实现加载item.src=item.getAttribute("index");//删除多余的项this.unobserve(item);}})}, options);//创建交叉监听器 //监听img元素 let target=Array.from(document.querySelectorAll("img")) //循环添加监控 target.forEach((item)=>{observer.observe(item); })
-
-
vue的图片懒加载:vue有插件能够实现图片懒加载
vue-lazyload
-
借鉴地址:.html
-
**第一步:**安装插件
-
npm install vue-lazyload --save-dev
-
-
**第二步:**在main.js中引入使用
-
import VueLazyload from 'vue-lazyload' //直接使用: Vue.use(VueLazyload)//或者配置使用 Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', loading: 'dist/loading.gif', attempt: 1 })
-
-
**第三步:**修改img标签为懒加载(将 :src 属性直接改为v-lazy)
-
<a href="javascript:;"><img v-lazy="'/static/img/' + item.productImage"></a>
-
-
冒泡排序
-
思路:两个for循环,内层的每次循环都会在右边产生一个最大的数
- 注意:每次外层循环都会出现一个最大的,所以内层循环的长度应该-i次
-
let arr=[1,2,3,5,3,1,2,4]let bubbleSort=function bubbleSort(arr){for(let i=0;i<arr.length-1;i++){let midarr;for(let j=0;j<arr.length-1-i;j++){if(arr[j]>arr[j+1]) {midarr=arr[j];arr[j]=arr[j+1];arr[j+1]=midarr;}}}return arr;}
快速排序
-
**思路:**找到数组最中间一项,然后按这个值将数组拆分为两个大小数组,两个数组再近一次拆分,最终再拼接在一起
-
const _quickSort = array => {if(array.length<=1){return array}// 创建变量:获取中间值,并创建两个左右数组let newArr=array.slice(0);let mid=Math.floor(newArr.length/2)let leftArr=[];let rightArr=[];let midNum=newArr.splice(mid,1)[0];//分大小放到左右数组中for(let i=0;i<newArr.length;i++){if(newArr[i]>midNum){rightArr.push(newArr[i])}else{leftArr.push(newArr[i]);}}//对分组的数组进行深层次排序let leftSort=_quickSort(leftArr);let rightSort=_quickSort(rightArr);//重新拼接数组return leftSort.concat(midNum,rightSort)}
-
数组深浅拷贝
-
指向同一个引用地址的两个数组不叫拷贝
- 效果:
arr1===arr2
- 效果:
-
浅拷贝:生成新内存空间,只拷贝旧数组中的第一层,数组中如果用引用数据类型,会与旧数组中的引用类型同时指向一个内存空间,藕断丝连
-
效果:
arr1!=arr2
,arr1.obj===arr2.obj
-
思路:重点是生成新数组
-
let oldArr=[1,2,3,4,[5,6]],newArr=[]; //方法一:slicenewArr=oldArr.slice(0); //方法二:concatnewArr=oldArr.concat([]); //方法三:展开运算符newArr=[...oldArr] //方法四:for循环oldArr.forEach((item)=>{newArr.push(item);})
-
-
深拷贝:生成的新数组里面,如果仍然有引用数据类型,对这一项再进一步拷贝,循环加递归
-
//:for循环加递归 let oldArr=[1,2,3,4,[5,6]];//深克隆方法 let deepClone=function(oldarr){let newArr=[]oldarr.forEach((item,index)=>{if(item!=null&&/^(object|function)$/.test(item)){newArr.push(deepClone(item));} newArr.push(item);})return newArr; }
-
对象深浅拷贝
-
对象的拷贝与数组相似,但是由于对象不能遍历,所以实现的方法有些不同
-
浅拷贝:与数组浅拷贝方式相似
let oldObj={name:"lili",arr:[1,2] } //方式一:展开运算符let newObj={...oldObj}//方式二:Object.assign()let newObj=Object.assign({},obj);
-
深拷贝:可以用JSON的方法实现,但是有缺陷,所以我们一般还是用循环加递归
-
let oldObj={name:"lili",arr:[1,2] } //方式一:JSON方法实现(有缺陷)//四大缺陷://遇到bigInt对象值会报错,//遇到undefined/null/function的值会过滤掉//遇到Date对象值会转为字符串//遇到Error对象值会转为空对象let newObj=JSON.parse(JSON.stringify(oldObj)); //方式二:for循环加递归实现function deepClone(oldObj) {let newObj;//如果是数组就用数组的方式循环if (Array.isArray(oldObj)) {newObj = [];oldObj.forEach((item) => {newObj.push(deepClone(item));})return newObj;}//如果是对象,就用对象的方式循环if (typeof oldObj == "object" && oldObj!= null) {newObj = {};//获取对象的所有可迭代和唯一的属性let keys = Object.getOwnPropertyNames(oldObj).concat(Object.getOwnPropertySymbols(oldObj));keys.forEach((item) => {newObj[item] = deepClone(oldObj[item]);//对每一项做深克隆检测})return newObj;}return oldObj;}// let nobj=deepClone(oldObj);console.log(nobj);console.log(nobj==oldObj);//falseconsole.log(nobj.arr==oldObj.arr);//false//方式三:工作中使用lodashnpm i lodash --save//安装lodash包import _ from "lodash"//在main.js中导入let newObj=_.cloneDeep(oldObj);//实现克隆
-
ES6新增
- ES6(ECMAScript 2015):第六次修订
- 新增元素:
- let/const:防止变量提升
- 块儿作用域:只针对于ES6修饰符
- 箭头函数:没有arguments、没有this
- 模板字符串:代替普通字符串
- 解构赋值:从数组中提取值变为局部变量
- **for…of…😗*可以遍历数组、Set、Map、类数组对象、对象、字符串【原理:iterator迭代器】
- 展开运算符:…arr
- 剩余运算符:…args
- class类的继承:
- async、await:Promise的语法糖
- promise:解决回调炼狱的
- Symbol:基本数据类型,唯一值
- Proxy:新增的类,vue3用这个做的状态值监控加
封装-判断标准普通对象
-
问题:document对象、
-
思路:
- 先用检测数据类型的方法判断结果是不是
[Object object]
,如果不是,直接返回false - 进一步判断,这个对象的构造函数是不是
function
类型 - 最后判断,这个对象的构造函数是不是
Object
类 - 全都符合,那它就是普通标准对象
- 先用检测数据类型的方法判断结果是不是
let hasOwn=Object.prototype.hasOwnProperty;//获取检查私有属性方法
let isPlainObject=function isPlainObject(obj){let proto,Ctor;//如果obj是null或undefined,或用检测方法检查出来的不是object,则直接不是纯对象if(!obj||Object.prototype.toString.call(obj)!="[Object object]") return false;proto=Object.getPrototypeOf(obj);//获取obj的原型//如果有proto中有constructor就返回constructor指向的构造函数,否则返回falseCtor=hasOwn.call(proto,"constructor")&&proto.constructor;//如果Ctor是构造函数类型,并且指向Object类,则说明是普通对象 return typeof Ctor="function"&& Ctor=Object}
封装-时间字符串格式化
-
问题:工作中经常遇到获取的时间字符串不是我们想要的格式,所以需要一个格式化字符串的方法
-
思路:传进去一个时间字符串,传进去我们想要的格式,最终返回对应格式的时间字符串
-
let time="2021-7-8" let formatTime=function formatTime(oldTime,temp="{0}年{1}月{2}日"){if(typeof oldTime!="string") throw new TypeError("转换的时间必须是字符串");if(typeof temp!="string") throw new TypeError("模板必须是字符串类型");let arr=oldTime.match(/\d+/g);//["2021","7","8"]return temp.replace(/\{(\d+)\}/g,(_,$1){//_是被捕获的整个元素,$1是第一个小分组(\d+)捕获的值let item=arr[$1]||"00";item=item.length<2?"0"+item:item;return item;})}
vue
路由懒加载
// import(路径) :实现懒加载
// /*webpackChunkName:文件名*/ :分组打包
component:()=>import(/*webpackChunkName:ranking*/"@/views/findMusic/Ranking.vue")
动态路由表
- 动态路由有两种方法:
- 前端控制路由:前端把路由写好,用户登录的时候根据用户的角色权限来用
v-if
动态展示路由 - **后端控制路由:**后台传来当前用户对应权限的路由表(JSON格式),我们前端根据JSON文件,转为路由表再实现动态渲染
- 前端控制路由:前端把路由写好,用户登录的时候根据用户的角色权限来用
vue.use()
-
vue.use和vue.prototype.$xxx的区别?
vue.use
主要用于挂载Plugin插件,它的主要目的是实现Vue功能的扩展vue.prototype.$xxx
:是体现了vue框架渐进式的特点,我们初期不需要安装所有的组件,比如vuex,vue-router,或者自己封装的函数模块,而是当我们需要什么时,只需要插入到Vue类的原型上,就能供所有vue实例使用
-
vue.use()的作用?
-
不是为vue写的插件不支持Vue.use()加载方法
-
非vue官方库不支持new Vue()加载方法
-
Vue.use的作用就是加载哪些可供Vue使用的插件:
-
这些供Vue使用的插件都需要有一个默认的方法
MyPlugin.install()
-
Vue.use(MyPlugin, { someOption: true })//它会自动去找MyPlugin中的install方法//-----------------------------插件可基于install方法给Vue扩展属性方法 MyPlugin.install = function (Vue, options) {//第一个Vue类,第二个是配置项// 1. 添加全局方法或 propertyVue.myGlobalMethod = function () {// 逻辑...}// 2. 添加全局资源Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}...})// 3. 注入组件选项Vue.mixin({created: function () {// 逻辑...}...})// 4. 添加实例方法Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...} }
-
Vue.myGlobalMethod=fn
:扩展全局方法或属性 -
Vue.directive()
:添加全局资源 -
Vue.mixin()
:注入组件选项 -
Vue.prototype.$myMethod()
:添加实例方法
-
-
-
DOM
虚拟DOM
- 什么是虚拟DOM?为什么用虚拟DOM?
- 虚拟DOM是faceBook提出的,解决真实DOM渲染带来性能问题的一种优化方案。
- 我之前也对这个虚拟DOM也产生过好奇,所以专门去看过一些虚拟DOM的文章,那我就说说我对虚拟DOM的理解把:
- 首先,我们知道,浏览器的内核是分为两部分:渲染引擎和JS引擎,浏览器通过DPR渲染出标签节点构成的真实DOM树是放在渲染引擎里的,而JS引擎本身是不能直接操作DOM内存中的DOM树的,所以为了能让他操作,浏览器在JS全局内置window对象中封装了document对象,然后对该对象添加了大量DOM操作接口,这些接口都是由c++语言实现的。
- 渲染引擎:取得网页的内容(HTML/XML/图像)、整理讯息(CSS),以及计算网页的显示方式,再输出到显示器。
- ==JS引擎:==解析Javascript语言,执行javascript语言来实现网页的动态效果。
- 所以,js操作真实DOM,每次都需要经过webkit这个中介来实现,JS操作DOM的次数越多,所导致的性能消耗就会越大,这也是我们将减少JS操作DOM作为性能优化的重点的原因。(而且操作DOM会导致重排和重绘)
- 虚拟DOM的出现,就是为减少JS操作DOM次数,(react和vue都利用了这个概念):它会根据js绑定的HTML节点去生成虚拟DOM对象(vnode),以后js对DOM的操作,实质上是操作这个对象的,当所有的操作处理完成之后,会生成一个新的虚拟DOM对象(new_vnode),再通过DOMDIFF算法,计算出差异,再通知webkit去渲染差异化的部分,这样才能够达到我们想要的性能优化效果
- 这就是我对虚拟DOM的理解,可能我理解的某些地方还不是很透彻,也希望老师们能给我指出来。
- JS操作真实DOM:
- JS操作虚拟DOM:
- React/vue操作虚拟DOM:
vue/react的DOM渲染
- vue的渲染机制:视图层基于template实现
- 第一步:把template模板编译为render函数
- 第二步:把Vue实例挂载到指定节点
- 第三步:基于
vue-template-compiler
模块实现模板解析[生成虚拟DOM(_vnode)] - 第四步:监听data的变化,如果变化了,触发render函数重新生成vnode节点
- 第五步:两个vnode节点做DOM DIFF算法计算出差异,最后通知这是DOM去重新渲染组件(组件与组件之间的隔离性,保证了真实DOM的局部重排)
- React的渲染机制:视图层基于jsx实现
- **第一步:**通过
babel-preset-react-app
将jsx转换为React.createElement()
表达式 - 第二步:调用render()函数,将每个React.createElement表达式渲染成一个虚拟节点element
- 第三步:众多element组成虚拟DOM
- 第四步:ReactDOMComponent将众多element转换为真实节点
- 第五步:当数据发生改变时,触发render()函数生成新的虚拟DOM,通过DOMDIFF算法,算出差异化,再渲染需要更新的真实DOM
- **第一步:**通过
DOM DIFF
- 两个节点做精细化比较,计算出不同的每小部分,再去渲染这些不同的小部分,其他相同的地方无需修改,实现最大可能的性能优化
跨域身份验证方法
-
登录态校验:
-
**思路:**利用cookie获取用户登录状态
- 第一步:客户端发送post请求让服务器做账号密码验证,如果服务器返回成功,客户端在本地cookie中手动设置:isLogin=true,之后再登录时,直接判断是否登陆即可
-
**缺点:**cookie很容易被修改,不安全
-
-
==cookie验证:==利用cookie与服务器端之间的关系
- **思路:**客户端发送请求,服务器端做账号密码校验,验证成功之后,在服务器的session池中存储一份
connect.sid
,并通过set-cookie:connect.sid
传给客户端,客户端收到后直接放到浏览器cookie中,之后再访问页面时,客户端会默认向服务器端发送对应cookie,服务器会做校验,再返回是否可访问。 - **缺点:**仍然可以被伪装获取到,不安全、多服务器的情况下不方便使用
- **思路:**客户端发送请求,服务器端做账号密码校验,验证成功之后,在服务器的session池中存储一份
-
token验证:(目前最流行的)
-
客户端向服务器发送登录请求,服务器进行校验,如果成功,服务器基于JWT(json web token)算法生成一个Token信息【含有密钥、登陆者、过期时间等】,生成之后传给客户端,客户端可以存储到本地(localStorage/sessionStorage…),之后每次访问页面时,都基于axios的请求拦截器发送本地存储的token信息,服务器拿到之后做反解析,然后再去验证它的有效性
-
案例:
-
请求加载最新供应链消息
客户端:请求接口+token
服务器:验证是否能通过token找到用户,若不能——该token不正确
验证token是否失效,若失效——凭证已失效
到权限表查询是否在权限内,若没有——该用户未分配资源
-
-
axios
axios二次封装
import axios from "axios";
let default=axios.defaults;
default.baseUrl="";//所有请求的前缀
default.timeout=1000;//请求超时时间
default.withCrident=true;//跨域是否携带资源凭证
default.transformRequest=data=>{//转换post请求参数的格式return data;
}axios.interceptors.request.use((config)=>{},(reason)=>{})//config:配置的请求头信息
axios.interceptors.response.use((response)=>{},(reason)=>{})//response:响应主体信息
http
http请求分类
- http分为两大类:get和post
- get:
get
:获取/head
:只获取响应头/delete
:删除/opations
:询问支持的方式
- post:
/post
:发送数据/put
:对全部数据更新/patch
:只更新修改了的数据
- get:
必备
文章目录
- 必备-17.突击
- W3C
- HTML/CSS
- HTML5新增标签元素
- css新增伪类/伪元素
- JS
- JS六座大山
- 函数防抖节流(柯里化)
- 手写call/apply/bind
- 图片懒加载
- 冒泡排序
- 快速排序
- 数组深浅拷贝
- 对象深浅拷贝
- ES6新增
- 封装-判断标准普通对象
- 封装-时间字符串格式化
- vue
- 路由懒加载
- 动态路由表
- vue.use()
- DOM
- 虚拟DOM
- vue/react的DOM渲染
- DOM DIFF
- 跨域身份验证方法
- axios
- axios二次封装
- http
- http请求分类
必备-17.突击
W3C
- W3C:万维网联盟,制定了一系列标准。
- 网页主要由三部分组成:结构、样式、行为
- 对应的三方面的规范:
- 结构:HTML和XML
- 样式:CSS
- 行为:ECMAScript2015(6)
HTML/CSS
HTML5新增标签元素
- 用于绘画的
canvas
元素 - 用于媒介回放的
video
和audio
元素 - 新的结构标签:
header、section、footer、aside、nav、article
- 新的表单控件:
type=number、email、color、date、time、url、tel、calendar、range、search
css新增伪类/伪元素
- :before:在元素内部最前添加内容
- :after:在元素内部最后添加内容
- li:first-child:第一个子li标签
- li:last-child:最后一个子li标签
- li:nth-child(n):匹配当前父标签中所有子标签排序的第n个标签,如果不是li则没有匹配的
- li:nth-last-child(n):从后往前排序
- li:nth-of-type(n):匹配当前父标签中同类型子标签排序的第n个标签
- li:nth-last-of-type(n):从后往前排序
- li:first-of-type
- li:last-of-type
- li:only-of-type
- :first-line:获取文本第一行
- :first-letter:获取文本第一个字
- :empty:选择空标签
- :focus:选择当前获取焦点的表单元素
- :enabled:选择可用的表单元素
- :disabled:选择禁用的表单元素
- :checked:选择选中的单选框或复选框
- :root:获取根元素元素
JS
JS六座大山
- 堆栈内存:闭包作用域
- 面向对象:原型原型链、this、继承、数据类型检测
- DOM操作:DOM方法、DOM事件
- Ajax:HTTP网络协议、跨域
- 同步异步编程
- ES6新规范
函数防抖节流(柯里化)
防抖和节流的区别:如果持续触发事件,防抖是最后触发事件n秒后才执行一次,节流的函数是每n秒后可执行一次
-
防抖:防止"老年帕金森",如果事件在n秒内被触发了多次,就重新计时,事件绑定的函数只能在最后一次触发的n秒后执行
-
**思路:**每次触发函数之前,都清除之前的定时器,即使没有定时器,
clearTimeout(timer)
也不会报错,以input为例
function debounce(fn,...args1) {//利用闭包保存timeout属性let timeout = null; // 创建一个标记用来存放定时器的返回值return function (...args2) {clearTimeout(timeout); // 清除之前的定时器,不管有没有let args=args2.concat(args1)timeout = setTimeout(()=>{ //必须用箭头函数,让函数内的this指向fn.apply(this, args);//修改执行函数的this指向,并且把参数全部传给它}, 500);};}function sayHi() {console.log('防抖成功');}var inp = document.getElementById('inp');inp.addEventListener('input', debounce(sayHi,1,2,3)); // 防抖
-
-
节流:让高频率触发的事件,在n秒内只执行一次绑定的函数,节流的作用就是稀释高频事件绑定函数的执行次数:以
window.onscroll
为例-
**思路:**每次触发事件时,都先判断当前函数有没有被锁住,如果有就不再执行,如果没有就执行,并在执行完成后解锁
-
function throttle(fn,...args1) {//throttle的作用是形成闭包let lock = true;//俗称节流锁,true表示可以执行绑定的函数,false表示不可以执行return function (...args2) {if (!lock) return;//timer=false时,表示上次触发事件的函数还没执行lock = false;//走到这里,先锁上,不让0.5秒内多次执行函数let args=args2.concat(args1)setTimeout(() => {//必须用箭头函数,让this指向DOM元素fn.apply(this, args);//修改fn中的this指向并传参}, 500)}}function sayHello() {console.log("haha")}let input=document.getElementById("inp");input.addEventListener("input",throttle(sayHello,2,3,4))
-
手写call/apply/bind
-
区别:
- call:让函数中的this指向我们指定的对象,并把后面的参数以散列的形式传给函数
- apply:与call的区别就是,后面的参数是以数组的形式传给函数的
- bind:生成一个改变了this指向的新函数,不立即执行,调用新函数才执行,底层用的call实现的
-
call:
-
**思路:**利用obj.fn(),fn中的this指向obj的原理实现
-
Function.prototype.mycall=function(obj,...args){if(obj) obj=window;//如果this是undefined或null,this指向windowif( !/^(object|function)$/.test(typeof obj)) Object(obj);//将不是对象类型的函数转为对象obj[Symbol("myfn")]=this;//将函数放到对象中,为了避免与原生方法属性名冲突,用Symbollet result=obj[Symbol("myfn")](...args);//这时的myfn里面的this指向objdelete obj[Symbol("myfn")];//不留痕迹return result;}//调用let obj={}fn.mycall(obj,1,2,3)
-
-
apply:
-
思路:与call唯一的区别就是,传实参时用数组
-
Function.prototype.myapply=function(obj,params){if(obj) obj=window;//如果this是undefined或null,this指向windowif( !/^(object|function)$/.test(typeof obj)) Object(obj);//将不是对象类型的函数转为对象obj[Symbol("myfn")]=this;//将函数放到对象中,为了避免与原生方法属性名冲突,用Symbollet result=obj[Symbol("myfn")](...params);//这时的myfn里面的this指向objdelete obj[Symbol("myfn")];//不留痕迹return result;}//调用let obj={}fn.mycall(obj,1,2,3)
-
-
bind:
-
**思路:**基于call实现,通过return返回一个新的函数
-
Function.prototype.mybind=function(obj,...args){return function(...arg){this.call(obj,...arg,...args);} }
-
图片懒加载
-
原生js:使用原生的
new IntersectionObserver()
-
**思路:**interstectionObserver()用来监控DOM元素与浏览器窗口的交集,通过
isIntersecting
属性来决定图片的加载 -
let observer = new IntersectionObserver(function(changes){//changes是监控的DOM节点集合changes.forEach((item,index)=>{if(item.isIntersectng){//修改img的src路径,实现加载item.src=item.getAttribute("index");//删除多余的项this.unobserve(item);}})}, options);//创建交叉监听器 //监听img元素 let target=Array.from(document.querySelectorAll("img")) //循环添加监控 target.forEach((item)=>{observer.observe(item); })
-
-
vue的图片懒加载:vue有插件能够实现图片懒加载
vue-lazyload
-
借鉴地址:.html
-
**第一步:**安装插件
-
npm install vue-lazyload --save-dev
-
-
**第二步:**在main.js中引入使用
-
import VueLazyload from 'vue-lazyload' //直接使用: Vue.use(VueLazyload)//或者配置使用 Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', loading: 'dist/loading.gif', attempt: 1 })
-
-
**第三步:**修改img标签为懒加载(将 :src 属性直接改为v-lazy)
-
<a href="javascript:;"><img v-lazy="'/static/img/' + item.productImage"></a>
-
-
冒泡排序
-
思路:两个for循环,内层的每次循环都会在右边产生一个最大的数
- 注意:每次外层循环都会出现一个最大的,所以内层循环的长度应该-i次
-
let arr=[1,2,3,5,3,1,2,4]let bubbleSort=function bubbleSort(arr){for(let i=0;i<arr.length-1;i++){let midarr;for(let j=0;j<arr.length-1-i;j++){if(arr[j]>arr[j+1]) {midarr=arr[j];arr[j]=arr[j+1];arr[j+1]=midarr;}}}return arr;}
快速排序
-
**思路:**找到数组最中间一项,然后按这个值将数组拆分为两个大小数组,两个数组再近一次拆分,最终再拼接在一起
-
const _quickSort = array => {if(array.length<=1){return array}// 创建变量:获取中间值,并创建两个左右数组let newArr=array.slice(0);let mid=Math.floor(newArr.length/2)let leftArr=[];let rightArr=[];let midNum=newArr.splice(mid,1)[0];//分大小放到左右数组中for(let i=0;i<newArr.length;i++){if(newArr[i]>midNum){rightArr.push(newArr[i])}else{leftArr.push(newArr[i]);}}//对分组的数组进行深层次排序let leftSort=_quickSort(leftArr);let rightSort=_quickSort(rightArr);//重新拼接数组return leftSort.concat(midNum,rightSort)}
-
数组深浅拷贝
-
指向同一个引用地址的两个数组不叫拷贝
- 效果:
arr1===arr2
- 效果:
-
浅拷贝:生成新内存空间,只拷贝旧数组中的第一层,数组中如果用引用数据类型,会与旧数组中的引用类型同时指向一个内存空间,藕断丝连
-
效果:
arr1!=arr2
,arr1.obj===arr2.obj
-
思路:重点是生成新数组
-
let oldArr=[1,2,3,4,[5,6]],newArr=[]; //方法一:slicenewArr=oldArr.slice(0); //方法二:concatnewArr=oldArr.concat([]); //方法三:展开运算符newArr=[...oldArr] //方法四:for循环oldArr.forEach((item)=>{newArr.push(item);})
-
-
深拷贝:生成的新数组里面,如果仍然有引用数据类型,对这一项再进一步拷贝,循环加递归
-
//:for循环加递归 let oldArr=[1,2,3,4,[5,6]];//深克隆方法 let deepClone=function(oldarr){let newArr=[]oldarr.forEach((item,index)=>{if(item!=null&&/^(object|function)$/.test(item)){newArr.push(deepClone(item));} newArr.push(item);})return newArr; }
-
对象深浅拷贝
-
对象的拷贝与数组相似,但是由于对象不能遍历,所以实现的方法有些不同
-
浅拷贝:与数组浅拷贝方式相似
let oldObj={name:"lili",arr:[1,2] } //方式一:展开运算符let newObj={...oldObj}//方式二:Object.assign()let newObj=Object.assign({},obj);
-
深拷贝:可以用JSON的方法实现,但是有缺陷,所以我们一般还是用循环加递归
-
let oldObj={name:"lili",arr:[1,2] } //方式一:JSON方法实现(有缺陷)//四大缺陷://遇到bigInt对象值会报错,//遇到undefined/null/function的值会过滤掉//遇到Date对象值会转为字符串//遇到Error对象值会转为空对象let newObj=JSON.parse(JSON.stringify(oldObj)); //方式二:for循环加递归实现function deepClone(oldObj) {let newObj;//如果是数组就用数组的方式循环if (Array.isArray(oldObj)) {newObj = [];oldObj.forEach((item) => {newObj.push(deepClone(item));})return newObj;}//如果是对象,就用对象的方式循环if (typeof oldObj == "object" && oldObj!= null) {newObj = {};//获取对象的所有可迭代和唯一的属性let keys = Object.getOwnPropertyNames(oldObj).concat(Object.getOwnPropertySymbols(oldObj));keys.forEach((item) => {newObj[item] = deepClone(oldObj[item]);//对每一项做深克隆检测})return newObj;}return oldObj;}// let nobj=deepClone(oldObj);console.log(nobj);console.log(nobj==oldObj);//falseconsole.log(nobj.arr==oldObj.arr);//false//方式三:工作中使用lodashnpm i lodash --save//安装lodash包import _ from "lodash"//在main.js中导入let newObj=_.cloneDeep(oldObj);//实现克隆
-
ES6新增
- ES6(ECMAScript 2015):第六次修订
- 新增元素:
- let/const:防止变量提升
- 块儿作用域:只针对于ES6修饰符
- 箭头函数:没有arguments、没有this
- 模板字符串:代替普通字符串
- 解构赋值:从数组中提取值变为局部变量
- **for…of…😗*可以遍历数组、Set、Map、类数组对象、对象、字符串【原理:iterator迭代器】
- 展开运算符:…arr
- 剩余运算符:…args
- class类的继承:
- async、await:Promise的语法糖
- promise:解决回调炼狱的
- Symbol:基本数据类型,唯一值
- Proxy:新增的类,vue3用这个做的状态值监控加
封装-判断标准普通对象
-
问题:document对象、
-
思路:
- 先用检测数据类型的方法判断结果是不是
[Object object]
,如果不是,直接返回false - 进一步判断,这个对象的构造函数是不是
function
类型 - 最后判断,这个对象的构造函数是不是
Object
类 - 全都符合,那它就是普通标准对象
- 先用检测数据类型的方法判断结果是不是
let hasOwn=Object.prototype.hasOwnProperty;//获取检查私有属性方法
let isPlainObject=function isPlainObject(obj){let proto,Ctor;//如果obj是null或undefined,或用检测方法检查出来的不是object,则直接不是纯对象if(!obj||Object.prototype.toString.call(obj)!="[Object object]") return false;proto=Object.getPrototypeOf(obj);//获取obj的原型//如果有proto中有constructor就返回constructor指向的构造函数,否则返回falseCtor=hasOwn.call(proto,"constructor")&&proto.constructor;//如果Ctor是构造函数类型,并且指向Object类,则说明是普通对象 return typeof Ctor="function"&& Ctor=Object}
封装-时间字符串格式化
-
问题:工作中经常遇到获取的时间字符串不是我们想要的格式,所以需要一个格式化字符串的方法
-
思路:传进去一个时间字符串,传进去我们想要的格式,最终返回对应格式的时间字符串
-
let time="2021-7-8" let formatTime=function formatTime(oldTime,temp="{0}年{1}月{2}日"){if(typeof oldTime!="string") throw new TypeError("转换的时间必须是字符串");if(typeof temp!="string") throw new TypeError("模板必须是字符串类型");let arr=oldTime.match(/\d+/g);//["2021","7","8"]return temp.replace(/\{(\d+)\}/g,(_,$1){//_是被捕获的整个元素,$1是第一个小分组(\d+)捕获的值let item=arr[$1]||"00";item=item.length<2?"0"+item:item;return item;})}
vue
路由懒加载
// import(路径) :实现懒加载
// /*webpackChunkName:文件名*/ :分组打包
component:()=>import(/*webpackChunkName:ranking*/"@/views/findMusic/Ranking.vue")
动态路由表
- 动态路由有两种方法:
- 前端控制路由:前端把路由写好,用户登录的时候根据用户的角色权限来用
v-if
动态展示路由 - **后端控制路由:**后台传来当前用户对应权限的路由表(JSON格式),我们前端根据JSON文件,转为路由表再实现动态渲染
- 前端控制路由:前端把路由写好,用户登录的时候根据用户的角色权限来用
vue.use()
-
vue.use和vue.prototype.$xxx的区别?
vue.use
主要用于挂载Plugin插件,它的主要目的是实现Vue功能的扩展vue.prototype.$xxx
:是体现了vue框架渐进式的特点,我们初期不需要安装所有的组件,比如vuex,vue-router,或者自己封装的函数模块,而是当我们需要什么时,只需要插入到Vue类的原型上,就能供所有vue实例使用
-
vue.use()的作用?
-
不是为vue写的插件不支持Vue.use()加载方法
-
非vue官方库不支持new Vue()加载方法
-
Vue.use的作用就是加载哪些可供Vue使用的插件:
-
这些供Vue使用的插件都需要有一个默认的方法
MyPlugin.install()
-
Vue.use(MyPlugin, { someOption: true })//它会自动去找MyPlugin中的install方法//-----------------------------插件可基于install方法给Vue扩展属性方法 MyPlugin.install = function (Vue, options) {//第一个Vue类,第二个是配置项// 1. 添加全局方法或 propertyVue.myGlobalMethod = function () {// 逻辑...}// 2. 添加全局资源Vue.directive('my-directive', {bind (el, binding, vnode, oldVnode) {// 逻辑...}...})// 3. 注入组件选项Vue.mixin({created: function () {// 逻辑...}...})// 4. 添加实例方法Vue.prototype.$myMethod = function (methodOptions) {// 逻辑...} }
-
Vue.myGlobalMethod=fn
:扩展全局方法或属性 -
Vue.directive()
:添加全局资源 -
Vue.mixin()
:注入组件选项 -
Vue.prototype.$myMethod()
:添加实例方法
-
-
-
DOM
虚拟DOM
- 什么是虚拟DOM?为什么用虚拟DOM?
- 虚拟DOM是faceBook提出的,解决真实DOM渲染带来性能问题的一种优化方案。
- 我之前也对这个虚拟DOM也产生过好奇,所以专门去看过一些虚拟DOM的文章,那我就说说我对虚拟DOM的理解把:
- 首先,我们知道,浏览器的内核是分为两部分:渲染引擎和JS引擎,浏览器通过DPR渲染出标签节点构成的真实DOM树是放在渲染引擎里的,而JS引擎本身是不能直接操作DOM内存中的DOM树的,所以为了能让他操作,浏览器在JS全局内置window对象中封装了document对象,然后对该对象添加了大量DOM操作接口,这些接口都是由c++语言实现的。
- 渲染引擎:取得网页的内容(HTML/XML/图像)、整理讯息(CSS),以及计算网页的显示方式,再输出到显示器。
- ==JS引擎:==解析Javascript语言,执行javascript语言来实现网页的动态效果。
- 所以,js操作真实DOM,每次都需要经过webkit这个中介来实现,JS操作DOM的次数越多,所导致的性能消耗就会越大,这也是我们将减少JS操作DOM作为性能优化的重点的原因。(而且操作DOM会导致重排和重绘)
- 虚拟DOM的出现,就是为减少JS操作DOM次数,(react和vue都利用了这个概念):它会根据js绑定的HTML节点去生成虚拟DOM对象(vnode),以后js对DOM的操作,实质上是操作这个对象的,当所有的操作处理完成之后,会生成一个新的虚拟DOM对象(new_vnode),再通过DOMDIFF算法,计算出差异,再通知webkit去渲染差异化的部分,这样才能够达到我们想要的性能优化效果
- 这就是我对虚拟DOM的理解,可能我理解的某些地方还不是很透彻,也希望老师们能给我指出来。
- JS操作真实DOM:
- JS操作虚拟DOM:
- React/vue操作虚拟DOM:
vue/react的DOM渲染
- vue的渲染机制:视图层基于template实现
- 第一步:把template模板编译为render函数
- 第二步:把Vue实例挂载到指定节点
- 第三步:基于
vue-template-compiler
模块实现模板解析[生成虚拟DOM(_vnode)] - 第四步:监听data的变化,如果变化了,触发render函数重新生成vnode节点
- 第五步:两个vnode节点做DOM DIFF算法计算出差异,最后通知这是DOM去重新渲染组件(组件与组件之间的隔离性,保证了真实DOM的局部重排)
- React的渲染机制:视图层基于jsx实现
- **第一步:**通过
babel-preset-react-app
将jsx转换为React.createElement()
表达式 - 第二步:调用render()函数,将每个React.createElement表达式渲染成一个虚拟节点element
- 第三步:众多element组成虚拟DOM
- 第四步:ReactDOMComponent将众多element转换为真实节点
- 第五步:当数据发生改变时,触发render()函数生成新的虚拟DOM,通过DOMDIFF算法,算出差异化,再渲染需要更新的真实DOM
- **第一步:**通过
DOM DIFF
- 两个节点做精细化比较,计算出不同的每小部分,再去渲染这些不同的小部分,其他相同的地方无需修改,实现最大可能的性能优化
跨域身份验证方法
-
登录态校验:
-
**思路:**利用cookie获取用户登录状态
- 第一步:客户端发送post请求让服务器做账号密码验证,如果服务器返回成功,客户端在本地cookie中手动设置:isLogin=true,之后再登录时,直接判断是否登陆即可
-
**缺点:**cookie很容易被修改,不安全
-
-
==cookie验证:==利用cookie与服务器端之间的关系
- **思路:**客户端发送请求,服务器端做账号密码校验,验证成功之后,在服务器的session池中存储一份
connect.sid
,并通过set-cookie:connect.sid
传给客户端,客户端收到后直接放到浏览器cookie中,之后再访问页面时,客户端会默认向服务器端发送对应cookie,服务器会做校验,再返回是否可访问。 - **缺点:**仍然可以被伪装获取到,不安全、多服务器的情况下不方便使用
- **思路:**客户端发送请求,服务器端做账号密码校验,验证成功之后,在服务器的session池中存储一份
-
token验证:(目前最流行的)
-
客户端向服务器发送登录请求,服务器进行校验,如果成功,服务器基于JWT(json web token)算法生成一个Token信息【含有密钥、登陆者、过期时间等】,生成之后传给客户端,客户端可以存储到本地(localStorage/sessionStorage…),之后每次访问页面时,都基于axios的请求拦截器发送本地存储的token信息,服务器拿到之后做反解析,然后再去验证它的有效性
-
案例:
-
请求加载最新供应链消息
客户端:请求接口+token
服务器:验证是否能通过token找到用户,若不能——该token不正确
验证token是否失效,若失效——凭证已失效
到权限表查询是否在权限内,若没有——该用户未分配资源
-
-
axios
axios二次封装
import axios from "axios";
let default=axios.defaults;
default.baseUrl="";//所有请求的前缀
default.timeout=1000;//请求超时时间
default.withCrident=true;//跨域是否携带资源凭证
default.transformRequest=data=>{//转换post请求参数的格式return data;
}axios.interceptors.request.use((config)=>{},(reason)=>{})//config:配置的请求头信息
axios.interceptors.response.use((response)=>{},(reason)=>{})//response:响应主体信息
http
http请求分类
- http分为两大类:get和post
- get:
get
:获取/head
:只获取响应头/delete
:删除/opations
:询问支持的方式
- post:
/post
:发送数据/put
:对全部数据更新/patch
:只更新修改了的数据
- get: