new
// 使用[`Object.__proto__`]
Function.prototype.construct = function (aArgs) {
var oNew = {};
oNew.__proto__ = this.prototype;
this.apply(oNew, aArgs);
return oNew;
};
//使用`Object.create()`方法
Function.prototype.construct = function (aArgs) {
var oNew = Object.create(this.prototype);
this.apply(oNew, aArgs);
return oNew;
};
// 使用闭包
Function.prototype.construct = function(aArgs) {
var fConstructor = this, fNewConstr = function() {
fConstructor.apply(this, aArgs);
};
fNewConstr.prototype = fConstructor.prototype;
return new fNewConstr();
}
// 使用 Function 构造器:
Function.prototype.construct = function (aArgs) {
var fNewConstr = new Function("");
fNewConstr.prototype = this.prototype;
var oNew = new fNewConstr();
this.apply(oNew, aArgs);
return oNew;
};
手写call、apply、bind
Function.prototype.call()
- call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
Function.prototype.myCall = function(context, args){
if(typeof context === 'undefined' || context === null){
context = 'undefined' !== typeof window? window : globalThis
}
let sb = Symbol();
context[sb] = this
//如果context是一个对象,则对象调用对象上的方法this指向该对象
let fn = context[sb](args)
return fn
}
fun.myCall(obj,1,2)
Function.prototype.apply()
- apply()方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
- 注意:call()方法的作用和apply()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
- 手写apply()
Function.prototype.myApply = function(context, args){
if(typeof context === 'undefined' || context === null){
context = 'undefined' !== typeof window? window : globalThis
}
let sb = Symbol();
context[sb] = this
//如果context是一个对象,则对象调用对象上的方法this指向该对象
let fn = context[sb](...args)
return fn
}
Function.prototype.bind()
- bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.myBind = function(context, ...args){
if(typeof context === 'undefined' || context === null){
context = 'undefined' !== typeof window? window : globalThis
}
let self = this
return function(){
return self.apply(context, args.concat(Array.prototype.slice.call(arguments)))
}
}
fun.myBind(obj,1,2)(3,4)
详解bind实现步骤
- bind是绑在原型上的方法 由于function XXX 的原型链 指向的是Function.prototype,因此我们在调用XXX.bind的时候,调用的是Function.prototype上的方法。
Function.prototype._bind = function(){}
这样,我们就可以在一个构造函数上直接调用我们的_bind方法
function myFun(){}
myFun._bind();
- 改变this的指向 bind最核心的特性就是改变this的指向,并且返回一个函数;改变this的指向我们可以通过apply和call实现。
- 首先通过self来保存当前的this,也就是传入的函数,因为我们知道this具有 隐式绑定 的规则。
Function.prototype._bind = function(thisObj){
const self = this //这里的this是调用_bind的函数
return function(){
self.apply(thisObj);
}
}
function myFun(){
console.log(this.a)
}
var obj = {a:1}
myFun._bind(obj)() //1
- 支持柯里化
function fn(x) {
return function(y) {
return x + y
}
}
var fn1 = fn(1)
fn1(2) //3
- 柯里化使用了闭包,当我们执行fn(1)时候,函数内使用了外层函数的x,从而形成了闭包。
- bind函数类似,首先获取外部函数的arguments,去除绑定的对象,其他的参数保存为变量args;然后return方法内再一次获取当前函数的arguments,最终用合并外层和内存函数的参数。
Function.prototype._bind = function(thisObj){
const self = this
const args = [...arguments].slice(1)
return function(){
const finalArgs = [...args, ...arguments]
self.apply(thisObj, finalArgs)
}
}
var obj = {i:1}
function myfun(a,b,c){
console.log(this.i + a + b + c)
}
var myFun1 = myFun._bind(obj, 1, 2)
myFun1(3) //7
- 考虑new的调用 通过bind绑定之后的方法,依然可以通过new来实现实例化,new的优先级高于bind
- 对比原始bind和上叙第三层_bind
// 原生
var obj = {i:1}
function myFun(a, b, c){
// 此处用new方法,this指向的是当前函数myFun
console.log(this.i + a + b + c);
}
var myFun1 = myFun.bind(obj,1,2);
new myFun1(3); // NAN
// 第三层的_bind
var obj = {i:1}
function myFun(a, b, c){
console.log(this.i + a + b + c);
}
var myfun1 = myFun._bind(obj, 1, 2);
new myFun1(3); // 7
- 因此,我们需要在bind内部,对new的进行处理,而new.target属性,正好可以用来检测构造函数方法是否是通过new运算符来被调用的。
- 实现一个new
function _create() {
// 1. 创建一个空的简单对象(即{});
let obj = {}
// 2. 链接到该对象(设置该对象的constructor)到另一个对象;
let Constructor = [].shift.call(arguments) //获取第一个参数
obj.__proto__ = Construtor.prototype
// 3. 将步骤1新创建的对象作为this的上下文;
let result = Construtor.apply(obj, arguments)
// 4. 如果该函数没有返回对象,则返回this
return typeof result === "object" ? retult : obj;
}
Function.prototype._bind(){
var self = this
// 将参数转为数组,再剔除第一个参数
var args = [...arguments].slice(1);
return function(){
var finalArgs = [...args, ...arguments];
if(new.target !== undefined){
var result = self.apply(this,finalArgs);
if(result instanceof Object){
return result;
}
return this;
}else{
// apply接收的第二个参数是类数组对象
return self.apply(args, finalArgs);
}
}
}
- 保留函数原型
- 当我们的构造函数有prototype属性,我们需要将prototype属性补上;
- 调用对象必须是函数
Function.prototype._bind(){
// 1. 判断是否为函数调用
if(typeof this !== 'function' || Object.prototype.toString.call(this) !== '[object Function]'){
return new TypeError(this + 'must be a function');
}
// 2. 获取外层参数
var args = [].slice(1).call([...arguments]);
// 3. 保留this即调用_bind的函数
var self = this
var bound = function(){
// 4. 合并外层和内部的参数
var finalArgs = [...args, ...arguments];
// 5. 判断是否是new的形式
if(new.target !== undefined){
var result = self.apply(this, finalArgs);
if(result instanceof Object){
return result;
}
return this;
}else{
return self.apply(args, finalArgs);
}
}
// 判断构造函数是否有原型
if(self.prototype) {
// 为什么使用了Object.create?因为我们要防止bound.prototype的修改而导致self.prototype被修改
bound.prototype = Object.create(self.prototype);
bound.prototype.constructor = self;
}
return bound;
}
订阅/发布
// 事件总线 | 发布订阅
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn){
if(this.cache[name]){
this.cache[name].push(fn)
}else{
this.cache[name] = [fn]
}
}
off(name, fn){
const tasks = this.cache[name]
if(tasks){
const index = tasks.findIndex((f) => f === fn || f.callback === fn)
if(index >= 0){
tasks.splice(index, 1)
}
}
}
emit(name, once = false){
// 创建副本,如果回调函数内解析注册相同事件,会造成四循环
const tasks = this.cache[name].slice()
for(let fn of tasks){
fn()
}
if(once){
delete this.cache[name]
}
}
}
// 测试
const eventEmitter = new EventEmitter()
eventEmitter.on("beforeRun", function(){
console.log("注册1")
})
eventEmitter.on("beforeRun", function(){
console.log("注册2")
})
eventEmitter.emit("beforeRun")
实现Promise
Promise.resolve
Promise.resolve2 = (value) =>{
//是Promise实例,直接返回即可
if(value && typeof value === 'object' && value instanceof Promise){
return value
}
// 否则其他情况一律再通过Promise包装一下
return new Promise((resolve) => {
resolve(value)
})
}
// 测试1
Promise.resolve(111).then(res=>{
console.log(res)
})
Promise.resolve2(111).then(res=>{
console.log(res)
})
// 测试2:参数是一个Promise
const p = new Promise((resolve) =>{
resolve(222)
})
Promise.resolve(p).then(res=>{
console.log(res)
})
Promise.resolve2(p).then(res=>{
console.log(res)
})
Promise.reject
Promise.reject2 = (err) =>{
return new Promise((_, reject)=>{
reject(err)
})
}
// 测试1
Promise.reject(111).catch(res=>{
console.log(res)
})
Promise.reject2(111).catch(res=>{
console.log(res)
})
// 测试2:参数是一个Promise
const p = new Promise((resolve) =>{
resolve(222)
})
Promise.reject(p).catch(res=>{
console.log(res)
})
Promise.reject2(p).catch(res=>{
console.log(res)
})
Promise.all
所有成功则成功和1个失败即失败
Promise.all2 = (promises) => {
return new Promise((resolve, reject)=>{
let len = promises.length
const results = new Array(len)
let count = 1
// index 用于保证结果顺序
promises.forEach((promise, index) => {
// 对p进行一次包装,防止非Promise对象
Promise.resolve(promise).then(res => {
results[index] = res
if(count === len) resolve(results)
count += 1
}).catch(err => {
reject(err)
})
});
})
}
// 测试
const p1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(1)
}, 1000)
})
const p2 = Promise.resolve(2)
const p3 = 3
const p4 = new Date()
const promises = [p1, p2, p3, p4]
const pp = Promise.all(promises)
pp.then(res=>{
console.log(res)
})
const pp2 = Promise.all2(promises)
pp2.then(res=>{
console.log(res)
}).catch(err => {
console.log(err)
})
Promise.allSettled
Promise.allSettled2 = (promises) => {
return new Promise((resolve, reject) => {
let len = promises.length
let count = 1
const results = new Array(len)
promises.forEach((promise, index) => {
// 对p进行一次包装,防止非Promise对象
Promise.resolve(promise).then(res => {
results[index] = {
status: 'fulfilled',
value: res
}
if (count++ === len) resolve(results)
}).catch(err => {
results[index] = {
status: 'rejected',
value: err
}
if (count++ === len) resolve(results)
})
});
})
}
// 测试
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
const p2 = Promise.resolve(2)
const p3 = 3
const p4 = new Date()
const p5 = Promise.reject(111)
const promises = [p1, p2, p3, p4, p5]
const pp = Promise.allSettled(promises).then(res => {
console.log(res)
})
const pp2 = Promise.allSettled2(promises).then(res => {
console.log(res)
})
Promise.race
Promise.race2 = (promises) => {
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
// 对p进行一次包装,防止非Promise对象
Promise.resolve(promise).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
});
})
}
// 测试
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
const p2 = Promise.resolve(2)
const p3 = 3
const p4 = new Date()
const p5 = Promise.reject(111)
const promises = [p1, p2, p3, p4, p5]
Promise.race(promises).then(res => {
console.log(res)
})
Promise.race2(promises).then(res => {
console.log(res)
})
实现Async/Await
function asyncFunc(gen){
// async函数返回的是一个promise
return new Promise((resolve, reject)=>{
// 执行传入的generator函数,generator函数返回的是一个迭代器
const iter = gen()
// 递归迭代器
function next(value){
let result
try {
result = iter.next(value)
} catch (error) {
reject(error)
}
if(result.done) return resolve(result.value)
Promise.resolve(result.value).then(data=>{
next(data)
}).catch(err => {
reject(err)
})
}
next()
})
}
实现一个可以控制请求并发数的最高效的发送请求功能。
参考链接:
function limitRequest(urls, limit){
return new Promise((reslove, reject)=>{
let len = urls.length
const results = new Array(len)
while(limit > 0 && urls.length > 0 ){
send(urls.shift(), len - urls.length - 1)
limit -= 1
}
function send(url, index){
fetch(url)
.then(response => response.json())
.then(res=>{
results[index] = res
if(urls.length > 0){
send(urls.shift(), len - urls.length - 1)
}else{
reslove(results)
}
})
}
})
}
// 服务端
const express = require('express')
const app = express()
app.use(express.static(__dirname))
app.get('/a', function(req, res){
let id = req.query.id
setTimeout(()=>{
res.setHeader("Access-Control-Allow-Origin", "*")
res.json({code:200, message: "成功接收到" + id})
}, id * 1000)
})
app.listen(91)
// 测试
const urls = [
'http://127.0.0.1:91/a?id=1',
'http://127.0.0.1:91/a?id=2',
'http://127.0.0.1:91/a?id=3',
'http://127.0.0.1:91/a?id=4',
'http://127.0.0.1:91/a?id=5',
'http://127.0.0.1:91/a?id=6',
]
limitRequest(urls, 3).then(res=>{
console.log(res)
})
防抖节流
函数防抖和节流,都是控制事件触发频率的方法。应用场景有很多,如:输入框持续输入、多次触发点击事件、onScroll等等。
- 防抖:指定的时间内执行多次,只执行最后一次
function debounce(fn, delay = 300){
let timer //闭包引用的外界变量
return function () {
const args = arguments
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, delay);
}
}
- 节流:指定的时间内执行多次,只执行第一次
function throttle(fn, delay){
let run = true
return function(){
const args = arguments
if(!run){
return
}
run = false
setTimeout(() => {
fn.apply(this, args)
run = true
}, delay)
}
}
浅拷贝和深拷贝
JSON.stringify()实现深拷贝
const old_obj = {
a: 1,
b: 'bb',
c: new Date(),
d: function(){},
e: [1,2,3]
}
const new_obj = JSON.parse(JSON.stringify(old_obj))
console.log(new_obj) //{ a: 1, b: 'bb', c: '2022-02-15T09:27:14.694Z', e: [ 1, 2, 3 ] }
问题: 无法实现函数的拷贝
Web Worker实现深拷贝
// index.html
const old_obj = {
a: 1,
b: 'bb',
c: new Date(),
// d: function d(){},
e: [1,2,3]
}
const worker = new Worker('work.js')
worker.postMessage(old_obj)
worker.onmessage = function(message){
const new_obj = message.data
new_obj.a = 111
console.log(old_obj)
console.log(new_obj)
}
// work.js
self.addEventListener('message', function (e) {
self.postMessage(e.data)
})
问题:无法实现函数的复制。old_obj对象中有函数会报错,报错如下:
Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': function d(){} could not be cloned.
lodash库实现深拷贝
const _ = require('lodash')
_.cloneDeep(old_obj)
递归遍历实现拷贝
// 使用Object.prototype.toString().call实现类型判断;其他还有typeof/instanceof
function deepClone(object){
const type = Object.prototype.toString.call(object)
console.log(type)
if(type === '[object Object]'){
const new_obj = new Object()
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
new_obj[key] = deepClone(object[key])
}
}
return new_obj
}else if(type === '[object Array]'){
const new_arr = new Array()
object.forEach((element) => {
new_arr.push(deepClone(element))
});
return new_arr
}else{
return object
}
}
ES5实现继承
高阶函数
组合函数
柯里化(异步求和)
偏函数
惰性函数
缓存函数
数组——扁平化
使用Array.prototype.flat()
const old_arr = [1,[2,3,[4,5,[6,7]]]]
const new_arr = old_arr.flat(4)
console.log(new_arr) //[1,2,3,4,5,6,7]
数组-去重
通过转换为Set去重
const old_arr = [1,2,2,2,3,4,4]
const new_set = new Set(old_arr)
const new_arr = [...new_set]
console.log(new_arr) //[ 1, 2, 3, 4 ]