原文地址:JavaScript基礎心法——call apply bindjavascript
歡迎star。java
若是有錯誤的地方歡迎指正。git
整理call
、apply
、bind
這三個方法的的知識點。github
以前這篇文章提到過this
的各類狀況,其中有一種狀況就是經過call
、apply
、bind
來將this
綁定到指定的對象上。web
也就是說,這三個方法能夠改變函數體內部this
的指向。segmentfault
這三個方法有什麼區別呢?分別適合應用在哪些場景中呢?數組
先舉個簡單的栗子 ~app
var person = { name: "axuebin", age: 25 }; function say(job){ console.log(this.name+":"+this.age+" "+job); } say.call(person,"FE"); // axuebin:25 FE say.apply(person,["FE"]); // axuebin:25 FE var sayPerson = say.bind(person,"FE"); sayPerson(); // axuebin:25 FE
對於對象person
而言,並無say
這樣一個方法,經過call
/apply
/bind
就能夠將外部的say
方法用於這個對象中,其實就是將say
內部的this
指向person
這個對象。ide
call
是屬於全部Function
的方法,也就是Function.prototype.call
。函數
The call() method calls a function with a given this value and arguments provided individually.
call() 方法調用一個函數, 其具備一個指定的this值和分別地提供的參數(參數的列表)。
它的語法是這樣的:
fun.call(thisArg[,arg1[,arg2,…]]);
其中,thisArg
就是this
指向,arg
是指定的參數。
call
的用處簡而言之就是可讓call()中的對象調用當前對象所擁有的function。
ECMAScript規範中是這樣定義call
的:
當以thisArg
和可選的arg1
,arg2
等等做爲參數在一個func
對象上調用call
方法,採用以下步驟:
IsCallable(func)
是false
, 則拋出一個TypeError
異常。argList
爲一個空列表。arg1
開始以從左到右的順序將每一個參數插入爲argList
的最後一個元素。thisArg
做爲this
值並以argList
做爲參數列表,調用func
的[[Call]]
內部方法,返回結果。call
方法的length
屬性是1。
在外面傳入的thisArg
值會修改併成爲this
值。thisArg
是undefined
或null
時它會被替換成全局對象,全部其餘值會被應用ToObject
並將結果做爲this
值,這是第三版引入的更改。
var obj = { a: 1 } function foo(b, c){ this.b = b; this.c = c; console.log(this.a + this.b + this.c); } foo.call(obj,2,3); // 6
在須要實現繼承的子類構造函數中,能夠經過call
調用父類構造函數實現繼承。
function Person(name, age){ this.name = name; this.age = age; this.say = function(){ console.log(this.name + ":" + this.age); } } function Student(name, age, job){ Person.call(this, name ,age); this.job = job; this.say = function(){ console.log(this.name + ":" + this.age + " " + this.job); } } var me = new Student("axuebin",25,"FE"); console.log(me.say()); // axuebin:25 FE
apply
也是屬於全部Function
的方法,也就是Function.prototype.apply
。
The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).
apply() 方法調用一個函數, 其具備一個指定的this值,以及做爲一個數組(或相似數組的對象)提供的參數。
它的語法是這樣的:
fun.apply(thisArg, [argsArray]);
其中,thisArg
就是this
指向,argsArray
是指定的參數數組。
經過語法就能夠看出call
和apply
的在參數上的一個區別:
call
的參數是一個列表,將每一個參數一個個列出來apply
的參數是一個數組,將每一個參數放到一個數組中當以thisArg
和argArray
爲參數在一個func
對象上調用apply
方法,採用以下步驟:
IsCallable(func)
是false
, 則拋出一個TypeError
異常 .若是argArray
是null
或undefined
, 則
thisArg
做爲this
值並以空參數列表調用func
的[[Call]]
內部方法的結果。Type(argArray)
不是Object
, 則拋出一個TypeError
異常 .len
爲以"length"
做爲參數調用argArray
的[[Get]]
內部方法的結果。n
爲ToUint32(len)
.argList
爲一個空列表 .index
爲0.只要index
<n
就重複
indexName
爲ToString(index)
.nextArg
爲以indexName
做爲參數調用argArray
的[[Get]]
內部方法的結果。nextArg
做爲最後一個元素插入到argList
裏。index
爲index + 1
.thisArg
做爲this
值並以argList
做爲參數列表,調用func
的[[Call]]
內部方法,返回結果。apply
方法的length
屬性是 2。
在外面傳入的thisArg
值會修改併成爲this
值。thisArg
是undefined
或null
時它會被替換成全局對象,全部其餘值會被應用ToObject
並將結果做爲this
值,這是第三版引入的更改。
在用法上apply
和call
同樣,就不說了。
參考連接:https://github.com/jawil/blog/issues/16
Function.prototype.myApply=function(context){ // 獲取調用`myApply`的函數自己,用this獲取 context.fn = this; // 執行這個函數 context.fn(); // 從上下文中刪除函數引用 delete context.fn; } var obj ={ name: "xb", getName: function(){ console.log(this.name); } } var me = { name: "axuebin" } obj.getName(); // xb obj.getName.myApply(me); // axuebin
確實成功地將this
指向了me
對象,而不是自己的obj
對象。
上文已經提到apply
須要接受一個參數數組,能夠是一個類數組對象,還記得獲取函數參數能夠用arguments
嗎?
Function.prototype.myApply=function(context){ // 獲取調用`myApply`的函數自己,用this獲取 context.fn = this; // 經過arguments獲取參數 var args = arguments[1]; // 執行這個函數,用ES6的...運算符將arg展開 context.fn(...args); // 從上下文中刪除函數引用 delete context.fn; } var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var me = { name: "axuebin" } obj.getName(); // xb:undefined obj.getName.myApply(me,[25]); // axuebin:25
context.fn(...arg)
是用了ES6的方法來將參數展開,若是看過上面那個連接,就知道這裏不經過...
運算符也是能夠的。
原博主經過拼接字符串,而後用eval
執行的方式將參數傳進context.fn
中:
for (var i = 0; i < args.length; i++) { fnStr += i == args.length - 1 ? args[i] : args[i] + ','; } fnStr += ')';//獲得"context.fn(arg1,arg2,arg3...)"這個字符串在,最後用eval執行 eval(fnStr); //仍是eval強大
咱們知道,當apply
的第一個參數,也就是this
的指向爲null
時,this
會指向window
。知道了這個,就簡單了~
Function.prototype.myApply=function(context){ // 獲取調用`myApply`的函數自己,用this獲取,若是context不存在,則爲window var context = context || window; context.fn = this; //獲取傳入的數組參數 var args = arguments[1]; if (args == undefined) { //沒有傳入參數直接執行 // 執行這個函數 context.fn() } else { // 執行這個函數 context.fn(...args); } // 從上下文中刪除函數引用 delete context.fn; } var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var name = "window.name"; var me = { name: "axuebin" } obj.getName(); // xb:25 obj.getName.myApply(); // window.name:undefined obj.getName.myApply(null, [25]); // window.name:25 obj.getName.myApply(me, [25]); // axuebin:25
ES6中新增了一種基礎數據類型Symbol
。
const name = Symbol(); const age = Symbol(); console.log(name === age); // false const obj = { [name]: "axuebin", [age]: 25 } console.log(obj); // {Symbol(): "axuebin", Symbol(): 25} console.log(obj[name]); // axuebin
因此咱們能夠經過Symbol
來建立一個屬性名。
var fn = Symbol(); context[fn] = this;
Function.prototype.myApply=function(context){ // 獲取調用`myApply`的函數自己,用this獲取,若是context不存在,則爲window var context = context || window; var fn = Symbol(); context[fn] = this; //獲取傳入的數組參數 var args = arguments[1]; if (args == undefined) { //沒有傳入參數直接執行 // 執行這個函數 context[fn]() } else { // 執行這個函數 context[fn](...args); } // 從上下文中刪除函數引用 delete context.fn; }
這樣就是一個完整的apply
了,咱們來測試一下:
var obj ={ name: "xb", getName: function(age){ console.log(this.name + ":" + age); } } var name = "window.name"; var me = { name: "axuebin" } obj.getName(); // xb:25 obj.getName.myApply(); // window.name:undefined obj.getName.myApply(null, [25]); // window.name:25 obj.getName.myApply(me, [25]); // axuebin:25
ok 沒啥毛病 ~
再次感謝1024大佬 ~
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
bind()方法建立一個新的函數, 當被調用時,將其this關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。
語法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
其中,thisArg
就是this
指向,arg
是指定的參數。
能夠看出,bind
會建立一個新函數(稱之爲綁定函數),原函數的一個拷貝,也就是說不會像call
和apply
那樣當即執行。
當這個綁定函數被調用時,它的this
值傳遞給bind
的一個參數,執行的參數是傳入bind
的其它參數和執行綁定函數時傳入的參數。
當咱們執行下面的代碼時,咱們但願能夠正確地輸出name
,而後現實是殘酷的
function Person(name){ this.name = name; this.say = function(){ setTimeout(function(){ console.log("hello " + this.name); },1000) } } var person = new Person("axuebin"); person.say(); //hello undefined
這裏this
運行時是指向window
的,因此this.name
是undefined
,爲何會這樣呢?看看MDN的解釋:
由setTimeout()調用的代碼運行在與所在函數徹底分離的執行環境上。這會致使,這些代碼中包含的 this 關鍵字在非嚴格模式會指向 window。
有一個常見的方法可使得正確的輸出:
function Person(name){ this.name = name; this.say = function(){ var self = this; setTimeout(function(){ console.log("hello " + self.name); },1000) } } var person = new Person("axuebin"); person.say(); //hello axuebin
沒錯,這裏咱們就能夠用到bind
了:
function Person(name){ this.name = name; this.say = function(){ setTimeout(function(){ console.log("hello " + this.name); }.bind(this),1000) } } var person = new Person("axuebin"); person.say(); //hello axuebin
Function.prototype.bind = function (oThis) { var aArgs = Array.prototype.slice.call(arguments, 1); var fToBind = this; var fNOP = function () {}; var fBound = function () { fBound.prototype = this instanceof fNOP ? new fNOP() : fBound.prototype; return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs ) } if( this.prototype ) { fNOP.prototype = this.prototype; } return fBound; }
this
指向this
指向的對象bind
是返回一個綁定函數可稍後執行,call
、apply
是當即調用call
給定參數須要將參數所有列出,apply
給定參數數組