面试题总结

一、线上跨越如何解决

https://www.cnblogs.com/qcwblog/p/12802508.html
https://www.jianshu.com/p/8fa2acd103ea
跨域解决方案
https://blog.csdn.net/qq_38128179/article/details/84956552

线上跨域配置
nginx服务器反向代理配置

server {
    listen 80;
    server_name 域名;
    root ####;

    location / {
      try_files $uri $uri/ /index.html;  //解决 HTML5 History 模式直接访问子路由404问题
    }

    location ^~/api/ {

      proxy_pass  域名或者ip地址:端口/; //匹配api开头的请求 端口后面加 / 请求的时候会把api去掉 不加 / 会带上完整的访问接口地址,这个可以和后端约定

    }

}

正向代理代理客户端,反向代理代理服务器。
解决办法:

npm start 本地开发环境解决:
在webpack配置文件 /config/index.js 里找到 proxyTable 开启代理 changeOrigin:true,

proxyTable: {
      '/api':{
        target:'http://xx.xx.xx.xx:5568',
        changeOrigin:true,
        pathRewrite:{
            '^/api':'/api'
        }
      }
    },

npm run build 把 dist 放线上后解决:
nginx 的 配置文件 xx.conf 的 server {} 里加如下:

location /api/ {
        # 把 /api 路径下的请求转发给真正的后端服务器
        proxy_pass http://xx.xx.xx.xx:5568;

        # 把host头传过去,后端服务程序将收到your.domain.name, 否则收到的是localhost:8080
        proxy_set_header Host $http_host;

        # 把cookie中的path部分从/api替换成/service
        proxy_cookie_path /api /;

        # 把cookie的path部分从localhost:8080替换成your.domain.name
        proxy_cookie_domain localhost:80 http://xx.xx.xx.xx:5568;
    }

二、webpack、loader、plugin作用,有哪些loader和plugin

什么是webpack?
webpack 是一个模块打包机,将根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源。

当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

主要功能:1、打包 2、转换 3、优化
loader
作用:让webpack去处理那些非javascript文件。
loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块。
loader特效:逆向编译,链式传递。
常用的loader
样式:style-loader、css-loader、less-loader、sass-loader等
文件:raw-loader、file-loader 、url-loader等
编译:babel-loader
校验测试:eslint-loader

// webpack 配置

module.exports = {
  module: {
    rules: [{ 
        test: /\.css$/, 
        use: ['style-loader', 'css-loader', 'postcss-loader']
    }]
  }
};

// 如上,css 文件编译顺序依次为:postcss-loader ---> css-loader ---> style-loader
// 编译过程中,第一个loader的值 传递给下一个loader,依次传递;最后一个loader编译完成后,将预期值传递给 webpack

plugin 作用:可以处理各种任务,从打包优化和压缩,一直到重新定义环境中的变量。
常用的Plugin:
define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件
extract-text-webpack-plugin 将js文件中引用的样式单独抽离成css文件
HotModuleReplacementPlugin 热更新
happypack:通过多进程模型,来加速代码构建
clean-webpack-plugin 清理每次打包下没有使用的文件

webpack打包优化速度
https://quincychen.cn/webpack-optimization/
一、减少文件搜索范围

  1. 优化 loader 配置
    在配置中对应的是 test, include, exclude 三个配置项,尽可能的优化这些配置,提高匹配效率。
{
  test: /\.js$/, // 正则表达式尽可能精简,比如项目中只有js文件,没有jsx,那么不要配置 /\.(js|jsx)$/
  use: 'babel-loader',
  include: [path.resolve('./src')], // 通过 include 缩小文件范围,非 include 下的文件不会参与正则匹配
  exclude: /node_modules/ // 第三方库文件都是 ES5 语法,不需要再 babel 处理
}
  1. 优化 resolve.module
    resolve.module 用户配置 webpack 去哪里寻找第三方模块,默认值是 ['node_modules'] ,即当前目录下的 ./node_modules 目录下找第三方模块,找不到时再往父级目录寻找。一般而言,一个项目只会有一个 node_modules 目录,因此,我们可以直接将其配置为 node_modules 的绝对路径,减少检索过程。
resolve: {
  module: [path.resolve('node_modules')]
}
  1. 优化 resolve.alias
  2. 优化 resolve.extensions
