函數(shù)中的 this 不止有 72 變
在課程 連接你、我、他 —— this 中我們學習了 this,最后留了一個問題,如何修改 this 的指向,今天一起學習。
修改 this 的指向可通過 apply、call、bind 這三個函數(shù)中的任意一個實現(xiàn)。那這三個函數(shù)是誰的方法呢?
在 MDN 中我查到了:
這張圖說明了這 3 個函數(shù)是 Function prototype 的方法,也就是說「每個函數(shù)都有著三個方法」。當定義一個函數(shù),這個函數(shù)默認包含這三個方法。
我們感受一下 Vue.js 中關(guān)于 apply、call 和 bind 的使用:
apply 的應(yīng)用:
- function once (fn) {
- var called = false;
- return function () {
- if (!called) {
- called = true;
- fn.apply(this, arguments);
- }
- }
- }
call 的應(yīng)用:
- var hasOwnProperty = Object.prototype.hasOwnProperty;
- function hasOwn (obj, key) {
- return hasOwnProperty.call(obj, key)
- }
bind的應(yīng)用:
- function polyfillBind (fn, ctx) {
- function boundFn (a) {
- var l = arguments.length;
- return l
- ? l > 1
- ? fn.apply(ctx, arguments)
- : fn.call(ctx, a)
- : fn.call(ctx)
- }
- boundFn._length = fn.length;
- return boundFn
- }
- function nativeBind (fn, ctx) {
- return fn.bind(ctx)
- }
- var bind = Function.prototype.bind
- ? nativeBind
- : polyfillBind;
你可能看不懂上面的用法,下面我們一一拋開謎底。
當一個新事物的出現(xiàn),總是有目的的,那么 apply、call 和 bind 的出現(xiàn)是為了解決什么問題呢?它們?yōu)槭裁词呛瘮?shù)的方法呢?為什么不是其它對象的方法。
通過 apply、call 可以自定義 this 調(diào)用某個函數(shù),比如定義一個全局函數(shù)(嚴格模式):
- 'use strict';
- function gFun(name, age) {
- console.log(this);
- }
這個函數(shù)可以通過下面 5 種方式調(diào)用,也就是說通過 apply、call、bind 可以調(diào)用一個函數(shù) F,其中「函數(shù) F 執(zhí)行上下文中的 this 可以在調(diào)用時指定」:
1.直接調(diào)用:
- gFun('suyan', 20); // this 為 undefined
2.通過 this 調(diào)用:
- this.gFun('suyan', 20); // this 為 window
3.通過 apply 調(diào)用,把所有的參數(shù)組合成一個數(shù)組作為 apply 的參數(shù):
- gFun.apply(this, ['suyan', 20]); // this 為 window
4.通過 call 調(diào)用,參數(shù)通過逗號分割,這是與 apply 調(diào)用的區(qū)別:
- gFun.call(this, 'suyan', 20); // this 為 window
5.通過 bind 調(diào)用,會返回一個原函數(shù)的拷貝,并擁有指定的 this和參數(shù):
- let bGFun = gFun.bind(this, 'suyan', 20);
- bGFun(); // this 為 window
我們一起看一些例子:
例1、setTimeOut 的使用:
- const time = {
- second: 1,
- afterOneSecond() {
- setTimeout(function () {
- this.second += 1;
- console.log(this.second);
- }, 1000);
- }
- };
- time.afterOneSecond();
上面這段代碼執(zhí)行后,第 6 行代碼的打印結(jié)果是 NaN,在連接你、我、他 —— this 這節(jié)課程中我們有提到過 this 設(shè)計的一個弊端是不能繼承。其實可以通過 bind 改造一下這個函數(shù):
- const time = {
- second: 1,
- afterOneSecond() {
- setTimeout(this.timeInvoke.bind(this), 1000);
- },
- timeInvoke() {
- this.second += 1;
- console.log(this.second);
- }
- };
- time.afterOneSecond();
函數(shù) timeInvoke 通過 bind 綁定 this,并返回一個新的函數(shù),執(zhí)行結(jié)果為 2。bind 好像具有「暫存」的功能,把當前的 this 暫存起來。
例 2、函數(shù)調(diào)用
- const person = {
- name: 'suyan',
- age: 20,
- showName(pre) {
- return pre + '-' + this.name;
- },
- update(name, age) {
- this.name = name;
- this.age = age;
- }
- };
- function generateName(fun) {
- let name = fun();
- console.log('showName = ', name);
- }
- generateName(person.showName);
執(zhí)行上面代碼會報錯,因為 showName 中的 this 為 undefined:
可以通過 bind 「暫存 this」:
- const person = {
- name: 'suyan',
- age: 20,
- showName(pre) {
- return pre + '-' + this.name;
- },
- update(name, age) {
- this.name = name;
- this.age = age;
- }
- };
- function generateName(fun) {
- let name = fun();
- console.log('showName = ', name);
- }
- // 指定 this 為 person 對象
- let bindShowName = person.showName.bind(person, '前端小課');
- generateName(bindShowName);
例 3、構(gòu)造函數(shù),通過 call 來調(diào)用某個函數(shù),替換 this。
- function Product(name, price) {
- this.name = name;
- this.price = price;
- }
- function Food(name, price) {
- // 調(diào)用 Product 函數(shù)
- Product.call(this, name, price);
- this.catagory = 'food';
- }
- let food = new Food('包子', 1);
- console.log(food.name); // 包子
例 4、調(diào)用匿名函數(shù)
- const animals = [
- {
- name: 'King'
- },
- {
- name: 'Fail'
- }
- ];
- for (let i = 0; i < animals.length; i++) {
- (function (i) {
- // 可以直接使用 this
- this.print = function () {
- console.log('#' + i + ' ' + this.name);
- };
- this.print();
- }).call(animals[i], i);
- }
結(jié)果為:
回頭再看看課程開始之前 Vue 中關(guān)于 apply、call 和 bind 的應(yīng)用,是不是能看懂了?