zoukankan      html  css  js  c++  java
  • 面试十题(2)

    1. JS 异步解决方案的发展历程以及优缺点。

    1. 回调函数(callback)
    setTimeout(() => {
        // callback 函数体
    }, 1000)
    
    • 优点:解决了同步的问题(同步问题,只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)
    • 缺点:回调地狱,不能用 try catch 捕获错误,不能 return
    ajax('XXX1', () => {
        // callback 函数体
        ajax('XXX2', () => {
            // callback 函数体
            ajax('XXX3', () => {
                // callback 函数体
            })
        })
    })
    
    1. Promise
    • Promise就是为了解决callback的问题而产生的。
    • Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
    • 优点:解决了回调地狱的问题
    ajax('XXX1')
      .then(res => {
          // 操作逻辑
          return ajax('XXX2')
      }).then(res => {
          // 操作逻辑
          return ajax('XXX3')
      }).then(res => {
          // 操作逻辑
      })
    
    • 缺点:无法取消 Promise ,错误需要通过回调函数来捕获
    1. Generator
    • 特点:可以控制函数的执行,可以配合 co 函数库使用
    function *fetch() {
        yield ajax('XXX1', () => {})
        yield ajax('XXX2', () => {})
        yield ajax('XXX3', () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    
    1. async/await
    • async、await 是异步的终极解决方案
    • await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。
    • 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    • 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。可以使用Promise.all
    async function test() {
      // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
      // 如果有依赖性的话,其实就是解决回调地狱的例子了
      await fetch('XXX1')
      await fetch('XXX2')
      await fetch('XXX3')
    }
    
    let a = 0
    let b = async () => {
      a = a + await 10
      console.log('2', a)
    }
    b()
    a++
    console.log('1', a)
    
    // 结果
    1 1
    2 10
    

    2. new操作符做了什么?如何实现一个 new?

    • 做了什么
      1. 创建了一个空的js对象(即{})
      2. 将空对象的原型prototype指向构造函数的原型
      3. 将空对象作为构造函数的上下文(改变this指向)
      4. 对构造函数有返回值的判断
    • 怎么实现
    function _new(constructor, ...args){
        // 1、创建一个空的对象
        // let obj = Object.create({})
        let obj = {} 
        // 2、将空对象的原型prototype指向构造函数的原型
        // Object.setPrototypeOf(obj, constructor.prototype)
        obj.__proto__ = constructor.prototype
        //3、改变构造函数的上下文(this),并将剩余的参数传入
        let result = constructor.apply(obj, args)
        //4、在构造函数有返回值的情况进行判断
        // return Object.prototype.toString.call(result) === '[object Object]' ? result : obj;
        return result instanceof Object ? result : obj
    }
    

    3. 谈谈你对 TCP 三次握手和四次挥手的理解

    • 三次握手: 检测双方都有 发送和接收 数据的能力
    AB你好,我是A收到,我是B好的,开始连接AB

    A:喂喂喂,我是A,你听的到吗?

    B:在在在,我能听到,我是B,你能听到我吗?

    A:听到了。我们今天去钓鱼吧。。balabala

    • 四次挥手: A等待2MSL后(大概4分钟),无回复,关闭
    AB你好,我要关了。稍等,还有最后一个包。我已经好了,随时关闭。你关闭吧,不用回复。AB
    • 为什么A进入TIME_WAIT需要等待最大报文段生存的时间后,才能关闭?

    原因是,担心网络不可靠而导致的丢包,最后一个回应B的ACK万一丢了怎么办,在这个时间内,A是可以重新发包的,但是超过了最大等待时间的话,就算收不到也没用了,所以就可以关闭了。

    4. React 中 setState 什么时候是同步的,什么时候是 异步的?

    • 由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更 新 state 。
    • React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事 件,setTimeout/setInterval 等。
    • 注意: setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
    • this.setState()接收 两种参数:对象或函数

    对象:即想要修改的state

    函数:接收两个函数,第一个函数接受两个参数,第一个是当前state,第二个是当前props,该函数返回一个对象,和直接传递对象参数是一样的,就是要修改的state;第二个函数参数是state改变后触发的回调。

    constructor() {
      this.state = {
        count: 10
      }
    }
    
    handleClick() {
      this.setState({
        count: this.state.count + 1
      })
      this.setState({
        count: this.state.count + 1
      })
      this.setState({
        count: this.state.count + 1
      })
    }
    // 最终的结果只加了1
    // setState提供了函数式用法,接收两个函数参数,第一个函数调用更新state,第二个函数是更新完之后的回调。
    
    increment(state, props) {
      return {
        count: state.count + 1
      }
    }
    
    handleClick() {
      this.setState(this.increment)
      this.setState(this.increment)
      this.setState(this.increment)
    }
    // 结果: 13
    // 对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。
    
    // 也就是说,第一次调用this.setState(increment),传给incrementstate参数的count10,第二次调用是11,第三次调用是12,最终handleClick执行完成后的结果就是this.state.count变成了13。
    
    
    // 值得注意的是:在increment函数被调用时,this.state并没有被改变,依然要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变,因为render只执行一次。
    

    5. React setState 笔试题,下面的代码输出什么?

    class Example extends React.Component {
      constructor() {
        super();
        this.state = {
          val: 0
        };
      }
      
      componentDidMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 1 log
    
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 2 log
    
        setTimeout(() => {
          this.setState({val: this.state.val + 1});
          console.log(this.state.val);  // 3 log
    
          this.setState({val: this.state.val + 1});
          console.log(this.state.val);  // 4 log
        }, 0);
      }
    
      render() {
        return null;
      }
    };
    
    // 0012
    

    6. 有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()

    1. Object.prototype.toString.call()

    每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用call或者apply方法来改变toString方法的执行上下文。

    Object.prototype.toString.call() 常用于判断浏览器内置对象时。

    const a = ['1','2'];
    a.toString(); // "1,2"
    Object.prototype.toString.call(a); // "[object Array]"
    
    这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined Object.prototype.toString.call('a') // "[object String]"
    Object.prototype.toString.call(1) // "[object Number]"
    Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
    Object.prototype.toString.call(null) // "[object Null]"
    Object.prototype.toString.call(undefined) // "[object Undefined]"
    Object.prototype.toString.call(function(){}) // "[object Function]"
    Object.prototype.toString.call({name: 'a'}) // "[object Object]"
    
    1. instanceof

    instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。

    使用 instanceof判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。

    但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

    []  instanceof Array; // true
    []  instanceof Object; // true
    

    延伸:instanceof的算法机制,js原型链

    // 取左边的__proto__和右边的prototype进行比较
    function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
      var O = R.prototype;// 取 R 的显示原型
      L = L.__proto__;// 取 L 的隐式原型
      while (true) {
        if (L === null)
          return false;
        if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
          return true;
        L = L.__proto__;
      }
     }
    
    1. Array.isArray()

    用来判断对象是否为数组

    instanceof 与 isArray:当检测Array实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes

    Array.isArray()与 Object.prototype.toString.call():Array.isArray()是ES6新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。Array.isArray的polyfill通常这样:

    if (!Array.isArray) {
      Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
      };
    }
    
    • 延伸:typeof 大多用于基础数据类型,基于机器码判断,typeof null === 'object'

    7. 介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景

    • 观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知

    • 发布-订阅模式是观察者模式的一种变体。发布-订阅只是把一部分功能抽象成一个独立的ChangeManager。

    • 都是某个对象改变,使依赖于它的多个对象得到通知。

    • 发布-订阅模式适合更复杂的场景。

    观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。

    // 观察者模式
    
    // ES6 写法:
    // 观察者
    class Observer {
        constructor() {
    
        }
        update(val) {
    
        }
    }
    // 观察者列表
    class ObserverList {
        constructor() {
            this.observerList = []
        }
        add(observer) {
            return this.observerList.push(observer);
        }
        remove(observer) {
            this.observerList = this.observerList.filter(ob => ob !== observer);
        }
        count() {
            return this.observerList.length;
        }
        get(index) {
            return this.observerList[index];
        }
    }
    // 目标
    class Subject {
        constructor() {
            this.observers = new ObserverList();
        }
        addObserver(observer) {
            this.observers.add(observer);
        }
        removeObserver(observer) {
            this.observers.remove(observer);
        }
        notify(...args) {
            let obCount = this.observers.count();
            for (let index = 0; index < obCount; index++) {
                this.observers.get(i).update(...args);
            }
        }
    }
    
    // ES5 写法:
    function Subject(){
      this.observers = [];
    }
    
    Subject.prototype = {
      add:function(observer){  // 添加
        this.observers.push(observer);
      },
      remove:function(observer){  // 删除
        var observers = this.observers;
        for(var i = 0;i < observers.length;i++){
          if(observers[i] === observer){
            observers.splice(i,1);
          }
        }
      },
      notify:function(){  // 通知
        var observers = this.observers;
        for(var i = 0;i < observers.length;i++){
          observers[i].update();
        }
      }
    }
    
    function Observer(name){
      this.name = name;
    }
    
    Observer.prototype = {
      update:function(){  // 更新
        console.log('my name is '+this.name);
      }
    }
    
    var sub = new Subject();
    
    var obs1 = new Observer('ttsy1');
    var obs2 = new Observer('ttsy2');
    
    sub.add(obs1);
    sub.add(obs2);
    sub.notify();  //my name is ttsy1、my name is ttsy2
    
    // 发布订阅模式
    
    // ES6 写法
    class PubSub {
        constructor() {
            this.subscribers = {}
        }
        subscribe(type, fn) {
            if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
              this.subscribers[type] = [];
            }
            
            this.subscribers[type].push(fn);
        }
        unsubscribe(type, fn) {
            let listeners = this.subscribers[type];
            if (!listeners || !listeners.length) return;
            this.subscribers[type] = listeners.filter(v => v !== fn);
        }
        publish(type, ...args) {
            let listeners = this.subscribers[type];
            if (!listeners || !listeners.length) return;
            listeners.forEach(fn => fn(...args));        
        }
    }
    
    let ob = new PubSub();
    ob.subscribe('add', (val) => console.log(val));
    ob.publish('add', 1);
    
    // ES5 写法
    let pubSub = {
      list:{},
      subscribe:function(key,fn){  // 订阅
        if (!this.list[key]) {
          this.list[key] = [];
        }
        this.list[key].push(fn);
      },
      publish:function(){  // 发布
        let arg = arguments;
        let key = [].shift.call(arg);
        let fns = this.list[key];
    
        if(!fns || fns.length<=0) return false;
    
        for(var i=0,len=fns.length;i<len;i++){
          fns[i].apply(this, arg);
        }
    
      },
      unSubscribe(key) {  // 取消订阅
        delete this.list[key];
      }
    };
    
    pubSub.subscribe('name', (name) => {
      console.log('your name is ' + name);
    });
    pubSub.subscribe('sex', (sex) => {
      console.log('your sex is ' + sex);
    });
    pubSub.publish('name', 'ttsy1');  // your name is ttsy1
    pubSub.publish('sex', 'male');  // your sex is male
    
    var dom= document.getElementById('dom');
    dom.onclick = function(){};
    <!--Dom一级事件 相当于观察者模式-->
    dom.addEventListener('click',function(){})<!--Dom二级事件,相当于发布订阅,会有一个事件池,存放注册的回调-->
    

    8. 聊聊 Redux 和 Vuex 的设计思想

    Redux

    • 整个应用只有一个Store:单一数据源
    • Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState
    • Action是一个对象,且必须具有type属性,用来告诉reducer执行哪个操作
    • reducer必须是一个纯函数,以此来保证相同的输入必定是相同的输出,确保返回的newState可预测可控
    • 大型应用中可以有多个reducer,通过combineReducer 来整合成一个根reducer

    Vuex

    • 由 State + Mutations(commit) + Actions(dispatch) 组成
    • 全局只有一个Store实例(单一数据源)
    • Mutations必须是同步事务,Why?:不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定先后顺序,A、B两个 mutation,调用顺序可能是 A -> B,但是最终改变 State 的结果可能是 B -> A
    • Actions 负责处理异步事务,然后在异步回调中触发一个或多个mutations,当然,也可以在业务代码中处理异步事务,然后在回调中同样操作

    Redux VS Vuex

    Redux: 
    // view——>actions——>reducer——>state变化——>view变化(同步异步一样)
    
    Vuex: 
    // view——>commit——>mutations——>state变化——>view变化(同步操作) 
    // view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)
    
    • 总的来说都是让 View 通过某种方式触发 Store 的事件(mutation)或方法(reducer),Store 的事件或方法对 State 进行修改(state.xxx = xxx)或返回一个新的 State(return newState),State 改变之后,View 发生响应式改变(setState vs 数据劫持响应式)。

    9. 说说浏览器和 Node 事件循环(event loop)的区别

    浏览器

    • 微任务和宏任务在浏览器的执行顺序是这样的: 执行一个宏任务=> 执行完微任务。 如此循环往复下去
    • 宏任务:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染
    • 微任务:new Promise().then(回调)、MutationObserver(html5新特性)

    node

    • 大体宏任务执行顺序:【timer定时器】=> 本阶段执行已经安排的setTimeout()和setInterval() 的回调函数。【i/o callbacks】=> 处理一些上一轮循环中的少数未执行的 I/O 回调【idle, prepare】=> 闲置阶段,仅系统内部使用。【poll 轮询】=> 获取新的I/O事件, 适当的条件下node将阻塞在这里。【check 阶段】=> 执行 setImmediate() 的回调。【close callbacks】=> 一些准备关闭的回调函数,如:socket.on('close', ...)。
    • 微任务和宏任务在Node的执行顺序:

    Node10以前: 执行完一个阶段的所有任务=>执行完nextTick队列里面的内容=>然后执行完微任务队列的内容

    Node 11以后: 和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。

    setTimeout(()=>{
        console.log('timer1')
        Promise.resolve().then(function() {
            console.log('promise1')
        })
    }, 0)
    setTimeout(()=>{
        console.log('timer2')
        Promise.resolve().then(function() {
            console.log('promise2')
        })
    }, 0)
    

    10. 介绍模块化发展历程

    模块化要解决的三个问题:

    1. 可维护性:降低编程人员对代码的维护成本,抽离公共代码
    2. 命名空间:解决全局变量污染和变量重名等问题
    3. 依赖管理:更好地管理依赖,并保证一定的加载顺序

    ①IIFE

    使用自执行函数来编写模块化,特点:在一个单独的函数作用域中执行代码,避免变量冲突。 缺点:只提供逻辑划分,但是不解决代码本身的划分,也就是说还是做不到“分文件”

    (function(){
      return {
    	data:[]
      }
    })()
    

    CommonJS

    2009年发布,Node 应用由模块组成,采用 CommonJS 模块规范。commonJS规范加载是同步的,也就是说,加载完成才执行后面的操作。 Node.js主要用于服务端编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快。分成三部分:1.模块标识。module:变量在每个模块内部,代表当前模块。2.模块定义。require:用来加载外部模块,读取并执行JS文件,返回该模块的exports对象。3.模块引用。exports:属性是对外的接口,用于导出当前模块的方法或变量

    AMD和require.js

    特点:依赖必须提前声明好。 "异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

    // 规定采用require语句加载模块,但是不同于CommonJS,它要求两个参数(模块名数组格式和回调函数)
    require(['math'], function (math) {
        math.add(2, 3);
    });
    
    // 在定义模块的时候需要使用define函数定义:
    define(id?, dependencies? factory)
    

    RequireJS是js模块化的工具框架,是AMD规范的具体实现。 有以下优点:

    1. 动态并行加载js,依赖前置,一个模块的回调函数必须得等到所有依赖都加载完毕之后,才可执行。无需再考虑js加载顺序问题。
    2. 规范化输入输出,使用起来方便。
    3. 对于不满足AMD规范的文件可以很好地兼容。

    CMD和SeaJS

    同样是受到Commonjs的启发,国内(阿里)诞生了一个CMD(Common Module Definition)规范。该规范借鉴了Commonjs的规范与AMD规范,在两者基础上做了改进。

    与AMD相比非常类似,CMD规范(2011)具有以下特点:

    1. define定义模块,require加载模块,exports暴露变量。
    2. 与AMD规范的主要区别在于定义模块和依赖引入的部分,与AMD模块规范相比,CMD模块更接近于Node对CommonJS规范的定义:不同于AMD的依赖前置,CMD推崇依赖就近(需要的时候再加载)
    3. 推崇api功能单一,一个模块干一件事。
    // CMD支持动态引入
    define(function(require,exports,module){
        ....
    })
    

    SeaJS是CMD规范的具体实现,跟RequireJs类似,CMD也是SeaJs推广过程中诞生的规范。CMD借鉴了很多AMD和Commonjs优点,同样SeaJs也对AMD和Commonjs做出了很多兼容。

    ES6 module

    2015年,ES6规范中,终于将模块化纳入JavaScript标准,从此js模块化被官方扶正,也是未来js的标准

    es6 module

    模块化

  • 相关阅读:
    poj3278 Catch That Cow
    poj2251 Dungeon Master
    poj1321 棋盘问题
    poj3083 Children of the Candy Cor
    jvm基础知识—垃圾回收机制
    jvm基础知识1
    java面试基础必备
    java soket通信总结 bio nio aio的区别和总结
    java scoket aIO 通信
    java scoket Blocking 阻塞IO socket通信四
  • 原文地址:https://www.cnblogs.com/jialuchun/p/15167997.html
Copyright © 2011-2022 走看看