resolve: {
  extensions: ['.js', '.vue']
}
  1. 优化 module.noParse 可以让 webpack 忽略一些模块的递归解析处理,这样做可以提高构建性能。

二、使用HappyPack来加速构建
HappyPack会采用多进程去打包构建,使用方式还是蛮简单的,但并不是支持所有的loader(file-loader和url-loader支持不好)
HappyPack是让webpack对loader的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。从而加速代码构建 与 DLL动态链接库结合来使用更佳。

使用方法如下:
首先引入HappyPack并创建线程池:

const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({size: require('os').cpus().length - 1});

替换之前的Loader为HappyPack的插件:

{
    test: /\.(js|mjs|jsx|ts|tsx)$/,
    include: paths.appSrc,
    use: ['happypack/loader?id=babel-application-js'],
},

将原Loader中的配置,移动到对应插件中:

new HappyPack({
    id: 'babel-application-js',
    threadPool: happyThreadPool,
    verbose: true,
    loaders: [
        {
            loader: require.resolve('babel-loader'),
            options: {
                ...省略
            },
        },
    ],
}),

babel-loader开启缓存
babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积大冗余,同时也会减慢编译效率。
可以加上cacheDirectory参数或使用 transform-runtime 插件试试。

// webpack.config.js
use: [{
                loader: 'babel-loader',
                options: {
                    cacheDirectory: true
                }]


// .bablerc
{
    "presets": [
        "env",
        "react"
    ],
    "plugins": ["transform-runtime"]
}

三、熟悉的ts、泛型与type的区别

https://blog.csdn.net/weixin_29081703/article/details/112566573

常用的ts:

基础类型:

1. boolean   //布尔
2. number    //数值
3. string    //字符串
4. string[]  //数组
5. enum      //枚举
   //举例:
   enum Color {Red,Green,Blue}
   let c:Color = Color.Red
6. any       //任意值
7. void      //空值

变量声明

let和const用法和在es6中相同

接口

接口在TypeScript中代表一种数据类型,通过接口用户可以自己扩展基础类型。在ES6中不存在接口。

类(class)

类在es6中就已经可以使用了,TypeScript对其进行了扩展。

  1. 增加了public(默认),private,protected访问限制符
  2. 在构造函数的参数中使用访问限制符,可以直接设置并初始化类变量

泛型、

https://baijiahao.baidu.com/s?id=1640743442457144753&wfr=spider&for=pc
泛型:一种抽象类型的能力

TS中泛型的实现使我们能够创建可重用的组件,一个组件可以支持多种类型的数据,为代码添加额外的抽象层和可重用性。泛型可以应用于TS中的函数、接口和类。

什么是泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
通俗的解释,泛型是类型系统中的“参数”,主要作用是为了类型的重用。从上面定义可以看出,它只会用在函数、接口和类中。

泛型主要用户两种场景:

  1. 泛型函数
  2. 泛型类,泛型类使用(<>)括起来,跟在类名后面

什么时候使用泛型

泛型在我们以类型安全的方式为项分配数据时提供了很大的帮助,但是泛型也不应该被滥用,除非这样的抽象有意义,也就是说,在可以使用多种类型的情况下简化或最小化代码。

对泛型的可行用例还没有深入人心;你经常会在你的代码库中到处找到一个合适的用例来节省代码的重复——但是一般来说,在决定是否使用泛型时,我们应该满足两个标准:

1、当函数、接口或类处理各种数据类型时
2、当函数、接口或类在多个位置使用该数据类型时
很可能在项目的早期,您没有一个保证使用泛型的组件。但是随着项目的增长,组件的功能经常会扩展。这种增加的可扩展性最终很可能遵循上述两个标准,在这种情况下,引入泛型将是比仅仅为了满足一系列数据类型而复制组件更干净的选择。

我们将在本文的后面探索更多的同时满足两个标准的用例。在此之前,让我们先来了解一下TS里泛型的其他特性。

高级类型:

https://blog.csdn.net/sinat_36422236/article/details/103225828

交叉类型(&) :T & U 就是将多个类型合并成一个类型
联合类型(|):表示其类型为连接的多个类型中的任意一个。T | U
类型别名(type):交叉类型与联合类型如果有多个地方需要使用,就需要通过类型别名的方式,给这两种类型声明一个别名。类型别名与声明变量的语法类似,只需要把 const、let 换成 type 关键字即可。
type Alias = T | U

TypeScript中interface和type的区别

https://blog.csdn.net/weixin_43758377/article/details/110470780

TypeScript中定义类型的两种方式
1、接口(interface)
2、类型别名(type alias)

相同点

  1. 都可以描述一个对象或者函数
  2. 都允许拓展(extends)

不同点

1、type可以声明基本类型别名、交叉类型、联合类型、元祖等类型。
interface只能定义对象类型。
2、type语句中还可以使用typeof获取实例的类型进行赋值。

typescript的优缺点

https://segmentfault.com/a/1190000019996860?utm_source=tag-newest

优势:

  • TypeScript支持面向对象的编程特性,比如类、接口、继承、泛型等等。
  • 它提供了可选静态类型的优点。
  • TypeScript在编译时提供了错误检查功能。它将编译代码,如果发现任何错误,它将在运行脚本之前突出显示这些错误。
  • TypeScript支持所有JavaScript库,因为它是JavaScript的超集。
  • TypeScript通过使用继承来支持可重用性。

缺点:

  • TypeScript需要很长时间来编译代码。
  • TypeScript不支持抽象类。
  • 要使用任何第三方库,必须使用定义文件。并不是所有第三方库都有可用的定义文件。

强类型语言:不允许改变变量的数据类型,除非进行强制类型转换。
弱类型语言: 变量的值其类型是宽泛的,可以被赋值多种类型的值。

静态类型语言:在编译阶段确定所有变量的类型;
动态类型语言:在执行阶段确定所有变量的类型;

四、实现一个发布订阅模式、与观察者的区别

五、实现一个add(1)(2)(3)函数

使用函数柯里化curry实现
思路:要判断当前传入函数的参数个数 (args.length) 是否大于等于原函数所需参数个数 (fn.length) ,如果是,则执行当前函数;如果是小于,则返回一个函数。

const curry = (fn, ...args) => 
  args.length >= fn.length 
      ? fn(...args) : (..._args) => curry(fn, ...args, ..._args);
 
function add1(x, y, z){
    return x+y+z;
}

const add = curry(add1);
add(1)(2)(3);
add(1,2)(3);
add(1,2,3);
add(1)(2,3);

柯里化有什么作用

主要有3个作用: 参数复用、提前返回和 延迟执行

我们来简单的解释一下:
参数复用:拿上面 f这个函数举例,只要传入一个参数 z,执行,计算结果就是 1 + 2 + z 的结果,1 和 2 这两个参数就直接可以复用了。

提前返回 和 延迟执行 也很好理解,因为每次调用函数时,它只接受一部分参数,并返回一个函数(提前返回),直到(延迟执行)传递所有参数为止。

转转面试题

1、http1.0,http1.1,http2.0区别
2、promise常用api,
1)promise.all多个请求是并列执行还是怎么执行,如果有一个失败
后面的还会执行吗?
2)如果一个请求失败如何再次发起请求?
https://www.cnblogs.com/huanglei-/p/9396783.html
https://blog.csdn.net/weixin_39556891/article/details/111806072
3、tranform和margin区别(从重绘和回流说起)
1).margin是属于布局属性,该属性的变化会导致页面的重排。 对布局属性进行动画,浏览器需要为每一帧进行重绘并上传到GPU中进行渲染。
2).transform是合成属性,浏览器会为元素创建一个独立的复合层,当元素内容没有发生变化,该层不会被重绘,通过重新复合来创建动画帧。
4、webpack执行机制,loader和plugin执行顺序
5、react hooks有哪些缺点
useRef和createRef区别
createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

