手写深拷贝?
- 递归、判断类型、检查环(也叫循环引用)、需要忽略原型
//用JSON方式实现: 使用JSON.stringify()以及JSON.parse()
//它是不可以拷贝 undefined , function, RegExp 等等类型的
var obj = {
a:1,
b:2,
c:3
}
var objString = JSON.stringify(obj);
var object = JSON.parse(objString);
//Object.assign(target,source):只能实现一层对象的深拷贝,支持不了对象嵌套
var obj1 = {
a:1,b:2,c:3
}
var obj2 = Object.assign({},obj1);
//递归拷贝
function deepClone(target){
let res;
if(typeof tartget === 'object'){
if(Array.isArray(target)){
res = [];
for(let i in target){
res.push(deepClone(target[i]));
}
}else if(target === null || target === undefined){
res = target;
}else{
res = {};
for(let i in target){
res[i] = deepClone(target[i]);
}
}
}else{
res = target;
}
return res;
}
手写节流函数、手写防抖函数
//节流函数:一段时间执行一次之后,就不执行第二次
//注意,有些地方认为节流函数不是立刻执行的,而是在冷却时间末尾执行的(相当于施法有吟唱时间),那样说也是对的
function throttle(fn,delay){
let canUse = true;
return function(){
if(canUse){
fn.apply.call(this,arguments);
canUse = false;
setTimeout(()=>canUse=true,delay);
}
}
}
const throttled = throttle(()=>console.log('hi'));
throttled();
//防抖函数:一段时间会等,然后带着一起做了
function debounce(fn,delay){
let timerId = null;
return function(){
const content = this;
if(timerId){
window.clearTimeout(timerId);
}
timerId = setTimeout(()=>{
fn.apply(content,arguments);
timerId = null;
},delay)
}
}
const debounced = debounce(()=>console.log('hi'))
debounced()
手写斐波那契数列并优化
- 斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, …
- 在种子数字 0 和 1 之后,后续的每一个数字都是前面两个数字之和
//使用递归计算大数字时,性能会非常低
function fibonacci(n){
if(n === 0 || n === 1) return n;
return fibonacci(n-1)+fibonacci(n-2);
}
//对中间求得的变量值,进行存储的话,就会大大减少函数被调用的次数
le fibonacci1 = function(){
let arr = [0,1];
return function(n){
let result = arr[n];
if(typeof result !== 'number'){
result = fibonacci1(n-1) + fibonacci1(n-2);
arr[n] = result;
}
return result;
}
}();
//递推法
function fibonacci2(n){
let current = 0;
let next = 1;
let temp;
for(let i = 0 ; i < n; i++){
temp = current;
current = next;
next += temp;
}
console.log(`fibonacci(${n}, ${next}, ${current + next})`);
return current;
}
function fibonacci3(n) {
let current = 0;
let next = 1;
for(let i = 0; i < n; i++) {
[current, next] = [next, current + next];
}
return current;
}
//尾调用优化:在ES6规范中,有一个尾调用优化,可以实现高效的尾递归方案 ,ES6的尾调用优化只在严格模式下开启,正常模式是无效的
//尾调用:某个函数的最后一步是调用另一个函数
'use strict';
function fib(n,current = 0,next = 1){
if(n === 0) return 0;
if(n === next) return next;
return fib(n-1,next,current+next);
}
手写flat
//先不考虑深度问题,实现方式如下:
var flat = function(arr){
let res = [],
flatMap = (arr)=>{
arr.map((element,index,array)=>{
if(Object.prototype.toString.call(element).slice(8,-1) === 'Array'){
flatMap(element);
}else{
res.push(element);
}
})
}
flatMap(arr);
return res;
}
//深度实现
var flat1 = function(arr,depth){
let res = [],depthArg = depth || 1,
depthNum = 0,
flatMap = (arr)=>{
arr.map((element,index,array)=>{
if(Object.prototype.toString.call(element).slice(8,-1) === 'Array'){
if(depthNum < depthArg){
depthNum++;
flatMap(element);
}else{
res.push(element);
}
}else{
res.push(element);
if(index === array.length - 1) depthNum = 0;
}
})
}
flatMap(arr);
return res;
}
手写bind
//先在函数原型上建立一个名为bind1的函数:
Function.prototype.bind1 = function(){
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
//获取this(数组的第一项)
const t = args.shift();
//找到fn1.bind(...)中的this,赋给self
const self = this;
//处理返回的函数
return function(){
return self.apply(t,args);
}
}
function fn(a,b,c){
console.log('this', this); // { x: 100 }
console.log(a, b, c); // 10 20 30
return 'this is fn';
}
const fn1 = fn.bind1({ x: 100 }, 10, 20, 30);
const res = fn1();
console.log(res); // this is fn1
http2和http1.1的区别?
- HTTP1.0和HTTP1.1的区别
- 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
- 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接
- 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除
- Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
- 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点
- HTTPS与HTTP的区别
- HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费
- HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的
- HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
- HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题
- http2和http1.1的区别
- 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮
- 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
- header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
- 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能
tcp和udp的区别?
- 1.基于连接与无连接;
- 2.对系统资源的要求(TCP较多,UDP少)
- 3.UDP程序结构较简单
- 4.流模式与数据报模式
- 5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
TCP三次握手过程
- 第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
- 第二次握手:主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
- 第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。
3次握手的特点
没有应用层的数据 ,SYN这个标志位只有在TCP建立连接时才会被置1 ,握手完成后SYN标志位被置0
TCP断开进行四次握手
第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;
第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。
es module和common.js的区别
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJs 是单个值导出,ES6 Module可以导出多个
- CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层
- CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined