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

编程微刊

共 7754字,需浏览 16分钟

 ·

2021-03-02 11:57

一、快速排序(算法)

参考解答:

算法步骤如下:

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



浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报