call,apply,bind的区别

大兔子大兔子 提交于 2020-03-17 12:38:03

基本语法

调用语法:

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

返回值:
call/apply:fun执行的结果
bind:返回fun的拷贝,并拥有指定的this值和初始参数

参数:
thisArg(可选):

1、fun的this指向thisArg对象
2、非严格模式下:thisArg指定为null,undefined,fun中的this指向window对象.
3、严格模式下:fun的this为undefined
4、值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean

param1,param2(可选): 传给fun的参数。

1、如果param不传或为 null/undefined,则表示不需要传入任何参数.
2、apply第二个参数为数组,数组内的值为传给fun的参数,就算只有一个参数也需要使用[ ]。

注意点

  • 调用call/apply/bind的必须是个函数
  • call与apply的唯一区别就是:
    • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
    • call从第2~n的参数都是传给fun的。
  • call/apply与bind的区别在于前者返回fun的返回值,并立刻调用,后面返回fun的拷贝,调用由编码人员决定。

基本代码演示

	    //后面this所指代的对象类
        function Person(name) {
            this.name=name;
        }

        //测试call、bind、apply的函数
        function TestFunc(age) {
            console.log("My name is "+this.name+",and I am "+age+" years old.");
            return "This is the return value";
        }

        //用作thisArg参数
        var person = new Person("翠花");

        console.log(TestFunc.call(person,"15"));

        console.log(TestFunc.apply(person,["16"]));

        var FuncPointer = TestFunc.bind(person,"17");
        FuncPointer();

在这里插入图片描述

call/apply/bind的核心理念:借用方法

生活中:

平时没时间做饭的我,周末想给孩子炖个腌笃鲜尝尝。但是没有适合的锅,而我又不想出去买。所以就问邻居借了一个锅来用,这样既达到了目的,又节省了开支,一举两得。

程序中:

A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。

使用:A.func.call(B,param1,param2…)

这就是call/apply/bind的核心理念:借用方法。
借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

call和apply的应用场景:

1、判断数据类型:

function isType(data, type) {
    const typeObj = {
        '[object String]': 'string',
        '[object Number]': 'number',
        '[object Boolean]': 'boolean',
        '[object Null]': 'null',
        '[object Undefined]': 'undefined',
        '[object Object]': 'object',
        '[object Array]': 'array',
        '[object Function]': 'function',
        '[object Date]': 'date', // Object.prototype.toString.call(new Date())
        '[object RegExp]': 'regExp',
        '[object Map]': 'map',
        '[object Set]': 'set',
        '[object HTMLDivElement]': 'dom', // document.querySelector('#app')
        '[object WeakMap]': 'weakMap',
        '[object Window]': 'window',  // Object.prototype.toString.call(window)
        '[object Error]': 'error', // new Error('1')
        '[object Arguments]': 'arguments',
    }
    let name = Object.prototype.toString.call(data) // 借用Object.prototype.toString()获取数据类型
    let typeName = typeObj[name] || '未知类型' // 匹配数据类型
    return typeName === type // 判断该数据类型是否为传入的类型
}
console.log(
    isType({}, 'object'), // true
    isType([], 'array'), // true
    isType(new Date(), 'object'), // false
    isType(new Date(), 'date'), // true
)

2、类数组借用数组的方法:
类数组因为不是真正的数组所有没有数组类型上自带的种种方法,所以我们需要去借用数组的方法。

比如借用数组的push方法:

var arrayLike = {
  0: 'OB',
  1: 'Koro1',
  length: 2
}
Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2');
console.log(arrayLike) // {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4}

3、apply获取数组最大值最小值:
apply直接传递数组做要调用方法的参数,也省一步展开数组,比如使用Math.max、Math.min来获取数组的最大值/最小值:

const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6

4、继承
ES5的继承也都是通过借用父类的构造方法来实现父类方法/属性的继承:

// 父类
function supFather(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green']; // 复杂类型
}
supFather.prototype.sayName = function (age) {
    console.log(this.name, 'age');
};
// 子类
function sub(name, age) {
    // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上
    supFather.call(this, name);
    this.age = age;
}
// 重写子类的prototype,修正constructor指向
function inheritPrototype(sonFn, fatherFn) {
    sonFn.prototype = Object.create(fatherFn.prototype); // 继承父类的属性以及方法
    sonFn.prototype.constructor = sonFn; // 修正constructor指向到继承的那个函数上
}
inheritPrototype(sub, supFather);
sub.prototype.sayAge = function () {
    console.log(this.age, 'foo');
};
// 实例化子类,可以在实例上找到属性、方法
const instance1 = new sub("OBKoro1", 24);
const instance2 = new sub("小明", 18);
instance1.colors.push('black')
console.log(instance1) // {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}
console.log(instance2) // {"name":"小明","colors":["red","blue","green"],"age":18} 

bind的应用场景:

1. 保存函数参数:
首先来看下一道经典的面试题:

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) // 依次输出:6 6 6 6 6
    }, i * 1000);
}

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。

那么如何使他输出: 1,2,3,4,5呢?

方法有很多:

  • 闭包, 保存变量
for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log('闭包:', i); // 依次输出:1 2 3 4 5
        }, i * 1000);
    }(i));
}

在这里创建了一个闭包,每次循环都会把i的最新值传进去,然后被闭包保存起来。

  • bind
for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:bind1 bind2 bind3 bind4 bind5
    }.bind(null, i), i * 1000);
}

实际上这里也用了闭包,我们知道bind会返回一个函数,这个函数也是闭包。

  • let
    用let声明i也可以输出1-5: 因为let是块级作用域,所以每次都会创建一个新的变量,所以setTimeout每次读的值都是不同的。

展示写到这,一下子太多东西我自己接受不了了。。。 以后再更新

参考文章:js基础-重新认识call,apply,bind

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!