1.手写 call 方法
手写 call 方法之前,我们得了解 call 的特征:
- call() -> 相当于调用了前面的函数
- this -> call 方法把前面函数的 this 指向了 call 方法的第一个参数
- call 从第二个参数开始就传进了函数里面
接下来我们来手写一下:
Function.prototype.myCall = function (ctx) {
// call 第一个值一定要是一个引用值,普通值没有意义,所以我们把普通值也转换为一个对象
ctx = ctx ? Object(ctx) : window
// 一个函数谁调用它,函数的 this 指向就指向调用者
// 保存调用者,也就是那个函数 // 示例:test.myCall({}, 1, 2)
ctx.originFn = this
// 保存参数的数组
var args = []
// 拿到 myCall 的第二个参数开始到结束的所有参数作为 test 的实参列表
for (var i = 1; i < arguments.length; i++) {
// 传 arguments[i] 字符串进去,然后后面传进 eval , eval 函数会将传入的字符串当做 JavaScript 代码进行执行
args.push('arguments[' + i + ']')
// 第二种是错误示范,方便大家理解为什么要用 eval
// args.push(arguments[i])
}
// 执行函数,并把接受的参数传进去
// 字符串 + 数组 数组会调用 toString() 转换成字符串
var ret = eval('ctx.originFn(' + args + ')')
// 这样会变成 ctx.originFn('zhangsan,lisi')
// var ret = ctx.originFn('' + args)
// 调用完后我们要删除掉这个函数(引用)
delete ctx.originFn
// 返回这个函数的返回值
return ret
}
-------------------------------------------------------------------
// 测试
function test() {
console.log(this, arguments)
}
test.myCall(
{
a: 1,
b: 2,
},
'zhangsan',
'lisi'
)
2. 手写 apply 方法
apply 方法和 call 方法差不多,接下来我们还是先来了解一下 apply 的特征:
- apply() -> 相当于调用了前面的函数
- 第二个参数只接收一个数组,后面多余的参数忽略
- 第二个参数传原始值报错,如果传 object,null,undefined,Function 不报错,但 arguments 的 length 为 0
由于判断的类型比较多,我们来自己封装一个 typeOf 函数
function typeOf(value) {
// 如果值为 null 返回 null
if (value === null) {
return 'null'
}
// ({}).toString.call(value) -> [object Object] 其实就是对象索引取值
return typeof value === 'object'
? {
'[object Object]': 'Object',
'[object Array]': 'Array',
'[object Number]': 'Number',
'[object String]': 'String',
'[object Boolean]': 'Boolean',
}[{}.toString.call(value)]
: typeof value
}
接下来我们来手写 apply:
Function.prototype.myApply = function (ctx, args) {
// apply 第一个值一定要是一个引用值,普通值没有意义,所以我们把普通值也转换为一个对象
ctx = ctx ? Object(ctx) : window
// 一个函数谁调用它,函数的 this 指向就指向调用者
// 保存调用者,也就是那个函数 // 示例:test.myApply({}, [1,2])
ctx.originFn = this
// 如果是原始值我们就报错,注意这里用的是 js 原生的 typeof
if (typeof agrs !== 'object' && typeof args !== 'function') {
throw new TypeError('CreateListFromArrayLike called on non-object')
}
// 如果没有参数或者参数是 object,null,undefined,Function 我们就直接执行函数
// 这里用的是我们自己封装的 typeOf
if (!args || typeOf(args) !== 'Array') {
return ctx.originFn()
}
var ret = eval('ctx.originFn(' + args + ')')
// 调用完后我们要删除掉这个函数(引用)
delete ctx.originFn
// 返回这个函数的返回值
return ret
---------------------------------------------------------
// 测试
function test() {
console.log(this, arguments)
}
test.myApply(
{
a: 1,
b: 2,
},
function () {}
)
}
手写 bind 方法
bind 方法会比前两个稍微难一点,所以拿到最后来讲,还是老样子,先了解 bind 方法的特征:
- bind() -> bind方法执行了,但是前面的函数不执行
- bind 会返回一个新的函数
- bind 的第一个参数会改变前面函数的 this 指向
- bind 从第二个参数开始会依次传进 test 方法里,返回的函数可以继续接着传参数,比如 test 接收两个参数,bind 方法传入一个参数,返回的函数调用接着再传一个
- new (bind方法返回的函数),this指向为 test 实例,实例应该继承原型上的属性和方法
了解完之后我们手写下 bind 方法:
Function.prototype.myBind = function (ctx) {
// 拿到 this 指向,也就是调用者
var originFn = this,
// bind 传递的参数,删掉一个参数,拿到后面的参数(第一个参数是 ctx 改变 this 指向我们不需要,我们只需要它后面的参数)
args = [].slice.call(arguments, 1),
_tempFn = function () {}
var newFn = function () {
// 新函数传递的参数
// 示例:const fn = test.bind('张三'); fn('李四');fn就是新函数,传进去的参数就是李四
var newArgs = [].slice.call(arguments)
// 如果 new bind方法返回的函数,我们就判断一下,如果是 new 的,那么就是 instanceof bind方法返回的函数,如果为 true 返回 this,否则就为 bind 方法第一个参数作为 this 指向
// 示例:new fn() fn 是什么,不就是我们的返回的 newFn 吗,所以 instanceof new Fn 判断它有没有 new,如果 new 了就直接返回 this,否则为 bind 方法第一个参数 ctx
return originFn.apply(
this instanceof newFn ? this : ctx,
args.concat(newArgs)
)
}
// 直接赋值共用一个原型不太好
// newFn.prototype = this.prototype
// 这里大家自己理解一下吧,我不知道怎么描述
_tempFn.prototype = this.prototype
newFn.prototype = new _tempFn()
// 返回新函数
return newFn
}