前端学习JScall、apply、bind
Junscall、apply、bind
前言
在前面已经了解了this,这篇文章讨论如何改变this的指向。
何为 this?
this 是用来指代这个函数当前的运行环境,可以这么理解:
const me = { name: '111' } const you = { name: '222' }
function sayName(context) { console.log(context.name) }
sayName(me) sayName(you)
|
我们可以把传入的执行环境简化,这时候就使用到了this
const me = { name: '111' } const you = { name: '222' }
function sayName() { console.log(this.name) }
sayName.call(me) sayName.call(you)
|
这里的call的参数就相当于传入上面的context
再看一个:
const me = { name: '111', sayName } const you = { name: '222', sayName }
function sayName() { console.log(this.name) }
me.sayName() you.sayName() sayName()
|
这样就可以理解了,直接调用函数就是在window下调用,this指代的就是window对象
那么理解了this之后,我们如何改变this指代的运行环境呢?这里就需要call、apply、bind了
他们有什么用
我们已经知道了call等可以改变this指向,那么改变这个有啥用啊?还是举个例子来看:
const me = { name: '111', say: function () { console.log(this.name) }, }
const you = { name: '222' }
me.say()
|
在上面这个代码里,我想输出you的name,也就是借用me的say方法,怎么做?可以直接把这个函数提取成公共函数,然后再去给you添加这个属性,但是这也有问题,我不希望you多出来一个属性 ☝️,那用完了再删掉不就行,比如:
const me = { name: '111', say, }
function say() { console.log(this.name) }
const you = { name: '222', }
me.say()
you.say = say you.say() delete you.say
|
这样当然可以,而且实际上这就是 call 做的 😆
同理,如果这个方法只在 me 上,也可以像这样去做,但是每次都这样写有点繁琐的,而且实际的 call 也有一些其他的处理,这时候我们就可以直接用 call 来处理这种情况
const me = { name: '111', say: function () { console.log(this.name) }, }
const you = { name: '222' }
me.say() me.say.call(you)
|
这样不就能够理解了,call 传入的you就是say的this
如果有参数呢?那就直接传参数呗 🤣👉🏻
const me = { name: '111', say: function (state, text) { console.log(`${this.name} ${state}地说: ${text}`) }, }
const you = { name: '222' }
me.say('生气😡', '不许🐶叫') me.say.call(you, '开心😆', '我就叫')
|
这样的话就可以简单理解了 call 了,实际上 apply 和 bind 就是 call 的变种,apply 是传入数组,bind 是返回一个新函数
基本使用
语法
fun.call(thisArg, param1, param2, ...) fun.apply(thisArg, [param1, param2, ...]) fun.bind(thisArg, param1, param2, ...)
|
参数
thisArg?: 可选,调用函数时的this值
- fun 的
this指向他
- 非严格模式下,如果
thisArg是null或undefined,this指向全局对象
- 严格模式下,
thisArg传入null或undefined,this就是null或undefined
- 如果
thisArg是原始值,this就是这个原始值的包装对象
param1, param2, ...: 可选,传入的参数
- call 和 bind: 除了第一个参数是
thisArg,后面的参数都是传入的参数
- apply: 除了第一个参数是
thisArg,第二个参数是数组,数组里面是传入的参数
应用场景
数据类型判断
看过数据类型判断八股的肯定都知道有一个方法最精准:Object.prototype.toString.call😈,他就用到了 call🤓。为什么使用 call 呢?因为其他的数据类型可能改写了原型上的toString方法。
let oToString = Object.prototype.toString
const arr = [ Object.prototype.toString, Array.prototype.toString, Function.prototype.toString, String.prototype.toString, Number.prototype.toString, Boolean.prototype.toString, ]
arr.forEach(item => { console.log(item === oToString) })
|
function isType(data, type) { const typeMap = { '[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 RegExp]': 'regExp', '[object Map]': 'map', '[object Set]': 'set', '[object HTMLDivElement]': 'dom', '[object WeakMap]': 'weakMap', '[object Window]': 'window', '[object Error]': 'error', '[object Arguments]': 'arguments', } const dataType = typeMap[Object.prototype.toString.call(data)] || '未知类型' return dataType === type }
console.log(isType([], 'array')) console.log(isType({}, 'object'))
|
手写
call
先来想想思路,目的是借用方法,那么我可以把这个方法作为这个对象的一个属性,然后调用,之后再删除这个属性。
那么可以这么写:
Function.prototype.myCall = function (context, ...args) { context.fn = this const res = context.fn(...args) delete context.fn return res }
|
当然这个肯定是不完善的,想想上面的基本使用,比如thisArg为原始值、null、undefined,以及 fn 这个名字可能会被占用,可以写出如下的修正版本
Function.prototype.myCall = function (context, ...args) { if (context === null || context === undefined) { context = window } else { context = Object(context) } const fn = Symbol('fn') context[fn] = this const res = context[fn](...args) delete context[fn] return res }
|
手写 apply
实际的 apply 的第二个参数是可以接受类数组的,这里就不考虑这个情况了
Function.prototype.myApply = function (context) { if (context === null || context === undefined) { context = window } else { context = Object(context) }
const args = arguments[1] const fn = Symbol('fn') context[fn] = this let res = null if (args) { if (!Array.isArray(args)) { throw new Error('你传入的第二个参数不是数组') } else { const arr = Array.from(args) res = context[fn](...arr) } } else { res = context[fn]() } delete context[fn] return res }
|
手写 bind
bind 需要返回一个函数
TODO,涉及到了原型还有 new,这里先不写了
参考链接