前沿:數組
學習就比如是座大山,人們沿着不一樣的路爬山,分享着本身看到的風景。你不必定能看到別人看到的風景,體會到別人的心情。只有本身去爬山,才能看到不同的風景,體會才更加深入。bash
進入正題:app
call() 方法調用一個函數, 其具備一個指定的 this
值和分別地提供的參數(參數的列表)。
函數
call()
和 apply()
的區別在於,call()
方法接受的是若干個參數的列表,而apply()
方法接受的是一個包含多個參數的數組
學習
舉個例子:測試
var func = function(arg1, arg2) {
...
};
func.call(this, arg1, arg2); // 使用 call,參數列表
func.apply(this, [arg1, arg2]) // 使用 apply,參數數組複製代碼
先看下面一個簡單的例子ui
var value = 1;
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1複製代碼
經過上面的介紹咱們知道,call()
主要有如下兩點this
call()
改變了this的指向bar
執行了若是在調用call()
的時候把函數 bar()
添加到foo()
對象中,即以下spa
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar(); // 1複製代碼
這個改動就能夠實現:改變了this的指向而且執行了函數bar
。prototype
可是這樣寫是有反作用的,即給foo
額外添加了一個屬性,怎麼解決呢?
解決方法很簡單,用 delete
刪掉就行了。
因此只要實現下面3步就能夠模擬實現了。
foo.fn = bar
foo.fn()
delete foo.fn
// 初版
Function.prototype.call2 = function(context) {
// 首先要獲取調用call的函數,用this能夠獲取
context.fn = this; // foo.fn = bar
context.fn(); // foo.fn()
delete context.fn; // delete foo.fn
}
// 測試一下
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo); // 1複製代碼
完美!
初版有一個問題,那就是函數 bar
不能接收參數,因此咱們能夠從 arguments
中獲取參數,取出第二個到最後一個參數放到數組中,爲何要拋棄第一個參數呢,由於第一個參數是 this
。
類數組對象轉成數組的方法上面已經介紹過了,可是這邊使用ES3的方案來作。
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}複製代碼
參數數組搞定了,接下來要作的就是執行函數 context.fn()
。
context.fn( args.join(',') ); // 這樣不行複製代碼
上面直接調用確定不行,args.join(',')
會返回一個字符串,並不會執行。
這邊採用 eval
方法來實現,拼成一個函數。
eval('context.fn(' + args +')')複製代碼
上面代碼中args
會自動調用 args.toString()
方法,由於'context.fn(' + args +')'
本質上是字符串拼接,會自動調用toString()
方法,以下代碼:
var args = ["a1", "b2", "c3"];
console.log(args);
// ["a1", "b2", "c3"]
console.log(args.toString());
// a1,b2,c3
console.log("" + args);
// a1,b2,c3複製代碼
因此說第二個版本就實現了,代碼以下:
// 第二版
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
eval('context.fn(' + args +')');
delete context.fn;
}
// 測試一下
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call2(foo, 'kevin', 18);
// kevin
// 18
// 1複製代碼
完美!!
還有2個細節須要注意:
null
或者 undefined
,此時 this 指向 window實現上面的三點很簡單,代碼以下
// 第三版
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window; // 實現細節 1 和 2
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result; // 實現細節 3
}
// 測試一下
var value = 2;
var obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
function foo() {
console.log(this);
}
bar.call2(null); // 2
foo.call2(123); // Number {123, fn: ƒ}
bar.call2(obj, 'kevin', 18); //1
複製代碼
完美!!!
ES3:
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}複製代碼
ES6:
Function.prototype.call = function (context) {
context = context ? Object(context) : window;
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn
return result;
}複製代碼
ES3:
Function.prototype.apply = function (context, arr) {
context = 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;
}複製代碼
ES6:
Function.prototype.apply = function (context, arr) {
context = context ? Object(context) : window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn
return result;
}複製代碼