实现迭代器生成函数

我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为ES6早帮我们考虑好了全套的解决方案,内置了贴心的 生成器 (Generator)供我们使用:

// 编写一个迭代器生成函数

function *iteratorGenerator() {

yield '1号选手'

yield '2号选手'

yield '3号选手'

}

const iterator = iteratorGenerator()

iterator.next()

iterator.next()

iterator.next()

丢进控制台,不负众望:

写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的,不仅仅是写一个迭代器对象,而是用ES5去写一个能够生成迭代器对象的迭代器生成函数(解析在注释里):

// 定义生成器函数,入参是任意集合

function iteratorGenerator(list) {

// idx记录当前访问的索引

var idx = 0

// len记录传入集合的长度

var len = list.length

return {

// 自定义next方法

next: function() {

// 如果索引还没有超出集合长度,done为false

var done = idx >= len

// 如果done为false,则可以继续取值

var value = !done ? list[idx++] : undefined

// 将当前值与遍历是否完毕(done)返回

return {

done: done,

value: value

}

}

}

}

var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])

iterator.next()

iterator.next()

iterator.next()

此处为了记录每次遍历的位置,我们实现了一个闭包,借助自由变量来做我们的迭代过程中的“游标”。

运行一下我们自定义的迭代器,结果符合预期:

实现一个call

call做了什么:

将函数设为对象的属性执行&删除这个函数指定this到函数并传入给定参数执行函数如果不传入参数,默认指向为 window

// 模拟 call bar.mycall(null);

//实现一个call方法:

Function.prototype.myCall = function(context) {

//此处没有考虑context非object情况

context.fn = this;

let args = [];

for (let i = 1, len = arguments.length; i < len; i++) {

args.push(arguments[i]);

}

context.fn(...args);

let result = context.fn(...args);

delete context.fn;

return result;

};

Promise

// 模拟实现Promise

// Promise利用三大手段解决回调地狱:

// 1. 回调函数延迟绑定

// 2. 返回值穿透

// 3. 错误冒泡

// 定义三种状态

const PENDING = 'PENDING'; // 进行中

const FULFILLED = 'FULFILLED'; // 已成功

const REJECTED = 'REJECTED'; // 已失败

