call
和 apply
都是为了解决改变 this
的指向。作用都是相同的,只是传参的方式不同。
除了第一个参数外,call
可以接收一个参数列表,apply
只接受一个参数数组。
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])
bind
和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind
实现柯里化。
如何实现一个 bind 函数
对于实现以下几个函数,可以从几个方面思考
- 不传入第一个参数,那么默认为
window
- 改变了 this 指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除?
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
如何实现一个call函数
Function.prototype.myCall = function (context) {
var context = context || window
// 给 context 添加一个属性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 将 context 后面的参数取出来
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 删除 fn
delete context.fn
return result
}
如何实现一个apply函数
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
call/apply
作用
用来改变函数内部 this
的指向。
特点
任何函数都可以调用这两个方法,说明它们是添加在函数原型上的方法(Function.prototype
)。
console.dir(Function.prototype)
调用 call
和 apply
的函数会立即执行。
call
和 apply
的返回值就是函数的返回值。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.log(this.name);
return 'success'
}
foo.call(obj) //=> warbler
console.log(foo.call(obj)); // => success
调用 call
和 apply
指向 undefined
或者 null
,会将 this
指向 window
。
function foo() {
console.log(this)
}
foo.call(undefined)
foo.call(null)
foo.apply(undefined)
foo.apply(null)
调用 call
和 apply
指向一个值类型, 会将 this
指向由它们的构造函数创建的实例。
function foo() {
console.log(this)
}
foo.call(11)
foo.call('11')
foo.call(true)
[图片上传失败...(image-15f2a1-1666010480746)]
调用 call
和 apply
指向一个引用类型, 会将 this
指向这个对象。
我们声明了一个全局变量 name
和一个全局作用域下的函数 foo
。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.log(this.name)
}
foo() //=> 一尾流莺
这段代码很好理解,name 等价于 window.name , foo() 等价于 window.foo() ,我们打印出this.name,当前的 this 指向它的调用者 window, 也就是 window.name 得到 一尾流莺。 但是如果我想打印出 warbler 该怎么办呢?在 obj 里面再定义一个 obj.fn 么? 当然不需要, 我们只需要调用 call/apply 改变 this 的指向,指向 obj 这个对象就可以了。这个时候 this.name 等价于 obj.name ,就得到了 warbler 。
foo.call(obj) //=> warbler
foo.apply(obj) //=> warbler
call 和 apply的区别
除了传参的形式不同没什么区别。
传给fn
的参数写法不同:
-
call
接收多个参数,第一个为函数上下文也就是this
,后边参数为函数本身的参数。 -
apply
接收两个参数,第一个参数为函数上下文this
,第二个参数为函数参数只不过是通过一个 数组 的形式传入的。
只要记住 apply
是以 a
开头,它传给 fun
的参数是 Array
,也是以 a
开头的,就可以很好的分别这两个函数了。
手写call/apply
手写call
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
function foo() {
console.dir(this);
return 'success'
}
/**
* Object()方法
* 如果传入的是值类型 会返回对应类型的构造函数创建的实例
* 如果传入的是对象 返回对象本身
* 如果传入 undefined 或者 null 会返回空对象
*/
Function.prototype._call = function(ctx, ...args) {
// 判断上下文类型 如果是undefined或者 null 指向window
// 否则使用 Object() 将上下文包装成对象
const o = ctx == undefined ? window : Object(ctx)
// 如何把函数foo的this 指向 ctx这个上下文呢
// 把函数foo赋值给对象o的一个属性 用这个对象o去调用foo this就指向了这个对象o
// 下面的this就是调用_call的函数foo 我们把this给对象o的属性fn 就是把函数foo赋值给了o.fn
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
o[key] = this
// 立即执行一次
const result = o[key](...args)
// 删除这个属性
delete o[key]
// 把函数的返回值赋值给_call的返回值
return result
}
手写 apply
之前讲过,call
和 apply
的唯一区别就是传递参数的不同,所以我们只需要改一下对参数的处理,其它的和 call
一致就可以了。
var age = 10
var obj = {
age: 20,
}
function foo(a, b) {
console.dir(this.age + a + b);
}
// 只需要把第二个参数改成数组形式就可以了。
Function.prototype._apply = function(ctx, array = []) {
const o = ctx == undefined ? window : Object(ctx)
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
o[key] = this
const result = o[key](...array)
delete o[key]
return result
}
foo(3, 4) // => 17
foo._apply(obj, [3, 4]) //=> 27
bind
作用
也是用来改变函数内部 this
的指向。
bind 和 call/apply 的区别
是否立刻执行:
-
call/apply
改变了函数的this
上下文后 马上 执行该函数。 -
bind
则是返回改变了上下文后的函数, 不执行该函数 。
返回值的区别:
-
call/apply
返回fun
的执行结果。 -
bind
返回fun
的拷贝,并指定了fun
的this
指向,保存了fun
的参数。
var name = '一尾流莺'
var obj = {
name: 'warbler',
}
// this 指向调用者document
document.onclick = function() {
console.dir(this); // => #document
}
// this 指向 obj
document.onclick = function() {
console.dir(this); // => #Object{name:'warbler}
}.bind(obj)
手写bind
Function.prototype._bind = function(ctx, ...args) {
// 下面的this就是调用_bind的函数,保存给_self
const _self = this
// bind 要返回一个函数, 就不会立即执行了
const newFn = function(...rest) {
// 调用 call 修改 this 指向
return _self.call(ctx, ...args, ...rest)
}
if (_self.prototype) {
// 复制源函数的prototype给newFn 一些情况下函数没有prototype,比如箭头函数
newFn.prototype = Object.create(_self.prototype);
}
return newFn
}