• 前端面试题总结


    防抖和节流

    防抖:用户触发事件过于频繁,只要最后一次事件的操作(只执行最后一次)
    应用场景:1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖。
    2. 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖。
    3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存。
    节流:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效(控制执行次数)。
    应用场景:

    scroll 事件,每隔一秒计算一次位置信息等。
    浏览器播放事件,每个一秒计算一次进度信息等。
    input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)。

    //防抖
    <body>
        防抖: <input id="input" type="text">
    </body>
    <script>
        // 监听input输入值
        input.addEventListener('input', function (e) {
            val(e.target.value)
        })
        function fn(time, fun) { // 防抖的核心代码
            let flag // 定义状态
            return function (value) {
                clearTimeout(flag)// 在执行之前清除定时器flag 
                flag = setTimeout(() => {
                    fun(value)
                }, time)
            }
        }
        let val = fn(1000, function (val) {
            console.log(val)
        })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    //节流
    <body>
        <button id="button">使劲点试试</button>
    </body>
    <script>
    function throttle(fun, time) {
        let flag = 0
        return function () {
            let now = +new Date().valueOf()
            // 当前的值 - 上一次的值 >= 传过来的事件执行时间
            if (now - flag >= time) {
                fun.apply(this, arguments)
                flag = now
            }
        }
    }
    button.onclick = throttle((e) => {
        console.log(e)
    }, 1000)
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    div水平垂直居中

    /* 方法1:flex */
    div.parent {
        display: flex;    
        justify-content: center;
        align-items: center;}
    /* 方法2:相对绝对布局 */
    div.parent {
        position: relative;}
    div.child {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);}
    /* 或者 */
    div.child {
        width: 50px;
        height: 10px;
        position: absolute;
        top: 50%;
        left: 50%;
        margin-left: -25px;
        margin-top: -5px;}
    /* 或 */
    div.child {
        width: 50px;
        height: 10px;
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
        margin: auto;}
    /* 方法3:grid布局 */
    div.parent {
        display: grid;}                 /*看这*/
    div.child {
        justify-self: center;
        align-self: center;}
    /* 方法4:inline-block */
    div.parent {
        font-size: 0;
        text-align: center;
        &::before {
            content: "";
            display: inline-block;                          /*看这*/
            width: 0;
            height: 100%;
            vertical-align: middle;}}
    div.child{
      display: inline-block;
      vertical-align: middle;}
    
    • 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

    水平居中

    • 行内元素: text-align: center
    • 块级元素: margin: 0 auto //div居中 div{width:200px;margin:0 auto;}绝对居中:position:absolute +left:50% + transform:translateX(-50%)display:flex + justify-content: center
    
    • 1
    • 2
    • 3
    • 4

    垂直居中

    • 设置line-height 等于height (line-height对单行文本的垂直居中显示)
    • position:absolute + top:50% + transform:translateY(-50%)display:flex + align - items: center
    • display:table + display:table-cell + vertical-align: middle;
    
    • 1
    • 2
    • 3
    • 4

    两栏布局

    <!-- HTML结构 -->
    <div class="wrap">
        <div class="left">
            左侧固定内容
        </div>
        <div class="right">
            右侧内容自适应
        </div>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用浮动—float

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;}
    .wrap {
        overflow: hidden;
        border: 1px solid red;}
    /* 脱离文档流 */
    .left {
        float: left;
        width: 200px;
        height: 200px;
        background: purple;
    }
    .right {
        margin-left: 200px;
        background: skyblue;
        height: 200px;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用绝对定位实现—absolute

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;
    }
    .wrap {
        overflow: hidden;
        position: relative;
    }
    /* 脱离文档流 */
    .left {
        position: absolute;
        left: 0;
        top: 0;
        width: 200px;
        height: 200px;
        background: purple;
    }
    .right {
        margin-left: 200px;
        background: skyblue;
        height: 200px;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    使用表格布局—table

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;}
    /* 表格布局 */
    .wrap {
        display: table;
        width: 100%;
    }
    .left {
        display: table-cell;
        width: 200px;
        height: 200px;
        background: purple;
    }
    .right {
        display: table-cell;
        background: skyblue;
        height: 200px;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用calc()函数

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;
    }
    /* 双float */
    .wrap {
        overflow: hidden;
    }
    .left {
        float: left;
        width: 200px;
        height: 200px;
        background: purple;
    }
    .right {
        float: left;
        background: skyblue;
        height: 200px;
        width: calc(100% - 200px);}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    使用inline-block和calc()函数

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;
    }
    /* 双float */
    .wrap {
        overflow: hidden;
        width: 100%;
        font-size: 0;
    }
    .left {
        display: inline-block;
        width: 200px;
        height: 200px;
        background: purple;
        font-size: 20px;
    }
    .right {
        display: inline-block;
        background: skyblue;
        height: 200px;
        width: calc(100% - 200px);
        font-size: 20px;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    使用弹性布局—flex

    /* 清除浏览器默认边距 */
    * {
        margin: 0;
        padding: 0;
    }
    .wrap {
        display: flex;
    }
    .left {
        height: 200px;
        background: purple;
        flex:0 0 200px
    }
    .right {
        background: skyblue;
        height: 200px;
        flex: 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    深拷贝、浅拷贝和赋值

    浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存。如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,
    深拷贝:深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
    赋值:当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据,也就是两个对象。
    浅拷贝的实现方式: 1、object.assign();2、lodash 里面的 _.clone ;3、…扩展运算符;4、 Array.prototype.concat ; 5、 Array.prototype.slice;
    深拷贝的实现方式:1、 JSON.parse(JSON.stringify());2、递归操作;3、cloneDeep;4、Jquery.extend()

    //深拷贝
    function deepClone(obj){
        if (obj == '' || typeof(obj) != 'object'){
            return obj
        }
        if (obj instanceof Set){
            const temp = new Set();
            obj.forEach((item)=>{
                temp.add(deepClone(item));
            })
            return temp
        }else if (obj instanceof Map){
            const temp = new Map();
            obj.forEach((item, key)=>{
                temp.set(key,deepClone(item));
            })
            return temp
        }else if (obj instanceof RegExp){
            const temp = new RegExp(obj)
            return temp
        }else{
            const temp = new obj.constructor();
            for (var key in obj){
                temp[key] = deepClone(obj[key])
            }
            return temp
        }
    }
    
    • 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

    类型判断

    //类型判断 方案一
    function type_of(obj){
       let res = Object.prototype.toString.call(obj).split(' ')[1];
       return res.substring(0,res.length-1).tolowerCase()
    }
    //方案二
    function type_of(obj){
       return Object.prototype.toString.call(obj).slice(0,-1).toLowercase()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    整数的千位分割

    在数字中加进一个符号,以免因数字位数太多而难以看出它的值。所以人们在数字中,每隔三位数加进一个逗号,也就是千位分隔符,以便更加容易认出数值。

    const format = (n) =>{
        let num = n.toString();
        let len = num.length;
        if(len<3){
            return num
        }else{
            let render = len % 3;
            console.log(render);
            if (render>0){
                return num.slice(0,render) + ',' + num.slice(render, len).match(/\d{3}/g).join(',');
            }else{
                return num.slice(0,len).match(/\d{3}/g).join(',');
            }
        }
    }
    let str = format(299123)
    console.log(str)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现生成一个随机数

    function random_N(min, max){
        return Math.floor(Math.random()*(max - min + 1) + min)
    }
    console.log(random_N(11,99));
    //轮询方法的封装
    const pollFectch = async(callback,pollingInterval, delayTimeStatus = false, stop = false)=>{
        if(stop) return
        const timeoutPromise = (delayTime)=> {
            return new Promise(resolve=>{
                window.timeId = setTimeout(resolve,delayTime);
            })
        }
        while(true){
            const pollingTime = 1*pollingInterval;
            const delayTime = pollingTime + (1000 * parseInt(Math.random()*40,10));
            try{
                await callback();
                await timeoutPromise(delayTimeStatus ? delayTime : pollingTime);
            }catch(e){
                await timeoutPromise(delayTimeStatus ? delayTime : pollingTime);
            }
        }
    }
    pollFectch(()=>{
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve();
                console.log(4243242342)
            },2000);
        })
    },200,false,false)//*任务队列 *轮询时间 延迟状态 停止定时器
    
    • 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

    Vue 事件机制、手写 on、off、emit、once

    Vue 事件机制 本质上就是 一个 发布-订阅 模式的实现。

    class Vue {
      constructor() {//  事件通道调度中心
        this._events = Object.create(null);}
      $on(event, fn) {
        if (Array.isArray(event)) {
          event.map(item => {
            this.$on(item, fn);
          });} else {
          (this._events[event] || (this._events[event] = [])).push(fn);}
        return this;}
      $once(event, fn) {
        function on() {
          this.$off(event, on);
          fn.apply(this, arguments);}
        on.fn = fn;
        this.$on(event, on);
        return this;}
      $off(event, fn) {
        if (!arguments.length) {
          this._events = Object.create(null);
          return this;}
        if (Array.isArray(event)) {
          event.map(item => {
            this.$off(item, fn);});
          return this;}
        const cbs = this._events[event];
        if (!cbs) {
         return this;}
        if (!fn) {
          this._events[event] = null;
          return this;}
        let cb;
        let i = cbs.length;
        while (i--) {
          cb = cbs[i];
          if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1);
            break;}
        }return this;}
      $emit(event) {
        let cbs = this._events[event];
        if (cbs) {
          const args = [].slice.call(arguments, 1);
          cbs.map(item => {
            args ? item.apply(this, args) : item.call(this);
          });}
        return this;}}
    ```### 手写call方法
    ```js
    Function.prototype._call = function(context){//context = globalThis
        if(typeof this != 'function'){
            return
        }
        that = context || window;  //如果有上下文的,就是传参者,否则就是window
        that.fn = this;
        const local = [...arguments].slice(1);
        let result = that.fn(...local)
        delete that.fn ;
        return result
    }
    let obj = {a: 1}
    function fns(a, b){
        console.log(a, b);
        console.log(this);
    }
    fns._call(obj,23,555)
    
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    手写apply方法

    Function.prototype._apply = function(context){
        if (typeof this !== 'function'){
            return
        }
        that = context ||window;
        that.fn = this;
        let result;
        if (arguments[1]){
            result = that.fn(...arguments[1]);
        }else{
            result = that.fn()
        }
        return result
    }
    let obj1 = {a: 1}
    function fn(...val){
        console.log(this);
        console.log(...val);
    }
    fn._apply(obj1,[1,4,5,6,8,9])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    手写bind方法

    Function.prototype._bind = function(context){
        if (typeof this != 'function'){
            return
        }
        const args = [...arguments].slice(1);
        fn = this
        return function Fn(){
            return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments))
        }
    }
    let obj2={a:2}
    function fn2(...val){
        console.log(this);
        console.log(...val);
    }
    fn2._bind(obj,231, 4124, 5432)()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    数组和字符串去重简单实现

    function _set(arr){
        let newArr = [];
        for (let i = 0;i < arr.length; i++){
            if (newArr.indexOf(arr[i]) == -1){
                newArr.push(arr[i]);
            }
        }
        return newArr
    }
    let aarr = [1,3,5,2,2,5,6,7,9,10]
    console.log(aarr)
    newarr = _set(aarr)
    console.log(newarr)
    console.log([...new Set([11, 11, 222, 222])])
    // 字符串去重
    let str1 = '123321你好你好'
    console.log([...new Set(str1.split(''))].join(''))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    排序-快速排序

    通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

    function quickSort(arr){
        if (arr.length <= 1){return arr}
        let n = Math.floor(arr.length/2)
        let cen = arr.splice(n,1)[0]
        let leftArr = [];
        let rightArr = [];
        for (let j = 0; j<arr.length; j++){
            let item = arr[j];
            cen > item ? leftArr.push(item):rightArr.push(item);
        }
        return fn(leftArr).concat(cen,fn(rightArr))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    排序-插入排序

    从第二个元素开始,把前面的元素序列当作已经有序的,然后找合适的位置插入。

    function insert(arr){
        let handle = [];
        handle.push(arr[0]);
        for (let i = 1; i < arr.length; i++){
            let a = arr[i];
            for(let j = handle.length - 1;j >= 0; j--){
                let b = handle[j];
                if (a > b){
                    handle.splice(j + 1, 0, a);
                    break;
                }
                if (j==0){
                    handle.unshift(a)
                }
            }
        }
        return handle
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    排序-选择排序

    在个数为n的数组中,每次选择未排序元素中的最小值,将其放在最左边。

    function choiceSort(arr){
        let temp;
        let len = arr.length;
        for (let i = 0; i < len - 1; i++){
            temp = i
            for (let j = i + 1; j < len; j++){
                if (arr[temp] > arr[j]){
                    temp = j;
                }
            }
            if (temp != i){
                [arr[temp],arr[i]] = [arr[i],arr[temp]];
            }
        }
        return arr
    }
    console.log(choiceSort([2, 8, 5, 92, 52, 1]))
    //最大值和最小值
    const array=[5,4,6,2,7,1,9];
    let minnum = array.reduce((a,b)=> a > b ? a : b);
    //reducer 逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果
    console.log(minnum)
    let maxnum = array.reduce((a,b)=> a < b ? a : b);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    排序-冒泡排序

    交换排序中一种简单的排序方法

    function sort(arr) {
            for (let i = 0; i < arr.length - 1; i++) {
                for (let j = 0; j < arr.length - 1 - i; j++) {
                    if (arr[j] > arr[j + 1]) {
                        //  当后一项小于前一项的值,则交换变量
                        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
                    }
                }
            }
            return arr
        }
        console.log(sort([10, 16, 6, 2, 48, 34]));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对象去重

    let arr7= [
        {a:1,b:2,id:42341},
        {a:1,b:2,id:42341},
        {a:3,b:4,id:77777},
        {a:4,b:8,id:88888}
    ]
    function ssff(arr11,id){
        var obj = {};
        function deWeightFour(){
            arr11 = arr11.reduce(function(a,b){
                obj[b.id] ? '' : obj[b.id] = true && a.push(b);
                return a
            }, [])
            return arr11
        }
        var newArr11 = deWeightFour();
        return newArr11
    }
    console.log(ssff(arr7, 'id'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    手写indexOf

    // 在函数前加上波浪号,其作用是把函数声明转换为表达式,
        // 就可以直接将下面的代码放入某个函数里运行。
        // 不用indexOf 和 includes
    ~ function(){
        function myIndexOf(a){
            let lena = a.length;
            y = this.length;
            flag = -1;
            for (var i = 0; i <= y - lena; i++){
                if (this.substr(i, lena) === a){
                    flag = i;
                    break;
                }
            }
            return flag
        }
        String.prototype.myIndexOf = myIndexOf;
    }()
    let demo = 'adsfuihgadfhghiwuqe';
    let str3 = 'h';
    console.log(demo.myIndexOf(str3));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    手写instanceof

    var arr=[];
    console.log(arr);
    function instance_of(){
        let classFuncPrototype = classFunc.prototype;
        proto = Object.getPrototypeOf(exmaple);
        while(true){
            if (proto === null){
                return false
            }
            if (proto === classFuncPrototype){
                return true
            }
            proto = Object.getPrototypeOf(proto)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    判断两个对象是否相等

    function compareObj(obj1,obj2){
        var flag = true;
        function compare(obj1,obj2){
            if (Object.keys(obj1).length != Object.keys(obj2).length){
                flag = false;
            }else{
                for (let x in obj1){
                    if (obj2.hasOwnProperty(x)){
                        if (obj1[x] !== obj2[x]){
                            compare(obj1[x], obj2[x])
                        }
                    }else{
                        flag = false;
                    }
                }
            }
            if (flag === false){
                return false;
            }else{
                return true;
            }
        }
        return compare(obj1, obj2)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    字符串转换成千位分割的数字

    String.prototype.toThousands = function(){
        var num = this;
        var result = [ ],counter = 0;
        num = (num || 0).toString().split('');
        for (var i = num.length - 1; i >= 0; i--){
            counter++;
            result.unshift(num[i]);
            if ((!counter % 3) && i != 0){
                result.unshift(',')
            }
        }
        return reslut.join('')
    }
    function toThousands(num){//另一种写法
        return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g,'$1');
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    判断JS对象中是否存在循环引用

    function cycle(obj,parent){
        var parentArr = parent || [obj];
        for (var i in obj){
            if (typeof obj[i] === "object"){
                parentArr.forEach((pobj)=>{
                    if(pobj ===obj[i]){
                        obj[i] = "[cycle]";
                    }
                });
                cycle(obj[i],[...parentArr,obj[i]])
            }
        }
        return obj;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    new的原理与实现

    new的通俗理解:new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,把这个空对象的原型(proto)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于);然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,那就返回这个对象,如果不是,那就返回我们创建的对象。

    new实际上是在堆内存中开辟一个空间。
       ①创建一个空对象,构造函数中的this指向这个空对象;
       ②这个新对象被执行[ [ 原型 ] ]连接;
       ③执行构造函数方法,属性和方法被添加到this引用的对象中;
       ④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。

    function _new(){
        let target = {};   //创建的新对象
        let [constructor,...args] = [...arguments];
           //执行[[原型]]连接,target是constructor的实例
        target.__proto__ = constructor.prototype;
            //执行构造函数,将属性或方法添加到创建的空对象上
        let result = constructor.prototype;
        if(result && (typeof (result) == "object" || typeof (result) == "function")){
               //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
            return result;
        }
           //如果构造函数返回的不是一个对象,返回创建的对象
        return target;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    CSS、HTML相关

    HTML5的新特性
    新特性主要是关于图像,位置,存储,多任务等功能的增加。

    绘画的 canvas画布
    用于媒介回放的因音频和视频元素 video 和 audio
    本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失
    sessionStorage 的数据在浏览器关闭后自动删除
    语意化更好的内容元素标签,比如article、footer、header、nav、section
    表单控件,calendar、date、time、email、url、search
    新的技术webworker, websocket, Geolocation
    移除的元素:
    纯表现的元素:basefont,big,center,font, s,strike,``tt,u
    对可用性产生负面影响的元素:frame,frameset,noframes
    产生混淆:acronym,applet,dir
    本地存储: localStorage 没有时间限制的数据存储; sessionStorage, session 的数据存储,当用户关闭浏览器窗口后,数据会被删除
    新事件:onresize、ondrag、onscroll、onmousewheel、onerror、onplay、onpause
    -WebSocket:建立持久通信协议,新的技术:webworker、websocket、Geolocation

    Html5新增的语义化标签、表达、表单元素

    新增的语义标签:以使开发者更方便清晰构建页面的布局
    
    标签	描述
    <header></header>	标签
    <footer></footer>	页脚
    <nav></nav>	导航标签
    <section></section>	内容区块标签
    <article></article>	页面内独立的内容区域
    <aside></aside>	侧边栏
    details	定义用户可以看到或者隐藏的额外细节
    增强型表单:
    1)html5修改一些新的input输入特性,改善更好的输入控制和验证
    
    输入类型	描述
    color	用于选取颜色
    date	选取日期
    datetime	选取日期(UTC时间)
    datetime-local	选取日期(无时区)
    month	选择一个月份
    week	选择周和年
    2)html5新增了五个表单元素
    
    名称	功能
    datalist	用户会在他们输入数据时看到预定义选项的下拉列表
    progress	进度条,展示连接/下载进度
    meter	刻度值,用于某些计量,例如温度、重量等
    keygen	提供一种验证用户的可靠方法生成一个公钥和私钥
    output	用于不同类型的输出,比如脚本输出
    3)html5新增表单属性
    
    属性	描述
    placehoder	输入框默认提示文字
    required	要求输入的内容是否可为空
    pattern	描述一个正则表达式验证输入的值
    min/max	设置元素最小/最大值
    step	为输入域规定合法的数字间隔
    
    
    • 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

    什么是rem、px、em区别。

    rem是一个相对单位,rem的是相对于html元素的字体大小,没有继承性
    em是一个相对单位,是相对于父元素字体大小有继承性
    px是一个“绝对单位”,就是css中定义的像素,利用px设置字体大小及元素的宽高等,比较稳定和精确。

    响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?

    实现方式
    1.百分比布局,但是无法对字体,边框等比例缩放
    2.弹性盒子布局 display:flex
    3.rem布局,1rem=html的font-size值的大小
    4. css3媒体查询 @media screen and(max-width: 750px){}
    5.vw+vh
    6.使用一些框架(bootstrap,vant)
    什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局
    响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的css样式 页面头部必须有meta声明的

    什么叫CSS盒模型?有哪几种盒模型?有什么区别?box-sizing属性有什么用?

    **CSS盒模型:**​ 在我们的HTML页面中,每一个元素都可以被看成一个盒子,而这个盒子由:内容(content)、内边距(padding)、 ​ 边框(border)、外边距(margin) 四部分组成.
    **盒模型分类:**分为标准盒模型和怪异盒模型一下两种。
    标准(W3C)盒模型的范围包括margin、border、padding、content,并且宽高只包含content,不包含其他部分 ​
    怪异(IE)盒模型的范围包括margin、border、padding、content,和标准盒模型不同的是,怪异盒模型的宽高包含了padding和 border
    **box-sizing作用:**用来控制元素的盒子模型的解析模式,默认为content-box ​ context-box标准盒模型 ​ border-box怪异盒模型

    为什么会出现浮动?浮动会带来哪些问题?清除浮动的方式有哪些?哪种最好?

    1、为什么会出现浮动:由于浮动元素脱离了文档流,所以文档流的块框表现得就像浮动框不存在一样。浮动元素会漂浮在文档流的块框上.
    2、浮动带来的问题:父元素的高度无法被撑开,影响与父元素同级的元素
    若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构。
    3、清除浮动的方式:
    父级div定义height
    结尾处加空div标签clear:both
    父级div定义伪类:after、before
    父级div定义overflow:hidden
    父级div定义overflow:auto。
    父级div也浮动,需要定义宽度。
    父级div定义display:table。
    结尾处加br标签clear:both
    比较好的是第3种,无需多余标签,方便维护,通过伪类就可以解决

    CSS中图片的alt和title有什么异同

    不同点: 元素的alt是表示图片加载失败显示的文本内容,而title是表示鼠标悬停图片时显示的文本内容。
    相同点: 在alt和title同时设置的时候,alt作为图片的替代文字出现,title是图片的解释文字。

    ::before和:before有何异同?

    单冒号(:)用于CSS3伪类,
    双冒号(::)用于CSS3伪元素。伪元素和伪类之所以这么容易混淆,是因为他们的效果类似而且
    伪类: 用于已有元素处于某种状态时为其添加对应的样式,这个状态是根据用户行为而动态变化的。
    例如: 当用户悬停在指定元素时,可以通过:hover来描述这个元素的状态,虽然它和一般css相似,可以为已有元素添加样式,但是它只有处于DOM树无法描述的状态下才能为元素添加样式,所以称为伪类。
    伪元素: 用于创建一些不在DOM树中的元素,并为其添加样式。
    例如: 我们可以通过:before来在一个元素之前添加一些文本,并为这些文本添加样式,虽然用户可以看见这些文本,但是它实际上并不在 DOM文档中。

    css3新增了那些特征?

    1、颜色:新增RGBA、HSLA模式
    2、文字阴影:(text-shadow)
    3、边框:圆角(border-radius)边框阴影:box-shadow
    4、盒子模型:box-sizing
    5、背景:background-size,background-origin background-clip(削弱)
    6、渐变:linear-gradient(线性渐变);radial-gradient (径向渐变)
    7、过渡:transition可实现动画
    8、自定义动画: animate@keyfrom
    9、媒体查询:多栏布局@media screen and (width:800px)
    10、border-image
    11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
    12、3D转换
    13、字体图标:font-size
    14、弹性布局:flex

    浏览器是如何渲染页面的

    浏览器将获取的HTML文档解析成DOM树。
    处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。
    将DOM和CSSOM合并为渲染树(rendering tree),代表一系列将被渲染的对象。
    渲染树的每个元素包含的内容都是计算过的,它被称之为布局layout。浏览器使用一种流式处理的方法,只需要一次绘制操作就可以布局所有的元素。

    元素居中的方式、以及垂直上下居中

    方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中
    方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用transfrom:translate(-50%,-50%)(最常用方法)
    方法三:父相子绝,子元素所有定位为0,margin设置auto自适应

    如何实现图片懒加载

    方案一:位置计算+滚动(Scroll)事件+DataSet API
    方案二:getBoundingClientRect API + Scroll with Throttle + DataSet API
    方案三:IntersectionObserver API + Dataset API
    方案四:LazyLoading属性

    style标签写在body前和body后有什么区别?

    页面加载自上而下 当然是先加载样式.
    写在body标签后由于浏览器以逐行方式对HTML文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)

    两/三栏布局(圣杯双飞翼)

    两栏布局,左边定宽,右边自适应
    三栏布局、圣杯布局、双飞翼布局
    圣杯布局和双飞翼布局是前端工程师需要日常掌握的重要布局方式。两者的功能相同,都是为了实现一个两侧宽度固定,中间宽度自适应的三栏布局。(中间先加载渲染)

    首先要给两侧设置padding预留出相应的空间
    随后分别为三列设置宽度与浮动,同时对footer设置清除浮动
    根据浮动的特性,由于center的宽度为100%,即占据了第一行的所有空间,所以left和right被“挤”到了第二行。
    接下来的工作是将left放置到之前预留出的位置上,这里使用负外边距
    这里使用position: relative和right: 200px将left的位置在原有位置基础上左移200px,以完成left的放

    常见的块级、行级、空元素

    在CSS中规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,比如div默认display属性值为“block”,成为“块级”元素;span默认display属性值为“inline”,是“行内”元素。
    我们在平常的项目中经常使用到的有:

    行内元素有:span a b i img input select strong
    块级元素有:div p h1-h6 ul table form ul ol li dl dt等
    空元素(没有内容):


    html和XML

    html被称为超文本标记语言, 是一种描述性语言,用html 可以创建能在互联网上传输的信息页,是构成网页文档的主要语言,它是由很多的标签组成
    xml 即可扩展标记语言,是Internet环境中跨平台的、依赖于内容的技术,是当前处理结构化文档信息的有力工具,满足了Web内容发布与交换的需要,适合作为各种存储与共享的通用平台。
    都可以通过DOM 变成方式来访问。
    都可以通过CSS来改变外观。
    html和xml 都是标记语言,都是基于文本编辑和修改的。
    xml不是要来取代html的,是对html的补充,用来与html协同工作的语言,基于上面这些优势,xml将来成为所有的数据处理和数据传输的常用工具非常可观。

    毛玻璃效果

    background: rgba(244, 243, 244, 0.18);
        box-shadow: 0 0.3px 0.7px rgba(0, 0, 0, 0.126),
          0 0.9px 1.7px rgba(0, 0, 0, 0.179), 0 1.8px 3.5px rgba(0, 0, 0, 0.224),
          0 3.7px 7.3px rgba(0, 0, 0, 0.277), 0 10px 20px rgba(0, 0, 0, 0.4);
        backdrop-filter: blur(10px);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    语义化的好处

    开发中,语义化让页面结构更加清晰,便于后期的维护,便于浏览器,搜索引擎解析;利于搜索引擎的爬取,利于seo。

    浏览器兼容性的问题

    在不同的浏览器中,浏览器的内核都是不相同的,所以各个浏览器对网页的解析存在一定的差异。
    简单来说就是写的代码在各个浏览器上显示不同的效果
    解决:
    1、css3新属性,加浏览器前缀兼容早期浏览
    -moz- 火狐浏览器
    -webkit- Safari, 谷歌浏览器等使用Webkit引擎的浏览器
    -o- Opera浏览器(早期)
    -ms- IE
    2、css hack解决浏览器兼容性不同浏览器,识别不同的样式,css hack本身就是处理浏览器兼容的
    3、图片默认有间距:
    几个img标签放在一起的时候,有些浏览器会有默认的间距,通配符清除间距也不起作用。 可以通过使用float属性为img布局(所有图片左浮)
    4、不同浏览器的标签默认的 margin 和 padding 不同
    解决办法:可以通过设置全局样式来解决这个问题,这样所有的样式都会重置,初始值都会相同了。

    CSS雪碧图/精灵图(Sprites)

    雪碧图不属于图片格式,而是一种图片应用形式。

    雪碧图又叫精灵图,因为 Sprites 的原因叫 “雪碧”,出现的原因是随着网速的提升,同时因为请求次数过多的时候会卡网页,所以我们就将 N 张小图集成到一张大图上,从而提升页面打开的速度。这种多张小图放在一张大图上的操作,就叫做精灵图(雪碧图)。

    格式 使用场景(特点)
    JPG/JPEG 1. 大的背景图; 2. 轮播图; 3. Banner 图
    PNG 1. 小 Logo; 2. 透明背景
    GIF 动态图片
    SVG 能适应不同设备且画质不能损坏的图片
    Base64 大小不超过 2KB,且更新率低的图片
    雪碧图 小图太多的时候,集中成一张图片减少 HTTP 请求
    1、png是便携式网络图片(Portable Network Graphics)是一种无损数据压缩位图文件格式.优点是:压缩比高,色彩好。 大多数地方都可以用。
    2、jpg是一种针对相片使用的一种失真压缩方法,是一种破坏性的压缩,在色调及颜色平滑变化做的不错。在www上,被用来储存和传输照片的格式。
    3、gif是一种位图文件格式,以8位色重现真色彩的图像。可以实现动画效果.
    4、webp格式是谷歌在2010年推出的图片格式,压缩率只有jpg的2/3,大小比png小了45%。缺点是压缩的时间更久了,兼容性不好,目前谷歌和opera支持。

    重绘和重排(回流)

    重绘:中的元素外观(如:颜色)发生改变,不影响布局时,产生重绘
    重排(回流):元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变时,或者几何属性需要改变时,产生重重排。
    重排负责元素的几何属性更新,重绘负责元素的样式更新
    重排必定会发生重绘,但重绘不一定会引发重排。(比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。)
    ​ 当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。
    区别:回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流,当页面布局和几何属性改变时就需要回流

    如何避免重绘重排

    1、通过class集中改变样式;2、缓存布局信息;3、将position属性设置为absolute或fixed,因为position属性为absolute或fixed的元素回流开销比较小,不用考虑它对其他元素的影响。
    js 中:尽量避免频繁使用:offsetTop、offsetLeft、offsetWidth、offsetHeight、width、height、getComputedStyle()。
    css中:

    使用 transform 替代 top
    使用 visibility 替换 display: none,因为visibility只会引起重绘,但是display: none会引发重排
    避免使用CSS表达式,可能会引发回流,如果要写就写简写(如:用 border 代替 border-width, border-style, border-color)。

    display: none和visible: hidden

    相同点:display:none、visible:hidden和opacity:0都能把网页上某个元素隐藏起来。
    区别
    1、 display:none :隐藏后的元素不占据任何空间,即该对象在页面上彻底消失。通俗来说就是看不见也摸不到。
    visible:hidden: 对象在网页上不可见,但隐藏后的元素空间依旧保留,但是仍然会影响页面布局。通俗来说就是看不见但摸得到。
    2、visibility: hidden:将元素隐藏,但是在网页中该占的位置还是占着。
    display: none:将元素的显示设为无,即在网页中不占任何的位置。
    3、visibility具有继承性,如果给父元素设置visibility:hidden,子元素也会继承这个属性。但是如果重新给子元素设置visibility: visible,则子元素又会显示出来。但display: none后该元素以及它的所有后代元素都会隐藏。
    质的区别
    4、CSS3的transition支持visibility属性,但是并不支持display。

    块级格式化上下文(BFC)

    BFC 块级格式化上下文 一块独立的区域,有自己的规则,其中的元素与外界的元素互不影响
    触发BFC:

    float的值left或right
    overflow的值不为visible(默认)
    display的值为inline-block、table-cell、table-caption
    position的值为absolute(绝对定位)或fixed固定定位
    规则:
    1、BFC的区域不会与float box重叠。
    2、BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
    3、计算BFC的高度时,浮动元素也会参与计算。
    4、内部的Box会在垂直方向上一个接一个放置。
    5、Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。
    BFC的应用
    1、可以用来自适应布局
    利用BFC的这个原理可以实现两栏布局,左边定宽,右边自适应。不会相互影响,哪怕高度不相等。
    给左边盒子加浮动,右边盒子加overflow:hidden;变成BFC,就可以消除外部左边盒子因浮动对他的影响
    2、可以清除浮动
    一个父元素中的子元素,设置浮动时,父元素没有设置高度,这时子元素脱离文档流,父元素感知不到子元素的高度,造成父元素的塌陷。 这时候给父元素添加overflow:hidden / auto,变成BFC就可以解决这种问题。

    解决垂直边距重叠

    1.父子关系的边距重叠
    父子关系,如果子元素设置了外边距,在没有把父元素变成BFC的情况下,父元素也会产生外边距。
    解决办法: 是给父元素添加一个 overflow:hidden,这样父元素就变为BFC,不会随子元素产生外边距
    2.同级兄弟关系的重叠
    同级元素在垂直方向上外边距会出现重叠现象,最后外边距的大小取两者绝对值较大的
    可通过添加一个空元素或伪类元素,设置overflow:hidden;解决

    什么是渐进增强优雅降级

    渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后在针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
    优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

    CSS优化、提高性能的方法有哪些?

    避免过度约束、避免后代选择符、避免链式选择符、使用紧凑的语法、避免不必要的命名空间、避免不必要的重复、最好使用表示语义的名字。、避免!important。

    CSS特性:继承性,层叠,优先级

    1、继承性:子标签会继承父标签的某些样式,如文本颜色和字号。(text- font- color)
    2、层叠性:样式冲突,遵循的原则是就近原则。
    3、优先级:定义CSS样式时,经常出现两个或更多规则应用在同一元素上,此时,谁的权重高显示谁的样式。(选择器相同,则执行层叠性;选择器不同,就会出现优先级的问题。)
    !Important > 行内式 > id > 类/伪类/属性 > 标签选择器 > 全局
    (对应权重:无穷大∞>1000>100>10>1>0)

    CSSz中定位有哪几种?分别举例?Z-index熟悉在使用的时候注意什么 ?

    static: 默认值 没有定位,元素出现在正常的流中
    relative(相对定位):生成相对定位的元素,相对于其正常(原先本身)位置进行定位
    absolute(绝对定位):生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位
    fixed(固定定位):生成绝对定位的元素,相对于浏览器窗口进行定位
    sticky 粘性定位 当前元素设置了粘性定位,滚动到顶部就会吸附顶部,往下滑还回到原来位置。
    z-index规则
    1、值可以是正整数、负整数或0,数值越大,盒子越靠上;
    2、如果属性值相同,则按照书写顺序,后来居上;
    3、数字后面不能加单位。
    4、z-index 只能应用于相对定位、绝对定位和固定定位的元素,其他标准流、浮动和静态定位无效

    用CSS画三角形

    首先,需要把元素的宽度、高度设为0。然后设置边框样式为透明,代码如下:

    div{
        width: 0;
        height: 0;
        border-top: 40px solid transparent;
        border-left: 40px solid transparent;
        border-right: 40px solid transparent;border-bottom: 40px solid #ff0000;
    }
     div{/*方法2*/
          width: 0;
          height: 0;
          border: 100px solid transparent;
          border-bottom-color:red;
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    JS相关

    JS的数据类型

    基本数据类型,ES5:Null,undefined,Boolean,Number,String。 ES6新增的:Symbol表示独一无二的值 。ES10新增:BigInt 表示任意大的整数
    引用数据类型: Object。包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。

    null 和 undefined 的区别

    相同:在 if 语句中, null 和 undefined 都会转为false,两者用相等运算符比较也是相等。
       但是null会被隐式转换成0,很不容易发现错误。设计的时候先有null,后有undefined,undefined是为了填补之前null的坑。
       具体区别: 。JavaScript的最初版本是这样区分的: null是一个表示"无"的对象(空对象指针),转为数值时为值0,undefined 转为数值时为NaN。

    ES10新增的大整数类型BigInt

    BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。
       此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。 BigInt目前是第3阶段提案, 一旦添加到规范中,它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型:
    要创建BigInt,只需在整数的末尾追加n即可。比较:

    console.log(9007199254740995n); // → 9007199254740995n
    console.log(9007199254740995); // → 9007199254740996
    或者,可以调用BigInt()构造函数

    BigInt(“9007199254740995”); // → 9007199254740995n
    // 注意最后一位的数字
    9007199254740992 === 9007199254740993; // → true
    console.log(9999999999999999); // → 10000000000000000

    什么是数据类型存储、堆栈内存

    基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。指的是保存在栈内存中的简单数据段;

    引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。

    引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值。

    堆(heap)和栈(stack)存储机制有什么区别

    : 是一种连续储存的数据结构,具有先进后出后进先出的性质。通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。

    : 是一种非连续的树形储存数据结构,具有队列优先,先进先出; 每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。

    js中的数据类型转换

    在JavaScript中类型转换有三种情况:
       转换为数字:Number(),parseInt(),parseFloat()方法。Number()可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回NaN。parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix是2-36之间的整数,表示被解析字符串的基数。parseFloat(string):解析一个参数并返回一个浮点数。
       转换为字符串:.toString()或String()方法。但是null,undefined不能调用,没有.toString方法
       转换为布尔值:Boolean()方法, 还有隐式转换。0, ‘’(空字符串), null, undefined, NaN会转成false,其它都是true。

    数据类型相比较(objected .is、=

    Object.is()在 = = = === ===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等。
    = = = === =属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以=这种方式的比较,除非两者存储的内存地址相同才相等,反之false。
    = = == == 等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会隐式使用valueOf转换后在进行判断。

    addeventlistener 监听事件

    事件类型分两种:事件捕获、事件冒泡。
       事件捕获就是:网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。

    事件冒泡:IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了。

    阻止事件冒泡,可以提高性能,减少了事件绑定,从而减少内存占用。方法:

    event.stopPropagation() .stop修饰符

    addEventListener(‘click’,函数名,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获。

    Javascript 的作用域和作用域链

    作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域。 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。全局作用域就是Js中最外层的作用域,在哪里都可以访问。函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套。其中一个内部属性是作用域,包含了函数被创建的作用域中对象的集合,称为函数的作用域链

    object的方法

    Object.is(value1, value2),是一种判断两个值是否相同的方法。value1:要比较的第一个值。value2:要比较的第二个值。返回值:一个布尔表达式,指示两个参数是否具有相同的值。

    Object.assign(target, …sources) 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。返回值:修改后的目标对象。

    Object.entries(obj) 把对象转成键值对( [key, value] )数组。obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
    Object.fromEntries则相反,是把键值对数组转为对象

    Object.values(obj) 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for…in 循环提供的顺序相同。obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。

    Object.prototype.hasOwnProperty(prop)方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。prop:要测试的属性的字符串名称或符号。返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。

    Object.keys(obj) 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。obj:要返回可枚举自身属性的对象。返回值:表示给定对象的所有可枚举属性的字符串数组。

    Object.prototype.toString()方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。返回值:表示对象的字符串。

    Object.freeze(obj)方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。obj:要冻结的对象。返回值:传递给函数的对象。

    const me = Object.create(person)方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。propertiesObject可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
    返回值:一个新对象,带着指定的原型对象和属性。

    对象和面向对象

    对象:属性和方法的集合叫做对象(万物皆对象)。

    面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。

    创建对象的方式(4种):new Object、字面量、构造函数、原型。

    数组(Array)的方法

    1、sort():sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
    2、splice():向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
    3、pop():从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
    4、push():向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
    5、unshift():向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
    6、shift():从头部删除一个元素 返回被删除掉的元素,改变原有数组。
    7、reverse( ): 原数组倒序 它的返回值是倒序之后的原数组
    8、concat( ):数组合并。
    9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
    10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了
    11、toString( ):数组转字符串;
    12、toLocaleString( ):将数组转换为本地数组。
    13、forEach( ):数组进行遍历;
    14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
    15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
    16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
    17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
    18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。
    19.isArray() 判断是否是数组。
    20. indexOf 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
    21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
    22. Array.of() 填充单个值
    23. Array.from()来源是类数组
    24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 , 第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
    find 查找这一组数 符合条件的第一个数 给他返回出来
    findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来 没有返回 -1
    keys 属性名 values属性值 entries属性和属性值
    forEach 循环遍历 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素

    可以改变数组的API:pop() push() 、shift() 、unshift() 、sort([func]) 、reverse() 、splice(pos,deleteCount,…item) 、copyWithin(pos[, start[, end]]) 、arr.fill(value[, start[, end]])

    ES6数组的常用方法:

    1、Array.from( ):将对象或字符串转成数组,注意得有length。
    2、Array.of( ): 将一组值转换为数组。
    3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
    target:从该位置开始替换数据;
    start:从该位置开始读取数据,默认为0;
    end:到该位置停止数据的读取,默认为数组的长度
    4、find( ):用于找出第一个符合条件的数组成员。
    5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
    6、fill(value,start,end):使用给定值,填充一个数组。
    value:填充的值;
    start:开始填充的位置;
    end:填充结束的位置。
    7、keys( ):对键名的遍历。
    8、values( ):对键值的遍历。
    9、entries( ):对键值对的遍历。
    10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
    11、flat( ):用于数组扁平,数组去除未定义。可以去除空项。
    12、flatMap( ):对原数组的每个成员执行一个函数。
    13、Map( ):是一组键值对的结构,具有极快的查找速度。
    14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

    字符串的方法

    1、chartAt( ):返回在指定位置的字符;
    2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
    3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
    4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
    5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
    6、substr( ):从起始索引号提取字符串中指定数目的字符;
    7、substring( ):提取字符串中两个指定的索引号之间的字符;
    8、toLowerCase( ):字符串转小写;
    9、toUpperCase( ):字符串转大写;
    10、valueOf( ):返回某个字符串对象的原始值;
    11、trim( ):删除字符串两边的空格;
    12、trimeState 取出开始的空格
    13、trimeEnd 去除末尾空格
    14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
    15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
    16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
    17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
    18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
    19、replace() 把指定的字符串替换成为别的字符

    JavaScript声明函数的几种方式

    函数声明 function 函数名(参数1,参数2,…){ //要执行的语句 }
    函数表达式:
       var func2=function(b){}//函数表达式
       var func3=function func4©{}//命名式函数表达式
       var func5=(function(n1,n2){})();//立即执行的函数表达式
       return function(){ };//作为返回值的函数表达式
    Function构造器
    var 变量名 = new Function(“参数1”,“参数2”,…,“参数n”,“函数体”);
    立即执行函数:var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()
    函数声明与函数表达式的区别:函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。
    立即执行函数作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。
    立即执行函数使用场景: ①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。 ②所有的这些工作只需要执行一次,比如只需要显示一个时间。③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中, 不会让任何变量泄露成全局变量。

    arguments 的对象是什么

    arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。
       取之可以用 展开运算符来 数组和类数组类数组: ①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法; ③类数组是一个普通对象,而真实的数组是Array类型。
    常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($(“div”));

    this指向的问题-重点

    在全局的环境下this是指向window 的。普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window
    在对象里调用的this,指向调用函数的那个对象。
    在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象。
    方法中的this谁调用就指向谁。
    箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this。

    对函数式编程的理解

    函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。 目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。

    闭包是什么,有什么优缺点

    闭包:一个函数加上到创建函数的作用域的链接,关闭了函数的自由变量,一般就是指函数包裹着函数。
    **优点:**闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理
    **缺点:**由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。在较低版本的IE中,可能导致内存泄露。
    解决方法:在退出函数之前,将不使用的局部变量全部删除或者清空。
    闭包使用场景 : 防抖、节流、函数套函数避免全局污染。
    闭包原理:函数执行分成两个阶段(预编译阶段和执行阶段)。

    1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
    如果已存在“闭包”,则只需要增加对应属性值即可。
    2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量

    方法apply、call、bind的封装与区别

    都是来改变this指向和函数的调⽤,实际上call与apply的功能是相同的,只是两者的传参方式不一样,
    apply方法跟⼀个 数组作为参数,call⽅法和apply使⽤后就直接调⽤。
    call⽅法跟的是⼀个参数列表。call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候)后期开发 call 多多一点。call 用扩展运算符就可以吧 apply 来代替。
    bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调⽤。
    bind 返回的函数可以作为构造函数吗?不可以,会报错, ERROR > Uncaught TypeError: s is not a constructor

    什么是函数柯里化(卡瑞化、加里化)

    我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。
    概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。
    特点:①接收单一参数,将更多的参数通过回调函数来搞定;②返回一个新函数,用于处理所有的想要传入的参数;③需要利用call/apply与arguments对象收集参数;④返回的这个函数正是用来处理收集起来的参数。
    作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。
    用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。

    // 简单的相加函数
    var add = function (x,y) {
        return x + y
    }
    add(1,2)// 调用:
    var add = function (x) { //柯里化函数(闭包)
        return function (y) {
            return x + y
        }
    }
    add(1)(2)// 调用:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    JS的垃圾回收机制和内存机制

    **垃圾回收:**浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
    **内存泄露:**如果那些不去清除不再使用的变量的话就会造成内存泄漏。内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
    存在的泄漏:闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。;DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收;Times计时器泄露。

    JS的运行机制

    js单线程:JavaScript语言的一大特点就是单线程,即同一时间只能做一件事情。
    js事件循环:js代码执行过程中会有很多任务,这些任务总的分成两类:同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务,再执行宏任务(先放到事件循环中)。
    微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听MutationObserver、async/await.
    宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。
    首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。

    setTimeout(function() {//任务执行顺序通过代码理解
      console.log(1)
    }, 0);
    new Promise(function(resolve, reject) {
      console.log(2);
      resolve()
    }).then(function() {
      console.log(3)
    });
    process.nextTick(function () {
      console.log(4)
    })
    console.log(5)//输出结果:25431
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    JS预解析(变量提升)导致了什么问题

    JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。 变量提升的表现是,在变量或函数声明之前访问变量或调用函数而不会报错。
    原因 JavaScript引擎在代码执行前有一个解析的过程(预编译),创建执行上线文,初始化一些代码执行时需要用到的对象。 当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性, 它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
    首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
    1.在解析阶段 JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来, 变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似, 不过函数执行上下文会多出this、arguments和函数的参数。
    全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments
    2.在执行阶段,就是按照代码的顺序依次执行。
    进行变量提升的原因:1)提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。2)容错性更好。变量提升可以在一定程度上提高JS的容错性。

    服务端渲染SSR

    解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。
    优点:
    ①首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件;
    ②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本
    ③可以生成缓存片段、节能;
    缺点:用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改;
    使用场景:vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。

    事件循环与事件队列Event Loop 、Event Queue

    在js中我们经常需要同时执行很多件任务,例如,定时器,事件。异步数据,而js是单线程的原因不能同时进行很多件事情,必须等上一件任务执行完了才会执行下一个,需要通过Event Loop 来处理很多任务的执行
       因为js是单线程的,代码执行的时候,将不同的函数执行上下文压入到栈中进行有序的执行,在执行同步代码的时候,如果遇到了异步事件,js引擎并不会一直等待其返回结果,就是将它挂起,继续执行栈中其他的任务。当同步任务执行完了,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。。任务队列分为的宏任务队列和微任务队列,当前的执行栈中执行完,js引擎会首先判断微任务队列是否有任务可以执行有的话,放到栈中执行。当微任务队列中的任务执行完了再去判断宏任务中的队列。

    JavaScript操作浏览器对象模型BOM

    BOM提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计Web页面的能力。
    window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;
    history : go(参数) , back() , foward() ;
    location : herf属性.
    1、window.location.href = ‘你所要跳转到的页面’; 2、window.open('你所要跳转到的页面’); 3、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 5、history.go(“baidu.com”);

    Math对象的方法和属性有哪些

    Math.PI //圆周率
    Math.floor() //向下取整
    Math.ceil() //向上取整
    Math.round() //四舍五入版 就近取整
    Math.abs() //绝对值
    Math.max()/Math.min() //求最大和最小值
    Math.random() //获取范围在[0,1)内的随机值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    cookies、sessionStorage 、localStorage 的区别

    cookie:一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie
    localStorage:5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信
    **sessionStorage:**关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。

    DOM的相关操作

    //拿到指定节点
    var id = document.getElementById("id");  //返回带有指定id的元素
    var name = document.getElementByTagName("li"); //返回带有指定标签的元素
    var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。
    //创建DOM节点
    var node = document.createElement("div");
    var attr = document.createAttribute("class");
    var text = document.createTextNode("试一下");`
     插入DOM节点
    node.appendChild(text) //插入新的子节点
    node.insertBefore(pre,child) //在node元素内child前加入新元素`
    // 删除DOM节点
    node.removeChild(text) //从父元素删除子元素节点
    // 修改DOM节点
    node.setAttribute("class","name") //修改设置属性节点
    node.replaceChild(pre,child)  //父节点内新子节点替换旧子节点`
    // 常用DOM属性
    node.innerHtml  //获取/替换元素内容
    node.parentNode  //元素节点的父节点
    node.parentElement  //元素节点的父元素节点(一般与Node节点相同)
    node.firstChild  //属性的第一个节点
    node.lastChild   //属性的最后一个节点
    node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
    node.nextElementSibling //节点元素后的兄弟元素节点
    node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
    node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
    node.childNodes  //元素节点的子节点(空格,换行默认为文本节点)
    node.children    //返回当前元素的所有元素节点
    node.nodeValue   //获取节点值
    node.nodeName    //获取节点名字
    node.attributes  //元素节点的属性节点
    node.getAttribute("age")  //元素节点的某个属性节点
    node.style.width = "20px"  //设置css样式`
    //常用的api
    offsetLeft/offsetTop     //offset经常用于获得元素位置 
    clientWidth/clientHeight //client经常用于获取元素大小 
    scrollTop/scrollLeft    //scroll 经常用于获取滚动距离
    
    • 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

    什么是函数式编程、命令式编程、声明式编程

    声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。 如:css, 正则表达式,sql 语句,html, xml…
    命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。如: for()
    函数式编程:把运算过程尽量写成一系列嵌套的函数调用。如 : forEach()

    ES6 的新特性

    新增了块级作用域(let,const)

    提供了定义类的语法糖(class)

    新增了一种基本数据类型(Symbol)

    新增了变量的解构赋值

    函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数

    数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法

    对象和数组新增了扩展运算符

    ES6 新增了模块化(import/export)

    ES6 新增了 Set 和 Map 数据结构

    ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

    ES6 新增了生成器(Generator)和遍历器(Iterator)

    require、import区别

    1、import是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。

    2、require 定义模块:module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。

    require与import的区别

    1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;

    2,require是运行时加载,import是编译时加载;

    3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;

    4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;

    5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;

    6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。

    箭头函数

    js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数。

    箭头函数是匿名函数不能作为构造函数,不能使用new。
    箭头函数不绑定arguments,取而代之用rest参数…解决。
    this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this。
    箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响。
    箭头函数没有prototype(原型),所以箭头函数本身没有this。
    箭头函数不能当做Generator函数,不能使用yield关键字。
    写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁。
    箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。

    简述 let const var 的区别 以及使用场景

    var let 是用来声明变量的,而const是声明常量的 var
    1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
    2、一个变量可多次声明,后面的声明会覆盖前面的声明
    3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的
    let
    1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区
    2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面
    3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
    const
    1、const声明一个只读的常量,声明后,值就不能改变
    2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。
    3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
    4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生

    什么是promise

    1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。是
    2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列
    3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
    4、Promise 对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了
    5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获
    promise 的 then 为什么可以支持链式调用
    promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用

    async/await的实现原理-重点

    函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
    await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用。
    await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法。
    如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错。await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行

    async function fn() {
        console.log(1);
        var result = await new Promise(function(resolve, reject) {
            setTimeout(function(){
                resolve(2);
            }, 2000);
        });
    	console.log(result);
        console.log(3);
        console.log(await 4); // 4 会被直接返回
    }
    fn();//1 2(2s后输出) 3 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    HTTP相关

    HTTP请求报文结构

    一个HTTP请求报文由四个部分组成:请求行、请求头部、空行、请求体。
    **1、请求行:**由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔(GET /data/info.html HTTP/1.1)
    1)方法字段:就是HTTP使用的请求方法,比如常见的GET/POST(1.0中的三种和1.1中新增的六种)
    2)URL: URI是一个统一资源标识符,它标识了请求所针对的资源。
    3)协议版本:协议版本旨在允许发送方指示消息的格式和理解后续HTTP通信的能力。

    **2、请求头部:**允许客户端向服务器传递关于请求的附加信息(可选值)。常见请求头部如下:

    请求头字段 含义
    Accept 浏览器可接受的MIME类型
    Content-Type 表示数据属于什么MIME类型。默认为text/plain纯文本格式
    Accept-Encoding 客户端能够进行解码的数据编码方式,如gzip
    User-Agent 包含发出请求的用户信息,客户端类型
    Cookie 客户机通过这个头可以向服务器带数据,这是最重要的请求头信息之一
    Content-Length 表示请求消息正文的长度(对于post请求来说是必须的)
    **3、空行:**它的作用是通过一个空行,告诉服务器请求头部到此为止

    **4、请求数据:**1)若方法字段是GET,则此项为空,没有数据
    2)若方法字段是POST,则通常来说此处放置的就是要提交的数据

    HTTP响应报文结构

    一个HTTP请求报文由四个部分组成:响应行、响应头、空行、响应体
    **1、相应行:**响应行一般由协议版本、状态码及其描述组成 (HTTP/1.1 200 OK)
    1)协议版本:协议版本旨在允许发送方指示消息的格式及其理解后续HTTP通信的能力。
    2)状态码:一个3位整数结果码,用于向客户端返回操作结果。
    3)原因短语:旨在对状态码进行简短的文本描述,帮助理解。
    **2、响应头:**允许服务器传递关于响应的附加信息,这些头部字段提供了关于服务器的相关信息以及URI所标识资源的信息

    响应头字段 含义
    Allow 服务器支持哪些请求方法(如GET、POST等)
    Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型
    Content-Length 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据
    Content- Type 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html
    Date 当前的GMT时间,例如,Date:Mon,31Dec200104:25:57GMT
    Location 这个头配合302状态码使用,用于重定向接收者到一个新URI地址
    **3、空行:**它的作用是通过一个空行,告诉客户端响应头部到此为止
    **4、响应体:**响应的消息体,如果客户端请求的数据类型是纯数据就返回纯数据,如果请求的是HTML页面,则返回的就是HTML代码,如果是JS就是JS代码HTTP 和 TCP 区别

    HTTP 是应用层协议,定义的是传输数据内容的规范;而 TCP 是传输层协议,定义的是数据传输和连接方式的规范。
    HTTP 协议中的数据是利用 TCP 协议传输的,所以支持 HTTP 的也一定支持 TCP。

    HTTP 和 TCP 区别

    HTTP 是应用层协议,定义的是传输数据内容的规范;而 TCP 是传输层协议,定义的是数据传输和连接方式的规范。
    HTTP 协议中的数据是利用 TCP 协议传输的,所以支持 HTTP 的也一定支持 TCP。

    TCP和UDP的区别

    在这里插入图片描述
    TCP 是面向连接的,UDP 是无连接的即发送数据前不需要先建立链接。
    TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。并且因为 TCP 可靠,面向连接,不会丢失数据因此适合大数据量的交换。
    TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
    TCP 只能是 1对1 的,UDP 支持 1对1,1对多。
    TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
    TCP 是面向连接的可靠性传输,而 UDP 是不可靠的。

    UDP连接

    UDP是基于 IP 之上开发能和应用打交道的协议。
    UDP 中一个最重要的信息是端口号,端口号其实就是一个数字,每个想访问网络的程序都需要绑定一个端口号。
    通过端口号 UDP 就能把指定的数据包发送给指定的程序了,所以通过 IP 地址信息把数据包发送给指定的电脑,而 UDP 通过端口号把数据包分发给正确的程序。

    TCP连接

    TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。
    在一个 TCP 连接中,会有 3 个过程。建立连接、传输数据、断开连接。

    三次握手

    三次握手:
    第一次:建立连接时,客户端发送syn包到服务器,等待服务端确认
    第二次:服务器收到syn包,必须确认客户的syn,同时也发送一个syn包,即syn+ACK包
    第三次:客户端收到服务器的syn和ack包,向服务器发送确认包ack,发送完毕,客户端和服务端连接成功,完成三次握手
    客户端发送到服务器。客户端发送 SYN 报文给服务器,并且指明客户端初始化序列号为 ISN©,即以 SYN=1, seq=x 的形式发送过去。此时客户端处于 SYN_SEND 状态。
    服务器发送给客户端。服务器收到客户端的 SYN 和 ISN©,也发送一个 SYN 回去,同时设置 ACK = ISN© + 1 以及指明服务器初始化序列号为 ISN(s),即以 SYN=1, ACK=x+1, seq=y 的形式发送给客户端。
    客户端发送到服务器。客户端收到服务器发送的消息后,设置 ACK = ISN(s) + 1,将自身的 ISN© + 1,即以 ACK=y+1, seq=x+1的形式发送给服务器。此时客户端处于 ESTABLISHED 阶段,服务器收到报文,也处于 ESTABLISHED 阶段,双方建立了连接。
    三次握手的目的:
    客户端发送数据给服务器,服务器确认自己可以接受客户端的请求。
    服务器发送数据给客户端,客户端确认可以发送数据给服务器,也可以接受到服务器的请求。
    客户端发送数据给服务器,服务器确认自己可以发送数据给客户端。
    可否携带数据:
    答案:第三次握手的时候可以携带,第一第二次不可以携带。
    原因:如果第一次可以携带数据的话,有可能是恶意攻击服务器。这时候释放大量的数据,不理会服务器的的承受能力,让服务器花费很多时间、内存空间接收报文。
    第三次握手的时候,客户端处于 ESTABLISHED 状态了,它可以建立连接并且知道服务器的接收、发送能力是正常的,所以可以携带数据了。

    四次挥手

    四次挥手:
    第一次:浏览器发送完数据后,发送fin请求断开连接
    第二次:服务器发送ack到客户端,确认客户端的断开请求
    第三次:服务器请求断开fin的请求
    第四次:客户端确认服务器的断开ack

    客户端发送给服务器。客户端以 FIN=1, seq=u 的形式发送给服务器,表示需要关闭客户端和服务器的数据传输。此时客户端处于 FIN_WAIT 状态。
    服务器发送给客户端。服务器收到信息,先返回 ACK 给客户端,即以 ACK=1, seq=v, ack=u+1 的形式返回给客户端,表示收到客户端报文了。此时服务器处于 CLOST_WAIT 状态。
    服务器发送给客户端。服务器等待一会,看客户端还有没有数据没发过来,等处理完这些数据之后,也想断开连接了,于是发送 FIN 给客户端,即以 FIN=1, ACK=1, seq=w, ack=u+1 的形式发送给客户端。此时服务器处于 LAST_ACK 状态。
    客户端发送给服务器。客户端收到 FIN 之后,返回 ACK 报文作为应答,即以 ACK=1, seq=w+1 的形式发送给服务器。此时客户端处于 TIME_WAIT 状态。

    HTTP常见状态码

    1xx Informational(信息状态码) 接受请求正在处理
    2xx Success(成功状态码) 请求正常处理完毕
    3xx Redirection(重定向状态码) 需要附加操作已完成请求
    4xx Client Error(客户端错误状态码) 服务器无法处理请求
    5xx Server Error(服务器错误状态码) 服务器处理请求出错

    101 Switching Protocols:在 HTTP 升级为 WebSocket 的时候,如果服务器同意变更,就会发送状态码为 101。
    200 OK:请求成功状态码,响应体中含有数据。
    204 No Content:含义同 200,但是响应报文不含实体的主体部分。
    206 Partial Content:表示部分内容请求成功。使用场景为 HTTP 分块下载和断点续传,当然也会带上相应的响应头字段 Content-Range。
    301 Move Permanently:永久重定向。HTTP 升级 HTTPS,之前站点再也不用,那就是 301。
    302 Found:临时重定向。当前站点暂时不可用,那就是 302,后续可能换回来。
    304 Not Modified:当命中协商缓存时会返回这个状态码。
    400 Bad Request:请求无效。通常为前后端数据格式不一致或者其他原因。
    403 Forbidden:服务器已经得到请求,但是拒绝执行,比如没权限、法律禁止等。
    404 Not Found:资源未找到,服务器不存在对应的资源。
    500 Internal Server Error:服务器报错,有些时候可以在 Response 看到后端 PHP 等技术的报错信息等。
    502 Bad Gateway:服务器正常,但是访问出错。
    503 Service Unavailable:服务器繁忙或者停机维护,暂时无法处理请求。

    Http和Https区别

    1.HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
    2.HTTP 是不安全的,而 HTTPS 是安全的
    3.HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
    4.在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
    5.HTTP 无法加密,而HTTPS 对传输的数据进行加密,证的网络协议,安全性高于HTTP协议。
    6.HTTP无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书,一般免费证书少,因而需要一定费用。

    HTTPS的工作原理

    浏览器请求 URL,找到服务器,向服务器发送请求。服务器将自己的证书(包含服务器公钥)、对称加密算法种类以及其他相关信息返回给浏览器。
    浏览器检查 CA 证书是否可依赖,确认证书有效。
    如果不是,给服务器发警告,询问是否可以继续使用。
    如果是,浏览器使用公钥加密一个随机对称秘钥,包含加密的 URL 一起发送给服务器。
    服务器用自己的私钥解密浏览器发送的钥匙,然后用这把对称加密的钥匙给浏览器请求的 URL 连接解密。
    服务器用浏览器发送的对称钥匙给请求的网页加密,浏览器使用相同的钥匙就可以解密网页。
    对称加密:
    对称加密 可以理解为对原始数据的可逆变换。比如 Hello 可以变换成 Ifmmp,规则就是每个字母变成它在字母表上的后一个字母,这里的秘钥就是 1
    非对称加密:
    非对称加密 有两个秘钥:一个公钥、一个私钥。
    公钥和私钥成对出现,每个用户对应的那套公钥和私钥是不同的。
    服务器留着不对外公布的私钥,然后将公钥告诉所有人知道。即公钥加密的内容只有私钥可以解密(服务器保留隐私),私钥加密的内容公钥可以解密(大众吃瓜权)。

    WebSocket/全双工通信协议

    WebSocket 是 HTML5 新增的一种全双工通信协议,客户端和服务器基于 TCP 握手连接成功后,两者之间就可以建立持久性的连接,实现双向数据传输。
    HTTP 协议是一种单向的网络协议,在建立连接后,它允许客户端向服务器发送请求资源后,服务器才会返回相应的数据,而服务器不能主动推送数据给客户端。
    (短)轮询(Polling)
    短轮询模式下,客户端每隔一段时间向服务器发送 HTTP 请求。服务器收到请求后,将最新的数据发回给客户端。弊端:某个时间段服务器没有更新内容,但是客户端每隔一段时间发送请求来询问,而这段时间内的请求是无效的。
    长轮询
    长轮询模式下,客户端向服务器发出请求,服务器并不一定会立即回应客户端,而是查看数据是否有更新。如果数据更新了的话,那就立即发送数据给客户端;如果没有更新,那就保持这个请求,等待有新的数据到来,才将数据返回给客户端。如果服务器长时间没有更新,那么一段时间后,请求变会超时,客户端收到消息后,会立即发送一个新的请求给服务器。
    弊端:当服务器向客户端发送数据后,必须等待下一次请求才能将新的数据发出,这样客户端接收新数据就有一个最短时间间隔。如果服务器更新频率较快,那么就会出现问题。

    WebSocket和HTTP对比

    都需要建立 TCP 连接;都属于七层协议中的应用层协议。
    HTTP 是单向数据流,客户端向服务器发送请求,服务器响应并返回数据;WebSocket 连接后可以实现客户端和服务器双向数据传递,除非某一端断开连接。
    HTTP 的 url 使用 http// 或者 https// 开头,而 WebSocket 的 url 使用 ws// 开头。

    Vue相关

    Vue.js 实现双向绑定的原理

    Vue.js 2.0 采用数据劫持(Proxy 模式)结合发布者-订阅者模式(PubSub 模式)的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
    每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
    Vue.js 3.0, 放弃了Object.defineProperty ,使用更快的ES6原生 Proxy (访问对象拦截器, 也称代理器)
    Model 改变 View的过程: 依赖于ES5的object.defindeProperty,通过 defineProperty 实现的数据劫持,getter 收集依赖,setter 调用更新回调(不同于观察者模式,是发布订阅 + 中介) View 改变 Model的过程: 依赖于 v-model ,该语法糖实现是在单向数据绑定的基础上,增加事件监听并赋值给对应的Model。
    步骤:
    1.observer主要负责对Vue数据进行递归遍历,包括子属性对象的属性,都加上setter和getter。这样的话给对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
    2.compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
    3.Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
    4.MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
    Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher
    Compile(指令解析器) : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新-视图
    Watcher(订阅者) : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
    ​ 在自身实例化时往属性订阅器(dep)里面添加自己
    ​ 自身必须有一个update()方法
    ​ 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

    Vue中如何监控某个属性值的变化

    比如现在需要监控data中, obj.a 的变化。Vue中监控对象属性的变化你可以这样:

    watch: {
          obj: {
          	handler (newValue, oldValue) {
            console.log('obj changed')},
              deep: true }}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:

    watch: {
       'obj.a': {
          	handler (newName, oldName) {
            console.log('obj.a changed')}}}
    
    • 1
    • 2
    • 3
    • 4

    还有一种方法,可以通过computed 来实现,只需要:

    computed: {
        a1 () {
          return this.obj.a}}
    
    • 1
    • 2
    • 3

    利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。

    Computed和Watch的区别及运用场景

    computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的 属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
    watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每 当监听的数据变化时都会执行回调进行后续操作。
    运用场景:
    1.当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。
    2.当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率, 并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
    3.多个因素影响一个显示,用Computed;一个因素的变化影响多个其他因素、显示,用Watch;

    虚拟dom

    1、什么是虚拟DOM
    虚拟DOM就是用来模拟DOM结构的一个js对象。
    2、虚拟 DOM 的简单实现原理主要包括以下 3 部分:
    ​ 1)用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
    ​ 2)diff 算法 — 比较两棵虚拟 DOM 树的差异;
    ​ 3)pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
    3、虚拟DOM的优点和缺点
    ​ 1. 优点:1). 保证性能下限
    ​ 2). 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,然后根据我们所写的代码去更新视图
    ​ 3). 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,但是 DOM 与平台的相关性是非常强的,相比之下虚拟 DOM 可以进行更方便地跨平台操作
    ​ 2. 缺点:1). 首次显示要慢些:首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢
    ​ 2). 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中无法进行针对性的极致优化。

    事件修饰符及其作用

    事件修饰符
    1).stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
    2).prevent :等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
    3).capture :当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发。如 div1中嵌套div2中嵌套div3.capture中嵌套div4,那么执行顺序为:div3=》div4=》div2=》div1
    4).self :只会触发自己范围内的事件,不包含子元素;
    5).once :只会触发一次。
    按键修饰符
    delete(捕获“删除”和”退格“键) 用法上和事件修饰符一样,挂载在v-on:后面:

    v-on:keyup.xxx=’yyy’
    系统修饰符
    ctrl .alt .shift .meta 这些修饰符可以实现按下相应按键触发鼠标或键盘事件的监听器。

    v-show指令和v-if指令的区别是什么

    v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

    v-if和v-for一起使用的弊端及解决办法

    由于v-for的优先级比v-if高,所以导致每循环一次就会去v-if一次,而v-if是通过创建和销毁dom元素来控制元素的显示与隐藏,所以就会不停的去创建和销毁元素,造成页面卡顿,性能下降。
    解决办法:
    1.在v-for的外层或内层包裹一个元素来使用v-if
    2.用computed处理

    vue常用指令、指令用法

    1.v-model 多用于表单元素实现双向数据绑定(同angular中的ng-model)
    2.v-bind 动态绑定 作用: 及时对页面的数据进行更改
    3.v-on:click 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面
    4.v-for 格式: v-for=“字段名 in(of) 数组json” 循环数组或json(同angular中的ng-repeat)
    5.v-show 显示内容 (同angular中的ng-show)
    6.v-hide 隐藏内容(同angular中的ng-hide)
    7.v-if 显示与隐藏 (dom元素的删除添加 同angular中的ng-if 默认值为false)
    8.v-else-if 必须和v-if连用
    9.v-else 必须和v-if连用 不能单独使用 否则报错 模板编译错误
    10.v-text 解析文本
    11.v-html 解析html标签
    12.v-bind:class 三种绑定方法 1、对象型 ‘{red:isred}’ 2、三元型 ‘isred?“red”:“blue”’ 3、数组型 ‘[{red:“isred”},{blue:“isblue”}]’
    13.v-once 进入页面时 只渲染一次 不在进行渲染
    14.v-cloak 防止闪烁
    15.v-pre 把标签内部的元素原位输出

    对Vue模板编译的理解

    模板指的就是template。如果我们传了一个template,我们会把template转换成一个render函数,然后通过render函数返回虚拟DOM,再把虚拟的DOM变成真正的DOM。
       首先,通过编译编译器把模板编译成AST语法树(抽象语法树即源代码的抽象语法结构的树状表现形式),编译是createCompiler的返回值,createCompiler是用以创建编译器的。负责合并选项。
       然后,AST会经过生成(将AST语法树转化成渲染功能字符串的过程)得到渲染函数,渲染的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名,子节点,文本等等)
       Vue 的编译过程就是将 template 转化为 render 函数的过程分为以下三步:
      第一步是将 模板字符串 转换成 element ASTs(解析器)
      第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
      第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)

    什么是keep-alive、有什么作用

    keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
    1、 一般结合路由一起使用,用于缓存路由组件;
    2、提供 include 和 exclude 属性,两者都支持字符串和正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
    3、对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件离开时,触发钩子函数 deactivated。
    keep-alive :增加两个 activated:当组件为活跃状态时候触发(进入页面的时候)、deactivated:缓存状态的时候触发。
    keep-alive作用就是能够缓存不活动的组件,组件进行切换的时候,默认会进行销毁,如果有需求,某个组件切换后不进行销毁,而是保存之前的状态,那么就可以利用keep-alive来实现。主要用于保留组件状态或避免重新渲染;
    使用:简单页面时:
      缓存: < keep-alive include=”组件名”>< /keep-alive>
      不缓存: < keep-alive exclude=”组件名”>< /keep-alive>

    是否封装过组件,说下封装组件的过程

    封装组件的目的:把业务逻辑相同,高度重复的代码封装起来,为了提高代码的复用,减少代码的冗余
      1.使用Vue.extend方法创建一个组件x,2.import x from " "引用组件,3.使用前先Vue.component方法注册组件{ “v-x”:x},4.然后使用组件

    Vue2.x和Vue3.x渲染器的diff算法分别说一下

    简单来说,diff算法有以下过程
    1.同级比较,再比较子节点
    2.先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
    3.比较都有子节点的情况(核心diff)
    3.递归比较子节点
    正常Diff两个树的时间复杂度是O(n3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
    Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
    Vue3.x借鉴了ivi算法和 inferno算法在创建VNode时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。

    mvvm和mvc的区别

    MVVM和MVC都是一种设计思想,主要就是MVC中的Controller演变成ViewModel,,MVVM主要通过数据来显示视图层而不是操作节点,解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度慢,影响用户体验问题。主要用于数据操作比较多的场景。
    场景:数据操作比较多的场景,更加便捷。
      传统的 MVC 架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新,但MVC 有一个巨大的缺陷就是随着项目功能越多、项目越加复杂,控制器中的代码会越来越多,不利于维护。
      在 MVVM 架构中,引入了 ViewModel 的概念。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。

    Vue 是如何实现数据绑定的?

    Vue 主要通过以下 4 个步骤来实现数据绑定的:
    1、实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
    2、实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
    3、实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
    4、实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

    JS延迟加载的方式

    JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
    1.把JS放在页面的最底部
    2.script标签的defer属性:
    脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
    3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。
    4.动态创建script标签,监听dom加载完毕再引入js文件

    Vue3.0 新特性

    1 . 数据响应式的变动,由原来的Object.defineProperty 的getter 和 setter,改变成为了Proxy 作为其观察机制。
    2 . 虚拟 DOM 重写。更有效的代码来创建虚拟节点
    3 . 提供了组件渲染的优化, 可以更好的逻辑复用
    4 . template 模板可以有多个根元素
    5 . 源码用 typescript 重写, 有更好的 类型推导 (类型检测更为严格, 更稳定)
    静态树、静态属性提升
    小结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便

    vue的生命周期以及钩子函数是如何实现的

    Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 重新渲染、卸载等一系列过程。
    钩子函数实现过程:vue的生命周期钩子实际上就是一个回调函数。当我们传入一个钩子函数时,vue内部会帮我们调用,并将生命周期钩子转换成数组,调用的时候,就又会把数组遍历一遍,类似一个发布订阅的模式。

    vue-router有哪几种导航钩子?

    三种:一种是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。第二种:组件内的钩子;第三种:单独路由独享组件。
    路由常见的钩子函数:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
    使用场景:beforeRouteEnter:当路由进入之前。登录验证、热力图的记录
        beforeRouteUpdate:当路由更新的时。如果当前路由发生了变化,但是不需要组件的创建和销毁的过程的时候,就需要用到这个钩子函数
        beforeRouteLeave:当路由离开时。支付、退出

    vue有哪些生命周期

    keep-alive :增加两个 activated:当组件为活跃状态时候触发(进入页面的时候)、deactivated:缓存状态的时候触发

    //vue2中的生命周期
    beforeCreate()  组件刚刚被创建(methods和data未初始化)
    created()  组件创建已完成,属性已绑定,但DOM还未生成($el属性还不存在)
    beforeMount()模板挂载前(el和data初始化)    mounted()  挂载后
    beforeUpdate()组件更新前 updated()组件更新后  beforeDestroy()组件销毁前  destroyed()组件销毁后
    //vue3中的生命周期
    setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method 
    onBeforeMount() : 组件挂载到节点上之前执行的函数。
    onMounted() : 组件挂载完成后执行的函数。
    onBeforeUpdate(): 组件更新之前执行的函数。
    onUpdated(): 组件更新完成之后执行的函数。
    onBeforeUnmount(): 组件卸载之前执行的函数。
    onUnmounted(): 组件卸载完成后执行的函数
    onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
    onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
    onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
    Vue2--------------vue3 对应关系
    beforeCreate  -> setup()
    created       -> setup()
    beforeMount   -> onBeforeMount
    mounted       -> onMounted
    beforeUpdate  -> onBeforeUpdate
    updated       -> onUpdated
    beforeDestroy -> onBeforeUnmount
    destroyed     -> onUnmounted
    activated     -> onActivated
    deactivated   -> onDeactivated
    errorCaptured -> onErrorCaptured
    
    • 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

    为什么要用axios

    axios 是一个基于promise的网络请求库 ,用于浏览器和 nodejs 的 HTTP 客户端,具有以下特征:
    1)支持 Promise API
    2)拦截请求和响应
    3)转换请求和响应数据
    4)取消请求
    5)自动转换JSON数据
    6)从浏览器中创建 XMLHttpRequest
    7)从 node.js 发出 http 请求
    8)客户端支持防止CSRF/XSRF

    SPA( single page application )单页面应用

    SPA( single page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;是会利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
    1、优点:
    1). 用户体验好、速度快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
    2). 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
    3). 基于上面一点,SPA 相对对服务器压力小;
    2、缺点:
    1). 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
    2). 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
    3). SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

    vue路由跳转的方式

    ①a标签进行跳转  首页
      ②router-link进行跳转

    首页
    ③编程式路由  
    ​ 1、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

    ​ 2、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

    ​ 3、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数

    this.$router.push("/my");
    Vue.use(VueRouter);//在路由文件中配置了重定向
    const routes = [
      { path: "/home",
        name: "Home",
        component: Home,},
      { path: "/",
        redirect: "/home",},
      { path: "/list",
        name: "List",
        component: () => import("../views/List.vue"),},
        this.$route.query//接收参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    导航守卫

    进行路由跳转的过程就是导航,在跳转前中后的过程中都有一函数,通过这个函数确定是否完成导航跳转,如果在跳转过程中进行限制就实现了路由的成功守卫。

    //找到router对象
    router.beforeEach((to, from, next)=> {
      //从from跳转到to,前置守卫 router.beforeEach() 跳转前调用
      document.title = to.matched[0].meta.title
      next()
    })
    {//路由配置需添加meta属性
      path: '/profile',
      component: Profile,
      meta: {
        title: '档案'
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    vue-router有哪几种路由守卫?

    答:全局前置守卫:beforeEach、后置守卫:afterEach(两个都是全局守卫)、全局解析守卫:beforeResolve、路由独享守卫:beforeEnter。
    router.afterEach((to, from) => {})

    meta:元数据,描述数据的数据
    前置守卫 router.beforeEach() 跳转前调用
    后置钩子router.afterEach() 跳转之后调用

    beforeEach的原理

    router.beforeEach()用来做一些进入页面的限制。比如登录,若没有登录就不能进入页面,只有登录了之后才有权限查看某些页面。也就是路由拦截。
    原理:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步执行,此时导航在所有守卫 resolve 完之前都一直处 等待状态中。
    每个守卫方法接收三个参数:to: Route: 即将要进入的目标 路由对象from: Route: 当前导航正要离开的路由next: Function: 一定要调用该方法来 resolve 这个钩子。执行依赖 next 方法的调用参数。next(): 进行队列中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

    怎么定义vue-router的动态路由?怎么获取传过来的值?

    动态路由的创建,使用path属性,使用动态路径参数,以冒号开头:

    { path:/test/:id’ name: ‘Test’ components: Test }
    
    • 1

    访问test目录下的所有文件,test上的所有属性都会映射到Test组件上。当读取/test下的路由时,参数会被放到

    this.$route.params
    
    • 1

    里面。可以通过

    this.$route.params.id
    
    • 1
  • 相关阅读:
    阿里云个人镜像仓库日常基本使用
    【AI视野·今日Robot 机器人论文速览 第四十五期】Mon, 2 Oct 2023
    从技术角度探索安卓群控实现的基本思路
    算法刷题-字符串
    访问者模式简介
    【Spring】Spring Security学习笔记
    总结:vue的通信方式
    SAP UI5 SimpleForm 的 two-column layout 布局
    POJ3268最短路径题解
    香港硬防服务器的防御有什么优缺点?
  • 原文地址:https://blog.csdn.net/weixin_45086164/article/details/127800021