前端问题汇总

技术篇

布局的三种模式
  1. 响应式布局
  2. 100%布局(弹性布局)
  3. 等比缩放布局(rem)
移动端白屏问题及其优化方案
  • 1、css文件加载需要一些时间,在加载的过程中页面是空白的。
    解决:可以考虑将css代码前置和内联。
  • 2、首屏无实际的数据内容,等待异步加载数据再渲染页面导致白屏。
    解决:在首屏直接同步渲染html,后续的滚屏等再采用异步请求数据和渲染html。
  • 3、首屏内联js的执行会阻塞页面的渲染。
    解决:尽量不在首屏html代码中放置内联脚本。
  • 还有一些其他的解决办法:
    根本原因是客户端渲染的无力,因此最简单的方法是在服务器端,使用模板引擎渲染所有页面。同时

1、减少文件加载体积,如html压缩,js压缩
2、加快js执行速度 比如常见的无限滚动的页面,可以使用js先渲染一个屏幕范围内的东西
3、提供一些友好的交互,比如提供一些假的滚动条
4、使用本地存储处理静态文件

1像素问题

原因:不同的设备,不同的设备像素比(dpr)导致的;
通过media来媒体查询

React:styled-components解决方案

可设置颜色、类型、粗细,有默认值,也可通过父组件来传递参数

//定义
import styled from 'styled-components'

const border = StyledComp => {
  return styled(StyledComp) `
    position: relative;
    border-radius: ${ props => props.radius || 0 }rem;
    &::after {
      pointer-events: none;
      position: absolute;
      z-index: 999;
      top: 0;
      left: 0;
      content: "";
      border-color: ${ props => props.color || '#ccc' };
      border-style: ${ props => props.style || 'solid' };
      border-width: ${ props => props.width || 0 };

      @media (max--moz-device-pixel-ratio: 1.49),
        (-webkit-max-device-pixel-ratio: 1.49),
        (max-device-pixel-ratio: 1.49),
        (max-resolution: 143dpi),
        (max-resolution: 1.49dppx) {
          width: 100%;
          height: 100%;
          transform: scale(1);
          border-radius: ${ props => props.radius || 0 }rem;
        }
        
      @media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49),
        (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49),
        (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49),
        (min-resolution: 144dpi) and (max-resolution: 239dpi),
        (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) {
          width: 200%;
          height: 200%;
          transform: scale(0.5);
          border-radius: ${ props => props.radius * 2 || 0 }rem;
        }
        
      @media (min--moz-device-pixel-ratio: 2.5),
        (-webkit-min-device-pixel-ratio: 2.5),
        (min-device-pixel-ratio: 2.5),
        (min-resolution: 240dpi),
        (min-resolution: 2.5dppx) {
          width: 300%;
          height: 300%;
          transform: scale(0.3333333);
          border-radius: ${ props => props.radius * 3 || 0 };
        }
        
      transform-origin: 0 0;
    }
  `
}

export default border

包裹一个被加边框的元素后生成一个新的组件

//使用
import border from '@/CommonStyled/border.js'​
//包裹使用
const SearchInput  = border(styled.div `...`)
​//jsx通过属性传递,ChildComponentStyle是Search组件里面的内容,可以通过{...this.props拿到父组件的属性}
<Search width="1px" radius={0.16}></Search>
​//Search.jsx
class Search extends Component {    
	render() {
	        return ( 
	                   <>
	                        <SearchContainer {...this.props}>
	                               <SearchInput {...this.props}>
	                                     <i className="iconfont">&#xe60e;</i>
	                                      <span>{this.props.message}</span>
	                               </SearchInput>
	                        </SearchContainer>
	                     </>
	                             )
	                                 }
	                                 }
vue:style
//加四周边框
$border(width = 0, color = #ccc, style = solid, radius = 0)
  position relative
  border-radius radius
  &::after
    pointer-events none
    position absolute
    z-index 999
    top 0
    left 0
    content ""
    border-color color
    border-style style
    border-width width

    @media (max--moz-device-pixel-ratio: 1.49),
      (-webkit-max-device-pixel-ratio: 1.49),
      (max-device-pixel-ratio: 1.49),
      (max-resolution: 143dpi),
      (max-resolution: 1.49dppx)
      width 100%
      height 100%
      transform scale(1)
      border-radius radius

    @media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49),
      (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49),
      (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49),
      (min-resolution: 144dpi) and (max-resolution: 239dpi),
      (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx)
      width 200%
      height 200%
      transform scale(0.5)
      border-radius radius * 2

    @media (min--moz-device-pixel-ratio: 2.5),
      (-webkit-min-device-pixel-ratio: 2.5),
      (min-device-pixel-ratio: 2.5),
      (min-resolution: 240dpi),
      (min-resolution: 2.5dppx)
      width 300%
      height 300%
      transform scale(0.3333333)
      border-radius radius * 3

    transform-origin 0 0
//加底部边框
$borderdown($color) 
    position: relative;

    @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5) 
      &::before 
        content: " ";
        position: absolute;
        left: 0px;
        bottom: 0px;
        background-color: $color;
        transform: scaleY(0.667);
        height: 1px;
        width: 100%;
      
  
    @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) 
      &::before 
        content: " ";
        position: absolute;
        left: 0px;
        bottom: 0px;
        background-color: $color;
        transform: scaleY(0.5);
        height: 1px;
        width: 100%;
      
  
    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) 
      &::before 
        content: " ";
        position: absolute;
        left: 0px;
        bottom: 0px;
        background-color: $color;
        transform: scaleY(0.333);
        height: 1px;
        width: 100%;
//使用
<style lang='stylus' scoped>
    @import "../../assets/style/border";
    @import "../../assets/style/borderDown";
    
    
    .className
        $border(1px,rgba(0,0,0,.08),,8px)
        .childclassName
            $borderdown(rgba(0,0,0,.08))
<style>
图片瀑布流实现思路
  1. 设定每一列图片的宽度和间距
  2. 获取当前窗口的总宽度,从而根据图片宽度去判断分成几列
  3. 获取所有图片元素,定义一个空数组来保存高度
  4. 遍历所有容器,开始判断  当页面加载完成,或页面宽度发生变化时,调用函数。
  5. 如果当前处于第一行时: 直接设置图片位置【 即 top为间距的大小,left为(当前图片的宽度+间距) * 当前图片的值+间距大小 】,并保存当前元素高度。
  6. 如果当前不处于第一行时:进行高度对比,通过遍历循环,拿到最小高度和相对应的索引,设置图片位置【 即 top为最小高度值+间距*2,left为 (当前图片的宽度+间距) * 索引 值+间距大小)】,并修改当前索引的高度为当前元素高度。
  7. 当页面加载完成,或页面宽度发生变化时,调用函数。
自己写的模块造的轮子怎么上传
1 编写模块

保存为index.js

2 初始化包描述文件
$ npm init package.json

{ 

 "name": "gp19-npm", 

 "version": "1.0.1", 

 "description": "gp19 self module", 

 "main": "index.js",

 "scripts": { 

  "test": "make test" 

 }, 

 "repository": { 

  "type": "Git", 

  "url": "git+https://github.com/lurongtao/gp19-npm.git" 

 }, 

 "keywords": [ 

  "demo" 

 ], 

 "author": "Felixlu", 

 "license": "ISC", 

 "bugs": {  "url": "https://github.com/lurongtao/gp19-npm/issues"  }, 

 "homepage": "https://github.com/lurongtao/gp19-npm#readme", 

}
3 注册npm仓库账号

$ npm adduser

4 上传包

$ npm publish

上传过程中容易的错:
  • 坑1:403 Forbidden
    查看npm源:npm config get registry
    切换npm源方法一:npm config set registry http://registry.npmjs.org
    切换npm源方法二:nrm use npm
  • 坑2:403
    首次上传包需要验证邮箱
5 安装包

$ npm install gp19-npm

6 卸载包

查看当前项目引用了哪些包 :
npm ls
卸载包:
npm unpublish --force

7 使用引入包
var hello = require('gp19-npm')
hello.sayHello()
移动端布局适配设备的方案
响应式布局

简而言之,就是页面元素的位置随着屏幕尺寸的变化而变化,通常会用百分比来定位,而在设计上需要预留—些可被“压缩”的空间。

Cover布局

就跟background—size的cover属性—样,保持页面的宽高比,取宽或高之中的较小者占满屏幕,超出的内容会被隐藏。此布局适用于主要内容集中在中部,边沿无重要内容的设计。

Contain布局

同样,也跟background—size的contain属性那样,保持页面的宽高比,取宽或高之中的较大者占满屏幕,不足的部分会用背景填充。个人比较推荐用这种方式,但在设计上需要背景为单色,或者是可平铺的背景。

规定文字显示行数,多余部分用省略号代替

在这里插入图片描述

1:设置元素宽度
width:200px;
overflow:hidden;//超出文本隐藏
display: -webkit-box;
text-overflow:ellipsis;//超出文本省略号显示
-webkit-line-clamp:2;//2行显示
-webkit-box-orient:vertical;//从上到下垂直排列

在这里插入图片描述

2:不设置宽度,弹性布局

父元素设置:display:flex
子元素:flex:1

flex:1;
text-overflow:ellipsis;//超出文本省略号显示
white-space:nowrap;//单行显示,不换行
overflow:hidden;//超出文本隐藏

在这里插入图片描述

BFC

有BFC特性的元素可以看做是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素。

BFC特征及应用
  • 1.当元素在同一个BFC中。垂直方向上的margin会重叠,解决的方法是将它们放在不同的BFC容器中。
  • 2.BFC可以包含浮动元素(清除浮动),只要设置容器overflow:hidden
  • 3.BFC可以阻止元素被浮动元素覆盖(文字环绕问题),只要设置元素overflow:hidden
触发条件
  • html根元素
  • 浮动元素:float除none以外的值
  • 绝对定位元素:position(absolute,fixed)
  • display为inline-block、flex
  • overflow除了visible以外的值(hidden、auto、scroll),建立BFC的最好方式莫过于overflow:hidden。
用过哪些css预处理器,有什么特性(Sass/Less/Stylus/StyledComponent)
  • Sass 最早也是最成熟的一款CSS预处理器语言,可以更有效有弹性的写出CSS,支持不包含花括号和分号的方式。受Less影响,已经进化到了全面兼容 CSS 的 SCSS(SCSS 需要使用分号和花括号而不是换行和缩进)
  • Less 受Sass的影响较大,但又使用CSS的语法,优点是简单和兼容CSS。
  • Stylus 支持的语法要更多样性一点,可以省略花括号"{}", 冒号 “:”, 分号";",或者直接使用纯 CSS,使用灵活度更高。
  • 它们都可以使用变量、常量、嵌套、混入、函数等特性。
  • styled-components是针对 react 写的一个 css-in-js 类库,它也可以通过 js 赋能解决了原生 css 所不具备的能力,比如变量、循环、函数等
