函数中的 this 不止有 72 变

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

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

在 MDN 中我查到了:

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

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

apply 的应用:

function once (fn) {
  var called = false;
  return function () {
    if (!called) {
      called = true;
      fn.apply(this, arguments);
    }
  }
}

call 的应用:

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

bind的应用:

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;

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

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

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

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

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

1.直接调用:

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

2.通过 this 调用:

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

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

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

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

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

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

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();

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

const time = {
    second: 1,
    afterOneSecond() {
        setTimeout(this.timeInvoke.bind(this), 1000);
    },
    timeInvoke() {
        this.second += 1;
        console.log(this.second);
    }
};
time.afterOneSecond();

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

例 2、函数调用

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);

执行上面代码会报错,因为 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、构造函数,通过 call 来调用某个函数,替换 this。

function Product(name, price) {
    this.name = name;
    this.price = price;
}


function Food(name, price) {
    // 调用 Product 函数
    Product.call(this, name, price);
    this.catagory = 'food';
}
let food = new Food('包子', 1);
console.log(food.name); // 包子 

例 4、调用匿名函数

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);
}

结果为:

回头再看看课程开始之前 Vue 中关于 apply、call 和 bind 的应用,是不是能看懂了?如果你还有更好的关于 apply、call 和 bind 的例子,或者有助于理解本节课程的方法,欢迎在留言取分享给大家,


推荐阅读:

&& ,|| 超越了我的认知
连接你、我、他 —— this


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值