一、基础部分理解
- call、apply 和 bind 都是用于改变函数运行时的上下文,即函数内部 this 所指的对象
- call 和 apply 的唯一区别是参数的写法:
function.call(obj, parameter1, parameter2…)
function.apply(obj, [parameter1, parameter2…]) - bind 和 call、apply 的区别是 使用bind并不会立刻调用函数,而后两者都是立刻调用
二、举例理解
1. 不引入参数
var a, b;
function test_1(){
this.id = "test1";
this.record = function(){
console.log("test 1 function id: " + this.id);
};
};
function test_2(){
this.id = "test2";
this.record = function(){
console.log("test 2 function id: " + this.id);
};
};
a = new test_1();
b = new test_2();
a.record();//result: "test 1 function id: test1"
b.record();//result: "test 2 function id: test2"
b.record.call(a); //result: "test 2 function id: test1"
我们可以看到本来a, b都正确打印出来所调用函数内部的id,但是当 b 调用了call 就将函数本来的 this 从指向本身 test_2 转为指向 a, 结果 b 虽然调用的是 test_2 的方法,但打印出来的 id 却是属于 test_1 的, 这就是因为 call 改变了 test_2 的 record 函数运行时的上下文
2.引入参数
var a, b;
function test_1(){
this.id = "add";
this.reference = 5;
this.record = function(c){
console.log("function id: " + this.id + " parameter: " + c + " " + this.reference + " result: " + (c + this.reference));
};
};
function test_2(){
this.id = "sub";
this.reference = 6;
this.record = function(d){
console.log("function id: " + this.id + " parameter: " + d + " " + this.reference + " result: " + (d - this.reference));
};
};
a = new test_1();
b = new test_2();
a.record(1);//result: "function id: add parameter: 1 5 result: 6"
b.record(2);//result: "function id: sub parameter: 2 6 result: -4"
b.record.call(a, 2); //result: "function id: add parameter: 2 5 result: -3"
我们可以看到引入参数后,参数本身不受 call 影响,因为 call 改变的是函数运行时的上下文,所以所有用 this 指定的变量都会被新 this 对象的上下文覆盖, 此例中 reference 参数就由本来的 6 变为了 test_1 中的 5,但是注意 最终执行的还是 test_2 的 record 方法
二、应用理解
纠结这三个函数的用法主要还是遇到了实际应用的问题
window.onload = function(){
var test = new RecordShift();
};
class RecordShift{
constructor(){
this.div = document.createElement("div");
this.div.style.setProperty("z-index", 10);
this.div.style.setProperty("background-color", "#66d9ff");
this.div.style.setProperty("position", "absolute");
this.div.style.setProperty("display", "block");
this.div.style.setProperty("left", window.innerWidth * 0.5 + "px");
this.div.style.setProperty("top", window.innerHeight * 0.5 + "px");
this.div.style.setProperty("width", "25px");
this.div.style.setProperty("height", "25px");
this.div.draggable = true;
this.start_position_x = 0;
this.start_position_y = 0;
this.div.addEventListener("dragstart", this.start);
this.div.addEventListener("dragend", this.end.bind(this));
document.body.appendChild(this.div);
}
start(){
this.start_position_x = window.event.clientX;
this.start_position_y = window.event.clientY;
console.log("drag start position x: " + this.start_position_x + " y: " + this.start_position_y);
}
end(){
this.shift_x = window.event.clientX - this.start_position_x;
this.shift_y = window.event.clientY - this.start_position_y;
console.log("drag end position x: " + this.shift_x + " y: " + this.shift_y);
}
}
当我们运行这段代码,页面中间将出现一个方块,拖动方块,可以触发 start 和 end 这两个函数,正确地打印出拖动开始的坐标和结束时的相对位移
现在稍微改造一下这个类,添加一个打印函数,再次运行代码
end(){
this.shift_x = window.event.clientX - this.start_position_x;
this.shift_y = window.event.clientY - this.start_position_y;
console.log("drag end position x: " + this.shift_x + " y: " + this.shift_y);
this.log();
}
log(){
console.log("class log function");
}
结果发现报错了,未找到 this.log 函数,明明坐标也是用 this 在类里指定好的,怎么变量就可以正常打印,函数就不可以了?这就是因为它们的上下文不一样导致的,这时候就要用到 call、apply 或者 bind 了。只需要改动这一个地方即可,在 this.end 后面添加 .bind(this),再次运行,没有报错,一切正常
this.div.addEventListener("dragend", this.end.bind(this));
总结
JS的坑不是一般的多呐…防不胜防的
希望对你有所帮助