start
- 番茄我初次学习
call apply
这个两个方法 , 由于对this
没有理解透彻,导致学习这两个方法的时候,感觉到异常吃力。 - 所以强烈建议学习本文之前,先去弄懂
执行上下文
&&this
。
开始
好了废话不多说,上来先了解一下这两个函数有什么用。
call
1. call 是什么?
先说call
, 官方文档
官方文档很简洁,但是我们一步一步分析。
第一:call 方法,是在函数的原型上的。所以我收到的第一个信息
- 默认情况下只有函数才能 .call()
第二:官方的解释是: call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 所以又收到了这么几个信息
- 可以指定 this
- 给出一个或多个参数
- 调用函数
2. call 有什么用
call 可以使用一个指定的 this 来调用一个函数。 用通俗易懂的话来描述 就是改变 this 指向并且调用函数。
示例一:默认的this指向
// 1. 定义一个函数
function tomato() {
// 2. 打印这个函数执行的时候的 this
console.log('我是番茄', this)
}
// 3.直接调用 this指向全局对象 window
tomato()
示例二:使用call修改this指向为自定义的对象
// 1. 定义一个函数
function tomato() {
// 2. 打印这个函数执行的时候的 this
console.log('我是番茄', this)
}
// 3.定义一个对象 obj
var obj = {
name: '我是obj',
}
// 4.使用call,修改 this 指向为 obj
tomato.call(obj)
示例三:使用call修改this指向为自定义的对象,并且传递参数
// 1. 定义一个函数
function tomato() {
// 2. 打印这个函数执行的时候的 this
console.log('我是番茄', this, '打印一下我接受到的参数', ...arguments)
}
// 3.定义一个对象 obj
var obj = {
name: '我是obj',
}
// 4.使用call,修改 this 指向为 obj
tomato.call(obj, 1, 2, 3, 4, 5, 6)
3. 手写一个 call 加深理解
第一版
基本功能实现
// 1. 既然 call 是方法 ,那我们也定义一个名称为 myCall 的函数,挂载在Function的原型上
Function.prototype.myCall = function (content) {
// 2. 函数接收的第一个参数是需要修改 this 指向的内容,所以我定义一个形参content
/**
* 3.我们这里模拟call怎么去修改this的指向?
* 修改this指向无非4种方式
* 3.1 默认指向全局对象(非严格模式);undefined(严格模式) =>默认的 pass
* 3.2 谁调用,this就指向谁 采用它
* 3.3 call apply bind =>我们就是在模拟这个方法 pass
* 3.4 new
*/
// 4.函数myCall的this指向谁? 谁调用,指向谁,例如 tomato.myCall() 这里this就指向 函数tomato
// console.log('this', this)
// 5.所以通过 content 去调用 tomato 即可
content.__fn__ = this
content.__fn__()
delete content.__fn__
/*
①
let content = {
__fn__: function tomato() {
console.log('我是番茄')
}
}
②
content.__fn__()
③
delete content.__fn__
*/
}
let obj = {
name: '我是obj',
}
function tomato() {
console.log('我是番茄', this)
}
tomato.myCall(obj)
第二版
去除掉注释
Function.prototype.myCall = function (content) {
content.__fn__ = this
content.__fn__()
delete content.__fn__
}
let obj = {
name: '我是obj',
}
function tomato() {
console.log('我是番茄', this)
}
tomato.myCall(obj)
好了测试一下,看是否 ok
第三版
支持接受额外参数
Function.prototype.myCall = function (content) {
// 处理未知的参数肯定使用 arguments ,这里要注意两点:1. arguments 是类数组,不支持数组某些方法; 2.当然可以用es6的解构,这里为了兼容低版本我们就使用for循环
var arr = []
for (var index = 1; index < arguments.length; index++) {
arr.push('arguments[' + index + ']')
}
content.__fn__ = this
eval('content.__fn__(' + arr + ')')
delete content.__fn__
}
说明:
- 这里可以直接 es6 的解构
- 如果不想使用 es6 的语法,这里可是使用 for 循环去遍历 arguments
- 我最开始是直接用数组存储参数,然后使用
arr.join(',')
拆分, join() 方法将数组作为字符串返回。如果数组项中有对象,对象会被转换为 [object Object] 这种方式 不可取。 - eval() 可以简单理解为传入了一个字符串的函数 例如
var str = "function say() {console.log('你好呀')};say()"
eval(str)
第四版
支持返回值; 支持传入 null
Function.prototype.myCall = function (content) {
content = content || window
var arr = []
for (var index = 1; index < arguments.length; index++) {
arr.push('arguments[' + index + ']')
}
content.__fn__ = this
var end = eval('content.__fn__(' + arr + ')')
delete content.__fn__
return end
}
手写 call 最终版
最终版本 一
Function.prototype.myCall = function (content) {
content = content || window
var arr = []
for (var index = 1; index < arguments.length; index++) {
arr.push('arguments[' + index + ']')
}
content.__fn__ = this
var end = eval('content.__fn__(' + arr + ')')
delete content.__fn__
return end
}
最终版本 二
Function.prototype.myCall = function (content) {
content = Object(content) || window
let arr = [...arguments].slice(1)
let symbolFn = Symbol('fn')
content[symbolFn] = this
let end = content[symbolFn](...arr)
delete content[symbolFn]
return end
}
apply
apply 和 call 功能非常相似了,它和 call 的区别就是,只是它的传参是以一个数组的形式,而 call 是多个参数
apply 的手写实现
Function.prototype.apply = function (context, arr) {
context = Object(context) || window
context.fn = this
var result
if (!arr) {
result = context.fn()
} else {
var args = []
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result
}
call apply 常用的场景
- 类数组转换成数组
function tomato() {
let arr = Array.prototype.slice.call(arguments)
arr.push(1)
console.log(arr)
}
tomato(1, 2, 3, 4, 5)
2.类型判断
Object.prototype.toString.call('123')
// '[object String]'
参考博客
JavaScript 深入之 call 和 apply 的模拟实现
end
- ღ( ´・ᴗ・` )比心