想要实现实现一个方法,必然要先了解它。其实call、apply、bind都是咱们平常开发中比较经常使用的一些方法。他们都有一个共同的做用:改变this指向。数组
call和apply本质上来讲并无什么区别,他们的做用是同样,区别仅在于param的传参方式。call接受连续传参,apply接受数组传参。markdown
bind和call\apply的区别想对来讲就大一些,call\apply都是当即执行的,它们的返回结果就是fn的执行结果;而bind并不当即执行,它的返回结果是fn的拷贝,改变this指向后不会当即执行,须要自行调用这个新函数。app
/** @params: targetThis (可选) fn的this的目标指向,默认指向window @params: param (可选) 传入fn的参数 */
fn.call(targetThis, param1, param2, param3 ...)
fn.apply(targetThis, [param1, param2, param3 ...])
fn.bind(targetThis, param1, param2, param3..)
(PS: 为了统一,下面文章都用fn、targetThis、params来叙述
复制代码
call的实现函数
call的做用只说一句改变this指向就太笼统了,它的执行能够大体拆分为以下几个步骤:ui
Function.prototype._call = function (context = window) {
// 首先了解各个参数的值
console.log('this', this) // foo
console.log('context', context) // target {value: 1}
console.log('arguments', arguments) // { {value: 1}, 'param1', 'param2'}
let _context = context // 第一个参数不传默认为window
_context.fn = this // 将foo做为target的私有方法, this改变
const args = [...arguments].slice(1) // 截取下标从1开始的参数 {'param1', 'param2'}
const result = _context.fn(...args) // 当即执行
delete _context.fn // 删除fn 不改变target
return result
}
// eg
let target = {
value: 1
}
function foo(param1, param2) {
console.log(param1)
console.log(param2)
console.log(this.value)
}
foo._call(target, 'param1', 'param2') // param1 param2 1
复制代码
apply的实现与call基本上是一致的,区别仅在于params的形式上,apply的参数为数组形式。this
Function.prototype._apply = function (context = window, arr) {
// 首先了解各个参数的值
console.log('this', this) // fn
console.log('context', context) // target {value: 1}
let _context = context// 第一个参数不传默认为window
_context.fn = this // 将fn做为target的私有方法, this改变
const result = arr.length ? _context.fn(...arr) : _context.fn() // 判断是否传入arr
delete _context.fn // 删除fn 不改变target
return result
}
// eg
let target = {
value: 1
}
function fn(param1, param2) {
console.log(param1)
console.log(param2)
console.log(this.value)
}
fn._apply(target, ['param1', 'param2']) // param1 param2 1
复制代码
因为ES5内置的Function.prototype.bind(..)实现过于复杂,这里咱们借助call/apply来实现bind,在实现bind以前咱们明确几个重点:spa
绑定函数也可使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。prototype
(划重点加感叹号)code
咱们来解读一下, “绑定函数也可使用new运算构造 ” 就是说会在new中使用硬绑定,而new绑定的优先级又高于硬绑定,咱们须要对有new的状况作特殊处理;“前置参数仍会提供给模拟函数”就是说,在new中使用硬绑定函数能够预先设置一些参数,在使用new进行初始化时能够传入其余参数,是柯里化的一种。orm
Function.prototype._bind = function (context = window) {
// 首先搞清楚各个参数的含义
console.log('this', this) // foo
console.log('context', context) // target {value: 1}
console.log('argument', arguments) // { {value1: 1}, 'param1', 'param2'}
const _this = this // 保存this
// 这里arguments为foo的arguments
const args = [...arguments].slice(1) // 截取下边为1开始的参数 ['param1', 'param2']
var fn = function () {
const bindArgs = [...arguments] // 这里的arguments为fn的
// 做为构造函数时,this指向实例fn this instanceof fn 返回true
// 不做为构造函数时, this指向window, this instanceof fn 返回false 须要将this指向改变为context
// [...args, ...bindArgs] 合并预先传入的参数和new 实例化时传入的参数
return _this.apply( this instanceof fn ? this : context , [...args, ...bindArgs ])
}
// 修改绑定函数的prototype为foo的prototype,继承foo的属性
// 建立一个空对象,让空对象__proto__指向_this.prototype, 作到修改fn的prototye不会影响到foo
fn.prototype = Object.create(_this.prototype)
return fn
}
// eg
let target = {
value: 1
}
function foo(param1, param2, param3) {
console.log(param1)
console.log(param2)
console.log(param3)
console.log(this.value)
}
const bindFn = foo._bind(target, 'param1', 'param2')
bindFn() // param1 param2 undefined 1
const newBindFn = new bindFn('param3') // param1 param2 param3 undefined
复制代码