image.png

输出结果:
image.png

  • 何时使用 useRef

6、事件循环机制
7、常用的布局
flex布局、自适应布局、圣杯布局

联通云面试题

1、echarts.js图表宽度自适应的问题

// 单个图表
window.onresize = myChart.resize;   

//多个图标可以这样写:
window.onresize = () => {
    this.myChart.resize()
    this.myChart1.resize()
}

举例如下:
https://blog.csdn.net/bangxionger9224/article/details/101768771

2、哪些正则不支持某些浏览器

3、rem和px、em的区别
px: 是固定的像素 ,是相对于显示器屏幕分辨率而言的。
rem: 相对于根元素font-size大小适配的。
em:(相对长度单位)子元素字体大小的em是相对于父元素字体大小。

4、表单发送时注意安全问题(从安全漏洞来说)

5、大屏图表

6、如何实现表单验证、复选框选中举例说出相关思路

新浪金融

1、call、apply、bind区别
call、apply如果第一个参数为null或undefined,this指向全局对象window
js实现call、apply、bind
2、变量a 使得a==1 && a==2 && a==3(可以使用闭包函数每次加1,然后执行3次)
http://caibaojian.com/variable-change.html
https://blog.csdn.net/qq_41569151/article/details/100147805

//1、js实现
let obj = {
  i: 1,
  valueOf: () => {
      return obj.i++;
  }
}
console.log(obj == 1 && obj == 2 && obj == 3);

