关于javascript:哪些-js-手写题是需要掌握的

31次阅读

共计 13627 个字符,预计需要花费 35 分钟才能阅读完成。

验证是否是邮箱

function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])[email protected]([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
    return regx.test(email);
}

实现 Array.of 办法

Array.of()办法用于将一组值,转换为数组

  • 这个办法的次要目标,是补救数组构造函数 Array() 的有余。因为参数个数的不同,会导致 Array() 的行为有差别。
  • Array.of()基本上能够用来代替 Array()new Array(),并且不存在因为参数不同而导致的重载。它的行为十分对立
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

实现

function ArrayOf(){return [].slice.call(arguments);
}

实现类数组转化为数组

类数组转换为数组的办法有这样几种:

  • 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 办法来实现转换
Array.from(arrayLike);

实现数组的乱序输入

次要的实现思路就是:

  • 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行替换。
  • 第二次取出数据数组第二个元素,随机产生一个除了索引为 1 的之外的索引值,并将第二个元素与该索引值对应的元素进行替换
  • 依照下面的法则执行,直到遍历实现
var arr = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < arr.length; i++) {const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
  [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)

还有一办法就是倒序遍历:

var arr = [1,2,3,4,5,6,7,8,9,10];
let length = arr.length,
    randomIndex,
    temp;
  while (length) {randomIndex = Math.floor(Math.random() * length--);
    temp = arr[length];
    arr[length] = arr[randomIndex];
    arr[randomIndex] = temp;
  }
console.log(arr)

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callbackfn + 'is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为 undefined 的状况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}
    // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
    if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

实现数组的扁平化

(1)递归实现

一般的递归思路很容易了解,就是通过循环递归的形式,一项一项地去遍历,如果每一项还是一个数组,那么就持续往下遍历,利用递归程序的办法,来实现数组的每一项的连贯:

let arr = [1, [2, [3, 4, 5]]];
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;
}
flatten(arr);  //  [1, 2, 3, 4,5]

(2)reduce 函数迭代

从下面一般的递归函数中能够看出,其实就是对数组的每一项进行解决,那么其实也能够用 reduce 来实现数组的拼接,从而简化第一种办法的代码,革新后的代码如下所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.reduce(function(prev, next){return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(arr));//  [1, 2, 3, 4,5]

(3)扩大运算符实现

这个办法的实现,采纳了扩大运算符和 some 的办法,两者独特应用,达到数组扁平化的目标:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

(4)split 和 toString

能够通过 split 和 toString 两个办法来独特实现数组扁平化,因为数组会默认带一个 toString 的办法,所以能够把数组间接转换成逗号分隔的字符串,而后再用 split 办法把字符串从新转换为数组,如上面的代码所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.toString().split(',');
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

通过这两个办法能够将多维数组间接转换成逗号连贯的字符串,而后再从新分隔成数组。

(5)ES6 中的 flat

咱们还能够间接调用 ES6 中的 flat 办法来实现数组扁平化。flat 办法的语法:arr.flat([depth])

其中 depth 是 flat 的参数,depth 是能够传递数组的开展深度(默认不填、数值是 1),即开展一层数组。如果层数不确定,参数能够传进 Infinity,代表不管多少层都要开展:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {return arr.flat(Infinity);
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

能够看出,一个嵌套了两层的数组,通过将 flat 办法的参数设置为 Infinity,达到了咱们预期的成果。其实同样也能够设置成 2,也能实现这样的成果。在编程过程中,如果数组的嵌套层数不确定,最好间接应用 Infinity,能够达到扁平化。(6)正则和 JSON 办法 在第 4 种办法中曾经应用 toString 办法,其中依然采纳了将 JSON.stringify 的办法先转换为字符串,而后通过正则表达式过滤掉字符串中的数组的方括号,最初再利用 JSON.parse 把它转换成数组:

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {let str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, '');
  str = '[' + str + ']';
  return JSON.parse(str); 
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

参考前端手写面试题具体解答

对象扁平化

function objectFlat(obj = {}) {const res = {}
  function flat(item, preKey = '') {Object.entries(item).forEach(([key, val]) => {const newKey = preKey ? `${preKey}.${key}` : key
      if (val && typeof val === 'object') {flat(val, newKey)
      } else {res[newKey] = val
      }
    })
  }
  flat(obj)
  return res
}

// 测试
const source = {a: { b: { c: 1, d: 2}, e: 3 }, f: {g: 2} }
console.log(objectFlat(source));

实现 Event(event bus)

event bus 既是 node 中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅 - 公布设计模式,是十分重要的根底。

简略版:

class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听下限
  }
}


// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从贮存事件键值对的 this._events 中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {handler.apply(this, args);
  } else {handler.call(this);
  }
  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
  if (!this._events.get(type)) {this._events.set(type, fn);
  }
};

面试版:

class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听下限
  }
}

// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从贮存事件键值对的 this._events 中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {handler.apply(this, args);
  } else {handler.call(this);
  }
  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
  if (!this._events.get(type)) {this._events.set(type, fn);
  }
};

// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  handler = this._events.get(type);
  if (Array.isArray(handler)) {
    // 如果是一个数组阐明有多个监听者, 须要顺次此触发外面的函数
    for (let i = 0; i < handler.length; i++) {if (args.length > 0) {handler[i].apply(this, args);
      } else {handler[i].call(this);
      }
    }
  } else {
    // 单个函数的状况咱们间接触发即可
    if (args.length > 0) {handler.apply(this, args);
    } else {handler.call(this);
    }
  }

  return true;
};

// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
  if (!handler) {this._events.set(type, fn);
  } else if (handler && typeof handler === "function") {
    // 如果 handler 是函数阐明只有一个监听者
    this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存
  } else {handler.push(fn); // 曾经有多个监听者, 那么间接往数组里 push 函数即可
  }
};

EventEmeitter.prototype.removeListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单

  // 如果是函数, 阐明只被监听了一次
  if (handler && typeof handler === "function") {this._events.delete(type, fn);
  } else {
    let postion;
    // 如果 handler 是数组, 阐明被监听屡次要找到对应的函数
    for (let i = 0; i < handler.length; i++) {if (handler[i] === fn) {postion = i;} else {postion = -1;}
    }
    // 如果找到匹配的函数, 从数组中革除
    if (postion !== -1) {
      // 找到数组对应的地位, 间接革除此回调
      handler.splice(postion, 1);
      // 如果革除后只有一个函数, 那么勾销数组, 以函数模式保留
      if (handler.length === 1) {this._events.set(type, handler[0]);
      }
    } else {return this;}
  }
};

实现具体过程和思路见实现 event

深拷贝

递归的残缺版本(思考到了 Symbol 属性):

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数解决
  if (typeof target !== 'object' || target === null) {return target;}
  // 哈希表中存在间接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对 Symbol 属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

实现 jsonp

// 动静的加载 js 文件
function addScript(src) {const script = document.createElement('script');
  script.src = src;
  script.type = "text/javascript";
  document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的 callback 函数来接管回调后果
function handleRes(res) {console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});

实现 Object.freeze

Object.freeze解冻一个对象,让其不能再增加 / 删除属性,也不能批改该对象已有属性的可枚举性、可配置可写性,也不能批改已有属性的值和它的原型属性,最初返回一个和传入参数雷同的对象

function myFreeze(obj){
  // 判断参数是否为 Object 类型,如果是就关闭对象,循环遍历对象。去掉原型属性,将其 writable 个性设置为 false
  if(obj instanceof Object){Object.seal(obj);  // 关闭对象
    for(let key in obj){if(obj.hasOwnProperty(key)){
        Object.defineProperty(obj,key,{writable:false   // 设置只读})
        // 如果属性值仍然为对象,要通过递归来进行进一步的解冻
        myFreeze(obj[key]);  
      }
    }
  }
}

实现 Object.is

Object.is不会转换被比拟的两个值的类型,这点和 === 更为类似,他们之间也存在一些区别

  • NaN=== 中是不相等的,而在 Object.is 中是相等的
  • +0- 0 在=== 中是相等的,而在 Object.is 中是不相等的
Object.is = function (x, y) {if (x === y) {
    // 当前情况下,只有一种状况是非凡的,即 +0 -0
    // 如果 x !== 0,则返回 true
    // 如果 x === 0,则须要判断 + 0 和 -0,则能够间接应用 1/+0 === Infinity 和 1/-0 === -Infinity 来进行判断
    return x !== 0 || 1 / x === 1 / y;
  }

  // x !== y 的状况下,只须要判断是否为 NaN,如果 x!==x,则阐明 x 是 NaN,同理 y 也一样
  // x 和 y 同时为 NaN 时,返回 true
  return x !== x && y !== y;
};

手写 Object.create

思路:将传入的对象作为原型

function create(obj) {function F() {}
  F.prototype = obj
  return new F()}

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是 d,呈现了 5 次

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';

 // 使其依照肯定的秩序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"

// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
});
console.log(` 字符最多的是 ${char},呈现了 ${num}次 `);

