浏览器缓存机制
浏览器读取缓存的步骤是:本地 -》线上
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 存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 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寻找文件的规则:
基本的模块解析规则:
- 解析相对路
- 寻找到当前模块对应的路径是否有文件或者文件夹
- 有文件直接加载文件
- 是文件夹的话,寻找文件中是否有package.json文件
- 有package.json文件则找到mian入口加载文件
- 无package.json文件或者无main字段则默认加载index.js文件
- 解析模块名
- 查找当前文件目录下,父级目录及以上目录下的 node_modules 文件夹,看是否有对应名称的模块;
- 解析绝对路径(一般不建议使用)
- 直接查找对应路径的文件
相关的配置属性reslove配置
- resolve.alias:配置别名
- resolve.extensions 配置文件查找后缀顺序;
webpack的loader
-
同一个rule配置loader的执行顺序;
从最后配置的load而开始向上执行;
-
两个rule配置同一种文件的执行规则;
不确定的执行顺序,需要固定执行顺序的话可以利用enforce字段;
-
所有的 loader 按照前置 -> 行内 -> 普通 -> 后置的顺序执行
- 前置:enforce:pre;
- 行内:const json = require('json-loader!./file.json')写在文件中;
- 普通就是经常写的普通的配置
- 后置:enforce: post;
写一个plugin插件
- plugin是一个类;
- plugin 实例中最重要的方法是 apply,该方法在 webpack compiler 安装插件时会被调用一次;
- apply的方法中会接收到compiler的webpack的编译对象
- 根据compiler对象可利用compiler.hooks获取到不同webpack的生命周期钩子
- compiler.hooks[不同的钩子]来 注册你的扩展动作;
- hooks有同步的还有异步的;
- 同步的钩子事件绑定:
- 异步的钩子时间绑定:
- hooks有同步的还有异步的;
loader和plugin的区别:
- loader :webpack其实只有识别,打包JS文件的,各种loader就是让 webpack可以识别各种文件践行打包
- plugin: Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果
webpack的构建流程:
- 初始化配置参数:拿到config.js和shell脚本的配置信息;
- 初始化编译:利用初始化得到的Compiler参数,加载所有的插件信息,开始编译
- 确定入口:拿到配置文件中的entry的入口文件地址;
- 开始编译:根据拿到的入口文件,利用配置的各种Loader来编译不同模块,递归直到编译了所有的模块信息;
- 完成模块编译:使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 根据配置的输出目录,写入到文件系统;
JavaScript
Javascript
执行上下文
什么是执行上下文?
执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行
执行上下文的类型(3中)
- 全局执行上下文:默认的基础上下文,不再任何函数中的代码就在全局执行上下文中;它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
- 函数执行上下文:函数执行的时候创建的执行上下文
- eval执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文
执行栈;
执行栈,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
隐式类型转换
大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:
- -,*,/,%这几个操作符会默认转换成数字
- +法
- string + number = string;
- number + string = string;
- object + number; 先执行obj的valueOf的方法,没有返回的话 执行toString方法
- number + boolean ;true = 1; false = 1; null = 0;
- 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区别
- require支持动态导入,inport不支持
- require是同步的,import是异步的
- require导入的数据是值拷贝,但是import是引用的内存地址;
输入 URL 到页面渲染的整个流程
-
DNS:是通过域名查询到具体的 IP;(智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来)
- 现在本地缓存中查询对应的IP
- 去DNS服务器中查询
- 去DNS根服务器中查询到。com域名的服务器
- 进入com域名查询到baidu这个域名
- 三级域名一般就是自己配置的域名IP了
以上的查询方式是迭代查询,还有一种是递归查询方式,区别就是一个是client发起的查询,一个是server发起的查询;DNS的查询方式是基于UDP的
-
TCP进行链接(TCP的三次握手)
-
TCP握手结束之后就是HTTP的加密方式TLS握手;
-
数据进入到 服务器可能会经过负载均衡的服务器进行分发请求响应
-
client拿到Html文件
GPU加速原理
浏览器渲染的页面是一个分层的结构类似于千层饼
- 浏览器先获取到DOM树根据规则来分割成 多个独立渲染层;
- CPU将每个层绘制进绘图中;得到位图上传到GPU
- GPU开始渲染;如果下次上传的渲染层没有发生变化,GPU就不需要对其进行重绘,并复合多个渲染层最终形成我们的图像
布局是有CPU处理的,渲染是由GPU处理的;
for in, for of,Object.keys()区别
- for-in是遍历对象的key,for-of是遍历对象的val;一个是遍历可枚举属性,一个是迭代一个可迭代属性值;
- 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指向的是自身)
-
箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名(就是函数的参数是相同的;普通函数会覆盖,箭头函数会报错);
整理的一些原理题
- 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;
};
- 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;
};
- 防抖和节流
// 防抖函数
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)
}
}
- 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;
}
- 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;
}
- Object.create实现:
function create(proto, obj) {
obj = obj || {};
function Create() {}
Create.prototype = proto;
var Result = Create.bind(obj);
return new Result();
}
- 台阶问题和斐波那契数列(一般写上来了递归版本,但是到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;
}
- 订阅发模式(Vue的双向数据绑定的原理差不多感觉)
- 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);
});
- 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
面试题:
宇宙条
- 非对称加密;
-
cookie和session的区别;
cookie和session怎么保持用户登录的:当client第一次请求server的时候,server会生成对应的session和session—id,并把它发送给client端,当client再次请求的时候回携带着这些session信息,server会解析出session—id查询到缓存的session,来确定当前用户的登录状态;
- 存储位置:cookie存在client;session存储在服务器端
- 失效时间:cookie可以设置失效时间,不设置失效时间会根据当前会话存储,(会话cookie);session在超过一定的操作时间(通常为30分钟)后会失效,但是当关闭浏览器时,为了保护用户信息,会自动调用session.invalidate()方法,该方法会清除掉session中的信息。
- 安全性:cookie存储在客户端,所以可以分析存放在本地的cookie并进行cookie欺骗,安全性较低;session存储在服务器上,不存在敏感信息泄漏的风险,安全性较高。
- 域支持范围不同:cookie支持跨域名访问。例如,所有a.com的cookie在a.com下都能用;session不支持跨域名访问。例如,www.a.com的session在api.a.com下不能用。
- 对服务器压力不同:cookie保存在客户端,不占用服务器资源;session是保存在服务器端,每个用户都会产生一个session,session过多的时候会消耗服务器资源,所以大型网站会有专门的session服务器。
-
web worker;(我没用过不太熟悉);
-
实现一个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);
- 计数调度器,保持多个请求并发;
// 解法一;维持一个队列或者栈,将栈的长度时刻保持在传递的任务数得长度;
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);
- Vue的Ui渲染是异步的还是同步的;
异步
- Vue.$nextTick()有啥性能优化点;
我感觉像是防抖函数()
- webpack的优化,从打包速度到打包体积大小;
-
打包时间上
-
优化 Loader:优化 Loader 的文件搜索范围,因为node_moudles中的文件都是打包之后的没必要再用loader搜索一遍了
-
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 }) ] -
DllPlugin:可以将特定的类库提前打包然后引入(一般就是第三方的库比如Vue全家桶系列)
-
代码压缩:webpack3时利用UglifyJS打包代码,可以webpack-parallel-uglify-plugin插件来并行打包过程; webpack4不需要这样只需要指定mode是production就可以默认开启;压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。
-
-
减少打包文件体积
- 按需加载
- Scope Hoisting:分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
- Tree Shaking:删除项目中未被引用的代码
// test.js export const a = 1 export const b = 2 // index.js import { a } from './test.js' // 开启Tree Shaking 打包;b没有被引用就不会打包;- Vue Cli3.0脚手架打包可以开启现代模式--modern:Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器。
-
头条面试自己没有准备好,第一次面试就选了头条有点难受
某未来
- Vue的双向数据绑定的原理;
- Vue的Template渲染到html中Vue是怎么处理的;
三个步骤
- parse(解析)将拿到的 Template的模板解析,生成AST语法树
- optimize(优化)将template中静态的标签打上标记,优化下一次数据更改的diff算法进行虚拟DOM比对时跳过这些静态标签
- generate(生成)generate 会将 AST 转化成 render funtion 字符串,最终得到 render 的字符串以及 staticRenderFns 字符串。
- webpack的了解程度(原理一些类)
- Hybrid了解程度;(JSBrdige)(我接触的比较少,所以总结了下);
- JSBridge就是NA端与JS通信的桥梁;
- JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
- JSBridge 的通信原理
- JavaScript 调用 Native的方式
- 注入API
- 拦截URL SCHEME
- JavaScript 调用 Native的方式
- JSBridge就是NA端与JS通信的桥梁;
某滴
-
vue-router利用history路由模式需要后端做什么?
需要配置统一子路由返回根文件
-
new一个构造函数如果构造函数中return 了一个对象会返回啥,return了一个基本类型呢,会返回啥
构造函数返回一个对象的话,new 返回的是当前return的对象;构造函数return一个基本类型,new返回的是this;
-
算法题。输入一个数组和一个值查找到数组中的两项相加等于输入的值;
-
z-index的比较规则;
-
flex的三个属性:
- flex-grow
- flex-shrink
- flex-basis:min-width/max-width > flex-basis > width
-
for-in和for-of的区别:
- for-in遍历可枚举的属性
- for-of遍历可迭代对象,生成一个迭代循环输出值
- 都会遍历原型链
-
什么时候post会发起options请求; 简单请求和复杂请求;最好去看一下哪种情况会触发options请求;options请求在干啥;
某手(上来就是撸代码,比较直接个人比较喜欢);
- 请实现一个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);
- Promise.allSettled实现;
- 在排序好的数组数字可重复里查找某一个数值的最大索引;(二分法 );
- 数组展平,需要传递层级;
最后;
一般的面试围绕着项目去讲解;我的技术站用的Vue比较多,所以vue原理需要了解比较多一些;
- 双向数据绑定的原理;(对象和数组)
- nexttick原理
- vueRouter原理:哈希模式和histroy模式;
- vueRouter的导航守卫的原理;
- Vuex的原理
- SSR的原理;为什么An的项目不能ssr(An没有虚拟DOM);
个人推荐两本掘金的小册子: - 前端面试之道(yck) - 剖析 Vue.js 内部运行机制(染陌同学)
建议:
-
小册子只是一个大纲,有的点讲解的比较粗糙(但是小册子还是很值的),可以自己百度谷歌这一部分内容进行深一步的了解;
-
Vue的原理相关可以结合小册子去看一下Vue技术揭秘;
-
算法相关的推荐一本书吧学习Javascript数据结构与算法,提取码:csxc;其他的请看力扣
祝大家都有能找到合适的工作;(别忘了点个关注);