• 前端面试题


    前端面试题

    在国家特殊节日让屏幕变灰

    设置样式HTML中

    html{
      filter: grayscale(100%); /*滤镜 灰色全透*/
    }
    
    • 1
    • 2
    • 3

    如何让字体小于12px

    浏览器是可以支持小于12px字体,但谷歌浏览器认为小于12px字体难以辨别,便对浏览器进行了调整

    在浏览器中设置

    • 点开浏览器右上角的三个点,点击“设置”
    • 选择“外观”,找到“自定义字体”,在自定义字体中找到“最小字号”,将最小字号的进度条直接拉到最左边,这就表示最小字号是多少由你说了算

    通过缩放来实现

    	transform: scale(0.5);
        transform-origin:0 0;
    
    • 1
    • 2

    通过svg加text实现

    		/*设置样式*/
    		svg text{
                font-size: 8px;
            }
            /**设置标签/
            <svg>
            	<text x="0" y="8">缩放字体8pxtext>
       		svg>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    盒子水平居中

    将子盒子改为行内块模式,通过text-align和line-height来实现

    		.wrapper{
                width: 600px;
                height: 600px;
                background-color: pink;
                text-align: center; // 左右居中
                line-height: 600px; // 上下居中
            }
            .box{
                width: 300px;
                height: 300px;
                background-color: skyblue;
                display: inline-block;
                vertical-align: middle;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    利用定位50%,加上负的margin实现

    		.wrapper{
                width: 600px;
                height: 600px;
                background-color: pink;
                position: relative;
            }
            .box{
                width: 300px;
                height: 300px;
                background-color: skyblue;
                position: absolute;
                top: 50%;
                left: 50%;
                margin-left: -150px; /* 子元素一半*/
                margin-top: -150px;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    利用定位加margin:auto

    		.wrapper{
                width: 600px;
                height: 600px;
                background-color: pink;
                position: relative;
            }
            .box{
                width: 300px;
                height: 300px;
                background-color: skyblue;
                position: absolute;
                top: 0;
                right: 0;
                bottom: 0;
                left: 0;
                margin: auto;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    利用定位加位移

    		.wrapper{
                width: 600px;
                height: 600px;
                background-color: pink;
                position: relative;
            }
            .box{
                width: 300px;
                height: 300px;
                background-color: skyblue;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%,-50%);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    利用flex盒子实现

    		.wrapper{
                width: 600px;
                height: 600px;
                background-color: pink;
                display: flex;
                justify-content: center;
                align-items: center;
            }
            .box{
                width: 300px;
                height: 300px;
                background-color: skyblue;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    判断数据类型方式

    Typeof

    • typeof通常用来判断基本数据类型
    • typeof用来判断null和引用数据类型都是’object’
    const test=undefined;
    console.log(typeof test)
    // undefined
    
    • 1
    • 2
    • 3

    Instaceof

    • a instanceof A 根据instanceof的定义:判断参照对象(大写字母A)的prototype属性所指向的对象是否在被行测对象a的原型链上
    • instanceof 只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型
    const arr=[1,2,2,3,3]
    console.log(arr instanceof Array)
    // true arr是数组实例
    
    • 1
    • 2
    • 3

    constructor判断

    • 针对于instanceof的弊端,我们使用constructor检测,constructor是原型对象的属性指向构造函数
    • 这种方式解决了instanceof的弊端,可以检测出除了undefined和null的9种类型(因为它两没有原生构造函数)
    const arr=[1,2,2,3,3]
    console.log(arr.constructor)
    // [Function: Array]
    
    
    • 1
    • 2
    • 3
    • 4

    通过Object下的toString.call()方法来判断

    • 所有typeof返回值为"object"的对象,都包含一个内部属性[[Class]],我们可以把他看作一个内部的分类,而非传统意义上面向对象的类,这个属性无法直接访问,一般通过Object.prototype.toString(…)来查看
    • 该方法可以对null和undifined进行判断
    const arr=[1,2,2,3,3]
    console.log(toString.call(arr))
    // [object Array]
    
    • 1
    • 2
    • 3

    数组去重方法

    双重for循环,利用splice方法

    const arr=[1,2,2,3]
    for (let i=0;i<arr.length;i++){
        for (let j=i+1;j<arr.length;j++){
            if(arr[i]=== arr[j]){ // 如果有相同,删除
                arr.splice(j,1)
            }
        }
    }
    console.log(arr) // [1,2,3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    建立新数组,利用indexof去重

    const arr=[1,2,2,3]
    const newArr=[]
    for (let i=0;i<arr.length;i++){
        if(newArr.indexOf(arr[i])===-1){ // 判断新建数组中是否有这个元素,没有的话就添加
            newArr.push(arr[i])
        }
    }
    console.log(newArr) // [1,2,3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    利用ES6新特性去重

    const arr=[1,2,2,3,3]
    const newArr=Array.from(new Set(arr)) // Set去除相同元素,然后转化为真正的数组
    console.log(newArr)
    
    • 1
    • 2
    • 3

    闭包

    闭包3个特性

    • 函数嵌套函数
    • 函数内部引用函数外部的参数与变量
    • 参数和变量不会被垃圾回收机制回收

    写法

    	function fun(){
            let num1=0;
            return function(){
                let num2=0
                console.log(++num1)
                console.log(++num2)
            }
        }
        const fun1=fun()
        fun1() // 1 1
        fun1() // 2 1 // 因为闭包num1被缓存了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    闭包优点与缺点

    • 优点
      • 保护函数内变量安全,实现封装,防止变量流入其他环境中发生命名冲突
      • 在内存中维持一个变量,可以做缓存
      • 匿名只执行函数可以减少内存消耗
    • 缺点
      • 私有变量不能被销毁,增大了内存消耗,容易造成内存泄露;解决方法使用完后手动赋值为null
      • 由于闭包涉及跨域访问,会导致性能损失;通过把跨域作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

    this指向

    this概念

    1. 普通函数:谁调用就指向谁,没有调用者,就指向window对象
    2. 箭头函数:this指向函数作用域所用对象

    this

    全局this

    1. 在全局作用域下,this始终指向window
    	var a=1
        console.log(this)
        console.log(this.a)
    
    • 1
    • 2
    • 3

    函数内的this

    1. 直接调用函数,指向window对象
    2. 被别的调用,指向调用对象
    const btn=document.querySelector('.btn')
        function test(){
            console.log(this)
        }
        test() // window //注意这个在严格模式下为undefined
        window.test() // window
        btn.onclick=test // 指向节点
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对象中的this

    1. 也是谁调用就指向谁
    2. 但得注意this丢失问题
    	const obj={
            name:'obj',
            test:function(){
                console.log(this.name)
            }
        }
        obj.test() // 指向obj,显示obj
        const fun=obj.test
        fun() // 指向window,显示为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    构造函数中this

    指向实例对象

    	function Person(name){
            this.name=name;
        
        }
        const p1=new Person('jack')
        console.log(p1.name) // 指向实例对象,显示jack
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    原型链上this

    会先在自己实例上找,没有的话,顺着原型链找

     	var oo = {
            f: function() {
                return this.a + this.b;
            }
        };
    
        var p = Object.create(oo);
        p.a = 1;
        p.b = 2;
        console.log(p.f()); // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    箭头函数this

    箭头函数中this是静态的,不会随着调用而改变,总是指向上一作用域中的this

    var name = 'window'
        const obj = {
            name:'obj',
            fn:()=>{
                console.log(this.name)
            }
        }
        obj.fn() //window
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    原型链

    请添加图片描述

    • Object的隐式原型为null
    • 构造函数Function的原型和隐式原型都指向Function原型
    • 原型链作用是在我们使用方法时,不仅会在实例上找,也会在原型链上查找

    new函数过程发生了什么

    1. new时先创建一个新对象
      let obj=new Object();
    2. 将空对象的__proto__成员指向了原函数对象prototype成员对象
      obj.proto=Person.prototype
    3. 修改this到obj上
      Person.call(obj)
    4. 返回一个对象

    详情


    在地址栏输入url到页面出现,经历了那些过程

    1. DNS查询/解析(先看本地是否有缓存)
      1. 浏览器DNS缓存
      2. 计算机DNS缓存
      3. 路由器DNS缓存
      4. 网络运营商DNS缓存
      5. 递归查询查找 a.b.c.d.com
    2. TCP三次握手
      1. 浏览器告诉服务器我准备好发送请求了
      2. 服务器告诉浏览器我也准备好,需要再次确认一下
      3. 浏览器告诉给服务器确认完毕,马上发送请求
    3. 发送请求
    4. 返回响应
    5. 渲染页面
      1. 调用html解析器将HTML解析成DOM树
      2. 调用css解析器将CSS解析成CSSOM数
      3. 调用js引擎解析JS
        1. 如果修改了DOM节点,重新生成DOM树
        2. 如果修改了CSS节点,重新生成CSSOM树
      4. 将DOM树 + CSSOM树 = render树
      5. layout布局
      6. render渲染
    6. 断开连接TCP四次挥手
      1. 浏览器告诉服务器请求报文发送完毕
      2. 服务器告诉浏览器请求报文接受完毕,可以等待断开
      3. 服务器告诉浏览器响应报文发送完毕
      4. 浏览器告诉服务器响应报文接受完毕,可以断开
    7. 服务器断开连接,然后浏览器断开连接

    详情


    Vue2数据响应式原理及缺陷

    概述

    当对象本身或对象属性被读和写的时候,我们需要知道该数据被操作了,并在这过程中执行一些函数,例如:render函数,而这一过程我把它定义为数据响应式

    实现

    Observer

    1. Observer通过Object.defineProperty将普通对象包装成一个带有getter\setter属性的特殊对象
    2. 对于对象,递归遍历对象的所有属性,以完成深度属性转换
    3. 对于数组,vue会重写数组的一些方法,更改Array的隐式原型

    Dep

    1. Observe只是让Vue感觉读写操作了,具体干什么还需要Dep来做
    2. 主要负责在getter中收集依赖,在setter中通知依赖更新

    Watcher

    1. 当Dep收集依赖后,当数据发生改变时,准备派发通知,不知道派发给谁,或者不知道谁用了该数据,这时就需要watcher
    2. 当数据发生改变时,vue不直接通知相关依赖更新,而是通知依赖对应的watcher实例去执行

    Scheduler

    1. 当watcher收到派发的更新通知后,watcher不会立即执行,而是将自己交给一个调度器scheduler
    2. 调度器scheduler维护一个执行队列,同一个watcher在该队列中只会存在一次,队列中的watcher不会立即执行,而是通过nextTick的工具函数执行,nextTick是一个微队列,会把需要执行的watcher放到事件循环的微队列中执行

    缺陷

    1. 浅度劫持,对于对象数组要进行处理
    2. 对象新增的属性没有响应式
    3. 数组的部分操作没有响应式(修改数组元素第一个的值,修改数组长度)

    参考


    双向绑定原理

    原理

    1. Vue双向绑定原理是通过数据劫持结合发布订阅者模式来实现的
    2. 数据和视图同步,视图变化,数据也就变化;数据变化,视图也就变化
    	const obj={}
        Object.defineProperty(obj,'hello',{
            get:function(){
                console.log("调用get方法")
            },
            set:function(newVal){
                console.log("调用set方法"+newVal)
            }
        })
        obj.hello; // 调用get方法
        obj.hello='hi'; //  调用set方法hi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    详情


    watch与computed,methods三者区别

    作用机制上

    1. 侦听属性watch与计算属性computed都是以vue的依赖追踪机制(订阅者模式)为基础的,只有当一个函数变化时,才会调用相关的函数
    2. methods里面是用来定义函数的,需要手动调用才会执行

    作用性质上

    1. methods中定义的是函数,你需要进行函数调用(func())
    2. computed是计算属性,他在使用了和data是对象中的数据类型是同一类
    3. watch类似于监听机制+事件机制
    watch: {
       firstName: function (val) {  this.fullName = val + this.lastName }
    }
    /*
    * 只有当firstName触发后才执行,而firstName对应的函数就相当于监听到事件发生后执行的方法
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    watch与computed异同

    相同

    1. 他们都是在依赖数据发生改变的情况下,被依赖的数据根据预先定义好的函数,发生自动的变化

    不同

    1. watch擅长处理
      1. 一个数据影响多个数据
      2. 当需要在数据变化时执行异步或开销较大的操作时
      3. watch可以监听新值与旧值
      4. watch属性还可以用来监听路由router的变化,只是这里的监听元素是固定的
    2. computed擅长处理(计算属性不能异步,异步函数return是没有任何意义的)
      1. 多个数据影响一个数据
      2. 当存在复杂逻辑、需要用到缓存时

    methods

    1. methods与watch和computed是一种不同的概念,methods指的是方法也就是定义要用的函数,他不会主动触发需要在被调用时才会触发
    2. methods与computed看不出来差别,但要注意computed是存在缓存的

    参考


    Vue组件为啥需要name属性

    1. 当项目使用keep-alive缓存时,可用组件name属性进行缓存过滤
    2. 当组件有name时,就可以组件自己调用自己(递归调用)
    3. 使用vue-tool时,工具中组件名就是name属性决定的

    Vue传值方式

    props与$emit(在封装的组件中很常用)

    1. 在父组件中通过属性传值与子组件,子组件在props接收
    2. 子组件通过this.$emit(‘事件名’,值),父组件通过@事件名="事件处理函数"接受父组件传递的值

    Vue.prototype.$bus

    1. 在mian.js中定义
      new Vue({
      	......
      	beforeCreate() {
      		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
      	}
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 要传值的组件定义this.$bus.$emit(‘事件名’,值)
    2. 接收值的组件定义this.$bus.$on(‘事件名’,回调函数),也可以通过this.$bus.$off(‘事件名’)解绑事件

    VueX

    1. state 用来存储数据
    2. mutation 用来对state进行成员操作
    3. getters 加工state成员给外界
    4. actions 也是用来对state进行成员操作不过是异步操作而且需要提交给mutation,在action中不能直接更改
    5. modules 模块化状态管理

    ref传值

    • 在父组件中在子组件上绑定ref,通过this.$refs.ref名字.子组件接受方法(数据)
     <Test ref="test"/>
     ...
     this.$refs.test.childMethod('val12343');
    
    • 1
    • 2
    • 3
    • 子组件定义方法接受子组件接受方法(val){ // val就是传过来的值}
    methods: {
        childMethod(val) {
          console.log(val); // val就是传过来的值 val12343
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    LoaclStorage(存储于本地)

    1. 先将需要的值存储于本地localStorage.setItem(“存储名”,存储值)
    2. 获取值通过localStorage.getItem(“存储名”)
    3. 不需要时也可以删除localStorage.removeItem(“存储名”)

    Vue-router路由实现原理

    Hash路由模式

    1. hash虽然标记在url中,但他不会发送网络请求,对服务端完全无用
    2. 通过window.location.hash实现的

    History路由模式

    1. 通过window.history实现的
    2. 分别通过调用history.pushState()和history.replaceState()方法来实现push和replace方法
    3. pushState和replaceState方法修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会立即发送请求该URL,这为更新视图但不重新发送请求提供了基础
    4. Hash与History两种模式的go、back和forward三个方法都是通过window.history.go()来实现的

    Abstract模式

    vue-router为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能


    Vue-router传参

    router-link传参

    params

    <router-link :to="{name:'home',params:{id:1}}" />
    <router-link :to="{path:'home',params:{id:1}}" />
    // 取参 this.$route.params.id 
    
    • 1
    • 2
    • 3

    query

    <router-link :to="{name:'home',query:{id:1}}" /> 
    <router-link :to="{path:'home',query:{id:1}}" /> 
    // 取参 this.$route.query.id
    
    • 1
    • 2
    • 3

    this.$router.push()传参

    params

    this.$router.push({name:'home',params:{id:1,age:2}})
    
    • 1

    query

    this.$router.push({path:'/home',query:{id:1,age:2}}) 
    this.$router.push(`/home?id=1`);
    
    • 1
    • 2

    参考


    Vue-router有那些守卫

    全局前置守卫beforeEach

    • 在router中进行配置
    • router.beforeEach((to,from,next)=>{})
      • to要到哪个路由去
      • from从哪个路由来
      • next下一步(无论失败与否都要调用,否则会阻止程序继续执行)

    全局后置守卫 afterEach

    • router.afterEach((to,from)=>{})

    组件内守卫(相当于给组件增加生命周期)

    beforeRouteEnter 进入组件之前

    beforeRouterEnter(to,from,next){}

    beforeRouteUpdate 组件被复用时调用

    beforeRouterUpdate(to,from,next){}

    beforeRouteLeave 离开组件时调用

    beforeRouteLeave(to,from,next){}


    移动端防止事件穿透

    事件穿透原理

    1. 假如页面上有两个元素A和B,B元素在A元素之上,B为遮罩层mask。我们在B元素上绑定 touchstart事件,作用是隐藏B元素;A元素上绑定click事件。我们发现,当我们点击B元素,B元素被隐藏了,随后,也触发了A元素的click事件
    2. 事件执行的顺序是 touchstart >touchmove>touched ==>click,click事件有300ms的延迟,当 touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转了

    解决方案

    禁用缩放

    click事件的延迟300ms是为了移动端缩放

     <!-- 1.禁用缩放 user-scalable=no -->
     <meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
    
    • 1
    • 2

    延迟上层元素消失

    touchstart后延迟350ms后再隐藏,先把透明度设为0,解决视觉层面效果,在设置定时器延迟,让元素消失

    $('.mask').on('touchstart',function() {
        console.log('mask-touchstart');
        $(this).css('opacity', 0); // 设置视觉层面消失
        setTimeout(function() {
            $('.mask').css('dispaly', 'none'); // DOM节点消失
        },350);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    pointer-events,让被覆盖元素短时间内无法触发click

    • pointer-events两个属性
      • auto默认值,鼠标默认值不会穿透当前层
      • none使元素无法触发事件
    $('.mask').on('touchstart',function() {
        console.log('mask-touchstart');
        $(this).css('display', 'none');
        $('.box').css('pointer-events', 'none');   // 让被覆盖元素无法响应click
        setTimeout(function() {
            $('.box').css('pointer-events', 'auto');   // 恢复被覆盖元素
        },300);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用fastclick库

    使用fastclick库后,所有点击事件都使用click,没有350ms延时,也没有样式穿透问题

    <script type='application/javascript' src='/path/to/fastclick.js'></script>
    ...
    if ('addEventListener' in document) {
    	document.addEventListener('DOMContentLoaded', function() {
    		FastClick.attach(document.body);
    	}, false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    针对安卓的,android:clickable=“true”

    在上层的布局中增加android:clickable="true"的属性,这样下层的事件就不会被触发了

    详情


    移动端适配

    viewport适配

    //获取meta节点
    var metaNode = document.querySelector('meta[name=viewport]');
     
    //定义设计稿宽度为375
    var designWidth = 375;
     
    //计算当前屏幕的宽度与设计稿比例
    var scale = document.documentElement.clientWidth/designWidth;
     
    //通过设置meta元素中content的initial-scale值达到移动端适配
    meta.content="initial-scale="+scale+",minimum-scale="+scale+",maximum-scale="+scale+",user-scalable=no";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    借助media实现rem适配

    //对屏幕大小划分了html不同的font-size
    @media screen and (min-width: 320px) {html{font-size:50px;}}
    @media screen and (min-width: 360px) {html{font-size:56.25px;}}
    @media screen and (min-width: 375px) {html{font-size:58.59375px;}}
    @media screen and (min-width: 400px) {html{font-size:62.5px;}}
    @media screen and (min-width: 414px) {html{font-size:64.6875px;}}
    @media screen and (min-width: 440px) {html{font-size:68.75px;}}
    @media screen and (min-width: 480px) {html{font-size:75px;}}
    @media screen and (min-width: 520px) {html{font-size:81.25px;}}
    @media screen and (min-width: 560px) {html{font-size:87.5px;}}
    @media screen and (min-width: 600px) {html{font-size:93.75px;}}
    @media screen and (min-width: 640px) {html{font-size:100px;}}
    @media screen and (min-width: 680px) {html{font-size:106.25px;}}
    @media screen and (min-width: 720px) {html{font-size:112.5px;}}
    @media screen and (min-width: 760px) {html{font-size:118.75px;}}
    @media screen and (min-width: 800px) {html{font-size:125px;}}
    @media screen and (min-width: 960px) {html{font-size:150px;}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    JS配合修改配合rem适配

    var designWidth = 375;  		// 设计稿宽度
    var remPx = 100;               // 在屏幕宽度375px,的时候,设置根元素字体大小 100px
    var scale = window.innerWidth / designWidth; //计算当前屏幕的宽度与设计稿比例
    // 根据屏幕宽度 动态计算根元素的 字体大小
    document.documentElement.style.fontSize = scale*remPx + 'px';
    
    • 1
    • 2
    • 3
    • 4
    • 5

    JS动态修改配合CSS预处理器(less)

    	// 计算屏幕宽度
    var screen = document.documentElement.clientWidth;
    	// 计算字体大小,取屏幕宽度的16分之一
    var size = screen / 16;
    	// 设置根元素字体大小
    document.documentElement.style.fontSize = size + 'px';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    js获取当前屏幕的宽度,将屏幕宽度的16分之一设置给html的font-size

    // 此时设计稿的宽度为375,定义一个less变量等于16分之一的设计稿宽度
    @rem: 375/16rem;
    
    • 1
    • 2

    JS动态修改配合rem适配

    	// 计算屏幕宽度
    var screen = document.documentElement.clientWidth;
    	// 设置根元素字体大小
    document.documentElement.style.fontSize = screen + 'px';
    
    • 1
    • 2
    • 3
    • 4

    详情

    借助插件适配

    • 让不同设备的视口取得最佳CSS像素
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    
    • 1
    • 安装 postcss,postcss-pxtorem,postcss-loader,postcss-import,postcss-url一系列插件
    npm install postcss@8.2.6 --save
    
    • 1
    npm install postcss-import@14.0.0 --save
    
    • 1
    npm install postcss-loader@5.0.0 --save
    
    • 1
    npm install postcss-pxtorem@5.1.1 --save
    
    • 1
    npm install postcss-url@10.1.1 --save
    
    • 1
    • 在项目根目录下添加 .postcssrc.js 文件
    module.exports = {
      plugins: {
        'postcss-pxtorem': {
          rootValue: 32, //根目录的字体大小设为32px
          propList: ['*'], //proplist:是那些属性需要转换成rem,全部
          minPixelValue: 2 //最小转换单位.2px
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 在项目中index.html的头部添加下面这段js代码:
    <script>(function () {
        function autoRootFontSize() {
          document.documentElement.style.fontSize = Math.min(screen.width, document.documentElement
            .getBoundingClientRect().width) / 470 * 32 + 'px';
          // 取screen.width和document.documentElement.getBoundingClientRect().width的最小值;
          // 除以470,乘以32;意思就是相对于470大小的32px;
        }
        window.addEventListener('resize', autoRootFontSize);
        autoRootFontSize();
      })();</script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注:设计师制作的效果图常为750px,为了方便直接读取UI标好的像素大小,根目录的字体大小就设置为32px;
    详情


    适配低版本浏览器

    Babel

    Babel默认只转换新的JavaScript句法(syntax),而不转换新的API.(不转换新的API.的意思是:对极少部分浏览器无法支持的api,转换之后的也无法正常的使用,比如:import,export)

    使用

    单文件转换

    1. 全局安装babel的命令行工具
    cnpm install babel-cli -g
    
    • 1
    1. 执行命令,安装babel-preset-env到本地
    cnpm install babel-preset-env --save-dev
    
    • 1
    1. 在项目根目录下创建 .babelrc 文件,文件中的内容如下
    {
     "presets": ["env"]
    }
    
    • 1
    • 2
    • 3
    1. 执行命令将es6转换为es5
    文件到文件:babel js文件路径 -o 转译后生成的文件路径
    
    • 1

    在package.json文件的脚本中配置转义命令

    1. 安装babel-cli命令行工具
    cnpm install babel-cli babel-preset-env --save-dev 
    
    • 1
    1. 创建 .babelrc 文件,写入以下代码
    {
     "presets": ["env"]
    }
    
    • 1
    • 2
    • 3
    1. 在你的 package.json 中添加一个 “scripts” 属性并将 babel 命令放在它的 build 属性中
    "scripts": {
         "build": "babel src -d lib"
       },
    
    • 1
    • 2
    • 3
    1. 终端运行命令开始转译
    npm run build
    
    • 1

    详情


    前端项目优化

    普通项目优化

    页面加载阶段

    1. dns预解析
    2. 使用cdn
    3. 静态资源压缩与合并
    4. 减少http请求
    5. 异步加载defer(页面解析完成后执行代码),async(当前JS解析完成后执行代码)
    6. 服务端渲染ssr
    7. 多使用内存和缓存

    页面渲染阶段

    1. css放前面,js放后面
    2. 减少DOM查询,多次使用的保存为变量
    3. 减少DOM操作,统一通过DOM片段操作
    4. 事件函数的防抖和节流
    5. 图片懒加载

    Vue项目优化

    代码层面优化

    1. Object.freeze(data)(方法可以冻结对象的属性)对于一些查询类的页面,调取接口回来后的数据可 不进行数据劫持
    2. v-if和v-for不要在一起使用。v-if的条件通过函数来处理
    3. v-for中加上key,对于虚拟dom树查找提高性能
    4. computed和watch注意区分使用场景。前者是有缓存的。后者是监听到数据变化后的回调无缓存
    5. created中发起请求,mounted钩子中代表页面dom加载完成可以进行dom操作
    6. 长列表性能优化,只渲染可视区域的列表
    7. 长表格性能优化,通过canvers来绘制表格
    8. 合理使用$nextTick去操作dom,适用于更新了数据马上就要操作dom的场景
    9. 操作dom不要使用js原生的方式来操作。用vue提供的三种方式来操作 比如,ref、自定义指令el、事件中的话用e.target获取dom
    10. 尽量不要在前端进行大量的数据处理
    11. 合理使用keep-alive来缓存页面数据,跳过created,mounted钩子,他有自己特定的钩子activted等
    12. 路由懒加载通过import配合箭头函数,还有其他的方式require
    13. 组件懒加载,异步加载
    14. 尽量少用float,可以用flex布局
    15. 频繁切换的使用v-show,不频繁的使用v-if
    16. 不要在模板中写过多的样式
    17. 服务端渲染ssr,优化seo,与首屏白屏问题
    18. 通过addEventListenr添加的事件,需要自行销毁
    19. 把组件中的css提取成单独的文件
    20. 少使用闭包与递归,递归可做尾递归的优化
    21. 使用字体图标或者svg来代替传统的Png等格式的图片
    22. 在Js中避免“嵌套循环”和“死循环”

    webpack优化

    1. 去除无用代码treeShaking
    2. babel编译es6到es5的时候,会有多余代码产生
    3. 减小app.js的体积,提取公共代码
    4. 减少vendor.js的体积,通过按需引入第三方库,或者有些资源可以通过script标签引入
    5. 代码切割,有一些组件没必要都打包到一起
    6. 使用chunck
    7. 使用SouceMap,来还原线上代码,更方便的去定位线上问题
    8. 构建结果,通过可视化插件,进行分析
    9. webpack对图片进行压缩等处理
    10. 图片可以使用webp,优雅降级处理
    11. 编译优化
    12. 模板预编译,使用vue-template-loader,把模板编译成渲染函数
    13. thread-loader多线程打包
    // webpack.base.js
     
    {
            test: /\.js$/,
            use: [
              'thread-loader',
              'babel-loader'
            ],
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    WEB层面

    1. 浏览器缓存的使用
    2. 开启gzip压缩
    3. CDN的使用,减少路由转发的次数,就近访问资源
    4. 使用chrome的性能分析工具,查找性能瓶颈
    5. dns预解析
    6. 静态资源的压缩与合并
    7. 减少https请求
    8. 异步加载defer,async
    9. 静态资源和服务不要放在同一台机器上。多个域名去并行加载解析

    安全层面

    XSS跨站请求攻击

    XSS 前端替换关键字,建议后端也替换,如<>的替换,避免脚本的执行

    XSRF跨站请求头伪造

    XSRF增加验证流程,比如输入密码,指纹,短信验证码,人脸识别等

    详情


    模块化

    概念

    1. 有组织的将一个大文件拆分成多个独立并相互依赖的小文件
    2. 模块内部有许多私有属性,只向外暴露一部分公开的接口

    模块化方法

    1. AMD
    2. CMD
    3. CommonJS(Node JS中采用该规范)通过module.exports暴露模块,通过require引入模块
      1. 代码运行在模块作用域,不会污染全局
      2. 加载模块顺序是按照词法解析顺序来加载
      3. 加载模块是同步的
      4. 单例加载:模块会被缓存起来,再次运行直接加载,除非手动清除
      5. 加载模块得到的结果是深拷贝
    4. ES6模块export暴露模块,import … from … 引入模块
      1. 尽量静态化,编译时就确定模块的依赖关系
      2. 加载模块的值是引用,所以在全局只有一份
      3. 加载模块也是异步的

    小程序生命周期

    应用的生命周期,在app.js中进行调用

    生命周期说明
    onLanch小程序初始化完成时触发,全局只触发一次
    onShow小程序启动,后者后台进入前台触发
    onHide小程序从前台进入后台触发
    onError小程序发生脚本错误或者API调用时报错时触发
    onPageNotFound小程序打开时页面不存在时触发
    onUnhandledRejection有未处理的promise时触发
    onThemeChange系统切换主题时触发

    页面生命周期,当你进入\切换到一个新页面时触发

    生命周期说明作用
    onLoad监听页面加载发送请求获取数据
    onShow监听页面显示发送请求获取数据
    onReady监听页面初次渲染完成获取页面元素
    onHide监听页面隐藏终止任务,如定时器或播放音乐
    onUnLoad页面卸载终止任务

    组件生命周期

    生命周期说明
    created监听页面加载
    attached监听页面显示
    ready页面初次渲染完成
    moved监听页面隐藏
    datached监听页面卸载
    error组件方法抛出错误时执行

    参考


    微信小程序如何分包

    为何要分包

    1. 在第一本版本的小程序中,小程序代码不能超过1M,所以小程序可以秒开;而后面随着业务发展小程序分包大小不能超过8M,8M不能秒开,于是就分包加载
    2. 小程序所有分包不能超过8M,单个分包,主包不能超过2M
    3. 小程序在启动时,默认下载主包,并启动主包内页面;当用户进入分包页面时,客户端下载分包并展示

    app.json中配置

    {
      "pages":[
        "pages/index",
        "pages/logs"
      ],
      "subpackages": [
        {
          "root": "packageA", // 分包根目录
          "pages": [
            "pages/cat",
            "pages/dog"
          ]
        }, {
          "root": "packageB",
          "name": "pack2", // 分包别名,分包预下载时可以使用
          "pages": [ // 分包页面路径,相对于分包根目录
            "pages/apple",
            "pages/banana"
          ],
          independent:false // 是否独立分包
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分包注意事项

    1. 主包无法加载分包的资源
    2. 分包可以加载主包的资源
    3. 分包与分包之间无法相互加载资源

    微信小程序获取用户信息

    获取用户的头像,昵称

    wx.getUserProfile
    
    • 1

    获取用户手机号

    1. 小程序端调用 wx.login() 方法,获取 code 后,将 code 通过后台 api 接口传递到后台
    2. 后台 api 接口收到 code 后,调用微信接口 jscode2session , 换取 openid 、 session_key 、 unionid
      1. openid同一用户不同应用下不相同
      2. unionid同一用户不同应用下相同
      3. session_key为应用安全,session_key应该存储在服务端
    3. 小程序端提交 code 调用后台 api 接口后 , 获取 api 接口返回的 userToken ,通过页面 button 点击调用后台接口 , 传递参数 userToken 、 iv 、 encryptedData 到后台进行数据解密,得到 phoneNumber
    <button
    	class="btn" 
    	open-type="getPhoneNumber"
    	bindgetphonenumber="getPhoneNumber"
    >   
       {{phoneNumber==null?'获取手机号':phoneNumber}}
    </button>
    ...
    getPhoneNumber: function (e) {
        var that = this;
        if (e.detail.errMsg == "getPhoneNumber:ok") {
          wx.request({
            url: app.serverUrl+'/api/login/decodePhone',
            data: {
              encryptedData: e.detail.encryptedData,
              iv: e.detail.iv,
              userToken: that.data.userToken,
            },
            header: {
              'content-type': 'application/json' 
            },
            success: function (res) {
              that.setData({
                phoneNumber:res.data.phoneNumber
              })
            }
          })
        }
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    后端给的二进制流如何渲染到页面

    // 调用方法准换为图片路径
    window.URL.createObjectURL(res)
    
    • 1
    • 2

    后端提供10W条数据,如何渲染到页面

    setTimeout进行分页渲染

    <template>
     <div class='testDate'>
        <div v-for="item in newList" :key="item.id">
            <span>{{item.text}}</span>
        </div>
     </div>
    </template>
    
    <script>
    export default {
      name: 'testDate',
      components: {},
      props: {},
      data() {
        return {
          list: [], // 用来模拟后端给的10W条数据
          newList: [], // 用于渲染的数据
          total: 100000, // 一共多少条数据
          page: 0, // 第几页,默认0为第一页
          limit: 200, // 一页200条数据
        };
      },
      computed: {},
      watch: {},
      created() {
        for (let i = 0; i < 100000; i += 1) {
          this.list.push({
            id: i,
            text: `测试数据${i}`,
          });
        }
      },
      mounted() {
        this.render();
      },
      methods: {
        render() {
          if (this.page >= Math.ceil(this.total / this.limit)) return; // 如果第几页的页数已经大于总共的页数,说明数据已经完了,直接return返回
          setTimeout(() => {
            for (let i = this.page * this.limit; i < this.page * this.limit + this.limit; i += 1) {
              this.newList.push(this.list[i]); // 添加到newList用于渲染的数据列表中
            }
            this.page += 1;
            this.render(); // 递归调用
          }, 0);
        },
      },
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    requestAnimationFrame,这样可以减少reflow次数(浏览器重新计算集合属性),提高性能

    <template>
     <div class='testDate'>
        <div v-for="item in newList" :key="item.id">
            <span>{{item.text}}</span>
        </div>
     </div>
    </template>
    
    <script>
    export default {
      name: 'testDate',
      components: {},
      props: {},
      data() {
        return {
          list: [], // 用来模拟后端给的10W条数据
          newList: [], // 用于渲染的数据
          total: 100000, // 一共多少条数据
          page: 0, // 第几页,默认0为第一页
          limit: 200, // 一页200条数据
        };
      },
      computed: {},
      watch: {},
      created() {
        for (let i = 0; i < 100000; i += 1) {
          this.list.push({
            id: i,
            text: `测试数据${i}`,
          });
        }
      },
      mounted() {
        this.render();
      },
      methods: {
        render() {
          if (this.page >= Math.ceil(this.total / this.limit)) return;
          requestAnimationFrame(() => {
            for (let i = this.page * this.limit; i < this.page * this.limit + this.limit; i += 1) {
              this.newList.push(this.list[i]);
            }
            this.page += 1;
            this.render();
          });
        },
      },
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    延迟加载

    1. document.documentElement.clientHeight获取屏幕高度
    2. getBoundingClientRect().top获取最后一个空白元素距离顶部距离
    <template>
     <div class='testDate'>
        <div v-for="item in newList" :key="item.id">
            <span>{{item.text}}</span>
        </div>
        <div ref="blank"></div> <!--加个空白元素来判断是否需要加载 -->
     </div>
    </template>
    
    <script>
    export default {
      name: 'testDate',
      components: {},
      props: {},
      data() {
        return {
          list: [], // 用来模拟后端给的10W条数据
          newList: [], // 用于渲染的数据
          total: 100000, // 一共多少条数据
          page: 0, // 第几页,默认0为第一页
          limit: 200, // 一页200条数据
        };
      },
      computed: {},
      watch: {},
      created() {
        for (let i = 0; i < 100000; i += 1) {
          this.list.push({
            id: i,
            text: `测试数据${i}`,
          });
        }
      },
      mounted() {
        this.render();
        window.addEventListener('mousewheel', this.render);
      },
      methods: {
        render() {
          if (this.page >= Math.ceil(this.total / this.limit)) return;
    	  // 当最后一个空白元素高度小于等于屏幕高度,则说明已经来到底部了,需要加载元素来补充视图
          if (this.$refs.blank.getBoundingClientRect().top <= document.documentElement.clientHeight) {
            for (let i = this.page * this.limit; i < this.page * this.limit + this.limit; i += 1) {
              this.newList.push(this.list[i]);
            }
            this.page += 1;
          }
        },
      },
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    存在以下表达式,由字母,非字母,运算表达式组成,写段代码计算值,计算规则如下:英文字母为其序列号,非英文字母为0

    实例:a+b=1+2=3;a+#=1+0=1

    function sumFun(value){
        let arr=[] // 定义一个数组保存值
        const valueArr=value.toUpperCase().split('') // 字符串转大写,转数组
        const englishLetter=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
        const operationSymbol=['+','-','*','/']
        valueArr.forEach(item=>{ // 循环遍历数组
            if(englishLetter.includes(item)){ // 是否是数字
                arr.push(englishLetter.findIndex(i=>i===item))
            }else if(operationSymbol.includes(item)){ // 是否是运算符
                arr.push(item)
            }else { // 其他都为0
                arr.push(0)
            }
        })
        return  eval(arr.join('')) // 通过eval计算字符串中的表达式,返回该表达式计算的结果
    }
    const value='a+b+c-b+#'
    console.log(sumFun(value))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    148. SAP UI5 表格数据如何导出成 Excel 文件(Table Export As Excel)
    Windows下编译Mediapipe,C++版本
    Integer 缓存机制
    Python教程:函数入门到精通
    记一次多个Java Agent同时使用的类增强冲突问题及分析
    网络安全(黑客)自学
    ARM64常见指令
    并查集介绍和基于并查集解决问题——LeetCode 952 按公因数计算最大组件大小
    嵌入式开发中模板方法模式实现
    docker-compose安装mqtt
  • 原文地址:https://blog.csdn.net/weixin_64925940/article/details/125663846