今天我要分享的是10个常见的前端手写功能,可以让你在数据处理上得心应手,让你的开发工作事半功倍。
开始吧!(下篇关注后续更新。。。)
-
首先创建一个新的空对象
-
设置原型,将对象的原型设置为函数的prototype对象
-
让函数的this指向这个对象,执行构造函数的代码
-
判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
function myNew(constructor, ...args) {
if (typeof constructor !== "function") {
throw "myNew function the first param must be a function";
}
let newObj = Object.create(constructor.prototype);
let res = constructor.apply(newObj, args);
let isObject = typeof res === "object" && res !== null;
let isFunction = typeof res === "function";
return isObject || isFunction ? res : newObj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(this.age);
};
let p1 = myNew(Person, "poety", 18);
console.log(p1.name);
console.log(p1);
p1.say();
测试结果:
实现call
思路:接受传入的context上下文,如果不传默认为window,将被调用的方法设置为上下文的属性,使用上下文对象来调用这个方法,删除新增属性,返回结果。
Function.prototype.myCall = function (context) {
if (typeof this !== "function") {
throw new Error("Type error");
}
context = context || window;
let args = [...arguments].slice(1);
let result = null;
context.fn = this;
result = context.fn(...args);
delete context.fn;
return result;
};
function f(a, b) {
console.log(a + b);
console.log(this.name);
}
let obj = {
name: 1,
};
f.myCall(obj, 1, 2);
测试结果:
实现apply
思路:除了传参方式是数组,其它与call没区别
Function.prototype.myApply = function (context) {
if (typeof this !== "function") {
throw new Error("Type error");
}
let result = null;
context = context || window;
const fnSymbol = Symbol();
context[fnSymbol] = this;
if (arguments[1]) {
result = context[fnSymbol](...arguments[1]);
} else {
result = context[fnSymbol]();
}
delete context[fnSymbol];
return result;
};
function f(a, b) {
console.log(a, b);
console.log(this.name);
}
let obj = {
name: "张三",
};
f.myApply(obj, [1, 2]);
测试结果:
实现bind
思路:bind返回的是一个函数,需要判断函数作为构造函数的情况,当作为构造函数时,this指向实例,不会被任何方式改变this,所以要忽略传入的context上下文。
bind可以分开传递参数,所以需要将参数拼接。如果绑定的是构造函数,还需要继承构造函数原型上的属性和方法,保证不丢失。
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new Error("Type error");
}
const args = [...arguments].slice(1);
const fn = this;
const Fn = function () {
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
Fn.prototype = Object.create(fn.prototype);
return Fn;
};
function Person(name, age) {
console.log(name);
console.log(age);
console.log(this);
}
Person.prototype.say = function () {
console.log("say");
};
var obj = {
name: "cc",
age: 18,
};
var bindFun = Person.myBind(obj, "cxx");
var a = new bindFun(10);
a.say();
function normalFun(name, age) {
console.log(name);
console.log(age);
console.log(this);
}
var obj = {
name: "aa",
age: 18,
};
var bindNormalFun = normalFun.myBind(obj, "cc");
bindNormalFun(12);
测试结果:
防抖
防抖是指在事件被触发n秒后在执行回调,如果在这n秒内时间又被触发,则重新计时。
可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
function debounce(fn, delay = 500) {
let timer = null;
return function(...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
节流
节流就是一定时间内执行一次事件,即使重复触发,也只有一次生效。
可以使用在监听滚动scroll事件上,通过事件节流来降低事件调用的频率。
1. 定时器版本
function throttle(fn, delay = 500) {
let timer = null;
return function(...args) {
// 当前有任务了,直接返回
if(timer) {
return;
}
timer = setTimeout(() => {
fn.apply(this, args);
//执行完后,需重置定时器,不然timer一直有值,无法开启下一个定时器
timer = null;
}, delay)
}
}
2. 时间戳版本
function throttle(fn, delay = 500) {
let prev = Date.now();
return function(...args) {
let now = Date.now();
if (now - prev >= delay) {
fn.apply(this, args);
prev = Date.now();
}
};
}
instanceof用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
myInstanceof(instance, constructor) {
//如果不是对象,或者是null,直接返回false
if (typeof instance !== "object" || instance === null) {
return false;
}
// 获取对象的原型
let proto = Object.getPrototypeOf(instance);
// 获取构造函数的 prototype 对象
let prototype = constructor.prototype;
// 判断构造函数的 prototype对象是否在对象的原型链上
while (true) {
// 到达原型链终点null,说明没找到
if (!proto) {
return false;
}
if (proto === prototype) {
return true;
}
// 如果没有找到,就继续从其原型上找
proto = Object.getPrototypeOf(proto);
}
}
//测试
let Fn = function () { }
let p1 = new Fn()
console.log(myInstanceof(p1, Fn));//true
console.log(myInstanceof([], Fn));//false
console.log(myInstanceof([], Array)) // true
console.log(myInstanceof(function(){}, Function)) // true
测试结果:
-
创建一个XMLHttpRequest对象
-
在这个对象上使用open方法创建一个HTTP请求(参数为请求方法、请求地址、是否异步和用户的认证信息)
-
通过send方法来向服务器发起请求(post请求可以入参作为发送的数据体)
-
监听请求成功后的状态变化:根据状态码进行相应的出来。onreadystatechange设置监听函数,当对象的readyState变为4的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是200则为成功,404或500为失败。
function ajax(url) {
const xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if(xhr.status == 200 || xhr.status == 304) {
handle(this.response);
} else {
console.error(this.statusText);
}
}
};
}
使用Promise封装Ajax:
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.send()
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
resolve(this.response)
} else {
reject(new Error(this.statusText));
}
}
}
})
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))
浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
function shallowCopy (obj){
if(typeof obj !== 'object' || obj === null) {
return obj;
}
let newObj = Array.isArray(obj) ? []: {};
for(let key in obj ){
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];
}
}
return newObj;
}
var obj ={
name:'张三',
age:8,
pal:['王五','王六','王七']
}
let obj2 = shallowCopy(obj);
obj2.name = '李四'
obj2.pal[0] = '王麻子'
console.log(obj);
console.log(obj2);
测试结果:
深拷贝
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
function deepCopy (obj, map = new WeakMap()){
if(typeof obj !== 'object' || obj === null) {
return obj;
}
let newObj = Array.isArray(obj) ? []: {};
if (map.has(obj)) {
return map.get(obj);
}
map.set(obj, newObj);
for(let key in obj ){
if(obj.hasOwnProperty(key)){
newObj[key] = deepCopy(obj[key], map);
}
}
return newObj
}
let obj1 = {
name : 'AK、哒哒哒',
arr : [1,[2,3],4],
};
let obj2=deepCopy(obj1)
obj2.name = "哒哒哒";
obj2.arr[1] = [5,6,7] ;
console.log('obj1',obj1)
console.log('obj2',obj2)
测试结果:
函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
作用:可以参数复用(公共的参数已经通过柯里化预置了)和延迟执行(柯里化时只是返回一个预置参数的新函数,并没有立刻执行,在满足条件后才会执行)。
参数定长的柯里化
思路:通过函数的length属性获取函数的形参个数,形参的个数就是所需参数的个数。维护一个数组,当数组的长度与函数接收参数的个数一致,再执行该函数。
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, [...args, ...args2]);
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
var curried = curry(sum);
console.log(curried(1, 2, 3));
console.log(curried(1, 2)(3));
console.log(curried(1)(2, 3));
console.log(curried(1)(2)(3));
测试结果:
参数不定长的柯里化
题目:如何实现一个方法,使计算结果能够满足如下预期。
add(1, 2, 3)
add(1)
add(1)(2)
add(1, 2)(3)
add(1)(2)(3)
add(1)(2)(3)(4)
思路:利用闭包和递归,如果参数为空,则判断递归结束,求和,返回结果。
function addCurry() {
let arr = [...arguments]
return function fn() {
if(arguments.length === 0) {
return arr.reduce((a, b) => a + b)
} else {
arr.push(...arguments)
return fn
}
}
}
console.log(addCurry(1)());
console.log(addCurry(1)(2)());
console.log(addCurry(1)(2)(3)());
console.log(addCurry(1, 2)(3)());
console.log(addCurry(1, 2, 3)());
上述写法,总是要以空括号()结尾,于是再改进为隐式转换.toString
写法,原理:当用 Function的值做计算的时候,会调用toString做隐式转换。注意一些旧版本的浏览器隐式转换会默认执行,新版本不行了。可以利用隐式转换或者alert。
function addCurry() {
let arr = [...arguments]
var fn = function() {
arr.push(...arguments);
return fn;
};
fn.toString = function () {
return arr.reduce(function (a, b) {
return a + b;
});
}
return fn;
}
console.log(addCurry(1)(2) == 3)
console.log(addCurry(1).toString());
console.log(addCurry(1, 2)(3).toString());
console.log(addCurry(1, 2)(3)(4)(5).toString());
测试结果:
数组扁平化其实就是将多维数组转为一维数组。
1. ES6中的flat
const arr = [1,[2,[3,[4,5]]],6]
console.log(arr.flat(Infinity))
2. 递归
let arr = [1, [2, [3, 4]]]
function flatten(arr) {
let result = []
for (let i = 0
//如果当前元素还是一个数组
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result
}
console.log(flatten(arr))
3. reduce函数迭代
从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce来实现数组的拼接,从而简化第一种方法的代码。
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce((total, cur) => {
return total.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
console.log(flatten(arr));
4. split和toString
数组的toString
方法可以把数组直接转换成逗号分隔的字符串。如[1, [2, [3, 4]]] => "1,2,3,4"
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(",").map(Number);
}
console.log(flatten(arr));
-
利用Set。new一个Set,参数为需要去重的数组,Set会自动删除重复的元素,在Array.form将Set转为数组返回
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log([...new Set(arr)]);
console.log(Array.from(new Set(arr)));
-
利用数组的filter()+indexOf去重。利用filter方法,返回arr.indexOf(num)等于index的值。原理就是indexOf会返回先找到的数字的索引。
function unique(arr) {
return arr.filter((item, index, array) => {
return array.indexOf(item) === index;
});
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
console.log(unique(arr));
-
利用Map。新建一个数组和map,如果当前值在map中没有出现过,就加入数组,最后返回数组
const unique = (arr) => {
const map = new Map()
const res = []
for (let item of arr) {
if (!map.has(item)) {
map.set(item, true)
res.push(item)
}
}
return res
}
const arr = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]
console.log(unique(arr))
思路:如果是null,直接返回String(null);基本类型和函数,直接使用typeof;
其它引用类型,使用Object.prototype.toString.call。
function getType(value) {
let type;
if (value === null) {
return String(value);
}
if (typeof value !== "object") {
return typeof value;
} else {
let valueClass = Object.prototype.toString.call(value);
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
}
}
console.info(getType(null));
console.info(getType(undefined));
console.info(getType(100));
console.info(getType("abc"));
console.info(getType(true));
console.info(getType(Symbol()));
console.info(getType({}));
console.info(getType([]));
console.info(getType(() => {}));
console.info(getType(new Date()));
console.info(getType(new RegExp("")));
console.info(getType(new Map()));
console.info(getType(new Set()));
console.info(getType(new WeakMap()));
console.info(getType(new WeakSet()));
console.info(getType(new Error()));
console.info(getType(new Promise(() => {})));
测试结果:
本篇文章到此就结束了,欢迎在评论区交流。
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论, 支持一下博主~
尽快更新下集~
作者-AK-哒哒哒,欢迎关注!
so
-
结尾依旧:长风破浪会有时,直挂云帆济沧海!
-
在线整理的确实很好,对文章进行了一个汇总整理,在线备战面试、刷题指南,拿走不谢,要学会站在别人的肩膀上提升自己点击这里-->
最后:
如果你现在正在找工作,可以私信“web”或者直接添加下方小助理进群领取前端面试小册、简历优化修改、大厂内推以及更多阿里、字节大厂面试真题合集,和p8大佬一起交流。