call apply Bind ??
call 和 apply
- 介紹
- 在你指定的作用域中調用函數
- 實際上等于設置函數體內
this
的值 - 非繼承而來的方法
- 作用
- 擴充函數的作用 eg.1
- 更方便的傳遞參數 eg.2
- apply參數
- 參數1: 在其中運行函數的作用域
- 參數2: 參數數組(類數組對象)
- call參數
- 參數1: 在其中運行函數的作用域
- 參數2: 一堆參數排排站
- 區別
- 參數2不同
- 優點
- 對象不需要和方法有任何的耦合關系,我們之前經常寫例如 eg 這種,再通過
p
去調用他
- 對象不需要和方法有任何的耦合關系,我們之前經常寫例如 eg 這種,再通過
- 代碼作用舉例
/* eg */
const p = {
color: "red",
sayColor: function() {
console.log(this.color);
}
};
p.sayColor();
/* eg.1 作用1:擴充作用域 */
global.color = "red";
let o = { color: "blue" };
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
sayColor.call(o); // 'blue'
// 理解:
// 運行sayColor.call(o)的時候,函數的執行環境變了,因為辭職函數體內的this對象指向了o
/* eg.2 作用2: 更方便的傳遞參數 */
function sum(a, b) {
return a + b;
}
function callSum(c, d) {
// 因為是在全局作用域調用的,this是window(node是global)
return sum.call(this, c, d);
}
function applySum1() {
return sum.apply(this, arguments);
}
function applySum2(c, d) {
return sum.apply(this, [c, d]);
}
console.log(callSum(1, 2)); // 3
console.log(applySum1(1, 2)); // 3
console.log(applySum2(1, 2)); // 3
/* bind用法 */
- 實際應用舉例
/* 通過apply 擴展,使得Math.max可以接收數組作為參數 */
Math.max(1, 2, 3);
Math.max.apply(this, [1, 2, 3]);
bind
- bind方法創建一個新的函數,在bind方法被調用時,這個新函數的this被指定為
bind()
的第一個參數,而其余參數作為新函數的參數,供調用時使用
- 舉例
// eg1.
global.color = "red";
let o = { color: "blue" };
function sayColor() {
return this.color;
}
sayColor(); // 'red'
sayColor.bind(o)(); // 'blue'
// eg2. bind() 傳遞參數
const foo = {
value: 1
};
const demo = function(name, age) {
console.log(this.value); // 1
console.log(this.name); // undefined
console.log(name); // "nametest"
console.log(age); // 19
};
demo.bind(foo, "nametest")(19);
一個綁定函數也能使用new操作符創建對象,這種行為就像把原函數當作構造器,提供的this值被忽略,同時調用時的參數被當作模擬函數
- 也就是說,當bind返回的函數當作構造函數的時候,bind時指定的this值會失效,但傳入的參數依然生效
const foo = {
value: 1
};
function demo(name, age) {
this.job = "programmer";
console.log(this.value); // undefined
console.log(name); // "nameTest2"
console.log(age); // 19
}
demo.prototype.friend = "huahua";
const bindName = demo.bind(foo, "nameTest2");
const newDemo = new bindName(18);
console.log(newDemo.friend); // "huahua"
console.log(newDemo.job); // "programmer"
使用的new操作符之后,綁定的this已經失效,此時的this指向bindName
,
手寫bind: 使用基本類型的擴充實現bind
Function.prototype.method = function(name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
};
Function.method("bind2", function(context, ...args) {
// 這里this 指向 sayName
this.apply(context, args);
});
const person = {
name: "hong",
sayName: function() {
console.log(this.name);
}
};
global.name = "ming";
person.sayName(); // "hong"
person.sayName.bind2(null, 2); // "ming"
手寫bind: 函數柯里化實現一個bind
- 其實bind就是把this 綁定到傳入的對象上
Function.prototype.bind2 = function(context) {
// 如果使用bind的不是函數就拋出錯誤
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function() {};
var fbound = function() {
// 當作為構造函數時,this 指向實例,self 指向綁定函數,因為下面一句 `fbound.prototype = this.prototype;`,已經修改了 fbound.prototype 為 綁定函數的 prototype,此時結果為 true,當結果為 true 的時候,this 指向實例。
// 當作為普通函數時,this 指向 window,self 指向綁定函數,此時結果為 false,當結果為 false 的時候,this 指向綁定的 context。
self.apply(
this instanceof self ? this : context,
args.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
// 如果直接修改fbound 的prototype 也會直接修改函數的prototype, 這時可以使用空函數進行中轉
fbound.prototype = new fNOP();
return fbound;
};
- [《javascript高級程序設計-高級技巧》(第5章-Function類型)]
- [call,apply-MDN]
- [《JavaScript語言精粹-第四章-擴充基本類型的功能》]
- 掘金-JavaScript深入之bind的模擬實現