介绍
本文是 JavaScript 高级深入浅出系列的第七篇,将手写 apply-bind-call 与认识 arguments
正文
注意:这里的手写仅仅是为了练习函数、this 和调用关系,不会考虑一些特殊情况
1. call 函数
Function.prototype._call = function(thisArg, ...args) {
// 1. 获取到需要被执行的函数,也就是调用 _call 的实例函数
var fn = this
// 2. 对 thisArg 转为对象类型(防止类型出错)Object(xxx) 可以直接给出其包装类
// 如果是 undefined 或者 null,那么 this => window
thisArg =
typeof thisArg === 'undefined' || thisArg === null
? window
: Object(thisArg)
// 3. 将传入的 thisArg 更改 fn 的 this
thisArg.fn = fn
// 4. 执行函数(使用展开运算符),如果有返回值就直接将执行的结果返回
const res = thisArg.fn(...args)
// 5. 删除 thisArg 的多余函数
delete thisArg.fn
return res
}
测试:
function fn(username, password) {
console.log('fn 函数被执行')
console.log('this.name', this.name) // foo
console.log('username', username) // username
console.log('password', password) // password
}
const foo = {
name: 'foo',
}
fn._call(foo, 'username', 'password')
2. apply 函数
Function.prototype._apply = function(thisArg, argArr) {
var fn = this
thisArg =
typeof thisArg === 'undefined' || thisArg === null
? window
: Object(thisArg)
thisArg.fn = fn
// 只有参数是不同的,apply的第二个参数是一个数组,这里做一下空值检查
const res = thisArg.fn(...(argArr || []))
delete thisArg.fn
return res
}
3. bind 函数
Function.prototype._bind = function(thisArg, ...args) {
const fn = this
thisArg =
typeof thisArg === 'undefined' || thisArg === null
? window
: Object(thisArg)
// 返回一个函数(闭包环境,所以能访问到 thisArg)
return function(...funcArgs) {
thisArg.fn = fn
// 这一步,因为可能参数传入的数量不同,所以需要将两次传入的参数混合一下,同时 funcArgs 需要替换掉同样位置的 args 的值
const finalArgs =
funcArgs && args && funcArgs.length >= args.length
? funcArgs.map((item, index) => (item = funcArgs[index]))
: args.map((item, index) => (item = funcArgs[index] || args[index]))
// 如果 funcArgs、args 任何一个为空,那么 findArgs === undefined,所以这里做一下空值检测
const result = thisArg.fn(...(finalArgs || []))
delete thisArg.fn
return result
}
}
测试一下特殊情况
function foo(username, password) {
}
// 1. bind 传入 2 个参数,单独调用传入 0 个参数 success
const barFn = foo._bind(bar, 'alex', 'zhang')
barFn()
// 2. bind 传入 2 个参数,单独调用传入 1 个参数 success
const barFn = foo._bind(bar, 'alex', 'zhang')
barFn('alexzzz')
// 3. bind 传入 2 个参数,单独调用传入 2 个参数 success
const barFn = foo._bind(bar, 'alex222', 'zhang222')
barFn('alex', 'zhang')
// 4. bind 传入 1 个参数,单独调用传入 2 个参数 success
const barFn = foo._bind(bar, 'alex')
barFn('alexzzz', 'zhangzzz')
// 5. bind 传入 1 个参数,单独调用传入 1 个参数 success
const barFn = foo._bind(bar, 'alex')
barFn('alexzzz')
// 6. bind 传入 1 个参数,单独调用传入 0 个参数 success
const barFn = foo._bind(bar, 'alex')
barFn()
// 7. bind 传入 0 个参数,单独调用传入 2 个参数 success
const barFn = foo._bind(bar)
barFn('alexzzz', 'zhangzzz')
// 8. bind 传入 0 个参数,单独调用传入 1 个参数 success
const barFn = foo._bind(bar)
barFn('alexzzz')
// 9. bind 传入 0 个参数,单独调用传入 0 个参数 success
const barFn = foo._bind(bar)
barFn()
经测试,各种参数数量输入情况均可以
4. arguments
arguments是一个对应于传递给函数的参数的类数组(array-like)对象
- 类数组:类似数组结构,但实际上是一个对象,拥有一些数组的特性,例如 length、通过索引值来访问值等,但是并不能调用数组内置方法,例如 forEach、map 等
function foo() {
console.log('arguments is', arguments)
}
foo(1, 2) // [1, 2]
foo(1, 2, 3, 4, 5, 6) // [1, 2, 3, 4, 5, 6]
foo(1, 2, 3, 4, 5, 6, 7, 8, 9) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
常见的对于arguments的操作:
- 获取参数的长度
arguments.length - 根据索引值获取某一个参数
arguments[2]获取第 3 个参数 arguments.callee:获取当前的函数
将类数组转换为数组的方法:
// ES6 之前
var res = Array.prototype.slice.call(arguments) // res 是一个真正的数组了
// 等价于 [].slice.call(arguments)
// ES6 之后
res = Array.from(arguments)
4.1 注意
- ES6 之后不再推荐使用 arguments,推荐使用剩余参数
- 箭头函数中没有 arguments
总结
本文中,你学习了 2 个知识点
- 手写 call、apply、bind 函数,同时复习了 this 指向等知识
- 了解了 arguments 与如何将类数组转换为数组