一、js手寫call:隱式綁定改變this
Function.prototype.customCall = function (thisArg, ...args) {
// 1、獲取被調(diào)用的函數(shù)
const fn = this; // 這里的this指向sum
// 2、綁定this,將thisArg轉(zhuǎn)成對象類型(防止傳入非對象類型)
thisArg = thisArg ? Object(thisArg) : window;
thisArg.fn = fn;
// 3、執(zhí)行函數(shù)
const result = thisArg.fn(...args);
delete thisArg.fn;
// 4、返回執(zhí)行結(jié)果
return result;
};
function sum (num1, num2) {
console.log('sum函數(shù)', this, num1, num2, num1 + num2); // sum函數(shù) String{'abc', fn: ?} 10 20 30
return num1 + num2;
}
sum.customCall('abc', 10, 20);
二、js手寫apply:隱式綁定改變this
Function.prototype.customapply = function (thisArg, args) {
// 1、獲取被執(zhí)行的函數(shù)
const fn = this; // 這里的this指向sum函數(shù)
// 2、綁定this
thisArg = thisArg ? Object(thisArg) : thisArg; // 處理thisArg為Number/null/undefined的情況
thisArg.fn = fn;
// 執(zhí)行函數(shù)
const result = args ? thisArg.fn(...args) : thisArg.fn(); // args可能為undefined
delete thisArg.fn;
// 返回執(zhí)行結(jié)果
return result;
};
function sum(num1, num2) {
console.log('sum被調(diào)用', this, num1, num2, num1 + num2); // sum被調(diào)用 String{'abc'} 10 20
return num1 + num2;
}
function sum1(num) {
console.log('sum1被調(diào)用', this, num); // sum1被調(diào)用 String{'abc', fn: ?} 10
return num;
}
function sum2() {
console.log('sum2被執(zhí)行');
}
sum.customapply('abc', [10, 20]); // 隱式調(diào)用
sum1.customapply('abc', [10]);
sum2.customapply('abc');
三、js手寫bind:隱式綁定改變this
Function.prototype.custombind = function (thisArg, ...args) {
// 1、獲取被調(diào)用的函數(shù)
const fn = this;
// 2、綁定this
thisArg = thisArg ? Object(thisArg) : window;
const retFunc = function (...extraArgs) {
thisArg.fn = fn;
const result = thisArg.fn(...args, ...extraArgs); // 對兩次傳入的參數(shù)進(jìn)行合并
delete thisArg.fn;
return result;
};
// 3、直接返回方法,且這個(gè)方法綁定了執(zhí)行參數(shù)
return retFunc;
};
function sum(num1, num2) {
console.log('sum被調(diào)用了', this, num1, num2, num1 + num2);
return num1 + num2;
}
sum.custombind('abc', 10)(20);
另一種手寫bind
Function.prototype.customBind = function() {
// 獲取要被執(zhí)行的函數(shù)
const fn = this;
// 獲取this
let thisArg = Array.prototype.shift.call(arguments);
thisArg = thisArg ? Object(thisArg) : window; // 必須,要保證thisArg是個(gè)Object
// 獲取參數(shù)
const args = Array.prototype.slice.call(arguments);
return function() {
const extraArgs = Array.prototype.slice.call(arguments);
// 綁定this
thisArg.fn = fn;
thisArg.fn(...args, ...extraArgs);
};
}
function sum (num1, num2) {
const result = num1 + num2;
console.log('sum被調(diào)用', this, num1, num2, result);
return result;
}
sum.customBind('abc', 10)(20); // sum被調(diào)用 String{'abc', fn: ?} 10 20 30
四、認(rèn)識arguments:傳遞給函數(shù)的參數(shù)的類數(shù)組(array-like)對象
類數(shù)組對象:長的像一個(gè)數(shù)組,實(shí)際上是一個(gè)對象
1、擁有數(shù)組的一些特性,例如length、或可以通過index索引來訪問
2、沒有數(shù)組的一些方法,例如forEach、map等
// 認(rèn)識arguments
function sum(num1, num2, num3) {
// 打印arguments
console.log(arguments); // Arguments(3)[1, 2, 3, callee: ?, Symbol(Symbol.iterator): ?]
// 常見的對arguments的操作有三個(gè):
// 1、獲取參數(shù)長度
console.log(arguments.length); // 3
// 2、根據(jù)索引值獲取某一個(gè)參數(shù)
console.log(arguments[0]); // 1
// 3、callee獲取當(dāng)前arguments所在的函數(shù)
console.log(arguments.callee); // function sum(num1, num2, num3) {}
}
sum(1, 2, 3);
// 類數(shù)組轉(zhuǎn)數(shù)組
function sum(num1, num2, num3) {
// 方法一:自己遍歷
// const newArr = [];
// for (let i = 0; i < arguments.length; i++) {
// newArr.push(arguments[i] * 2);
// }
// return newArr;
// 方法二:slice(借助slice內(nèi)部的遍歷)
// const newArr = Array.prototype.slice.call(arguments);
// 或
// const newArr = [].slice.call(arguments);
// return newArr;
// 方法三:ES6的from方法
// const arr = Array.from(arguments);
// return arr.map(v => v * 2);
// 方法四:展開運(yùn)算符
const arr = [...arguments];
return arr.map(v => v * 2);
}
sum(1, 2, 3);
// 箭頭函數(shù)中沒有arguments,會(huì)去上層作用域找
const hello = () => {
// console.log(arguments); // arguments is not defined
};
const hi = function () {
const fn = () => {
console.log(arguments); // Arguments[123, callee: ?, Symbol(Symbol.iterator): ?]
};
return fn();
};
hello();
hi(123);
五、bind的一些面試題
???????bind()方法主要就是將函數(shù)綁定到某個(gè)對象,bind()會(huì)創(chuàng)建一個(gè)函數(shù),函數(shù)體內(nèi)的this對象的值會(huì)被綁定到傳入bind()中的第一個(gè)參數(shù)的值,例如:f.bind(obj),實(shí)際上可以理解為obj.f(),這時(shí)f函數(shù)體內(nèi)的this自然指向的是obj;
var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func();
},
c: 'hello'
}
a.b(); // undefined 這里的this指向的是全局作用域
console.log(a.c); // hello
var a = {
b: function() {
var _this = this; // 通過賦值的方式將this賦值給that
var func = function() {
console.log(_this.c);
}
func();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
// 使用bind方法一
var a = {
b: function() {
var func = function() {
console.log(this.c);
}.bind(this);
func();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
// 使用bind方法二
var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func.bind(this)();
},
c: 'hello'
}
a.b(); // hello
console.log(a.c); // hello
// 分析:這里的bind方法會(huì)把它的第一個(gè)實(shí)參綁定給f函數(shù)體內(nèi)的this,所以里的this即指向{x:1}對象;
// 從第二個(gè)參數(shù)起,會(huì)依次傳遞給原始函數(shù),這里的第二個(gè)參數(shù)2即是f函數(shù)的y參數(shù);
// 最后調(diào)用m(3)的時(shí)候,這里的3便是最后一個(gè)參數(shù)z了,所以執(zhí)行結(jié)果為1+2+3=6
// 分步處理參數(shù)的過程其實(shí)是一個(gè)典型的函數(shù)柯里化的過程(Curry)
function f(y,z){
return this.x+y+z;
}
var m = f.bind({x:1},2);
console.log(m(3)); // 6
// 分析:直接調(diào)用a的話,this指向的是global或window對象,所以會(huì)報(bào)錯(cuò);
// 通過bind或者call方式綁定this至document對象即可正常調(diào)用
var a = document.write;
a('hello'); // error
a.bind(document)('hello'); // hello
a.call(document,'hello'); // hello
// 實(shí)現(xiàn)預(yù)定義參數(shù)
// 分析:Array.prototype.slice.call(arguments)是用來將參數(shù)由類數(shù)組轉(zhuǎn)換為真正的數(shù)組;
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1,2,3]
// 第一個(gè)參數(shù)undefined表示this的指向,第二個(gè)參數(shù)10即表示list中真正的第一個(gè)參數(shù),依次類推
var a = list.bind(undefined, 10);
var list2 = a(); // [10]
var list3 = a(1, 2, 3); // [10,1,2,3]