面试题收集

177 阅读7分钟

浏览器输入URL的全过程   TCP/IP协议与HTTP协议

TCP (传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议

HTTP(超文本传输协议)是利用TCP在两台电脑(通常是Web服务器和客户端)之间传输信息的协议

TCP协议对应于传输层,而HTTP协议对应于应用层,Http会通过TCP建立起一个到服务器的连接通道, 当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。从HTTP/1.1起,  默认开启Connection: keep-alive,保持长连接特性,但依然是无状态连接。

浏览器中的事件循环

闭包

1. 在实践开发中,有一种优化手段叫做记忆函数

当我们使用useMemo/useCallback时,由于新增了对于闭包的使用,新增了对于依赖项的比较逻辑,因此,盲目使用它们,甚至可能会让你的组件变得更慢。

2. 防抖:(闭包举例)

/*****************************简化后的分割线 ******************************/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置"

此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。到这里,已经把防抖实现了,现在给出定义:

防抖:对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

节流:其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

segmentfault.com/a/119000001…

this 指向

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数被某一个对象所拥有, 那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。

new的创建过程

function _new(fn, ...arg) {
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj, arg);
    return ret instanceof Object ? ret : obj;
}

function _new(fn , ...args){
	const obj={}
	const Constructor = fn
	obj.__proto__ = Constructor.prototype
	const result = Constructor.call(obj , ...args)
	return typeof result === "object" ? result : obj
}
  • 首先创建一个空的对象,空对象的__proto__属性指向构造函数的原型对象
  • 执行构造函数,修改this指向
  • 如果构造函数返回一个非基本类型的值,则返回这个值,否则上面创建的对象

 子类续承父类 

 Child.prototype = Object.create(Parent.prototype); 
 Child.prototype.constructor = Child;

自己实现一个instanceof

检查一个对象的的原型链上有没有这个类的,

JS中面向对象以及 __proto__,ptototype 和 constructor juejin.cn/post/684490…

function myInstanceof(targetObj, targetClass) {
  // 参数检查
  if(!targetObj || !targetClass || !targetObj.__proto__ || !targetClass.prototype){
    return false;
  }

  let current = targetObj;

  while(current) {   // 一直往原型链上面找
    if(current.__proto__ === targetClass.prototype) {
      return true;    // 找到了返回true
    }

    current = current.__proto__;
  }

  return false;     // 没找到返回false
}

// 用我们前面的继承实验下
function Parent() {}
function Child() {}

Child.prototype.__proto__ = Parent.prototype;

const obj = new Child();
console.log(myInstanceof(obj, Child) );   // true
console.log(myInstanceof(obj, Parent) );   // true
console.log(myInstanceof({}, Parent) );   // false作者:蒋鹏飞链接:https://juejin.cn/post/6844904069887164423来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Generator 函数

next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

async函数它就是 Generator 函数的语法糖,就是将 Generator 函数的星号(*)替换成async,将yield替换成await
async function gen(x) {
var y = await x + 2;
return y;
};
详细:es6.ruanyifeng.com/#docs/async

vue 你怎么理解vue中的diff算法?

源码分析1:必要性,lifecycle.js - mountComponent() 

                     组件中可能存在很多个data中的key使用
源码分析2:执行方式,patch.js - patchVnode()
patchVnode是diff发生的地方,整体策略:深度优先,同层比较 源码分析

3:高效性,patch.js - updateChildren()

1.diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即diff),将变化的地方更新在真 实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。

2.vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到 发生变化的地方。
3.vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染 结果newVnode,此过程称为patch。

4.diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文 本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果 没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非 常精确找到相同节点,因此整个patch过程非常高效。

vue extend/$mount/el

//Vue构造器var MyComponent = Vue.extend({
  template: '<div>Hello!</div>'
})

// 1. 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')

// 2. 同上
new MyComponent({ el: '#app' })

// 3. 在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)

React虚拟dom

