每日小记

128 阅读16分钟
  • 多个canvas绘制点和多个canvas绘制图片的区别?
  1. 绘制点的时候canvas上的点的坐标需要重新计算

  2. 绘制图片的时候绘制的坐标点不需要重新计算,

  3. canvas的图层问题--后绘制的显示在前绘制的上面

const ctx = canvas[num].getContext("2d");

  let image: any = new Image();//创建image对象

  image.src = list[num]

  image.crossOrigin = 'Anonymous'

  if (ctx) {

    image.onload = function () {

      ctx.drawImage(image, 2400 * 0, 0, 2400, 1080)

        

      drawAbnormalReact(canvas, abnormalList, num, maxList, (flag, num) => {

      })

      drawAbnormalText(canvas, abnormalList, num, maxList, (flag, num) => {

        console.log(num)

      })

    }

  }

  • 前端分页:

arr.slice(开始索引,结束索引)

开始索引:(currentPage-1)*pageSize

结束索引:currentPage*pageSize

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。即不可改变的是这个地址,const声明的常量是可以改变的,为其添加新的属性,但是不可以为其重新赋值

const a=[];

a.push('b')  可以执行的

a=['david'] 为a重新赋值是不可以执行的

移入移出是触发事件,请使用mouseenter,mouseleave事件组合

  • promise.all多个异步加载--等待所有加载全部完成再继续执行其他操作

列一:

var p1 = Promise.resolve(3);var p2 = 1337;var p3 = new Promise((resolve, reject) => {

  setTimeout(resolve, 100, 'foo');});

Promise.all([p1, p2, p3]).then(values => {

  console.log(values); // [3, 1337, "foo"]

});

列二

const promiseList: any[] = []

        jsonUrls.map((item: string, i: number) => {

          let promise = new Promise(async (resolve, reject) => {

            const res = await axios.get(${requestUrl}/publicFile/${item});

            resolve(res.data)

          });

          promiseList.push(promise)

        })

        Promise.all(

promiseList

). then((val) => {

          resultList.push(val)

          const result = resultList.flat(2)

          sourceDta = new Map(result)

          sourceDtaCopy = new Map(result)

          if (result?.length) {

            this.dotCanvasWith = Math.max(...(Array.from(sourceDta.keys()))) //dotcanvas 最大宽度(即源数据的最大值)

            this.dotMin = Math.min(...(Array.from(sourceDta.keys()))) //即源数据的最小值

            this.drawDotMax = this.dotCanvasWith - this.actualWith  //最大可移动的范围  像素

            if (sourceDta.size > 0) {

              const arr = Array.from(sourceDta.keys())

              this.setState({

                arrNum: arr,

              }, () => {

                this.initCanvas()

                this.initDraw()

              })

            }

          }

        });

  • js拖拽功能节流阀防抖动:
  • 鼠标松开,元素仍然可以拖拽,不触发onmouseup:

解决办法就是直接干掉H5的拖拽事件:

window.onload = function(){

    var drag = document.getElementById('drag');

    drag.onmousedown = function(event){}

    document.onmousemove = function(event){}/drag.onmousemove = function(event){}

    鼠标抬起停止移动

    document.onmouseup = function(event){

        console.log( "抬起停止移动" ) 

        this.onmouseup = null;

        this.onmousemove = null; //修复低版本ie bug 

        if(typeof drag.releaseCapture!='undefined'){ drag.releaseCapture(); }

        在鼠标松开的时候,元素仍然可以拖动即不触发onmouseup事件

        document.ondragstart = function(ev){

ev.preventDefault();

}

document.ondragend = function(ev){

ev.preventDefault();

}

    }

}

  • 使用js做动画,则使用requestAnimationFrame,效果比定时器好

start = () => {

    console.log('start')

    const { circularLeft } = this.state

    this.setState({

      circularLeft: circularLeft + 1

    })

    if (this.state.circularLeft < 500) {

      this.setState({

        ids: window.requestAnimationFrame(this.start) 启用动画

      })

      console.log('circularLeft=', this.state.circularLeft)

    } else {

      window.cancelAnimationFrame(this.state.ids)

    }

  }

  end = () => {

    window.cancelAnimationFrame(this.state.ids) 结束动画

  }

    开始

    结束

    <div className={styles.circular} style={{ left: this.state.circularLeft }}>

  • vuex解决命名冲突的方法:

需要在store的index.js中引入各个模块,为了解决不同模块命名冲突的问题,将不同模块的namespaced:true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名

1,

const state = {

  userId:'',//用户id

  userName:'',//用户名称

  token:'',//token

  permission:''//权限

}

// getters

const  getters = {

  // 获取用户信息

  getUserInfo(){

    return state;

  }

}

// actions

const actions = {}

// mutations

const mutations = {

  setUserInfo(state,payload) {

    console.log("payload:"+payload);

    console.info(payload);

    state.userId = payload.userId;

    state.userName = payload.userName;

    state.token = payload.token;

    state.permission = payload.permission;

  }

}

export default {

  namespaced: true,

  state,

  getters,

  actions,

  mutations

}

2、使用模块中的mutations、getters、actions时候,要加上模块名,例如模块userInfo中的commint的mutations时

格式:模块名/模块中的mutations

this.$store.commit("userInfo/setUserInfo",userInfo)

this.$store.dispatch('userInfo/Login', loginParams)

3、获取属性时同样加上模块名 state+模块名+state中的属性

this.$store.state.userInfo.userName

vuex 只有commit才可以触发mutation(同步)

异步dispatch--action--commit-mutation,在mutation可用改变state的数据,在action中可用通过commit触发mutati

列如页面

this.$store.dispatch('user/getLogin')

在user模块下面

const action={

    getLogin({commit}){

        return new Promise((resolve,reject)=>{异步

            login().then(res=>{

                resolve(res)

                commit('SET_TOKEN', data.token)

            })

        })

    }

}

const mutation={同步

    SET_TOKEN:(state,token)=>{

    state.token=token

    }

}

export default {

  namespaced: true,

  state,

  mutations,

  actions

}

刷新页面vuex state中的数据会消失

在vuex中若不使用 模块化namespaced,在操作同步或者异步的时候则不需要添加模块的名称:

例如:user.js

const user={

    state:{

        roles: [],

        permissions: []

    },

    mutaitons:{

         SET_ROLES: (state, roles) => {

          state.roles = roles

        },

        SET_PERMISSIONS: (state, permissions) => {

          state.permissions = permissions

        }

    },

    actions:{

       GetInfo({ commit, state }){

            return new Promise((resolve,reject)=>{

               getInfo().then(res=>{

                    commit('SET_ROLES',res)

                }) 

            })

        } 

    }

}

export default user

在login.vue文件中  :