闭包及应用

理解:闭包就是能够读取其他函数内部变量的函数,所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁;

闭包的三个特性

1.函数嵌套函数

2.函数内部可以引用外部的参数和变量

3.参数和变量不会被垃圾回收机制回收

闭包的常见用途
  • 创建特权方法用于访问控制
  • 事件处理程序及回调
闭包的使用场景

1.可以储存一个可以长期驻扎在内存中的变量

2.避免全局变量的污染

3.保证私有成员的存在

闭包的缺点

1.闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

为什么闭包不会被垃圾回收机制回收(javascript的垃圾回收机制)

在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收,如果两个对象相互引用,而不再被第三方所引用,那么这两个互相吸引的对象也会被回收,因为A被B引用,B又被A外面的C引用,所以定义了闭包的函数虽然销毁了但是其变量对象依旧被绑定在函数上,只要仍然被引用,变量会继续保存在内存中,这就是为什么函数A执行后不会被回收的原因。

闭包的实现

实际应用:循环中使用闭包解决 var 定义函数的问题

原函数:

for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。

改进:

for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
//我们首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。
函数的节流和防抖
节流

防抖是延迟执行,而节流是间隔执行,和防抖的区别在于,防抖每次触发事件都会重置定时器,而节流在定时器到时间后再清空定时器,函数节流即每隔一段时间就执行一次,实现原理为设置一个定时器,约定多长时间后执行事件,如果时间到了,那么执行函数并重置定时器

//func是用户传入需要节流的函数
//wait是等待时间
const throttle = (func,wait = 50)=>{
//上一次执行该函数的时间
let lastTime = 0
return function(...args){
//当前时间
let now = +new Date()
//将当前时间和上一次指向函数时间对比
//如果差值大于设置的等待时间就执行函数
if(now - lastTime > wait){
	lastTime = now
	func.apply(this.args)
}
}
}
setInterval(
	throttle(()=>{
		console.log(1)
	},500),
)
防抖

防抖即短时间内大量触发同一事件,只会执行一次函数,防抖常用于搜索框/滚动条的监听事件处理,如果不做防抖,每次输入/滚动屏幕都会触发事件处理,造成性能的浪费,实现原理是设置一个定时器约定在xx毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到xx毫秒内没有第二次操作。

//func是用户传入需要进行防抖操作的函数
//wait是等待时间
const debounce = (func,wait = 50)=>{
	//缓存一个定时器id
	let timer = 0
	//这里返回的函数是每次用户实际调用的防抖函数
	//如果已经设定过定时器了就清空上一次的定时器
	//开始一个新的定时器,延迟用户传入的方法
	returen function(...args){
		if(timer) clearTimeout(timer)
		timer = setTimeout(()=>{
		fun.apply(this,args)
		},wait)
	}
}
函数柯里化和反柯里化
柯里化

柯里化,可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数。因为这样的特性,也被称为部分计算函数。柯里化,是一个逐步接收参数的过程。

反柯里化

反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。目的是创建一个更普适性的函数,可以被不同的对象使用。有鸠占鹊巢的效果。

浏览器渲染页面的过程
  • 1.当我们打开一个网页时,浏览器都会去请求对应的HTML文件,虽然我们平时写代码都会分js、CSS、HTML文件,也就是字符串,但是计算机硬件是不理解这些字符串的,所以网络中传输的内容其实都是0,1这些字节数据,当浏览器接收到这些字节数据以后,他会将这些字节数据转换为字符串,也就是我们平时写的代码。
  • 2.当数据转换为字符串之后,浏览器会先将这些字符串通过词法分析转换为标记,这一过程在词法分析中叫做标记化。
  • 3.标记化结束之后,这些标记会紧接着转换为Node,最后这些Node会根据不同Node之前的联系构建为一颗DOM树。
  • 4.CSS文件同样也会转换为CSSOM树
  • 5.生成渲染树,当浏览器生成DOM树和CSSOM树,将他们组合成渲染树,渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是display:none的,那么就不会在渲染树中显示。
  • 6.当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流),然后调用GPU绘制,合成图层,显示在屏幕上。
浏览器缓存

缓存可以说是性能优化中简单高效的一种方式了,他可以显著减少网络传输所带来的损耗,对于一个数据请求来说,可以分为发起网络请求,后端处理,浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能,比如直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要将数据回传回来,这要就减少了响应数据

缓存位置

1.Service Worker

Service Worker的缓存与浏览器其他内建缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存,如何读取缓存,并且缓存是持续性的。
Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能,使用Service Worker的话,传输协议必须为HTTPS。因为Service Worker中涉及到请求拦截,所以必须使用HTTPS协议来保障安全。
Service Worker实现缓存功能一般分为三个步骤:首先需要先注册Service Worker,然后监听到install事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以童工拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

2.Memory Cache

Memory Cache也就是内存中的缓存,读取内存中的数据要比磁盘快,但是内存缓存虽然读取高效,可是缓存持续性很短,会随进程的释放而释放,一旦我们关闭了Tab页面,内存中的缓存也就被释放了,对于大文件来说,大概率是不存储在内存中的,小文件优先存在内存中,当前系统内存使用率高的话,文件优先存储进硬盘

3.Disk Cache

Disk Cache也就是存储在硬盘中的缓存,读取速度慢点。但是什么都可以存储到磁盘中,比Memory Cache胜在容量和存储的时效性上,在所有浏览器缓存中,Disk Cache覆盖面基本是最大的,它会根据HTTP Herder中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。

4.Push Cache

Push Cache是HTTP/2中的内容,当以上三种缓存都没有命中时,他才会被使用,并且缓存的时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放,

因为在国内并不普及,所以资料较少,查阅资料得出一下三个结论

  • 所有的资源都可以被推送,但Edge和Safari浏览器兼容性不好
  • 可以推送no-cache和no-store的资源
  • 一旦连接被关闭,Push Cache就被释放
  • 多个页面可以使用相同的HTTP/2连接,也就是说能使用同样的缓存
  • Push Cache中的缓存只能被使用一次
  • 浏览器可以拒绝接受已经存在的资源推送
  • 你可以给其他域名推送资源

5.网络请求

如果所有的缓存都没有命中的话,那么只能发起请求来获取资源了

缓存策略

通常浏览器的缓存策略分为两种:强缓存协商缓存,并且缓存策略都是通过设置HTTP Header来实现的

1.强缓存

强缓存可以通过设置两种HTTP Header实现ExpiresCache-Control。强缓存表示在缓存期间不需要请求,state code为200。

Expires:

Expires:web,22 Oct 2018 08:41:00 GMT

Expires是HTTP/1的产物,表示资源会在web,22 Oct 2018 08:41:00 GMT后过期,需要再次请求。并且Expires受限于本地时间,如果修改了本地时间可能会造成缓存失效。

Cache-Control

 Cache-control:max-age=30

Cache-Control出现于HTTP/1.1,优先级高于Expires。该属性值表示资源会在30秒后过期,需要再次请求。

Cache-Control可以在请求头或者响应头中的设置,并且可以组合使用多种指令

2.协商缓存

  • 如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种HTTP Header实现:Last-Modified和ETag。
  • 当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回304状态码,并且更新浏览器缓存有效期

Last-Modified和If-Modified-Since

  • Last-Modified表示本地文件的最后修改日期,If-Modified-Since会将Last-Modified的值发送给服务器,询问服务器在该日期后的资源是否有更新,有更新的话就会将新的资源发送回来否则返回304状态码
  • Last-Modified是一个响应首部,其中包含源头服务器认定的资源做出修改的及时间。它通常被用作一个验证器来判断接收到的或者存储的资源彼此是否一致。由于精确度比ETag要低,所以这是一个备用机制。包含If-Modified-Since或If-Unmodified-Since部首的条件请求会使用这个字段
    基于客户端和服务器端协商的缓存机制
Last-Modified ----response header

If-Modified-Since ----request header

需要与cache-control共同使用

max-age的优先级高于Last-Modified

缺点

某些服务器端不能获取精确地修改时间
文件修改时间改了,但是文件内容却没有变

ETag和If-None-Match

  • ETag类似于文件指纹,If-None-Match会将当前的ETag发送给服务器,咨询该资源ETag是否变动,有变动的话就将新的资源发送回来。并且ETag优先级比Last-Modified高
  • ETagHTTP响应头是资源的特定版本的标识符,这可以让缓存更高效,并且节省宽带,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新互相覆盖
    文件内容的hash值
etag--response header
if-none-match --request header
要与cache-control共同使用

两者对比

  • 首先在精度上,ETag要高于Last-Modified.
  • 在性能上,ETag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而ETag需要服务器通过算法来计算出一个hash值
  • 在优先级上,服务器校验优先考虑ETag
在什么场景下使用缓存策略

1.频发变动的资源

对于频繁变动的资源,首先要使用Cache-Control:no-cache使浏览器每次都请求服务器,然后配合ETag或者Last-Modified来验证资源是否有效,这样做虽然不可以节省请求数量,但是能显著减少相应数据的大小。

2.代码文件

这里特指除了HTML外的代码文件,因为HTML文件一般不缓存或者缓存时间很短

一般来说现在都会使用工具来打包代码那么我们就可以对文件名进行哈希处理,只有当代码修改后才会产生新的文件名,基于此,我们就可以给代码文件设置缓存有效期一年Cache-Control:max-age=31536000,这样只有当HTML文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存

浏览器端事件循环==Event loop

当我们执行JS代码的时候其实就是往执行栈中放入函数,其实当遇到异步的代码时,会被挂起并在需要执行的时候加入到Task(有多种Task)队列中。一旦执行栈为空,Event Loop就会从Task队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说JS中的异步还是同步行为

事件循环

不同的任务源会被分配到不同的Task队列中,任务源可以分为微任务(microtask)宏任务(macrotask)。在ES6规范中。microtask称为jobs,macrotask称为task.

Event loop的执行顺序:

  • 首先执行同步代码,这属于宏任务
  • 当执行完所有的同步代码之后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如果必要会渲染页面
  • 然后开始下一轮Event loop,执行宏任务中的异步代码。,也就是setTimeout中的回调函数