虚拟DOM:js对象来描述真实的DOM,编程概念,以最低的DOM操作的成本,新旧对比,达到最少的DOM操作量,最少更新

diff虚拟DOM对象,批量的最小化的操作dom

为什么用Jsx :原理:babel-loader会预编译JSX为React.createElement(...)

React.createElement:创建虚拟DOM

React.Component:实现自定义组件

ReactDOM.render:渲染真实DOM

pureComponent内置了shouldComponetUpdate

React 生命周期:

1. 挂载卸载过程

  • constructor()
  • getDerivedStateFromProps
  • render()
  • componentDidmount ()

2. 更新过程

  • getDerivedStateFromProps(nextProps, prevState)   代替componentWillReceiveProps()
  • shouldComponentUpdate(nextProps,nextState)
  • render()
  • getSnapshotBeforeUpdate(prevProps, prevState)    代替componentWillUpdate。
  • componentDidUpdate(prevProps,prevState)

3.卸载

  • componentWillUnmount ()

4.错误处理

  • static getDerivedStateFromError()
  • componentDidCatch();

react优化

  1. 使用 memo 来缓存组件

  2. 使用 useMemo 缓存大量计算

  3. 使用 PureComponent、shouldComponentUpdate

  4. 避免使用内联对象

  5. 避免使用匿名函数

  6. 延迟加载不是立即需要的组件:通过利用 React.lazy 和 React.Suspense 可以轻松完成按需加载

  7. 调整 CSS 而不是强制组件加载和卸载

浅拷贝的实现

1.简单的引用复制

2.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);     // true

深拷贝

1.JSON对象的parse和stringify

对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。同时如果对象中存在循环引用的情况也无法正确处理。

2.还有一种实现深拷贝的方法 就是 lodash里面的 _.cloneDeep

3.手动实现

function cloneDeep(obj) { 
    // 处理3中简单类型 null or undefined or function  
    if (obj== null || typeof obj != "object") return obj;    
    // 处理 Date  
    if (obj instanceof Date) {      
        var copy = new Date();     
        copy.setTime(obj.getTime());      
        return copy;  
    }  
    // 处理 Array or Object  
    if (obj instanceof Array | obj instanceof Object) {
        var copy = (obj instanceof Array)?[]:{};     
        for (var attr in obj) {         
           if (obj.hasOwnProperty(attr))             
           copy[attr] = cloneDeep(obj[attr]);             }     
        return copy;
    } 
    throw new Error("Unable to clone obj! Its type isn't supported.");
}

实现 Promise.all 方法

总结 promise.all 的特点

1、接收一个 Promise 实例的数组,

2、如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象

3、如果全部成功,状态变为 resolved,返回值将组成一个数组传给回调

4、只要有一个失败,状态就变为 rejected,返回值将直接传递给回调all() 的返回值也是新的 Promise 对象

Promise.myAll = function(iterators) {
  const promises = Array.from(iterators)
  const num = promises.length
  let count = 0
  let resolvedValue = new Array(num)
  return new Promise((resolve, reject) => {
    for(let item of promises){
      Promise.resolve(item)
        .then(data => {
          // 保存这个promise实例的value
          resolvedValue[count++] = data
          // 通过计数器,标记是否所有实例均 fulfilled
          if(count === num){
            resolve(resolvedValue)
          }
        })
        .catch(err => reject(err))
    }
  })
}
Promise.myAll([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});

数组拍平

Array.prototype.flatt = function(){
let arr = [];
this.forEach(item => {
   if(item instanceof Array){
       arr = arr.concat(item.flatt());
    }else{
       arr.push(item);
    }
})
return arr;
}
const arr = [2,[2,3,4,[5,6]]];
console.log(arr.flatt());

数组去重写法

  • [...new Set([2,"12",2,12,1,2,1,6,12,13,6])]  //[2, "12", 12, 1, 6, 13] es6的新特性
  • Array.from(new Set([1, 1, 2, 2]))