不少人都知道這三種方法怎用,可是都不知道他們的原理javascript
call() 方法調用一個函數,其具備一個指定的this值和分別地提供的參數(參數的列表)。
複製代碼
let name='憨蛋';
let person={
name:'romin'
}
function say(){
console.log('my name is'+this.name)
}
say.call(person);
// my name is romin
複製代碼
cal
l 改變了say
函數的this
指向,同時say
函數執行。java
那麼咱們就來實現它數組
say
函數放進 person
對象中便可。person
中的 say
函數let person={
name:'romin',
say:function(){
console.log(this.name)
}
}
person.say();
複製代碼
因爲上面的方法改變了 person
對象的結構,須要須要多餘的 say
函數刪除掉,要執行 delete person.say
的操做。bash
這時候,咱們已經明確了該作的事情:app
一、把待執行的函數(call
前面的函數)放入指定的對象(call
的第一個參數)中;函數
二、執行該函數;ui
三、刪除對象中添加的函數;this
Function.prototype.call = function(context){
// 第一步
//這裏的this就是代調用的函數
context.fn = this;
// 第二步
context.fn();
// 第三步
delete context.fn;
}
複製代碼
call
中的第一個參數也多是null
,也多是字符串,咱們須要對 context 進行處理,不然 字符串上無法掛載 函數 fn
。spa
// 對context 作處理
context = context?Object(context):window;
複製代碼
以上面的爲例,call
中可能會有多個參數,除了context
,剩餘的參數都要交給 say
來處理的。prototype
let name='憨蛋';
let person={
name:'romin'
}
function say(animal,age){
console.log(animal)
console.log(age)
console.log('my name is '+this.name+',我屬'+animal);
}
say.call(person,'🐯',15);
// my name is romin,我屬🐯
複製代碼
call
裏的參數該怎麼交給 say
,而且讓它執行呢?使用 arguments
的話,它是類數組,和 call
的要求不一致,call
要求 一個個傳參的。 拼出一個參數字符串來。
let args = [];
for(let i=1;i<arguments.length;i++){
args.push('arguments['+i+']')
}
// ['arguments[1]','arguments[2]']
// args 和 字符串拼接,調用了它的 toString() ,至關於 context.fn(arguments[1],arguments[1])
eval('context.fn('+ args +')')
複製代碼
完整的實現:
Function.prototype.call = function(context){
// 對context 作處理
context = context?Object(context):window;
// 第一步,掛載函數
//這裏的this就是代調用的函數
context.fn = this;
// 第二步,準備參數,而後讓函數執行
let args = [];
for(let i=1;i<arguments.length;i++){
args.push('arguments['+i+']')
}
let result = eval('context.fn('+ args +')')
// 第三步
delete context.fn;
// 注意可能有返回值
return result;
}
複製代碼
apply() 方法調用一個具備給定this值的函數,以及做爲一個數組(或相似數組對象)提供的參數。
複製代碼
apply
和 call
相似,只是傳參的不一樣而已,能夠直接拿上面的代碼進行改造
Function.prototype.apply = function(context,arr){
// 對context 作處理
context = context?Object(context):window;
// 第一步,掛載函數
//這裏的this就是代調用的函數
context.fn = this;
let result;
if(!arr){
result = context.fn();
}else{
let args = [];
for(let i=0;i<arr.length;i++){
args.push('arr['+i+']')
}
result = eval('context.fn('+ args +')')
}
// 第三步
delete context.fn;
// 注意可能有返回值
return result;
}
複製代碼
`bind()`方法建立一個新的函數,在調用時設置`this`關鍵字爲提供的值。並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。
複製代碼
特色:改變 this
指向,同時返回一個函數(高階函數),能夠傳入參數;
let name='憨蛋';
let person={
name:'romin'
}
function say(animal,age){
console.log(animal)
console.log(age)
console.log('my name is '+this.name+',我屬'+animal);
}
let rominsay = say.bind(person,'🐯');
rominsay(15);
複製代碼
它的模擬實現,若是不帶參數列表:
Function.prototype.bind = function(context){
let that = this;
return function(){
// 一樣要注意可能有返回值
return that.apply(context);
}
}
複製代碼
第二版,若是有參數:
Function.prototype.bind = function(context){
let that = this;
// 取到參數列表
let bindArgs = Array.prototype.slice.call(arguments,1);
return function(){
// 取到調用時候的參數列表
let args = Array.prototype.slice.call(arguments);
// 一樣要注意可能有返回值
return that.apply(context,bindArgs.concat(args));
}
}
複製代碼
接下來請看,下面的例子使用原生的 bind
方法:
let person={
name:'romin',
gender:'男',
}
function Say(name,age){
this.habit = '上班划水';
console.log(this.gender);
console.log(name);
console.log(age);
}
Say.prototype.friend = '憨蛋';
let RominSay = Say.bind(person,'romin');
let say = new RominSay('15');
console.log(say.friend) // 憨蛋
console.log(say.gender);// undefined
console.log(say.habit)// 上班划水
複製代碼
若是使用本身實現的方法,那麼結果是
console.log(say.friend) // undefined
console.log(say.gender);// undefined
console.log(say.habit)// undefined
複製代碼
那麼就會有兩個疑惑:
gender
失效了friend
屬性沒有繼承成功;habit
也沒有取到;可是請注意下面的一個問題:
若是被綁定的函數被new ,那麼 返回的函數中的this 是當前函數的實例
複製代碼
套用上面的話,RominSay
被 new
出了一個 say
, 那麼 this
就是 當前 RominSay
的實例say
(而不該該是 person
了),那麼就能經過原型鏈找到 friend
屬性,
對上面的實現方法進行改造
Function.prototype.bind = function(context){
let that = this;
// 取到參數列表
let bindArgs = Array.prototype.slice.call(arguments,1);
function newFun(){
// 取到調用時候的參數列表
let args = Array.prototype.slice.call(arguments);
//當newFun做爲構造函數時,this 指向實例,若是不是,this還指向 context
return that.apply(this instanceof newFun ?this:context,bindArgs.concat(args));
}
// 修改返回函數的 prototype 爲綁定函數的 prototype,實例就能夠繼承綁定函數的原型中的值
newFun.prototype = this.prototype;
return newFun;
}
複製代碼