资讯专栏INFORMATION COLUMN

javascript 基础之 call, apply, bind

xeblog / 2013人阅读

摘要:系统,扎实的语言基础是一个优秀的前端工程师必须具备的。第一个参数为调用函数时的指向,随后的参数则作为函数的参数并调用,也就是。和的区别只有一个,就是它只有两个参数,而且第二个参数为调用函数时的参数构成的数组。

系统,扎实的 javascript 语言基础是一个优秀的前端工程师必须具备的。在看了一些关于 call,apply,bind 的文章后,我还是打算写下这篇总结,原因其实有好几个。首先,在如今 ES6 大行其道的今天,很多文章中讲述的它们的应用场景其实用 ES6 可以更优雅的解决。再则,讲它们的实现原理的文章不多,本文将把它们通过代码一一模拟实现,让它们不再神秘。不谦虚的说,关于 call,apply,bind 的知识,看这一篇文章就够了。

改变函数中 this 指向的三兄弟

我们知道在 javascript 的 function 中有 thisarguments 等关键字。本文不讨论 this 指向问题,那个都可以多带带整一篇文章了。一个常见的使用场景是当你使用 . 来调用一个函数的时候,此时函数中 this 指向 . 前面的调用者:

const person = {
    name: "YuTengjing",
    age: 22,
    introduce() {
        console.log(`Hello everyone! My name is ${this.name}. I"m ${this.age} years old.`);
    }
};

// this 此时指向 person
console.log(person.introduce()); // => Hello everyone! My name is YuTengjing. I"m 22 years old. 

通过 call,apply,bind 这三兄弟可以改变 introduce 中 this 的指向。

call
const myFriend = {
    name: "dongdong",
    age: 21,
};

console.log(person.introduce.call(myFriend)); // => Hello everyone! My name is dongdong. I"m 21 years old. 

通过上面代码我们可以看出 introduce 这个函数中的 this 指向被改成了 myFriend。Function.prototype.call 的函数签名是 fun.call(thisArg, arg1, arg2, ...)。第一个参数为调用函数时 this 的指向,随后的参数则作为函数的参数并调用,也就是 fn(arg1, arg2, ...)。

apply

apply 和 call 的区别只有一个,就是它只有两个参数,而且第二个参数为调用函数时的参数构成的数组。函数签名:func.apply(thisArg, [argsArray])。如果不用给函数传参数,那么他俩就其实是完全一样的,需要传参数的时候注意它的应该将参数转换成数组形式。

一个简单的例子:

function displayHobbies(...hobbies) {
    console.log(`${this.name} likes ${hobbies.join(", ")}.`);
}

// 下面两个等价
displayHobbies.call({ name: "Bob" }, "swimming", "basketball", "anime"); // => // => Bob likes swimming, basketball, anime. 
displayHobbies.apply({ name: "Bob" }, ["swimming", "basketball", "anime"]); // => Bob likes swimming, basketball, anime. 

有些 API 比如 Math.max 它的参数为多参数,当我们有多参数构成的数组使或者说参数很多时该怎么办呢?

// Math.max 参数为多参数
console.log(Math.max(1, 2, 3)); // => 3

// 现在已知一个很大的元素为随机大小的整数数组
const bigRandomArray = [...Array(10000).keys()].map(num => Math.trunc(num * Math.random()));

// 怎样使用 Math.max 获取 bigRandomArray 中的最大值呢?Math.max 接受的是多参数而不是数组参数啊!
// 思考下面的写法
console.log(Math.max.apply(null, bigRandomArray)); // => 9936

可以上 ES6 的话就简单了,使用扩展运算符即可,优雅简洁。

console.log(Math.max(...bigRandomArray));
bind

bind 和上面两个用途差别还是比较大,如同字面意思(绑定),是用来绑定 this 指向的,返回一个原函数被绑定 this 后的新函数。一个简单的例子:

const person = {
    name: "YuTengjing",
    age: 22,
};

function introduce() {
    console.log(`Hello everyone! My name is ${this.name}. I"m ${this.age} years old.`);
}

const myFriend = { name: "dongdong", age: 21 };
person.introduce = introduce.bind(myFriend);

// person.introduce 的 this 已经被绑定到 myFriend 上了
console.log(person.introduce()); // => Hello everyone! My name is dongdong. I"m 21 years old.
console.log(person.introduce.call(person)); // => Hello everyone! My name is dongdong. I"m 21 years old. 

bind 的函数签名是 func.bind(thisArg, arg1, arg2, ...)。春招的时候被问过 bind 的第二个参数是干嘛用的,因为我之前写代码本身不怎么用这几个 API,用的时候我也只用第一个参数,所以当时面试的时候被问这个问题的时候我还是愣了一下。不过其实如果可以传多个参数的话,猜也能猜得出来是干嘛用的,我当时就猜对了φ(* ̄0 ̄)。