微任务包括:process.nextTick,promise,MutationObserver.

宏任务包括:script,setTimeout,setInterval,setImmediate,I/O,UI rendering.

这里存在一个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了script,浏览器会先执行一个宏任务,接下来有异步代码的话才会先执行 微任务。

客户端渲染(前端渲染SPA)和服务端渲染(后端渲染SSR)的区别,以及优缺点

客户端渲染和服务端渲染

客户端渲染
客户端渲染就是指浏览器会从后端得到信息,然后由浏览器来完成的将这些信息解析成HTML,再进行显示。
服务端渲染
服务端渲染的情况,由服务端接受请求后处理数据并生成html返回给浏览器,浏览器只进行了HTML的解析和显示。
服务端渲染,客户端渲染的优缺点

服务端渲染

优点

  • 对 SEO 友好
  • 首屏加载快
  • 不占用客户端资源

缺点

  • 对服务器压力大
  • 不利于前后端分离,开发效率低

客户端渲染

优点

  • 对 SEO 友好
  • 首屏加载快
  • 不占用客户端资源

缺点

  • 对服务器压力大
  • 不利于前后端分离,开发效率低
session+cookie 与 token 认证
  • session 是基于cookie实现的。
  • cookie保存在客户端浏览器中,而session是保存在服务器上;
  • cookie的机制是通过检查客户身上的通行证来确定用户的身份,session的机制就是通过检查服务器上的客户表来确定客户的身份;session相当于在服务器上建立一份客户档案,客户来访问的时候只需要通过查询客户表就可以了。
cookie和session的区别:
  1. 存放的位置:cookie是存在与客户端的临时文件夹中;session是存在于服务器的内存中,一个session域对象为一个用户浏览器服务。
  2. 安全性:cookie是以明文的方式存放在客户端上,安全性比较低,但是可以通过一个加密算法进行加密后存放;session是存放在服务器的内存中,所以安全性比较好。
  3. 生命周期:cookie的生命周期是通过设置有效时间来进行累计的,从创建开始计时,有效时间结束即cookie生命周期结束;session的生命周期是间隔的,从创建时开始计时,如果在有效时间内没有被访问,那么session的生命周期就会被销毁,但是如果在这个时间内访问过,那么将重新计算session的生命周期。关机会造成session生命周期的结束,但是对cookie没有影响。
  4. 访问范围:cookie为多个用户浏览器共享;session为一个用户浏览器独享。
跨域的解决方案
  1. jsonp

核心思想:浏览器的script、img、iframe标签是不受同源策略限制的 ,所以通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的callback函数,并把把我们需要的json数据作为参数传入。在服务器端,当req.params参数中带有callback属性时,则把数据作为callback的参数执行,并拼接成一个字符串后返回。

  • 优点:兼容性好,在很古老的浏览器中也可以用,简单易用,支持浏览器与服务器双向通信。
  • 缺点:只支持GET请求,且只支持跨域HTTP请求这种情况(不支持HTTPS
  1. document.domain + iframe跨域

此方案仅限主域相同,子域不同的跨域应用场景。

实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

  1. location.hash + iframe

实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

  1. window.name + iframe跨域

name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

  1. postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

  1. 跨域资源共享(CORS)
  1. 后端添加允许的请求头
	// 配置 cors 跨域
	header("Access-Control-Allow-Origin:*");
	header("Access-Control-Request-Methods:GET, POST, PUT, DELETE, OPTIONS");
	header('Access-Control-Allow-Headers:x-requested-with,content-type,test-to
  1. nginx代理跨域

1、 nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

2、 nginx反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

  1. nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1、 非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一个proxy服务器。

2、 vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

  1. WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

jsonp跨域的流程

jsonp是⼀种⾮正式传输协议,⽤于解决跨域问题

1、创建⼀个全局函数
2、创建⼀个script标签
3、给script添加src
4、给src添加回调函数test(callback=test) callback是传给后端的⼀个参数
5、将script放到⻚⾯上
6、script请求完成,将⾃⼰从⻚⾯上删除

路由实现原理

本质:监听URL的变化,然后匹配路由规则,显示相应的页面,并且无需刷新页面

路由需要实现三个功能:

  • 1.浏览器地址变化,切换页面
  • 2.点击浏览器【后退】、【前进】按钮,网页内容跟随变化
  • 3.刷新浏览器,网页加载当前路由对应的内容

目前路由只有两种实现模式

  • Hash 模式
  • History模式
Hash 模式
  • 点击跳转或者浏览器历史跳转,当#后面的哈希值发生变化时,不会向服务器请求数据,可以通过hashchange事件来监听到URL的变化,从而进行跳转页面,
  • 手动刷新不会触发hashchange事件,可以采用load事件监听解析URL,匹配相应的路由规则,跳转到相应的页面,然后通过DOM替换更改页面内容
History模式
  • 利用history API实现url地址改变,网页内容改变
  • History.back()、History.forward()、History.go()移动到之前访问过的页面时,页面通常是从浏览器缓存之中加载的,而不是重新要求服务器发送新的网页
  • History.pushState()用于在历史记录中添加一条记录,该方法接受三个参数:
  • 1.依次为一个与添加的记录相关联的状态对象:state;
  • 2.新页面的标题title;
  • 3.必须与当前页面处在同一个域下的新的网址URL

该方法不会触发页面刷新,只是导致History对象发生变化,地址栏会有反应,不会触发

hashchange事件

History.replaceState( )方法用来修改History对象的当前记录

两种模式的对比
  • hash模式只可以更改#后面的内容,History模式可以通过API设置任意的同源的URL
  • history模式可以通过API添加任意类型的数据到历史记录中去,hash模式只能更改哈希值,也就是字符串
  • hash模式无需后端配置,并且兼容性好。history模式在用户手动输入地址或者刷新页面的时候会发起URL请求,后端需要配置index.html页面用于匹配不到静态资源的时候。
Ajax创建过程
  • (1)创建XMLHttpRequest对象,也就是创建—个异步调用对象
  • (2)创建—个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
  • (3)设置响应HTTP请求状态变化的函数
  • (4)发送HTTP请求
  • (5)获取异步调用返回的数据
  • (6)使用JavaScript和DOM实现局部刷新
权限管理如何实现

(1)前端控制:

前端的控制比较简单,从后台获取到用户的权限之后,可以存在session或者cookie中,然后在页面加载的时候,通过session或者cookie中存的权限来选择让该功能展现或者禁用。

(2)后台控制:

  • 仅仅依靠前端的控制是无法完美解决权限控制的问题,因为前端页面的加载过程是在浏览器中完成的,用户可以自行篡改页面;或者用户可以直接通过URI请求来获取非法权限功能。所以需要在后台实现权限控制。
  • 后台的控制方法也很多,比如filter、spring的AOP等。在此选用springMVC的interceptor来控制。

(3)全局异常管理:

思路是在拦截器中权限校验失败时,抛出—个权限校验失败的异常,然后通过全局异常管理类来捕获并返回前端特定的格式。

AMD 和 CMD
  • AMD 推崇依赖前置(事先加载好需要⽤到的模块)
  • CMD 推崇就近(⽤到时再加载)
// AMD
define(['./a', './b'], function(a, b) {
// 加载模块完毕可以使用
a.do()
b.do()
})
// CMD
define(function(require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require('./a')
a.doSomething()
})

ES6篇

ES6新添加的特性

1.增加块作用域
2.增加let const
3.解构赋值
4.函数参数扩展 (函数参数可以使用默认值、不定参数以及拓展参数)
5.增加class类的支持
6.增加箭头函数
7.增加模块和模块加载(ES6中开始支持原生模块化啦)
8.math, number, string, array, object增加新的API

  • Default Parameters(默认参数)
  • Default Parameters(默认参数)
  • Multi-line Strings (多行字符串)
  • Destructuring Assignment (解构赋值)
  • Enhanced Object Literals (增强的对象文本)
  • Arrow Functions (箭头函数)
  • Promises
  • Block-Scoped Constructs Let and Const(块作用域构造Let and Const)
  • Classes(类)
  • Modules(模块)
写出几种IE6 bug的解决方法

1)png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8.也可以引用—段脚本处理.
2)IE6双倍边距bug:在该元素中加入display:inline 或 display:block
3)像素问题 使用多个float和注释引起的 使用dislpay:inline —3px
4)超链接hover 点击后失效 使用正确的书写顺序 link visited hover active
5)z—index问题 给父级添加position:relative
6)Min—height 最小高度 !Important 解决’ 7.select 在ie6下遮盖 使用iframe嵌套
7)为什么没有办法定义1px左右的宽度容器(IE6默认的行高造成的,使用over:hidden,zoom:0.08 line—height:1px)

ES6中Set、Map的区别(数据)
  • Map对象是键值对集合,但key不仅可以是字符串还可以是其他各种类型的值,包括对象都可以成为Map的键
  • Set对象类似于数组,其成员的值都是唯一的,且无法通过下标取值。
  • 任意两个NaN虽然不相等,但是在set和map中都会被视作是相等的。
map 和 forEach的区别

map和forEach都是对数组的遍历,但是他们是有区别的,forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。
例如,在react经常需要使用数据进行渲染元素的时候,那么这时候,我们就需要使用map了,返回一个新的元素的数组进行渲染。而forEach就是不需要返回新的数据,需要在原数组的每一项上修改的时候,我们经常会用到。

ES6的generator yield

Generator的初衷是用来解决异步的嵌套问题

语法:

使用 * 声明一个函数,这个函数在调用时会返回一个Generator对象,使用 * 声明的函数内部使用yield生成阻塞行为,yield后面声明的内容,配合一个外部调用的next方法来控制被阻塞代码的执行,并且yiled会将Generator中的value值设置成为刚刚被阻塞的内容的返回值,一个yiled配合一个next使用。

所以在使用时通常可以将异步处理放在yiled后生成一个阻塞,配合一个返回值是promise的函数进行异步处理操作,需要使用两次的next,第一次是调用yiled后的函数,第二次是获得异步操作的结果

实际应用:redux中间件的saga中takeEvery内部封装了这两部操作,两步并作一步直接让开发者拿到结果

let var const 区别

什么是提升:变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做提升,并且提升的是声明。

区别:

  • var 存在提升,我们能在声明之前使用。letconst 因为暂时性死区的原因,不能在声明前使用
  • var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会
  • letconst 作用基本一致,但是后者声明的变量不能再次赋值
装饰器是什么

