javascript-call、apply和bind方法的理解

96
家里有棵核桃树
2017.12.14 13:04* 字数 1359

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); // Cat
animal.showName.apply(cat1); // Cat
animal.showName.call(cat2); // cat2
animal.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(); // user
let getUserName1 = user.getName; // this指向全局作用域
getUserName1(); // window
let 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=5
add1(5); // add1:sum=10
let add2 = add.bind("add2", "hello");
add2(); // add2:sum=0hello
add2(" 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. 相关知识

javascript/jquey
Gupao