❌ API 工程师
想想你会吗?
实现 call
Function.prototype.myCall = function(context = window, ...args) {
if (typeof this !== 'function') {
throw new TypeError(`expect function, but got a ${typeof this}`);
}
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
// 测试一下
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(...args) {
Person.myCall(this, ...args);
this.eat = 'eating';
}
const kk = new Student('kk', 16);
console.log(kk);
另一种写法
Function.prototype.myCall = function(context) {
context = context || window;
context.fn = this;
let arr = [],
args = arguments;
for (var i = 1; i < args.length; i++) {
if (typeof args[i] === 'string') {
args[i] = '"' + args[i] + '"'; // 将字符串添加"" 塞进数组里变成 ["1"]
}
arr.push(args[i]);
}
let _args = arr.toString();
let result = eval('context.fn(' + _args + ')');
delete context.fn;
return result;
};
实现 apply
注意,apply 传进来的是是数组,在进行扩展运算符的时候要小心!
Function.prototype.myApply = function(context = window) {
if (typeof this !== 'function') {
throw new TypeError(`expect function, but got a ${typeof this}`);
}
const fn = Symbol('fn');
context[fn] = this;
let result;
if (arguments[1]) {
result = context[fn](...arguments[1]);
} else {
result = context[fn]();
}
delete context[fn];
return result;
};
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(...args) {
Person.myApply(this, args);
this.eat = 'eat';
}
const kk = new Student('kk', 16);
console.log(kk);
实现 bind
Function.prototype.myBind = function() {
if (typeof this !== 'function') {
throw new TypeError(`expect function, but got ${typeof this}`);
}
const slice = Array.prototype.slice,
thatFunc = this,
thatArgs = arguments[0],
args = slice.call(arguments, 1);
return function() {
const _args = args.concat(slice.call(arguments));
return thatFunc.apply(thatArgs, _args);
};
};
const a = {
x: 1,
getX: function() {
return this.x;
},
};
const b = a.getX;
const c = {
x: 2,
};
const d = b.myBind(c, 3);
d();
实现 map
Array.prototype.map = function(callback) {
if (typeof callback !== 'function') {
throw new TypeError(`expect got function, but got ${typeof callback}`);
}
if (!Array.isArray(this)) {
throw new TypeError(`expect got array, but got ${typeof this}`);
}
const arr = this;
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i], i, arr));
}
return result;
};
[1, 2, 3].map(i => i + 1);
实现 filter
Array.prototype.filter = function(callback) {
if (typeof callback !== 'function') {
throw new TypeError(`expect got function, but got ${typeof callback}`);
}
if (!Array.isArray(this)) {
throw new TypeError(`expect got array, but got ${typeof callback}`);
}
const arr = this;
let result = [];
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i, arr) ? result.push(arr[i]) : '';
}
return result;
}
[0, 1, 2, 3].filter(i => i > 0);
实现 reduce
Array.prototype.reduce = function(callback, initValue) {
if (typeof callback !== 'function') {
throw new TypeError(`expect got function, but got ${typeof callback}`);
}
if (!Array.isArray(this)) {
throw new TypeError(`expect got array, but got ${typeof callback}`);
}
const arr = this;
let index = 0,
accumulator = initValue;
if (!accumulator) {
index = 1;
accumulator = arr[0];
}
for (; index < arr.length; index++) {
let sum = callback(accumulator, arr[index], index, arr);
accumulator = sum;
}
return accumulator;
}
[1, 2, 3].reduce((x, y) => x + y, 10);
实现 new
new 实现原理: new 是用来实例化一个类,从而在内存中分配一个实例对象。
会做如下操作:
创建一个空的 Javascript 对象,即{};
将函数的
prototype赋值给对象的__proto__属性;调用函数,并将步骤 1 新建的对象作为函数的
this上下文;
function myNew(func) {
if (typeof func !== 'function') {
throw new TypeError(`expect function, but got ${typeof func}`);
}
return function() {
const obj = {
__proto__: func.prototype,
};
func.call(obj, ...arguments);
return obj;
};
}
myNew(Person)('kk', 16);
另一种写法
function myNew2(func, ...args) {
if (typeof func !== 'function') {
throw new TypeError(`expect function, but got ${typeof func}`);
}
let obj = {};
if (func.prototype !== null) {
obj.__proto__ = func.prototype;
}
// 也可以写成
// const obj = Object.create(func.prototype); // 以func为原型创造对象
let res = func.apply(obj, args);
if (res !== null && (typeof res === 'function' || typeof res === 'object')) {
return res;
}
return obj;
}
myNew2(Person, 'kk', 16);
实现 instanceof
通过new关键字实现原理,我们可以知道,当构造函数在执行的时候,会讲构造函数的prototype 赋值给实例对象的__proto__属性,因此可以通过判断实例对象或者其原型链中的__proto__ 是否等于构造函数的prototype。
function instanceOf(left, right) {
let leftVal = left.__proto__;
const rightVal = right.prototype;
while (leftVal) {
if (leftVal === rightVal) {
return true;
}
leftVal = leftVal.__proto__;
}
return false;
}
const kk = new Person('kk', 16);
const bb = new Student('bb', 3);
instanceOf(kk, Person); // true
instanceOf(kk, Student); // false
instanceOf(bb, Student); // true
实现 Object.create 的基本原理
function create(obj) {
function F() {}
F.prototype = obj;
let func = new F();
if (obj === null) {
func.__proto__ = null;
}
return func;
}
create(null);
create({});
create({ a: 2 });
实现节流 throttle
节流,单位时间内只会触发一次,稀释函数的执行频率。高频调用,就需要用到节流。
一般使用场景:鼠标不断移动、监听滚动事件。
function throttle(fn, delay) {
let prev = Date.now();
return function() {
const context = this;
const now = Date.now();
if (now - prev >= delay) {
fn.apply(context, arguments);
prev = Date.now();
}
};
}
function print() {
console.log('节流。鼠标移动,1秒内只会触发一次。减少请求的频率');
}
document.onmousemove = throttle(print, 1000);
实现防抖 debounce
防抖,只有当停止操作时才会触发。
一般使用场景:用于模糊查询输入框 "减少请求次数" 这一类的场景
function debounce(fn, delay) {
let timer = null;
return function() {
const context = this;
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, arguments);
}, delay);
};
}
function print() {
console.log('防抖,鼠标停止操作后,等待1s后,触发函数。减少执行次数');
}
document.onmousemove = debounce(print, 1000);
🤔 throttle & debounce
throttle:一直触发,触发每隔一段时间,还是可以被调用
debounce:一直触发,就一直不能调用。只有等到一段时间,才能被调用
实现 setTimeout 模拟 setInterval
setTimeout(function() {
// do something
setTimeout(arguments.callee, 1000);
}, 500);
温馨提示
argument.callee 指的是该函数的函数体内当前正在执行的函数。
argument.callee,已经在 ES5 严格模式中删除了。
另一种写法 👇
function selfSetTimeout(callback, delay) {
let timer = null;
function fn() {
callback && callback();
timer && clearTimeout(timer);
timer = setTimeout(fn, delay);
}
return setTimeout(fn, delay);
}
// 测试一下
const timer = selfSetTimeout(() => {
console.log(1);
}, 100);
实现深拷贝
// 乞丐版
JSON.parse(JSON.stringify());
// 基础版
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
}
// 基础版 -> 对象引用, 递归调用
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
return target;
}
// 基础版 -> 考虑数组
function clone(target) {
if (target !== null && typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
return target;
}
// 循环引用,如何解决循环引用造成栈溢出的问题?
// 我们可以额外开辟一个存储空间,
// 来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,
// 先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,
// 如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题
function clone(target, map = new Map()) {
if (target !== null && typeof target === 'object') {
if (map.has(target)) {
return map.get(target);
}
let cloneTarget = Array.isArray(target) ? [] : {};
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
}
return target;
}
let target = {
a: 1,
b: 2,
c: [3, 4, 5],
d: {
d1: 6,
},
};
target.target = target;
clone(target);
// 2. 性能优化 -> 变成弱引用 new WeakMap()
// 2. 性能优化 -> 改掉for in, 使用 while
function forEach(array, fn) {
let index = -1;
const length = array.length;
while (++index < length) {
fn(array[index], index);
}
console.log(array);
return array;
}
function clone(target, map = new WeakMap()) {
if (typeof target === 'object') {
if (map.get(target)) {
return map.get(target);
}
let isArray = Array.isArray(target);
let cloneTarget = isArray ? [] : {};
map.set(target, cloneTarget);
let keys = isArray ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
return target;
}
实现 eventEmitter,拥有四个方法 on, off, once 和 trigger
class EventEmitter {
constructor() {
this.listeners = Object.create(null);
}
on(eventName, callback) {
if (typeof callback !== 'function') {
throw TypeError(`expect for a function, but got a ${typeof callback}`);
}
let eventArray = this.listeners[eventName];
if (!eventArray) {
eventArray = this.listeners[eventName] = [];
}
this.listeners[eventName].push(callback);
return this;
}
once(eventName, callback) {
if (typeof callback !== 'function') {
throw TypeError(`expect for a function, but got a ${typeof callback}`);
}
let eventArray = this.listeners[eventName];
if (!eventArray) {
eventArray = this.listeners[eventName] = [];
}
let isOnce = false;
var fn = function() {
if (isOnce) return;
callback();
isOnce = true;
};
this.listeners[eventName].push(fn);
return this;
}
off(eventName, callback) {
if (!eventName) return (this.listeners = {});
let eventArr = this.listeners[eventName];
if (!eventArr) return;
if (!callback) {
this.listeners[eventName] && delete this.listeners[eventName];
return;
}
const idx = eventArr.indexOf(callback);
if (idx !== -1) this.listeners[eventName].splice(idx, 1);
}
trigger(eventName, ...args) {
let eventArray = this.listeners[eventName];
if (!eventArray) {
throw TypeError(`not found function, trigger name is ${eventName}`);
}
if (!eventArray) return null;
eventArray.forEach(cb => {
typeof cb === 'function' && cb(...args);
});
}
}
测试一下 👇
const test = new EventEmitter();
test.once('click', () => {
console.log(1);
});
test.once('click', () => {
console.log(2);
});
test.once('click', () => {
console.log(3);
});
test.on('click', () => {
console.log(4);
});
test.on('click', () => {
console.log(5);
});
test.on('click', () => {
console.log(6);
});
function f7() {
console.log(7);
}
test.on('click', f7);
test.on('login', name => {
console.log(`${name} 已登录`);
});
test.trigger('click');
test.trigger('login', 'kk');
// 所有事件都移除
test.off();
// 移除 login
test.off('login');
// 移除 click中 f7
test.off('click', f7);
console.log(test.listeners);
也可以用 Map 实现,其实换汤不换药!读取的方式有差别以外,其他都一样。自己实现看看吧
class Observer {
static events = new Map();
static on(name, fn) {
this.events.set(name, { isOnce: false, fn });
}
static once(name, fn) {
this.events.set(name, { isOnce: true, fn });
}
static off(name) {
this.events.delete(name);
}
static emit(name, data) {
const cache = this.events.get(name);
if (!cache) throw Error('没有找到此方法');
if (cache.isOnce) this.events.delete(name);
cache.fn(data);
}
}