铁汁们这个问题最好自己动手写一遍,我以为我会了结果面试被问懵逼了...
Call
需求
使用指定的this调用函数
思路
变更this
首先要知道this是怎么指的,冴羽大大的这篇文章我觉得讲得很不错
简单一点,可以大概理解为this指向了函数调用者,[[调用者]].function
那么思路就是,让指定的object成为函数的调用者
处理参数
call接受任意个参数,从第二个开始作为被调用函数的参数。这是和apply不一样的地方。
接受任意参数有两种思路:
- 处理arguments (原生js)
拼出一个字符串,像这样
"f(arguments[1], ...,arguments[n])"
然后用eval执行
- 解构运算符 (ES6)
call(target, ...args)
原生js处理的思路并不是那么容易想到,但是见过了以后就会很轻松想到这上面。
说起来,拼字符串然后eval一下来实现code generation也是一个常规操作哈
实现
Function.prototype.myCall = function (thisTarger) {
// 接受null
var thisTarget = thisTarget || window
thisTarger.f = this // 关键步骤
args = []
for (var i = 1; i < arguments.length; ++i) {
args.push('arguments[' + i + ']')
}
// 数组这里会隐式转换成逗号分隔的形式
var result = eval('thisTarger.f(' + args + ')')
delete thisTarger.f
return result // 记得返回值
}
apply
apply只接受两个参数,第二个是原函数参数数组,所以我们就把从arguments拼接变成从给的数组拼接就完事了。
Function.prototype.myApply = function (thisTarget, arr=[]) {
var thisTarget = thisTarget || window
thisTarget.f = this
var args = []
var result
// 只有这里变了
for (var i = 0; i < arr.length; ++i) {
args.push('arr[' + i + ']')
}
result = eval('thisTarget.f(' + args + ')')
delete thisTarger.f
return result
}
bind
需求
- bind返回一个函数,该函数执行时使用给定的this
- bind可以接受参数,在调用时绑定函数的参数列表中
坑
返回一个函数,那就是说可以被new,如果被new的话,this不应该指向给定的this,因为如果new的this指向会被改变的话,整个晋西北就乱成一锅粥了。
思路
bind会返回一个函数,方便表达就叫boundF吧。boundF一定是我们在bind内部声明的函数,所以整个过程涉及到三个函数不要搞混了。
- 原函数
- bind
- bind中声明的boundF
调用使用指定this
用call/apply就好了
预绑定参数
在bind缓存arguments,用闭包绑到boundF中,和boundF接受的参数合并
处理new
new的表现
- this指向了boundF正在构造的新对象
- boundF的prototype在新对象的原型链上
解决思路
我们主要需要做两件事:
- 判断是不是被new了,是的话把this还给新对象
即判断boundF是不是在新对象(this)的原型链上
- 保证new出来的对象原型链和原函数一致而不是和boundF一致
把原函数的原型链搞到boundF的prototype上即可
实现
整活!实现中有一些细节问题要考虑,所以强烈推荐大家自己写一遍!
Function.prototype.myBind = function (thisTarget) {
// this指向原函数
var that = this
// 构造闭包,因为arguments在boundF中会被覆盖访问不到了
var args = Array.prototype.slice.apply(arguments, 1)
var boundF = function () {
// 这里的arguments是boundF的arguments, 也就是实际调用时的参数
var bindArgs = Array.prototype.slice.call(arguments)
// 这里的this有两种:
// boundF (普通调用)
// newObj (new)
// 注意boundF instanceof boundF是false的
return that.apply(this instanceof boundF ? this : thisTarget, args.concat(bindArgs))
}
// 切换原型链
boundF.prototype = Object.create(this)
return boundF
}
Reference
github.com/mqyqingfeng… github.com/mqyqingfeng…
这是【恶补系列】的第二篇文章,给坚持下来的自己点个赞(逃