call() 和 apply()

call()apply()都是用于更改this的指向。

  1. // 更改 this 的指向
  2. function Car(brand, color) {
  3. this.brand = brand;
  4. this.color = color;
  5. }
  6. var newCar = {};
  7. Car.call(newCar, "Benz", "red");
  8. //Car.apply(newCar, ["LiXiang", "black"]);
  9. console.log(newCar)

image.png :::info call()apply()的作用是一样的,他们的第一个参数都是要指向的对象,call()的第二个参数乃至第N个参数是传入的参数,apply的第二个参数是一个数组,也是要传入的参数。 :::

举例 🌰 :

  1. function Compute() {
  2. this.plus = function (a, b) {
  3. console.log(a + b);
  4. };
  5. this.minus = function (a, b) {
  6. console.log(a - b);
  7. };
  8. }
  9. function FullCompute() {
  10. // 通过 call() 可以拿到 Compute 下面的函数
  11. Compute.call(this);
  12. this.mul = function (a, b) {
  13. console.log(a * b);
  14. };
  15. this.div = function (a, b) {
  16. console.log(a / b);
  17. };
  18. }
  19. var compute = new FullCompute();
  20. compute.plus(1, 2);
  21. compute.minus(1, 2);
  22. compute.mul(1, 2);
  23. compute.div(1, 2);

重写 call() 和 apply() 方法

重写的思路其实是基于对象调用方法,方法里的this指向该对象
重写call()方法

  1. // 写到函数的原型上,这样所有函数都能调用
  2. Function.prototype.myCall = function (context) {
  3. // context 参数是要指向的 this 对象
  4. // 这样的判断不严谨
  5. if (typeof context === "object" && context !== null) {
  6. // 产生一个随机的属性民
  7. var prop =
  8. Math.random().toString(36).substr(3, 6) +
  9. new Date().getTime().toString(36);
  10. // 将参数 push 进一个新的数组里面
  11. var args = [];
  12. // i 从 0 开始是因为 0 是要指向的 this 对象而不是传递的参数
  13. for (var i = 1; i < arguments.length; i++) {
  14. args.push(arguments[i]);
  15. }
  16. // 给这个要指向的 this 对象新增一个属性,属性名就是生成的随机字符串
  17. // 属性值就是当前调用这个方法的 this ,也就是 test 函数
  18. context[prop] = this;
  19. // 因为 call 的参数是这样的 test.call({}, 1, 2)
  20. // 所以我们可以使用 eval() 将数组的参数进行结构 String([1, 2]) => 1, 2
  21. var res = eval("context[prop](" + args + ")");
  22. delete context[prop];
  23. // 将结果返回出去
  24. return res;
  25. }
  26. };
  27. // 测试一下
  28. function test(a, b) {
  29. console.log(a, b); // 1 2
  30. console.log(this); // { name: "name1" }
  31. console.log(a + b); // 3
  32. return a + b;
  33. }
  34. test.myCall({ name: "name1" }, 1, 2);

重写apply()方法

  1. Function.prototype.myApply = function (context) {
  2. if (typeof context === "object" && context !== null) {
  3. var prop =
  4. Math.random().toString(36).substr(3, 6) +
  5. new Date().getTime().toString(36);
  6. var args = [];
  7. // call 和 appley 只是接收的参数形式不同
  8. // 所以 arguments[1] 参数我们要取的参数
  9. for (var i = 0; i < arguments[1].length; i++) {
  10. args.push(arguments[1][i]);
  11. }
  12. context[prop] = this;
  13. var res = eval("context[prop](" + args + ")");
  14. delete context[prop];
  15. return res;
  16. }
  17. };
  18. // 测试一下
  19. function test(a, b) {
  20. console.log(a, b); // 1 2
  21. console.log(this); // { name: "name2" }
  22. console.log(a + b); // 3
  23. return a + b;
  24. }
  25. test.myApply({ name: "name2" }, [1, 2]);

bind()

bind也用于改变函数内的this指向。

  1. var p1 = {
  2. name: "张三",
  3. hobby: this.hobby,
  4. play: function (sex, age) {
  5. console.log("年龄" + age + ";性别" + sex + ";喜欢" + this.hobby);
  6. },
  7. };
  8. var p2 = {
  9. name: "李四",
  10. hobby: "踢球",
  11. };
  12. p1.play.call(p2, "男", 20); // 年龄20;性别男;喜欢踢球
  13. p1.play.apply(p2, ["男", 20]); // 年龄20;性别男;喜欢踢球
  14. p1.play.bind(p2, "男", 20)(); // 年龄20;性别男;喜欢踢球

bindcall/apply的区别:
call/apply改变函数的this后立即执行。
bind改变函数的this后不会立即执行,而是返回一个函数。

所以我们可以根据自己的需求来决定到底是用callapply还是bind

  1. var el = document.getElementById("box");
  2. // 因为事件处理程序中的 this 指向元素本身
  3. el.addEventListener("click", tabClick.bind(this), false);
  4. function tabClick(){
  5. // do...
  6. }

bind 后的实例化

因为bind改变this后不会立即执行,所以它不会影响我们实例化构造函数。

  1. var p = {
  2. age: 18,
  3. };
  4. function Person() {
  5. console.log(this);
  6. console.log(this.age);
  7. }
  8. // bind(p) 改变的是 Person 函数内部的 this
  9. // 也就是把 window 变成了 p
  10. // 但是 new person2 的时候会产生新的 this 对象,所以没有影响
  11. var person2 = Person.bind(p);
  12. new person2();

call则不会

  1. var p = {
  2. age: 18,
  3. };
  4. function Person() {
  5. console.log(this);
  6. console.log(this.age);
  7. }
  8. // call(p) 改变的是 Person 函数内部的 this
  9. // 没有返回值
  10. // person2 是 undefind,所以无法实例化
  11. var person2 = Person.call(p);
  12. new person2();
  13. // 这样的方式和 Person.bind(p) 是一样的道理
  14. Person.call(p);
  15. new Person();

重写 bind() 方法

  1. Function.prototype.myBind = function (context) {
  2. var _this = this;
  3. // 将类数组转换成数字且从 1 开始截取
  4. var args = Array.prototype.slice.call(arguments, 1);
  5. return function () {
  6. var args2 = Array.prototype.slice.call(arguments);
  7. // 这里的 this 指向 widnow 所以外界要保存
  8. _this.apply(context, args.concat(args2));
  9. };
  10. };
  11. var p = {};
  12. function Person(name, sex) {
  13. console.log(this);
  14. console.log(arguments);
  15. }
  16. Person.bind(p, "name1", "sex1")(); // {} ["name1", "sex1"]
  17. Person.myBind(p, "name2", "sex2")(); // {} ["name2", "sex2"]
  18. Person.myBind(p, "name3", "sex3")("name4", "sex4"); // {} ["name3", "sex3", "name4", "sex4"]