装饰器是ES6中一种增强类的功能函数,基本实现就是传入一个类,在修饰器加工后返回一个具有修饰器中功能的新的类,常用在react中,类似于高阶组件的实现,但时没有了高阶组件的嵌套结构,语法上清晰明了
但是在webpack环境中如果使用修饰器,需要另外载入一些解析修饰器语法的babel

 "@babel/core": "^7.10.5",
 "@babel/preset-env": "^7.10.4",
 "@babel/plugin-proposal-decorators": "^7.10.5"

JavaScript篇

原型
  • javascript规定,每一个函数都有一个prototype对象属性,指向另一个对象(原型链上面的)。prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承,这意味着我们可以把一些不变的属性和方法,直接定义到prototype对象属性上。
    prototype就是调用构造函数所创建的那个实例对象的原型(proto)
原型链

实例对象和原型之间的连接,叫做原型链,每个对象都又一个proto属性,原型链上的对象就是依靠这个属性连接在一起的。

其实原型链就是多个对象通过 __proto__ 的方式连接了起来。

对象的 __proto__ 属性指向原型, __proto__ 将对象和原型连接起来组成了原型链。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

继承
原型链继承

直接让子类的原型对象指向父类实例来继承父类的属性和方法

Child.prototype=new Parent( )

缺点:

  • 1.由于所有Child实例原型都指向同一个Parent实例,因此对某个Child实例的父类引用类型变量修改会影响所有的Child实例
  • 2.在创建子类实例时无法向父类构造传参,即没有实现super()功能
构造函数继承

构造函数继承,就是在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去;这样既可以避免实例之间共享一个原型实例,又能向父类构造函数传参

在子类的构造函数中执行继承
Parent.app(this,arguments);

缺点:

1.继承不到父类原型链上的属性和方法

组合式继承

组合使用原型链继承和构造函数继承

Child.prototype=new Parent()
	//相当于在Child的构造函数中给Parent绑定this
	Child.prototype.constructor=Child

缺点:

1.每次创建子类实例都执行了两次构造函数(Parent.call( )和new Parent( )),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,不优雅

寄生式组合继承

将子类原型指向父类实例改为子类原型指向父类原型的浅拷贝,减去一次构造函数的执行,这种继承方式称为寄生组合式继承.

this的指向

在这里插入图片描述

普通函数
  • 对于直接调用 foo 来说,this 一定是 window
  • 对于 obj.foo() 来说,谁调用了函数,谁就是 thisthis 就是 obj 对象
  • 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this
箭头函数

首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this

bind,call,apply:

对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window

多个规则同时出现的情况,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。

JS的基本数据类型,判断方法有哪些?typeof 判断哪个类型会出现问题?
  1. typeof

其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 8 种:number、boolean、symbol、string、object、undefined、function、bigInt 等。
但是 typeof 有一定的缺陷。typeof NaN 的返回值是 number,typeof [] 返回值是 object,typeof null 结果也是 object。所以用typeof除了string和boolean其他的都不太靠谱

  1. instanceof

一般用来判断引用数据类型的判断,比如 Object、Function、Array、Date、RegExp等等。实现原理主要就是判断右边变量的 prototype 在不在左边变量的原型链上,判断对象和构造函数在原型链上是否有关系,如果有关系,返回真,否则返回假。所以使用 instanceof 来判断类型的话只能知道是不是要判断的这个类型,如果不是就不能知道其准确的类型。

  1. Object.prototype.toString.call

toString 是 Object 原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString 运行时 this 指向的对象类型, 返回的类型格式为 [object,xxx],xxx 是具体的数据类型,包括 String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument…

作用域链

作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

谈谈你对缓存的理解
定义

缓存就相当于是对资源的一种副本实现,不管是在客户端还是服务端储存着,用相同的URL进行请求,直接从副本中请求资源而不再访问源服务器。

为什么使用缓存
  • 1.提高访问速度:缓存相对服务器端离用户更近,所以在请求过程中从缓存中取内容比在源服务器上取内容用的时间更少,加快了用户体验。
  • 2.降低网络传输:副本被重复使用,大大降低了用户的宽带使用,其实也是一种变相的省电,同时保证了宽带请求在有一个低水平上,更容易维护了。
缓存的种类

缓存种类很多,像是浏览器缓存,cdn缓存等都是我们比较熟悉的,当然还有代理服务器缓存,网关缓存等。

http的原理
  • 工作原理:客户机与服务器建立连接后,发送一个请求给服务器,请求格式为:统一资源标识符、协议版本号。服务器收到请求的信息(包括请求行,请求头,请求体)。服务器接收到请求后,给予相应的响应信息,格式为一个状态行(包括响应行,响应头,响应体)。
  • 基于HTTP协议的客户/服务器模式的信息交换过程,分为四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。
  • 服务器可能同时接受多个请求,这时就会产生多个session,每个session分别处理各自的请求。
http的状态有哪些(常⽤的)?

100——客户必须继续发出请求
101——客户要求服务器根据请求转换HTTP协议版本
200——交易成功
304——客户端已经执⾏了GET,但⽂件未变化
404——没有发现⽂件、查询或URl
500——服务器产⽣内部错误
505——服务器不⽀持或拒绝⽀请求头中指定的HTTP版本

你有哪些前端性能优化的⽅法?
  • (1) 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图⽚⼤⼩控制合适;⽹⻚Gzip,CDN托 管,data缓存 ,图⽚服务器。
  • (2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端⽤变量保存AJAX请求结果,每 次操作本地变量,不⽤请求,减少请求次数
  • (3) ⽤innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
  • (4) 当需要设置的样式很多时设置className⽽不是直接操作style。
  • (5) 少⽤全局变量、缓存DOM节点查找的结果。减少IO读取操作。
  • (6) 避免使⽤CSS Expression(css表达式)⼜称Dynamic properties(动态属性)。
  • (7) 图⽚预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
  • (8) 避免在⻚⾯的主体布局中使⽤table,table要等其中的内容完全下载之后才会显示出来,显示
    ⽐div+css布局慢
阻止事件冒泡
ev.stopPropagation();
常⻅的模块化⽅式有哪些?优点是什么?

1、服务端 (commonJS) 例如 Node.js
2、客户端(AMD CMD) 例如 require sea.js
3、ES6:module(export import);

优点:

1、解决⽂件之间的依赖关系
2、避免命名冲突 、解决全局变量级全局函数泛⽤的现象
3、解决代码的复杂性
4、按需加载

手写一个深克隆

原理:

深克隆就是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题, 首先判断执行对象的类型是数组、对象或是普通类型,通过for in遍历数组或者对象, 判断value的类型 ,如果是对象类型那么就递归再次执行深克隆,直到全是普通类型。

//深克隆
function deepClone(obj){
    var cloneObj
    if(obj && typeof obj !== 'object'){
        cloneObj=obj
        console.log('普通')
    }
    else if(obj && typeof obj==='object'){
        //判断传递的对象是数组还是对象,来确定新建一个对象还是数组
        cloneObj=Array.isArray(obj)?{}:[]
        for(var key in obj){
            //hasOwnProperty(property)
            //判断对象是否有某个特定的属性。必须用字符串指定该属性
            if(obj.hasOwnProperty(key)){
                //如果obj的子元素是对象,则进行递归操作
                if(obj[key] && typeof obj[key]==='object'){
                    cloneObj[key]=deepClone(obj[key])
                }else{
                    
                    //如果obj子元素不是对象,则直接赋值
                    cloneObj[key]=obj[key]
                }
            }
        }
    }
    return cloneObj
}
var testarray=[
    1,
    33,
    6,
    2,
    [
        1,
        5,
        6
    ],
    [1,
    5
],
{
    a:3,
    b:35
}
]
var result = deepClone(testarray)
result[5][1]=899
console.log(result) */
call、apply、bind的区别,手写实现它们

共同点就是修改 this 指向,不同点就是:

  • call()和apply()是立刻执行的,而bind()是返回了一个函数
  • call则可以传递多个参数,第一个参数和apply一样,是用来替换的对象,后边是参数列表。
  • apply最多只能有两个参数——新this对象和一个数组argArray
call实现原理
  • 首先context为可选参数,如果不传的话默认上下文为window
  • 接下来给context创建一个fn属性,并将值设置为需要调用的函数
  • 因为call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
  • 然后调用函数将对象上的函数删除
Function.prototype.myCall= function(context){
	if(typeof this !== 'function'){
		throw new TypeError('Error')
	}
	context = context \\ window
	context.fn = this
	const args =[...arguments].slice(1)
	const resul = context.fn(...args)
	delete context.fn
	return result
}
apply实现原理
Function.prototype.myApply= function(context){
	if(type this !== 'function'){
		throw new TypeError('Error')
	}
	context = context || window
	context.fn=this
	let result
	//处理参数和call有区别
	if(arguments[1]){
		result = context.fn(...arguments[1])
	}else{
		result= context.fn()
	}
	delete context.fn()
	return result
}
bind实现原理
  • bind需要返回一个函数,需要判断一些边界问题
  • 首先context为可选参数,如果不传值的话默认上下文为window
  • 接下来给context创建一个fn属性,并将值设置为需要调用的函数
  • bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式
  • 对于直接调用来说,这里选择了apply的方式实现,但是对于参数需要注意以下的情况:因为bind可以实现类似这样的代码f.bind(obj,1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的的实现args.concat(…arguments)
  • 通过ne’w来调用,对于new的情况来说,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this
Function.prototype.myBind = function(context){
	if(type this !=='function'){
		throw new TypeError('Error')
	}
	const _this = this
	const args =[...arguments].slice(1)
	//返回一个函数
	return function F(){
		//因为返回一个函数,我们可以new F(),
	}
}
async await的好处

一个函数如果加上async,那么该函数就会返回一个promise

async function test(){
	return "1"
}
console.log(test())    //->promise{<resolve>:"1"}

async就是将函数返回值使用Promise.resolve( )包裹一下,和then中处理返回值一样,并且await只能配套async使用

async function test(){
	let value = await sleep( )
}

优点:

async和await可以说是异步终极解决方案了,相比直接使用promise来说,优势在于处理then的调用链,能够更清晰准确的写出代码,毕竟写一大堆then也很恶心,并且也能优雅的解决回调地狱问题。

缺点:

因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。

await 在 forEach 中不生效解决方案
一、场景
function test() {
  let arr = [3, 2, 1];
  arr.forEach(async (item) => {
    const res = await fetch(item);
    console.log(res);
  });
  console.log("end");
}

function fetch(x) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x);
    }, 500 * x);
  });
}

