手写获取数组的重复元素,要求尽可能用多种方法实现
Let arr = [11, 23, 26, 23, 11, 9]
Const filterUnique = arr => arr.filter(i => arr.indexOf(i) !== arr.lastIndexOf(i))
console.log(filterUnique(arr)) // [11, 23, 23, 11]
手写 Array.prototype.reduce()
array.reduce(): 接收两个参数,一个处理数据的函数,一个是初始值
接收的处理数据函数,可接收4个参数(total, curValue, curIndex, array)
total(上一次调用回调返回的值,或者是提供的初始值)
curValue(数组中当前被处理的元素)
curIndex(当前元素在数组中的索引)
array(调用reduce的数组)
手动实现
- 定义一个函数myfunction(callback, initValue)
当this或者callback不为正确语法,抛出错误;this指向调用reduce的数组
把调用的值通过object(this)包装成一个类对象。
Array.prototype.reduce.call(1212, (pre,cut) =>{return ‘’ + pre+cur}, 0)
把手写的myReduce写入Array的原型链上Array.prototype.MyReduce = MyReduce;
function MyReduce(fun, initValue){
if (this === null){
// this 不存在,抛出错误
throw new TypeError(‘called on null or undefined’)
}
if (typeof fun !== ‘function’) {
// fun 不是function时,抛出错误
throw new TypeError(fun + ‘is not a function’);
}
const value = Object(this);
let preValue, curValue, curIndex;
if(initValue !== undefined){
preValue = initValue;
curValue = arr[0];
curIndex = 0;
} else {
preValue = arr[0];
curValue = arr[1];
curIndex = 1
}
for (let I = curIndex; I < value.length; I++){
const item = value[I];
preValue = fun(preValue, item, I, arr);
}
return preValue;
}
Function setReduce(preValue, curValue, index, arr){
return `${preValue} - ${curValue}`
}
// 把方法写入原型链上
Array.prototype.MyReduce = MyReduce;
2.Array.prototype.myReduce = function(callbackFn, initialValue) {
// 处理回调类型异常
if(Object.prototype.toString.call(callbackFn) !== ‘[object Function]’) {
throw new TypeError(callbackFn + ‘is not a function’)
}
var acc = initialValue || this[0];
var startIndex = initialValue ? 0 : 1;
for(var I = startIndex, Len = this.length; I < Len; I++){
acc = callbackFn(acc, this[I], I, this)
}
return acc;
}
Const arr = [1, 2, 3, 4]
arr.myReduce((total, cur) => total + cur); // 10
arr.myReduce((total, cur) => total + cur, 10); // 20
Arr.myReduce(’555’); // 555 is not a function
手写 Promise
先回顾下最简单的Promise使用方式:
const p1 = new Promise((resolve, reject) => {
console.log(‘create a promise’)
resolve(‘成功了’)
})
console.log(‘after new promise’)
const p2 = p1.then(data => {
console.log(data)
throw new Error(’失败了’)
})
const p3 = p2.then(data => {
console.log(‘success’, data)
}, err => {
console.log(‘faild’, err)
})
promise有三个状态:pending, fulfilled, or rejected
New promise时, 需要传递一个executor()执行器,立即执行
executor接受两个参数,分别是resolve和reject;
promise 的默认状态:pending
Promise 有一个value保存成功状态的值,可以是undefined/thenable/promise
Promise 有一个reason保存失败状态的值,
promise 只能从pending 到 rejected, 或者从 pending 到 fulfilled, 状态一旦确认,就不会再改变
promise 必须有一个then方法,then 接收两个参数,分别是promise成功的回调onFulfilled, 和 promise 失败的回调onRejected
若调用then, promise 已经成功,执行onFulfilled, 参数是promise 的value
若调用then,promise 已经失败,执行onRejected,参数是promise 的reason
then 抛出异常,把异常作为参数,传递下一个then的失败回调onRejected
// 三个状态: pending, fulfilled, rejected
Const pending = ‘pending’;
Const fulfilled = ‘fulfilled’;
Const rejected = ‘rejected’;
Class Promise {
constructor(executor){
// 默认状态为pending
this.status = pending;
// 存放成功状态的值,默认为undefined
this.value = undefined;
// 存放失败状态的值,默认为undefined
this.reason = undefined;
// 调用此方法就是成功
let resolve = (value) => {
// 状态为pending时可以更新状态,防止executor中调用两次resolve/reject方法
if (this.status === pending){
this.status = fulfilled;
this.value = value;
}
}
// 调用此方法就失败
let reject = (reason) => {
// 状态为pending时可以更新状态,防止executor中调用两次resolve/reject方法
if (this.status === pending){
this.status = rejected;
this.reason = reason;
}
}
try {
executor(resolve, reject)
} catch(error) {
// 发生异常时执行失败逻辑
reject(error)
}
//包含一个then,接收两个参数onFulfilled, onRejected
then(onFulfilled, onRejected){
if(this.status === fulfilled) {
onFulfilled(this.value)
}
if(this.status === rejected) {
onRejected(reason)
}
}
}
}
手写 apply
Function.prototype.myApply = function(context){
if(context === undefined || context === null){
context = window
} else {
context = Object(context)
}
// 判断是否为类数组对象
function isArrayLike(o) {
if (
o && // o 不是null,undefined等
typeof o === ‘object’ && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < Math.pow(2,32)
){
return true
}
return false
}
const fn = Symbol(‘uniqueFn’)
context[fn] = this
let args = arguments[1]
let result
if (!Array.isArray(args) && isArrayLike(args)){
throw new Error(‘CreateListFromArrayLike called on non-object’) // 第二个参数不为数组且不为类对象数组
} else {
args = Array.from(args) // 转为数组
result = context[fn](…args)
}
delete context[fn]
return result
}
手写 call 函数
call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数
Function.prototype.myCall = function(context){
if(context === undefined || context === null){
context = window // 指定null 和 undefined 的this值会自动指向全局对象
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的实例对象
}
const fn = Symbol(‘uniqueFn’) // 用Symbol是防止跟上下文的原属性冲突
context[fn] = this
let arg = […arguments].slice(1)
let result = context[fn](…arg)
delete context[fn]
return result
}
手写bind方法
实现bind要额外考虑一个问题:方法一个绑定函数也能使用new操作符创建对象:这种行为像把原函数当成构造器,提供的this值被忽略,同时调用时的参数被提供给模拟函数
Function.prototype.myBind = function(context){
if (typeof this !== ‘function’){
throw new Error(‘Expected this is a function’)
}
let self = this
let args = […arguments].slice(1)
let fn = function(…innerArg){
const isNew = this instanceof fn // 返回的fn是否通过new调用
return self.apply(isNew ? This : Object(context), args.concat(innerArg)). // new 调用就绑定到this上,否则绑定到传入的context上
}
// 复制原函数的prototype给fn,一些情况下函数没有prototype,比如箭头函数
if(self.prototype){
fn.prototype = Object.create(self.prototype)
}
return fn // 返回拷贝的函数
}
手写 new
New 到底做了什么?
- 创建一个新的对象
- 继承父类原型上的方法
- 添加父类的属性到新的对象上并初始化,保存方法的执行结果
- 如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回新创建的对象
Function _new(obj, …rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上,并获取obj函数执行的结果
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回新创建的对象
return typeof result === ‘object’ ? result : newObj;
}
手写深拷贝
基础版本:
function deepClone(target){
let result;
if (typeof target === ‘object’){
if(Array.isArray(target)){
result = []
for(let I in target){
result.push(deepClone(target[I]))
}
}
else {
result = {}
for (let key in target){
result[key] = target[key]
}
}
} else {
result = target;
}
return result;
}
Let A = [1,2,3, {a:1, b:2}]
Let B = deepClone(A)
B[3].a = 99
console.log(A). // [1,2,3,[4,5]]
console.log(B) // [1,2,3,{a:99, b: 2}]
改用class类写
class DeepClone{
constructor(){
cloneVal: null;
}
clone(val, map = new WeakMap()){
let type = this.getType(val); // 当是引用类型的时候先拿到其确定的类型
if(this.isObj(val)){
switch(type){
case ‘date’: // 日期类型重新new 一次传入之前的值,data实例化本身结果不变
return new Date(val);
break;
case ‘regexp’: // 正则类型直接new 一个新的正则传入source和flags即可
return new RegExp(val.source, val.flags);
break;
case ‘function’: // 若是函数类型是直接通过function包裹返回一个新的函数,并且改变this指向
return new RegExp(val.source, val.flags);
break;
default:
this.cloneVal = Array.isArray(val) ? [] : {};
if (map.has(val)) return map.get(val)
map.set(val, this.cloneVal)
for(let key in val){
if (val.hasOwnProperty(key)) {. // 判断是不是自身的key
this.cloneVal[key] = new DeepClone().clone(val[key], map);
}
}
return this.cloneVal;
}
} else {
return val; // 当是基本数据类型的时候直接返回
}
}
/** 判断是否是引用类型*/
isObj(val) {
return (typeof val == ‘object’ || typeof val == ‘function’) && val != null
}
/**获取类型*/
getType(data){
var s = Object.prototype.toString.call(data);
return s.match(/\[object(.*?)\]/)[1].toLowerCase();
}
}
/** 测试*/
var a = {
a: 1,
b: true,
c: undefined,
d: null,
e: function(alb){
return a + b
},
f: /\W+/gi,
time: new Date(),
}
Const deepClone = new DeepClone()
Let b = deepClone.clone(a)
console.log(b)
手写虚拟 dom 转换成真实 dom
{
tag: ‘DIV’,
attrs: {
id: ‘app’
},
children: [
{
tag: ’SPAN’,
children: [
{tag: ‘A’, children: []}
]
},
{
tag: ’SPAN’,
children: [
{tag: ‘A’, children: []},
{tag: ‘A’, children: []}
]
}
]
}
// 把上述虚拟Dom转化成下方真实Dom
<div id=“app”>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
// 真正的渲染函数
function _render(vnode){
// 如果是数字类型转化为字符串
if(typeof node === ’number’){
vnode = String(vnode);
}
// 字符串类型直接是文本节点
if (typeof vnode === ‘string’){
return document.createTextNode(vnode);
}
// 普通DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs){
// 遍历属性
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key];
dom.setAttribute(key, value);
})
}
// 子数组进行递归操作 这一步是关键
vnode.children.forEach((child) => dom.appendChild(_render(child)));
return dom;
}
手写 assign,要考虑全面,包括 symbol 也要考虑在内
let obj1 = {
a: 1,
b: 2
}
obj1[Symbol(‘abc’)] = ‘abc’
let obj2 = {
a: 3,
d: 5
}
obj2[Symbol(‘abc’)] = ‘abc’
function myAssign(…obj){
let res = {}
for(let obj of objs) {
for(let prop of Reflect.ownKeys(obj)) {
res[prop] = obj[prop]
}
}
return res
}
let obj3 = myAssign(obj1, obj2)
console.log(obj3)
手写 ES6 的模板字符串
// 1.创建一个正则表达式
let args = //
// 2.从${}中取值
let args = /${}/
// 3.由于$, {, }都是有特殊含义的,需要转义
let args = /\$\{\}/
// 4.现在要将${}中的变量名取到,需要使用()
/* 正则圆括号:
(1)在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰
(2)取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到
*/
let args = /\$\{()\}/
// 5.变量名可能是任意的字符, 匹配任意的是. 变量的名称可能是多个字符,需要用到 + 还需要全局匹配
let args = /\$\{(.+)\}/g
// 6.步骤5中的正则,是贪婪模式的,如果存在多个${}, 那第一次把所有匹配到的内容都拿到,是这样的
// 惰性的正则(加?可以使其变为惰性的,取到1次就不再取了)
let args = /\$\{(.+?)\}/g
// 实现strParse函数
function strParse(string){
let args = /\$\{(.+?)\}/g
// replace方法有两个参数,第二个参数是要替换的内容,或者是一个返回字符串的函数,这个函数有几个参数
return string.replace(args, function(){
return eval(arguments[1])
})
}
// 测试一下
Let esStr = ‘第一个字符串是${str1}, 第二个字符串是${str2}’
Let str = strParse(esStr)
console.log(str) // 第一个字符串是abc,第二个字符串是def
手写发布订阅模式,订阅,触发,移除
发布-订阅模式其实是一种对象间一对多的依赖关系 ,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知,订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel), 当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码
实现思路:
1.创建一个EventEmitter类
2.在该类上创建一个事件中心(Map)
3.on方法用来把函数fn都加到事件中心中(订阅者注册事件到调度中心)
4.emit方法取到arguments里第一个当做event,根据event值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
5.off方法可根据event值取消订阅
6.once方法只监听一次,调用完毕后删除缓存函数(订阅一次)
7.注册一个newListener用于监听新的事件订阅
第一步,创建一个类,并初始化一个事件存储中心
class EventEmitter{
// 用来存放注册的事件和回调
constructor(){
this._events = {};
}
}
第二步,实现事件的订阅方法on(将事件回调函数存储到对应的事件上)
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {}
}
on(eventName, callback){
// 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
const callbacks = this._events[eventName] || [];
callbacks.push(callback);
this._events[eventName] = callbacks
}
}
第三步,实现事件的发布方法emit(基本思路:获取到事件对应的回调函数依次执行)
class EventEmitter{
// 用来存放注册的事件和回调
constructor(){
this._events = {}
}
// args用于收集发布事件时传递的参数
emit(eventName, …args){
const callbacks = this._events[eventName] || [];
callbacks.forEach(cb => cb(…args))
}
}
第四步,实现事件的取消订阅的方法off(找到事件对应的回调函数,删除对应的回调函数)
class EventEmitter{
// 用来存放注册的事件和回调
constructor(){
this._events = {}
}
off(eventName, callback){
const callbacks = this._events[eventName] || []
const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback // 用于once的取消订阅)
this._events[eventName] = newCallbacks
}
}
第五步,实现事件的单次订阅方法once(基本思路:1先注册 2.事件执行后取消订阅)
class EventEmitter{
// 用来存放注册的事件与回调
constructor(){
this._events = {}
}
once(eventName, callback){
// 由于需要在回调函数执行后,取消订阅当前事件,需要对传入的回调函数做一层包装,然后绑定包装后的函数
const one = (…args) => {
// 执行回调函数
callback(…args)
// 取消订阅当前事件
this.off(eventName, one)
}
// 考虑:如果当前事件在未执行,被用户取消订阅,能否取消
// 由于:订阅事件时,修改了原回调函数的引用,所以,用户触发off时不能找到对应回调函数
// 所以,需要在当前函数与用户传人的回调函数做一个绑定,通过自定义属性来实现
one.initialCallback = callback;
this.on(eventName, one)
}
}
第六步,注册一个newListener用于监听新的事件订阅(在用户注册事件时,发布一下newListener事件)
class EventEmitter{
// 用来存放注册的事件和回调
constructor(){
this._events = {}
}
on(eventName, callback){
// 如果绑定事件不是newListener就触发回调
if (this._events[eventName]){
if(this.eventName !== ‘newListener’){
this.emit(‘newListener’, eventName)
}
}
// 由于一个事件可能注册多个回调函数,使用数组来存储事件队列
const callbacks = this._events[eventName] || [];
callbacks.push(callback);
this._events[eventName] = callbacks
}
}
手写斐波那锲数列
// count: 指的是第几个
// 返回值是斐波那锲数列第count项的值
function fibonacci(count){
if (count === 1 || count === 2) return 1
return fibonacci(count - 2) + fibonacci(count -1)
}
// for循环版(比前者空间复杂度更低)
function fibonacci(n){
let n1 = 1; n2 = 1;
for(let I = 2; I < n; I++){
[n1, n2] = [n2, n1+n2]
}
return n2
}
for(let I = 1; I <=10, I++){
console.log(fibonacci(I))
}
手写防抖和节流
防抖:当事件触发时,相应函数的不会被立即触发,而是会被推迟执行
只有等待一段时间后也没有再次触发该事件,才会真正执行响应函数
输入框频繁输入
频繁点击按钮,触发某个事件
监听浏览器滚动事件
监听用户缩放浏览器resize事件
简易版防抖函数:
// 第一个参数是需要进行防抖处理的函数,第二个参数是延迟时间,默认是1s,
function debounce(fn, delay = 1000){
// 实现防抖函数的核心是使用setTimeout
// time变量用于保存setTimeout返回的id
let time = null
function _debounce(…args){
// 如果time不为0,有定时器存在,将该定时器清除
if(time !== null){
clearTimeout(time)
}
time = setTimeout(() => {
// 使用apply改变fn的this, 同时将参数传递给fn
fn.apply(this, args)
}, delay)
}
// 防抖函数会返回另一个函数,该函数才是真正被调用的函数
return _debounce
}
什么是节流
节流函数会按照一定的频率来执行函数
节流类似于技能cd,不管按了多少次,必须等cd结束后才能释放技能,如果在cd时间段,不管触发了几次事件,只会执行一次,只有当下一次cd转换,才会再次执行
- 实现节流函数
// interval 间隔时间,也就是cd的长短
function throttle(fn, interval){
// 该变量用于记录上一次函数的执行事件
let lastTime = 0
const _throttle = function(…args){
// 获取当前时间
const nowTime = new Date().getTime()
// cd剩余时间
const remainTime = nowTime - lastTime
// 如果剩余时间大于间隔时间,可以再次执行函数
if(remainTime - interval >= 0){
fn.apply(this, args)
// 将上一次函数执行的时间设置为nowTime, 这样下次才能重新进入cd
lastTime = nowTime
}
}
// 返回_throttle函数
return _throttle
}
网友评论