面试总结

1,107 阅读22分钟

浏览器缓存机制

浏览器读取缓存的步骤是:本地 -》线上

Service Worker

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

Memory Cache(内存)

Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存

那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?

先说结论,这是不可能的。首先计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。内存中其实可以存储大部分的文件,比如说 JSS、HTML、CSS、图片等等。

当然,我通过一些实践和猜测也得出了一些结论:

  • 对于大文件来说,大概率是不存储在内存中的,反之优先;
  • 当前系统内存使用率高的话,文件优先存储进硬盘;

Disk Cache(硬盘缓存)

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。

网络请求

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

那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,接下来我们就来学习缓存策略这部分的内容。

缓存策略

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

强缓存

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

Expires

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

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

Cache-control

Cache-control: max-age=30

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

属性 解释
public 表示响应可以被client和proxy server缓存
private 响应只能被client缓存
max-age=30 缓存30s后过期
s-maxage=30 覆盖max-age但是只在proxy server中生效
no-store 不做任何缓存
no-cache 资源被缓存,但是立即失效,下次请求需要确认资源是否过期
max-stale=30 资源30秒内即使过期也使用缓存;
min-fresh=30 30秒内获取最新响应

协商缓存

当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。如果缓存过期了,就需要发起请求验证资源是否有更新。

Last-Modified

Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。

但是 Last-Modified 存在一些弊端:

  1. 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  2. 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

ETag

ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来

网络相关

请看我的上篇文章http

webpack

基本配置信息概念

  • entry: webpack的入口文件,webpack会根据入口文件解析整个项目的依赖关系,内部构成一个依赖图,根据依赖图进行打包,生成一个或者多个bundle 文件;

  • output: webpack 最终构建出来的静态文件;

  • loader: 可以把 loader 理解为是一个转换器,负责把某种文件格式的内容转换成 webpack 可以支持打包的模块

  • plugin: 丰富打包过程中对文件的修改的能力;loader是广度增驾能力,plugin是深度增驾能力;

  • mode:可以是 development,production,none 其中的一个;

    我们这里简单介绍一下 development 和 production 模式的区别:

    • 这两个模式会使用 DefinePlugin 来将 process.env.NODE_ENV 的值分别设置为 development 和 production,方便开发者在项目业务代码中判断当前构建模式。
    • production 模式会启用 TerserPlugin 来压缩 JS 代码,让生成的代码文件更小。
    • development 模式会启用 devtools: 'eval' 配置,提升构建和再构建的速度。

webpack寻找文件的规则:

基本的模块解析规则:

  1. 解析相对路
    • 寻找到当前模块对应的路径是否有文件或者文件夹
    • 有文件直接加载文件
    • 是文件夹的话,寻找文件中是否有package.json文件
    • 有package.json文件则找到mian入口加载文件
    • 无package.json文件或者无main字段则默认加载index.js文件
  2. 解析模块名
    • 查找当前文件目录下,父级目录及以上目录下的 node_modules 文件夹,看是否有对应名称的模块;
  3. 解析绝对路径(一般不建议使用)
    • 直接查找对应路径的文件

相关的配置属性reslove配置

  1. resolve.alias:配置别名
  2. resolve.extensions 配置文件查找后缀顺序;

webpack的loader

  1. 同一个rule配置loader的执行顺序;

    从最后配置的load而开始向上执行;

  2. 两个rule配置同一种文件的执行规则;

    不确定的执行顺序,需要固定执行顺序的话可以利用enforce字段;

  3. 所有的 loader 按照前置 -> 行内 -> 普通 -> 后置的顺序执行

    • 前置:enforce:pre;
    • 行内:const json = require('json-loader!./file.json')写在文件中;
    • 普通就是经常写的普通的配置
    • 后置:enforce: post;

写一个plugin插件

  1. plugin是一个类;
  2. plugin 实例中最重要的方法是 apply,该方法在 webpack compiler 安装插件时会被调用一次;
  3. apply的方法中会接收到compiler的webpack的编译对象
  4. 根据compiler对象可利用compiler.hooks获取到不同webpack的生命周期钩子
  5. compiler.hooks[不同的钩子]来 注册你的扩展动作;
    1. hooks有同步的还有异步的;
      • 同步的钩子事件绑定:
      • 异步的钩子时间绑定:

loader和plugin的区别:

  • loader :webpack其实只有识别,打包JS文件的,各种loader就是让 webpack可以识别各种文件践行打包
  • plugin: Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

webpack的构建流程:

  1. 初始化配置参数:拿到config.js和shell脚本的配置信息;
  2. 初始化编译:利用初始化得到的Compiler参数,加载所有的插件信息,开始编译
  3. 确定入口:拿到配置文件中的entry的入口文件地址;
  4. 开始编译:根据拿到的入口文件,利用配置的各种Loader来编译不同模块,递归直到编译了所有的模块信息;
  5. 完成模块编译:使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 根据配置的输出目录,写入到文件系统;

JavaScript

Javascript

执行上下文

什么是执行上下文?

执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行

执行上下文的类型(3中)

  • 全局执行上下文:默认的基础上下文,不再任何函数中的代码就在全局执行上下文中;它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文:函数执行的时候创建的执行上下文
  • eval执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文

执行栈;

执行栈,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

隐式类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

  • -,*,/,%这几个操作符会默认转换成数字
  • +法
    1. string + number = string;
    2. number + string = string;
    3. object + number; 先执行obj的valueOf的方法,没有返回的话 执行toString方法
    4. number + boolean ;true = 1; false = 1; null = 0;
    5. number + undefiend = NAN
  • [1].toString() === '1' true; [1,2].tostring() === '1,2';

题目: 怎么让a === 1 && a===2&& a ===3 为true;

let a = {
    a: 1,
    valueOf: function() {
        return this.a ++;
    }
}
if (a == 1 & a == 2 & a == 3) {
  console.log(1)
}

模块化

  • es6: import / export
  • commonjs: require / module.exports / exports(node)
  • amd: require / defined

import和commonjs区别

  1. require支持动态导入,inport不支持
  2. require是同步的,import是异步的
  3. require导入的数据是值拷贝,但是import是引用的内存地址;

输入 URL 到页面渲染的整个流程

  1. DNS:是通过域名查询到具体的 IP;(智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来)

    1. 现在本地缓存中查询对应的IP
    2. 去DNS服务器中查询
    3. 去DNS根服务器中查询到。com域名的服务器
    4. 进入com域名查询到baidu这个域名
    5. 三级域名一般就是自己配置的域名IP了

    以上的查询方式是迭代查询,还有一种是递归查询方式,区别就是一个是client发起的查询,一个是server发起的查询;DNS的查询方式是基于UDP的

  2. TCP进行链接(TCP的三次握手)

  3. TCP握手结束之后就是HTTP的加密方式TLS握手;

  4. 数据进入到 服务器可能会经过负载均衡的服务器进行分发请求响应

  5. client拿到Html文件

GPU加速原理

浏览器渲染的页面是一个分层的结构类似于千层饼

  1. 浏览器先获取到DOM树根据规则来分割成 多个独立渲染层;
  2. CPU将每个层绘制进绘图中;得到位图上传到GPU
  3. GPU开始渲染;如果下次上传的渲染层没有发生变化,GPU就不需要对其进行重绘,并复合多个渲染层最终形成我们的图像

布局是有CPU处理的,渲染是由GPU处理的;

for in, for of,Object.keys()区别

  1. for-in是遍历对象的key,for-of是遍历对象的val;一个是遍历可枚举属性,一个是迭代一个可迭代属性值;
  2. for-in会遍历原型链,Object.keys不会查询原型链,并且返回是一个数组;

跨域和安全

由于域名、协议和端口号不一致,导致会受到同源策略的限制。

ES6

let, const, var区别

let和var

  • let声明块级作用域;
  • var存在变量声明提升,let不存在;
  • 暂时性死区:ES6规定,如果区块中存在let和const命令,这个区块就是一个块级作用域,在区块内使用let或const声明变量之前,该变量都是不可用的,这称为“暂时性死区”。
  • let不能重复声明,会报错;var可以

