call() 和 apply()
call()和apply()都是用于更改this的指向。
// 更改 this 的指向function Car(brand, color) {this.brand = brand;this.color = color;}var newCar = {};Car.call(newCar, "Benz", "red");//Car.apply(newCar, ["LiXiang", "black"]);console.log(newCar)
:::info
call()和apply()的作用是一样的,他们的第一个参数都是要指向的对象,call()的第二个参数乃至第N个参数是传入的参数,apply的第二个参数是一个数组,也是要传入的参数。
:::
举例 🌰 :
function Compute() {this.plus = function (a, b) {console.log(a + b);};this.minus = function (a, b) {console.log(a - b);};}function FullCompute() {// 通过 call() 可以拿到 Compute 下面的函数Compute.call(this);this.mul = function (a, b) {console.log(a * b);};this.div = function (a, b) {console.log(a / b);};}var compute = new FullCompute();compute.plus(1, 2);compute.minus(1, 2);compute.mul(1, 2);compute.div(1, 2);
重写 call() 和 apply() 方法
重写的思路其实是基于对象调用方法,方法里的this指向该对象
重写call()方法
// 写到函数的原型上,这样所有函数都能调用Function.prototype.myCall = function (context) {// context 参数是要指向的 this 对象// 这样的判断不严谨if (typeof context === "object" && context !== null) {// 产生一个随机的属性民var prop =Math.random().toString(36).substr(3, 6) +new Date().getTime().toString(36);// 将参数 push 进一个新的数组里面var args = [];// i 从 0 开始是因为 0 是要指向的 this 对象而不是传递的参数for (var i = 1; i < arguments.length; i++) {args.push(arguments[i]);}// 给这个要指向的 this 对象新增一个属性,属性名就是生成的随机字符串// 属性值就是当前调用这个方法的 this ,也就是 test 函数context[prop] = this;// 因为 call 的参数是这样的 test.call({}, 1, 2)// 所以我们可以使用 eval() 将数组的参数进行结构 String([1, 2]) => 1, 2var res = eval("context[prop](" + args + ")");delete context[prop];// 将结果返回出去return res;}};// 测试一下function test(a, b) {console.log(a, b); // 1 2console.log(this); // { name: "name1" }console.log(a + b); // 3return a + b;}test.myCall({ name: "name1" }, 1, 2);
重写apply()方法
Function.prototype.myApply = function (context) {if (typeof context === "object" && context !== null) {var prop =Math.random().toString(36).substr(3, 6) +new Date().getTime().toString(36);var args = [];// call 和 appley 只是接收的参数形式不同// 所以 arguments[1] 参数我们要取的参数for (var i = 0; i < arguments[1].length; i++) {args.push(arguments[1][i]);}context[prop] = this;var res = eval("context[prop](" + args + ")");delete context[prop];return res;}};// 测试一下function test(a, b) {console.log(a, b); // 1 2console.log(this); // { name: "name2" }console.log(a + b); // 3return a + b;}test.myApply({ name: "name2" }, [1, 2]);
bind()
bind也用于改变函数内的this指向。
var p1 = {name: "张三",hobby: this.hobby,play: function (sex, age) {console.log("年龄" + age + ";性别" + sex + ";喜欢" + this.hobby);},};var p2 = {name: "李四",hobby: "踢球",};p1.play.call(p2, "男", 20); // 年龄20;性别男;喜欢踢球p1.play.apply(p2, ["男", 20]); // 年龄20;性别男;喜欢踢球p1.play.bind(p2, "男", 20)(); // 年龄20;性别男;喜欢踢球
bind和call/apply的区别:call/apply改变函数的this后立即执行。bind改变函数的this后不会立即执行,而是返回一个函数。
所以我们可以根据自己的需求来决定到底是用call、apply还是bind。
var el = document.getElementById("box");// 因为事件处理程序中的 this 指向元素本身el.addEventListener("click", tabClick.bind(this), false);function tabClick(){// do...}
bind 后的实例化
因为bind改变this后不会立即执行,所以它不会影响我们实例化构造函数。
var p = {age: 18,};function Person() {console.log(this);console.log(this.age);}// bind(p) 改变的是 Person 函数内部的 this// 也就是把 window 变成了 p// 但是 new person2 的时候会产生新的 this 对象,所以没有影响var person2 = Person.bind(p);new person2();
而call则不会
var p = {age: 18,};function Person() {console.log(this);console.log(this.age);}// call(p) 改变的是 Person 函数内部的 this// 没有返回值// person2 是 undefind,所以无法实例化var person2 = Person.call(p);new person2();// 这样的方式和 Person.bind(p) 是一样的道理Person.call(p);new Person();
重写 bind() 方法
Function.prototype.myBind = function (context) {var _this = this;// 将类数组转换成数字且从 1 开始截取var args = Array.prototype.slice.call(arguments, 1);return function () {var args2 = Array.prototype.slice.call(arguments);// 这里的 this 指向 widnow 所以外界要保存_this.apply(context, args.concat(args2));};};var p = {};function Person(name, sex) {console.log(this);console.log(arguments);}Person.bind(p, "name1", "sex1")(); // {} ["name1", "sex1"]Person.myBind(p, "name2", "sex2")(); // {} ["name2", "sex2"]Person.myBind(p, "name3", "sex3")("name4", "sex4"); // {} ["name3", "sex3", "name4", "sex4"]