学以致用

我们学习知识的时候不能只是停留在理解层面,需要去思考它们有什么用,应用场景有哪些。这样的话,当你处在这种场景中,你就能很自然的想出解决方案。

多参函数转换为单个数组参数调用

javascript 中有很多 API 是接受多个参数的比如之前提过的 Math.max,还有很多例如 Math.min,Array.prototype.push 等它们都是接受多个参数的 API,但是有时候我们只有多个参数构成的数组,而且可能还特别大,这个时候就可以利用 apply 巧妙的来转换。

下面是利用 apply 来巧妙的合并数组:

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]

但是,其实用 ES6 可以非常的简洁:

arr1.push(...arr2);

所以,忘了这种用法吧( ̄︶ ̄)↗ 。

将类数组转换为数组

JavaScript类型化数组是一种类似数组的对象,它们有数组的一些属性,但是如果你用 Array.isArray() 去测试会返回 false,常见的像 arguments,NodeList 等。

function testArrayLike() {
    // 有 length 属性没有 slice 属性
    console.log(arguments.length); // => 3
    console.log(arguments.slice); // => undefined

    // 类数组不是数组
    console.log(Array.isArray(arguments)); // => false
    console.log(arguments); // => { [Iterator]  0: "a", 1: "b", 2: "c", [Symbol(Symbol.iterator)]: [λ: values] } 
    
    const array = Array.prototype.slice.call(arguments);
    console.log(Array.isArray(array)); // => true
    console.log(array); // => [ "a", "b", "c" ]
}

testArrayLike("a", "b", "c");

其实 把 slice 换成 concat,splice 等其它 API 也是可以的。思考:为什么通过 Array.prototype.slice.call(arrayLike) 可以转换类数组为数组?

我没有研究过 slice 的具体实现,猜测是下面这样的:

Array.prototype.mySlice = function(start=0, end) {
    const array = this;
    const end = end === undefined ? array.length : end;
    
    const resultArray = [];
    if (array.length === 0) return resultArray;
    for (let index = start; index < end; index++) {
        resultArray.push(array[index]);
    }
    return resultArray;
}

我想 slice 内部实现可能就是会像我上面的代码一样只需要一个 length 属性,遍历元素返回新数组,所以调用 slice 时将其 this 指向类数组能正常工作。

其实,这个用法也可以忘了,用 ES6 来转换不造多简单,ES6 大法好

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105146.html

相关文章

  • javascript基础this

    摘要:出现箭头函数的时候,指向为定义时的上下文对象而非指向时,并且不能被改变首先我们先看一个例子由上面的例子我们可以看出来此时指针在用改变了之后指向的依然是全局对象非严格浏览器环境中是而非。 javascript基础之this指针 越往后面学越发现基础的重要性,所以打算重新过一遍基础,之后出几个vue和react的实战教程。ok,严归正传。 首先什么是this this是执行上下文创建时确定...

    Zoom 评论0 收藏0
  • JS系列call & apply & bind

    摘要:参考链接在中,和是对象自带的三个方法,都是为了改变函数体内部的指向。返回值是函数方法不会立即执行,而是返回一个改变了上下文后的函数。而原函数中的并没有被改变,依旧指向全局对象。原因是,在中,多次是无效的。 参考链接:https://juejin.im/post/59bfe8... 在JavaScript中,call、apply和bind是Function对象自带的三个方法,都是为了改变...

    xiaochao 评论0 收藏0
  • call,apply and bind in JavaScript

    摘要:文章尽量使用大量实例进行讲解,它们的使用场景。在严格模式下,函数被调用后,里面的默认是后面通过调用函数上的和方法,该变指向,函数里面的指向。利用,可以传入外层的上下文。同样适用的还有,里面的对象,它也是一种类数组对象。 call,apply and bind in JavaScript 在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个...

    JohnLui 评论0 收藏0
  • JavaScript深入bind的模拟实现

    摘要:也就是说当返回的函数作为构造函数的时候,时指定的值会失效,但传入的参数依然生效。构造函数效果的优化实现但是在这个写法中,我们直接将,我们直接修改的时候,也会直接修改函数的。 JavaScript深入系列第十一篇,通过bind函数的模拟实现,带大家真正了解bind的特性 bind 一句话介绍 bind: bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数...

    FingerLiu 评论0 收藏0
  • JavaScript进阶模拟call,applybind

    摘要:模拟和模拟一样,现摘抄下面的代码添加一个返回值对象然后我们定义一个函数,如果执行下面的代码能够返回和函数一样的值,就达到我们的目的。 原文:https://zhehuaxuan.github.io/... 作者:zhehuaxuan 目的 本文主要用于理解和掌握call,apply和bind的使用和原理,本文适用于对它们的用法不是很熟悉,或者想搞清楚它们原理的童鞋。 好,那我们开始...

    CoderBear 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<