今天在代码的海洋里遨游时,我意外发现了一个有趣的魔法道具——call!它能让函数像变色龙一样改变自己的this属性。下面是我的魔法笔记,记录了如何亲手打造这根"魔法指挥棒"。
第一章:函数的"变脸"艺术
想象一下,函数是个有原则的演员,平时只认自己的导演(this)。但有了call,它就能瞬间切换剧组:
function gretting() {
return `hello,I am ${this.name}`;
}
const lj = { name: '雷军' };
const trump = { name: 'Trump' };
console.log(gretting.call(lj)); // "hello,I am 雷军"
console.log(gretting.call(trump)); // "hello,I am Trump"
这简直是函数界的"变脸"绝活!但更神奇的是call、apply和bind这三胞胎的不同性格:
- call:急性子,立即执行,参数一个个传
(obj, 1, 2, 3) - apply:同样急性子,但喜欢把参数打包
(obj, [1, 2, 3]) - bind:慢性子,先记下配置稍后执行
const newFn = fn.bind(obj)大家想了解一下具体用法,可以看看我的这篇文章JavaScript中的this指向:从懵圈到豁然开朗的奇幻之旅 相信看完能加深对这三胞胎用法的印象
我们知道了call、apply和bind这三胞胎的不同用法,那么我们怎么手写一个挂载到Function原型链上面的函数呢?接下来我就来讲讲我的方法
第二章:打造我们的魔法棒 myCall
现在进入重头戏——亲手锻造myCall魔法棒!核心步骤就像制作汉堡:
Function.prototype.myCall = function (context, ...args) {
// 步骤1:处理空导演(context为null/undefined时指向window)
if (context == null) context = window;
// 步骤2:安全检测(确保调用者是个函数)
if (typeof this !== 'function') {
throw new TypeError('魔法棒只能用于函数!');
}
// 步骤3:创建唯一ID防止冲突(Symbol的妙用)
const fnKey = Symbol('fn');
// 步骤4:给导演临时加戏(将函数绑定到context)
context[fnKey] = this;
// 步骤5:执行函数并传递参数
const result = context[fnKey](...args);
// 步骤6:清理现场(删除临时属性)
delete context[fnKey];
return result;
};
重点魔法解析
Symbol防冲突:就像给临时演员戴上面具
```
const fnKey = Symbol('fn'); // 创建唯一钥匙
context[fnKey] = this; // 安全挂载函数
```
这是es6新增的数据类型,避免覆盖导演原有的道具(比如原本的context.fn属性)
Rest参数妙用:魔法师的收纳袋
```
步骤3:创建唯一ID防止冲突(Symbol的妙用)
function (context, ...args) // 收集所有剩余参数
context[fnKey](...args) // 展开参数传递
```
这是rest运算符,比传统的`arguments`更优雅,自动转成真数组,可以使用数组的方法
我们可以传参看看结果
var name = 'zhangsan'
function gretting(...args) {
console.log(args, arguments[0], arguments[1]);
// 将类数组转换成数组
// console.log([...arguments], Array.from(arguments));
return `hello,I am ${this.name}`
}
console.log(gretting(1, 2, 3));
可以看到打印了一个数组,魔法师收纳袋的作用
灵活转变this
const fnKey = Symbol('fn');
// 步骤4:给导演临时加戏(将函数绑定到context)
context[fnKey] = this;
// 步骤5:执行函数并传递参数
const result = context[fnKey](...args);
我们该如何灵活的玩转this呢?我们要改变函数的this指向,我们需要怎么便捷地做到呢》 我们来在我们写得call函数里面改写一下
Function.prototype.myCall = function (context, ...args) {
console.log('/////');
if (context === null || context === undefined) {
context = window
}
// this? gretting
console.log(this);
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.mycall called on non-function')
}
}
gretting.myCall()
可以看到结果此时是this是指向我们的函数的,我们可以想到对象的方法的this会指向对象的,我们是不是可以把它作为我们需要指向对象的方法呢,这样我们的this就会指向我们需要指向的对象了,
这个时候我们需要用到es6新增的Symbol数据类型,因为其的唯一,可以帮我避免覆盖原先对象中存在的属性或者方法
context[fnKey] = this由于this就是我们的函数 gretting,我们就这样把其绑定到我们需要指向的对象方法了,这样gretting被context调用时,this就会指向我们所指定的对象
const result = context[fnKey](...args)最后通过对象调用方法,并返回,这样我们就实现了改变this指向,打印我们所指定对象的name
删除临时属性:魔法界的"事了拂衣去"
```
delete context[fnKey]; // 消除痕迹
```
避免污染context对象,做有素质的魔法师,我们需要删除这个属性方法
第三章:当导演缺席时的神奇规则
当call遇到null或undefined时,不同模式下有不同表现:
载
// 非严格模式下:自动找大老板window
gretting.call(null); // "hello,I am Trump"
// 严格模式下:保持原则
"use strict";
gretting.call(null); // TypeError: 导演不能为空!
在我们的myCall中,我们选择非严格模式的行为:
if (context == null) context = window;
第四章:魔法实战演示
看我们的魔法棒如何指挥函数:
var obj = { name: '刘老板' };
console.log(gretting.myCall(obj, 1, 2, 3));
// 输出:
///// (我们的魔法信号)
hello,I am 刘老板
有趣的是函数内部的arguments和rest参数的区别:
function test(...args) {
console.log(args); // 真数组 [1, 2, 3]
console.log(arguments); // 类数组 {0:1, 1:2, 2:3}
}
第五章:魔法原理深度解析
-
原型链的魔法底座
Function.prototype.myCall = ...所有函数都继承自
Function.prototype,所以才能实现任何函数.myCall()的调用方式 -
this的双重身份
- 当
gretting.myCall()时,myCall内部的this指向gretting - 当
context[fnKey]()时,函数内部的this指向context
- 当
-
类数组的变身术
传统转换方式:// 两种将arguments转数组的方法 [...arguments] Array.from(arguments)
第六章:魔法界的三大禁忌
-
忌忘记类型检查
if (typeof this !== 'function') // 防止非函数调用 -
忌留下魔法痕迹
delete context[fnKey] // 务必清理临时属性 -
忌处理context不当
context = context || window // 处理边界情况
魔法总结:三大核心技巧
-
Symbol护盾
使用唯一属性键防止冲突,就像魔法师用专属咒语避免法术干扰 -
参数双通道
...args收参和传参的优雅处理,如同魔法阵的能量传递 -
环境清理术
执行后立即删除临时属性,体现专业魔法师的素养
通过今天的学习,我深刻理解了JavaScript中this绑定的魔法机制。原来每个函数体内都住着一个变色龙,而call/apply就是训练它的魔法指令。最重要的是,我们亲手打造的myCall魔法棒不仅能用,还理解了背后的每一条魔法符文!