test();

//期望的打印顺序是
3;
2;
1;
end;
//结果打印顺序居然是
end;
1;
2;
3;

原因:那就是 forEach 只支持同步代码。forEach 并不会去处理异步的情况

二、解决办法
  • 第一种是使用 Promise.all 的方式
async function test() {
  let arr = [3, 2, 1];
  await Promise.all(
    arr.map(async (item) => {
      const res = await fetch(item);
      console.log(res);
    })
  );
  console.log("end");
}

这样可以生效的原因是 async 函数肯定会返回一个 Promise 对象,调用 map 以后返回值就是一个存放了 Promise 的数组了,这样我们把数组传入 Promise.all 中就可以解决问题了。但是这种方式其实并不能达成我们要的效果,如果你希望内部的 fetch 是顺序完成的,可以选择第二种方式

  • 2 另一种方法是使用 for…of
async function test() {
  let arr = [3, 2, 1];
  for (const item of arr) {
    const res = await fetch(item);
    console.log(res);
  }
  console.log("end");
}

因为 for…of 内部处理的机制和 forEach 不同,forEach 是直接调用回调函数,for…of 是通过迭代器的方式去遍历

async function test() {
  let arr = [3, 2, 1];
  const iterator = arr[Symbol.iterator]();
  let res = iterator.next();
  while (!res.done) {
    const value = res.value;
    const res1 = await fetch(value);
    console.log(res1);
    res = iterator.next();
  }
  console.log("end");
}
//以上代码等价于 for...of,可以看成 for...of 是以上代码的语法糖
常见web安全和防护措施

1.sql注入原理
就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
2.XSS原理及防范
Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意 html标签或者JavaScript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。首先,避免直接在cookie 中泄露用户隐私,例如email、密码等等。其次,通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。如果网站不需要再浏览器端对cookie 进行操作,可以在Set-Cookie 末尾加上HttpOnly 来防止javascript 代码直接获取cookie 。尽量采用POST 而非GET 提交表单。
3.CSRF
XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤:
登录受信任网站A,并在本地生成Cookie。
在不登出A的情况下,访问危险网站B。
服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。

git篇

常见的命令
  • 查看分支 git branch -a
  • 创建分支 git branch name
  • 切换分支 git checkout name
  • 创建并切换 git checkout -b name
  • 合并某分支到当前分支 git merge name
    -> 删除分支 git branch -d name
分支管理策略
主分支 master

代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布。 Git主分支的名字,默认叫做Master。它是自动建立的,版本库初始化以后,默认就是在主分支在进行开发

开发分支 develop

主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做Develop。这个分支可以用来生成代码的最新代码版本。如果想正式对外发布,就在Master分支上,对Develop分支进行"合并"(merge)

功能分支 feature

功能分支,它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。 功能分支的名字,可以采用feature-*的形式命名。

预发布分支 release

预发布分支,它是指发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从Develop分支上面 分出来的,预发布结束以后,必须合并进Develop和Master分支。它的命名,可以采用release-*的形式。

bug 分支 fixbug

bug分支。软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。它的命名,可以采用fixbug-*的形式。

其它分支 other

还有就是其它分支了,大家可以根据需要创建即可……

git怎么删除远程和本地分支
  • 删除本地分支:git branch -D 分支名
  • 删除远程分支:git branch origin -D 分支名
场景:你是第—天来公司上班的,项目代码托管在GitLab,目地址:git@lab.com:org/project.git,现在有—处代码需要你修改。请完成此项任务中,与 git/gitlab相关的操作步骤。

在这里插入图片描述

如果线上出现bug,git怎么操作?
方法1

在当前主分支修改bug,暂存当前的改动的代码,目的是让工作空间和远程代码—致:
Git stash
修改完bug后提交修改:
git add .
git commit —m “fix bug 1”
git push
从暂存区把之前的修改恢复,这样就和之前改动—样了
git stash pop
这时可能会出现冲突,因为你之前修改的文件,可能和bug是同—个文件,如果有冲突会提示:
Auto—merging xxx.Java
CONFLICT (content): Merge conflict in xxx.java
前往xxx.java解决冲突
注意stash pop意思是从暂存区恢复到工作空间,同时删除此条暂存记录。

方法二:

拉—个新分支,老司机都推荐这样做,充分利用了git特性,先暂存—下工作空间改动:
git stash
新建—个分支,并且换到这个新分支
git branch fix_bug //新建分支
git checkout fix_bug //切换分支
这时候就可以安心的在这个fix_bug分支改bug了,改完之后:
git add .
git commit —m “fix a bug”
切换到master主分支
git checkout master
从fix_bug合并到master分支
git merge fix_bug
提交代码
git push
然后从暂存区恢复代码
git stash pop
此时如有冲突,需要解决冲突