const 和 let

  • 常亮的声明;所有let的特性都有,只有一点特殊,const的变量为只读的不能修改

箭头函数、普通函数区别

  • 箭头函数没有prototype(原型),所以箭头函数本身没有this

  • 箭头函数的this指向在==定义==的时候继承自外层第一个普通函数的this

  • 不能直接修改箭头函数的this指向(箭头函数不能利用call和apply和bind改变this的指向;改变不了)

  • 箭头函数的arguments

    • 全局变量定义的箭头函数(全局变量中没有arguments属性)会报错(变量未声明的错误)
    • 在函数当中定义的箭头函数,arguments指向的是函数的arguments;
  • new 调用箭头函数会报错,b is not a constructor;因为箭头函数没有prototype就没有constructor

  • 箭头函数没有new.target(一般构造函数的new.target指向的是自身)

  • 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名(就是函数的参数是相同的;普通函数会覆盖,箭头函数会报错);

整理的一些原理题

  1. call实现(此处省略apply实现)

Function.prototype.myCall = function (env, ...arg) {
    env = env || window;
    let fn = this;
    env.fn = fn;
    let result = env.fn(...arg);
    delete env.fn;
    return result;
};
  1. bind实现
Function.prototype.myBind = function(env, ...arg) {
    env = env || window;
    let fn = this;
    function result (...arg) {
        if (this instanceof result) {
            fn.call(this, ...args.concat(arg));
            result.prototype = fn.prototype;
            return this;
        } else {
            return fn.call(env, ...args.concat(arg));
        }
    }

    return result;
};

  1. 防抖和节流
// 防抖函数
function noShake(fn, time) {
    let timer = null;

    return function (...arg) {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn(...arg);
        }, time * 1000);
    };
}
// 节流利用一个标识符实现
function throttle(fn, time) {
    let flag = true;
    
    return function(...arg) {
        if (!flag) return false;
        flag = false;
        setTimeout(function() {
            fn(...arg);
            flag = true;
        }, time * 1000)
    }
}

  1. instansof原理和实现
// a instansof A 的判断原理就是判断a的原型链上是否存在A.prototype;
function instansof(obj, fn) {
    let _proto = a.__proto__;
    let proto = fn.prototype;
    while(_proto) {
        if (_proto === proto) {
            return true;
        } else {
            _proto = _proto.__proto__;
        }
    }
    retrun false;
}

  1. new实现
/**
    1. 创建一个新的对象;
    2. 改变对象的原型链的指向
    3. 返回这个对象(这个时候需要进行判断,构造函数的返回值是啥);
*/
function myNew(fn, ...arg) {
    let obj = {};
    obj.__proto__ = fn.prototype;
    let result = fn.call(obj, ...arg);
    return typeof result === 'object' ?  result : obj;
} 

  1. Object.create实现:

function create(proto, obj) {
    obj = obj || {};
    function Create() {}
    Create.prototype = proto;
    var Result = Create.bind(obj);
    return new Result();
}

  1. 台阶问题和斐波那契数列(一般写上来了递归版本,但是到100层的时候就会出现卡死性能问题,最好写出来非递归版本感觉像是动态规划)
function step1 (n) {
    if (n === 1) {
        return 1;
    }
    if (n === 2) {
        return 2;
    }
    return step1(n - 1) + step1(n - 2);
}

function step2 (n) {
    if (n <= 0) return 0;
    if (n === 1) return 1;
    if (n === 2) return 2;
    var count = 3;
    var twoStep = 1;
    var oneStep = 2;
    var all = 0;
    while (count <= n) {
        all = oneStep + twoStep;
        twoStep = oneStep;
        oneStep = all;
        count ++;
    }
    return all;
}

  1. 订阅发模式(Vue的双向数据绑定的原理差不多感觉)


  1. promise实现

