每日十题 金三银四面试题(五)

一、快速排序(算法)

参考解答:

算法步骤如下:

1. 从数列中挑出一个元素,称为 “基准”(pivot);
2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;


    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}


function partition(arr, left ,right) {     // 分区操作
    var pivot = left,                      // 设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }        
    }
    swap(arr, pivot, index - 1);
    return index-1;
}


function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}


function partition2(arr, low, high) {
  let pivot = arr[low];
  while (low < high) {
    while (low < high && arr[high] > pivot) {
      --high;
    }
    arr[low] = arr[high];
    while (low < high && arr[low] <= pivot) {
      ++low;
    }
    arr[high] = arr[low];
  }
  arr[low] = pivot;
  return low;
}


function quickSort2(arr, low, high) {
  if (low < high) {
    let pivot = partition2(arr, low, high);
    quickSort2(arr, low, pivot - 1);
    quickSort2(arr, pivot + 1, high);
  }
  return arr;
}

二、说一下 vue 组件之间的传值通信

参考解答:

组件传值可分为父子组件传值和非父子组件传值(兄弟组件传值)

  1. 父组件给子组件传值:使用props

  2. 子组件给父组件传值:使用$emit触发事件

  3. 兄弟组件:使用Bus.js, 两个组件引入同一个Bus.js

使用vuex可以处理上述情况的传值问题。

四、说说 Vue 双向绑定的原理

参考解答:

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

  • 当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

  • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

  • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

  • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果

节选自: https://juejin.cn/post/6844903858804621325

五、介绍下虚拟 DOM,对虚拟 DOM 的理解

参考解答:

我对Virtual DOM 的理解:

首先对我们准备插入文档中的DOM树结构进行分析,使用js对象数据类型表示出。Virtual DOM 算法是有三个核心实现。一是用JS对象模拟DOM树,可以理解组合VNode。二是diff算法,比较新旧VNode的差异。三是打补丁patch,将差异应用到真正的DOM树上。

六、谈谈你对 webpack 的看法

参考解答:

我认为webpack的主要原理是将所有的资源都看成一个模块,并且把页面逻辑当成一个整体,通过给定入口文件,找到所有依赖,将各个依赖经过loader和plugins处理后,打包在一起,最后输出浏览器可识别的js文件。

webpack的核心概念:

  • Entry: 入口文件,Webpack 会从该文件开始进行分析与编译;

  • Output: 出口路径,打包后创建 bundler 的文件路径以及文件名;

  • Module: 模块,在 Webpack 中任何文件都可以作为一个模块,会根据配置的不同的 Loader 进行加载和打包;

  • Chunk: 代码块,可以根据配置,将所有模块代码合并成一个或多个代码块,以便按需加载,提高性能;

  • Loader: 模块加载器,进行各种文件类型的加载与转换;

  • Plugin: 拓展插件,可以通过 Webpack 相应的事件钩子,介入到打包过程中的任意环节,从而对代码按需修改;

七、手写一个Promise

参考解答:

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";


function MyPromise(fn) {
  // 保存初始化状态
  var self = this;


  // 初始化状态
  this.state = PENDING;


  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;


  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];


  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];


  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }


    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;


        // 设置传入的值
        self.value = value;


        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }


  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;


        // 设置传入的值
        self.value = value;


        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }


  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}


MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };


  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };


  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }


  // 如果状态已经凝固,则直接执行对应状态的函数


  if (this.state === RESOLVED) {
    onResolved(this.value);
  }


  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

八、手写一个观察者模式

参考解答:

var events = (function() {
  var topics = {};


  return {
    // 注册监听函数
    subscribe: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
      topics[topic].push(handler);
    },


    // 发布事件,触发观察者回调事件
    publish: function(topic, info) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic].forEach(function(handler) {
          handler(info);
        });
      }
    },


    // 移除主题的一个观察者的回调事件
    remove: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) return;


      var handlerIndex = -1;
      topics[topic].forEach(function(item, index) {
        if (item === handler) {
          handlerIndex = index;
        }
      });


      if (handlerIndex >= 0) {
        topics[topic].splice(handlerIndex, 1);
      }
    },


    // 移除主题的所有观察者的回调事件
    removeAll: function(topic) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
    }
  };
})();

九、手写一个jsonp

参考解答:

function jsonp(url, params, callback) {
  // 判断是否含有参数
  let queryString = url.indexOf("?") === "-1" ? "?" : "&";


  // 添加参数
  for (var k in params) {
    if (params.hasOwnProperty(k)) {
      queryString += k + "=" + params[k] + "&";
    }
  }


  // 处理回调函数名
  let random = Math.random()
      .toString()
      .replace(".", ""),
    callbackName = "myJsonp" + random;


  // 添加回调函数
  queryString += "callback=" + callbackName;


  // 构建请求
  let scriptNode = document.createElement("script");
  scriptNode.src = url + queryString;


  window[callbackName] = function() {
    // 调用回调函数
    callback(...arguments);


    // 删除这个引入的脚本
    document.getElementsByTagName("head")[0].removeChild(scriptNode);
  };


  // 发起请求
  document.getElementsByTagName("head")[0].appendChild(scriptNode);
}

十、实现一个浅拷贝和一个深拷贝

参考解答:

// 浅拷贝的实现;


function shallowCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;


  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {};


  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }


  return newObject;
}


// 深拷贝的实现;


function deepCopy(object) {
  if (!object || typeof object !== "object") return;


  let newObject = Array.isArray(object) ? [] : {};


  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }


  return newObject;
}

备注:解答仅供参考,如需了解更多,可自行搜索相应问题。

参考链接:https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/master/JavaScript/JavaScript.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值