实现 ES6 的 const

因为 ES5 环境没有 block 的概念,所以是无奈百分百实现 const,只能是挂载到某个对象下,要么是全局的window,要么就是自定义一个object 来当容器

var __const = function __const (data, value) {
    window.data = value // 把要定义的 data 挂载到 window 下,并赋值 value
    Object.defineProperty(window, data, { // 利用 Object.defineProperty 的能力劫持以后对象,并批改其属性描述符
      enumerable: false,
      configurable: false,
      get: function () {return value},
      set: function (data) {if (data !== value) { // 当要对以后属性进行赋值时,则抛出谬误!throw new TypeError('Assignment to constant variable.')
        } else {return value}
      }
    })
  }
  __const('a', 10)
  console.log(a)
  delete a
  console.log(a)
  for (let item in window) { // 因为 const 定义的属性在 global 下也是不存在的,所以用到了 enumerable: false 来模仿这一性能
    if (item === 'a') { // 因为不可枚举,所以不执行
      console.log(window[item])
    }
  }
  a = 20 // 报错

Vue目前双向绑定的外围实现思路就是利用 Object.definePropertygetset 进行劫持,监听用户对属性进行调用以及赋值时的具体情况,从而实现的双向绑定

实现单例模式

外围要点: 用闭包和 Proxy 属性拦挡

function proxy(func) {
    let instance;
    let handler = {constructor(target, args) {if(!instance) {instance = Reflect.constructor(fun, args);
            }
            return instance;
        }
    }
    return new Proxy(func, handler);
}

循环打印红黄绿

上面来看一道比拟典型的问题,通过这个问题来比照几种异步编程办法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?

三个亮灯函数:

function red() {console.log('red');
}
function green() {console.log('green');
}
function yellow() {console.log('yellow');
}

这道题简单的中央在于 须要“交替反复”亮灯,而不是“亮完一次”就完结了。

(1)用 callback 实现

const task = (timer, light, callback) => {setTimeout(() => {if (light === 'red') {red()
        }
        else if (light === 'green') {green()
        }
        else if (light === 'yellow') {yellow()
        }
        callback()}, timer)
}
task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', Function.prototype)
    })
})

这里存在一个 bug:代码只是实现了一次流程,执行后红黄绿灯别离只亮一次。该如何让它交替反复进行呢?

下面提到过递归,能够递归亮灯的一个周期:

const step = () => {task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', step)
        })
    })
}
step()

留神看黄灯亮的回调里又再次调用了 step 办法 以实现循环亮灯。

(2)用 promise 实现

const task = (timer, light) => 
    new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()
            }
            else if (light === 'green') {green()
            }
            else if (light === 'yellow') {yellow()
            }
            resolve()}, timer)
    })
const step = () => {task(3000, 'red')
        .then(() => task(2000, 'green'))
        .then(() => task(2100, 'yellow'))
        .then(step)
}
step()

这里将回调移除,在一次亮灯完结后,resolve 以后 promise,并仍然应用递归进行。

(3)用 async/await 实现

const taskRunner =  async () => {await task(3000, 'red')
    await task(2000, 'green')
    await task(2100, 'yellow')
    taskRunner()}
taskRunner()

setTimeout 与 setInterval 实现

setTimeout 模仿实现 setInterval

题目形容: setInterval 用来实现循环定时调用 可能会存在肯定的问题 能用 setTimeout 解决吗

实现代码如下:

