本文共 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 的原理,旨在深刻了解這兩個方法的用法和區別,但願你能有所收穫。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。