一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
防抖
什么是防抖?
(在某段时间(eg:1000ms)里再次点击同一事件,那么时间又从0开始计时(即使前一事件时间已经倒计时一半了,也会清除计时,从新开始),触发的事件永远是固定时间段(eg:1000)里面的最后一个点击事件. 现象: 设置的防抖时间为1000ms,1000ms里多次点击的话, 最后时间触发的时间有可能大于1000ms)
手撕防抖
// 防抖
const debounce = (fun, delay) => {
let time = null;
return (...args) => {
clearTimeout(time);
time = setTimeout(function() {
fun.apply(this, args);
}, delay)
}
}
节流
什么是节流?
(在某段时间(eg:1000ms)里再次多次点击同一事件,只执行此时间段内(eg:1000ms)以内的第一次点击,此时间段内后面的点击不触发. 此时间段后, 再次重复执行此逻辑. 现象: 设置的节流时间为1000ms,1000ms内多次点击, 1000ms里只执行一次事件. 区别:同一时间段内多次点击:防抖=>最后的执行事有可能大于此时间段时间,并且只执行时间段内最后一次点击.节流: 同一时间段内,只执行第一次点击, 其他点击不触发).
手撕节流
//节流
const throttle = (fun, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fun.apply(this, args);
flag = true;
}, delay)
}
}
call
做了什么?
将函数设为对象的属性,执⾏&删除这个函数,指定this到函数并传⼊给定参数执⾏函数,如果不传⼊参数,默认指向为 window。
手撕call
//实现一个call
Function.prototype.myCall = function (context) {
context.fn = this;
console.log('context.fn', context.fn);
let args = [];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
const result = context.fn(...args);
//这里删除开始window添加的对象方法,使用完删除
delete context.fn;
return result;
}
//eg:
// let m = (a, b) => {
let m = function(a, b) {
console.log('a,b', a, b);
console.log(this.name);
}
let n = {
name: 'baby',
age: '15',
}
m.myCall(n, 47, '拉拉');
上面截图是之前理解的,感觉有错误,下面是重新理解;
传的第一个参数就相当于this,第一个传的除过this之外的值,传的是谁,谁就是那个要被添加函数属性的对象,传的n,对象就是n。call里面的this是调用call点前的对象,就是m,而m添加到n里面的一个函数,所以m里的this就是n(外层)。call里的参数content的值是n。
上面call里的arguments循环时从第二个开始,原因是第一个函数n而并非真正要的参数
为什么不用箭头函数:因为下面会用到arguments以及this, 箭头函数里没有this的绑定 并且没有arguments,里面的context指的是window,这的this指的是this里 ".前的对象" 也就是m,给window里的fn添加object方法对象,最后,会删除开始给window添加的对象, 最后还要返回这个对象, 所以这要重新赋一个位置。
eg中:上面是箭头函数的话 , 这里的this是window,那么this.name就是undefined,因为箭头函数里没有this绑定, 里面的this 指的是上面一层this的指向,如果是普通函数的话, this指的就是n了;把m对象应用到n里面,如果下面是:w.o.myCall指的是w里面的o对象应用到n里面。

函数m改成箭头函数后, m里面的this指向window;
上面说的,参数里只写一个content的话, 而传的参数是一个以上, 那么这个接受函数参数的content只接第一个参数,也就是n;

时隔一段时间后再看的, 重新理解:
m.myCall(n, 47, '拉拉');
m的原型上的myCall方法里面的this指的是m,
这个应该是用了this指向里面的'.前的对象', 这里m是myCall点前的对象,所以myCall里的this指向m,
又是因为,myCall是m父对象原型上的方法,原型方法里的this指向的是引用的子对象;
而被添加到n对象里的m对象方法, m是个普通函数的话, 那m里面的this指向是n(this指向外层),
而m是个箭头函数的话, m里的this指向的是window(箭头函数没有this绑定,它的this和外部的this保持一致);
myCall里的content其实指的是第一个参数(也就是这里的n)(如果想所有都接受的话也可以myCall参数里面占3个位置,myCall(content,n,m)但是不建议太麻烦, 因为argumens就可以接受到所有的参数),
而arguments指的是所有参数, 因为第一个参数是n不是真正的参数, 所以, 遍历argumens时, i是从1开始的;
如下图:

myCall里如果是箭头函数的话:
那么里面就不能使用arguments, 因为箭头函数没有arguments, 也没有this绑定, myCall里面的this就会和外部的this保持一致,那就是window;
所以使用的是普通函数;
如下:

apply
apply原理与call很相似,就传参的方式不同。
手撕apply
//手写apply
Function.prototype.myApply=function(context,arr){
var contexts=Object(context)||window;
contexts.fn=this;
let result;
if(!arr){
return contexts.fn();
}else{
let args=[];
for(var i=0;i<arr.length;i++){
args.push(`arr[${i}]`);
}
result=eval(`contexts.fn(${args})`);
}
delete contexts.fn;
return result;
}
先判断是否有值,有值的话转为对象, 否则赋值window。
eval: 是允许执行一段代码字符串; 如果你字符串里面是可执行的js 那么他就会执行这一段代码;有执行动态代码的能力\
但是eval要慎用, 因为有危险系数;
具体可以参考这篇文章:blog.csdn.net/weixin_4609…
第一个参数传n进去:

没有n的情况下,第一个参数不传n传的this,其实就是window;

问题:
下面这块这样写,虽然也是可行的;但是前提是,第二个参数也就是传递给函数的参数是个数组是可以的,
但是:
apply, 第二个参数(传递给函数的参数)可以是数组也可以是个类数组.
类数组:
结构像数组的对象,有length,但是没有数组的api.
拥有 length 属性:类数组对象通常具有一个 length等属性,表示对象中元素的个数。
按照索引访问元素:可以通过索引值从类数组对象中获取元素,就像访问数组元素一样。 没有数组原型上的方法:类数组对象不具备数组原型上的方法(如 push、pop、forEach 等),因此无法直接使用这些方法。
常见的类数组对象包括:
arguments 对象:在函数内部自动创建的对象,存储了函数调用时传递的参数。
HTMLCollection 和 NodeList:DOM 操作返回的一些对象,如:document.getElementsByTagName() 返回的对象集合。
字符串:字符串可以通过索引访问每个字符,并具有 length 属性。
刚开始我认为下面的写法是可行的 ;
Function.prototype.myApply = function (context, arr) {
var contexts = Object(context) || window;
contexts.fn = this;
let result;
if (!arr) {
return contexts.fn();
} else {
//刚开始不太理解为什么要这样写,大厂题库里面的写法
//let args = [];
//for (var i = 0; i < arr.length; i++) {
// args.push(`arr[${i}]`);
//}
// result = eval(`contexts.fn(${args})`);
//刚开始我的想法,觉得跟call除了参数不同,其他的差不多是一致的,所以下面这样写貌似也可行
result=contexts.fn(arr);
}
delete contexts.fn;
return result;
}
参数是数组的话,没啥问题,如下:
但参数是类数组的话,就有问题了,函数接受的参数不正常, 如下:

代码改成下面这种, 接受的参数就正常了,如下:
Function.prototype.myApply = function (context, arr) {
var contexts = Object(context) || window;
contexts.fn = this;
let result;
if (!arr) {
return contexts.fn();
} else {
let args = [];
for (var i = 0; i < arr.length; i++) {
args.push(`arr[${i}]`);
}
result = eval(`contexts.fn(${args})`);
}
delete contexts.fn;
return result;
}

instanceof
什么是instanceof?
instanceof用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上。
手撕instanceof
实现一个instanceof , 模拟
//实现一个instanceof, 模拟
//L代表左侧, R代表右侧
function instance_of(L,R){
var L_proto=L.__proto__;
var RProtoType=R.prototype;
if(L_proto===null){
return false;
}
if(L_proto===RProtoType){
return true
}
}
//eg:
function K(){}
var L=new K();
instance_of(L,K);
L代表左侧, R代表右侧。