//2、 利用defineProperty的get方法
let  i =0;
Object.defineProperty(window,  'a', {
  get: () => {
    return ++i;
  }
})
console.log(a == 1 && a == 2 && a == 3);

3、vue与react区别、vue响应式原理、vue diff算法、vue3新增了哪些api
4、输入url发生的过程、缓存、js事件循环机制、跨域问题
5、原型、原型链、继承
6、大文件上传、断点续传思路
https://blog.csdn.net/lixiaosenlin/article/details/114931287
https://www.cnblogs.com/fjzhang/p/7227401.html
解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路。

大文件切片上传,断点续传FORM-DATA

  • 简单逻辑分析

切片上传顾名思义就是将一个大文件分割成多个小文件进行分别上传,待所有切片上传完成后再将它们合并成一个文件,这样就实现了一个大文件的切片上传,同时如果上传过程出现问题,下次继续上传时还能实现断点续传。
切片上传的关键在于上传后要将所有切片文件合并,那么合并时就需要考虑一下问题了:
需要将哪些文件进行合并?
找到需要合并后的文件,要按怎么样的顺序进行合并?
=>首先第一问题,为了能够快速方便的找到哪些文件是需要合并的,在将切片文件上传到服务器时,我们需要在服务器端建立一个单独的临时文件夹用于保存所有的切片
=>第二个问题,为了保证合并后的文件与原文件保持一致,在切片时需要给每个切片添加一个索引,这样就能在合并时按照索引进行按顺序合并了。
如果切片在上传过程中出现了问题,导致上传中断,那么下次上传时为了实现不重复上传,也就是所说的断点续传,就需要进行判断,如果文件存在则直接跳过,那么如何去判断一个文件(或切片)是否存在了呢?
这时就需要用到我们前面提到的spark-md5库了,该库可根据文件内容生成一串hash值,只要文件内容不变那么生成出来的hash值也永远都是一样的,所以我们可以利用hash值加索引的形式进行文件切片的命名
切片思路分析:
要将一个文件进行切片,需要借助文件的size属性和slice方法
方法一(固定个数):将一个文件切成固定个数,比如20个,然后用size/20计算出每个切片文件的大小,再利用slice进行截取
方法二(固定大小):固定每个切片文件的大小,比如100k,然后用size/100计算需要分成几个切片,同样也是再用slice截取
在本案例中,我们将采取方法一和方法二合并的方式进行切片:我们先根据方法二固定每个切片的大小,计算出切片的个数,然后再规定一个最大个数,如果计算出的个数超过了最大个数,就需要根据方法一进行> 重新切片。如果没有超出个数,则按固定大小切片。

  • 简单步骤分析

在梳理出切片逻辑后,下面就是要一步步实现了:
首先我们先来封装一个返回promise实例的方法retrieveHash,该方法主要用于根据文件内容生成一个hash值,需要借助spark-md5和FileReader
封装一个所有切片上传完成后发送合并请求方法uploadComplete
在该方法外面需要定义一个计数器,每上传完成一个切片需要调用一次该方法,每调用一次该方法,计数器就需要累加1
当计数器的值等于切片的个数时,则说明所有切片已经上传完成,这时就可以发送合并请求进行切片合并了
这里有一点需要注意:就是在发送合并请求前,最好是延迟几秒再发送,以避免一些不必要的错误
在文件域的change事件中得到要上传的文件
调用上面封装好的获取hash值的方法retrieveHash,然后根据hash值向服务端发送一个请求来获取已经上传过的切片列表filelist(用于断点续传判断)
根据上面分析的切片逻辑进行切片,并将切片文件信息保存在数组中
遍历切片数组,首先判断该切片是否已经上传,也就是看该切片文件是否已经存在于上面所获取到的文件列表filelist中
如果存在则调用uploadComplete,让计数器累加。如果不存在,则调用服务端切片上传接口进行文件上传,同时在上传完成后,仍需调用uploadComplete方法进行计数器累加,一旦计数器的值跟切片个数相等,则会自动调用合并接口进行文件合并
至此大文件的切片上传和断点续传就实现了。

