1.实现bind apply call
bind, apply, call的本质都是改变作用域. Function原型链中的this, 指的是function本身
bind会返回一个新的函数
Function.prototype.myBind = function(context) {
const key = Symbol('fn');
context[key] = this;
return function (...args) {
const res = context[key](...args);
delete context[key];
return res;
}
}
const test = {
name: "fy",
showName: function (last) {
console.log(this.name + " is " + last);
},
};
test.showName.myBind({ name: "Mr.fy" })("handsome");
call语法的重点是参数是分开传入的(没有处理async)
Function.prototype.myCall = function (context, ...args) {
const key = Symbol('fn');
context[key] = this;
const res = context[key](...args);
delete context[key];
return res;
}
function testapply(arg1, arg2, arg3) { console.log('name: ', this.name, arg1, arg2, arg3); }
testapply.call({name: 'aaa'}, 111,222,333);
testapply.myCall({name: 'aaa'}, 111,222,333);
apply语法的重点是参数被作为列表传入
Function.prototype.myApply = function (context, args) {
const key = Symbol('fn');
context[key] = this;
const res = context[key](...args);
delete context[key];
return res;
}
function testapply(arg1, arg2, arg3) { console.log('name: ', this.name, arg1, arg2, arg3); }
testapply.call({name: 'aaa'}, [111,222,333]);
testapply.myCall({name: 'aaa'}, [111,222,333]);
2.new_create_instanceof
new操作符做了这些事:
- 创建一个全新的对象
- 这个对象的__proto__要指向构造函数的原型prototype
- 它将新生成的对象的
__proto__属性赋值为构造函数的 prototype 属性,使得通过构造函数创建的所有对象可以共享相同的原型。 这意味着同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一个类的对象。
- 执行构造函数,使用 call/apply 改变 this 的指向
- 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
function myNew(constructor, ...args) {
// 创建一个新的空对象
const obj = {};
// 将这个空对象的__proto__指向构造函数的原型
// obj.__proto__ = Con.prototype;
Object.setPrototypeOf(obj, constructor.prototype);
const res = constructor.apply(obj, args)
return res instanceof Object ? res : obj;
}
实现object.create
function create(obj) {
const newObj = {};
newObj.__proto__ = obj;
newObj.__proto__.constructor = obj.__proto__.constructor;
return newObj;
}
实现instanceof instanceof的关键在于检查原型链
- proto(隐式原型)与prototype(显式原型)
- 每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象, prototype是函数才有的属性
- JavaScript中任意对象都有一个内置属性[[prototype]], 大多数浏览器都支持通过__proto__来访问
- 隐式原型指向创建这个对象的函数(constructor)的prototype
function myInstanceOf(left, right) {
const prototype = right.prototype;
let proto = Object.getPrototypeOf(left);
while(true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
3.深拷贝
简单的深拷贝可以通过JSON.parse这种方式实现, 但这种方式无法解决function/Symbol/循环引用的情况.
function deepClone(obj) {
const map = new WeakMap();
function clone(target) {
if (typeof target === 'object' && target !== null) {
if (map.has(target)) {
return map.get(target);
}
const result = Array.isArray(target) ? [] : {};
map.set(target, result);
for (let key in target) {
if (target.hasOwnProperty(key)) {
result[key] = clone(target[key]);
}
}
} else {
// 对于function, string, number, boolean, symbol, undefined, null, 都可以直接返回
return target;
}
}
return clone(obj);
}
4.节流防抖
防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数 如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算
function debounce(fn, wait) {
let timeout = null;
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(context, args);
}, wait);
}
}
节流函数原理: 指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。
const throttle = (func, wait = 50) => {
let lastTime = 0
return function(...args) {
let now = +new Date()
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
5.异步并发控制函数
异步并发控制器/调度器, 限制执行函数的同时最大执行数
- 该函数返回一个执行函数, 该执行函数接收一个异步任务函数.
- 执行函数被调用时, 会根据maxnum来执行task, 如果正在执行的task数不超过maxnum, 则立即执行, 否则会等到任意一个正在执行的task结束后执行, 并返回值为task返回值的Promise
function asyncTaskControler(capacity) {
const waitting = [];
let runningNum = 0;
return async (func) => {
if (runningNum >= capacity) {
await new Promise(r => waitting.push(r));
}
runningNum++;
try {
return await func();
} finally {
runningNum--;
const next = waitting.shift();
next && next();
}
}
}
另一种实现
const scheduler = (max) => {
let num = 0;
const list = [];
return (task) => {
return new Promise((resolve, reject) => {
const exec = async () => {
num++;
task().then((res) => {
resolve(res);
}).catch((err) => {
reject(err);
}).finally(() => {
num--;
const next = list.shift();
next && next();
});
}
if (num < max) {
exec();
} else {
list.push(exec);
}
})
}
}
6.数组flat
数组flat方法是ES6新增的一个特性,可以将多维数组展平为低维数组。如果不传参默认展平一层,传参可以规定展平的层级。
function flat(array) {
const res = [];
for (const item of array) {
if (Array.isArray(item)) {
res.push(...flat(item));
} else {
res.push(item);
}
}
return res;
}