apply、call、bind 异同

  1. 【同】都可改变 this 对象的指向;
  2. 【同】第一个参数都是 this 要指向的对象,若没有这个参数或参数为 undefined 或 null,则默认指向全局 window;
  3. 【同】都可以传参,但是 apply 是数组,而 call 是参数列表,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入
  4. 【异】bind 是返回 绑定 this 之后的函数,便于稍后调用;apply 、call 则是 立即执行
  5. 【异】bind()会返回一个新的函数,如果这个返回的新的函数作为 构造函数 创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例。(重点 - 易忽略)
  6. 【异】bind可多次调用,this始终指向第一个bind绑定的第一个参数。(原理看bind实现)

    call 函数实现

    法一讲解:

  7. 判断调用对象是否为函数:this

  8. 判断传入上下文是否存在,若不存在,设为 window
  9. 截取参数:const args = [...arguments].slice(1)
  10. 将函数设为上下文对象的一个属性:context.fn = this
  11. 使用上下文来调用这个方法,并返回结果:return context.fn(...args)
  12. 删除上下文对象上的方法:delete context.fn
  13. 返回结果 ```javascript // 法一: Function.prototype.myCall = function(context) { // console.log(this); // this 指向调用 myCall 的函数,如:test 函数 // 判断调用对象 if (typeof this !== “function”) { console.error(“type error”); } // 获取参数 let args = […arguments].slice(1), result = null; // 判断 context 是否传入,如果未传入则设置为 window context = context || window; // 将调用函数设为对象的方法 context.fn = this; // 调用函数 result = context.fn(…args); // 将属性删除 delete context.fn; return result; };

法二(推荐): Function.prototype.myCall = function (context, …args) { if (!context || context === null) { context = window; } // 创造唯一的key值 作为我们构造的context内部方法名 let fn = Symbol(); context[fn] = this; //this指向调用call的函数 // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了 return contextfn; };

// 调用 var obj = { name: ‘chen’, getName: function test() { console.log(this.name) } } obj.getName.myCall({name: ‘lu’}, 1, 2, 3)

  1. <a name="HeR15"></a>
  2. ## apply 函数实现
  3. 法一讲解:
  4. 1. 判断调用对象是否为函数:`this`
  5. 2. 判断传入上下文是否存在,若不存在,设为 `window`
  6. 3. 将函数设为上下文对象的一个属性:`context.fn = this`
  7. 4. 判断参数值是否有传入
  8. 5. 使用上下文来调用这个方法,并返回结果:`return context.fn(...args)`
  9. 6. 删除上下文对象上的方法:`delete context.fn`
  10. 7. 返回结果
  11. ```javascript
  12. // 法一:
  13. Function.prototype.myApply = function(context) {
  14. // 判断调用对象是否为函数
  15. if (typeof this !== 'function') {
  16. throw new TypeError('Error')
  17. }
  18. let result = null;
  19. // 判断 context 是否存在,若不存在设为 window
  20. context = context || window;
  21. // 将方法设为对象里面的方法
  22. context.fn = this;
  23. // 调用方法,判断是否传入参数
  24. if(arguments[1]) {
  25. console.log('有参数', arguments[1])
  26. result = context.fn(...arguments[1]);
  27. } else {
  28. console.log('无参数')
  29. result = context.fn();
  30. }
  31. // 删除 context 上的 fn 属性方法
  32. delete context.fn;
  33. return result;
  34. };
  35. // 法二(推荐):原理和 call 函数方法实现中法二一样
  36. Function.prototype.myApply = function (context, ...args) {
  37. if (!context || context === null) {
  38. context = window;
  39. }
  40. // 创造唯一的key值 作为我们构造的context内部方法名
  41. let fn = Symbol();
  42. context[fn] = this; //this指向调用apply 的函数
  43. // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
  44. return context[fn](...args);
  45. };
  46. // 调用
  47. var obj = {
  48. name: 'chen',
  49. getName: function test() {
  50. console.log(this.name)
  51. }
  52. }
  53. obj.getName.myApply({name: 'lu'}, [1, 2])

bind 函数实现

bind函数实现可能比较复杂一点,因为涉及到参数合并(类似:柯里化);
法一讲解:

  1. 判断调用对象是否为函数:this
  2. 保存当前函数的引用,获取传入函数的其他参数
  3. 创建一个函数返回
  4. 函数内用 apply来绑定函数调用,需判断函数作为构造函数的情况;若为构造函数使用需传入当前函数的 thisapply调用,若不是传入指定上下文对象。 ```javascript 法一: Function.prototype.myBind = function(context) { // 判断调用对象是否为函数 if (typeof this !== ‘function’) {
    1. throw new TypeError('Error')
    } // 获取参数,注意此时变量声明用的是 var var args = […arguments].slice(1); var fn = this; // 创建函数并返回 return function Fn() { // 根据调用方式不同,传入不同的值 return fn.apply(
    1. this instanceof Fn ? this : context,
    2. args.concat(...arguments);
    ); } }

法二(推荐): Function.prototype.myBind = function(context, …args) { if(!context || context === null){ context = window; } // 创造唯一的key值 作为构造的context内部方法名 const fn = Symbol(); context[fn] = this; let this = this; // 重点,也是复杂点 const result = function (…innerAgs) { /* 第一中情况:用 bind 绑定后的函数作为构造函数,通过 new 操作符使用,则不绑定传入的 this, 而是将 this 指向实例对象。此时由于 new 操作符作用,this 指向 result 实例对象,而 result 又继承自传入的 this ,依原型链知识可知如下结论: this.proto === result.prototype // this instanceof result // true this.proto.proto === result.prototype.__proto === this.prototype // this instanceof _this // true */ if (this instanceof _this === true) { // 此时this指向指向result的实例 这时候不需要改变this指向 this[fn] = _this; thisfn; // ES6 方法来合并数组 } else { // 若只是普通函数调用,直接改变 this 指向为传入的 context contextfn; } }

// 若绑定的是构造函数,需继承构造函数的属性和方法 // 实现继承的方式是使用:Object.create() result.prototype = Object.create(this.prototype);

return result; } ``` 参考理解(TODO):

  1. 面试官问:能否模拟实现JS的bind方法
  2. 面试感悟,手写bind,apply,call