共计 10070 个字符,预计需要花费 26 分钟才能阅读完成。
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),
)
})
})
}
}
查找字符串中呈现最多的字符和个数
例: 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}次 `);
实现数组的 push 办法
let arr = [];
Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ;
}
return this.length;
}
手写 Promise.race
该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态 只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.
Promise.race = function (args) {return new Promise((resolve, reject) => {for (let i = 0, len = args.length; i < len; i++) {args[i].then(resolve, reject)
}
})
}
模仿 Object.create
Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。
// 模仿 Object.create
function create(proto) {function F() {}
F.prototype = proto;
return new F();}
实现数组的 filter 办法
Array.prototype._filter = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {fn(this[i]) && res.push(this[i]);
}
return res;
}
手写 instanceof 办法
instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
实现步骤:
- 首先获取类型的原型
- 而后取得对象的原型
- 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
具体实现:
function myInstanceof(left, right) {let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
验证是否是身份证
function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return regx.test(number);
}
实现简略路由
// 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]()}
}
参考:前端手写面试题具体解答
实现 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;
};
将数字每千分位用逗号隔开
数字有小数版本:
let format = n => {let num = n.toString() // 转成字符串
let decimals = ''
// 判断是否有小数
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {return num} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是 3 的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是 3 的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
数字无小数版本:
let format = n => {let num = n.toString()
let len = num.length
if (len <= 3) {return num} else {
let remainder = len % 3
if (remainder > 0) { // 不是 3 的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是 3 的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'
实现数组元素求和
- arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
- arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) {if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21
实现防抖函数(debounce)
防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则从新计时。
那么与节流函数的区别间接看这个动画实现即可。
手写简化版:
// 防抖函数
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {clearTimeout(timer);
timer = setTimeout(() => {fn.apply(this, args);
}, delay);
};
};
实用场景:
- 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
- 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似
生存环境请用 lodash.debounce
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;
}
查找文章中呈现频率最高的单词
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;
}
应用 Promise 封装 AJAX 申请
// promise 封装实现:function getJSON(url) {
// 创立一个 promise 对象
let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
// 新建一个 http 申请
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
// 当申请胜利或失败时,扭转 promise 的状态
if (this.status === 200) {resolve(this.response);
} else {reject(new Error(this.statusText));
}
};
// 设置谬误监听函数
xhr.onerror = function() {reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置申请头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 申请
xhr.send(null);
});
return promise;
}
实现数组的 flat 办法
function _flat(arr, depth) {if(!Array.isArray(arr) || depth <= 0) {return arr;}
return arr.reduce((prev, cur) => {if (Array.isArray(cur)) {return prev.concat(_flat(cur, depth - 1))
} else {return prev.concat(cur);
}
}, []);
}
将 js 对象转化为树形构造
// 转换前:source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
代码实现:
function jsonToTree(data) {
// 初始化后果数组,并判断输出数据的格局
let result = []
if(!Array.isArray(data)) {return result}
// 应用 map,将以后对象的 id 与以后对象对应存储起来
let map = {};
data.forEach(item => {map[item.id] = item;
});
//
data.forEach(item => {let parent = map[item.pid];
if(parent) {(parent.children || (parent.children = [])).push(item);
} else {result.push(item);
}
});
return result;
}
用正则写一个依据 name 获取 cookie 中的值的办法
function getCookie(name) {var match = document.cookie.match(new RegExp('(^|)' + name + '=([^;]*)'));
if (match) return unescape(match[2]);
}
- 获取页面上的
cookie
能够应用document.cookie
这里获取到的是相似于这样的字符串:
'username=poetry; user-id=12345; user-roles=home, me, setting'
能够看到这么几个信息:
- 每一个 cookie 都是由
name=value
这样的模式存储的 - 每一项的结尾可能是一个空串
''
(比方username
的结尾其实就是), 也可能是一个空字符串' '
(比方user-id
的结尾就是) - 每一项用
";"
来辨别 - 如果某项中有多个值的时候,是用
","
来连贯的 (比方user-roles
的值) - 每一项的结尾可能是有
";"
的(比方username
的结尾),也可能是没有的 (比方user-roles
的结尾) - 所以咱们将这里的正则拆分一下:
'(^|)'
示意的就是获取每一项的结尾,因为咱们晓得如果^
不是放在[]
里的话就是示意结尾匹配。所以这里(^|)
的意思其实就被拆分为(^)
示意的匹配username
这种状况,它后面什么都没有是一个空串 (你能够把(^)
了解为^
它前面还有一个暗藏的''
);而|
示意的就是或者是一个" "
(为了匹配user-id
结尾的这种状况)+name+
这没什么好说的=([^;]*)
这里匹配的就是=
前面的值了,比方poetry
;刚刚说了^
要是放在[]
里的话就示意"除了 ^ 前面的内容都能匹配"
,也就是非的意思。所以这里([^;]*)
示意的是除了";"
这个字符串别的都匹配 (*
应该都晓得什么意思吧,匹配 0 次或屡次)- 有的大佬等号前面是这样写的
'=([^;]*)(;|$)'
,而最初为什么能够把'(;|$)'
给省略呢?因为其实最初一个cookie
项是没有';'
的,所以它能够合并到=([^;]*)
这一步。 - 最初获取到的
match
其实是一个长度为 4 的数组。比方:
[
"username=poetry;",
"","poetry",";"
]
- 第 0 项:全量
- 第 1 项:结尾
- 第 2 项:两头的值
- 第 3 项:结尾
所以咱们是要拿第 2 项 match[2]
的值。
- 为了避免获取到的值是
%xxx
这样的字符序列,须要用unescape()
办法解码。
实现 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);
}
正文完
发表至: javascript
2022-10-18