本文共 1320 字,读完只需 5 分钟javascript
JS 函数 call 和 apply 用来手动改变 this 的指向,call 和 apply 惟一的区别就在于函数参数的传递方式不一样,call 是以逗号的形式,apply 是以数组的形式:java
let person1 = {
name: "person1",
say: function(age, sex) {
console.log(this.name + ' age: ' + age + ' sex: ' + sex);
}
}
let person2 = {
name: "person"
}
person1.say.call(person2, 20, "男");
person1.say.apply(person2, [20, "男"]);
复制代码
本文就尝试用其余方式来模拟实现 call 和 apply。es6
首先观察 call 和 apply 有什么特色?数组
基于 call 函数是调用函数的属性的特色,call 的 this 指向调用函数,咱们能够尝试把调用函数的做为传入的新对象的一个属性,执行后,再删除这个属性就行了。闭包
Function.prototype.newCall = function (context) {
context.fn = this; // this 指的是 say 函数
context.fn();
delete context.fn;
}
var person = {
name: "jayChou"
};
var say = function() {
console.log(this.name);
}
say.newCall(person); // jayChou
复制代码
是否是就初步模拟实现了 call 函数呢,因为 call 还涉及到传参的问题,因此咱们进入到下一环节。app
在给对象临时一个函数,并执行时,传入的参数是除了 context 其他的参数。那么咱们能够截取 arguments 参数数组的第一个后,将剩余的参数传入临时数组。函数
在前面我有讲过函数 arguments 类数组对象的特色,arguments 是不支持数组的大多数方法, 可是支持for 循环来遍历数组。post
Function.prototype.newCall = function (context) {
context.fn = this;
let args = [];
for(let i=1; i< arguments.length; i++) {
args.push('arguments[' + i + ']');
}
// args => [arguments[1], arguments[2], arguments[3], ...]
context.fn(args.join(',')); // ???
delete context.fn;
}
var person = {
name: "jayChou"
};
var say = function(age, sex) {
console.log(`name: ${this.name},age: ${age}, sex: ${sex}`);
}
say.newCall(person);
复制代码
上面传递参数的方式最后确定是失败的,咱们能够尝试 eval 的方式,将参数添加子函数的做用域中。ui
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码this
Function.prototype.newCall = function (context) {
context.fn = this;
let args = [];
for(var i=1; i< arguments.length; i++) {
args.push('arguments[' + i + ']');
}
// args => [arguments[1], arguments[2], arguments[3], ...]
eval('context.fn(' + args + ')');
delete context.fn;
}
var person = {
name: "jayChou"
};
function say(age, sex) {
console.log(`name: ${this.name},age: ${age}, sex: ${sex}`);
}
say.newCall(person, 18, '男'); // name: jayChou,age: 18, sex: 男
复制代码
成功啦!
实现了函数参数的传递,那么函数返回值怎么处理呢。并且,若是传入的对象是 null,又该如何处理?因此还须要再作一些工做:
Function.prototype.newCall = function (context) {
if (typeof context === 'object') {
context = context || window
} else {
context = Object.create(null);
}
context.fn = this;
let args = [];
for(var i=1; i< arguments.length; i++) {
args.push('arguments[' + i + ']');
}
// args => [arguments[1], arguments[2], arguments[3], ...]
var result = eval('context.fn(' + args + ')'); // 处理返回值
delete context.fn;
return result; // 返回返回值
}
var person = {
name: "jayChou"
};
function say(age, sex) {
console.log(`name: ${this.name},age: ${age}, sex: ${sex}`);
return age + sex;
}
var check = say.newCall(person, 18, '男');
console.log(check); // 18男
复制代码
判断传入对象的类型,若是为 null 就指向 window 对象。利用 eval 来执行字符串代码,并返回字符串代码执行的结果,就完成了模拟 call。 大功告成!
前面咱们用的 eval 方式能够用 ES6 的解决还存在的一些问题,有没有注意到,这段代码是有问题的。
context.fn = this;
复制代码
假如对象在被 call 调用前,已经有 fn 属性怎么办?
ES6 中提供了一种新的基本数据类型,Symbol,表示独一无二的值,另外,Symbol 做为属性的时候,不能使用点运算符。因此再加上 ES 的 rest 剩余参数替代 arguments 遍历的工做就有:
Function.prototype.newCall = function (context,...params) {
if (typeof context === 'object') {
context = context || window
} else {
context = Object.create(null);
}
let fn = Symbol();
context[fn] = this
var result = context[fn](...params);
delete context.fn;
return result;
}
var person = {
name: "jayChou"
};
function say(age, sex) {
console.log(`name: ${this.name},age: ${age}, sex: ${sex}`);
return age + sex;
}
var check = say.newCall(person, 18, '男');
console.log(check); // 18男
复制代码
apply 和 call 的实现原理,基本相似,区别在于 apply 的参数是以数组的形式传入。
Function.prototype.newApply = function (context, arr) {
if (typeof context === 'object') {
context = context || window
} else {
context = Object.create(null);
}
context.fn = this;
var result;
if (!arr) { // 判断函数参数是否为空
result = context.fn();
}
else {
var args = [];
for (var i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')');
}
delete context.fn;
return result;
}
复制代码
es6 实现
Function.prototype.newApply = function(context, parameter) {
if (typeof context === 'object') {
context = context || window
} else {
context = Object.create(null)
}
let fn = Symbol()
context[fn] = this;
var result = context[fn](...parameter);
delete context[fn];
return result;
}
复制代码
本文经过原生 JS 的 ES5 的方法和 ES 6 的方法模拟实现了 call 和 apply 的原理,旨在深刻了解这两个方法的用法和区别,但愿你能有所收获。
欢迎关注个人我的公众号“谢南波”,专一分享原创文章。