一个分支节点,你和你同事一起开发,你同事已经提交了,你要提交怎么做
  1. 首先保存本地修改git commit //将暂存区里的改动给提交到本地的版本库或者直接push到git/gerrit(只是上传,不合并分支)
  2. git pull --rebase更新远程代码到本地
  3. 此时可能产生冲突,需要手动修改代码解决冲突(此时是在某个解决冲突的节点上,并不是在分支上
  4. 解决完冲突后,get rebase --continue从解决冲突的节点回到分支,此时解决冲突的那些修改已经保存到分支的git记录中
  5. git add添加新的修改到暂存区
  6. git commit --amend追加提交到刚刚一开始没有合并的提交中
  7. git push origin ......//将当前分支推送到origin主机的对应分支
  8. git merge
补充问题:为什么要先commit,然后pull,然后再push

这个先 commit 再 pull 再 push 的情况就是为了应对多人合并开发的情况:
1、commit 是为了告诉 git 我这次提交改了哪些东西,不然你只是改了但是 git 不知道你改了,也就无从判断比较;
2、pull是为了本地 commit 和远程commit 的对比记录,git 是按照文件的行数操作进行对比的,如果同时操作了某文件的同一行那么就会产生冲突,git 也会把这个冲突给标记出来,这个时候就需要先把和你冲突的那个人拉过来问问保留谁的代码,然后在 git add && git commit && git pull 这三连,再次 pull 一次是为了防止再你们协商的时候另一个人给又提交了一版东西,如果真发生了那流程重复一遍,通常没有冲突的时候就直接给你合并了,不会把你的代码给覆盖掉3、出现代码覆盖或者丢失的情况:比如A B两人的代码pull 时候的版本都是1,A在本地提交了2,3并且推送到远程了,B 进行修改的时候没有commit 操作,他先自己写了东西,然后 git pull 这个时候 B 本地版本已经到3了,B 在本地版本3的时候改了 A 写过的代码,再进行了git commit && git push 那么在远程版本中就是4,而且 A 的代码被覆盖了,所以说所有人都要先 commit 再 pull,不然真的会覆盖代码的

git分支git pull --rebase 跟git pull --merge(git pull)什么区别

git pull分别做了两个操作分别是获取合并。添加了rebase就是以rebase的方式进行合并分支,默认使用merge方法。

merge和rebase的区别:

情景:在feature分支进行开发,master分支也有新的提交
merge
在这里插入图片描述
在这里插入图片描述
指令:

git checkout feature  //切换到feature分支
git merge master  //将master合并到当前分支

原理:

git会自动根据两个分支的共同祖先即2这个commit和俩个分支的最新提交即47进行一个三方合并,并将合并中修改的内容生成一个新的commit,即8。简单来说就是合并两个分支并生成一个新的提交。

如果合并的时候遇到冲突,仅需要修改后重新commit

优点:记录了真实的commit情况,包括每个分支的详情

缺点:当commit比较频繁时,分支会很杂乱

rebase
在这里插入图片描述
指令:

git checkout feature  //将分支切换到feature
git rebase master  //将开发中的master分支合并到当前稳定feature分支

原理:

这些命令会把你的feature分支里的每个提交(commit)5、6、7取消掉,并且把它们临时作为补丁保存到".git/rebase"目录中,然后把"feature"分支更新 为最新的"master"分支,最后把保存的这些补丁应用到"feature"分支上。feature分支更新之后,它会指向这些新创建的提交(commit),之前的commit会被删除,运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除.

优点:这样的好处是干净,分支上上不会有无意义的解决分支的commit;

缺点:如果合并的分支中存在多个commit,需要重复处理多次冲突。

简述一下webpack,以及loader与plugin的区别
  • loader 用于加载某些资源文件。因为 webpaack 只能理解 JavaScript 和 JSON 文件,对于其他资源例如 css 、图片、jsx 、视频等等是没有办法加载的,就需要对应的 loader 将资源转化加载进来。字面意思三个可以看出,loader 适用于加载的,它作用域一个个文件上。
  • plugin 用于拓展 webpack 的功能。针对是loader结束后,webpack打包的整个过程。它并不直接操作文件,而是基于事件机制工作。目的在于解决loader无法实现的其他事,从打包优化和压缩。
webpack 优化有哪些?
- 减少 Webpack 打包时间

1.优化 Loader
Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低.
2. HappyPack (Node是单线程,webpack打包也是单线程)
HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了
3.代码压缩
(举例 js)
webpack3 :webpack-parallel-uglify-plugin 来并行运行 UglifyJS
webpack4 :mode 设置为 production 就可以默认开启以上功能
4.DllPlugin
DllPlugin 可以将特定的类库提前打包然后引入。
减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。
5.resolve.alias:
resolve.alias通过别名来映射路径,能让 Webpack 更快找到路径
6.module.noParse:
当确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助
7.externals
想引用一个库,但是不想让webpack打包,且不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals。
8.resolve.extensions
import 时最好写扩展名
如果你导入的文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面。

减少 Webpack 打包后的文件体积
  1. 懒加载、按需加载
    使用按需加载,将每个路由页面单独打包为一个文件;
    也可以对大型类库使用按需加载
  2. Scope Hoisting
    Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
  3. Tree Shaking
    移除 JavaScript 上下文中的未引用代码(dead-code)
    Webpack 4 :mode 设置为 production 会自动启动这个优化功能。
从启动webpack构建到输出结果经历了一系列过程,它们是什么
  1. 解析webpack配置参数,合并从shell传入和webpack.config.js文件里配置的参数,生产最后的配置结果。
  2. 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
  3. 从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
  4. 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
  5. 递归完后得到每个文件的最终结果,根据entry配置生成代码块chunk。
  6. 输出所有chunk到文件系统。
项目上线流程是怎样的?

流程建议

—模拟线上的开发环境

本地反向代理线上真实环境开发即可。(apache, nginx, nodejs均可实现)

—模拟线上的测试环境

模拟线上的测试环境,其实是需要—台有真实数据的测试机,建议没条件搭daily的,就直接 用线上数据测好了,只不过程序部分走你们的测试环境而已,有条件搭daily最好。

—可连调的测试环境

可连调的测试环境,分为2种。—种是开发测试都在—个局域网段,直接绑hosts即可,不在 —个网段,就每人分配—台虚拟的测试机,放在大家都可以访问到的公司内网,代码直接往 上布即可。

—自动化的上线系统

自动化的上线系统,可以采用Jenkins。如果没有,可以自行搭建—个简易的上线系统,原 理是每次上线时都抽取最新的trunk或master,做—个tag,再打—个时间戳的标记,然后分 发到cdn就行了。界面里就2个功能,打tag,回滚到某tag,部署。

—适合前后端的开发流程

开发流程依据公司所用到的工具,构建,框架。原则就是分散独立开发,互相不干扰,连调 时有hosts可绑即可。

简单的可操作流程

  • —代码通过git管理,新需求创建新分支,分支开发,主干发布 —上线走简易上线系统,参见上—节
  • —通过gulp+webpack连到发布系统,—键集成,本地只关心原码开发
  • —本地环境通过webpack反向代理的server
  • —搭建基于linux的本地测试机,自动完成build+push功能
git与svn的区别
  • git是分布式的,svn不是。
  • git跟svn—样有自己的集中式版本库或服务器。但git更倾向于被使用于分布式模式,克隆版本库后即使没有网络也能够commit文件,查看历史版本记录,创建项目分支等,等网络再次连接上Push到服务器端。
  • git把内容按元数据方式存储,而svn是按文件。
  • 所有的资源控制系统都是把文件的元信息隐藏在—个类似.svn,.cvs等的文件夹里。
  • git目录是处于你的机器上的—个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。
  • git没有—个全局的版本号,svn有。
  • git的内容完整性优于svn。因为git的内容存储使用的是SHA-1哈希算法。
  • git可以有无限个版本库,svn只能有—个指定中央版本库。
  • 当svn中央版本库有问题时,所有工作成员都—起瘫痪直到版本库维修完毕或者新的版本库设立完成。
  • 每—个git都是—个版本库,区别是它们是否拥有活跃目录(Git Working Tree)。如果主要版本库(例如:置於GitHub的版本库)有问题,工作成员仍然可以在自己的本地版本库(local repository)提交,等待主要版本库恢复即可。工作成员也可以提交到其他的版本库!

Node.js篇

Koa与Express比较
语法区别:

experss 异步使用 回调
koa1 异步使用 generator + yeild
koa2 异步使用 await/async

中间件区别:
  • koa采用洋葱模型,进行顺序执行,出去反向执行,支持context传递数据
  • express本身无洋葱模型,需要引入插件,不支持context
  • express的中间件中执行异步函数,执行顺序不会按照洋葱模型,异步的执行结果有可能被放到最后,response之前。
  • 这是由于,其中间件执行机制,递归回调中没有等待中间件中的异步函数执行完毕,就是没有await中间件异步函数
集成度区别
  • express 内置了很多中间件,集成度高,使用省心,
  • koa 轻量简洁,容易定制
Node的事件循环(Event loop)

Node的Event Loop和浏览器中的是完全不相同的东西,Node的Event Loop分为6个阶段,他们会按照顺序反复运行。每当进入每一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。

6个阶段
  • timer阶段

timer阶段会执行setTimeout和setInterval回调,并且是由poll阶段控制的。同样。在Node中定时器指定的时间也不是准确的时间,只能是尽快执行。

  • I/O阶段

I/O阶段会处理一些上一轮循环中的少数未执行的I/O回调

  • idle,prepare阶段

idle,prepare阶段内部实现

  • poll阶段

poll是一个至关重要的阶段,在poll阶段中,系统会做两件事情

1.回到timer阶段执行回调
2.执行I/O回调
并且在进入该阶段时如果没有设定了timer的话,会发生以下两件事

  • 如果poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
  • 如果poll队列为空时,会发生两件事

1.如果有setImmediate回调需要执行,poll阶段会停止并且进入到check阶段执行回调
2. 如果没有setImmediate回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去

当然设定了timer的话且poll队列为空,则会判断是否有timer超时,如果有的话回到timer阶段执行回调

  • check阶段

check阶段执行setImmediate

  • close callbacks阶段

close callbacks阶段执行close事件

node的导出模块有那2种方式,说说他们的区别?
1.module.exports
  • 我们可以直接通过为module.exports赋值的方式来导出模块
  • module.exports可以导出单个成员
  • exports的功能module.exports都可以实现
2.exports
  • exports变量是node提供的一个对module.exports的引用。
  • exports用添加属性的方式来导出,且只能导出一个对象。
  • 如果直接将exports变量指向一个值,不会影响module.exports,但是这样等于切断了exports与module.exports的联系。

Vue篇

生命周期钩子函数

beforeCreate 钩子函数调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。

然后会执行 created 钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。

接下来会先执行 beforeMount 钩子函数,开始创建 VDOM,最后执行 mounted 钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。

接下来是数据更新时会调用的钩子函数 beforeUpdateupdated,这两个钩子函数没什么好说的,就是分别在数据更新前和更新后会调用。

另外还有 keep-alive 独有的生命周期,分别为 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

最后就是销毁组件的钩子函数 beforeDestroydestroyed。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。

介绍一下Vue中父子组件生命周期的执行顺序
  • 加载渲染过程

父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  • 子组件更新过程

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  • 父组件更新过程

父 beforeUpdate -> 父 updated

  • 销毁过程

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

说一下vue中路由跳转和传值的方式
跳转方式
  • router-link
  • this.$router.push()
  • this.$router.replace():跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是它会替换掉当前的 history 记录
  • this.$router.go(n):根据 history 记录进行前进后退
传值方式
  • 动态路由
  • query
  • params
vue-router 守卫和路由懒加载
方法一 resolve

这一种方法较常见。它主要是使用了resolve的异步机制,用require代替了import,实现按需加载

/* vue异步组件技术 */
{
  path: '/home',
  name: 'home',
  component: resolve => require(['@/components/home'],resolve)
},{
  path: '/index',
  name: 'Index',
  component: resolve => require(['@/components/index'],resolve)
},{
  path: '/about',
  name: 'about',
  component: resolve => require(['@/components/about'],resolve)
} 
方法二 官网方法路由懒加载(使用import)

vue-router在官网提供了一种方法,可以理解也是为通过Promise的resolve机制。因为Promise函数返回的Promise为resolve组件本身,而我们又可以使用import来导入组件。

// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
/* const Home = () => import('@/components/home')
const Index = () => import('@/components/index')
const About = () => import('@/components/about') */
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块
const Home =  () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
const About = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about')
{
  path: '/about',
  component: About
}, {
  path: '/index',
  component: Index
}, {
  path: '/home',
  component: Home
}
方法三 webpack提供的require.ensure()

vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。
这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

{
  path: '/home',
  name: 'home',
  component: resolve => require.ensure([], () => r(require('@/components/home')), 'demo')
}, {
  path: '/index',
  name: 'Index',
  component: resolve => require.ensure([], () => r(require('@/components/index')), 'demo')
}, {
  path: '/about',
  name: 'about',
  component: resolve => require.ensure([], () => r(require('@/components/about')), 'demo-01')
}
computed和watcher的区别
  • watcher 顾名思义就是监听数据变化,它监听的数据来自 props、data、computed 的数据。他有两个参数分别是 newValue 和 oldValue。watcher 默认是浅监听,如果想要深度监听的话可以加一个deep:true;
  • computed 用于处理复杂的逻辑运算,比较适合对多个变量或者对象进行处理后返回一个结果值
  • computed 支持缓存,只有依赖数据发生改变,才会重新进行计算;watcher 不支持缓存,数据变,直接会触发相应的操作
  • computed 不支持异步,当computed内有异步操作时无效,无法监听数据的变化。watch 支持异步。
watch、computed和methods之间的对比
  • computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用,有返回值;
  • methods方法表示一个具体的操作,主要书写业务逻辑;
  • watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computed和methods的结合体
Vuex 状态管理的工作原理

在这里插入图片描述

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走action,但action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

Vue 和 React 之间的区别是什么?
  • Vue 的表单可以使用 v-model 支持双向绑定,相比于 React 来说开发上更加方便,当然了 v-model 其实就是个语法糖,本质上和 React 写表单的方式没什么区别。
  • 改变数据方式不同,Vue 修改状态相比来说要简单许多,React 需要使用 setState 来改变状态,并且使用这个 API 也有一些坑点。并且 Vue 的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是React 还是需要用户手动去优化这方面的问题。
  • React 16 以后,有些钩子函数会执行多次,这是因为引入 Fiber 的原因。
    React 需要使用 JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过 JS 来控制页面,更加的灵活。Vue 使用了模板语法,相比于 JSX 来说没有那么灵活,但是完全可以脱离工具链,通过直接编写 render 函数就能在浏览器中运行。
  • 在生态上来说,两者其实没多大的差距,当然 React 的用户是远远高于 Vue 的
  • 在上手成本上来说,Vue 一开始的定位就是尽可能的降低前端开发的门槛,然而 React 更多的是去改变用户去接受它的概念和思想,相较于 Vue 来说上手成本略高。
Vuex 和 Redux 区别,它们的共同思想。
  1. 表面区别就是vuex是通过将store注入到组件实例中,通过dispatch和commit来维护state的状态,并可以通过mapstate和this.$store来读取state数据。而redux则是需要通过connect将state和dispatch链接来映射state并操作state。redux没有commit,直接通过dispatch派发一个action来维护state的数据。并且只能通过reducer一个函数来操作state。
  2. rudex使用的是不可变数据;vuex是可变的数据。
  3. rudex每次都是返回一个新的state;而vuex是直接改变state。
共同思想:

单一的数据源 变化可以预测 他们都是将数据从视图中抽离的一种方案;vuex借鉴了redux,将store作为全局的数据中心,进行模式管理

vue、react 怎么检测数据变化的

Vue

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

React

react状态变化只能通过setState,调用setState就会更新状态重新渲染dom

VUE中template模板是怎么通过Compile编译的

浏览器中是不能直接解析template模板的,Vue会通过编译器将模板经过几个阶段最终编译为render函数,然后执行render函数生成虚拟DOM,最终映射为真实 DOM。在编译过程中主要分为三个阶段:
在第一个阶段中,主要通过各种正则表达式去匹配模板中的内容,然后将内容提取出来做各种逻辑操作,之后生成一个最基本的 AST 对象。然后会根据这个AST 对象中的属性,进一步扩展 AST。在这一阶段中,还会进行一些基本的判断逻辑。比如说对比前后开闭标签是否一致,判断根组件是否只存在一个等。
接下来就是优化 AST 的阶段。比如对节点进行了静态内容提取,也就是将不会变动的节点提取出来,实现复用虚拟DOM,跳过对比算法的功能。
最后一个阶段就是通过遍历整个 AST对象生成 render 函数了.

vue中的key
  • key 就是给每个节点一个唯一的标识,key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。
  • 如果不加 key ,在更新的时候 vue 会去比较所有的项,然后把全部的元素进行替换,造成性能的降低
如何解决vue tabbar刷新后高亮问题

vue tabbar在页面刷新后,active会清零,出现下面的高亮和页面不匹配的情况,解决方法第一种是 tabbar中加入route属性,开启路由模式,但是有一个弊端就是如果涉及到页面内有二级路由,那么将 会全部高亮失效,因此有二级路由的时候不推荐使用route。第二种是在每次进入的页面中设置meta, 然后给每一个router设置name属性与active相对应,根绝页面路由的meta属性,修改active值使其与 name属性匹配。

Vue父子组件的通信方式有哪些

Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信

1、prop

这个在我们日常开发当中用到的非常多。简单来说,我们可以通过 Prop 向子组件传递数据。用一个形象的比喻来说,父子组件之间的数据传递相当于自上而下的下水管子,只能从上往下流,不能逆流。这也正是 Vue 的设计理念之单向数据流。而 Prop 正是管道与管道之间的一个衔接口,这样水(数据)才能往下流。

2、$emit

触发当前实例上的事件。附加参数都会传给监听器回调。

3、.sync 修饰符

在 vue@1.x 的时候曾作为双向绑定功能存在,即子组件可以修改父组件中的值。因为它违反了单向数据流的设计理念,所以在 vue@2.0 的时候被干掉了。但是在 vue@2.3.0+ 以上版本又重新引入了这个 .sync 修饰符。但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。说白了就是让我们手动进行更新父组件中的值了,从而使数据改动来源更加的明显。下面引入自官方的一段话:
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。

Vuex

官方推荐的,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

$parent

父实例,如果当前实例有的话。通过访问父实例也能进行数据之间的交互,但极小情况下会直接修改父组件中的数据。

$root

当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过访问根组件也能进行数据之间的交互,但极小情况下会直接修改父组件中的数据。

vue 跟element ui 一起使用有什么冲突的地方
1.在Vue中使用element ui 中的element-icons引用出错;

原因:file-loader默认处理文件格式不包含‘.woff和.woff’;

解决方法:修改vue配置文件webpack.config.js
在这里插入图片描述

2.vue版本和element-ui中引用的vue版本冲突

原因:vue版本和element-ui中引用的vue版本冲突。

解决办法:安装相同版本的vue.另外: vue-template-compiler 这个插件也安装和vue相同版本的

补充:vue-template-compiler 作用: 该模块可用于将 Vue 2.0 模板预编译为渲染函数(template => ast => render),以避免运行时编译开销和 CSP 限制。大都数场景下,与 vue-loader一起使用,只有在编写具有非常特定需求的构建工具时,才需要单独使用它

3.vue+elementui项目打包后样式变化

原因:elementUI样式优先级高,覆盖本地样式

解决方法:
(1)简单粗暴使用!important加强style优先级
(2)将样式impoimport至App的上方
在这里插入图片描述
(3)增加css选择器 , 增强优先级

Vue单页面应用和多页面应用的区别
1.刷新方式
  • 单页面:组件进行切换,页面进行局部刷新或更改
  • 多页面:整页刷新
2.路由模式
  • 单页面: 可以使用hash,也可以使用history
  • 多页面:普通的链接跳转
3.搜索引擎优化
  • 单页面:不利用seo检索,需要利用服务器端渲染优化
  • 多页面比较利于seo
4.资源文件
  • 单页面:组件公用的资源只需要加载一次
  • 多页面:每个页面都需要加载公共的资源
5.数据传递
  • 单页面:可以比较容易的实现数据传递,比如路由传参,vuex传参等
  • 多页面: 依赖url传参,或者使用cookie,本地存储等
6.在用户体验方面
  • 单页面:页面片段切换时间较快,可以实现转场动画,用户体验较好
  • 多页面:在切换页面时加载缓慢特别是在网速较慢的情况下,而且无法实现转场动画的效果
7.开发和维护成本
  • 单页面:开发成本高,但后期维护相对容易
  • 多页面与单页面相反。

React篇

redux 的工作流程(流程图)

在这里插入图片描述

简明来说,首先创建一个store分别管理相应的reducer,在组件内部派发action之后,看是否需要中间件进行处理,如果是ajax等异步操作,可以放入thunk或者saga等中间件,进行处理之后然后传到reducer之中,如果组件里面需要数据,可以通过connect高阶组件或者getState()获取reducer里面的数据。

redux 中间件原理(三层函数)

中间件提供第三方插件的模式,自定义拦截action—> reducer的过程。变为action—〉 middlewares —> reducer。这种机制可以让我们改变数据流,实现如异步action, action过滤,日志输出,异常报告等功能。

常见的中间件:
  • redux—logger:提供日志输出
  • redux—thunk:处理异步操作
  • redux—promise:处理异步操作,actionCreator的返回值是promise
redux-thunk、redux-saga 使用
  • redux是相当于一个仓库,配合react使用时能够成为一种状态管理工具,但是redux本身的特性规定其在派发信息时只能派发同步并且是扁平化的内容,这样在redux中若想实现异步操作就会有很大的局限性,中间件等同于一个仓库管理员的身份,每次想去仓库中取货,必须经过管理员去取钥匙,redux-thunk,redux-saga是两个常用的中间件,通过中间件能够增强redux本身的功能,redux-thunk中能够处理函数,其内部机制就是识别派发的参数类型,若是函数则直接调用,若是对象直接进行派发的操作,redux-saga则在内部封装了一些方法能够拦截派发的请求,到内部执行异步操作后,再去处理数据。
  • redux-thunk直接函数,较为直接,但是由于派发内容可以是扁平的对象,也可以是函数,所以不易于维护。
  • redux-saga仍旧派发扁平对象,符合redux派发规则,但是实现的逻辑较为复杂,其实就是在内部封装了处理异步的行为。
React JSX 原理

用 JavaScript 对象来表现一个 DOM 元素的结构JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接
在 JavaScript 代码里面编写类似 HTML 标签结构的语法,
这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。
React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等,
jsx实际上就是一种javascript对象,他经过react语法的构造,还有编译转化,最后得到dom元素,可以插到页面中:
所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:
JSX —使用react构造组件,bable进行编译—> JavaScript对象 — ReactDOM.render()—>DOM元素 —>插入页面

React 组件间数据共享
  • 1.父组件向子组件传值:父组件通过属性进行传递,子组件通过props获取
  • 2.子组件向父组件传值:触发父组件传给子组件的函数传值
  • 3.兄弟组件传值:可以用上面两种方法一点一点方法传给相同父组件在传下去

使用context传递数据
利用redux传递数据

react中Context 的使用

Context是通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
React.createContext(defaultValue):创建一个上下文的容器(组件), defaultValue 可以设置共享的默认数据,这个容器会提供 Provider 和 Consumer
Provider:在需要传递数据的地方用其包裹,属性 value 中提供什么,它就能向后代树传递什么。如果没有 Provider ,后代树只能获取到defaultValue
Consumer:在需要获取值的地方用 Consumer 包裹,就能通过this.context 取得值。
函数组件使用hook方法 useContext 获取context

  • 优点:减少代码层层传递的复杂度
  • 缺点:因为依赖祖先组件的传值,子组件的复用性降低了
组件的生命周期

—、 初始化阶段:

  • Constructor初始化状态
  • componentWillMount:组件即将被装载、渲染到页面上
  • render:组件在这里生成虚拟的DOM节点
  • componentDidMount:组件真正在被装载之后

二、 运行中状态:

  • componentWillReceiveProps:组件将要接收到属性的时候调用
  • shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)
  • componentWillUpdate:组件即将更新不能修改属性和状态
  • render:组件重新描绘
  • componentDidUpdate:组件已经更新