login().then(res=>{

    this.$store.dispatch('GetInfo')  此处不需要加上模块名称

})

getter.js  相当于computed的功能,getter 里面的属性 是依赖于state中的数据

在模块app中:

const state={

    device:{

    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,

    withoutAnimation: false

   }

}

const getters={

    device:state=>state.app.device

}

export default getters

或者

const getters={

    device(state){

        return state.app.device

    }

}

export default getters

在navbar.vue使用getters

import { mapGetters } from 'vuex'

computed:{

    ...mapGetters(['device'])

}

{{device.opened}}

...mapActions

在methods中使用

methods:{

    ...mapActions(['fn'])  等价于 this.$store.dispatch('fn')

}

如果需要传值则需要手动调用==>this.$store.dispatch('fn',this.name)

mapMutations

...mapMutations(['fn'])等价于 this.$store.commit('fn')

通过splice改变数组,然后通过commit方法是可以改变页面数据-->

const operaIndex=videos.findIndex(m=>m.id===item.id)

videos.splice(operaIndex,1,{})

that.$store.commit('system/INIT_VIDEOS',videos)

that.$store.commit('system/EQUIPMENT_INFO',{})

页面上的数据:this.videos=this.$store.state.system.videos

但是使用这样的方法是不可以改变页面上的数据,需要再通过其他事件再次改变页面数据--->

this.$nextTick(()=>{

  this.$forceUpdate()

  let list=Array(9).fill({})

  this.$store.commit('system/INIT_VIDEOS',list)

  this.$emit('clearList')

})

通过clearList事件主动触发改变页面state中的数据

clearList(){

    this.videos=this.$store.state.system.videos

}

index.vue:

methods: {

    init () {

      this.$store.dispatch('system/getTask').then(res => {

        this.list = res

      })

    }

  },

  mounted () {

    this.init()

}

system.js

import Vue from 'vue'

const state = {

  user: {},

  task: []

}

const mutations = {

  GET_TASK: (state, list) => {

    state.task = list

  }

}

const actions = {

  getTask ({ commit }) {

    return new Promise((resolve, reject) => {

      Vue.axios

        .get('http://192.168.1.145/index')

        .then(res => {

          resolve(res.data)

          commit('GET_TASK', res.data)

        })

        .catch(error => {

          reject(error)

        })

    })

  }

}

export default {

  namespaced: true,

  state,

  mutations,

  actions

}

  • vue请求后台数据的方法:fetch(原生)/axios(第三方库)

1.axios

axios是一个第三方库,需要引入文件,可以直接在bootcdn中直接下载文件引入

axios返回的是一个promise对象,所以可以通过.then().catch()的方法调用

写法是在methods中写一个函数,在函数里写axios方法,写法与ajax相类似

axios的请求方式遵循restful规范,各种请求方式都可以,这里主要讲get和post,只要学会post,其他的都和get一样

get:

axios({

     url: 'http://localhost/get.php',//请求的路径

     method: 'get',//请求方式

     params: {//params是参数

       a: 1,

       b: 2

     }

     })

     .then( res => {//res为请求到的内容

        console.log( res )

      })

      .catch( error => {

            if( error ){

                throw error

            }

        })

    }

})

post:需要另外设置请求头

//统一设置请求头信息,全部设置

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

let params = new URLSearchParams()//实例化这个URLSearchParams()这是axios自带的

params.append('a',1)//使用.append()方法添加参数

params.append('b',2)

axios({

    url: 'http://localhost/post.php',

     method: 'post',

     data: params,

     headers: {

      'Content-Type': "application/x-www-form-urlencoded"//单独设置请求头

      }

     })

    .then(res => {

     console.log( res )

   })

   .catch( error => {

   if( error ){

     throw error

  }

})

2,fetch

fetch返回的也是一个promise对象,所以也需要用.then().catch()方法调用

fetch是原生的数据请求方法

fetch也有post和get两种方法

post方法也需要设置请求头

fetch的参数post和get的书写方式不同,详情看代码

并且fetch方法需要格式化数据,格式有三种:res.text()//纯文本;res.json(); res.blob()//一般不用

fetch不写method的话默认就是get

fetch方法调用时需要使用两个.then(),第一个.then设置数据格式,第二个输出数据

\

get:

new Vue({

    el: '#app',

    methods: {

        axiospostHandler(){

            fetch('http://localhost/get.php?a=1&b=2')//这里传数据写在?后面

                .then(res=> res.text()) // 数据格式化 res.json() res.blob()

                .then(data => {

                     console.log( data )

                })

                .catch(error => {

                    if( error ){

                      throw error

                    }

                })

            })

        }

  })

post:

post(){

    fetch('http://localhost/post.php',{

        method: 'post',

        headers: new Headers({

             'Content-Type': 'application/x-www-form-urlencoded' // 指定提交方式为表单提交

        }),

        body: new URLSearchParams([["a", 1],["b", 2]]).toString()//post方法数据写在body中,需要实例化URLSearchParams()方法,传进去一个二维数组,然后转成字符串相当于a=1,b=2

        })

        .then( res => res.text())//设置数据格式

        .then( data => console.log( data ))

        .catch( error => {

            if( error ){

              throw error

            }

    })

}

offsetHeight      //返回元素的高度(包括元素高度、内边距和边框,不包括外边距)

clientHeight       //返回元素的高度(包括元素高度、内边距,不包括边框和外边距)

padding-top:在不知道dom的宽度和高度的时候默认是继承dom的宽度的100%

vue中数据的加载完成和页面dom加载完成是不同步的,在数据已经加载完成的时候,页面的dom还未加载完成,所以如果在数据加载完成的时候获取dom的高度需要是使用nextTick,nextTick函数表示dom加载完成执行的函数

that.$nextTick(()=>{//dom加载完成

     console.log(that.$refs.scheduleLeft.offsetHeight)

 })

小程序跳转到tabar的页面使用wx.switchTab,跳转到tabar对应的列表,需要在onShow请求接口(否则页面不刷新)

Math.max(num1,num2...numn) 返回指定数字中的最高值

数组的reduce方法,返回的值是最后一次调用回调函数返回的值

arr.reduce(function(prev,cur,index,arr){

...

}, init);

prev--表示上一次调用回调时的返回值,或者初始值 init;

cur--表示当前正在处理的数组元素

index--正在处理的数组元素对应的索引,若提供 init 值,则索引为0,否则索引为1

arr--表示原数组

init--表示初始值

如果设置了init则第一次执行回调函数的prev值就是init,则此时的index为0

arr.reduce((prve,cur)=>{

    prev.indexof(cur)==-1&&prev.push(cur)

    return prev

},[])

  • indexOf() 的用法

1,方法可返回数组中某个指定的元素第一次出现的位置,如果没有该元素则返回-1,开始位置的索引为 0 arr.indexOf('apple')

2,indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置,且对大小写敏感,如果找不到返回-1,stringObject.indexOf(searchvalue,fromindex),。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1,如果没有formindex,则检索的位置是从0开始,如果找到一个 searchvalue,则返回 searchvalue 的第一次出现的位置

如果一个变量等于一个数组执行push方法,那么这个变量的结果就是数组的长度

let a=[]

let b=a.push(23)

b=1//变量b等于数组a的长度

  • git :

最开始创建新的分支:如果fatal: Not a valid object name: 'master'.

1、git add .添加所有项目文件到本地仓库缓存

2、 git commit -m .   把本地所有文件提交到暂存区,然后再创建分支

3、git brach test

add是添加到 暂存区  commit是添加到本地库, push是推送到远程仓库

缓存:localStorage,cookie,indexedDB(浏览器数据库)

1,Cookie 的大小不超过4KB,且每次请求都会发送回服务器

2,LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引

3,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引,IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)

antd备注:

1,:gutter=[水平间距,垂直间距] 像素

  

  

  

  

\

  • less:

1,变量:

@width: 10px; @height: @width + 10px; 

#header { width: @width; height: @height; }

2,混合:是一种将一组属性从一个规则集包含(或混入)到另一个规则集的方法

.bordered { border-top: dotted 1px black; border-bottom: solid 2px black; }

#menu a { color: #111; .bordered(); } .post a { color: red; .bordered(); }

3,嵌套:

.clearfix { display: block; zoom: 1; &:after { content: " "; display: block; font-size: 0; height: 0; clear: both; visibility: hidden; } }

& 表示当前选择器的父级

4,混合和嵌套一起使用

#headr{

  #budle.button()    等价于  #budle>.button  

}

#header(){

    .tab{}

}

5,映射--将混合(mixins)和规则集(rulesets)作为一组值的映射(map)使用

#color(){  混合

    primary: blue;

    secondary: green;

}

.button{

    color:#color[primary];  blue

  border:1px #color[secondary] solid;

}

\

6,作用域:先从 本地查找变量或者混合,如果没有则从父级作用域继承

@var:red;

.page{

    @var:white;

    #header:{

        color:@var  white

    }

}

7,导入,可以直接导入有个less文件,此文件中的所有变量就可以全部使用了,如果导入的文件是 .less 扩展名,则可以将扩展名省略掉:

例如导入main.less文件

@impor "main"

导入一个css

@import "main.css"

  • less的@import指令

1:@import指令可以放在代码中的任何位置,导入文件时的处理方式取决于文件的扩展名:

    01-如果扩展名是 .css,文件内容将被原样输出

    02-如果是任何其他扩展名,将作为LESS文件被导入

    03-如果没有扩展名,将给他添加一个 .less 的扩展名,并作为LESS文件被导入

2, 例如在style.less 中,通过 @import指令将多个less文件合并成一个文件

    @import "product.less"

    @import "news.less"

    @import "info.less"

3,一个@import指令可以使用一个或多个配置项,当使用多个配置项时,各配置项之间用逗号隔开,@import (optional, reference) "foo.less";

    01-reference-使用文件,但不会输出其内容(即,文件作为样式库使用)

    02-inline-对文件的内容不作任何处理,直接输出

    03-less-无论文件的扩展名是什么,都将作为LESS文件被输出

    04-css-无论文件的扩展名是什么,都将作为css文件被输出

    05-once-文件只被导入一次

    06-multiple-文件可以被多次导入

    07-optional-当文件不存在时,继续编译(即,该文件是可选的)

  • scss:

使用@import可以在任何地方导入样式文件

列如现在有一个局部 Sass 文件 _myfile.scss

.xkd{ font-size: 14px; }

然后把这个文件导入另一个scss文件 style.scss

.div{ 

    color: #ccc; 

    .div-top{ 

    border: 1px solid #000;

    @import "./myfile";

  }

}

编译成css-->