(function () {
    let upload4 = document.querySelector("#upload4"),
        upload_inp = upload4.querySelector('.upload-inp'),
        upload_upload = upload4.querySelector('.upload-btn'),
        sel_files = upload4.querySelector('.files'),
        cur_pro = upload4.querySelector('.cur-pro'),
        pro_val = upload4.querySelector('.pro-val'),
        progress = upload4.querySelector('.progress');
// retrieveHash该方法主要用于根据文件内容生成一个hash值
    const retriveHash = function retriveHash(file) {
        return new Promise((resolve, reject) => {
            let spark = new SparkMD5.ArrayBuffer();
            let fr = new FileReader();
            fr.readAsArrayBuffer(file);
            fr.onload = (ev) => {
                spark.append(ev.target.result);
                let hash = spark.end();
                let suffix = /\.([0-9a-zA-Z]+)$/.exec(file.name)[1];
                resolve({
                    hash,
                    suffix
                });
            };
        });


    }

//uploadComplete方法:所有切片上传完成后发送合并请求方法
    let complete = 0;
    const uploadComplete = function uploadComplete(hash, count) {
        complete++;
        let progerss = (complete / count * 100).toFixed(2) + '%';
        cur_pro.style.width = progerss;
        pro_val.innerHTML = progerss;
        if (complete < count) return;
        cur_pro.style.width = '100%';
        pro_val.innerHTML = '100%';
        setTimeout(() => {
            request.post('/upload_merge', {
                hash,
                count
            }, {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            }).then(res => {
                console.log(res);
                // alert('上传成功了');
            }).catch(err => {
                console.log(err);
            });
        }, 3000);
    }
    upload_upload.addEventListener('click', function () {
        upload_inp.click();
    });

    upload_inp.addEventListener('change', async function () {
        let file = this.files[0];
        progress.style.display = 'inline-block';
        cur_pro.style.width = '0%';
        pro_val.innerHTML = '0%';
        let chunks = [];
        let {
            hash,
            suffix
        } = await retriveHash(file);
        sel_files.innerHTML = `${hash}.${suffix}`;
        let {
            filelist
        } = await request.get('/uploaded', {
            params: {
                hash
            }
        });

        let maxSize = 100 * 1024; //100k
        let count = Math.ceil(file.size / maxSize);
        //限制切片的数量不能超过20个,并重新计算每个切片的大小
        if (count > 20) {
            maxSize = file.size / 20;
            count = 20;
        }

        let index = 0;
        while (index < count) {
            chunks.push({
                file: file.slice(index * maxSize, (index + 1) * maxSize),
                filename: `${hash}_${index+1}.${suffix}`
            });
            index++;
        }

        chunks.forEach((item, index) => {
            //如果已经上传过就不再上传了
            if (filelist && filelist.length > 0 && filelist.includes(item.filename)) {
                uploadComplete(hash, count);
                return;
            }
            let formData = new FormData();
            formData.append('file', item.file);
            formData.append('filename', item.filename);
            request.post('/upload_chunk', formData).then(res => {
                uploadComplete(hash, count);
                // console.log(res);
                // alert('上传成功了');
            }).catch(err => {
                console.log(err);
            });
        });
    });


})()