class Promise {

constructor(exector) {

// 初始化状态

this.status = PENDING;

// 将成功、失败结果放在this上,便于then、catch访问

this.value = undefined;

this.reason = undefined;

// 成功态回调函数队列

this.onFulfilledCallbacks = [];

// 失败态回调函数队列

this.onRejectedCallbacks = [];

const resolve = value => {

// 只有进行中状态才能更改状态

if (this.status === PENDING) {

this.status = FULFILLED;

this.value = value;

// 成功态函数依次执行

this.onFulfilledCallbacks.forEach(fn => fn(this.value));

}

}

const reject = reason => {

// 只有进行中状态才能更改状态

if (this.status === PENDING) {

this.status = REJECTED;

this.reason = reason;

// 失败态函数依次执行

this.onRejectedCallbacks.forEach(fn => fn(this.reason))

}

}

try {

// 立即执行executor

// 把内部的resolve和reject传入executor,用户可调用resolve和reject

exector(resolve, reject);

} catch(e) {

// executor执行出错,将错误内容reject抛出去

reject(e);

}

}

then(onFulfilled, onRejected) {

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;

onRejected = typeof onRejected === 'function'? onRejected :

reason => { throw new Error(reason instanceof Error ? reason.message : reason) }

// 保存this

const self = this;

return new Promise((resolve, reject) => {

if (self.status === PENDING) {

self.onFulfilledCallbacks.push(() => {

// try捕获错误

try {

// 模拟微任务

setTimeout(() => {

const result = onFulfilled(self.value);

// 分两种情况:

// 1. 回调函数返回值是Promise,执行then操作

// 2. 如果不是Promise,调用新Promise的resolve函数

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

});

self.onRejectedCallbacks.push(() => {

// 以下同理

try {

setTimeout(() => {

const result = onRejected(self.reason);

// 不同点:此时是reject

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

})

} else if (self.status === FULFILLED) {

try {

setTimeout(() => {

const result = onFulfilled(self.value);

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

});

} catch(e) {

reject(e);

}

} else if (self.status === REJECTED) {

try {

setTimeout(() => {

const result = onRejected(self.reason);

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

}

});

}

catch(onRejected) {

return this.then(null, onRejected);

}

static resolve(value) {

if (value instanceof Promise) {

// 如果是Promise实例,直接返回

return value;

} else {

// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED

return new Promise((resolve, reject) => resolve(value));

}

}

static reject(reason) {

return new Promise((resolve, reject) => {

reject(reason);

})

}

static all(promiseArr) {

const len = promiseArr.length;

const values = new Array(len);

// 记录已经成功执行的promise个数

let count = 0;

return new Promise((resolve, reject) => {

for (let i = 0; i < len; i++) {

// Promise.resolve()处理,确保每一个都是promise实例

Promise.resolve(promiseArr[i]).then(

val => {

values[i] = val;

count++;

// 如果全部执行完,返回promise的状态就可以改变了

if (count === len) resolve(values);

},

err => reject(err),

);

}

})

}

static race(promiseArr) {

return new Promise((resolve, reject) => {

promiseArr.forEach(p => {

Promise.resolve(p).then(

val => resolve(val),

err => reject(err),

)

})

})

}

}

查找文章中出现频率最高的单词

function findMostWord(article) {

// 合法性判断

if (!article) return;

// 参数处理

article = article.trim().toLowerCase();

let wordList = article.match(/[a-z]+/g),

visited = [],

maxNum = 0,

maxWord = "";

article = " " + wordList.join(" ") + " ";

// 遍历判断单词出现次数

wordList.forEach(function(item) {

if (visited.indexOf(item) < 0) {

// 加入 visited

visited.push(item);

let word = new RegExp(" " + item + " ", "g"),

num = article.match(word).length;

if (num > maxNum) {

maxNum = num;

maxWord = item;

}

}

});

return maxWord + " " + maxNum;

}

实现apply方法

apply原理与call很相似,不多赘述

// 模拟 apply

Function.prototype.myapply = function(context, arr) {

var context = Object(context) || window;

context.fn = this;

var result;

if (!arr) {

result = context.fn();

} else {

var args = [];

for (var i = 0, len = arr.length; i < len; i++) {

args.push("arr[" + i + "]");

}

result = eval("context.fn(" + args + ")");

}

delete context.fn;

return result;

};

手写 Object.create

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

function create(obj) {

function F() {}

F.prototype = obj

return new F()

}

参考 前端进阶面试题详细解答

手写 bind 函数

bind 函数的实现步骤:

判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。保存当前函数的引用,获取其余传入参数值。创建一个函数返回函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。

// bind 函数实现

Function.prototype.myBind = function(context) {

// 判断调用对象是否为函数

if (typeof this !== "function") {

throw new TypeError("Error");

}

// 获取参数

var args = [...arguments].slice(1),

fn = this;

return function Fn() {

// 根据调用方式,传入不同绑定值

return fn.apply(

this instanceof Fn ? this : context,

args.concat(...arguments)

);

};

};

深拷贝

递归的完整版本(考虑到了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;

}

字符串解析问题

var a = {

b: 123,

c: '456',

e: '789',

}

var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;

// => 'a123aa456aa {a.d}aaaa'

实现函数使得将str字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d})

类似于模版字符串,但有一点出入,实际上原理大差不差

const fn1 = (str, obj) => {

let res = '';

// 标志位,标志前面是否有{

let flag = false;

let start;

for (let i = 0; i < str.length; i++) {

if (str[i] === '{') {

flag = true;

start = i + 1;

continue;

}

if (!flag) res += str[i];

else {

if (str[i] === '}') {

flag = false;

res += match(str.slice(start, i), obj);

}

}

}

return res;

}

// 对象匹配操作

const match = (str, obj) => {

const keys = str.split('.').slice(1);

let index = 0;

let o = obj;

while (index < keys.length) {

const key = keys[index];

if (!o[key]) {

return `{${str}}`;

} else {

o = o[key];

}

index++;

}

return o;

}

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';

parseParam(url)

/* 结果{ user: 'anonymous', id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true}*/

function parseParam(url) {

const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来

const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中

let paramsObj = {};

// 将 params 存到对象中

paramsArr.forEach(param => {

if (/=/.test(param)) { // 处理有 value 的参数

let [key, val] = param.split('='); // 分割 key 和 value

val = decodeURIComponent(val); // 解码

val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值

paramsObj[key] = [].concat(paramsObj[key], val);

} else { // 如果对象没有这个 key,创建 key 并设置值

paramsObj[key] = val;

}

} else { // 处理没有 value 的参数

paramsObj[param] = true;

}

})

return paramsObj;

}

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {

if (this == undefined) {

throw new TypeError('this is null or not defined');

}

if (typeof callback !== 'function') {

throw new TypeError(callback + ' is not a function');

}

const res = [];

// 同理

const O = Object(this);

const len = O.length >>> 0;

for (let i = 0; i < len; i++) {

if (i in O) {

// 调用回调函数并传入新数组

res[i] = callback.call(thisArg, O[i], i, this);

}

}

return res;

}

递归反转链表

// node节点

class Node {

constructor(element,next) {

this.element = element

this.next = next

}

}

class LinkedList {

constructor() {

this.head = null // 默认应该指向第一个节点

this.size = 0 // 通过这个长度可以遍历这个链表

}

// 增加O(n)

add(index,element) {

if(arguments.length === 1) {

// 向末尾添加

element = index // 当前元素等于传递的第一项

index = this.size // 索引指向最后一个元素

}

if(index < 0 || index > this.size) {

throw new Error('添加的索引不正常')

}

if(index === 0) {

// 直接找到头部 把头部改掉 性能更好

let head = this.head

this.head = new Node(element,head)

} else {

// 获取当前头指针

let current = this.head

// 不停遍历 直到找到最后一项 添加的索引是1就找到第0个的next赋值

for (let i = 0; i < index-1; i++) { // 找到它的前一个

current = current.next

}

// 让创建的元素指向上一个元素的下一个

// 看图理解next层级 ![](http://img-repo.poetries.top/images/20210522115056.png)

current.next = new Node(element,current.next) // 让当前元素指向下一个元素的next

}

this.size++;

}

// 删除O(n)

remove(index) {

if(index < 0 || index >= this.size) {

throw new Error('删除的索引不正常')

}

this.size--

if(index === 0) {

let head = this.head

this.head = this.head.next // 移动指针位置

return head // 返回删除的元素

}else {

let current = this.head

for (let i = 0; i < index-1; i++) { // index-1找到它的前一个

current = current.next

}

let returnVal = current.next // 返回删除的元素

// 找到待删除的指针的上一个 current.next.next

// 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可

current.next = current.next.next

return returnVal

}

}

// 查找O(n)

get(index) {

if(index < 0 || index >= this.size) {

throw new Error('查找的索引不正常')

}

let current = this.head

for (let i = 0; i < index; i++) {

current = current.next

}

return current

}

reverse() {

const reverse = head=>{

if(head == null || head.next == null) {

return head

}

let newHead = reverse(head.next)

// 从这个链表的最后一个开始反转,让当前下一个元素的next指向自己,自己指向null

// ![](http://img-repo.poetries.top/images/20210522161710.png)

// 刚开始反转的是最后两个

head.next.next = head

head.next = null

return newHead

}

return reverse(this.head)

}

}

let ll = new LinkedList()

ll.add(1)

ll.add(2)

ll.add(3)

ll.add(4)

// console.dir(ll,{depth: 1000})

console.log(ll.reverse())

实现数组的map方法

Array.prototype._map = function(fn) {

if (typeof fn !== "function") {

throw Error('参数必须是一个函数');

}

const res = [];

for (let i = 0, len = this.length; i < len; i++) {

res.push(fn(this[i]));

}

return res;

}

实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒

// 使用 promise来实现 sleep

const sleep = (time) => {

return new Promise(resolve => setTimeout(resolve, time))

}

sleep(1000).then(() => {

// 这里写你的骚操作

})

判断是否是电话号码

function isPhone(tel) {

var regx = /^1[34578]\d{9}$/;

return regx.test(tel);

}

图片懒加载

// 前端框架 javascript 前端常见手写面试题集锦  第1张

function isVisible(el) {

const position = el.getBoundingClientRect()

const windowHeight = document.documentElement.clientHeight

// 顶部边缘可见

const topVisible = position.top > 0 && position.top < windowHeight;

// 底部边缘可见

const bottomVisible = position.bottom < windowHeight && position.bottom > 0;

return topVisible || bottomVisible;

}

function imageLazyLoad() {

const images = document.querySelectorAll('img')

for (let img of images) {

const realSrc = img.dataset.src

if (!realSrc) continue

if (isVisible(img)) {

img.src = realSrc

img.dataset.src = ''

}

}

}

// 测试

window.addEventListener('load', imageLazyLoad)

window.addEventListener('scroll', imageLazyLoad)

// or

window.addEventListener('scroll', throttle(imageLazyLoad, 1000))

模拟Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

// 模拟 Object.create

function create(proto) {

function F() {}

F.prototype = proto;

return new F();

}

Promise并行限制

就是实现有并行限制的Promise调度器问题

class Scheduler {

constructor() {

this.queue = [];

this.maxCount = 2;

this.runCounts = 0;

}

add(promiseCreator) {

this.queue.push(promiseCreator);

}

taskStart() {

for (let i = 0; i < this.maxCount; i++) {

this.request();

}

}

request() {

if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {

return;

}

this.runCounts++;

this.queue.shift()().then(() => {

this.runCounts--;

this.request();

});

}

}

const timeout = time => new Promise(resolve => {

setTimeout(resolve, time);

})

const scheduler = new Scheduler();

const addTask = (time,order) => {

scheduler.add(() => timeout(time).then(()=>console.log(order)))

}

addTask(1000, '1');

addTask(500, '2');

addTask(300, '3');

addTask(400, '4');

scheduler.taskStart()

// 2

// 3

// 1

// 4

实现防抖函数(debounce)

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

那么与节流函数的区别直接看这个动画实现即可。

手写简化版:

// 防抖函数

const debounce = (fn, delay) => {

let timer = null;

return (...args) => {

clearTimeout(timer);

timer = setTimeout(() => {

fn.apply(this, args);

}, delay);

};

};

适用场景:

按钮提交场景:防止多次提交按钮,只执行最后提交的一次服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

生存环境请用lodash.debounce

Object.is

Object.is解决的主要是这两个问题:

+0 === -0 // true

NaN === NaN // false

const is= (x, y) => {

if (x === y) {

// +0和-0应该不相等

return x !== 0 || y !== 0 || 1/x === 1/y;

} else {

return x !== x && y !== y;

}

}

查看原文