.div { color: #ccc; } 

.div .div-top { border: 1px solid #000; } 

.div .div-top .xkd { font-size: 14px; }

  • calc:

div:{

    width: calc(100% / 3)   将父级宽度的100%除以三

}

  • 注意事项:

01-首先,从左到右进行计算。

02-首先将计算除法或乘法,并且还将首先计算括号内的数学表达式。

03-calc()当前在Opera中不受支持

04-需要前缀-moz-和-webkit-才能覆盖早期的Firefox和Chrome版本。

05-我们可以对操作使用不同的单位,例如calc(50%– 10px)

06-+和-符号必须用空格分隔,例如calc(100%- 5px) 但是, *和/符号不需要空格

  • calc和less一起使用注意事项:

会计算变量和数学公式的值

@var:50vh/2

width:calc(50%-(@var-20px))   calc(50%-(25vh-20px))

// 使用 css 时可以用 >>> 进行样式穿透

.test-wrapper >>> .ant-select {

    font-size: 16px;

}

  • 使用 scss, less 时,可以用 /deep/ 进行样式穿透

.test-wrapper /deep/ .ant-select {

    font-size: 16px;

}

  • canvas

同一画布上绘制不同模块时,记得使用 beiginPath()和closePath()框选起来,作用在不同的上下文上

1,绘制矩形

canvas 提供了三种方法绘制矩形:

1、fillRect(x, y, width, height):绘制一个填充的矩形。

2、strokeRect(x, y, width, height):绘制一个矩形的边框。

3、clearRect(x, y, widh, height):清除指定的矩形区域,然后这块区域会变的完全透明。

说明:这 3 个方法具有相同的参数。

x, y:指的是矩形的左上角的坐标。(相对于canvas的坐标原点)

width, height:指的是绘制的矩形的宽和高。

function draw(){ 

    var canvas = document.getElementById('tutorial');

    if(!canvas.getContext) return;

    var ctx = canvas.getContext("2d"); 

    ctx.fillRect(10, 10, 100, 50); // 绘制矩形,填充的默认颜色为黑色 

    ctx.strokeRect(10, 70, 100, 50); // 绘制矩形边框 

draw();

ctx.clearRect(15, 15, 50, 25); 清除一个坐标是 15 15 长是50 高是25的矩形

同样使用rect(x, y, width, height)结合stroke()和fill()绘制矩形,

rect(x, y, width, height)和stroke()结合使用等价于--strokeRect(x, y, width, height)

rect(x, y, width, height)和fill()结合使用等价于--fillRect(x, y, width, height)

2,绘制路径

beginPath()

新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径

moveTo(x, y)

把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。

lineTo(x,y)

lineTo() 方法添加一个新点,然后创建从该点到画布中最后指定点的线条(该方法并不会创建线条,需要配合stroke()使用)。

closePath()

闭合路径之后,图形绘制命令又重新指向到上下文中

stroke()

通过线条来绘制图形轮廓

fill()

通过填充路径的内容区域生成实心的图形

绘制不同的线

function draw(){

    var canvas = document.getElementById('myCanvas');

    var ctx = canvas.getContext("2d");

    ctx.beginPath()

    ctx.moveTo(0,0)

    ctx.lineTo(50,50)

    ctx.strokeStyle='green'

    ctx.stroke()

    ctx.closePath()

    ctx.beginPath()

    ctx.moveTo(50,50)

    ctx.lineTo(100,0)

    ctx.strokeStyle='blue'

    ctx.stroke()

    ctx.closePath()

    ctx.beginPath()

    ctx.moveTo(100,0)

    ctx.lineTo(150,50)

    ctx.strokeStyle='red'

    ctx.stroke()

    ctx.beginPath()

    ctx.moveTo(150,50)

    ctx.lineTo(200,0)

    ctx.strokeStyle='yellow'

    ctx.stroke()

}

draw()

function draw(){ 

    var canvas = document.getElementById('tutorial'); 

    if (!canvas.getContext) return; 

    var ctx = canvas.getContext("2d"); 

    ctx.beginPath(); //新建一条path 

    ctx.moveTo(50, 50); //把画笔移动到指定的坐标 起始点 

    ctx.lineTo(200, 50); //绘制一条从当前位置到指定坐标(200, 50)的直线. //闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合,则什么都不做 

    ctx.closePath(); 

    ctx.stroke(); //绘制路径。 

draw();

绘制三角形

function draw(){

    var canvas = document.getElementById('tutorial');

    if (!canvas.getContext) return;

    var ctx=canvas.getContext("2d");

    ctx.beginPath();

    ctx.moveTo(50,50)

    ctx.lineTo(200,50)

    ctx.lineTo(200,200)

    ctx.closePath()

    ctx.stroke()描边,stroke不会自动closePath()

}

function draw(){

    var canvas = document.getElementById('tutorial');

    if (!canvas.getContext) return;

    var ctx=canvas.getContext("2d");

    ctx.beginPath();

    ctx.moveTo(50,50)

    ctx.lineTo(200,50)

    ctx.lineTo(200,200)

    ctx.fill()填充,fill会自动closePath()

}

draw()

tips:1,先绘制(绘制路径或者图形)然后再描边或者填充

      2,如果path没有闭合,则fill()会自动闭合路径

      3,stroke()描边,不会自动结束路径

绘制圆弧:

1,arc(x, y, r, startAngle, endAngle, anticlockwise): 

以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。

anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)。

注意:

01,这里的度数都是弧度。Math.PI = 3.14 = 180°  90°--》Math.PI/2

02,0 弧度是指的 x 轴正方向。

03,不需要moveTo()设置起始点,因为绘制圆弧的时候设置圆心的位置就是设置起始点

function draw(){

    var canvas = document.getElementById('tutorial');

    if (!canvas.getContext) return;

    var ctx=canvas.getContext("2d");

    ctx.beginPath()

    ctx.arc(50,50,40,0,Math.PI/2,false)  以50,50为圆心,40为半径,绘画出0-90°的圆弧,

    ctx.fill()

}

draw()

2,arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。

function draw(){

    var canvas = document.getElementById('tutorial');

    if (!canvas.getContext) return;

    var ctx=canvas.getContext("2d");

    ctx.beginPath()

    ctx.arcTo(50,50,50,90,40)  开始点 50,50,结束点 50,90,半径是40的圆弧

    ctx.fill()

}

作用在不同的上下文中,需要beiginPath()和closePath()框选起来使用,当使用beiginPath()和closePath()的时候画连续的线,每次调用closePath需要配合moveTo使用,重新选择线的起始点,如果不使用closePath,则不需要重新选择起始点

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext("2d");

ctx.globalAlpha = 0.5;

ctx.beginPath()

ctx.moveTo(0,0)

ctx.lineTo(100,100)

ctx.lineWidth=10

ctx.lineCap='round'

ctx.strokeStyle='red'

ctx.stroke()

ctx.closePath()

ctx.beginPath()

ctx.moveTo(100,100)

ctx.lineTo(200,0)

ctx.lineWidth=10

ctx.strokeStyle='blue'

ctx.stroke()

ctx.closePath()

ctx.beginPath()

ctx.moveTo(200,0)

ctx.lineTo(300,100)

ctx.lineWidth=10

ctx.strokeStyle='green'

ctx.lineCap='round'

ctx.stroke()

ctx.closePath()

tips:绘制的时候需要改变上下文需要结束 ctx.closePath(),同一画布上绘制不同模块时,记得使用 beiginPath()和closePath()框选起来,在里面使用stroke.style可以画不同颜色的东西,canvas没有层级,实现层级,只能通过绘制时候的顺序和方式来处理层级

绘制线和圈

  let sss = [{x: 375, y: 118},{x: 594, y: 118}];

  for(let j = 0 ;j<sss.length;j++){

    context.drawImage(img, 0, 0,800,600)  先出现图片

    var circle = new Circle(sss[j].x, sss[j].y);

    circles.push(circle);

    circles[0].color = "red";

    for (var i = 0; i < circles.length; i++) {

      var circle = circles[i];

      // 绘制圆圈

      context.globalAlpha = 0.85;

      context.beginPath();

      context.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);

      context.fillStyle = circle.color;

      context.strokeStyle = "black";

      context.fill();

      context.stroke();

    }

  

    var point = new Point(sss[j].x, sss[j].y);

    points.push(point);

    context.beginPath();

    context.lineWidth = 4;

    //从起始点开始绘制

    context.moveTo(points[0].x, points[0].y);

    for (var i = 0; i < points.length; i++) {

      context.lineTo(points[i].x, points[i].y);

    }

    context.fillStyle = "rgb(2,100,30)";

    context.fill();

    context.strokeStyle = "#9d4dca";

    context.stroke();

  }

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext("2d");

ctx.beginPath()

ctx.strokeRect(50,50,100,100)

ctx.fillRect(100,100,200,200)

ctx.arc(50,50,20,0,Math.PI*2,true)

ctx.fillStyle='red'

ctx.fill()

ctx.closePath()

ctx.beginPath()

ctx.arc(100,100,20,0,Math.PI*2,true)

ctx.fillStyle='blue'

ctx.fill()

  • drawImage()在canvas插入图片

1.必须等图片加载完成后才能使用drawImage方法

2.Image默认不支持访问跨域资源,必须手动指定crossOrigin属性,才可以跨域访问图片

addImage(src) {

      return new Promise((resolve, reject) => {

        const img = new Image();

        img.crossOrigin = "anonymous";

        img.onload = () => {

          resolve(img);

        };

        img.onerror = () => {

          reject();

        };

        img.src = src;

        if (img.complete) {

          resolve(img);

        }

      });

    }

    this.addImage(src).then((img) => {

        this.ctx.drawImage(img, 0, 0, img.width, img.height);

        // 在这之后才能继续绘制其他图片

        this.addImage(xxx);

    });

clearRect() 方法清空给定矩形内的指定像素。

列如:清除画布

ctx.clearRect(0,0,canvas.width,canvas.height)

3,添加改变样式

01-通过fillStyle和strokeStyle来改变颜色,默认填充和描边的颜色都是黑色,如果你要给每个图形上不同的颜色,你需要重新设置 fillStyle 或 strokeStyle 的值。

fillStyle = color 设置图形的填充颜色

strokeStyle = color 设置图形轮廓的颜色

02-设置透明度,会影响canvas所有板块的透明度

通过globalAlpha来设置(大于0的数据)

ctx.globalAlpha = 0.5;

通过fillStyle='rgba(128, 100, 162, 0.7)'

03-lineWidth线宽,线宽。只能是正值。默认是 1.0

ctx.lineWidth=20

04-lineCap 线条末端样式

butt:线段末端以方形结束

round:线段末端以圆形结束

square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。

ctx.lineCap='round'

05-设置线条和线条结合处的样式

round 通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度。

bevel 在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角。

miter(默认) 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext("2d");

ctx.lineWidth = 20;

ctx.globalAlpha=0.5

ctx.lineJoin = 'bevel';

ctx.beginPath();

ctx.moveTo(0, 0);

ctx.lineTo(50, 50);

ctx.lineTo(100, 0);

ctx.lineTo(150, 50);

ctx.lineTo(200, 0);

ctx.stroke();

4,设置虚线

用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式。 setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset属性设置起始偏移量。

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext("2d");

ctx.setLineDash([20,5]) 每个线段是20,每个线段间隔5

ctx.strokeStyle='green'

ctx.strokeRect(50,50,50,50)

5,绘制文本

fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。

strokeText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。

font = value 当前我们用来绘制文本的样式。这个字符串使用和 CSS font 属性相同的语法。 默认的字体是 10px sans-serif。

textAlign = value 文本对齐选项。 可选的值包括:start, end, left, right or center。 默认值是 start。

textBaseline = value 基线对齐选项,可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。。

direction = value 文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext("2d");

ctx.beginPath()

ctx.fillStyle='red'

ctx.font = "30px 微软雅黑"

ctx.fillText('好好学习',10,30,100)

ctx.closePath()

ctx.beginPath()

ctx.strokeStyle='yellow'

ctx.font = "30px 微软雅黑"

ctx.strokeText('天天向上',110,30,100)

ctx.closePath()

6,save() 和 restore() 方法是用来保存和恢复 canvas 状态的

一个绘画的状态包括:

当前应用的变形(即移动,旋转和缩放)

strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值

当前的裁切路径(clipping path)

可以调用任意多次 save方法(类似数组的 push())。

主要是当对画布有如下转换操作之前可以先使用context.save();保存画布当前状态,方便下面的图画好后需要恢复画布原来的状态时调用context.restore();

主要是以下方法使用:

translate()/rotate()/ scale()/ transform()/ setTransform()

var canvas = document.getElementById('myCanvas');

var context = canvas.getContext("2d");

context.fillStyle='#f00';

context.fillRect(0,0,30,30);

//变换坐标系之前先保存画布状态

context.save();

context.translate(30,30)//这里重新把30,30定为坐标系原点

context.fillStyle='#0F0';

context.fillRect(0,0,50,50);

//恢复画布之前状态再画

context.restore();再次把0,0设置成坐标系原点

context.fillStyle='#00f';

context.fillRect(10,10,30,30);

7,translate(x, y)用来移动 canvas 的原点到指定的位置,translate 移动的是 canvas 的坐标原点(坐标变换)

clientX/clientY/offsetX/offsetY/screenX/screenY/pageX/pageY的区别

浏览器的有效区域

clientX:当鼠标事件发生时(不管是onclick,还是omousemove,onmouseover等),鼠标相对于浏览器的有效区域x轴的位置;

clientY:当鼠标事件发生时,鼠标相对于浏览器的有效区域y轴的位置;

浏览器的有效区域--是指整个大的浏览器可视区域,不包括地址栏

screenX:当鼠标事件发生时,鼠标相对于显示器屏幕x轴的位置;

screenY:当鼠标事件发生时,鼠标相对于显示器屏幕y轴的位置;

offsetX:当鼠标事件发生时,鼠标相对于事件源x轴的位置

offsetY:当鼠标事件发生时,鼠标相对于事件源y轴的位置

事件源--事件作用在元素上,比如在div上加一个事件,那个事件源就是该div

pageX/pageY--pageX和pageY两个属性代表鼠标光标在页面中的位置,因此坐标是从页面本身而非视口的左边和顶边计算的(相对于body比较)。在没有滚动的情况下,clientX和cilentY与pageX和pageY是相等的

使用 async / await, 搭配 promise,不能使用store中的commit 同步操作

初始化数组,创建多个空对象的数组

const list=Array(9).fill({})

startsWith() 方法用于检测字符串是否以指定的子字符串开始。(区分大小写)/返回true/false

string.startsWith(searchvalue, start)

searchvalue

必需,要查找的字符串。

start

可选,查找的开始位置,默认为 0

endswith判断文本是否以某个或某几个字符结束;

string.endsWith(searchvalue, length)

searchvalue

必需,要搜索的子字符串。

length

设置字符串的长度。默认值为原始字符串长度 string.length

::v-deep  穿透,改变elementUI框架的样式

改变el-input的placeholder样式

::v-deep input::-webkit-input-placeholder {

      color:#3774F1 !important;

    }

    ::v-deep input::-moz-input-placeholder {

      color:#3774F1 !important;

    }

    ::v-deep input::-ms-input-placeholder {

      color:#3774F1 !important;

    }

获取elementUI 组件元素的高度:this.refs.loginForm.refs.loginForm.el.clientHeight

获取图片的高度

let img=document.getElementById('homeImg')

      img.addEventListener('load',()=>{

        this.imgHeight=this.$refs.homeImg.clientHeight

      })

使用SVG作为图标:

icon-class-->是指SVG文件的名称

\

vertical-align: middle;--定义内联元素的垂直居中

使用伪类定义样式在不确定元素宽度的时候,父元素使用inline-block

.pop-title{

  position: relative;

  display: inline-block;

  em{

    font-style: normal;

    color: #D5E2F3;

    font-weight: normal;

    font-size: 16px;

    position: relative;

    z-index: 33;

  }

  &::after{

    position: absolute;

    content:'';

    bottom: 2px;

    left: -2px;

    right: -2px;

    height: 6px;

    background-color: #1D82F5;

    border-radius: 3px;

    z-index: 1;

  }

}

\

使用vuex的state属性

computed: {

    theme() {

      return this.$store.state.settings.theme;

    },

}

<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"

  {{ item.meta.title }}</el-menu-item

js 数组map返回一个新的数组,必须要有return,否则返回undefined

  • 箭头函数和普通函数的区别:

箭头函数内的this对象是定义时所在对象,而不是使用时所在的对象,不可以当做构造函数,不可以使用new,不可以使用argument对象,该对象在函数体内不存在,可以使用rest参数代替,,不可以使用yield命令,因此箭头函数不能用作generator函数

同源策略:域名,协议,端口号相同

  • 解决跨域:

1,jsonp跨域

2,使用node中间件代理跨域

3,后端头部信息设置安全域名

  • 严格模式的限制:

1,变量必须先声明在使用

2,函数的参数不能有同名属性,否则报错

3,不能使用with语句

4,禁止this指向全局对象

  • vue组件作用域的独立的:

1、父组件模板在父组件作用域内编译,父组件模板的数据用父组件内data数据;

2、子组件模板在子组件作用域内编译,子组件模板的数据用子组件内data数据,如果要用父组件的必须用props传递;

3、子组件标签的数据,使用父组件内的data数据

i++和++i被称为自增。i++是先进行运算,运算结束之后i的值再加1。而++i是先先将i的值加1之后,才进行运算。但是不管是i++还是++i,i最后的值都是相同的

  • vue封装一个全局的组件:

定义好一个组件--》在main.js引入--》使用Vue.use(全局组件)

js--单线程--所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推

Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步模式就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;

异步模式则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行队列上的后一个任务,而是执行回调函数;后一个任务则是不等前一个任务的回调函数的执行而执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

可能有人告诉你,Javascript内部存在着先进先出的异步任务队列,仅仅用以存储异步任务,与同步任务分开管理。

进程执行完全部同步代码后,每当进程空闲、触发回调或定时器到达规定的时间,Javascript会从队列中顺序取出符合条件的异步任务并执行之。

  • 深度优先遍历:

该方法是以纵向的维度对dom树进行遍历,从一个dom节点开始,一直遍历其子节点,直到它的所有子节点都被遍历完毕之后在遍历它的兄弟节点

  • 广度优先遍历:

该方法是以横向的维度对dom树进行遍历,从该节点的第一个子节点开始,遍历其所有的兄弟节点,再遍历第一个节点的子节点,完成该遍历之后,暂时不深入,开始遍历其兄弟节点的子节点。

  • 基本数据类型有:

Number; String;Boolean;Null;Undefined;Symbol(ES6 新增数据类型);

bigInt

引用数据类型统称为 Object 类型,细分的话有

Object;Array;Date;Function;RegExp

基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保

存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的

对象。

顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。

所以每次使用完对象的时候都要把它设置为 null, 从而减少无用内存的消耗

  • 判断数据类型有几种方法:

typeof

缺点:typeof null 的值为 Object,无法分辨是 null 还是 Object

 instanceof

缺点:只能判断对象是否存在于目标对象的原型链上

 constructor

 Object.prototype.toString.call()

一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、

string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、

date 、 math 数据类型。

  • 防抖(防止数据抖动):* 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。防抖--固定时间内事件只能发生一次-->利用settimeout和闭包:

      let dom=document.querySelector('input');

      dom.addEventListener('input',shakeFun(requestDemo,2000))

        //封装一个防抖的函数,利用settimeout和闭包

        function shakeFun(fn,time){

            let timer=null;//如果在规定的时间内没有什么进行的操作,定时器为null

            return args=>{

                if(timer) clearTimeout(timer)  //如果存在则清除

                timer=setTimeout(fn,time) //否则规定时间内执行某个操作

            }

        }

        function requestDemo(){

            console.log('发送请求')

        }

  • 节流:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活-->一定时间内将多个变成一个

//节流:在一定时间内,只调用一次函数(防抖和节流大同小异),规定时间后是要销毁函数的

            //使用场景:1,提交表达 2,高频率事件

        function throttle(fn,time){

            let timer=null

            return args=>{

                if(timer) clearTimeout(timer)  //如果存在则清除

                timer=setTimeout(fn,time) //否则规定时间内执行某个操作

            }

        }

  • 高级函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高级函数

1,若A函数,接收的参数是一个函数,那么A函数就是高级函数

2,若A函数,调用的返回值依然是一个函数,那么A就可以为高级函数,常见的高级函数--Promise,setTimeout,以及数组常见方法map/filter等

  • 函数柯里化--通过调用函数,返回的是函数的形式,实现多次接收参数最后统一处理的函数编码形式

saveForm=(dataType)=>{//onChange={this.saveForm('password')}-->是把saveForm函数的返回值作为onChange事件的回调

        return (event)=>{//saveForm函数的返回值-->即onChange事件的回调

           this.setState({

            [dataType]:event.target.value

           })

        }

    }

  • 内存泄漏:指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。

  • js回收机制:1,标记清楚 2,引用计数

  • 标记清除:

js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

function test(){  

var a=10;//被标记,进入环境  

 var b=20;//被标记,进入环境  

 }  

test();//执行完毕之后a、b又被标记离开环境,被回收

引用计数

**
**

 function test(){  

   var a={};//a的引用次数为0  

  var b=a;//a的引用次数加1,为1  

   var c=a;//a的引用次数加1,为2  

   var b={};//a的引用次数减1,为1

}

垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存

  • 造成内存泄漏:

1,意外的全局变量引起的内存泄露 

2,闭包引起的内存泄露 

3,没有清理的DOM元素引用 

4,被遗忘的定时器或者回调

  • 6、怎样避免内存泄露

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象  原则:不用了的东西要及时归还

  • 那么闭包的作用也就很明显了。
  1. 可以在函数的外部访问到函数内部的局部变量。

  2. 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁

  • vue:监听state中的数据:利用computed

computed: {

    getUserIcons() {

        return this.$store.state.topo.userIcons;

    }

},

  • apply,call,bind

apply第二个参数是数组,自动调用

call可以分别传参,自动调用

bind和call一样传参,且需要手动调用

vue 中 el组件的节点,el--组件的节点,data--组件的data,

一旦进入页面或者组件执行的生命周期有:beforeCreate-->create-->beforeMount-->mounted

在 beforeCreate阶段没有elel和data

在create/beforeMount阶段没有$el,但是有data

在mounted有el,也有el,也有data

利用Keepalive组件执行的生命周期有activated,deactivated

nextTick--获取更新后数据的DOM

vuex是单向数据流:

vue还是单向数据流,v-model只不过是语法糖,它是:value="sth"和@change="val => sth = val"的简写形式。

Vue是怎么实现数据响应式更新的,面试官期望听到的回答是通过Object.defineProperty()的get和set方法来实现响应式更新

vuex是单向数据流,只能通过mutation和actions里面的方法去改变,且不是持久化存储,需要利用localstorage缓存或者第三方插件vuex-persist

  • 垂直居中:

1,方法一:margin:auto法

2,方法二:负margin法,利用定位加margin负值:

.container{ 

width: 500px; 

height: 400px; 

border: 2px solid #379; 

position: relative; 

.inner{ 

width: 480px; 

height: 380px; 

background-color: #746; 

position: absolute; 

top: 50%; left: 50%; 

margin-top: -190px; /height的一半

margin-left: -240px; /width的一半

}

3,display: table-cell; 法

div{ 

width: 260px; 

height: 230px; 

border: 3px solid #555; 

display: table-cell; 

vertical-align: middle; 

text-align: center; 

img{ vertical-align: middle; }

4,flex布局

  • 打包出现空白

1,修改publicpath-->./

2,路由模式改成hash模式,不要使用history模式

3,提醒后台做个重定向

单页面应用spa--缺点1,SEO优化不好 2,在某些情况下性能不是很好

  • type--别名

type User = {

    name: string

    age: number

};

type SetUser = (name: string, age: number): void;

interface:

interface User {

    name: string

    age: number

}

interface SetUser {

    (name: string, age: number): void;

}

  • 两者的联系:

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface

  • 两者的区别:

1,type可以但是interface不行的:

    type 可以声明基本类型别名,联合类型,元组等类型

    

    // 基本类型别名

    type Name = string

    // 联合类型

    interface Dog {

        wong();

    }

    interface Cat {

        miao();

    }

    type Pet = Dog | Cat 联合类型

    

    type 语句中还可以使用 typeof 获取实例的类型进行赋值

    // 当你想获取一个变量的类型时,使用 typeof

    let div = document.createElement('div');

    type B = typeof div

    interface可以但是type不行的:

    interface 能够声明合并

    interface User {

        name: string

        age: number

    }

    interface User {

        sex: string

    }

    User 接口为 {

        name: string

        age: number

        sex: string

    }

  • 属性查找规则:先在当前实例上找,如果有就返回,没有就继续顺着原型链(prototype)一层一层的往上面找,直到找到顶层  null,如果到null还没有找到就会报错,属性会报undefined,函数会报 is not function

被构造的原型 proto 指向的是构造他的函数的 prototype

通过hasOwnProtype来判断自身的私有属性

  • React与Vue

  • 相同点

1、使用 Virtual DOM,有较高的运行速度

2、提供组件化功能

3、可使用mobx与vuex进行状态管理,响应式、依赖追踪

  • React

1、子组件重复渲染问题需要手动优化

2、可以使用redux进行状态管理,函数式、不可变、模式化,时间旅行

3、可使用JSX,完全的javascript能力

优点:引入了一个叫做虚拟DOM的概念,运行速度快;提供了标准化的API,解决了跨浏览器问题、兼容性更好;代码更加模块化,重用代码更容易,可维护性高。

缺点:React是目标是UI组件,通常可以和其它框架组合使用,并不适合单独做一个完整的框架

  • Vue

1、可使用JSX,但推荐使用模版语言而不是JSX

2、学习曲线平缓

优点:渐进式构建能力是Vue.js最大的优势,Vue有一个简洁而且合理的架构,使得它易于理解和构建。Vue有一个强大的充满激情人群的社区,这为Vue.js增加了巨大的价值,使得为一个空白项目创建一个综合的解决方案变得十分容易。

缺点:在模型-视图应用程序和状态容器类型的应用程序之间的互相转换可能会令人感到困惑;它类似于Web组件的模式,而不是真正的Web组件。

  • 数组去重方法:

一,利用es6 set

Array.from(new Set(arr))

二、利用for嵌套for,然后splice去重(ES5中最常用)

function unique(arr){            

        for(var i=0; i<arr.length; i++){

            for(var j=i+1; j<arr.length; j++){

                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个

                    arr.splice(j,1);

                    j--;

                }

            }

        }

return arr;

}

三、利用indexOf去重

function unqie(arr){

        if (!Array.isArray(arr)) {

        console.log('type error!')

        return

    }

       var array = [];

    for (var i = 0; i < arr.length; i++) {

        if (array .indexOf(arr[i]) === -1) {

            array .push(arr[i])

        }

    }

    return array;

}

四,利用includes,类似于indexof

function unique(arr) {

    if (!Array.isArray(arr)) {

        console.log('type error!')

        return

    }

    var array =[];

    for(var i = 0; i < arr.length; i++) {

            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值

                    array.push(arr[i]);

              }

    }

    return array

}

五,利用filter

function unique(arr) {

  return arr.filter(function(item, index, arr) {

    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素

    return arr.indexOf(item, 0) === index;

  });

}

  • vue虚拟DOM:

vue特点:1,组件化  2,数据驱动

数据的改变-->产生虚拟DOM(计算变更)-->然后虚拟DOM-->创建操作真实的DOM-->真实DOM发生改变会驱动视图的改变即:数据变更-->产生虚拟DOM-->创建操作真实DOM-->视图的更新,vue通过path方法把虚拟DOM-->真实DOM

虚拟DOM:就是js对象,包括3个属性tag,props,children-->

   

         

  • 11
  •      

  • 22
  •    

\

对应的js对象是:-->

{

    tag:'div',标签

    props:{属性

        id:'demo',

        className:'container'

    },

    children:[

        {

          tag:'ul',标签

          props:{},

          children:[

            {

                tag:'li',

                props:{},

                children:'11'

              },

             {

                tag:'li',

                props:{},

                children:'22'

              }

            ]

        }

    ]

}

            创建虚拟dom

        //引入snabbdom

        const snabbdom=window.snabbdom

        //将vnode(虚拟节点)塞入空的容器里面

        const path=snabbdom.init([

            snabbdom_class,

            snabbdom_props,

            snabbdom_style,

            snabbdom_eventlisteners

        ])

        //获取空的容器

        const container=document.getElementById('container')

        //创建vnode虚拟节点

        const h=snabbdom.h //创建节点的方法 h

        const vnode=h('ul#list',{},[ //参数就是tag(标签),props(属性),children(字节的)

            h('li.item',{},'第一项'),

            h('li.item',{},'第二项')

        ])

        path(vnode,container) //把创建的虚拟节点放到空容器里面

        //节点更新--点击按钮更新数据

        const btn=document.getElementById('btn')

        btn.addEventListener('click',()=>{

            const newNode=h('ul#list',{},[ //参数就是tag(标签),props(属性),children(字节的)

                h('li.item',{},'第一项'),

                h('li.item',{},'第二项'),

                h('li.item',{},'第三项')

            ])

            //用新的节点更新旧的节点

            //新的节点和旧的节点比较,在同一级通过key以及标签名来比较,若相同则不做进一步比较,若不相同则会创建一个新的节点或者更新节点--》然后插入新的dom元素,删除旧的dom元素

            path(vnode,newNode)

            vnode=newNode

        })

新虚拟节点和旧的虚拟节点进行比较--》只更新或者添加变化的虚拟节点--》转化为真实dom-->视图的更新

  • diff算法:

1,只比较同一层级,不跨级比较

2,标签名不相同,直接删除,不继续深度比较

3,标签名相同,key相同就认为是相同节点,不继续深度比较

  • 虚拟dom核心概念

1,vNode--虚拟节点

2,h函数--创建虚拟几点

3,path函数--初次渲染,把虚拟节点放到容器里面,数据更新的时候,新节点和旧节点比较,用新的节点去更新旧的节点

4,key

5,标签名不相同,直接删除,不继续深度比较

  • vue的双向绑定原理:

//双向绑定通过

        //定义一个对象并添加一个属性username,通过调用set方法,把设置的值赋给uName

        let objs={}

        Object.defineProperty(objs,'username',{

            get:function(val){

            },

            set:function(val){ //val是指给username属性修改或者设置的数据

                document.getElementById('uName').innerText=val

            }

        })

        document.getElementById('username').addEventListener('keyup',function(){

            objs.username=event.target.value //监听input框的输入值,把input框的输入值付值给对象objs的username,赋值的时候会调用objs对象调用set方法(修改对象属性的值会调用内部的set方法,获取是调用get方法)

        })//修改或者设置obj的username属性值会触发 Object.defineProperty的set方法

  • vue 页面出现抖动出现{{}}

在页面显示的最大div模块上添加如下样式

1,:style只有vue渲染的时候才识别,在此之前style=“display: none;” 让页面不显示

2,vue渲染后,:style="{display: ‘block’}"有让页面显示出来。

vue/react 渲染内容抖动:

第一种方法,再被渲染的区域预先放置loading提示,在渲染结束后隐藏/删除loading,显示正常内容。

第二种方法,给数据一个初始值,可以撑起页面样式,获取后再替换。

第二种方法的变形,从css上下手,让未渲染的部分可以显示且结构与渲染好后一致,渲染完成只改变对应的数据,不让页面结构发生改变。

vue可以使用指令v-cloak

cookie:记录客户端状态的机制,--》服务器向客户端颁发的通行证,客户端向服务器发送请求的时候,需要把cookie一起发送给服务器,这样服务器就能从通行证上确认客户的信息。

服务器还可以根据需要修改Cookie的内容

1,cookie存储在浏览器里面

2,cookie存储的是字符串

3,cookie可以设置存储期限

4,cookie存储的都比较小

5,Cookie具有不可跨域名性

跨域只是针对浏览器,cookie存储在浏览器里面,所以他具有不可跨域性

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(Integer.MAX_VALUE);           // 设置生命周期为MAX_VALUE 存储期限

response.addCookie(cookie);                     // 输出到客户端

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

cookie.setDomain(".helloweenvsfei.com");           // 设置域名

cookie.setPath("/");                              // 设置路径

cookie.setMaxAge(Integer.MAX_VALUE);               // 设置有效期

response.addCookie(cookie);                       // 输出到客户端

  • 利用cookie做免登录:

1,实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。(不安全)

2,把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可

3,一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。

session:不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上,所以增加了服务器的存储压力,Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

1,session是客户端第一次向服务器发送请求的时候创建的,即客户端向第一次服务器发送请求的时候创建好一份客户档案,然后客户来访的时候只需要查询客户档案即可

2,session存储的是对象

3,如果session设置的比较复杂,且大量客户访问服务器的时候会导致内存溢出,所以session里面的信息要尽量的精简。

4,为了防止内存溢出,服务器会把长时间没有活跃的session删除

**
**

  • cookie和session的区别

1,存储不一样,cookie存储在浏览器上,session存储在服务器上

2,cookie存储的是字符串,session存储的是对象

3,cookie可以设置存储时间,session为了防止内存溢出,服务器会清楚长时间没有活跃的session

4,单个cookie的大小在4kb, Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型

5, 两者最大的区别在于生存周期,一个是IE启动到IE关闭.(浏览器页面一关 ,session就消失了),一个是预先设置的生存周期,或永久的保存于本地的文件。(cookie)

**
**

sessionStorage、localStorage和cookie的区别

共同点:都是保存在浏览器端、且同源的

区别:

1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下

2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭

4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的

5、web Storage支持事件通知机制,可以将数据更新的通知发送给监听者

6、web Storage的api接口使用更方便

session很容易失效,用户体验很差;

虽然cookie不安全,但是可以加密 ;

cookie也分为永久和暂时存在的;

浏览器 有禁止cookie功能 ,但一般用户都不会设置;

一定要设置失效时间,要不然浏览器关闭就消失了;

申明map对象:

const sourceDta:Map<any, any>=new Map()

通过key取value

keys.forEach((key: string | number)=>{

   console.log(sourceDta.get(key));

})

暂停:clearInterval(this.autoTimer)/clearTimeout(this.autoTimer)

继续播放:--》继续调定时器

ping IP--》通过cmd打开对话框--输入ping,再+空格键,再+需要输入的IP地址,然后再+空格键,点击回车键确定。

利用axios监听文件上传的进度,使用onUploadProgress,使用该方法时当计算得出的percent--等于100的时候只是代表文件上传到后台,但是后台有没有处理好并不知道,所以需要在上传成功后再设置为100

const config={

    headers: {

      'Content-Type': 'multipart/form-data',

      'Token':localStorage.getItem('Token')?localStorage.getItem('Token'):null

    },

    onUploadProgress: (progress: any)=>{

        if(progress.lengthComputable){

        let percent = (progress.loaded / progress.total*100 | 0)

        //percent--等于100的时候只是代表文件上传到后台,但是后台有没有处理好并不知道,所以需要在上传成          功后再设置为100

        if(percent<100){

          console.log(percent)

          this.setState({

            percent

         })

        }

      }

     }

}

axios.post('upload',formdata,config).then(res=>{

    if(res.code===0){

        this.setState({

            percent:100 后台处理好上传的文件时手动设置为100

        })

    }

}).catch(err=>{

})