7、后端数据量很大,一次性返给前端,前端该怎么处理?
采用的办法有两种:

  • 分次加载数据
   var total = 10000; // 总条数
    var once = 1000; // 每次加载条数
    var loadCount = total / once; // 加载次数
    var recordCount = 0;
    var ulObj = document.querySelector("ul.async-load");
    function addLi(){
        // 性能优化,创建一个虚拟的dom节点
        var fragment = document.createDocumentFragment();
        for(var index = 0 ; index < once ; index ++){
            var li = document.createElement("li");
            li.innerText = `
  • 这是第${recordCount}次加载
  • li${index}`; fragment.appendChild(li); } ulObj.appendChild(fragment); recordCount++; loop(); } function loop(){ if(recordCount < loadCount){ window.requestAnimationFrame(addLi) } } loop();
    • 滚动加载数据
        var scrollUl = document.querySelector("ul.scroll-load");
        var scrollRecordCount = 0;
        function scrollLoad(){
            var boxHeight = $(".scroll-load").height(),
                total = 10000,
                once = 1000,
                loadCount = total / once;
                loadata(once , scrollRecordCount);
            $(".scroll-load").scroll(function(){
                var realHeight = $(".scroll-load")[0].scrollHeight;
                var scrollHeight = $(this).scrollTop();
                if(boxHeight+scrollHeight + 50 >= realHeight && scrollRecordCount < loadCount){
                    // 加载数据
                    loadata(once , scrollRecordCount);
                }
            })
        }
        // 加载数据
        function loadata(once , recordCount){
            console.log(recordCount);
            var fragment = document.createDocumentFragment();
            for(var index = 0 ; index < once ; index++){
                var li = document.createElement("li");
                li.innerText = `
  • 这是第${recordCount}次加载 li${index}
  • ` fragment.appendChild(li); } scrollUl.appendChild(fragment); scrollRecordCount++; } scrollLoad();

    window.requestAnimationFrame() 参数是一个回调函数。
    回调的次数建议每秒60次。这个回调函数只有一个传参。
    与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说requestAnimationFrame的步伐跟着系统的刷新步伐走。

    8、JS多个异步请求 按顺序执行
    http://caibaojian.com/javascript-queue-function.html
    假设你有几个函数fn1、fn2和fn3需要按顺序调用,

    var stack = [];
    // 执行其他操作,定义fn1
    stack.push(fn1);
    // 执行其他操作,定义fn2、fn3
    stack.push(fn2, fn3);
    // 调用的时候
    stack.forEach(function(fn) { fn() });
    

    但是假如stack 队列中有某个函数是类似的异步函数,我们的实现就乱套了:
    实现如下:

    // 定义index 和next
    // next 方法
    var index = 0;
    function next() {
        var fn = stack[index];
        index = index + 1; // 其实也可以用shift 把fn 拿出来
        if (typeof fn === 'function') fn();
    }
    
    var stack = [];
    function fn1() {
        console.log('第一个调用');
        next();  // stack 中每一个函数都必须调用`next`
    };
    stack.push(fn1);
    
    function fn2() {
        setTimeout(function fn2Timeout() {
             console.log('第二个调用');
             next();  // 调用`next`
        }, 0);
    }
    stack.push(fn2, function() {
        console.log('第三个调用');
        next(); // 最后一个可以不调用,调用也没用。
    });
    
    next(); // 调用next,最终按顺序输出'第一个调用'、'第二个调用'、'第三个调用'。
    

    如何实现一个 LazyMan?

    https://blog.csdn.net/weixin_34405557/article/details/89088763
    https://juejin.cn/post/6844903791188246541
    https://blog.csdn.net/weixin_30376453/article/details/94860590

        实现一个LazyMan,可以按照以下方式调用:
        LazyMan(“Hank”)输出:
        Hi! This is Hank!
     
        LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
        Hi! This is Hank!
        //等待10秒..
        Wake up after 10
        Eat dinner~
     
        LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
        Hi This is Hank!
        Eat dinner~
        Eat supper~
     
        LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
        //等待5秒
        Wake up after 5
        Hi This is Hank!
        Eat supper
        以此类推。
    
    // ES5方式实现
    function _LazyMan(name) {
        this.tasks = [];   
        var self = this;
        var fn =(function(n){
            var name = n;
            return function(){
                console.log("Hi! This is " + name + "!");
                self.next();
            }
        })(name);
        this.tasks.push(fn);
        setTimeout(function(){
            self.next();
        }, 0); // 在下一个事件循环启动任务
    }
    /* 事件调度函数 */
    _LazyMan.prototype.next = function() { 
        var fn = this.tasks.shift();
        fn && fn();
    }
    _LazyMan.prototype.eat = function(name) {
        var self = this;
        var fn =(function(name){
            return function(){
                console.log("Eat " + name + "~");
                self.next()
            }
        })(name);
        this.tasks.push(fn);
        return this; // 实现链式调用
    }
    _LazyMan.prototype.sleep = function(time) {
        var self = this;
        var fn = (function(time){
            return function() {
                setTimeout(function(){
                    console.log("Wake up after " + time + "s!");
                    self.next();
                }, time * 1000);
            }
        })(time);
        this.tasks.push(fn);
       return this;
    }
    _LazyMan.prototype.sleepFirst = function(time) {
        var self = this;
        var fn = (function(time) {
            return function() {
                setTimeout(function() {
                    console.log("Wake up after " + time + "s!");
                    self.next();
                }, time * 1000);
            }
        })(time);
        this.tasks.unshift(fn);
        return this;
    }
    /* 封装 */
    function LazyMan(name){
        return new _LazyMan(name);
    }
    
    // ES6方式实现
    考察知识点:闭包,事件轮询机制,链式调用,队列
    class _LazyMan {
      constructor(name) {
        this.tasks = [];
        const task = () => {
          console.log(`Hi! This is ${name}`);
          this.next();
        }
        this.tasks.push(task);
        setTimeout(() => {               // 把 this.next() 放到调用栈清空之后执行
          this.next();
        }, 0);
      }
    
      next() {
        const task = this.tasks.shift(); // 取第一个任务执行
        task && task();
      }
    
      sleep(time) {
        this._sleepWrapper(time, false);
        return this;                     // 链式调用
      }
    
      sleepFirst(time) {
        this._sleepWrapper(time, true);
        return this;
      }
    
      _sleepWrapper(time, first) {
        const task = () => {
          setTimeout(() => {
            console.log(`Wake up after ${time}`);
            this.next();
          }, time * 1000)
        }
        if (first) {
          this.tasks.unshift(task);     // 放到任务队列顶部
        } else {
          this.tasks.push(task);        // 放到任务队列尾部
        }
      }
    
      eat(name) {
        const task = () => {
          console.log(`Eat ${name}`);
          this.next();
        }
        this.tasks.push(task);
        return this;
      }
    }
    
    function LazyMan(name) {
      return new _LazyMan(name);
    }
    
    

    8、promise顺序执行的几种方式
    https://www.jianshu.com/p/005569312177
    https://www.cnblogs.com/zhensg123/p/14725672.html
    https://www.cnblogs.com/rusr/p/8488483.html

    //递归方式解决
    const arr = [fn1, fn2,fn3];
    function squenePromise(arr){
        var pro = arr.shift();
        if(pro){
            pro().then(() => {
                squenePromise(arr)
            });
        }
    }
    squenePromise(arr);
    

    龙湖面试题

    1、vue3相对vue2有哪些改变
    2、type和inteface的区别
    3、diff算法
    4、浏览器的渲染
    5、解决前端代码部署发布后页面代码不更新问题

    解决办法

    1、在vue项目中解决
    在入口文件index.html添加:

    
    
    
    
    
    

    2、如果是部署在nginx上
    在nginx的配置文件 nginx.config添加:

    location = /index.html {
    
        add_header Cache-Control "no-cache, no-store";
    
    }
    

    如果是用nginx做反向代理的,这么加

    location = /xx(xx为你的代理的项目名) {
    
        add_header Cache-Control "no-cache, no-store";
    
    }
    

    如果nginx上有 proxy_cache 的配置,也考虑删掉,这是nginx的服务器缓存

    6、打开一个网页出现崩溃是什么原因,如何解决
    [https://blog.csdn.net/weixin_39775029/article/details/112753028]
    7、for in 和 for of的区别
    [https://www.cnblogs.com/rogerwu/p/10738776.html]
    1)for...in 循环:只能获得对象的键名,不能获得键值
    for...of 循环:允许遍历获得键值
    2)对于普通对象,没有部署原生的 iterator 接口,直接使用 for...of 会报错。
    可以使用 for...in 循环遍历键名
    也可以使用 Object.keys(obj) 方法将对象的键名生成一个数组,然后遍历这个数组。
    3)for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for...of 则不会这样
    4)forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效。for...of 循环可以与break、continue 和 return 配合使用,跳出循环。
    5)无论是 for...in 还是 for...of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法。

    总之,for...in 循环主要是为了遍历对象而生,
    for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

    五八到家
    1、http报文结构
    2、链表反转
    3、 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
    4、tcp、udp的区别
    5、继承的方式
    6、 前端性能优化
    7、webpack

    随机歌单算法:有100首歌,随机播放,不能重复播放,怎么实现比较时间复杂度低?

    8.16
    1、垃圾回收机制
    2、对模块化的理解
    3、vue的diff算法
    4、性能优化

    你可能感兴趣的:(面试题总结)