实现简略路由
// hash 路由
class Route{constructor(){
// 路由存储对象
this.routes = {}
// 以后 hash
this.currentHash = ''
// 绑定 this,防止监听时 this 指向扭转
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()}
}
实现 forEach 办法
Array.prototype.myForEach = function(callback, context=window) {
// this=>arr
let self = this,
i = 0,
len = self.length;
for(;i<len;i++) {typeof callback == 'function' && callback.call(context,self[i], i)
}
}
实现观察者模式
观察者模式(基于公布订阅模式)有观察者,也有被观察者
观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者
class Subject { // 被观察者 学生
constructor(name) {
this.state = 'happy'
this.observers = []; // 存储所有的观察者}
// 收集所有的观察者
attach(o){ // Subject. prototype. attch
this.observers.push(o)
}
// 更新被观察者 状态的办法
setState(newState) {
this.state = newState; // 更新状态
// this 指被观察者 学生
this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态
}
}
class Observer{ // 观察者 父母和老师
constructor(name) {this.name = name}
update(student) {console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state)
}
}
let student = new Subject('学生');
let parent = new Observer('父母');
let teacher = new Observer('老师');
// 被观察者存储观察者的前提,须要先接收观察者
student. attach(parent);
student. attach(teacher);
student. setState('被欺侮了');
实现 apply 办法
思路: 利用
this
的上下文个性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {
// this-->func context--> obj args--> 传递过去的参数
// 在 context 上加一个惟一值不影响 context 上的属性
let key = Symbol('key')
context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法
// let args = [...arguments].slice(1) // 第一个参数为 obj 所以删除, 伪数组转为数组
let result = context[key](...args); // 这里和 call 传参不一样
// 革除定义的 this 不删除会导致 context 属性越来越多
delete context[key];
// 返回后果
return result;
}
// 应用
function f(a,b){console.log(a,b)
console.log(this.name)
}
let obj={name:'张三'}
f.myApply(obj,[1,2]) //arguments[1]
基于 Generator 函数实现 async/await 原理
外围:传递给我一个
Generator
函数,把函数中的内容基于Iterator
迭代器的特点一步步的执行
function readFile(file) {
return new Promise(resolve => {setTimeout(() => {resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {const iterator = generator(); // 接下来要执行 next
// data 为第一次执行之后的返回后果,用于传给第二次执行
const next = (data) => {let { value, done} = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data
if (done) return; // 执行结束 (到第三次) 间接返回
// 第一次执行 next 时,yield 返回的 promise 实例 赋值给了 value
value.then(data => {next(data); // 当第一次 value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步)
});
}
next();};
asyncFunc(function* () {
// 生成器函数:控制代码一步步执行
let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回
data = yield readFile(data + 'b.js');
return data;
})
数组中的数据依据 key 去重
给定一个任意数组,实现一个通用函数,让数组中的数据依据 key 排重:
const dedup = (data, getKey = () => {}) => {// todo}
let data = [{ id: 1, v: 1},
{id: 2, v: 2},
{id: 1, v: 1},
];
// 以 id 作为排重 key,执行函数失去后果
// data = [// { id: 1, v: 1},
// {id: 2, v: 2},
// ];
实现
const dedup = (data, getKey = () => {}) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)
if (!pre[key]) {pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}
应用
let data = [{ id: 1, v: 1},
{id: 2, v: 2},
{id: 1, v: 1},
];
console.log(dedup(data, (item) => item.id))
// 以 id 作为排重 key,执行函数失去后果
// data = [// { id: 1, v: 1},
// {id: 2, v: 2},
// ];
实现 map 办法
- 回调函数的参数有哪些,返回值如何解决
- 不批改原来的数组
Array.prototype.myMap = function(callback, context){
// 转换类数组
var arr = Array.prototype.slice.call(this),// 因为是 ES5 所以就不必... 开展符了
mappedArr = [],
i = 0;
for (; i < arr.length; i++){// 把以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))
mappedArr.push(callback.call(context, arr[i], i, this));
}
return mappedArr;
}
实现 redux-thunk
redux-thunk
能够利用redux
中间件让redux
反对异步的action
// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({dispatch, getState}) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState);
}
return next(action);
};
export default thunk
原生实现
function ajax() {let xhr = new XMLHttpRequest() // 实例化,以调用办法
xhr.open('get', 'https://www.google.com') // 参数 2,url。参数三:异步
xhr.onreadystatechange = () => { // 每当 readyState 属性扭转时,就会调用该函数。if (xhr.readyState === 4) { //XMLHttpRequest 代理以后所处状态。if (xhr.status >= 200 && xhr.status < 300) { //200-300 申请胜利
let string = request.responseText
//JSON.parse() 办法用来解析 JSON 字符串,结构由字符串形容的 JavaScript 值或对象
let object = JSON.parse(string)
}
}
}
request.send() // 用于理论收回 HTTP 申请。不带参数为 GET 申请}
参考:前端手写面试题具体解答
实现模板字符串解析性能
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的构造
}
return template; // 如果模板没有模板字符串间接返回
}
实现 filter 办法
Array.prototype.myFilter=function(callback, context=window){
let len = this.length
newArr = [],
i=0
for(; i < len; i++){if(callback.apply(context, [this[i], i , this])){newArr.push(this[i]);
}
}
return newArr;
}
实现 new 的过程
new 操作符做了这些事:
- 创立一个全新的对象
- 这个对象的
__proto__
要指向构造函数的原型 prototype - 执行构造函数,应用
call/apply
扭转 this 的指向 - 返回值为
object
类型则作为new
办法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
// 基于原型链 创立一个新对象
let newObj = Object.create(fn.prototype);
// 增加属性到新对象上 并获取 obj 函数的后果
let res = fn.apply(newObj, args); // 扭转 this 指向
// 如果执行后果有返回值并且是一个对象, 返回执行的后果, 否则, 返回新创建的对象
return typeof res === 'object' ? res: newObj;
}
// 用法
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();
实现 every 办法
Array.prototype.myEvery=function(callback, context = window){
var len=this.length,
flag=true,
i = 0;
for(;i < len; i++){if(!callback.apply(context,[this[i], i , this])){
flag=false;
break;
}
}
return flag;
}
// var obj = {num: 1}
// var aa=arr.myEvery(function(v,index,arr){
// return v.num>=12;
// },obj)
// console.log(aa)
实现 findIndex 办法
var users = [{id: 1, name: '张三'},
{id: 2, name: '张三'},
{id: 3, name: '张三'},
{id: 4, name: '张三'}
]
Array.prototype.myFindIndex = 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 i
}
}
}
var ret = users.myFind(function (item, index) {return item.id === 2})
console.log(ret)
实现公布订阅模式
简介:
公布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态产生扭转时,所依赖它的对象都将失去状态扭转的告诉。
次要的作用(长处):
- 广泛应用于异步编程中(代替了传递回调函数)
- 对象之间涣散耦合的编写代码
毛病:
- 创立订阅者自身要耗费肯定的工夫和内存
- 多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护
实现的思路:
- 创立一个对象(缓存列表)
on
办法用来把回调函数fn
都加到缓存列表中emit
依据key
值去执行对应缓存列表中的函数off
办法能够依据key
值勾销订阅
class EventEmiter {constructor() {
// 事件对象,寄存订阅的名字和事件
this._events = {}}
// 订阅事件的办法
on(eventName,callback) {if(!this._events) {this._events = {}
}
// 合并之前订阅的 cb
this._events[eventName] = [...(this._events[eventName] || []),callback]
}
// 触发事件的办法
emit(eventName, ...args) {if(!this._events[eventName]) {return}
// 遍历执行所有订阅的事件
this._events[eventName].forEach(fn=>fn(...args))
}
off(eventName,cb) {if(!this._events[eventName]) {return}
// 删除订阅的事件
this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)
}
// 绑定一次 触发后将绑定的移除掉 再次触发掉
once(eventName,callback) {const one = (...args)=>{
// 等 callback 执行结束在删除
callback(args)
this.off(eventName,one)
}
one.l = callback // 自定义属性
this.on(eventName,one)
}
}
测试用例
let event = new EventEmiter()
let login1 = function(...args) {console.log('login success1', args)
}
let login2 = function(...args) {console.log('login success2', args)
}
// event.on('login',login1)
event.once('login',login2)
event.off('login',login1) // 解除订阅
event.emit('login', 1,2,3,4,5)
event.emit('login', 6,7,8,9)
event.emit('login', 10,11,12)
公布订阅者模式和观察者模式的区别?
- 公布 / 订阅模式是观察者模式的一种变形,两者区别在于,公布 / 订阅模式在观察者模式的根底上,在指标和观察者之间减少一个调度核心。
- 观察者模式 是由具体指标调度,比方当事件触发,
Subject
就会去调用观察者的办法,所以观察者模式的订阅者与发布者之间是存在依赖的。 - 公布 / 订阅模式 由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在。
实现 reduce 办法
- 初始值不传怎么解决
- 回调函数的参数有哪些,返回值如何解决。
Array.prototype.myReduce = function(fn, initialValue) {var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0]; // 不传默认取数组第一项
startIndex = initialValue ? 0 : 1;
for(var i = startIndex; i < arr.length; i++) {// 把初始值、以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].reduce((initVal,curr,index,arr))
res = fn.call(null, res, arr[i], i, this);
}
return res;
}
实现 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);
}
实现一个 compose 函数
组合多个函数,从右到左,比方:
compose(f, g, h)
最终失去这个后果(...args) => f(g(h(...args))).
题目形容: 实现一个 compose
函数
// 用法如下:
function fn1(x) {return x + 1;}
function fn2(x) {return x + 2;}
function fn3(x) {return x + 3;}
function fn4(x) {return x + 4;}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11
实现代码如下
function compose(...funcs) {if (!funcs.length) return (v) => v;
if (funcs.length === 1) {return funcs[0]
}
return funcs.reduce((a, b) => {return (...args) => a(b(...args)))
}
}
compose
创立了一个从右向左执行的数据流。如果要实现从左到右的数据流,能够间接更改compose
的局部代码即可实现
- 更换
Api
接口:把reduce
改为reduceRight
- 交互包裹地位:把
a(b(...args))
改为b(a(...args))
数组去重办法汇总
首先: 我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);
// splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一
len--;
j--;
}
}
}
return arr;
}
思维: 双重
for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {let arr = a.concat(b);
return arr.filter((item, index)=> {//return arr.indexOf(item) === index
return arr.includes(item)
})
}
思维: 利用
indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) {return Array.from(new Set(array));
}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [{ name: "张三", age: "18"},
{name: "张三", age: "19"},
{name: "张三", age: "20"},
{name: "李四", age: "19"},
{name: "王五", age: "20"},
{name: "赵六", age: "21"}
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果长期对象中有这个名字,什么都不做
if (temp[curv.name]) { }else {
// 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到 prev 中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("后果", resources);
这种办法是利用高阶函数
reduce
进行去重,这里只须要留神initialValue
得放一个空数组[],不然没法push
实现 bind 办法
bind
的实现比照其余两个函数稍微地简单了一点,波及到参数合并(相似函数柯里化),因为bind
须要返回一个函数,须要判断一些边界问题,以下是bind
的实现
bind
返回了一个函数,对于函数来说有两种形式调用,一种是间接调用,一种是通过new
的形式,咱们先来说间接调用的形式- 对于间接调用来说,这里抉择了
apply
的形式实现,然而对于参数须要留神以下状况:因为bind
能够实现相似这样的代码f.bind(obj, 1)(2)
,所以咱们须要将两边的参数拼接起来 - 最初来说通过
new
的形式,对于new
的状况来说,不会被任何形式扭转this
,所以对于这种状况咱们须要疏忽传入的this
简洁版本
- 对于一般函数,绑定
this
指向 - 对于构造函数,要保障原函数的原型对象上的属性不能失落
Function.prototype.myBind = function(context = window, ...args) {
// this 示意调用 bind 的函数
let self = this;
// 返回了一个函数,...innerArgs 为理论调用时传入的参数
let fBound = function(...innerArgs) {//this instanceof fBound 为 true 示意构造函数的状况。如 new func.bind(obj)
// 当作为构造函数时,this 指向实例,此时 this instanceof fBound 后果为 true,能够让实例取得来自绑定函数的值
// 当作为一般函数时,this 指向 window,此时后果为 false,将绑定函数的 this 指向 context
return self.apply(
this instanceof fBound ? this : context,
args.concat(innerArgs)
);
}
// 如果绑定的是构造函数,那么须要继承构造函数原型属性和办法:保障原函数的原型对象上的属性不失落
// 实现继承的形式: 应用 Object.create
fBound.prototype = Object.create(this.prototype);
return fBound;
}
// 测试用例
function Person(name, age) {console.log('Person name:', name);
console.log('Person age:', age);
console.log('Person this:', this); // 构造函数 this 指向实例对象
}
// 构造函数原型的办法
Person.prototype.say = function() {console.log('person say');
}
// 一般函数
function normalFun(name, age) {console.log('一般函数 name:', name);
console.log('一般函数 age:', age);
console.log('一般函数 this:', this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
}
var obj = {
name: 'poetries',
age: 18
}
// 先测试作为结构函数调用
var bindFun = Person.myBind(obj, 'poetry1') // undefined
var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say
// 再测试作为一般函数调用
var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
bindNormalFun(12) // 一般函数 name: poetry2 一般函数 age: 12 一般函数 this: {name: 'poetries', age: 18}
留神:
bind
之后不能再次批改this
的指向,bind
屡次后执行,函数this
还是指向第一次bind
的对象