class Promise {
    constructor(fn) {
        this.status = 'pending';
        this.sArr = [];
        this.fArr = [];
        this.value = '';
        this.err = '';
        fn(resolve.bind(this), reject.bind(this));

        function resolve(data) {
            this.status = 'resolve';
            this.value = data;
            this.sArr.forEach(fcb => {
                fcb(data);
            });
        }
        function reject(err) {
            this.status = 'reject';
            this.err = err;
        }
    }
    then(scb, fcb) {
        let p = null;
        switch (this.status) {
        case 'pending':
            p = new Promise(reslove => {
                this.sArr.push((data) => {
                    let result = scb(data);
                    if (result instanceof Promise) {
                        result.then((data) => {
                            reslove(data);
                        });
                    } else {
                        reslove(result);
                    }
                });
                this.fArr.push(fcb);
            });
            break;
        case 'resolve':
            var result = scb(this.value);
            if (result instanceof Promise) {
                p = result;
            } else {
                p = new Promise((resolve) => {
                    resolve(result);
                });
            }
            break;
        case 'reject':
            fcb(this.err);
            break;
        default:
            break;
        }
        return p;
    }
}


let p = new Promise((resolve) => {
    setTimeout(() => {
        resolve('settimeout1');
    }, 1000);
});

p.then((data) => {
    console.log(data);
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('settimeout2');
        }, 1000);
    });
}).then(data => {
    console.log(data);
    return 3333;
}).then(data => {
    console.log(data);
});
  1. sleep函数:
function sleep(time) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('sleep');
        }, time * 1000);
    });
}

function repeat(count, time, fn) {

    return function(...arg) {
        for (let i = 1; i <= count; i++) {
            sleep((time / 1000) * i).then(() => {
                fn(arg.join(' '));
            });
        }
    };
}

let log3 = repeat(3, 1000, console.log);
log3('hello', 'world', '!');

// 1000ms
// hello world
// 1000ms
// hello world
// 1000ms
// hello world

面试题:

宇宙条

  1. 非对称加密;

HTTPS加密过程

  1. cookie和session的区别;

    cookie和session怎么保持用户登录的:当client第一次请求server的时候,server会生成对应的session和session—id,并把它发送给client端,当client再次请求的时候回携带着这些session信息,server会解析出session—id查询到缓存的session,来确定当前用户的登录状态;

    1. 存储位置:cookie存在client;session存储在服务器端
    2. 失效时间:cookie可以设置失效时间,不设置失效时间会根据当前会话存储,(会话cookie);session在超过一定的操作时间(通常为30分钟)后会失效,但是当关闭浏览器时,为了保护用户信息,会自动调用session.invalidate()方法,该方法会清除掉session中的信息。
    3. 安全性:cookie存储在客户端,所以可以分析存放在本地的cookie并进行cookie欺骗,安全性较低;session存储在服务器上,不存在敏感信息泄漏的风险,安全性较高。
    4. 域支持范围不同:cookie支持跨域名访问。例如,所有a.com的cookie在a.com下都能用;session不支持跨域名访问。例如,www.a.com的session在api.a.com下不能用。
    5. 对服务器压力不同:cookie保存在客户端,不占用服务器资源;session是保存在服务器端,每个用户都会产生一个session,session过多的时候会消耗服务器资源,所以大型网站会有专门的session服务器。
  2. web worker;(我没用过不太熟悉);

  3. 实现一个new 函数;

function myNew(fn, options) {
    var obj = {};
    fn.call(obj, options);
    // 改变原型链指向要改变
    obj.__proto__ = fn.prototype;
    return obj;
}

function Cat(options) {
    this.name = options.name;
    this.age = options.age;
}

let cat = myNew(Cat, {
    name: 'cat',
    age: 12
});

console.log(cat);

  1. 计数调度器,保持多个请求并发;
// 解法一;维持一个队列或者栈,将栈的长度时刻保持在传递的任务数得长度;
function send(stask) {
    let task = new Promise((resolve) => {
        setTimeout(() => {
            resolve('test');
        }, 1000);
    });
    stask.add(task);
    task.then((data) => {
        console.log(data);
        stask.delete(task);
        send(stask);
    });
}

// 写一个调度器
function allSend(num) {
    let count = 0;
    let stask = new Stack();
    while(count < num) {
        count ++;
        send(stask);
    }
}

class Stack {
    constructor() {
        this.arr = [];
    }
    add(item) {
        this.arr.push(item);
    }
    delete(item) {
        if (item) {
            let index = this.arr.indexOf(item);
            this.arr.splice(index, 1);
        } else {
            this.arr.pop();
        }
    }
}

// allSend(3);

--------------------------------------------------------------------------------------
// 解法二;写一个类,利用promise的then来写;关键步骤为68行的添加resolve;和79行的执行;

class Scheduler {
    constructor(num) {
        this.runningTaskNum = num;
        // 总任务数组
        this.tasks = [];
        // 正在执行任务数组;
        this.runningTasks = [];
    }
    add(task) {
        return new Promise((resolve) => {
            // 此处需要缓存任务的resolve;当任务执行完毕之后执行resolve让外部接收到这次任务的数据;
            task.resolve = resolve;
            if (this.runningTasks.length < this.runningTaskNum) {
                this.startTask(task);
            } else {
                this.tasks.push(task);
            }
        });
    }
    startTask(task) {
        this.runningTasks.push(task);
        task().then((data) => {
            task.resolve(data);
            this.moveTask(task);
        });
    }
    moveTask(task) {
        if (this.tasks.length >= 1) {
            this.startTask(this.tasks.shift());
        } else {
            let index = this.runningTasks.indexOf(task);
            this.runningTasks.splice(index, 1);
        }
    }
}

const timeout = (time, order) => {
    return new Promise(resolve => {
        setTimeout(function () {
            resolve(order);
        }, time);
    });
};

const scheduler = new Scheduler(2);

const addTask = (time, order) => {
    scheduler.add(
        () => timeout(time, order)
    ).then(function (data){
        console.log(data, new Date().getUTCSeconds());
    });
};

addTask(1000, 4);
addTask(2000, 2);
addTask(1000, 3);
addTask(1000, 1);
addTask(1000, 5);

  1. Vue的Ui渲染是异步的还是同步的;

异步

  1. Vue.$nextTick()有啥性能优化点;

我感觉像是防抖函数()

  1. webpack的优化,从打包速度到打包体积大小;
  • 打包时间上

    1. 优化 Loader:优化 Loader 的文件搜索范围,因为node_moudles中的文件都是打包之后的没必要再用loader搜索一遍了

    2. HappyPack;因为node是单线程所以loader的执行是同步的,多个loader同步执行是很慢的;HappyPack 可以将 Loader 的同步执行转换为并行的

      module: {
        loaders: [
          {
            test: /\.js$/,
            include: [resolve('src')],
            exclude: /node_modules/,
            // id 后面的内容对应下面
            loader: 'happypack/loader?id=happybabel'
          }
        ]
      },
      plugins: [
        new HappyPack({
          id: 'happybabel',
          loaders: ['babel-loader?cacheDirectory'],
          // 开启 4 个线程
          threads: 4
        })
      ]
      
      
    3. DllPlugin:可以将特定的类库提前打包然后引入(一般就是第三方的库比如Vue全家桶系列)

    4. 代码压缩:webpack3时利用UglifyJS打包代码,可以webpack-parallel-uglify-plugin插件来并行打包过程; webpack4不需要这样只需要指定mode是production就可以默认开启;压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

  • 减少打包文件体积

    1. 按需加载
    2. Scope Hoisting:分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
    3. Tree Shaking:删除项目中未被引用的代码
    // test.js
    export const a = 1
    export const b = 2
    // index.js
    import { a } from './test.js'
    
    // 开启Tree Shaking 打包;b没有被引用就不会打包;
    
    1. Vue Cli3.0脚手架打包可以开启现代模式--modern:Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器。
  • 头条面试自己没有准备好,第一次面试就选了头条有点难受