三、 销毁阶段:

componentWillUnmount:组件即将销毁

React新老版生命周期函数
老版生命周期

在这里插入图片描述

新版生命周期

在这里插入图片描述
Render函数靠mouting时触发,如果在里面调用状态会造成死循环
componentWillMount

相当于vue中的beforemount,在dom渲染之前触发的

componentDidMount

在dom渲染完之后,是一个创建时期调用的钩子

componentWillReceiveProps(props.state)

检测父组件给的信息,一有变化就会run

componentWillUnmount

在组件被卸载时调用

shouldComponentUpdata钩子:

此钩子新老版生命周期函数中都存在,主要用于在渲染之前,判断是否执行渲染,相当于一个开关,当执行结果为false时不会触发后面的render函数。那么我们就可以利用此钩子去做一些优化的操作,例如我们都知道父组件渲染时会带着子组件一起渲染,但有的时候子组件的数据并没有发生变化但还是会带着被渲染一次,就造成了不必要的性能浪费,这个时候在shouldComponentUpdata中加一层判断的话就可以解决这个问题。(当然,在创建组件时如果将继承的Component改成PureComponent,可帮忙做上面的优化)

getDerivedStateFromProps

这是一个新增的钩子,此钩子的处罚比较频繁,当组件被传入值时,不管相不相等都会执行,数据更新时也会执行(不管自身还是父组件传来的)

this.forceupdate()

强制渲染

getSnapshotBeforeUpdate

在DOM渲染之前获得一个快照

React 表单开发(受控组件,非受控组件)
  1. 受控组件

    相当于input中的value值通过state值获取,onChange事件改变state中的value值。实现了双向绑定,任意一方的数据发生变化,另一方也会随之改变 。

  2. 非受控组件

    不需要设置对应的state属性,可通过ref来直接操作真是的dom。

immutable (什么是immutable, 为什么要使用,重要的API)
什么是Immutable Data

