关于手写 call、apply、bind 的一些个人见解及具体代码和注释,有错误的地方往评论以纠正
call
Function.prototype._call = function (uThis, ...args) {
let _this = uThis === undefined || uThis === null ? globalThis : Object(uThis);
const key = Symbol()
_this[key] = this
const res = _this[key](...args)
Reflect.deleteProperty(_this, key)
return res
}
测试代码(通用),所有测试均在 node 环境下
obj = {
name: '小王',
age: 18
}
function test (doingSomething, makeSomething) {
console.log(this);
console.log(this.name + ' ' + this.age + ' ' + doingSomething + ' ' + makeSomething);
}
function test2 () {
console.log(arguments);
}
测试
test2._call({})
test2.call({})
test2._call(null, '')
test2.call(null, '')
test2._call(obj, 'eating')
test2.call(obj, 'eating')
apply
使用 apply 传入的第二个参数需要为数组,但存在几种特殊的的参数情况:
-
{} 对象有内容、无内容都可,通过 new 得到的也可 -
undefined -
null 注意点:有一些特殊情况第二个参数的值会发生转变 传入参数 apply处理 调用函数中
{length:2} =>[undefined,undefined] =>[Arguments]{'0':undefined,length;1}
undefined =>[] =>[Arguments]{}
null =>[] =>[Arguments]{}
new String('abc') =>['a','b','c'] =>[Arguments]{ '0': 'a', '1': 'd', '2': 'v' }
new Number、Set、Map =>[] =>[Arguments]{}
总结:
- 类似数组的对象(一些通过 new 得到的对象加了 length 属性就属于类似数组的对象)、通过 new String() 得到的对象都会做转换
- 只要是对象一律符合传入参数的规则(通过 new 得到的也一样),一律转为空数组
- undefined、null一律转为空数组
代码
Function.prototype._apply = function (uThis, args = []) {
let _this = uThis === undefined || uThis === null ? globalThis : Object(uThis);
function isObject (args) {
return typeof args === 'object' && args !== null
}
function isLikeArray (o) {
if (isObject(o) && Number.isFinite(o.length) && o.length >= 0 && o.length < 4294967296) {
return true
} else {
return false
}
}
(function () {
if (isLikeArray(args)) {
args = Array.from(args)
}
else if (isObject(args)) {
args = []
}
else if (!Array.isArray(args)) {
throw new TypeError('second argument to apply must be an array')
}
})(args)
const key = Symbol()
_this[key] = this
const res = _this[key](...args);
Reflect.deleteProperty(_this, key)
return res
}
测试
test1._apply(obj)
test1.apply(obj)
test2._apply(obj, { length: 2 })
test2.apply(obj, { length: 2 })
test._apply(obj, '')
test.apply(obj, '')
test._apply(obj, 1)
test.apply(obj, 1)
test._apply(obj, [])
test.apply(obj, [])
test._apply(obj, ['Eating'])
test.apply(obj, ['Eating'])
bind
Function.prototype._bind = function (uThis, ...args) {
const fn = this
uThis = uThis === undefined || uThis === null ? globalThis : Object(uThis);
const newFn = function (...newargs) {
const byNew = this instanceof newFn
const _this = byNew ? this : Object(uThis)
return fn.call(_this, ...args, ...newargs)
}
if (fn.prototype) {
newFn.prototype = Object.create(fn.prototype);
}
return newFn
}
测试
test2._bind()()
test2.bind()()
test2._bind(null, 'eating')('make rice')
test2.bind(null, 'eating')('make rice')
console.log(new (student._bind(obj))('小王', 18));
console.log(new (student.bind(obj))('小王', 18));
参考文章
|