函数中的 this 不止有 72 变

开发 前端
在课程 连接你、我、他 —— this 中我们学习了 this,最后留了一个问题,如何修改 this 的指向,今天一起学习。

在课程 连接你、我、他 —— this 中我们学习了 this,最后留了一个问题,如何修改 this 的指向,今天一起学习。

修改 this 的指向可通过 apply、call、bind 这三个函数中的任意一个实现。那这三个函数是谁的方法呢?

在 MDN 中我查到了:

这张图说明了这 3 个函数是 Function prototype 的方法,也就是说「每个函数都有着三个方法」。当定义一个函数,这个函数默认包含这三个方法。

我们感受一下 Vue.js 中关于 apply、call 和 bind 的使用:

apply 的应用:

  1. function once (fn) { 
  2.   var called = false
  3.   return function () { 
  4.     if (!called) { 
  5.       called = true
  6.       fn.apply(this, arguments); 
  7.     } 
  8.   } 

call 的应用:

  1. var hasOwnProperty = Object.prototype.hasOwnProperty; 
  2. function hasOwn (obj, key) { 
  3.   return hasOwnProperty.call(obj, key) 

bind的应用:

  1. function polyfillBind (fn, ctx) { 
  2.   function boundFn (a) { 
  3.     var l = arguments.length; 
  4.     return l 
  5.       ? l > 1 
  6.         ? fn.apply(ctx, arguments) 
  7.         : fn.call(ctx, a) 
  8.       : fn.call(ctx) 
  9.   } 
  10.  
  11.   boundFn._length = fn.length; 
  12.   return boundFn 
  13.  
  14. function nativeBind (fn, ctx) { 
  15.   return fn.bind(ctx) 
  16.  
  17. var bind = Function.prototype.bind 
  18.   ? nativeBind 
  19.   : polyfillBind; 

你可能看不懂上面的用法,下面我们一一抛开谜底。

当一个新事物的出现,总是有目的的,那么 apply、call 和 bind 的出现是为了解决什么问题呢?它们为什么是函数的方法呢?为什么不是其它对象的方法。

通过 apply、call 可以自定义 this 调用某个函数,比如定义一个全局函数(严格模式):

  1. 'use strict'; 
  2. function gFun(name, age) { 
  3.     console.log(this); 

这个函数可以通过下面 5 种方式调用,也就是说通过 apply、call、bind 可以调用一个函数 F,其中「函数 F 执行上下文中的 this 可以在调用时指定」:

1.直接调用:

  1. gFun('suyan', 20); // this 为 undefined 

2.通过 this 调用:

  1. this.gFun('suyan', 20); // this 为 window 

3.通过 apply 调用,把所有的参数组合成一个数组作为 apply 的参数:

  1. gFun.apply(this, ['suyan', 20]); // this 为 window 

4.通过 call 调用,参数通过逗号分割,这是与 apply 调用的区别:

  1. gFun.call(this, 'suyan', 20);  // this 为 window 

5.通过 bind 调用,会返回一个原函数的拷贝,并拥有指定的 this和参数:

  1. let bGFun = gFun.bind(this, 'suyan', 20); 
  2. bGFun();  // this 为 window 

我们一起看一些例子:

例1、setTimeOut 的使用:

  1. const time = { 
  2.     second: 1, 
  3.     afterOneSecond() { 
  4.         setTimeout(function () { 
  5.             this.second += 1; 
  6.             console.log(this.second); 
  7.         }, 1000); 
  8.     } 
  9. }; 
  10. time.afterOneSecond(); 

上面这段代码执行后,第 6 行代码的打印结果是 NaN,在连接你、我、他 —— this 这节课程中我们有提到过 this 设计的一个弊端是不能继承。其实可以通过 bind 改造一下这个函数:

  1. const time = { 
  2.     second: 1, 
  3.     afterOneSecond() { 
  4.         setTimeout(this.timeInvoke.bind(this), 1000); 
  5.     }, 
  6.     timeInvoke() { 
  7.         this.second += 1; 
  8.         console.log(this.second); 
  9.     } 
  10. }; 
  11. time.afterOneSecond(); 

函数 timeInvoke 通过 bind 绑定 this,并返回一个新的函数,执行结果为 2。bind 好像具有「暂存」的功能,把当前的 this 暂存起来。

例 2、函数调用

  1. const person = { 
  2.     name: 'suyan', 
  3.     age: 20, 
  4.     showName(pre) { 
  5.         return pre + '-' + this.name; 
  6.     }, 
  7.     update(name, age) { 
  8.         this.name = name; 
  9.         this.age = age; 
  10.     } 
  11. }; 
  12.  
  13. function generateName(fun) { 
  14.     let name = fun(); 
  15.     console.log('showName = ', name); 
  16.  
  17. generateName(person.showName); 

执行上面代码会报错,因为 showName 中的 this 为 undefined:

可以通过 bind 「暂存 this」:

  1. const person = { 
  2.     name: 'suyan', 
  3.     age: 20, 
  4.     showName(pre) { 
  5.         return pre + '-' + this.name; 
  6.     }, 
  7.     update(name, age) { 
  8.         this.name = name; 
  9.         this.age = age; 
  10.     } 
  11. }; 
  12.  
  13. function generateName(fun) { 
  14.     let name = fun(); 
  15.     console.log('showName = ', name); 
  16. // 指定 this 为 person 对象 
  17. let bindShowName = person.showName.bind(person, '前端小课'); 
  18. generateName(bindShowName); 

例 3、构造函数,通过 call 来调用某个函数,替换 this。

  1. function Product(name, price) { 
  2.     this.name = name; 
  3.     this.price = price; 
  4.  
  5. function Food(name, price) { 
  6.     // 调用 Product 函数 
  7.     Product.call(this, name, price); 
  8.     this.catagory = 'food'
  9. let food = new Food('包子', 1); 
  10. console.log(food.name); // 包子  

例 4、调用匿名函数

  1. const animals = [ 
  2.     { 
  3.         name: 'King' 
  4.     }, 
  5.     { 
  6.         name: 'Fail' 
  7.     } 
  8. ]; 
  9.  
  10. for (let i = 0; i < animals.length; i++) { 
  11.     (function (i) { 
  12.         // 可以直接使用 this 
  13.         this.print = function () { 
  14.             console.log('#' + i + ' ' + this.name); 
  15.         }; 
  16.         this.print(); 
  17.     }).call(animals[i], i); 

结果为:

回头再看看课程开始之前 Vue 中关于 apply、call 和 bind 的应用,是不是能看懂了?

 

责任编辑:赵宁宁 来源: 素燕
相关推荐

2010-08-24 10:12:45

2009-08-03 18:24:28

C# 4.0协变和逆变

2014-01-21 09:29:45

国产操作系统操作系统

2020-03-20 19:10:37

支付宝新版移动应用

2010-04-23 19:54:16

2015-08-17 11:30:51

2018-04-17 12:28:33

iOS 11.4苹果ClassKit

2010-09-09 11:17:44

SQL函数公历

2010-04-23 15:40:26

72ub手机费

2011-01-14 10:27:18

C#.netasp.net

2020-06-23 15:00:45

AI人工智能远程办公

2021-04-16 15:17:19

Windows 10Windows操作系统

2024-02-22 12:18:16

Python函数用法

2015-02-02 10:03:50

2015-09-10 04:42:10

iPone苹果发布会

2011-08-08 10:47:45

超微机箱服务器

2022-04-13 11:18:48

渗透测试Mock

2022-04-18 20:12:03

TypeScript静态类型JavaScrip
点赞
收藏

51CTO技术栈公众号