1.Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象
2.Immutable 实现的原理是 Persistent Data Structure (持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变

TIP:当然,通过这一点,我们可以去做时间旅行,也就是以前调用之前存在过的旧数据。

3.同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗, Immutable 使用了 Structural Sharing···· (结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

TPI:数据结构
在这里插入图片描述
例如我们现在数据C2发生改变,那么他只会影响到B2与A,其他不会受到影响的数据直接复制过来就好了无需重新进行操作,性能有所提升。

immutable.js的优缺点

优点:

-降低mutable带来的复杂度
-节省内存
-历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单地将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据就被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
-拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面对对象,这样开发组件和调试更方便。

缺点:

-需要重新学习api
资源包大小增加(源码5000行左右)
容易与原生对象混淆:由于api与原生不同,混用的话容易出错。

React 中 keys 的作用是什么?

Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。

为什么不建议在componentWillMount做Ajax等操作?

一个组件的 componentWillMount 比 componentDidMount 也早调用不了几微秒,性能没啥提高;而且,等到异步渲染开启的时候,componentWillMount 就可能被中途打断,中断之后渲染又要重做一遍(例如:在 componentWillMount 中做 AJAX 调用,代码里看到只有调用一次,但是实际上可能调用 N 多次,这明显不合适)。相反,若把 AJAX 放在 componentDidMount,因为 componentDidMount 在第二阶段,所以绝对不会多次重复调用,这才是 AJAX 合适的位置。

React的diff算法,规则是什么?

React Diff 算法的差异查找实质是对两个JavaScript对象(虚拟DOM和真实DOM)的差异查找,所以React更新阶段才会有Diff算法的运用。
规则:

  • Web UI中 DOM 节点跨层级的移动操作特别少,可以忽略不计
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 对于同一层级的一组子节点,它们可以通过唯一的 id 进行区分
为什么有时候连续多次setState只有一次生效?

因为React会批处理机制中存储的多个 setState进行合并,React会对多次连续的 setState进行合并,如果你想立即使用上次 setState后的结果进行下一次 setState,可以让 setState 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数。

React状态管理工具都有哪些?redux actionCreator都有什么?

一种是将Redux进一步封装的如dva、refrect、refast等,二是采用observable的方案如MobX、dob

actionCreator就是定义个函数然后传入一个数据并返回一个带有action对象,然后供组件使用

介绍React hooks

在这里插入图片描述

hooks是react 16.8引入的特性,他允许你在不写 class 的情况下操作 state 和 react 的其他特性。
hook s只是多了一种写组件的方法,使编写一个组件更简单更方便,同时可以自定义hook把公共的逻辑提取出来,让逻辑在多个组件之间共享。
在编写函数组件并意识到需要向其添加一些state 时,就可以使用hook。

hook的钩子:
  • useState() 保存组件状态
  • useEffect() 处理副作用
  • useContext() 获取context的值,减少组件层级
  • useReducer() 跟 redux 中的数据流的概念非常接近
  • useCallback 记忆函数
  • useRef 表示可以定义一个变量, 在函数体内实现唯一的引用
  • useMemo() 主要用于渲染过程优化,两个参数依次是计算函数(通常是组件函数)和依赖状态列表,当依赖的状态发生改变时,才会触发计算函数的执行。如果没有指定依赖,则每一次渲染过程都会执行该计算函数。

React Hooks 提供为状态管理提供了新的可能性,尽管我们可能需要额外去维护一些内部的状态,但是可以避免通过 renderProps / HOC 等复杂的方式来处理状态管理的问题。Hooks 带来的好处如下:

  • 更细粒度的代码复用,并且不会产生过多的副作用
  • 函数式编程风格,代码更简洁,同时降低了使用和理解门槛
  • 减少组件嵌套层数
  • 组件数据流向更清晰

微信小程序篇

简述微信支付的开发原理

微信支付存在两种模式的实现,其中第二种模式较为简洁且易于实现
基本逻辑:
首先,用户需要在开发者的系统中下单,完成下单行为后向开发者的服务器发送请求,这个请求能够携带商品信息等数据,在服务器接收到请求时,需要向微信服务器发送请求,以完成一个预支付的行为,在发送请求前需要按照微信服务器的规则生成一些必要的参数,微信服务器在接收到请求时,会对签名进行验证,随后会返回一个支付url,开发者服务器需要将连接处理成为二维码供用户扫码支付
之后,用户在扫码后微信服务器会验证连接真实性及有效性,在完成支付行为后,微信服务器会以一种异步的形式,向开发者服务器发送请求,以通知开发者服务器支付状态,方便开发者执行后续操作。

小程序与普通网页开发的区别
  • 网页开发渲染线程和脚本线程是互斥的,而在小程序中,两者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。
  • 网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具
说一下小程序的优化(不完全,仅供参考,可自行补充)

其实小程序的view 部分是运行在 webview上的,所以前端领域的优化大部分都能运用在小程序上
(1) 首屏加载方面,可以使用骨架屏 和基础内容的展示 避免白屏;或者是利用 storage 对异步数据进行缓存,二次进入小程序就可以先使用缓存渲染,再去进行后台更新
(2) 小程序的项目会根据功能进行分包,主包可以放默认启动页 和 用到的公共资源,其余的分包在用户打开页面时或者 加载完主包后再进行加载。
(3) setData 会 触发视图页面的更新,会阻塞用户操作,要合理使用setData,避免通信开销过大
(4) 其他的 类似 对pageScroll、input 事件 的监听 或者 执行操作 ,要用到 节流或 防抖进行性能优化。

微信支付怎么做?说说流程

在这里插入图片描述

1.申请微信公众号及支付功能申请:根据公众号申请流程申请即可。

2.获取商户支付配置信息及支付测试配置:支付授权目录最多可以配置三个域名,测试授权目录只可以—个,这里需要 注意的是域名大小写必须要网站URL—致,否则会无法通过授权,提示支付请求的 URL不合法。另外,测试支付的微信号必须加到测试白名单,否则无法进行支付测 试。

3.H5页面发起支付请求,请求生成支付订单,获取用户授权(获取用户的openid)

4.调用统—下单API,生成预付单

5.生成JSAPI页面调用的支付参数并签名,注意时间戳timeStamp是32位字符串

6.返回支付参数prepay—id,paySign参数的html文本给前端。

7.微信浏览器自动调起支付JSAPI接口支付,提示用户输入密码。

8.确认支付,输入密码,提交支付。

9.步通知商户支付结果,商户收到通知返回确认信息。

10.返回支付结果,并发微信消息提示。

11.展示支付信息给用户,跳转到支付结果页面。

小程序中的路由跳转switchTab navigateTo redirectTo的区别
switchTab

跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面

navigateTo

保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层,类似于history.push。

redirectTo

关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。作用相当于history.repalce 在我的项目中有一个漫画的页面,里面涉及了大量的上一页下一页操作,如果用不用redirectto的话,每次都会记录,后退的时候不能一下子回退到其他页面,用户体验不太好。

小程序的tabbar实现原理

小程序的tabbar是一个系统自带的组件,将pages的列表存储到tabbar的data之中, 根据点击的位置确认switchtab的页面,然后在页面show的时候通过gettabbar获取到tabbar,然后通过 修改内部的选中的index,使其出现高亮的效果。不过在项目中tabbar包含的页面都是一次性加载,第 二次进入页面的时候不会触发onload钩子,某些操作需要写到onshow钩子中

如何通过小程序 globalData 实现全局变量监听?
  • 小程序是有一个全局变量 globalData ,我们在小程序内的任何地方拿到 globalData ,这就给了我们模拟状态管理的基础条件。但 globalData 有个致命缺点就是他不是响应式的,我们不知道它什么时候被修改,如果我们做一些异步操作修改了 globalData 我们也不能确定被修改的时间点,不能确定何时去取新值。
  • 在 app.js 内定义一个 watch() 函数,三个变量分别是
    dataName:被监听变量名
    callback:回调函数
    that:回调函数执行环境,(如果回调函数内需要用到 this 则需要传过来)
watch: function (dataName, callback, that) {
	var obj = this.globalData;
	Object.defineProperty(obj, dataName, {
	  configurable: true,
	  enumerable: true,
	  set: function (value) {
          //这里必须用临时变量保存 否则会无限循环导致内存溢出
		this['temp'+dataName] = value;
		callback.call(that, value);
	  },
	  get: function () {
		return this['temp'+dataName];
	  }
	})
}

在需要获得数据的页面或自定义组件内

getApp().watch(
  "currCity",
  (res) => {
    this.setData({ currCity: res });
  },
  this
);

常见面试笔试部分

有这样一个URL:http://www.baidu.com/?a=1&b=2&c=&d=xxx&e,请写一段JS程序提取URL中的各个GET参数,将其按key-value形式返回到一个json解构中,如{a:‘1’,b:‘2’,c:’’,d:‘xxx’,e:undefined}
使用正则:
 var objJson={};
        var str ="http://www.baidu.com/?a=1&b=2&c=&d=xxx&e"
        if(/\?/.test(str)){
            //indexOf:取某个字符的下标
            var strString = str.substring(str.indexOf("?") + 1);//a=1&b=2&c=&d=xxx&e
            //split:用&分割字符串
             var strArray = strString.split("&");//["a=1", "b=2", "c=", "d=xxx", "e"]
             for(var i=0,len=strArray.length; i<len ;i++){
                 var strItem = strArray[i];
                 var item = strItem.split("=")//["a", "1"]
                 objJson[item[0]]= item[1];
             }
             console.log(objJson);//{a: "1", b: "2", c: "", d: "xxx", e: undefined}
        }
不使用正则
 var result={}
        var str ="http://www.baidu.com/?a=1&b=2&c=&d=xxx&e";
        var url=str.split("?")[1].split("&");//["a=1", "b=2", "c=", "d=xxx", "e"]
        for(var i =0,len =url.length;i<len;i++){
            result[url[i].split("=")[0]]=url[i].split("=")[1]
        }
        console.log(result);
let str = 'https://www.baidu.com/s?tn=02003390_hao_pg&ie=&wd';
        let str1 = str.split('?')[1];
        console.log(str1);// tn=02003390_hao_pg&ie=&wd
        let arr = str1.split('&');
        console.log(arr);// ["tn=02003390_hao_pg", "ie=", "wd"]
        let obj = arr.reduce((obj, item) => {
            let arr = item.split('=');
            obj[arr[0]] = arr[1];
            return obj;
        }, {})
        console.log(obj);// {tn: "02003390_hao_pg", ie: "", wd: undefined}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值