1 动机
最近在面试,面试官让我写写apply call bind,没写出来就很尴尬。网上写法比较多,自己整理然后写了一些思路比较清晰的写法。
2 call
用法:
function.call(thisArg, arg1, arg2, ...)
其中thisArg是要绑定的this的值,在非严格模式下如果传入的是undefined或者null就会指向全局对象。arg1就是传入的参数。
返回值:
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
代码:
基于此,设计了如下的call函数的简单版本,尽量不使用es6里面的语法,分别就是解析传入的this为context,然后将函数挂载在这个context对象上,这样函数里面遇到this相关的取值就会从context上面去找了
Function.prototype.call2 = function (context) {
// context就是传入的thisArg
context = context || window
// this就是调用的function
context.fn = this
// 收集参数
const arr = []
for (let i=1; i<arguments.length; i++) {
arr.push(arguments[i])
}
// 执行函数
const result = eval('context.fn('+arr+')')
delete context.fn
return result
}
但是这样还是不够,另外考虑context可能不是对象,还有就是context的fn被占用的情况。
Function.prototype.call2 = function (context) {
context = context || window
if(typeof(context) !== 'object') {
context = Object(context)
}
let rand = 'fn'+Math.ceil(Math.random()*10000)
while(context[rand] === undefined){
// this就是调用的function
context[rand] = this
}
// 收集参数
const arr = []
for (let i=1; i<arguments.length; i++) {
arr.push(arguments[i])
}
// 执行函数
const result = eval('context[rand]('+arr+')')
delete context.fn
return result
}
3 apply
apply是类似的,直接上代码
Function.prototype.apply2 = function(context,args){
context = context || window
if(typeof(context) !== Object) {
context = Object(context)
}
// 新加fn
let randomNumber = Math.ceil(Math.random)
while(context['fn'+randomNumber] === undefined){
context['fn'+randomNumber] = this
}
const result = eval("context['fn'+randomNumber]("+args+")")
delete context['fn'+randomNumber]
return result
}
4 bind
根据文档,我们需要实现这几个功能
- 接收若干个参数,其中第一个是要绑定的this对象,后面的部分接收的参数
- 返回一个函数,接收的参数作为剩余部分的传入参数
- bind返回的函数可以作为一个构造函数使用
前2个目标比较简单,先进行实现。整体的流程就是,读取传入的要绑定的this,在这里命名为context对象,读取args参数,然后返回一个函数bindFunction。
在函数bindFunction,读取剩余参数为args2,然后直接执行函数,获取result,返回。
Function.prototype.bind2 = function(context) {
context = context || window
if(typeof(context) !== 'object') {
context = Object(context)
}
let rand = 'fn'+Math.ceil(Math.random()*10000)
while(context[rand] === undefined){
context[rand] = this
}
const args = []
for(let i=1; i<arguments.length; i++){
args.push(arguments[i])
}
const bindFunction = function() {
const context2 = context
const rand2 = rand
const args2 = []
for(let j=0; j<arguments.length; j++) {
args2.push(arguments[j])
}
result = eval('context2[rand2]('+args.concat(args2)+')')
return result
}
return bindFunction
}
第三个目标要考虑两个部分,一个是作为构造函数的话,需要继承原型链,这里采取一个中间函数newFun来继承
const newFun = function(){}
newFun.prototype = context[rand].prototype
bindFunction.prototype = new newFun()
新加一个判断,当作为构造函数的时候,在运行语句里面加一个new
// 作为构造函数调用
if(new.target !== undefined){
result = eval('new context2[rand2]('+args.concat(args2)+')')
// 作为普通函数调用
} else{
result = eval('context2[rand2]('+args.concat(args2)+')')
}
那么完整的代码就是
Function.prototype.bind2 = function(context) {
context = context || window
if(typeof(context) !== 'object') {
context = Object(context)
}
let rand = 'fn'+Math.ceil(Math.random()*10000)
while(context[rand] === undefined){
// this就是调用的function
context[rand] = this
}
const args = []
for(let i=1; i<arguments.length; i++){
args.push(arguments[i])
}
const bindFunction = function() {
const context2 = context
const rand2 = rand
const args2 = []
for(let j=0; j<arguments.length; j++) {
args2.push(arguments[j])
}
// 作为构造函数调用
if(new.target !== undefined){
result = eval('new context2[rand2]('+args.concat(args2)+')')
// 作为普通函数调用
} else{
result = eval('context2[rand2]('+args.concat(args2)+')')
}
return result
}
const newFun = function(){}
newFun.prototype = context[rand].prototype
bindFunction.prototype = new newFun()
return bindFunction
}
最后补几个测试用例
function add(a,b,c,d){
console.log(this.age)
return {sum : a+b+c+d}
}
const bd = {
age:100861
}
const func1 = add.bind2(bd)
console.log(func1(3,4,88,8))
const b = new add(1,2,3,4)
console.log(b)
const func2 = add.bind(bd)
const c = new add(1,2,3,4)
console.log(c)
function sum1(a,b){
this.sum = a+b
}
sum1.prototype.ssum = function(){
console.log(this.sum,"jj")
}
// const math1 = new sum1(3,5)
const math2 = {name:10086}
const mathSum = sum1.bind2(math2)
const obj = new mathSum(2,3)
obj.ssum()
console.log(obj)