本篇文章通过代码示例,给大家深入解析一下如何实现 call、apply 和 bind,至于这几个方法的具体用法,MDN 或者站内的文章已经描述得很清楚,这里不再赘述。
|
本篇文章通过代码示例,给大家深入解析一下如何实现 call、apply 和 bind,至于这几个方法的具体用法,MDN 或者站内的文章已经描述得很清楚,这里不再赘述。
手写实现 callES3 版本 Function.prototype.myCall = function(thisArg){
if(typeof this != 'function'){
throw new Error('The caller must be a function')
}
if(thisArg === undefined || thisArg === null){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
var args = []
for(var i = 1;i < arguments.length;i ++){
args.push('arguments[' + i + ']')
}
thisArg.fn = this
var res = eval('thisArg.fn(' + args + ')')
delete thisArg.fn
return res
}ES6 版本 Function.prototype.myCall = function(thisArg,...args){
if(typeof this != 'function'){
throw new Error('The caller must be a function')
}
if(thisArg === undefined || thisArg === null){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
thisArg.fn = this
const res = thisArg.fn(...args)
delete thisArg.fn
return res
}通过 实现要点
手写实现 applyapply 的用法和 call 很类似,因此实现也很类似。需要注意的区别是,call 在接受一个 thisArg 参数之后还可以接收多个参数(即接受的是参数列表),而 apply 在接收一个 thisArg 参数之后,通常第二个参数是一个数组或者类数组对象: fn.call(thisArg,arg1,arg2,...) fn.apply(thisArg,[arg1,arg2,...]) 如果第二个参数传的是 null 或者 undefined,那么相当于是整体只传了 thisArg 参数。 ES3 版本 Function.prototype.myApply = function(thisArg,args){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
if(thisArg === null || thisArg === undefined){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
if(args === null || args === undefined){
args = []
} else if(!Array.isArray(args)){
throw new Error('CreateListFromArrayLike called on non-object')
}
var _args = []
for(var i = 0;i < args.length;i ++){
_args.push('args[' + i + ']')
}
thisArg.fn = this
var res = _args.length ? eval('thisArg.fn(' + _args + ')'):thisArg.fn()
delete thisArg.fn
return res
}ES6 版本 Function.prototype.myApply = function(thisArg,args){
if(typeof thisArg != 'function'){
throw new Error('the caller must be a function')
}
if(thisArg === null || thisArg === undefined){
thisArg = globalThis
} else {
thisArg = Object(thisArg)
}
if(args === null || args === undefined){
args = []
}
// 如果传入的不是数组,仿照 apply 抛出错误
else if(!Array.isArray(args)){
throw new Error('CreateListFromArrayLike called on non-object')
}
thisArg.fn = this
const res = thisArg.fn(...args)
delete thisArg.fn
return res
}实现要点 基本上和 call 的实现是差不多的,只是我们需要检查第二个参数的类型。 手写实现 bind
ES3 版本 这个版本更接近 MDN 上的 polyfill 版本。 Function.prototype.myBind = function(thisArg){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
var fnToBind = this
var args1 = Array.prototype.slice.call(arguments,1)
var fnBound = function(){
// 如果是通过 new 调用
return fnToBind.apply(this instanceof fnBound ? this:thisArg,args1.concat(args2))
}
// 实例继承
var Fn = function(){}
Fn.prototype = this.prototype
fnBound.prototype = new Fn()
return fnBound
}ES6 版本 Function.prototype.myBind = function(thisArg,...args1){
if(typeof this != 'function'){
throw new Error('the caller must be a function')
}
const fnToBind = this
return function fnBound(...args2){
// 如果是通过 new 调用的
if(this instanceof fnBound){
return new fnToBind(...args1,...args2)
} else {
return fnToBind.apply(thisArg,[...args1,...args2])
}
}
}实现要点 1. 2.先看比较简单的 ES6 版本: 1). 参数获取:因为 ES6 可以使用剩余参数,所以很容易就可以获取执行原函数所需要的参数,而且也可以用展开运算符轻松合并数组。 2). 调用方式:前面说过,如果返回的新函数 fnBound 是通过 new 调用的,那么其内部的 this 会是 fnBound 构造函数的实例,而不是当初我们指定的 thisArg,因此 3.再来看比较麻烦一点的 ES3 版本: 1). 参数获取:现在我们用不了剩余参数了,所以只能在函数体内部通过 arguments 获取所有参数。对于 2). 调用方式:同样,这里也要判断 fnBound 是 new 调用还是普通调用。在 ES6 版本的实现中,如果是 new 调用 fnBound,那么直接返回 // 这里的 this 指的是 fnToBind fnBound.prototype = this.prototype 这样只是拷贝了原型引用,如果修改 // this 指的是 fnToBind fnBound.prototype = Object.create(this.prototype) 通过 // this 指的是 fnToBind
const Fn = function(){}
Fn.prototype = this.prototype
fnBound.prototype = new Fn()这是上面代码采用的方法:通过空构造函数 Fn 在 fnToBind 和 fnBound 之间建立了一个联系。如果要通过实例去访问 fnToBind 的原型上面的属性,可以沿着如下原型链查找:
更多编程相关知识,请访问:编程教学!! 以上就是一文带你深入了解实现call、apply和bind方法的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
