每日十题 金三银四面试题(五)
共 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 组件之间的传值通信
参考解答:
组件传值可分为父子组件传值和非父子组件传值(兄弟组件传值)
父组件给子组件传值:使用props
子组件给父组件传值:使用$emit触发事件
兄弟组件:使用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;
}