[聚合文章] js方法call、apply和bind理解

JavaScript 2017-12-19 29 阅读

1. 介绍

刚出来找前端工作的时候,最常见的面试题就是“谈谈你对call和apply的理解”,以前只知道这些名词,但是一点也不理解。随着对jquery的熟悉发现jquery源码中很多都用到了apply方法,就顺便总结了一些功能类似的call和bind方法的使用。

JavaScript中的每一个Function对象的原型上都有一个apply()方法和一个call()方法,call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。call和apply是为了动态改变this而出现的,当一个对象没有某个方法,但是其它对象有,我们可以借助call或apply用其它对象的方法来操作。
bind方法也可以用来改变当前函数执行的上下文,和call、apply不同的是bind返回对应函数,便于稍后调用而apply 、call 则是立即调用 。

1.1 相关定义:

  • call()
    语法:fun.call([thisObj[,arg1[, arg2[, [,.argN]]]]])
    定义:调用一个对象的一个方法,以另一个对象替换当前对象。
    说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
  • apply()
    语法:fun.apply([thisObj[,argArray]])
    定义:应用某一对象的一个方法,用另一个对象替换当前对象。
    说明: 如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
  • bind()
    语法:fun.bind(thisArg[, arg1[, arg2[, ...]]])
    定义:创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,可以在调用之前提供一个给定的参数序列。
    说明: 参数arg1, arg2, ...当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。bind返回由指定的this值和初始化参数改造的原函数拷贝。

1.2 差异:

  • call和apply:call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。
  • bind和call、apply:bind返回对应函数,便于稍后调用而apply 、call 则是立即调用 。

1.3 应用场景:

  • 使用原生对象的方法
  • 实现继承
  • bind方法的简单使用

2. 使用

2.1 使用原生对象的方法

(1)类数组使用Array原生方法

let arrayLike = {0: "hello", 1: "sunny", 2: "~", length: 3};let arrayLikeToArray1_1 = Array.prototype.slice.call(arrayLike); // ["hello", "sunny", "~"]let arrayLikeToArray1_2 = Array.prototype.slice.call(arrayLike, 0, 2); // ["hello", "sunny"]let arrayLikeToArray2_1 = Array.prototype.slice.apply(arrayLike); // ["hello", "sunny", "~"]let arrayLikeToArray2_2 = Array.prototype.slice.apply(arrayLike, [0, 2]); // ["hello", "sunny"]

(2)扩展Array对象属性,使类数组也可以使用这些静态方法
测试的时候只扩展了部分属性,如果有其它需求自己扩展呦~~~方法类似。

let array1 = ["hello", "sunny", "~"];Array.join = function (a, sep) {    // return Array.prototype.join.call(a, sep);    return Array.prototype.join.apply(a, [sep]);};Array.slice = function (a, start, end) {    // return Array.prototype.slice.call(a, start, end);    return Array.prototype.slice.apply(a, [start, end]);};Array.map = function (a, callback, thisArg) {    // return Array.prototype.map.call(a, callback, thisArg);    return Array.prototype.map.apply(a, [callback, thisArg]);};console.log(Array.join(array1, "-")); // hello-sunny-~console.log(Array.slice(array1, 0, 1)); // ["hello"]Array.map(array1, function (value, index) {    console.log("index=" + index + ",value=" + value);}); // index=0,value=hello index=1,value=sunny index=2,value=~

说明:用call和apply都是可以扩展的哈,注释掉的方法也是可行的。

2.2 实现继承

子类使用父类的方法,严格来讲不算是继承,只是调用其它对象的方法(该方法内的this指向使用者)。

function Animal(name) {    this.name = name || "Animal";    this.showName = function() {        console.log(this.name);    }}function Cat(name) {    this.name = name || "Cat";}let animal = new Animal();let cat1 = new Cat();let cat2 = new Cat("cat2");// 子类使用父类的方法,也可以延伸至某些对象调用其它对象的方法animal.showName.call(cat1); // Catanimal.showName.apply(cat1); // Catanimal.showName.call(cat2); // cat2animal.showName.apply(cat2); // cat2

2.3 bind方法的简单使用

说明:在实际开发中很少看到bind的使用,所以只做了两个简易的demo。对于bind的更高级用法,需要大家自己去摸索了,我的了解就仅限于这里了。
(1)创建绑定函数

window.name = "window";let user = {    name: "user",    getName: function() {        console.log(this.name);    }};user.getName(); // userlet getUserName1 = user.getName; // this指向全局作用域getUserName1(); // windowlet getUserName2 = user.getName.bind(user);getUserName2(); // user

(2)偏函数

function add() {    let sum = 0;    for (let i = 0, len = arguments.length; i < len; i++) {        sum += arguments[i];    }    console.log(this + ":sum=" + sum);}let add1 = add.bind("add1", 2, 3);add1(); // add1:sum=5add1(5); // add1:sum=10let add2 = add.bind("add2", "hello");add2(); // add2:sum=0helloadd2(" sunny"); // add2:sum=0hello sunny

3. 扩展

3.1 类数组

  • 类数组是一个对象,虽然该对象并不是由Array构造函数所创建的,但它依然呈现出数组的行为。类数组对象拥有一个特性:可以在类数组对象上应用(利用call、apply方法)数组的操作方法。
  • 在浏览器环境中,document.getElementsByTagName()语句返回的就是一个类数组对象。在function调用中,function代码内的arguments变量(保存传入的参数)也是一个类数组对象。在ECMAScript 5标准中,字符串string就是一个只读的类数组对象。

几种常见的类数组:

let obj = {0: "hello", 1: "sunny", 2: "~", length: 3};let anchor = document.getElementsByTagName("a");let msg = "hello";function test() {    console.log(arguments); // arguments}

3.2 柯里化

  • 之前看函数式编程的时候了解过一点,大概意思就是把一个有n个参数的函数变成n个只有1个参数的函数。
    柯里化的简单实现:
// 普通函数function sum1(x, y, z) {    console.log("sum1:x+y+z=" + (x + y + z));}sum1(1, 3, 4); // sum1:x+y+z=8// 柯里化后的函数function sum2(x) {    return function (y) {        return function (z) {            console.log("sum2:x+y+z=" + (x + y + z));        }    }}sum2(1)(3)(4); // sum2:x+y+z=8

3.3 偏函数

  • 偏函数,固定函数中的某一个或几个参数,返回一个新的函数,接收剩下的参数, 参数个数可能是1个,也可能是2个,甚至更多。具体示例见bind方法简单使用-demo2

4. 相关知识

  • MDN-bind、call、apply介绍
  • 注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。