某未来

  1. Vue的双向数据绑定的原理;
  2. Vue的Template渲染到html中Vue是怎么处理的;

    三个步骤

    • parse(解析)将拿到的 Template的模板解析,生成AST语法树
    • optimize(优化)将template中静态的标签打上标记,优化下一次数据更改的diff算法进行虚拟DOM比对时跳过这些静态标签
    • generate(生成)generate 会将 AST 转化成 render funtion 字符串,最终得到 render 的字符串以及 staticRenderFns 字符串。
  3. webpack的了解程度(原理一些类)
  4. Hybrid了解程度;(JSBrdige)(我接触的比较少,所以总结了下);
    1. JSBridge就是NA端与JS通信的桥梁;
      • JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。
      • Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
    2. JSBridge 的通信原理
      1. JavaScript 调用 Native的方式
        • 注入API
        • 拦截URL SCHEME

某滴

  1. vue-router利用history路由模式需要后端做什么?

    需要配置统一子路由返回根文件

  2. new一个构造函数如果构造函数中return 了一个对象会返回啥,return了一个基本类型呢,会返回啥

    构造函数返回一个对象的话,new 返回的是当前return的对象;构造函数return一个基本类型,new返回的是this;

  3. 算法题。输入一个数组和一个值查找到数组中的两项相加等于输入的值;

  4. z-index的比较规则;

  5. flex的三个属性:

    • flex-grow
    • flex-shrink
    • flex-basis:min-width/max-width > flex-basis > width
  6. for-in和for-of的区别:

    • for-in遍历可枚举的属性
    • for-of遍历可迭代对象,生成一个迭代循环输出值
    • 都会遍历原型链
  7. 什么时候post会发起options请求; 简单请求和复杂请求;最好去看一下哪种情况会触发options请求;options请求在干啥;

某手(上来就是撸代码,比较直接个人比较喜欢);

  1. 请实现一个cacheRequest方法,保证当使用ajax(请求相同资源时, 此题中相同资源的判断是以url为判断依据),真实网络层中, 实际只发出一次请求(假设已存在request方法用于封装ajax请求,调用格式为 :request(url, successCallback, failCallback)

比如调用方代码(并行请求)如下

// a.js cacheRequest('/user', data => { console.log('我是从A中请求的user,数据为' + data); })

// b.js cacheRequest('/user', data => { console.log('我是从B中请求的user,数据为' + data); })

// 1.
   var a = { x: 1 };
   var b = a;
   a.x = a = { n: 1 };
   console.log(a);
   console.log(b);

// 2.
   Function.prototype.a = () => alert(1);
   Object.prototype.b = () => alert(2);
   function A() {}
   const a = new A();
   a.a();
   a.b();

// 3.
   console.log(a);
   var a = function() {};

console.log(xx)
window.xx = 1
window.xx = 2

   
   console.log(b);
   let b =  function() {};
   
   console.log(c);
   function c() {}

// 4.
   var x = 10;
   function a(y) {
     var x = 20;
     return b(y);
   }
   
   function b(y) {
     return x + y
   }
   
   a(20);

  1. Promise.allSettled实现;
  2. 在排序好的数组数字可重复里查找某一个数值的最大索引;(二分法 );
  3. 数组展平,需要传递层级;

最后;

一般的面试围绕着项目去讲解;我的技术站用的Vue比较多,所以vue原理需要了解比较多一些;

  • 双向数据绑定的原理;(对象和数组)
  • nexttick原理
  • vueRouter原理:哈希模式和histroy模式;
  • vueRouter的导航守卫的原理;
  • Vuex的原理
  • SSR的原理;为什么An的项目不能ssr(An没有虚拟DOM);

个人推荐两本掘金的小册子: - 前端面试之道(yck) - 剖析 Vue.js 内部运行机制(染陌同学)

建议:

  • 小册子只是一个大纲,有的点讲解的比较粗糙(但是小册子还是很值的),可以自己百度谷歌这一部分内容进行深一步的了解;

  • Vue的原理相关可以结合小册子去看一下Vue技术揭秘

  • 算法相关的推荐一本书吧学习Javascript数据结构与算法,提取码:csxc;其他的请看力扣

祝大家都有能找到合适的工作;(别忘了点个关注);