1 Javascript 基础
1.1 手写Object.create
解析:Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
解决方案:将传入对象作为原型。
function create(obj) {
const A = function() {};
A.prototype = obj;
return new A();
}
1.2 手写instanceof
解析:instanceof
运算符 用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
解决方案:
- 获取类型的原型
- 获取对象的原型
- 然后递归循环对象的原型是否为类型原型,直到null(原型链终端),如果不同,则为false,相同为true
function NewInstanceOf(source, target) {
if (typeof source !== 'object') {
return false;
}
// 这里也可以使用while循环方式
if (source.__proto__ !== target.prototype) {
if (source.__proto__ === null) {
return false;
}
return NewInstanceOf(source.__proto__, target);
}
return true;
}
1.3 手写new操作符号
解析:new
运算符 创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
new会经过以下的过程:
- 首先会创建一个新的对象
- 设置对象原型为函数的prototype原型
- 设置函数this指向这个对象,执行构造函数代码(设置函数函数属性为对象属性)
- 判断当前返回类型,如果是值类型,则返回创建对象,如果是引用类型,就返回这个引用类型对象。
const newFunc = (obj, ...args) => {
let newObj = null;
if (typeof obj !== 'function') {
throw ('type error');
}
newObj = Object.create(obj.prototype);
const result = obj.apply(newObj, args);
if (result && (typeof result === 'function' || typeof result === 'function')) {
return result;
}
return newObj;
}
1.4 手写防抖函数
解析:指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
function debounce(fn, time) {
let timer = null;
return function (...args) {
!!timer && window.clearTimeout(timer);
timer = setTimeout(() => fn(...args), time);
}
}
1.5 手写节流函数
解析:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
function throttle(fn, time) {
let timer = null;
return function (...args) {
if (timer) {
return;
}
timer = setTimeout(() => {
window.clearTimeout(timer);
timer = null;
fn(...args);
}, time);
}
}
1.6 手写判断类型函数
解决方案:主要是根据为typeof为object做区分,使用Object.prototype.toString获对象类型信息。
function computedType(arg) {
if (null === arg) {
return 'null';
}
if (typeof arg === 'object') {
const str = Object.prototype.toString.call(arg);
return str.replace(/\]/, '').split(' ')[1];
}
return typeof arg;
}
1.7 手写call函数
解决方案:
- 判断调用对象是否为函数,即使我们是定义在函数原型上面,但是可能出现call等调用方式
- 判断上下文传入对象是否存在,否则,返回window
- 需要将函数作为上下文对象的属性,使用上下文对象调用这个方法,保存结果返回
- 删除刚才上下文函数的新增属性,返回结果
Function.prototype.newCall = function(context = window, ...args) {
if (typeof this !== 'function') {
throw('type error');
}
context._fn = this;
const result = context._fn(...args);
delete context._fn;
return result;
}
1.8 手写apply函数
解决反感:实现和call类似,只是参数需要简单处理下。
Function.prototype.newApply = function(context = window, args) {
if (typeof this !== 'function') {
throw('type error');
}
context._fn = this;
const result = context._fn(...args);
delete context._fn;
return result;
}
1.9 手写bind函数
解析:bind需要返回一个函数。其参数除了上下文执行对象,还有函数执行上下文的参数。
Function.prototype.newBind = function(context = window, ...args) {
if (typeof this !== 'function') {
throw('type error');
}
context.fn = this;
return function (...newArgs) {
const result = context.fn(...args, ...newArgs);
delete context.fn;
return result;
}
}
1.10 函数柯里化
解析:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
function curry(fn, ...args) {
// 这里重点在于:
// 1.使用fn.length来获取函数参数长度
// 2.当少于长度的时候,返回闭包函数形式,这个函数可以累积参数长度
return args.length === fn.length
? fn(...args)
: (...newArgs) => curry(fn, ...args, ...newArgs);
}
2 数据转换
2.1 交换a b值,不能使用临时变量
解决方案:利用两数相减,两数相加,计算差值。
a = a + b;
b = a - b;
a = a - b;
另外还能利用js高级语法来进行交换。
[a, b] = [b, a];
2.2 实现数据乱序输出
解决方案:从0开始遍历,产生一个随机数,随机数不为小于当前数,将随机数和当前值进行交换,直至数组末端。
function renderRandomArray(arr = []) {
for(let i = 0; i < arr.length; i++) {
const random = i + Math.round(Math.random() * (arr.length - i - 1));
[arr[i], arr[random]] = [arr[random], arr[i]];
}
return arr;
}
2.3 数组扁平化
解决方案:递归
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
2.4 数据去重
解决方式:map
function uniqueArray(arr) {
let map = {};
let newArr = [];
for (const i of arr) {
if (!map[i]) {
map[i] = true;
newArr.push(i);
}
}
return newArr;
}
2.5 将js对象转化为树结果
// 转换前:
source = [
{ id: 1, parentId: 0, name: 'rank1' },
{ id: 2, parentId: 1, name: 'rank2' },
{ id: 3, parentId: 2, name: 'rank3' }
]
// 转换为:
tree = [{
id: 1,
parentId: 0,
name: 'rank1',
children: [{
id: 2,
parentId: 1,
name: 'rank2',
children: [{ id: 3, parentId: 1, name: 'rank3' }]
}]
}]
解决方案:
- 建立一个map,将数组每一项的id作为key,项值作为value。
- 设置临时变量result,遍历数组,判断是否有父节点,如果有,将父节点取出,设置父节点的children为该项,如果没有,push到result中。
- 最后返回result。
function JsonToTree(arr) {
if (!Array.isArray(arr)) {
return arr;
}
let result = [];
let map = new Map();
for (const item of arr) {
map.set(item.id, item);
}
for(const item of arr) {
if (map.get(item.parentId)) {
let parent = map.get(item.parentId);
parent.children = item;
map.set(item.parentId, parent);
}
else {
result.push(item);
}
}
return result;
}
3 场景问题
3.1 循环打印红黄绿
问题:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
function flash(type, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(type);
resolve();
}, time);
});
}
function run() {
return flash('red', 3000)
.then(() => flash('green', 1000))
.then(() => flash('yellow', 2000))
.then(() => run());
}
run();
3.2 判断对象是否存在循环引用
解决方式:通过map的方式保存递归的对象,判断是否有循环引用。
function isCycle(obj, tem = {}) {
for(let o in obj) {
const v = obj[o];
if (typeof v === 'object') {
if (tem[v]) {
return true;
}
tem[v] = true;
if (isCycle(v, tem)) {
return true;
}
}
}
return false;
}
网友评论