持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 13 天,点击查看活动详情
1、call、aplly、bind 实现
call、aplly、bind 本质都是改变 this 的指向,不同点 call、aplly 是直接调用函数,bind 是返回一个新的函数,call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()来进行调用才可以。call 跟 aplly 就只有参数上不同。
bind 实现
1、箭头函数的 this 永远指向它所在的作用域
2、函数作为构造函数用 new 关键字调用时,不应该改变其 this 指向,因为 new绑定 的优先级高于 显示绑定 和 硬绑定
Function.prototype.mybind = function(thisArg) {
if (typeof this !== 'function') {
throw new Error("Bind must be called on a function");
}
// 拿到参数,为了传给调用者, 强制转化arguments为数组格式
const args = Array.prototype.slice.call(arguments, 1),
// 保存 this
self = this,
// 构建一个干净的函数,用于保存原函数的原型
nop = function() {},
// 绑定的函数
bound = function() {
// this instanceof nop, 判断是否使用 new 来调用 bound
// 如果是 new 来调用的话,this的指向就是其实例,
// 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
return self.apply(
this instanceof nop ? this : thisArg,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
if (this.prototype) {
nop.prototype = this.prototype;
}
// 修改绑定函数的原型指向
bound.prototype = new nop();
return bound;
}
}
Function.prototype.bind = function(context, ...args) {
if (typeof this !== 'function') {
throw new Error("Type Error");
}
// 保存this的值
var self = this;
return function F() {
// 考虑new的情况
if(this instanceof F) {
return new self(...args, ...arguments)
}
return self.apply(context, [...args, ...arguments])
}
}
Function.prototype.myBind = function(context) {
// 保存当前函数引用
var self = this;
// 获取myBind第二个及以后的参数
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// 获取函数调用时(myBind返回的函数被调用时),myBind返回的函数中的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 执行原函数,并改变this指向到context
return self.apply(context, args.concat(bindArgs));
}
}
call 实现
bind 是封装了 call 的方法改变了 this 的指向并返回一个新的函数,那么 call 是如何做到改变 this 的指向呢?原理很简单,在方法调用模式下,this 总是指向调用它所在方法的对象,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数特殊)
Function.prototype.mycall = function(thisArg) {
// this指向调用call的对象
if (typeof this !== 'function') {
// 调用call的若不是函数则报错
throw new Error('bi xu shi hanshu')
}
const args = [...arguments].slice(1);
thisArg = thisArg || window;
// 将调用call函数的对象添加到thisArg的属性中
thisArg.fn = this;
// 执行该属性
const result = thisArg.fn(...arg);
// 删除该属性
delete thisArg.fn;
// 返回函数执行结果
return result;
}
Function.prototype.call = function(context = window, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
aplly 实现
Function.prototype.myapply = function(thisArg) {
if (typeof this !== 'function') {
throw new Error('bi xu shi hanshu')
}
const args = arguments[1]
thisArg.fn = this;
const result = thisArg.fn(...args);
delete thisArg.fn;
return result;
}
Function.prototype.apply = function(context = window, args) {
if (typeof this !== 'function') {
throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
2、reduce 实现原理
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
Array.prototype.myReduce = function(callback, initValue=0){
if(typeof callback !== 'function'){
throw new Error('bi xu shi hanshu')
}
let result = initValue
for (let i = 0; i < this.length; i++) {
result = callback(result, this[i], i, this)
}
return result
}
Array.prototype.reduce = function(callback, initialValue) {
if (this == undefined) {
throw new TypeError('this is null or not defined');
}
if (typeof callback !== 'function') {
throw new TypeError(callbackfn + ' is not a function');
}
const O = Object(this);
const len = this.length >>> 0;
let accumulator = initialValue;
let k = 0;
// 如果第二个参数为undefined的情况下
// 则数组的第一个有效值作为累加器的初始值
if (accumulator === undefined) {
while (k < len && !(k in O)) {
k++;
}
// 如果超出数组界限还没有找到累加器的初始值,则TypeError
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = O[k++];
}
while (k < len) {
if (k in O) {
accumulator = callback.call(undefined, accumulator, O[k], k, O);
}
k++;
}
return accumulator;
}
3、new 实现
1、创建一个新对象;
2、将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象。
function myNew() {
// 创建一个实例对象
var obj = new Object();
// 取得外部传入的构造器
var Constructor = Array.prototype.shift.call(arguments);
// 实现继承,实例可以访问构造器的属性
obj.__proto__ = Constructor.prototype;
// 调用构造器,并改变其 this 指向到实例
var ret = Constructor.apply(obj, arguments);
// 如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象
returntypeof ret === 'object' ? ret : obj;
}
4、class 实现继承
使用寄生组合继承的方式
1、原型链继承,使子类可以调用父类原型上的方法和属性
2、借用构造函数继承,可以实现向父类传参
3、寄生继承,创造干净的没有构造方法的函数,用来寄生父类的 prototype
// 实现继承,通过继承父类 prototype
function __extends(child, parent) {
// 修改对象原型
Object.setPrototypeOf(child, parent);
// 寄生继承,创建一个干净的构造函数,用于继承父类的 prototype
// 这样做的好处是,修改子类的 prototype 不会影响父类的 prototype
function __() {
// 修正 constructor 指向子类
this.constructor = child;
}
// 原型继承,继承父类原型属性,但是无法向父类构造函数传参
child.prototype =
parent === null
? Object.create(parent)
: ((__.prototype = parent.prototype), new __());
}
var B = (function() {
function B(opt) {
this.name = opt.name;
}
return B;
})();
var A = (function(_super) {
__extends(A, _super);
function A() {
// 借用继承,可以实现向父类传参, 使用 super 可以向父类传参
return (_super !== null && _super.apply(this, { name: 'B' })) || this;
}
return A;
})(B);
5、async/await 实现
原理就是利用 generator(生成器)分割代码片段。然后我们使用一个函数让其自迭代,每一个yield 用 promise 包裹起来。执行下一步的时机由 promise 来控制
function asyncToGenerator(fn){
return function(){
const self = this
let args = arguments
return new Promise(function(resolve,reject){
let gen = fn.apply(self, args)
function _next(value){
asyncGeneratorStep(gen, resolve, reject, _next, _throw, '_next', value)
}
function _throw(err){
asyncGeneratorStep(gen, resolve, reject, _next, _throw, '_throw', err)
}
_next(undefined)
})
}
}
function asyncGeneratorSteo(gen, reaolve, reject, _next, _throw, key, arg){
try{
var info = gen[key](arg)
var value = info.value
}catch(err){
reject(err)
return
}
if(info.done){
resolve(value)
}else{
Promise.resolve(value).then(_next, _throw)
}
}
6、实现一个双向绑定
defineProperty 版本
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty(myObj, keyName, {
// value: "小明", // 属性的值
// writable: false, // 能否重写
// configurable: false, // 能否设置其他
// enumerable: false, // 是否可以在 for...in 循环和 Object.keys() 中被枚举
/* ---------------分隔符---------------- */
//当重新设置myObj.username时,触发set函数
set:function(setValue){
console.log("有人在改变我的username值,改变的值会传递给我的参数(setValue)="+setValue);
},
//当获取myObj.username时,触发get函数
get: function(){
console.log("有人在获取我的username值,他想获取的值由我说了算,我返回(return)什么,myObj.username就等于什么")
return "我名由我"//如果不返回值,获取的值=undefined
},
})
// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变化 --> 修改视图
set(newVal) {
input.value = newVal;
span.innerHTML = newVal;
}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
data.text = e.target.value;
});
proxy 版本
// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {
set(target, key, value) {
target[key] = value;
// 数据变化 --> 修改视图
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data);
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
proxy.text = e.target.value;
});
7、Object.create 的基本实现原理
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
8、instanceof 实现
原理:L 的 proto 是不是等于 R.prototype,不等于再找 L.proto.proto 直到 proto 为 null
// L 表示左表达式,R 表示右表达式
function instance_of(L, R) {
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null) return false;
// 这里重点:当 O 严格等于 L 时,返回 true
if (O === L) return true;
L = L.__proto__;
}
}
9、Array.isArray 实现
Array.myIsArray = function(o) {
return Object.prototype.toString.call(Object(o)) === '[object Array]';
};
console.log(Array.myIsArray([])); // true
10、getOwnPropertyNames 实现
Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
if (typeof Object.getOwnPropertyNames !== 'function') {
Object.getOwnPropertyNames = function(o) {
if (o !== Object(o)) {
throw new Error('Object.getOwnPropertyNames called on non-object');
}
var props = [], p;
for (p in o) {
if (Object.prototype.hasOwnProperty.call(o, p)) {
props.push(p);
}
}
return props;
};
}
11、Promise 实现
实现原理:其实就是一个发布订阅者模式
1、构造函数接收一个 executor 函数,并会在 new Promise() 时立即执行该函数
2、then 时收集依赖,将回调函数收集到 成功/失败队列
3、executor 函数中调用 resolve/reject 函数
4、resolve/reject 函数被调用时会通知触发队列中的回调
const isFunction = variable =>typeof variable === 'function';
// 定义Promise的三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
// 构造函数,new 时触发
constructor(handle: Function) {
try {
handle(this._resolve, this._reject);
} catch (err) {
this._reject(err);
}
}
// 状态 pending fulfilled rejected
private _status: string = PENDING;
// 储存 value,用于 then 返回
private _value: string | undefined = undefined;
// 失败队列,在 then 时注入,resolve 时触发
private _rejectedQueues: any = [];
// 成功队列,在 then 时注入,resolve 时触发
private _fulfilledQueues: any = [];
// resovle 时执行的函数
private _resolve = val => {
const run = () => {
if (this._status !== PENDING) return;
this._status = FULFILLED;
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = value => {
let cb;
while ((cb = this._fulfilledQueues.shift())) {
cb(value);
}
};
// 依次执行失败队列中的函数,并清空队列
const runRejected = error => {
let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(error);
}
};
/*
* 如果resolve的参数为Promise对象,
* 则必须等待该Promise对象状态改变后当前Promsie的状态才会改变
* 且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(
value => {
this._value = value;
runFulfilled(value);
},
err => {
this._value = err;
runRejected(err);
}
);
} else {
this._value = val;
runFulfilled(val);
}
};
// 异步调用
setTimeout(run);
};
// reject 时执行的函数
private _reject = err => {
if (this._status !== PENDING) return;
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this._status = REJECTED;
this._value = err;
let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(err);
}
};
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run);
};
// then 方法
then(onFulfilled?, onRejected?) {
const { _value, _status } = this;
// 返回一个新的Promise对象
returnnew MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功时执行的函数
const fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value);
} else {
const res = onFulfilled(value);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext);
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err);
}
};
// 封装一个失败时执行的函数
const rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error);
} else {
const res = onRejected(error);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext);
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err);
}
};
switch (_status) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case PENDING:
this._fulfilledQueues.push(fulfilled);
this._rejectedQueues.push(rejected);
break;
// 当状态已经改变时,立即执行对应的回调函数
case FULFILLED:
fulfilled(_value);
break;
case REJECTED:
rejected(_value);
break;
}
});
}
// catch 方法
catch(onRejected) {
returnthis.then(undefined, onRejected);
}
// finally 方法
finally(cb) {
returnthis.then(
value => MyPromise.resolve(cb()).then(() => value),
reason =>
MyPromise.resolve(cb()).then(() => {
throw reason;
})
);
}
// 静态 resolve 方法
static resolve(value) {
// 如果参数是MyPromise实例,直接返回这个实例
if (value instanceof MyPromise) return value;
returnnew MyPromise(resolve => resolve(value));
}
// 静态 reject 方法
static reject(value) {
returnnew MyPromise((resolve, reject) => reject(value));
}
// 静态 all 方法
static all(list) {
returnnew MyPromise((resolve, reject) => {
// 返回值的集合
let values = [];
let count = 0;
for (let [i, p] of list.entries()) {
// 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
this.resolve(p).then(
res => {
values[i] = res;
count++;
// 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
if (count === list.length) resolve(values);
},
err => {
// 有一个被rejected时返回的MyPromise状态就变成rejected
reject(err);
}
);
}
});
}
// 添加静态race方法
static race(list) {
returnnew MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
this.resolve(p).then(
res => {
resolve(res);
},
err => {
reject(err);
}
);
}
});
}
}
12、防抖/截流
防抖函数 onscroll 结束时触发一次,延迟执行
防抖的场景:
1、登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
2、调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
3、文本编辑器实时保存,当无任何更改操作一秒后进行保存
function debounce(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// 使用
window.onscroll = debounce(function() {
console.log('debounce');
}, 1000);
节流函数 onscroll 时,每隔一段时间触发一次,像水滴一样
1、scroll 事件,每隔一秒计算一次位置信息等
2、浏览器播放事件,每个一秒计算一次进度信息等
3、input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)
function throttle(fn, delay) {
var prevTime = Date.now();
return function() {
var curTime = Date.now();
if (curTime - prevTime > delay) {
fn.apply(this, arguments);
prevTime = curTime;
}
};
}
// 使用
var throtteScroll = throttle(function() {
console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;
13、函数柯里化实现
它的本质就是将一个参数很多的函数分解成单一参数的多个函数。
实际应用中:
1、延迟计算 (用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,开始执行函数)
2、动态创建函数 (参数不够时会返回接受剩下参数的函数)
3、参数复用(每个参数可以多次复用)
const curry = fn =>
(judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);
currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10
14、手写一个深拷贝
浅拷贝只复制地址值,实际上还是指向同一堆内存中的数据,深拷贝则是重新创建了一个相同的数据,二者指向的堆内存的地址值是不同的。这个时候修改赋值前的变量数据不会影响赋值后的变量。
判断类型函数
function getType(obj) {
const str = Object.prototype.toString.call(obj);
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
if (obj instanceof Element) {
// 判断是否是dom元素,如div等
return 'element';
}
return map[str];
}
简单版深拷贝,列举三个例子 array object function,可以自行扩展。主要是引发大家的思考
function deepCopy(ori) {
const type = getType(ori);
let copy;
switch (type) {
case'array':
return copyArray(ori, type, copy);
case'object':
return copyObject(ori, type, copy);
case'function':
return copyFunction(ori, type, copy);
default:
return ori;
}
}
// entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。
function copyArray(ori, type, copy = []) {
for (const [index, value] of ori.entries()) {
copy[index] = deepCopy(value);
}
return copy;
}
// Object.entries() 可以把一个对象的键值以数组的形式遍历出来,结果和 for...in 一致,但不会遍历原型属性
function copyObject(ori, type, copy = {}) {
for (const [key, value] of Object.entries(ori)) {
copy[key] = deepCopy(value);
}
return copy;
}
// eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
function copyFunction(ori, type, copy = () => {}) {
const fun = eval(ori.toString());
fun.prototype = ori.prototype
return fun
}
15、数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组
方法一:使用flat()
const res1 = arr.flat(Infinity);
方法二:利用正则 字符串和数组互转(但数据类型都会变为字符串)
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
方法三:正则改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法四:使用reduce 递归
const flatten = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, [])
}
const res4 = flatten(arr);
方法五:函数递归
const res5 = [];
const fn = arr => {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
fn(arr[i]);
} else {
res5.push(arr[i]);
}
}
}
fn(arr);
16、数组去重
方法一:利用Set
const res1 = Array.from(new Set(arr));
方法二:两层for循环+splice
const unique1 = arr => {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
// 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
len--;
j--;
}
}
}
return arr;
}
方法三:利用indexOf
const unique2 = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
}
return res;
}
方法四:利用include
const unique3 = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) res.push(arr[i]);
}
return res;
}
方法五:利用filter
const unique4 = arr => {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
});
}
方法六:利用Map
const unique5 = arr => {
const map = new Map();
const res = [];
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
map.set(arr[i], true)
res.push(arr[i]);
}
}
return res;
}
17、类数组转化为数组
类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。
方法一:Array.from
Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
Array.from(document.querySelectorAll('div'))
方法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
方法三:扩展运算符
[...document.querySelectorAll('div')]
方法四:利用concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));
18、Array.prototype.filter()
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
Array.prototype.filter = function(callback, thisArg) {
if (this == undefined) {
throw new TypeError('this is null or not undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + 'is not a function');
}
const res = [];
// 让O成为回调函数的对象传递(强制转换对象)
const O = Object(this);
// >>>0 保证len为number,且为正整数
const len = O.length >>> 0;
for (let i = 0; i < len; i++) {
// 检查i是否在O的属性(会检查原型链)
if (i in O) {
// 回调函数调用传参
if (callback.call(thisArg, O[i], i, O)) {
res.push(O[i]);
}
}
}
return res;
}
19、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;
}
20、Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined');
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
while (k < len) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
Array.prototype.reduce()
未完待续...随时更新,关注不迷路!