共计 3608 个字符,预计需要花费 10 分钟才能阅读完成。
它们有什么不同?怎么用?
call 接收多个参数,第一个为函数上下文也就是 this,后边参数为函数本身的参数。
let obj = {
name: “ 一个 ”
}
function allName(firstName, lastName) {
console.log(this)
console.log(` 我的全名是“${firstName}${this.name}${lastName}”`)
}
// 很明显此时 allName 函数是没有 name 属性的
allName(‘ 我是 ’, ‘ 前端 ’) // 我的全名是“我是前端”this 指向 window
allName.call(obj, ‘ 我是 ’, ‘ 前端 ’) // 我的全名是“我是一个前端”this 指向 obj
apply
apply 接收两个参数,第一个参数为函数上下文 this,第二个参数为函数参数只不过是通过一个数组的形式传入的。
allName.apply(obj, [‘ 我是 ’, ‘ 前端 ’])// 我的全名是“我是一个前端”this 指向 obj
bind
bind 接收多个参数,第一个是 bind 返回值返回值是一个函数上下文的 this,不会立即执行。
let obj = {
name: “ 一个 ”
}
function allName(firstName, lastName, flag) {
console.log(this)
console.log(` 我的全名是 ”${firstName}${this.name}${lastName}” 我的座右铭是 ”${flag}”`)
}
allName.bind(obj) // 不会执行
let fn = allName.bind(obj)
fn(‘ 我是 ’, ‘ 前端 ’, ‘ 好好学习天天向上 ’)
// 也可以这样用,参数可以分开传。bind 后的函数参数默认排列在原函数参数后边
fn = allName.bind(obj, “ 你是 ”)
fn(‘ 前端 ’, ‘ 好好学习天天向上 ’)
接下来搓搓手实现 call、apply 和 bind
实现 call
let Person = {
name: ‘Tom’,
say() {
console.log(this)
console.log(` 我叫 ${this.name}`)
}
}
// 先看代码执行效果
Person.say() // 我叫 Tom
Person1 = {
name: ‘Tom1’
}
// 我们尝试用原生方法 call 来实现 this 指向 Person1
Person.say.call(Person1) // 我叫 Tom1
通过第一次打印执行和第二次打印执行我发现,如果 Person1 有 say 方法那么 Person1 直接执行 Person1.say() 结果就是我是 Tom1, 是的 call 就是这么实现的。
再看代码
Function.prototype.MyCall = function(context) {
//context 就是 demo 中的 Person1
// 必须此时调用 MyCall 的函数是 say 方法,那么我们只需要在 context 上扩展一个 say 方法指向调用 MyCall 的 say 方法这样 this
console.log(this)
context.say = this //Mycall 里边的 this 就是我们虚拟的 say 方法
context.say()
}
// 测试
Person.say.MyCall(Person1)// 我叫 Tom1
perfect!爆棚的满足感!不过拿脚趾头想想也不会这么简单,继续完善我们自己找茬 1、call 支持多个参数,有可能一个也不没有 2、考虑多参数时要把参数传给扩展方法。3、给上下文定义的函数要保持唯一不能是 say4、扩展完我们需要吧自定义函数删除接下来针对找茬问题一一解决
let Person = {
name: ‘Tom’,
say() {
console.log(this)
console.log(` 我叫 ${this.name}`)
}
}
Person1 = {
name: ‘Tom1’
}
// 如果没有参数
Person.say.call()
没有指定 this,this 指向 window
我们也要这样
Function.prototype.MyCall = function(context) {
// 如果没有参数我们参考 call 的处理方式
context = context || window
//context 就是 demo 中的 Person1
// 必须此时调用 MyCall 的函数是 say 方法,那么我们只需要在 context 上扩展一个 say 方法指向调用 MyCall 的 say 方法这样 this
context.say = this //Mycall 里边的 this 就是我们虚拟的 say 方法
context.say()
}
Person.say.MyCall()
没毛病!继续解决
// 找茬 2:我们默认定义 context.say = this fn 如果已经被占用 嘎嘎 sb 了。不怕 搞定它
// say 需要是一个唯一值 是不是突然想到 es6 的新类型 Symbol fn = Symbol() 不过我们装逼不嫌事大 都说自己实现了
function mySymbol(obj) {
// 不要问我为什么这么写,我也不知道就感觉这样 nb
let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
// 牛逼也要严谨
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj) // 递归调用
} else {
return unique
}
}
// 接下来我们一并把多参数和执行完删除自定义方法删除掉一块搞定
Function.prototype.myCall1 = function(context) {
// 如果没有传或传的值为空对象 context 指向 window
context = context || window
let fn = mySymbol(context)
context.fn = this // 给 context 添加一个方法 指向 this
// 处理参数 去除第一个参数 this 其它传入 fn 函数
let arg = […arguments].slice(1) //[…xxx] 把类数组变成数组,arguments 为啥不是数组自行搜索 slice 返回一个新数组
context.fn(…arg) // 执行 fn
delete context.fn // 删除方法
}
let Person = {
name: ‘Tom’,
say(age) {
console.log(this)
console.log(` 我叫 ${this.name} 我今年 ${age}`)
}
}
Person1 = {
name: ‘Tom1’
}
Person.say.call(Person1,18)// 我叫 Tom1 我今年 18
测试结果相当完美!
实现 apply
接下来 apply 就简单多了,只有多参数时第二个参数是数组,就不一步步细说了。
Function.prototype.myApply = function(context) {
// 如果没有传或传的值为空对象 context 指向 window
if (typeof context === “undefined” || context === null) {
context = window
}
let fn = mySymbol(context)
context.fn = this // 给 context 添加一个方法 指向 this
// 处理参数 去除第一个参数 this 其它传入 fn 函数
let arg = […arguments].slice(1) //[…xxx] 把类数组变成数组,arguments 为啥不是数组自行搜索 slice 返回一个新数组
context.fn(arg) // 执行 fn
delete context.fn // 删除方法
}
实现 bind
这个和 call、apply 区别还是很大的,容我去抽根烟回来收拾它还是老套路先分析 bind 都能干些什么,有什么特点
1、函数调用,改变 this
2、返回一个绑定 this 的函数
3、接收多个参数
4、支持柯里化形式传参 fn(1)(2)
Function.prototype.bind = function(context) {
// 返回一个绑定 this 的函数,我们需要在此保存 this
let self = this
// 可以支持柯里化传参,保存参数
let arg = […arguments].slice(1)
// 返回一个函数
return function() {
// 同样因为支持柯里化形式传参我们需要再次获取存储参数
let newArg = […arguments]
console.log(newArg)
// 返回函数绑定 this,传入两次保存的参数
// 考虑返回函数有返回值做了 return
return self.apply(context, arg.concat(newArg))
}
}
// 搞定测试
let fn = Person.say.bind(Person1)
fn()
fn(18)
是的,完美,实现了绑定 this,返回函数,不立即执行,可以柯里化形式传参。
简版的实现就算完成了
欢迎吐槽 or 点赞!