JS call方法原理
原文轉自:https://blog.csdn.net/u010377383/article/details/80646415
前言
本來計劃是, 先把深入React技術棧過完,
但是,現在在滿足RN app開發情況下,我還是先深入js一個系列。
(一)call原始碼解析
首先上一個call使用
function add(c, d) {
return this.a + this.b + c + d;
}
const obj = { a: 1, b: 2 };
console.error(add.call(obj, 3, 4)); // 10
大統上的說法就是,call改變了this的指向。然後,介紹this xxx什麼一大堆名詞,反正不管你懂不懂,成功繞暈你就已經ok了,但是實際發生的過程,可以看成下面的樣子。
const o = {
a: 1,
b: 2,
add: function(c, d) {
return this.a + this.b + c + d
}
};
給o物件新增一個add屬性,這個時候 this 就指向了 o,
o.add(5,7)得到的結果和add.call(o, 5, 6)相同。
但是給物件o添加了一個額外的add屬性,這個屬性我們是不需要的,所以可以使用delete刪除它。
so, 基本為以下三部。
// 1. 將函式設為物件的屬性
o.fn = bar
// 2. 執行該函式
o.fn()
// 3. 刪除該函式
delete o.fn
所以我們基於ES3 實現call
Function.prototype.es3Call = function (context) {
var content = context || window;
content.fn = this;
var args = [];
// arguments是類陣列物件,遍歷之前需要儲存長度,過濾出第一個傳參
for (var i = 1, len = arguments.length ; i < len; i++) {
// 避免object之類傳入
args.push('arguments[' + i + ']');
}
var result = eval('content.fn('+args+')');
delete content.fn;
return result;
}
console.error(add.es3Call(obj, 3, 4)); // 10
console.log(add.es3Call({ a: 3, b: 9 }, 3, 4)); // 19
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4
基於ES6 實現call, 其實差別不大,es6新增… rest,這就可以放棄eval的寫法,如下
// ES6 call 實現
Function.prototype.es6Call = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
const result = context.fn(...args);
return result;
}
console.error(add.es6Call(obj, 3, 4));
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4
(二)apply原始碼解析
apply和call區別在於apply第二個引數是Array,而call是將一個個傳入
// 基於es3實現
Function.prototype.es3Apply = function (context, arr) {
var context = context || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
if (!(arr instanceof Array)) throw new Error('params must be array');
result = eval('context.fn('+arr+')');
}
delete context.fn;
return result
}
console.log(add.apply(obj, [1, 2])); // 6
// 基於es6實現
Function.prototype.es6Apply = function (context, arr) {
var context = context || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
if (!(arr instanceof Array)) throw new Error('params must be array');
result = context.fn(...arr);
}
delete context.fn;
return result
}
console.error(add.es6Apply(obj, [1, 2])); // 6
(三)bind原始碼解析
bind() 方法會建立一個新函式。
當這個新函式被呼叫時,bind() 的第一個引數將作為它執行時的 this,
之後的一序列引數將會在傳遞的實參前傳入作為它的引數。
先看一個使用bind方法的例項
function foo(c, d) {
this.b = 100
console.log(this.a)
console.log(this.b)
console.log(c)
console.log(d)
}
// 我們將foo bind到{a: 1}
var func = foo.bind({a: 1}, '1st');
func('2nd'); // 1 100 1st 2nd
// 即使再次call也不能改變this。
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
// 當 bind 返回的函式作為建構函式的時候,
// bind 時指定的 this 值會失效,但傳入的引數依然生效。
// 所以使用func為建構函式時,this不會指向{a: 1}物件,this.a的值為undefined。如下
// new func('4th'); //undefined 100 1st 4th
首先使用ES3實現
Function.prototype.es3Bind = function (context) {
if (typeof this !== "function") throw new TypeError('what is trying to be bound is not callback');
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
const fBound = function () {
// 獲取函式的引數
var bindArgs = Array.prototype.slice.call(arguments);
// 返回函式的執行結果
// 判斷函式是作為建構函式還是普通函式
// 建構函式this instanceof fNOP返回true,將繫結函式的this指向該例項,可以讓例項獲得來自繫結函式的值。
// 當作為普通函式時,this 指向 window,此時結果為 false,將繫結函式的 this 指向 context
return self.apply(this instanceof fNOP ? this: context, args.concat(bindArgs));
}
// 建立空函式
var fNOP = function () {};
// fNOP函式的prototype為繫結函式的prototype
fNOP.prototype = this.prototype;
// 返回函式的prototype等於fNOP函式的例項實現繼承
fBound.prototype = new fNOP();
// 以上三句相當於Object.create(this.prototype)
return fBound;
}
var func = foo.es3Bind({a: 1}, '1st');
func('2nd'); // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th'); //undefined 100 1st 4th
es6實現
Function.prototype.es6Bind = function(context, ...rest) {
if (typeof this !== 'function') throw new TypeError('invalid invoked!');
var self = this;
return function F(...args) {
if (this instanceof F) {
return new self(...rest, ...args)
}
return self.apply(context, rest.concat(args))
}
}
var func = foo.es3Bind({a: 1}, '1st');
func('2nd'); // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th'); //undefined 100 1st 4th
---------------------
作者:justin-1
來源:CSDN
原文:https://blog.csdn.net/u010377383/article/details/80646415
版權宣告:本文為博主原創文章,轉載請附上博文連結!