call、apply、bind区别与使用

call、apply、bind区别与使用

1.call、apply、bind基本介绍

语法:
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中的他会死指向window对象
  3. 严格模式下,fun的this为undefined
  4. 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

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

  1. 如果param不传或者为null、undefined,则表示不需要传入任何参数
  2. apply第二个参数是数组,数组内的值为传递给fun的参数

** 调用call、apply、bind的必须是函数  **

作用:

改变函数执行时的this指向,目前所有关于他们的运用,都是基于这一点来进行的。

call/apply与bind的区别

**** 执行 ****

  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的函数,不执行该函数

**** 返回值****

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

2.call、apply、bind的核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

3.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).类数组借用数组的方法

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获取数组中最值

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

4.call、apply该用哪个?
  • 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply
  • 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
  • 参数集合已经是一个数组的情况,用apply,比如上文的获取数组最大值/最小值。

5.bind的应用场景

1).保存函数参数

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

上述代码每各1秒输出一次6,因为受js运行机制影响,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));
}
for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

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

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

2).回调函数this丢失问题

this.pageClass = new Page(this.handleMessage.bind(this)) // 绑定回调函数的this指向
or
this.pageClass = new Page(() => this.handleMessage()) // 箭头函数绑定this指向

6.手写call/apply、bind

#1.手写call思路

  1. 根据call的规则设置上下文对象,也就是this的指向。
  2. 通过设置context的属性,将函数的this指向隐式绑定到context上
  3. 通过隐式绑定执行函数并传递参数。
  4. 删除临时属性,返回函数执行结果
Function.prototype.myCall = function (context, ...arr) {
    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
    context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
    let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
    delete context[specialPrototype]; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

**** 拓展:****

  • Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。
  • 每个Symbol()返回的symbol值都是唯一的。
  • 一个symbol值能够作为对象属性的标识符。
  • _symbol 是一种基本数据类型 _
  • 可以使用symbol来作为对象属性名。
  • Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。

获取symbol方式定义的对象属性方法:

// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]

// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']

#2.手写apply思路:

  1. 传递给函数的参数处理,不太一样,其他部分跟call一样。
  2. apply接受第二个参数为类数组对象, 这里用了JavaScript权威指南中判断是否为类数组对象的方法。
Function.prototype.myApply = function (context) {
    if (context === null || context === undefined) {
        context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    // JavaScript权威指南判断是否为类数组对象
    function isArrayLike(o) {
        if (o && // o不是null、undefined等
            typeof o === 'object' && // o是对象
            isFinite(o.length) && // o.length是有限数值
            o.length >= 0 && // o.length为非负值
            o.length === Math.floor(o.length) && // o.length是整数
            o.length < 4294967296) // o.length < 2^32
            return true
        else
            return false
    }
    const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
    context[specialPrototype] = this; // 隐式绑定this指向到context上
    let args = arguments[1]; // 获取参数数组
    let result
    // 处理传进来的第二个参数
    if (args) {
        // 是否传递第二个参数
        if (!Array.isArray(args) && !isArrayLike(args)) {
            throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误');
        } else {
            args = Array.from(args) // 转为数组
            result = context[specialPrototype](...args); // 执行函数并展开数组,传递函数参数
        }
    } else {
        result = context[specialPrototype](); // 执行函数
    }
    delete context[specialPrototype]; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

#3.手写bind思路

1.拷贝源函数:
  • 通过变量储存源函数
  • 使用Object.create复制源函数的prototype给fToBind

2.返回拷贝的函数

3.调用拷贝的函数:
  • new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
  • 绑定this+传递参数
  • 返回源函数的执行结果
Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // 存储源函数以及上方的params(函数参数)
    // 对返回的函数 secondParams 二次传参
    let fToBind = function (...secondParams) {
        console.log('secondParams',secondParams,...secondParams)
        const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
        const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
        return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
    };
    fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
    return fToBind; // 返回拷贝的函数
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值