一.实现new/call/apply/bind
1.new的实现
思路:创建一个空对象,让空对象能够访问到构造函数的原型对象,同时执行构造函数
function myNew(fn) {
let obj={};
obj._proto_=fn.prototype;
const args=[...arguments].slice(1);
fn.apply(obj,args)
return obj
}
测试代码
function Person(name,age) {
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
console.log(this.name)
}
let p1=myNew(Person,'zhangsan',22);
let p2=new Person('lisi',23);
console.log(p1)
p1.sayName()
console.log(p2)
p2.sayName()
2.call/apply的实现
思路:无论是call还是bind都是用来改变this指向的,思路是将函数作为obj的一个属性指向,然后再删除这个属性
Function.prototype.myCall=function (obj) {
const fn=this;
let args=[...arguments].slice(1);
obj.x=fn;
const result=obj.x(args);
delete obj.x;
return result
}
测试代码
let obj={
name:'zhangsan',
age:21
};
function sayName() {
console.log(this.name)
}
sayName();
sayName.call(obj)
sayName.myCall(obj)
apply是基于call实现的,不同的是获取的参数,这也是为什么call比apply快的原因,代码如下
Function.prototype.myApply=function(obj){
const fn=this;
let args=[...arguments].slice(1);
let result=fn.call(obj,...args);
return result;
}
3.bind的实现
思路:bind体现了函数式编程的思想,它会返回一个函数,所以可以不立即执行
Function.prototype.myBind=function (obj) {
const fn=this;
const context=obj;
return function () {
const args=[...arguments]
fn.apply(context,args)
}
}
二.防抖和节流
1.防抖
防抖指的是指定时间内只允许函数触发一次,思路是使用定时器,单位时间再触发会清除定时器并且重新触发。
代码
<script>
const handle=()=>{
console.log('有防抖触发函数')
}
function debounce(fn,delay) {
let timeout;
return function () {
console.log('无防抖情况')
const context=this;
let args=[...arguments]
if(timeout) clearTimeout(timeout);
timeout=setTimeout(()=>{
fn.apply(context,args)
},delay)
}
}
document.getElementById('input').addEventListener('mousemove',debounce(handle,1000))
</script>
2.节流
节流指的是只有到了指定时间才会触发函数,没有到时间就不会触发
<script>
const handle=()=>{
console.log('触发函数')
}
function throttle(fn,delay) {
let previous=Date.now();
let context=this;
return function () {
console.log('11111')
console.log(previous)
let args=[...arguments];
let now=Date.now();
if(now-previous>delay){
fn.apply(context,args)
previous=now
}
}
}
document.getElementById('input').addEventListener('mousemove',throttle(handle,1000))
</script>
三.深/浅拷贝和数组扁平化
1.深/浅拷贝
B浅拷贝A 指的是B和A共享一个堆内存空间,所以B的内容改变也会影响A的内容,因为他们指向同一个地址空间。一般情况下B=A是一种浅拷贝,当然也可以用Object.assign方法
B深拷贝A 则是将对象的内容完全复制。也就是说此时B和A虽然完全相同,但是他们的堆地址空间是完全不一样的。不会互相影响。实现方法B=JSON.parse(JSON.stringify(A),如果让你实现一个的话,你可以向一下这么写
function deepClone(obj) {
let newobj=Array.isArray(obj)?[]:{};
for(let key in obj){
newobj[key]=isObject(obj[key])?deepClone(obj[key]):obj[key]
}
return newobj
}
function isObject(obj) {
return typeof obj==='object'&&obj!==null
}
测试代码
let obj={
a:1,
b:{
c:[1,2,3],
d:2
},
e:3
}
console.log(deepClone(obj))
此外,数组的深拷贝则是可以用slice和concat方法
2.数组扁平化
什么是扁平化,就是让数组中层层嵌套数组的形式,变成只有一层的情况。例如[1,[2,[3,4]],5]变成[1,2,3,4,5]。实现的方式有很多,一般的手写是递归版的
function flatArr(arr) {
let newArr=[];
arr.forEach((item,i,arr)=>{
if(Array.isArray(item)){
newArr=newArr.concat(flatArr(item))
}else {
newArr.push(item)
}
})
return newArr
}
console.log(flatArr([1,2,[3,4,5],6,7]))
四.柯里化
1.函数式编程
以下是笔者的理解:我们在中学阶段都知道y=f(x),对于指定的表达式,输入x得到的一定是唯一的y,只存在一对一的关系。函数式编程就是这样,对于指定输入,总是得到唯一的输出。这里笔者只是用到柯里化的时候提一句,其实函数式编程有很多内容,有兴趣的可以进一步了解。或者等待我日后详细更新。
2.柯里化实现
思路:柯里化是将输入多个参数转换为一个参数,其实利用函数式编程中函数能返回函数以及闭包的特性,能够让我们的所有参数得到缓存
//柯里化要实现的效果
function add(a,b){
return a+b
}
//普通函数接受多个参数,获取计算的结果。例如这里就需要用add(1,2)的方式
//但是当对函数进行柯里化处理后,即let curryAdd=curry(add)后
//可以使用curryAdd(1)(2)()的方式得到结果3
下面是简单的实现方式
function curry(fn) {
let allArgs=[];
return function next() {
let args=[...arguments];
if(args.length>0){
allArgs=allArgs.concat(args)
return next;
}else {
return fn.apply(null,allArgs)
}
}
}
测试代码
const add=function () {
let sum=0;
for(let i=0;i<arguments.length;i++){
sum+=arguments[i]
}
return sum
}
let curryAdd=curry(add);
curryAdd(1)
curryAdd(2,3)
let result=curryAdd(4)();
console.log(result)