function mySetInterval(fn, t) {
  let timerId = null;
  function interval() {fn();
    timerId = setTimeout(interval, t); // 递归调用
  }
  timerId = setTimeout(interval, t); // 首次调用
  return {
    // 利用闭包的个性 保留 timerId
    cancel:() => {clearTimeout(timerId)
    }
  }
}
// 测试
var a = mySetInterval(()=>{console.log(111);
},1000)
var b = mySetInterval(() => {console.log(222)
}, 1000)

// 终止定时器
a.cancel()
b.cancel()

为什么要用 setTimeout 模仿实现 setIntervalsetInterval 的缺点是什么?

setInterval(fn(), N);

下面这句代码的意思其实是 fn() 将会在 N 秒之后被推入工作队列。在 setInterval 被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一

// 最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入

// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假如的网络提早
    count++;
    console.log(
        "与原设定的距离时差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)

// 输入:// 与原设定的距离时差了:567 毫秒
// 与原设定的距离时差了:552 毫秒
// 与原设定的距离时差了:563 毫秒
// 与原设定的距离时差了:554 毫秒(2 次)
// 与原设定的距离时差了:564 毫秒
// 与原设定的距离时差了:602 毫秒
// 与原设定的距离时差了:573 毫秒
// 与原设定的距离时差了:633 毫秒

再次强调,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。

setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到音讯队列中

上图可见,setInterval 每隔 100ms 往队列中增加一个事件;100ms 后,增加 T1 定时器代码至队列中,主线程中还有工作在执行,所以期待,some event 执行完结后执行 T1定时器代码;又过了 100msT2 定时器被增加到队列中,主线程还在执行 T1 代码,所以期待;又过了 100ms,实践上又要往队列里推一个定时器代码,但因为此时 T2 还在队列中,所以 T3 不会被增加(T3 被跳过),后果就是此时被跳过;这里咱们能够看到,T1 定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果

setInterval 有两个毛病

  • 应用 setInterval 时,某些距离会被跳过
  • 可能多个定时器会间断执行

能够这么了解 :每个setTimeout 产生的工作会间接 push 到工作队列中;而 setInterval 在每次把工作 push 到工作队列前,都要进行一下判断 (看上次的工作是否仍在队列中)。因此咱们个别用setTimeout 模仿setInterval,来躲避掉下面的毛病

setInterval 模仿实现 setTimeout

const mySetTimeout = (fn, t) => {const timer = setInterval(() => {clearInterval(timer);
    fn();}, t);
};
// 测试
// mySetTimeout(()=>{//   console.log(1);
// },1000)

实现 find 办法

  • find 接管一个办法作为参数,办法外部返回一个条件
  • find 会遍历所有的元素,执行你给定的带有条件返回值的函数
  • 合乎该条件的元素会作为 find 办法的返回值
  • 如果遍历完结还没有合乎该条件的元素,则返回 undefined
var users = [{id: 1, name: '张三'},
  {id: 2, name: '张三'},
  {id: 3, name: '张三'},
  {id: 4, name: '张三'}
]

Array.prototype.myFind = function (callback) {// var callback = function (item, index) {return item.id === 4}
  for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {return this[i]
    }
  }
}

var ret = users.myFind(function (item, index) {return item.id === 2})

console.log(ret)

实现数组扁平化 flat 办法

题目形容: 实现一个办法使多维数组变成一维数组

let ary = [1, [2, [3, [4, 5]]], 6];
let str = JSON.stringify(ary);

第 0 种解决: 间接的调用

arr_flat = arr.flat(Infinity);

第一种解决

ary = str.replace(/(\[|\])/g, '').split(',');

第二种解决

str = str.replace(/(\[\]))/g, '');
str = '[' + str + ']';
ary = JSON.parse(str);

第三种解决:递归解决

let result = [];
let fn = function(ary) {for(let i = 0; i < ary.length; i++) }{let item = ary[i];
    if (Array.isArray(ary[i])){fn(item);
    } else {result.push(item);
    }
  }
}

第四种解决:用 reduce 实现数组的 flat 办法

function flatten(ary) {return ary.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}
let ary = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(ary))

第五种解决:能用迭代的思路去实现

function flatten(arr) {if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {arr = [].concat(...arr);
  }
  return arr;
}
// console.log(flatten([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

第六种解决:扩大运算符

while (ary.some(Array.isArray)) {ary = [].concat(...ary);
}

正文完
 0