apply、call、bind三者的区别
相同点
1.1 三者都能改变函数中的this指向
1.2 第一个参数都是是要改变的this指向的对象
不同点
2.1、第二个参数:apply传的是参数数组;call和bind传的是参数列表,且apply和call是一次性传入的,bind可以分多次传入
2.2、bind是返回绑定this之后的函数,需要调用。apply和call则是立即执行的。
光说总结怎么行呢,上例子:
基础数据:
let name = = "lucky";
const obj = {
name: "martin",
say: function (content = "") {
console.log(this.name + content,"---",this);
},
hobby: function(content1="", content2="") {
console.log(`${
this.name}喜欢${
content1}${
content2? '和':''}`+ content2,"---",this);
},
play: function(content1="", content2="") {
console.log(`${
this.name}喜欢打${
content1}${
content2? '和':''}`+ content2,"---",this);
}
};
obj.say(); //martin,this指向obj对象
下面例子:三者都能改变函数中的this指向且第一个参数都是是要改变的this指向的对象。
//这个指向obj, 也是因为是obj调用了这个方法
obj.say("你好"); //martin你好 this指向obj对象
// this指向window对象, 因为call改变了它的指向
obj.say.call(null, "你好"); //lucy你好
obj.say.apply(null, "你好"); //lucy你好
obj.say.bind(null, "你好"); //lucy你好
下面例子:apply传的是参数数组;call和bind传的是参数列表。
obj.hobby.call(null, "吃","喝")//lucy喜欢吃和喝
obj.hobby.apply(null, ["吃2","喝2"])//lucy喜欢吃和喝
obj.hobby.bind(null, "吃3","喝3")lucy喜欢吃和喝
下面例子:call和apply是立即执行,bind是绑定后返回一个函数,需要调用后才会执行。
obj.play.call(null, "乒乓球3","豆豆");//lucky喜欢打乒乓球3和豆豆
obj.play.apply(null, ["乒乓球3","豆豆"]);//lucky喜欢打乒乓球3和豆豆
let play2 = obj.play.bind(null, "羽毛球3","豆豆"); //此不会执行打印,需要作以下调用
play2();//lucky喜欢打羽毛球3和豆豆
下面例子:bind可以多次传参:
1、可以在绑定时传参;
2、也可以在调用时传参;
3、还可以一部分在调用时传,一部分在绑定时传,需要注意的是绑定的参数是不会变的了,调用时传的参只能顺绑定的参后面传入;
// 1、绑定时传参 [注] bind的时候如果绑定参数,调用的时候再传参也不会被改变
let play3 = obj.play.bind(null, "王者1","豆豆");
play3();//lucky喜欢打王者1和豆豆
//下面打印就是因为绑定的时候参数也被绑定了,所以打印的是绑定时的内容
play3("王者2","豆豆");//lucky喜欢打王者1和豆豆
// 2、调用时传参
let play5 = obj.play.bind(null);
play5("王者2","豆豆2");//lucky喜欢打王者2和豆豆2
/*
3、调用的时候传参,已经被绑定的参数不变, 调用时传的参会从绑定的参之后直接顺位下去,如下所示:
第一个参数已绑定,调用时的第一个参数直接变成第二个参数
*/
let play4 = obj.play.bind(null,"王者3");
play4("王者4","豆豆");//lucky喜欢打王者3和王者4
下面是setTimeout相关知识。
/*
setTimeout的第一个参数是将要执行的代码或者函数名
obj.say,其实是传了一个方法进去,执行方法,此时是在全局执行上下文的环境中执行
所以它指向window对象
*/
setTimeout(obj.say, 0); //lucy,this指向window对象
// 这个指向obj, 是因为是obj调用了这个方法(与下同)
setTimeout(() => {
obj.say();
})
//这个指向obj, 也是因为是obj调用了这个方法
setTimeout(obj.say("你好"), 0); //martin你好 this指向obj对象
节流函数和防抖函数
下面是基础代码
<body>
<div>
<!-- 节流函数 -->
<button onclick="handleThrottle1(num1, num2, num3)">时间差节流函数</button>
<button onclick="handleThrottle(obj)">setTimeout节流函数</button>
<!-- 不用apply 会出现的问题 -->
<button onclick="foo.bar()">节流函数改变this指向</button>
<!-- 防抖函数 -->
<button onclick="handleDebounce(num1, num2, num3)">防抖函数</button>
</div>
</body>
//javascript 代码
let num1 = 1;
num2 = 2;
num3 = 3;
const obj = {
name: "cjj",
age: 18
}
节流函数:让一个函数无法在短时间内连续调用,【在设置时间内只执行一次】防止用户频繁的行为导致程序奔溃,影响体验。具体看下面事例:
// 方法一 时间差法 节流函数
function throttle1(fn, wait) {
let pre = new Date().getTime();
return function(...args) {
let now = new Date().getTime();
if(now - pre >= wait) {
fn.apply(this, args);//将this正确指向
// fn(...args);
console.log(this.obj,"----打印obj---")
pre = new Date().getTime();
}
}
}
//需要节流的函数
function myFn(arg1, arg2, arg3) {
console.log("----打印成功----", arg1, arg2, arg3);
}
//触发事件
const handleThrottle1 = throttle1(myFn,2000);
// 方法二 setTimeout方法 节流函数
function throttle(fn, wait) {
let timer = null
return function(...args) {
if(!timer) {
// fn(...args);
fn.apply(this, ...args);//将this正确指向
timer = setTimeout(()=> {
timer = null
}, wait)
}
}
}
//需要节流的函数
function myObjFn(obj) {
console.log(obj,"----打印---");
}
//触发事件
const handleThrottle = throttle(myObjFn,2000);
防抖函数:事件延迟时间触发。在延迟的时间内,事件再次被触发将会重新计算延迟时间。通俗一点就是:事件只触发一下会延迟,多下就执行最后一次。具体看下面事例:
//防抖函数
function debounce(fn, wait) {
let timer = null;
return function (...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...args);
time = null;
}, wait)
}
}
//需要防抖的函数
function MF(arg1, arg2, arg3) {
console.log("-----防抖----", arg1, arg2, arg3);
}
//触发事件
const handleDebounce = debounce(MF, 1000);
在大部分情况下,不使用apply等方法,好像也能实现节流和防抖的效果,那我是不是可以直接调用方法而不改变this指向?其实有些情况,不使用apply等函数,它会出现this指向问题,所以还是有需要改变this指向的。下面我用一个例子说明:
// 节流函数
function throttle(fn, wait) {
let timer = null
return function(...args) {
if(!timer) {
// fn(...args);
fn.apply(this, ...args);//将this正确指向
timer = setTimeout(()=> {
timer = null
}, wait)
}
}
}
// 使用apply的理由
class Foo {
constructor() {
this.a = "a";
this.bar = throttle(this.bar, 500);
}
bar() {
/*
具体可修改上面throttle方法内的fn的注释,二选一即可
如果节流函数使用 fn(...args) 下面打印的this是 undefined, this.a会报错
如果节流函数使用 fn.apply(this, ...args); 下面打印的this是 实例化的对象foo, this.a 是 "a"
*/
console.log(this,"----this----");
console.log(this.a,"----a----");
}
}
const foo = new Foo();