Function.prototype.call2 = function(context,...list) {
context = context || window;
context.fn = this;
let result = context.fn(...list);
delete context.fn
return result;
}
var value = "JS";
var foo = {
value: "Node"
};
function bar(...list) {
console.log(this.value);
console.log(list)
}
bar.call2(foo,1,2,3);
bar.call2(null,4,5,6);
複製代碼
Function.prototype.apply2 = function(context,arr) {
context = context || window;
context.fn = this;
let result = context.fn(...arr);
delete context.fn
return result;
}
var value = "JS";
var foo = {
value: "Node"
};
function bar(...list) {
console.log(this.value);
console.log(list)
}
bar.apply2(foo,[1,2,3]);
bar.apply2(null,[4,5,6]);
複製代碼
Function.prototype.bind1 = function(context, ...arr1) {
let func = this;
return function(...arr2) {
func.apply(context, [...arr1, ...arr2]);
// func.call(context, ...arr1, ...arr2);
}
}
var value = "JS";
var foo = {
value: "Node"
};
function bar(...list) {
console.log(this.value);
console.log(list)
}
bar.bind1(foo, 1, 2)(3, 4);
bar.bind1(null, 5, 6)(7, 8);
bar.bind1(null)(5, 6,7, 8);
複製代碼
Function.prototype.bind2 = function(context, ...arr1) {
context = context || window;
context.fn = this;
return function(...arr2){
return context.fn(...arr1,...arr2);
};
}
var value = "JS";
var foo = {
value: "Node"
};
function bar(...list) {
console.log(this.value);
console.log(list)
}
bar.bind2(foo, 1, 2)(3, 4);
bar.bind2(null, 5, 6)(7, 8);
bar.bind2(null)(5, 6,7, 8);
複製代碼
call() 方法調用一個函數, 其具備一個指定的
this
值和分別地提供的參數(參數的列表)。html
call()
和 apply()
的區別在於,call()
方法接受的是若干個參數的列表,而apply()
方法接受的是一個包含多個參數的數組node
舉個例子:git
var func = function(arg1, arg2) {
...
};
func.call(this, arg1, arg2); // 使用 call,參數列表
func.apply(this, [arg1, arg2]) // 使用 apply,參數數組複製代碼
複製代碼
下面列舉一些經常使用用法:github
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];
// 將第二個數組融合進第一個數組// 至關於 vegetables.push('celery', 'beetroot');Array.prototype.push.apply(vegetables, moreVegs);
// 4
vegetables;
// ['parsnip', 'potato', 'celery', 'beetroot']複製代碼
複製代碼
當第二個數組(如示例中的 moreVegs
)太大時不要使用這個方法來合併數組,由於一個函數可以接受的參數個數是有限制的。不一樣的引擎有不一樣的限制,JS核心限制在 65535,有些引擎會拋出異常,有些不拋出異常但丟失多餘參數。數組
如何解決呢?方法就是將參數數組切塊後循環傳入目標方法bash
functionconcatOfArray(arr1, arr2) {
var QUANTUM = 32768;
for (var i = 0, len = arr2.length; i < len; i += QUANTUM) {
Array.prototype.push.apply(
arr1,
arr2.slice(i, Math.min(i + QUANTUM, len) )
);
}
return arr1;
}
// 驗證代碼var arr1 = [-3, -2, -1];
var arr2 = [];
for(var i = 0; i < 1000000; i++) {
arr2.push(i);
}
Array.prototype.push.apply(arr1, arr2);
// Uncaught RangeError: Maximum call stack size exceeded
concatOfArray(arr1, arr2);
// (1000003) [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]複製代碼
複製代碼
var numbers = [5, 458 , 120 , -215 ];
Math.max.apply(Math, numbers); //458 Math.max.call(Math, 5, 458 , 120 , -215); //458// ES6Math.max.call(Math, ...numbers); // 458複製代碼
複製代碼
爲何要這麼用呢,由於數組 numbers
自己沒有 max
方法,可是 Math
有呀,因此這裏就是藉助 call / apply
使用 Math.max
方法。數據結構
functionisArray(obj){
returnObject.prototype.toString.call(obj) === '[object Array]';
}
isArray([1, 2, 3]);
// true// 直接使用 toString()
[1, 2, 3].toString(); // "1,2,3""123".toString(); // "123"123.toString(); // SyntaxError: Invalid or unexpected tokenNumber(123).toString(); // "123"Object(123).toString(); // "123"複製代碼
複製代碼
能夠經過toString()
來獲取每一個對象的類型,可是不一樣對象的 toString()
有不一樣的實現,因此經過 Object.prototype.toString()
來檢測,須要以 call() / apply()
的形式來調用,傳遞要檢查的對象做爲第一個參數。app
另外一個驗證是不是數組的方法dom
var toStr = Function.prototype.call.bind(Object.prototype.toString);
functionisArray(obj){
return toStr(obj) === '[object Array]';
}
isArray([1, 2, 3]);
// true// 使用改造後的 toStr
toStr([1, 2, 3]); // "[object Array]"
toStr("123"); // "[object String]"
toStr(123); // "[object Number]"
toStr(Object(123)); // "[object Number]"複製代碼
複製代碼
上面方法首先使用 Function.prototype.call
函數指定一個 this
值,而後 .bind
返回一個新的函數,始終將 Object.prototype.toString
設置爲傳入參數。其實等價於 Object.prototype.toString.call()
。函數
這裏有一個前提是toString()
方法沒有被覆蓋
Object.prototype.toString = function() {
return'';
}
isArray([1, 2, 3]);
// false複製代碼
複製代碼
var domNodes = document.getElementsByTagName("*");
domNodes.unshift("h1");
// TypeError: domNodes.unshift is not a functionvar domNodeArrays = Array.prototype.slice.call(domNodes);
domNodeArrays.unshift("h1"); // 505 不一樣環境下數據不一樣// (505) ["h1", html.gr__hujiang_com, head, meta, ...] 複製代碼
複製代碼
類數組對象有下面兩個特性
length
屬性push
、shift
、 forEach
以及 indexOf
等數組對象具備的方法要說明的是,類數組對象是一個對象。JS中存在一種名爲類數組的對象結構,好比 arguments
對象,還有DOM API 返回的 NodeList
對象都屬於類數組對象,類數組對象不能使用 push/pop/shift/unshift
等數組方法,經過 Array.prototype.slice.call
轉換成真正的數組,就可使用 Array
下全部方法。
類數組對象轉數組的其餘方法:
// 上面代碼等同於var arr = [].slice.call(arguments);
ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
複製代碼
複製代碼
Array.from()
能夠將兩類對象轉爲真正的數組:類數組對象和可遍歷(iterable)對象(包括ES6新增的數據結構 Set 和 Map)。
PS擴展一:爲何經過 Array.prototype.slice.call()
就能夠把類數組對象轉換成數組?
其實很簡單,slice
將 Array-like
對象經過下標操做放進了新的 Array
裏面。
下面代碼是 MDN 關於 slice
的Polyfill,連接 Array.prototype.slice()
Array.prototype.slice = function(begin, end) {
end = (typeof end !== 'undefined') ? end : this.length;
// For array like object we handle it ourselves.var i, cloned = [],
size, len = this.length;
// Handle negative value for "begin"var start = begin || 0;
start = (start >= 0) ? start : Math.max(0, len + start);
// Handle negative value for "end"var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
if (end < 0) {
upTo = len + end;
}
// Actual expected size of the slice
size = upTo - start;
if (size > 0) {
cloned = newArray(size);
if (this.charAt) {
for (i = 0; i < size; i++) {
cloned[i] = this.charAt(start + i);
}
} else {
for (i = 0; i < size; i++) {
cloned[i] = this[start + i];
}
}
}
return cloned;
};
}
複製代碼
複製代碼
PS擴展二:經過 Array.prototype.slice.call()
就足夠了嗎?存在什麼問題?
在低版本IE下不支持經過Array.prototype.slice.call(args)
將類數組對象轉換成數組,由於低版本IE(IE < 9)下的DOM
對象是以 com
對象的形式實現的,js對象與 com
對象不能進行轉換。
兼容寫法以下:
functiontoArray(nodes){
try {
// works in every browser except IEreturnArray.prototype.slice.call(nodes);
} catch(err) {
// Fails in IE < 9var arr = [],
length = nodes.length;
for(var i = 0; i < length; i++){
// arr.push(nodes[i]); // 兩種均可以
arr[i] = nodes[i];
}
return arr;
}
}
複製代碼
複製代碼
PS 擴展三:爲何要有類數組對象呢?或者說類數組對象是爲何解決什麼問題纔出現的?
JavaScript類型化數組是一種相似數組的對象,並提供了一種用於訪問原始二進制數據的機制。
Array
存儲的對象能動態增多和減小,而且能夠存儲任何JavaScript值。JavaScript引擎會作一些內部優化,以便對數組的操做能夠很快。然而,隨着Web應用程序變得愈來愈強大,尤爲一些新增長的功能例如:音頻視頻編輯,訪問WebSockets的原始數據等,很明顯有些時候若是使用JavaScript代碼能夠快速方便地經過類型化數組來操做原始的二進制數據,這將會很是有幫助。
一句話就是,能夠更快的操做複雜數據。
functionSuperType(){
this.color=["red", "green", "blue"];
}
functionSubType(){
// 核心代碼,繼承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color);
// ["red", "green", "blue", "black"]var instance2 = new SubType();
console.log(instance2.color);
// ["red", "green", "blue"]複製代碼
複製代碼
在子構造函數中,經過調用父構造函數的call
方法來實現繼承,因而SubType
的每一個實例都會將SuperType
中的屬性複製一份。
缺點:
更多繼承方案查看我以前的文章。JavaScript經常使用八種繼承方案
如下內容參考自 JavaScript深刻之call和apply的模擬實現
先看下面一個簡單的例子
var value = 1;
var foo = {
value: 1
};
functionbar() {
console.log(this.value);
}
bar.call(foo); // 1複製代碼
複製代碼
經過上面的介紹咱們知道,call()
主要有如下兩點
call()
改變了this的指向bar
執行了若是在調用call()
的時候把函數 bar()
添加到foo()
對象中,即以下
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar(); // 1複製代碼
複製代碼
這個改動就能夠實現:改變了this的指向而且執行了函數bar
。
可是這樣寫是有反作用的,即給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
};
functionbar() {
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,c3console.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
};
functionbar(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 || window; // 實現細節 1
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; // 實現細節 2
}
// 測試一下var value = 2;
var obj = {
value: 1
}
functionbar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
bar.call2(null); // 2console.log(bar.call2(obj, 'kevin', 18));
// 1// {// value: 1,// name: 'kevin',// age: 18// }複製代碼
複製代碼
完美!!!
ES3:
Function.prototype.call = function (context) {
context = 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 || 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 || 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 || window;
context.fn = this;
let result;
if (!arr) {
result = context.fn();
} else {
result = context.fn(...arr);
}
delete context.fn
return result;
}
複製代碼
複製代碼